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

引用与参考

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

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

论文部分

摘要划重点:

  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)\)

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

  • \(\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}^{\text {world }}, a_{t}\right) \]

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

    \[L_{t+1}^{\text {world }}=\mathcal{T}^{\text {world }}\left(L_{t}^{\text {ego }}, L_{t}^{\text {world }}, a_{t}\right) \]

    又因为假设的原因,world仅和自身有关,所以:\(L_{t+1}^{\text {world }}=\mathcal{T}^{\text {world }}\left(L_{t}^{\text {world }}\right)\),那么从一开始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}\right)-\hat{L}_{t+\Delta}^{e g o}\right|\right] \tag{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,\theta_t,v_t)\) 来预测下一个时刻车辆的状态:\((x_{t+1},y_{t+1},\theta_{t+1},v_{t+1})\)

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

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


3.2 Action-value function

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

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

\[\begin{aligned}V\left(L_{t}^{e g o}, \hat{L}_{t}^{\text {world }}\right)=& \max _{a} Q\left(L_{t}^{e g o}, \hat{L}_{t}^{\text {world }}, a\right) \\Q\left(L_{t}^{\text {ego }}, \hat{L}_{t}^{\text {world }}, a_{t}\right)= & r\left(L_{t}^{\text {ego }}, \hat{L}_{t}^{\text {world }}, a_{t}\right)+ \gamma V\left(\mathcal{T}^{\text {ego }}\left(L_{t}^{\text {ego }}, \hat{L}_{t}^{\text {world }}, a\right), \hat{L}_{t+1}^{\text {world }}\right)\end{aligned} \]

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

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

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

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

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

实验处理

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

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

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

这里同样我们也离散化了我们的转向和油门 \(M_s \times M_t\),然后在转向或踩油门的时候,我们不进行刹车,那么当\(M_s=9,M_t=3\)。整个动作空间就是\(9\times3+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\right)\) 去监督学习VP(visuomotor policy) \(\pi(\hat I_t )\) ,所以呢 \(Q_{t}\left(\hat{L}_{t}^{e g o}, \cdot\right)\) 代表了车辆在那个状态下做出哪个动作是最优的,然后优化迭代我们直接通过期望:

\[E_{\hat{L}_{t}^{e g o}, \hat{I}_{t}}\left[\sum_{a} \pi\left(a \mid \hat{I}_{t}\right) Q_{t}\left(\hat{L}_{t}^{e g o}, a\right)+\alpha H\left(\pi\left(\cdot \mid \hat{I}_{t}\right)\right)\right] \tag{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
  • 看了代码知道了 不算是限制在地图里,而是因为读取了地图中的waypoint 来进行reward判断,就是需要沿着车道线行驶不能出界或者偏移 详情见reward的操作


4 实验部分处理

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

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

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

  3. 使用上面的数据去学习forward model,而不是直接去使用autopilot去做监督学习

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

代码运行部分

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

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

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

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

Stage 0: ego model

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

  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的脚本

    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"
    

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

    ray.init(logging_level=40, local_mode=True, log_to_driver=False)
    
    • 太绝了啊!这都是什么好东西!

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

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

    data_phase0.py

      • 错误示意:

        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, \theta\right)\) 自身车辆的状态是处于整个离散化空间的中间的;对于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的过程)

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

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

  • 首先提出这个问题的时候,我还没意识到又是一次上帝视角去判断所有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++类型只能显示显示而已)

posted @ 2021-08-03 14:51  Kin_Zhang  阅读(680)  评论(3编辑  收藏  举报