ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SMARTS+PPO baseline全流程实战:环境搭建、训练调参与踩坑记录

SMARTS+PPO baseline全流程实战:环境搭建、训练调参与踩坑记录 跑SMARTS PPO baseline这件事我自己前后折腾了将近两个周末中间踩的坑比预想的多得多。SMARTS作为自动驾驶多智能体强化学习环境很多刚接触的人第一反应是照着GitHub README装一遍然后直接开跑但真实情况是环境依赖、SUMO版本、Python版本、路径问题、环境接口封装……任何一个地方没对齐训练根本起不来。这篇文章把SMARTSPPO baseline从零到一完整跑通的全过程、核心参数、调试思路和个人踩坑记录都整理出来适合刚接触SMARTS、或者刚接触PPO想找一个真实交通仿真环境练手的人直接参考。我默认你已经会用Python、能操作命令行、对强化学习的基本概念状态、动作、奖励、策略、on-policy/off-policy有最基础的了解。如果这些还没完全熟也不影响我会尽量用大白话把关键环节讲清楚。整个流程我在Ubuntu 22.04、Python 3.8、单张RTX 3090上跑通过一遍CPU也能跑就是慢一些。1. 先搞清楚SMARTS和PPO baseline到底是什么1.1 SMARTS不是普通的仿真器SMARTS全称是Scalable Multi-Agent RL Training School由华为诺亚方舟实验室开源定位是面向自动驾驶决策和多智能体交互的大规模训练平台。它的底层套用了SUMO这个成熟的交通仿真软件在上面叠加了强化学习训练所需的gym风格接口、多智能体管理、场景分发、路网编辑等能力。和OpenAI gym仓库里的玩具环境不同SMARTS解决的问题更接近真实交通决策多辆车在同一个路口、匝道、环岛场景下互相博弈每辆车都有自己的观测和动作空间环境按固定周期步进给策略网络反馈状态和奖励。你可以用它跑单智能体比如一辆自动驾驶车辆在交通流中学会变道或通过无信号灯的环岛也可以跑多智能体让所有车辆都由策略控制。这个特性决定了它是研究自动驾驶决策、混行交通博弈、多智能体协作与竞争问题非常好用的平台。我第一次看到SMARTS的时候最大的感触是它把真实路网仿真和RL训练流水线做了比较干净的隔离。路网由SUMO负责渲染、碰撞检测、车辆动力学RL层由SMARTS负责封装观测/动作/奖励。这意味着你可以把大部分精力放在算法本身不用从头写交通模拟逻辑。1.2 为什么baseline首选PPO而不是DDPG或SAC很多人在选baseline算法时会纠结。我的建议非常直接如果只是想验证环境通不通、观测/动作封装对不对、整个训练流水线能不能跑起来PPO是成本最低的选择。PPOProximal Policy Optimization近端策略优化是一种on-policy的actor-critic算法。它通过clip操作限制新旧策略的更新幅度避免一步更新太大导致策略崩掉。相比DDPG、TD3这类off-policy算法PPO对超参数的敏感度低很多相比A2C/A3CPPO又加了重要性采样比裁剪和GAE优势估计训练稳定性明显更好。在实际工程里PPO几乎是开箱即用的代名词这也是它在SMARTS官方示例和很多自动驾驶决策论文里被作为默认baseline的原因。在SMARTS这类连续动作、高维度观测、奖励相对稀疏甚至经常延迟的场景里PPO还有一个额外优势on-policy采样天然适合仿真器这类可以随时重置和快速跑step的环境。你不像off-policy算法那样依赖一个非常大的replay buffer也不容易出现旧数据分布和环境当前状态严重不匹配的问题。所以如果你第一次跑SMARTS别胡思乱想直接PPO。1.3 跑通baseline需要准备哪些东西我这边最终跑通的环境配置如下给大家一个参照项目推荐配置我自己用的版本操作系统Ubuntu 20.04 / 22.04Ubuntu 22.04Python3.8 或 3.93.8GPU可选纯CPU也能跑小场景RTX 3090内存建议16G以上32GSUMO与SMARTS版本匹配1.14以上1.18SMARTSGitHub主分支0.7.x深度学习框架PyTorch 1.13PyTorch 2.0这里多说一句Python版本。SMARTS对Python版本有一定要求太高的版本比如3.11、3.12有时会因为依赖库不兼容直接各种报错。我自己最开始图省事用了Python 3.11结果好几个依赖编译不过去。所以如果你开始装SMARTS老老实实建一个Python 3.8的虚拟环境能省掉非常多麻烦。2. 环境安装跑通环境比跑训练更花时间2.1 SUMO安装与配置SMARTS本身不做车辆动力学和路网渲染这些事情全部委托给SUMO。安装SUMO有两种方式apt直接装稳定版或者从源码编译。我强烈建议用apt装稳定版编译装太费时间而且版本兼容性不一定要更好。Ubuntu下用apt装SUMO的命令如下sudo add-apt-repository ppa:sumo/stable sudo apt-get update sudo apt-get install sumo sumo-tools sumo-doc如果你能比较顺利地通过apt把SUMO装上多半可以跳过源码编译这步。出发点是版本别太旧1.14以上的SUMO对SMARTS的兼容性更好太老的版本在某些路网格式和车辆控制接口上会出问题。装完SUMO之后必须设置环境变量SUMO_HOME。SMARTS启动时会去找SUMO_HOME下的一些工具和数据文件如果没有这个变量后面smarts.check验证必挂。设置方法echo export SUMO_HOME/usr/share/sumo ~/.bashrc source ~/.bashrc sumo --version注意不同的SUMO安装方式SUMO_HOME指向的路径可能不一样。apt默认装在/usr/share/sumo源码编译的可能在你自己的目录。如果sumo --version能正常输出但SMARTS仍提示找不到SUMO优先检查SUMO_HOME这个变量是否指向了正确目录。这里有一个容易忽略的点SMARTS找的不是sumo可执行文件而是SUMO的home目录用来加载工具脚本和数据文件。2.2 SMARTS仓库克隆与Python依赖安装克隆SMARTS仓库并创建虚拟环境git clone https://github.com/huawei-noah/SMARTS.git cd SMARTS python3.8 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install -e .[train]这里的.[train]会安装训练相关的额外依赖包括PyTorch、Ray等。如果不加[train]很多训练脚本跑不了。我会推荐多花一点时间把依赖装全不要等到运行到一半再补。如果你在国内网络环境不好pip安装慢或超时可以临时用清华镜像加速具体做法是pip install -e .[train] -i https://pypi.tuna.tsinghua.edu.cn/simple装完之后做了一个非常关键的验证动作python -m smarts.check这个命令会检查SUMO_HOME、Python版本、依赖包、内置示例场景等是否齐备。如果所有检查项都是通过状态说明环境基本没问题了如果这里有红色FAIL后面训练的时候大概率也会在这个环节卡住。2.3 SMARTS安装踩坑清单我把这段时间遇到的最典型的几个安装问题整理成了一张速查表后面你遇到类似情况可以直接对照来看现象可能原因解决方法No module named smarts没有正确安装或当前虚拟环境没激活确认在SMARTS目录下执行pip install -e .并检查终端是否在对应虚拟环境内SUMO_HOME is not set环境变量没配置在~/.bashrc中配置export SUMO_HOME/usr/share/sumolibgdal相关编译错误系统缺少基础编译库sudo apt-get install build-essential libgdal-dev后重装Python 3.11各种奇奇怪怪的报错SMARTS部分旧依赖不支持3.11换到Python 3.8或3.9重新建虚拟环境Ray启动失败或端口占用多进程环境未正确初始化检查是否有残留ray进程ray stop --force后重试安装SMARTS的整个过程我给一个经验性的时间预期网络好的情况下从零到smarts.check全部通过大概需要30到60分钟。如果你遇到编译问题或者Python版本切换问题可能折腾两三个小时这太正常了别灰心。3. 跑通PPO baseline的完整流程3.1 代码从哪来官方示例还是自己写环境装好之后接下来就是代码。SMARTS仓库里本身带有一些算法示例包括基于PyTorch的PPO实现。不过我自己没有直接用官方那份而是参考它重新写了一个更精简的训练入口原因有两个一是官方示例和仓库版本绑定得比较紧我改场景参数时总觉得牵一发动全身二是自己写的话观测向量拼接、reward归一化、模型保存这些细节都在自己掌握中后期换算法也更方便。如果你不想一开始就自己写可以先跑通官方示例把日志和reward曲线跑出来再基于自己的理解去改代码。这里我分享一个我常用的目录骨架非常清晰smarts_ppo_baseline/ ├── scenario/ # 场景文件可复制自SMARTS官方 ├── train_ppo.py # 训练入口 ├── ppo_agent.py # PPO策略/价值网络定义 ├── buffer.py # 轨迹缓存 ├── config.py # 超参数配置 └── utils.py # 日志、模型保存等工具这样一个工程结构无论你是继续加SAC、TD3还是加真车闭环都比较容易扩展。3.2 构建SMARTS环境核心接口拆解SMARTS环境的基本使用方式和gym很接近但多了几个特殊的设置概念。最核心的是AgentInterface它定义了每个智能体的观测、动作类型以及动作步频。下面是我在代码里实际用到的环境初始化方式关键部分做了注释说明import gym from smarts.env.utils.action_space import ActionSpace from smarts.env.utils.observation_space import ObservationSpace from smarts.core.agent_interface import AgentInterface, AgentType from smarts.core.agent import AgentSpec from smarts.core.scenario import Scenario scenario_dir scenario/intersection # 换成你自己的场景路径 agent_interface AgentInterface( AgentType.StandardWithRoadCamera, # 观测向量里包含车辆自身状态、和邻居的相对位置、车道信息 # 以及一张可视范围内的道路语义图road camera max_episode_steps500, # 单条episode最大步长防止无限跑下去 ) env gym.make( smarts.env:hiway-v0, scenarios[scenario_dir], agent_interfaces{agent: agent_interface}, headlessTrue, # 关掉可视化界面训练模式下会快很多 ) obs env.reset()这里有几个地方要特别注意。scenario_dir必须指向一个包含scenario.py或map.net.xml等场景文件的有效目录。SMARTS官方仓库的scenarios目录下有很多现成场景比如环岛、交叉路口、高速匝道等建议先从简单的直路换道或者交叉路口开始。headlessTrue是训练模式的关键开关不关的话会弹出SUMO GUI界面GPU资源被大量占用训练速度会明显变慢。env.reset()返回的是dictkey是智能体IDvalue是观测。SMARTS是多智能体环境即使你只控制一辆车返回给外部的一律是dict封装。写代码的时候不要习惯性地以为返回的是单个numpy数组我第一次跑就因为这个原因报了不少错。3.3 PPO核心参数配置接下来是PPO算法部分的超参数。这一步如果照搬gym经典控制问题的参数来跑SMARTS效果大概率很差。两者的状态空间、奖励密度、问题复杂度完全不同。我调试下来比较稳的配置如下参数推荐值说明learning_rate3e-4Adam优化器的学习率再高容易震荡gamma0.99折扣因子SMARTS任务一般不是极长horizon0.99够用gae_lambda0.95GAE优势估计的平滑系数clip_epsilon0.2PPO裁剪阈值官方默认值update_epochs10每次采样后更新轮数不宜太大mini_batch_size128小批量大小显存足够可以调大num_envs8并行环境数CPU训练可降低到4或2max_grad_norm0.5梯度裁剪防止RNN/大网络梯度爆炸这里解释一个容易踩的点learning_rate不要盲目调大。SMARTS的奖励不像CartPole那样非常稠密和稳定它包含碰撞惩罚、贴近目标的速度奖励、舒适度惩罚等多个部分混合不同量级叠加后梯度的波动会非常大。默认3e-4是比较安全的起点后续如果你想缩短训练时间可以尝试前一半训练用5e-4后一半降到1e-4。3.4 启动训练与日志观察所有代码就绪后启动训练的入口大致长这样python train_ppo.py \ --scenario scenario/intersection \ --total_timesteps 1000000 \ --num_envs 8 \ --log_dir ./logs/ppo_intersection训练过程中日志目录里会生成TensorBoard文件。我习惯同时开两个终端一个跑训练一个盯曲线tensorboard --logdir ./logs/ppo_intersection判断PPO baseline是否正常跑通我的标准不是一次就达到最高分而是看三个信号训练时能看到reward曲线整体上升episode长度或者到达目标所花步数有下降趋势不出现大面积NaN或者长时间reward完全不动的死水状态。我第一次跑大概50万步的时候reward才开始比较明显地爬升前20万步几乎是一条横线。这个现象很正常SMARTS的交通场景不是简简单单推个杆子策略要先学会基本的油门刹车控制和规避意识然后才开始优化车速和变道时机。如果你跑了10万步完全没任何变化也别急着认定算法坏了先看观测和奖励有没有正确传到网络里再判断收敛问题。4. PPO在SMARTS里的调参与避坑实录4.1 为什么同样的代码我跑出来的效果就是不对很多新手跑SMARTS的时候会遇到一个共同的困惑明明照着别人的代码写reward曲线却完全不一样甚至直接发散。这个问题多半出在环境的随机性上。SMARTS每次env.reset()道路上的背景交通流车辆都是随机初始化的包括位置、速度、意图。PPO虽然对超参数不敏感但对采到的数据分布很敏感。如果你的num_envs设置得太少比如只有1个采样数据方差会非常大策略更新就会忽上忽下。解决思路有两个一是增加并行环境数量让一批数据更平均二是固定全局随机种子便于复现和调试。前一种提升样本效率后一种帮助你定位问题到底出在算法还是环境。另外reward的设计非常关键。SMARTS默认环境下如果你只给一个到达终点头部的奖励对PPO来说信号太稀疏了。我个人的经验是在reward里面加入三部分到达终点奖励、每一步接近目标的微小正向引导、碰撞/违反交规的惩罚。前提是你要能从环境返回的observation里提取出车辆当前位置和目标位置SMARTS的观测里是带这些信息的。4.2 SMARTS环境特性带来的特殊坑这一节想单独聊几个SMARTS特有的坑很多不是PPO本身的而是天真的RL教程里完全遇不到的。第一多智能体观测是dict结构。就算你只控制agent这个角色环境的step返回仍然是next_obs {agent: array(...)} reward {agent: 1.0} done {agent: False}很多新人在写PPO数据采集时直接把obs当作numpy数组concat进buffer结果要么报错要么维度全乱。可以做的统一处理是obs_array np.concatenate([obs[agent_id] for agent_id in sorted(obs.keys())])当然其实单智能体场景直接取obs[agent]就行但最好在代码里加一个断言确保智能体ID不是空的。第二done的含义比gym更复杂。SMARTS里一条episode可能因为正常到达终点结束也可能因为碰撞或超时结束。不要简单地把所有doneTrue都当作成功来算回报更不要每次都写if done: obs env.reset()就把提前终止的episode信息直接丢弃。在PPO实现里如果分不清success和failure你会看到reward曲线莫名其妙地很高但实际策略并没有好好避障因为它学会了早点撞车结束episode省得扣分这种空子。我建议单独维护一个episode_success变量把碰撞结束的episode在日志里单独统计。第三决策频率问题。SUMO本身的仿真步长通常是0.1秒但策略不需要每0.1秒做一次决策。SMARTS的AgentInterface里可以设置动作执行频率比如可以每10个仿真步决策一次也就是1秒一个动作。这个宏控low-level control设计能显著降低PPO学习难度。动作空间如果变成0到1的油门/刹车标量加方向盘转角比原始连续速度控制要容易收敛很多。4.3 性能优化怎么加速训练训练速度是交通仿真环境的老大难问题。SMARTS在CPU上跑一个episode最快也要几秒如果你的场景车流密度高可能几十秒一个episode。这种速度用单环境跑PPO效果会让人崩溃。性能优化的优先级我建议按这个顺序来第一headlessTrue关掉所有GUI渲染这能带来至少2到3倍的性能提升。第二增大num_envs用向量环境并行采样。这里我用的是SMARTS自带的多进程环境支持把多个环境实例分配到不同CPU核上GPU只需要处理模型的前向和更新。第三batch size和mini_batch size的配合。从实用角度如果GPU显存够尽量把num_envs * rollout_steps撑到4000以上再更新一次策略这样PPO的梯度估计会更稳。有一点要注意SMARTS的多进程和Ray、Python多线程之间偶尔会有冲突。如果你的并行环境数量超过CPU核心数系统会疯狂换页训练速度不升反降。我实测下来16核CPU的机器用8个并行环境效果最好超过12个反而会看到CPU占用升满但吞吐量下降。5. 常见问题与排查技巧实录5.1 我自己的问题排查顺序平时调试SMARTS PPO我遵循一个固定的排查流程效率很高先打印一次环境step的原始返回值确认观测、奖励、done三个变量的类型和shape符合预期然后跑一个完全随机的策略收集100个transition检查是否有NaN、是否有全零观测、reward是否太离谱如果随机策略没问题再换成PPO小规模训练观察前1000个transition里优势估计的均值和标准差是否正常最后才看收敛曲线。这个顺序看起来很笨但能避免一个很常见的情况你调了一星期算法最后发现只是奖励某一个分量算错了导致梯度方向完全不对。另外我强烈建议训练中途把模型周期性保存下来并且在验证脚本里用headlessFalse打开可视化界面实际看车是怎么开的。很多在曲线里看不出来的问题一看画面就明白了。比如车会不会原地打转、会不会离前车只剩半米才猛刹车、会不会在T字路口不停画圈。RL训练本质上是在试错可视化验证是最直观的反馈。5.2 代码运行期问题速查表我把跑训练时最容易碰到的几个问题整理成了速查表现象可能原因排查方法 / 解决方案训练刚开始就报KeyError: agent环境返回的agent ID和你在代码里写死的不一致先打印env.reset()返回的dict的keysreward一直为0奖励函数还没有把环境提供的字段correctly映射查看SMARTS的reward定义检查env.step返回的rewards字典也可以在环境里打开debug日志某个并行环境卡死多进程采样中某个环境异常退出适当降低num_envs或增加timeout等待时间显存溢出mini_batch_size太大或网络层数太深减小batch size或改用更大的batch但降低update的epoch数训练loss正常但reward不涨奖励信号确实太稀疏或者环境的随机性太强先改成简单场景验证再逐步增加交通流密度同一个场景多次训练结果差异巨大随机种子/环境初始条件方差大训练前固定env.seed()和np.random.seed()并记录每个seed的结果取平均上面表格里的每一条我基本都真实遇到过。其中多个并行环境中的某个卡死是最让人头疼的很难定位是哪个子进程的问题。后来我采取的办法是给每个env的采样循环加超时检查和异常日志一旦某个子进程超过N秒没有返回就强制杀掉并重新创建这个环境。5.3 提高成功率的小技巧汇总最后分享几个能明显提高baseline成功率的小窍门。场景选择上不要一上来就挑战人车混行十字路口这种高交互场景。SMARTS官方仓库里的场景难度差距非常大。我建议第一个训练场景选直道或者单一弯道先把整个训练流程跑通再切换到交叉路口、换道等场景。很多人在这一步就放弃了其实不是因为算法不行而是起点太难了。奖励设计上把数值scale到合理范围。如果碰撞惩罚是-1000而正常驾驶奖励是0.1那么PPO网络最开始的梯度几乎全被碰撞样本带偏训练会非常不稳定。我的经验是让总奖励的量级大概在[-1, 10]之间其中成功到达终点给10碰撞给-1每个step给一个很小的进度奖励。这样网络能够比较好地学习。训练时长上不建议每次都追求几百万step。先用20到30万step小规模验证能不能看到reward涨再决定要不要加长训练。这样可以快速迭代reward设计和网络结构。6. 拿SMARTS跑PPO最终我的一些体会整个过程折腾下来我的最大体会是SMARTS和PPO结合最大的门槛其实不是算法而是环境理解和数据流水线。PPO本身是我非常熟悉的算法一两百行就能搭出来SMARTS的接口虽然也是gym风格但多智能体dict结构、done的区分、场景随机性、SUMO带来的性能瓶颈这些坑远比印象中要多。如果你正准备跑这个baseline我的建议是先花时间把smarts.check这关彻底跑通再花时间把一次env.step的输入输出彻底搞明白最后再开始调PPO。这三个步骤任何一个跳过去后面都会加倍还回来。我自己最开始就是因为头太铁直接跳过环境验证去跑训练结果连续两天都在查各种诡异报错。这个系列既然用了(一)做标题我后续是会继续写下去的。接下来的计划是把PPO在SMARTS里的多智能体版本做一遍对比然后试试SAC、TD3这类off-policy算法在同一场景下的表现差异也会整理一下如何自定义路网和交通流密度。如果大家在复现这个baseline时遇到这个系列里没写到的坑欢迎多交流毕竟这种环境算法组合的可复现性很多时候真的就差那么一个参数、一个路径、一个环境变量。
返回列表