基于Unity ML-Agents的强化学习自行车智能体训练实践

📅 2026/8/27 5:00:03
基于Unity ML-Agents的强化学习自行车智能体训练实践
简介从规则算法的局限出发强化学习为交通仿真提供了新的思路。其核心原理是智能体通过与环境持续交互试错利用奖励信号自主学会导航、避障与群体协同策略无需手工编写复杂规则。该技术可显著提升仿真系统的真实感和泛化能力广泛应用于自动驾驶仿真、机器人导航等领域。本文基于Unity ML-Agents release 15系统讲解自行车智能体的环境搭建、观察空间设计、奖励函数迭代以及PPO超参数调优方法并深入剖析训练不收敛、奖励欺骗等常见问题的排查思路帮助开发者高效实现从单智能体控制到多车群体协同的完整训练流程。1. 为什么我放弃规则算法改用强化学习训练自行车智能体做交通仿真这行的朋友应该都有同感用传统方式模拟自行车群体行为简直是一场噩梦。自行车不像机动车它没有固定的车道约束骑行者会在机动车道边缘、人行道、路口缝隙之间穿行超车、避让、减速、刹车的行为边界极其模糊。我最早用的是基于规则的有限状态机方案给自行车设定跟驰、换道、避障、停车几个状态结果一跑起来就露馅转弯时两辆车同时往同一个空档挤或者前方出现静止障碍物时整个车群集体卡死场面一度非常尴尬。后来我把方向转到Unity ML-Agents release 15上用强化学习训练自行车智能体自主导航和避障情况才真正有了改观。这套方案的思路跟写规则完全不同不是我告诉智能体遇到行人应该减速到多少、距离多少开始刹车而是让它自己在仿真环境里不断试错通过奖励信号学会什么时候该加速、什么时候该避让、怎么在车流里找到一条安全路径。训练完成后自行车群体会表现出非常自然的涌现行为你根本不需要为每一条规则写死逻辑。本篇内容就是围绕这个项目展开的完整复盘包括环境搭建、智能体建模、奖励函数设计、训练参数调优以及我在实际训练中踩过的一堆坑。如果你打算用 Unity ML-Agents 做交通仿真、机器人导航这类任务这篇内容应该能帮你省掉至少两周的摸索时间。2. 项目目标拆解我们要的智能到底是什么2.1 三个行为层级的定义动手写代码之前我先把项目目标拆成了三个层级这也是整个训练方案设计的基础。第一层是单骑手基础控制。智能体要能在平坦路面上稳定前进不撞路缘、不原地打转掌握最基本的转向和加减速能力。这一层相当于教一个新手骑车是整个系统的地基。第二层是动态避障。场景里会出现静止障碍物比如路边停放的车辆、施工围挡和运动障碍物比如行人、其他自行车智能体需要实时感知周围环境在保持自身速度的前提下规划出绕行路径。这里的关键不只是撞不上而是要绕得流畅——不能为了躲一个行人急刹车把后面的车全堵住。第三层是群体协同。多辆自行车同时在场景中行驶各自有目标方向但共享道路空间。智能体需要学会在车流中穿梭时保持安全间距避免频繁急刹更不能把别的车逼到无路可走。这一层最难因为每个智能体都是独立决策的训练时会出现很多两个人互相让路结果堵死之类的博弈问题。2.2 为什么选ML-Agents而不是自研RL框架还有一个很多人会问的问题为什么不用现成的 Stable-Baselines3 或者自己撸一个 PPO而非要跑到 Unity ML-Agents 里做答案是仿真和训练闭环的效率差异。自行车交通仿真的核心难点是场景本身——你要有真实的道路几何、车辆碰撞体、传感器模拟、可视化调试界面。这些在 Unity 里天生就有而且 ML-Agents 直接内置了强化学习训练流程Python 端只负责跑算法Unity 端只负责跑环境两边通过通信协议交换数据省去了自己写仿真器、自己画可视化界面的功夫。对于这种需要频繁观察智能体行为的项目Unity 的 Scene 视图就是最好的调试工具。当然ML-Agents 也有它的局限性比如自定义算法集成比较麻烦网络结构修改要通过配置文件这些我在后面第5节会详细说。但总体来看对于仿真环境中训练具备空间感知能力的智能体这类任务ML-Agents 仍然是最省力的选择。3. Unity ML-Agents release 15 环境搭建与坑点3.1 版本选择和安装步骤先说版本。Unity ML-Agents 的版本迭代很快不同 release 之间的 API 差异不小网上很多教程用的还是旧版接口。我这次用的是release 15对应 Unity 版本建议 2021.3 LTS 以上。如果你用的是 Unity 6也基本兼容但个别 API 有微小改动最好先跑一遍官方示例确认。安装分三步在 Unity 中安装 ML-Agents 插件包。通过 Window - Package Manager - Add package from git URL输入https://github.com/Unity-Technologies/ml-agents.git?pathcom.unity.ml-agents并指定 release_15 分支。Python 端安装 mlagents Python 包release 15 对应的是mlagents0.30.0直接用 pip 安装pip install mlagents0.30.0。确认 Python 版本兼容性。release 15 要求 Python 3.8-3.10我用的是 3.9实测稳定。这里有个容易踩的坑如果直接pip install mlagents不带版本号默认装的是最新版接口可能跟 release 15 的 Unity 端对不上训练启动时会直接报通信协议不匹配。强烈建议 Unity 端插件版本和 Python 端包版本保持一致这是 ML-Agents 项目最常见的环境问题来源。3.2 场景构建中容易被忽略的物理细节场景搭建看起来简单——放一个地面放几辆车拉几条路——但物理参数没调好训练出来的智能体全是鬼火少年。第一个坑是摩擦系数。Unity 默认的 Physic Material 摩擦系数是 0.4 到 0.6 之间对于一般物体没问题但自行车轮胎的抓地力需求比较特殊。我测试过前轮摩擦系数设成 1.2、后轮设成 1.0配合 2.0 的轮胎侧向刚度智能体的过弯稳定性和制动表现是最自然的。摩擦太低车会在转弯时侧滑智能体学到的是不敢转弯而不是安全转弯摩擦太高刹车太灵敏又会出现频繁点头的现象。第二个坑是刚体质量和惯性张量。自行车的质量我设为 15kg含骑手重心位置放在车架中部偏下低于两个车轮的轴心连线。这个重心高度对自行车平衡至关重要。你可能觉得 15kg 有点轻但这是仿真不是真实的物理负载关键是角速度响应要跟真实自行车曲线接近。第三个坑是碰撞体形状。不要用 Box Collider 直接套整个自行车模型不然转弯时车轮会悬空碰到隐形角块出现莫名其妙的碰撞反馈。我把车身拆分成了车架、前轮、后轮三个碰撞体前轮用 Capsule后轮用 Cylinder车架用多个小 Box 拼接才保证了碰撞检测的精度。3.3 传感器选择的对比实验ML-Agents 的观察空间可以来自多种传感器Raycast射线检测、Camera视觉、RigidBody自身状态。在自行车避障场景里我做了三组对比实验。传感器方案观察空间维度训练收敛速度避障效果备注纯 Raycast12条射线12个距离值较快一般对近距离障碍有效远距离感知弱Raycast 目标方向向量15个值较快良好目标信息帮助导航Raycast 目标方向 速度向量18个值中等优秀速度感知让避障更平滑纯 Camera视觉84x84x3图像很慢依赖训练时长泛化性强但训练成本高最终我选了Raycast 目标方向 速度向量这个组合。纯视觉方案虽然泛化能力上限更高但训练时长大约是射线方案的 5 到 8 倍在自行车这类连续控制任务上性价比太低。射线方案有一个问题需要特别注意射线的检测层一定要过滤掉自己身上其他部件的碰撞体否则智能体学到的避障其实是躲自己这个问题调试了好久才发现。4. 智能体行为建模从会骑到会避让4.1 动作空间设计连续控制 vs 离散控制ML-Agents 的动作空间有两种Continuous Actions和Discrete Actions。很多人第一次做这类项目时会纠结我直接给出结论自行车控制必须用连续动作空间。原因有两点。第一自行车的转向和速度是连续量离散动作比如左转15度右转30度加速5km/h阶跃太大控制不平滑训练出来的轨迹会非常生硬。第二连续动作配合高斯噪声探索可以让智能体学到更精细的微操比如在窄缝中轻微调整方向这是离散动作很难表达的。我的动作空间设计如下动作1转向取值范围 [-1, 1]映射到前轮转角 [-30度, 30度]动作2加速/制动取值范围 [-1, 1]负数映射到制动力正数映射到驱动力这个设计的巧妙之处在于智能体只需要输出两个连续值其余的都交给底层的自行车动力学模型去处理简化了学习难度。比如动作2 输出 -0.3 时底层模型会根据当前车速计算制动力矩而不是直接让车急停。4.2 状态空间与观察向量到底给智能体看什么观察空间的构成直接决定了智能体能学到什么。我的最终观察向量是 18 维12 维12条射线探测到的障碍物距离每条射线间距 15 度覆盖车前方 165 度范围2 维目标点相对于智能体的方向用 sin 和 cos 编码避免角度跳变问题2 维当前速度向量x 和 z 方向分量1 维当前前进方向与目标方向的夹角1 维当前转向角让智能体感知自己是否已经转到位这里要特别说一下角度编码的问题。如果你直接把目标方向存成角度值 [0, 360] 度智能体会面临一个经典困境当目标方向从 359 度变到 1 度时数值变化是 358但其实只差 2 度。这种非线性会让神经网络在训练初期非常困惑。解决办法是用 sin/cos 编码把角度拆成两个连续值这个问题就消失了。另外一个容易被忽视的点速度信息非常关键。如果观察空间里没有速度智能体只知道前方有障碍但不知道自己是快是慢就很难学会速度过快时应该制动、速度较慢时应该转向这种关联。加入速度向量后避障的流畅度有了肉眼可见的提升。4.3 奖励函数设计的迭代过程奖励函数是强化学习项目里最玄学也最核心的部分。我前后迭代了四版每一版都有明确的问题和反思。第一版只给到达目标的稀疏奖励。训练了 60 万步智能体完全没学会移动因为初始策略是随机的几乎不可能偶然到达目标点奖励信号太稀疏智能体根本学不到东西。第二版加入距离惩罚。每步给予负奖励数值等于距离目标点的距离米。这次智能体学会了往目标方向走但路径非常自私——它会从任何障碍物正中间穿过去只要方向是目标点就行。原因是距离惩罚主导了决策碰撞惩罚不够威慑。第三版加入碰撞惩罚每次碰撞 -5.0同时取消距离惩罚改用目标距离减少给奖励、增加给惩罚的相对距离奖励。这一版行为合理了很多但出现了一个新问题智能体学会了原地打转。原因是只要它停在原地距离不增加也不减少每步只有小的时间惩罚 -0.01还不如乱动的期望收益大。我用一个原地不动惩罚解决了这个问题——当智能体速度低于 0.2 m/s 且持续时间超过 3 秒额外给 -1.0 的惩罚。第四版最终版在第三版基础上加入两个优化项。一是接近障碍物的平滑惩罚当射线检测距离小于 1.5m 时给出跟距离成反比的负奖励让智能体提前减速而不是临到跟前才猛打方向。二是效率奖励每步奖励 距离目标点的相对减少量 - 时间步惩罚 0.01 - 碰撞惩罚 - 近距离惩罚。这一版训练出来的行为已经非常接近真实骑手的表现看到前方路障会提前变道通过人行道时会减速观察整个运动轨迹平滑自然。最终奖励公式可以写成这样reward (distance_prev - distance_now) * 0.5 - 0.01 * time_step - 5.0 * collision - 0.5 * close_to_obstacleclose_to_obstacle项在射线距离小于 1.5m 时启用数值为1.5 - min_ray_distance鼓励智能体跟障碍物保持安全间距。5. 训练配置与超参数调优实战5.1 用YAML文件控制训练流程ML-Agents 的强项之一是不用改 Python 代码就能调整训练超参数只需要写一个 YAML 配置文件。这里是我最终使用的配置节选核心部分behaviors: BikeRider: trainer_type: ppo hyperparameters: batch_size: 2048 buffer_size: 20480 learning_rate: 0.0003 beta: 0.005 epsilon: 0.2 lambd: 0.95 num_epoch: 3 learning_rate_schedule: linear network_settings: normalize: true hidden_units: 512 num_layers: 3 activation: relu reward_signals: extrinsic: gamma: 0.99 strength: 1.0 max_steps: 20000000 time_horizon: 128 summary_freq: 10000几个关键参数的调整思路值得展开讲一下。**学习率learning_rate**为什么要设 0.0003这是 PPO 算法的黄金起始点再高容易训练震荡再低收敛太慢。我试过 0.001前 20 万步 reward 曲线疯狂抖动loss 完全不稳定降到 0.0003 之后才进入稳定收敛状态。配合learning_rate_schedule: linear训练过程中学习率会线性衰减让训练后期更稳定。**normalize 为什么必须打开**观察空间的 18 个维度数值量纲差异很大射线距离是 0 到 30 米速度是 0 到 10 m/s角度差是 -180 到 180 度。如果不做归一化神经网络会天然偏向数值大的特征距离值会主导梯度更新角度差几乎学不到。把normalize: true打开后训练收敛速度明显加快。batch_size 和 buffer_size 的比例要匹配。buffer_size 是经验池容量batch_size 是每次训练用的样本数比例通常是 10:1。我试过 buffer_size 10240、batch_size 1024 的配置训练时 loss 曲线起伏明显因为样本相关性太强改成 20480/2048 之后明显平滑了。5.2 PPO 与 SAC 的选择ML-Agents release 15 内置了 PPO 和 SAC 两种算法。很多人会问选哪个我的经验是连续控制任务优先试 PPOSAC 只在 PPO 效果不理想时再考虑。PPO 的优势是稳定、调参简单对学习率和网络结构不敏感几乎不用改默认配置就能跑出还行的结果。SAC 的优势是样本效率高理论上同样的训练步数能学到更好的策略但它对温度参数熵正则系数非常敏感调起来很耗时间。对于自行车避障这个任务SAC 在我实验中的表现并没有比 PPO 好多少但调参成本高了好几倍。最终我选了 PPO。唯一需要给 PPO 做的特殊处理是探索噪声的设置。ML-Agents 的 PPO 默认在动作空间上添加高斯噪声实现探索噪声标准差由epsilon参数控制。初始设 0.2 是合适的但如果训练后期还保持这个值智能体的动作会一直抖动训练曲线很难收敛到平坦。release 15 里可以通过exogenous_demo或者训练后直接固定策略来避免这个问题不过最省事的做法是训完 200 万步后观察曲线如果奖励已经稳定就不需要额外处理了。5.3 训练过程中的监控指标解读执行训练的命令非常简单mlagents-learn config/bikerider.yaml --run-idbike_v1 --port5004Unity 编辑器会自动连接点击 Play 后训练开始。我在训练过程中重点盯三个指标。第一个是累积奖励Cumulative Reward这是最直观的指标。正常情况是曲线先下降探索期碰撞多、惩罚大然后逐渐上升进入平台期。如果曲线一直下降不回升基本可以判定奖励函数有问题别硬等。第二个是策略损失Policy Loss。PPO 的策略损失一般在 0.01 到 0.1 之间波动是正常的。如果损失持续升高说明训练不稳定可能是学习率太大或者 batch_size 太小。第三个是熵Entropy。这个指标很多人忽略但它极其重要。熵代表策略的随机程度训练初期熵值高策略接近随机训练后期熵值应该降低但不应降为 0。如果熵值快速掉到接近 0说明策略已经变得非常死板失去了探索能力典型原因是学习率过大或神经网络容量不足。如果熵值一直不降说明智能体还在大量随机尝试需要把beta参数调小让策略更早聚焦。6. 复杂交通场景构建与群体行6.1 场景布局的三个层次训练好的单智能体只是第一步要让仿真系统真正模拟真实交通场景还需要搭好场景。我的做法是把场景拆成三个层次基础道路层包括双向两车道的机动车道、两侧非机动车道、人行道、交叉路口。道路用 Unity 的 Terrain 或者简单平面拼接关键是路缘石要用碰撞体隔离不然智能体会抄近路直接穿行人行道。静态障碍层路边停放的车辆、行道树、电线杆、施工围挡、垃圾桶。这些不需要复杂模型一个 Box Collider 加一个外观模型就行。但要注意分布密度——障碍太稀疏智能体学不到避障太密集智能体找不到路径训练时奖励长期为负根本学不出来。我测试下来平均每 20 米放 1 到 2 个静态障碍物比较合适。动态参与者层行人、机动车、其他自行车。行人和机动车用预设轨迹驱动避免增加训练难度其他自行车则是独立训练的智能体多辆同时在场。6.2 群体协同训练的两阶段方案训练多辆自行车协同的时候我踩了一个大坑直接把 10 个智能体放进同一个场景同时训练结果训练了一个星期奖励曲线纹丝不动。原因是多智能体同时探索时环境对单个智能体来说极不稳定——同一时刻 10 辆车都在动任何一个智能体都看不清楚到底是谁挡住了我。后来我改用两阶段训练方案。第一阶段单智能体基础训练。场景里只有一辆自行车学习基础控制和避障。训练到 200 万步奖励稳定后才进入第二阶段。第二阶段群体协同训练。这个阶段的关键技巧是冻结部分智能体的策略。具体操作先放 5 辆自行车其中 3 辆使用第一阶段训好的策略不参与训练只让 2 辆进入训练状态。这样环境相对稳定新训练的两个智能体可以学到在有其他车辆存在的条件下如何协同。等这 2 辆训练稳定后再增加训练数量逐步全部开放。这个方案的背后逻辑是课程学习先把任务难度拆成梯度让智能体从简单环境逐步过渡到复杂环境远比一开始就让它在高复杂度环境中盲试要高效得多。6.3 域随机化的必要性训练完成后我遇到一个泛化问题智能体在训练场景里表现完美但换一个全新的场景布局比如换一套道路结构、不同的障碍物位置表现就明显变差会频繁撞上新位置的障碍物。原因很典型过拟合训练场景。解决办法是域随机化在训练过程中随机化场景参数障碍物位置每次 reset 时随机摆放但保持密度大致一致起点和目标点随机生成自行车初始速度随机化0.5 到 2 m/s地面摩擦系数在 0.8 到 1.4 之间随机做了域随机化之后智能体的鲁棒性明显提升在新场景中的成功率从 62% 提升到了 91%。这也是强化学习项目落地时最容易被忽略的一环——很多人在训练场景里跑得欢一上真实环境就崩多半就是没做域随机化。7. 训练过程中的疑难杂症与排查思路7.1 训练不收敛时先检查奖励曲线形态训练不收敛是最常见的问题但不收敛其实可以分为好几种情况处理方式完全不同。我把我的排查经验整理成了一张排查表按优先级排序现象可能原因解决方式奖励曲线先降后升但很慢探索阶段正常网络容量不足增大 hidden_units 和 num_layers奖励一直负增长无上升趋势奖励函数设计不合理目标不可达检查奖励中的惩罚项是否压过正向奖励奖励曲线震荡剧烈学习率过大或 batch_size 过小降低学习率增加 batch_size奖励在某一水平横盘局部最优探索不足调大 epsilon或在动作空间加噪声奖励快速上升后崩掉策略更新太快破坏了已有知识降低 epsilon或减小学习率这里想强调一个容易被忽略的点奖励函数的范围。如果你的奖励值总是变化范围太大比如 -100 到 100神经网络会很难稳定学习。我初版设计时碰撞惩罚给的是 -20距离奖励最大给到 2结果训练时 loss 值波动非常大。后来把所有奖励项都规范到 [-5, 1] 范围训练稳定性立马改善。7.2 智能体学会作弊行为怎么办强化学习训练中智能体经常找到一些训练者没想到的捷径这在圈内叫 reward hacking。我在这个项目里遇到两次。第一次是智能体学会了倒车。我的动作空间里加速度为负值就是刹车但因为刹车和倒车在底层模型上没有严格区分智能体发现倒车也能接近目标因为距离惩罚是相对值后退同样可以缩小距离于是在目标点反方向时选择倒着骑。修复方案是给倒车行为加惩罚当速度方向和朝向夹角大于 90 度时每步扣 0.5 分。第二次是智能体贴着障碍物蹭过去。碰撞惩罚是 -5但近距离惩罚只有 -0.5两者差距太大智能体发现蹭着墙走虽然一直扣分但总比绕远路效率高。修复方案是把近距离惩罚从 -0.5 提高到 -3让贴墙行驶的总惩罚超过绕路的时间成本。这种问题的本质是奖励函数的局部最优陷阱智能体找到了一条不是我们期望但得分更高的策略。排查思路很简单——训练过程中多盯着智能体看发现奇怪行为认真分析它到底在优化什么然后针对性调整奖励项。7.3 从 TensorBoard 曲线反推问题mlagents-learn训练时会自动在results/bike_v1目录下生成 TensorBoard 日志用tensorboard --logdir results/bike_v1就可以可视化。我通常在遇到疑难问题时关注三张图Value Loss价值网络的损失。如果持续上升说明价值网络在追赶策略网络的变化两者不同步通常意味着整个训练过程不稳定。Entropy前文提过熵值过高或过低都值得警惕。External Reward环境奖励的均值曲线这个和 Cumulative Reward 不同它只看环境给的奖励不包含 intrinsic reward能看到更纯粹的学习信号。一个实用的诊断技巧如果价值损失和策略损失的曲线趋势相反优先调学习率如果两者趋势一致但都不稳定优先调 batch_size 和 buffer_size如果所有指标都正常但 reward 不升那基本就是奖励函数本身的问题别浪费时间调超参了。8. 项目扩展方向与我的个人体会整个项目跑通之后我做了一些后续扩展也想清楚了很多事。最值得做的是接入真实交通数据。我的场景目前是程序化随机生成的道路结构、障碍物分布、起点终点都是随机组合。但如果想让它真正模拟某个城市的交通环境可以把真实的 OpenStreetMap 数据导入 Unity把路网转成可行区域再根据真实的车流密度分布调整障碍物和自行车的生成参数。这样训练出来的智能体会更贴近实际场景的交通流特征。也可以把训练好的策略导出到 ROS。ML-Agents 支持把训练好的模型导出为 ONNX 格式Unity 通过Unity.Robotics.ROS-TCP-Connector把传感器数据发到 ROS 端再把决策结果传回来。这样仿真系统就能无缝对接真实的机器人控制框架后续如果要移植到实体自行车机器人的决策系统上路径是通的。还有一个方向是加入更多行为层级比如让智能体学会礼让行人看到斑马线附近的行人停止或者学会跟随机动车流的节奏红灯停绿灯行。这些本质上都是奖励函数设计的问题你可以把红灯停车编码成惩罚项当智能体在红灯区域内且速度大于 0 时每步扣分 1.0。但要注意这种硬性规则最好放在底层控制里不要完全交给学习否则训练成本会急剧上升。回到个人体会。做了这个项目之后我对强化学习在仿真中的应用有了更清晰的认知强化学习不是万能的它最适合的任务是规则写不清楚、但通过试错可以学会的场景——自行车群体交通行为恰恰就是这种。关键在于把问题拆解好不要期望强化学习解决所有问题把能规则化的部分灯光、碰撞体、动力学模型交给底层把真正需要智能的部分路径选择、避让策略、效率权衡留给学习这是整个项目给我最大的收获。如果你也想复现这个项目我的建议是先别急着搭复杂场景把一个自行车智能体在空场地里跑通再逐步加障碍、加交互。每加一块都先确认前面那块没有退步。这种增量式的训练方法比一次性搭好完整环境再训练要可靠得多。祝你好运。本文还有配套的精品资源点击获取