【论文阅读】Learning to drive from a world on rails

引用与参考

代码地址:https://github.com/dotchen/WorldOnRails

论文地址:https://arxiv.org/abs/2105.00636

论文部分

  • [x] 已看完 写在ipad上了 正在进行中

摘要划重点:

  1. 视觉 只有视觉 → 所以关于加入激光点云的想法还是可以继续
  2. 行驶数据建立在model-based 使用车辆本身自行车模型 →
  3. world on rails的意思是:环境并不会随agent的行为而改变(很符合... Carla里的仿真了,车辆不会和你交互 只会按照自己的行程走 特别是在leaderboard测试的)
    但是这一点在现实中,并不适用,现实中有车辆交互博弈学习等 → 这也正是用学习的原因

world on rails的假设后:简化学习问题、得知这个世界的动态信息?、自己的车低维度动作、状态空间

通过之前pre-recorded驾驶数据得出自己现在这步骤的影响

  1. 在已有的数据轨迹中学习世界的模型(learn world model)
  2. 对于所有pre-recorded轨迹预估action-value
  3. 训练RV policy,获得action-value function,从而获取所有动作的影响

理论方法总括

首先我们要学习的是一个能由传感器信息做出输入,输出动作的policy (pi(I))

在训练时的轨迹序列是:( au=left{left(hat{I}_{1}, hat{L}_{1}, hat{a}_{1} ight),left(hat{I}_{2}, hat{L}_{2}, hat{a}_{2} ight), ldots ight})

  • (hat{I}_{t}) 传感器信息
  • (hat{L}_{t}) 驾驶数据,主要是自身车辆和其他参与者的位置、速度、朝向
  • (hat{a}_{t}) 做出的动作

戴帽的是从行驶数据中来的,普通的则是free or random变量

我们要利用这些驾驶数据学习的是:关于世界的forward model (mathcal{T})action-value function (Q),整体过程就是 (L_t),(a_t) 通过 (mathcal{T}) 预测得到(L_{t+1}),最后的agent对应的policy (pi(I_t))仅以传感器信息作为输入

整个算法流程 (公式请看下面定义)

3.1 Forward model分解

驾驶状态 (L_t) 和 forward model分成两个部分:

  1. 仅考虑自身车辆的控制:

    [L_{t+1}^{e g o}=mathcal{T}^{e g o}left(L_{t}^{e g o}, L_{t}^{ ext {world }}, a_{t} ight) ]

  2. 建模剩下的世界模型:

    [L_{t+1}^{ ext {world }}=mathcal{T}^{ ext {world }}left(L_{t}^{ ext {ego }}, L_{t}^{ ext {world }}, a_{t} ight) ]

    又因为假设的原因,world仅和自身有关,所以:(L_{t+1}^{ ext {world }}=mathcal{T}^{ ext {world }}left(L_{t}^{ ext {world }} ight)),那么从一开始world状态就能知道整个world的模型

由以上,就只需建模对于自身车辆的forward model,这里使用L1进行回归训练 (mathcal{T}^{e g o})

[E_{hat{L}_{t: t+T}^{e g o}, hat{a}_{t}}left[sum_{Delta=1}^{T}left|mathcal{T}^{e g o Delta}left(hat{L}_{t}^{e g o}, hat{a}_{t+Delta-1} ight)-hat{L}_{t+Delta}^{e g o} ight| ight] ag{1} ]

注意这里的自身车辆状态其实可以通过自行车模型来计算得来

实验处理

在收集到的subset轨迹上,训练自身车辆的forward model (mathcal{T}^{e g o}),收集的数据保证在整个动作空间展开,例如:

  • 转向从([-1,1]);油门从([0,1]);前两者都是均匀采样,刹车是只有({0,1})

正如前面提到的forward model (mathcal{T}^{e g o}) 是由现在的((x_t,y_t, heta_t,v_t)) 来预测下一个时刻车辆的状态:((x_{t+1},y_{t+1}, heta_{t+1},v_{t+1}))

在这里可以使用已知的自行车模型作为 (mathcal{T}^{e g o}) 的结构先验:我们仅学习车辆的(f_b,r_b);从转向(s)到轮转向(phi)的映射;油门和刹车到加速度的映射

  • [x] 这一条具体怎么做还需要看代码的实现方式,(f_b,r_b)这两者是车辆的基本参数吧?

    详情见 代码阅读,是学习学到的这两个参数


3.2 Action-value function

这里我们想要的是一个给出行驶状态和动作,返回一个动作价值函数 [所以从这里可以知道对比lbc的方法 他是把鸟瞰图,或者说激活RGB图像的方式换成了RL里面对于动作价值的概念]

这里的公式就是Bellman基本公式,强化学习书里的,关于value function和q function的区别见:https://www.zhihu.com/question/59122948/answer/1899310296

[egin{aligned}Vleft(L_{t}^{e g o}, hat{L}_{t}^{ ext {world }} ight)=& max _{a} Qleft(L_{t}^{e g o}, hat{L}_{t}^{ ext {world }}, a ight) \Qleft(L_{t}^{ ext {ego }}, hat{L}_{t}^{ ext {world }}, a_{t} ight)= & rleft(L_{t}^{ ext {ego }}, hat{L}_{t}^{ ext {world }}, a_{t} ight)+ gamma Vleft(mathcal{T}^{ ext {ego }}left(L_{t}^{ ext {ego }}, hat{L}_{t}^{ ext {world }}, a ight), hat{L}_{t+1}^{ ext {world }} ight)end{aligned} ]

其中,(hat{L}_{t}^{ ext {world }})是直接记录下来的周围环境(世界)的,不会对(Q)造成影响(是假设),所以这整个式子可以简化一下:

[V_{t}left(L_{t}^{e g o} ight)=max _{a} Q_{t}left(L_{t}^{e g o}, a ight) ag{2} ]

其中的(Q)值计算由此可得:

[Q_{t}left(L_{t}^{e g o}, a_{t} ight)= rleft(L_{t}^{e g o}, hat{L}_{t}^{ ext {world }}, a_{t} ight)+ gamma V_{t+1}left(mathcal{T}^{e g o}left(L_{t}^{e g o}, a ight) ight) ]

  • 自身车辆的状态 (L_{t}^{e g o}) 由 (位置、朝向、速度) 组成;这样对每一个 (V_{t}left(L_{t}^{e g o} ight)) 状态的价值我们都进行计算,在最后的eval过程中,线性拟合其值如果value falls between bins

实验处理

对于每个时间(t) 我们把vaule function弄成一个4D的tensor:(N_H imes N_W)是关于位置的;(N_v)是关于速度的;(N_ heta)是关于朝向的

实验中(N_H=N_W=96, N_v=4, N_ heta=5),每个点代表了物理的(frac{1}{3} imes frac{1}{3} m^{2})区域,(2m/s)的速度范围和(38°)的朝向范围

(hat{L}_{t}^{e g o}=left(x_{t}, y_{t}, v, heta ight)) 自身车辆的状态是处于整个离散化空间的中间的;对于value fall outside就直接为0

这里同样我们也离散化了我们的转向和油门 (M_s imes M_t),然后在转向或踩油门的时候,我们不进行刹车,那么当(M_s=9,M_t=3)。整个动作空间就是(9 imes3+1=28)

reward的设计如下:

规定zero-speed area:比如遇到红灯、接近其他车辆/行人等

  • +1:保持在目标车道
  • 0:如果偏离了车道,但是+1到0直接的变化不是二值,而是线性拟合smoothly penalized
  • +5:如果在zero-speed area里停下了
    但是这里zero-speed只会给一次,不会累加了,以免出现车辆直接一直停下不走了
    因为有了zero-speed区域,所以关于碰撞的惩罚也就不需要有了
  • (r_{stop}=0.01) 以避免agent为了躲避而偏离车道 → 这一点我感觉只要不压实线,虚线变道应该是可以的吧 → 可能是对于leaderboard场景的trick设置

最后是通过high-level commands[左转、右转、直走、跟随路径、change right、 change left]来计算的整个动作价值函数


3.3 Policy Distillation

然后再使用 (Q_{t}left(hat{L}_{t}^{e g o}, cdot ight)) 去监督学习VP(visuomotor policy) (pi(hat I_t )) ,所以呢 (Q_{t}left(hat{L}_{t}^{e g o}, cdot ight)) 代表了车辆在那个状态下做出哪个动作是最优的,然后优化迭代我们直接通过期望:

[E_{hat{L}_{t}^{e g o}, hat{I}_{t}}left[sum_{a} pileft(a mid hat{I}_{t} ight) Q_{t}left(hat{L}_{t}^{e g o}, a ight)+alpha Hleft(pileft(cdot mid hat{I}_{t} ight) ight) ight] ag{3} ]

其中(H) 是entropy regularizer,(alpha) 是温度超参数,这样的处理是为了拿到diverse output policy

实验处理

对于policy network的设计使用的是以RGB作为输入,ResNet34作为网络框架

  1. flatten ResNet features
  2. concatenating speed
  3. 喂到网络里去
  4. 输出动作空间的分布:categorical distribution over discretized action spaces
  • [x] 从图中应该比较明显可知:这个value map是限制在地图里的,也就是说如果换一个环境是需要重新再来的,从这一点上可能就没有lbc好?所以每个地图环境都是需要一个value map去对应的

    看了代码知道了 不算是限制在地图里,而是因为读取了地图中的waypoint 来进行reward判断,就是需要沿着车道线行驶不能出界或者偏移 详情见reward的操作

  • [ ] 关于加入value map其实有改进,针对lbc 的方法强化了正确动作的概率,类似于value map再做一层限制同样也是第二个agent的先验


4 实验部分处理

  1. 通过carla内的行为规划器 [5] Carla之全局规划follow 来收集数据 (pi_b)

    并没有使用行为规划器来进行,而是直接生成min-max的一系列动作,根据reward 选出最好的进行 做出动作

  2. 使用autopilot来添加转向的噪音

    • [ ] 这一点还需细看是怎么加进去的
  3. 使用上面的数据去学习forward model,而不是直接去使用autopilot去做监督学习

对应每个我都直接放在上面的细节中了

  • [x] 所以还是行为规划器,Q action-value只是用来帮助激活RGB的某个部分,更或者是说保存整个地图信息在这个情况下怎样做是最好的;但是并没有去改善行为规划器的东西?而是从中学习得到value值

代码运行部分

Carla版本0.9.10.1,python版本3.7,已经在Planning主机上设置好了,

①打开termnial ②输入zsh ③输入carla_wor

运行结构主要由三个大过程组成,第一过程可以直接使用文件夹内的ego_model.th来

Rails系列代码阅读,后续传上来后再一个个贴吧

Stage 0: ego model

这一过程... 我暂时不知道是干啥的 因为按道理说第一过程才是开始收集train_scenario 的才对

  • [x] 待查明,这一过程ego的作用 → 训练得知车辆运动模型 train car dynamics
  1. 首先到config.yaml文件,修改ego_data和ego_model的储存位置

    ego_data_dir: collection/phase0
    ego_model_dir: collection/phase0
    main_data_dir: collection
    main_model_dir: collection
    
  2. 启动Carla,这里是写的sh文件来启动多个Carla窗口,注意一个carla本身就需要2G显存,正常6G显存电脑建议只启动2个即可

    ./scripts/launch_carla.sh 2 2000
    # ./scripts/launch_carla.sh [NUM RUNNERS] [WORLD PORT]
    
  3. 启动收集data的脚本

    • [x] 咦?在这里就有收集数据的嘛??? → train dynamics
    python -m rails.data_phase0 --num-runners=2 --port=2000
    

    首先把 在这里我就没有运行下去了,主要是出现了pygame 然后它就自己也没报什么错误,也没保存什么文件就.. 就.. 就停止了

    未知错误示意

    大概探索了一下,从leaderboard_evaluator.py里出来的,但是因为没办法print所以不知道更具体为什么,猜测原因是ray.remote,为什么没办法print啊!!!真是的!!!

    crash_message = "Agent couldn't be set up"
    
    • [x] 奇奇怪怪 为什么issue里没有人和我遇到一样的问题

    被自己气死了!.... emmm 果然猜测是对的,改一下ray.init部分就可以显示了

    ray.init(logging_level=40, local_mode=True, log_to_driver=False)
    
    • [x] 然后就显示wandb的,查一下

      太绝了啊!这都是什么好东西!

    • [x] 有空查一下ray的东西

      查完感觉!超棒!太爽了吧!

    关于rails代码详细介绍请跳转吧,写在一起太长了,不过上面的步骤已经能让整个运行起来了:

    data_phase0.py

    • [ ] 但是运行过程中,前20个左右的frame可能是OK的,后面总是提示说,保存不了图片?但是为啥这个是和frame有关的呢?奇怪

      • 错误示意:

        Traceback (most recent call last):
          File "/home/udi/KinZhang/WorldOnRails/leaderboard/leaderboard/scenarios/scenario_manager.py", line 152, in _tick_scenario
            ego_action = self._agent()
          File "/home/udi/KinZhang/WorldOnRails/leaderboard/leaderboard/autoagents/agent_wrapper.py", line 88, in __call__
            return self._agent()
          File "/home/udi/KinZhang/WorldOnRails/leaderboard/leaderboard/autoagents/autonomous_agent.py", line 115, in __call__
            control = self.run_step(input_data, timestamp)
          File "autoagents/collector_agents/random_collector.py", line 124, in run_step
            self.flush_data()
          File "autoagents/collector_agents/random_collector.py", line 67, in flush_data
            'vid': wandb.Video(np.stack(self.rgbs).transpose((0,3,1,2)), fps=20, format='mp4')
          File "<__array_function__ internals>", line 6, in stack
          File "/home/udi/anaconda3/envs/carla_py37/lib/python3.7/site-packages/numpy/core/shape_base.py", line 423, in stack
            raise ValueError('need at least one array to stack')
        ValueError: need at least one array to stack
        
        During handling of the above exception, another exception occurred:
        
        Traceback (most recent call last):
          File "/home/udi/KinZhang/WorldOnRails/leaderboard/leaderboard/leaderboard_evaluator.py", line 365, in _load_and_run_scenario
            self.manager.run_scenario()
          File "/home/udi/KinZhang/WorldOnRails/leaderboard/leaderboard/scenarios/scenario_manager.py", line 136, in run_scenario
            self._tick_scenario(timestamp)
          File "/home/udi/KinZhang/WorldOnRails/leaderboard/leaderboard/scenarios/scenario_manager.py", line 159, in _tick_scenario
            raise AgentError(e)
        leaderboard.autoagents.agent_wrapper.AgentError: need at least one array to stack
        ======[Agent] Wallclock_time = 2021-07-08 14:50:50.458292 / 6.338849 / Sim_time = 4.550000067800283 / 0.717682718910227x
        
        Stopping the route, the agent has crashed:
        > need at least one array to stack
        
  4. 这里运行收集的数据是由随机动作得到的数据集,主要作用是用来训练车辆动力学

  5. 收集完后进行训练此收集数据

    python -m rails.train_phase0 --data-dir=[EGO data DIR]
    # 注意这里的[EGO data DIR] 需要和上面config里的ego_model_dir一致
    

Stage 1: Q-computation

Tips 前提知晓

  1. 这个阶段!真的很耗时!两块TiTan XP(和1080Ti差不多)训练10个epochs需要用4天
  2. 但是这个阶段更耗存储空间(如果要收集到作者有的数据集需要3.4TB in the lmdb format,详情见此issue:https://github.com/dotchen/WorldOnRails/issues/17#issuecomment-858148921
  3. 收集数据如果因为空间不够,一定要记得删掉未完成的那一个part不然frame无法正确读取,计算q value的脚本会报错
  4. 记得提前对 config_nocrash.yaml 文件进行修改config 和 数据路径不然... 会报错的,详情见倒数第二部分可能出现的问题
  5. 此次使用会出现CUDA报错问题,详情见倒数第二部分可能出现的问题

收集数据

完成上面的车辆参数学习后,我们就可以把我们的动作(油门、方向盘、刹车)转成速度输出了,也就是上面理论部分提到的:

(hat{L}_{t}^{e g o}=left(x_{t}, y_{t}, v, heta ight)) 自身车辆的状态是处于整个离散化空间的中间的;对于value fall outside就直接为0

这里主要是收集数据

# Open Carla
./scripts/launch_carla.sh [NUM RUNNERS] [WORLD PORT]

# 这一层设置你想要收集的数据
python -m rails.data_phase1 --scenario={train_scenario, nocrash_train_scenario} --num-runners=[NUM RUNNERS] --port=[WORLD PORT]
  1. 打开Carla

  2. 收集数据,scenario 是指在哪个环境下,比如前者就是通过leaderboard来收集数据,后者可指定route在Town01下的4个不同的训练天气

    所以这个部分总结来看:在一个位置,根据已知的min, max的动作阈值,生成一张动作表,然后通过地图(其实也算是上帝视角)来给各个动作块附上reward,然后选取最大的。reward的标准呢在这里,不得不说一句这个作者代码是真的牛掰... 硬生生看来我好久才理解了

计算Q Value

等待上面数据收集完成后,就可以关闭Carla,运行Q value label的脚本

# Q-labeling
python -m rails.data_phase2 --num-runners=[NUM RUNNERS]

代码阅读:/

运行后可以从wandb上看到一些记录的细节:

大概可以看到即使只有1W(原作者数据集的1/1000都不到 也是花了1小时才做好的Q value label的过程)

  • [x] 为什么需要forward model呢,直接拿action来训练不行吗?比如 油门、方向盘、刹车去对应出value map,而且实际输出图片也看出来是这样的 (但是估计是通过forward反映射了?)

    因为需要从油门、方向盘、刹车 → 速度 → 预测位置,最后一个预测位置也就是我们需要的state in the world

  • [x] 但是为什么不直接从定位GPS中读取呢?因为加了噪音,有误差嘛?

    我忘记这个问题为什么问出来了,但是大概是这样的,首先做出的动作,并没有实际做出,而是根据地图的上帝因素进行判断,判断的前提是拿ego_model知晓了 这个动作做出后 位置大概在哪里,然后和地图waypoint偏移什么的来进行比较

  • [x] 但是实际上在 data_phase1的时候就有计算哪个动作的reward更高,通过论文中说到的标准来确定,所以这么看来动作是离散的 问题有点大 frame是20内

    首先提出这个问题的时候,我还没意识到又是一次上帝视角去判断所有reward;第二动作确实是离散的,收集的频率为4Hz,也就是说动作为保持250ms直到下一次更新?

Stage 2: Image model training

python -m rails.train_phase2

可能会遇到的运行问题

  1. ModuleNotFoundError: No module named 'leaderboard.leaderboard_evaluator'

    原因:这是因为我下载了leaderboard后,结构是这样的leaderboard/leaderboard/code,

    解决方案:

    1. 把leaderboard复制出来就好了
    2. 或者是在原主leaderboard下加一个空的__init__.py文件即可
  2. > cuda runtime error (38) : no CUDA-capable device is detected at /opt/conda/conda-bld/pytorch_1579040055865/work/aten/src/THC/THCGeneral.cpp:50

    运行第二阶段的数据收集时,也就是这行

    python -m rails.data_phase1 --scenario={train_scenario, nocrash_train_scenario} --num-runners=[NUM RUNNERS] --port=[WORLD PORT]
    

    原因:主要错误也指明了,是找不到cuda

    解决方案:定位到在q_collector.py文件中device是没有指定哪一个的

    参考:https://github.com/pytorch/pytorch/issues/5046

    for key, value in config.items():
          setattr(self, key, value)
    os.environ["CUDA_VISIBLE_DEVICES"] = '0'
    device = torch.device('cuda')
    
    ego_model = EgoModel(1./FPS*(self.num_repeat+1)).to(device)
    
    
  3. 感觉第二阶段的bug挺多的啊... 整个config中路径都没有被读入哎 emmm

    我知道了,是我没有看清楚config.yaml,这个data_phase1读取的config是!!!! config_nocrash.yaml真是绝了,应该在readme里面说一声的

Debug小技巧

对于python来说,debug感觉上应该比C/C++轻松不少的,首先是我一开始设置了很久的launch因为这个不是直接运行一个py文件而是 -m脚本方式运行,所以launch中应该要加入其下的Python env但是我加了很久,怎么着都不行,所以就另辟蹊径走到了,直接开一个口进行监听的方式

具体参考:https://blog.csdn.net/m0_37991005/article/details/113342656

步骤:

  1. 配置launch.json文件

    其中launch文件是这样的:注意第一个Debug是我当时设置了很久的环境 也没设对,所以第二个启动时注意选择好VScode界面

    {
        // Use IntelliSense to learn about possible attributes.
        // Hover to view descriptions of existing attributes.
        // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
        "version": "0.2.0",
        "configurations": [
            {
                "name": "Debug",
                "type": "python",
                // PYTHONPATH = ${CARLA_ROOT}/PythonAPI/carla/":"${SCENARIO_RUNNER_ROOT}":"${LEADERBOARD_ROOT}
                //~/KinZhang/WorldOnRails/PythonAPI/carla/:~/KinZhang/WorldOnRails/leaderboard:~/KinZhang/WorldOnRails/scenario_runner
                // "python.pythonPath": "~/KinZhang/WorldOnRails/PythonAPI/carla/",
                "request": "launch",
                "program": "${file}",
                "console": "integratedTerminal",
                "cwd": "${fileDirname}"
                
            },
            {
                "name": "Attach Debug",
                "type": "python",
                "request": "attach",
                "connect": {
                "host": "localhost",
                "port": 5678
                }
            }
        ]
    }
    
  2. 配置后,首先运行python文件(注意看中间写的debugpy --listen 5678 --wait-for-client

    python -m debugpy --listen 5678 --wait-for-client -m rails.data_phase1 --num-runners=1 --port=2000
    
  3. 然后是进入监听状态,再去VSCode里点击:首先选择好对应的那个Python文件,然后再选择好哪一个debug设置,最后点小绿就能debug上了

  4. 调试示意图:

    同时可以到下面的debug terminal进行实时的调试测试(python可以 编译后的C/C++类型只能显示显示而已)

原文地址:https://www.cnblogs.com/kin-zhang/p/15094246.html