ML-Agents 这个项目我前前后后折腾了不少时间,从最开始的 0.8 一直用到 1.9.1,中间踩过的坑能写满一整个记事本。这篇内容就围绕“创建一个新的训练环境”展开,以官方文档为主线,结合我自己做过的几个模拟项目,把从空白项目到跑通训练的全流程拆开讲清楚。主要面向两类人:一类是刚接触强化学习、想在 Unity 里快速验证想法的初学者;另一类是被版本兼容、观察设置、奖励设计折磨到头秃的实践者。版本锁定在 ML-Agents 1.9.1 和 Unity 2018-2020,这套配置是目前我试下来最稳的组合之一。
很多新手拿到官方文档第一反应是照着 Example 环境改,这路子没错,但如果你不知道每个组件为什么存在,改了也是白改。本文会从环境结构、代码实现、训练配置、调参排错四个方面逐步走一遍,最后附上我在实际项目中遇到的高频问题。我会尽量说人话,每个配置都会解释背后的原因,而不是单纯让你照着抄。
1. 新训练环境的核心结构拆解
1.1 一个训练环境到底由哪些部分组成
先说结论:一个完整的 ML-Agents 训练环境,本质上就是一场不断重播的“剧本杀”。场景里的所有物体是舞台,Agent 是演员,Academy(现在叫 Behavior,抱歉这里有点绕)是导演,Reward 是观众反馈,而训练配置则决定了演员的表演风格。
在 1.9.1 版本中,你需要的核心层级是这样:
- Unity 场景:包含地面、光源、目标、障碍物等所有静态或动态物体。
- Agent 对象:挂载 Agent 脚本的子物体,负责感知环境、做出决策、接收奖励。
- Behavior Parameters 组件:替代了旧版的 Brain,定义了这个 Agent 如何决策——是外部隔一段时间给一次指令(决策频率),还是自己按照固定步数思考。
- 决策脚本:继承自
Agent类的 C# 代码,包含OnEpisodeBegin()、CollectObservations()、OnActionReceived()、Heuristic()四个关键方法。 - Python 端:通过
mlagents-learn命令加载训练配置和场景名称,启动与 Unity 的通信。 - 训练配置文件(YAML):定义强化学习算法(PPO、SAC)的超参数、网络结构、奖励信号等。
对初学者来说,最容易混淆的是把“Agent 对象的脚本”和“训练配置”混为一谈。实际上,Agent 脚本只负责与环境交互的接口,真正的学习发生在 Python 端的神经网络里。Unity 这边只是不断产生数据流,通过 socket 发给 Python,Python 算完再把动作指令传回来。
1.2 为什么我推荐从官方环境改造而不是完全从零开始
官方文档反复强调要先跑通一个现成环境,不是没有道理的。从零开始建环境,你需要同时处理场景物理、相机设置、脚本逻辑、训练配置四件事,任何一个环节出错,训练都不收敛。而你根本分不清是代码 bug 还是超参问题。
我自己经历过最痛苦的一次:一个简单的“方块找目标”任务,Agent 怎么都学不会,后来发现是场景中阴影和光照影响了视觉观察,加上奖励给的太稀疏,双重原因叠加。这种问题如果你用官方示例先跑通基准,再逐步替换,就能很快定位。
所以我的建议是:先拷贝一个官方示例环境(比如 3DBall 或 PushBlock),把场景内容替换成你自己的玩法,保留原来的框架,然后一点一点把你不需要的部分删掉。等到项目成功跑通一次训练,再考虑完全从空白场景构建。这个顺序能少走 80% 的弯路。
2. 从空白场景创建新环境的完整实操流程
2.1 环境准备工作:Package 版本与 Python 版本匹配
这一步是地基,很多人卡在最开始就是这个原因。ML-Agents 1.9.1 对应的 Unity 包版本是 Release 9,Python 包版本是mlagents==0.28.0(我试过 0.30.0 也能跑,但 0.28 最稳)。Unity 2018 到 2020 版本都可以用,但需要留意:
- Unity 2019.4 LTS 搭配 1.9.1 是我最推荐的组合,测试覆盖最多。
- Unity 2020 以上需要把包从
com.unity.ml-agents路径导入,同时确认scopedRegistries是否正确。
具体安装步骤不展开了,简单列一下:
- 创建一个 3D 项目(建议用内置渲染管线,不要用 URP,否则感知传感器会多一层色彩空间的坑)。
- 从官方 GitHub 下载 releases 对应的 unity package,或者通过 Package Manager 添加。
- 添加
ml-agents、ml-agents-extensions(如果有需要)到项目。 - 在 Python 环境(建议用 conda 独立环境)安装全套依赖:
pip install mlagents==0.28.0 torch==1.10.2 -f ...(具体 wheel 请根据你的 CUDA 版本选择)。
这里有一个官方文档里没细说但特别重要的点:Jupyter Notebook 里提示No module named 'mlagents_envs'时,往往是你pip install mlagents时把基础包装好了,但是导入了错误的环境。正确姿势是在项目的 Python 环境里运行python -m mlagents_envs,而不是直接import mlagents_envs。
2.2 场景搭建与 Agent 对象的设计
场景逻辑决定了任务的可学性。以最经典的“方块寻找目标”任务为例,你需要:
- 地面:一个足够大的 Plane,否则 Agent 走走就掉出边界。
- 目标:一个可被视觉识别的小方块,随机位置生成。
- Agent 主体:一个 Capsule 或 Cube,挂上 Rigidbody(注意冻结旋转,只保留 xz 移动)。
- 墙体或 Boundary:用一个 invisible wall 把有效区域圈起来,避免 Agent 逃逸。
在层级结构中,建议单独建一个空物体叫Environment,下面挂EnvController脚本,负责在每轮训练开始前随机摆放目标位置。这比让 Agent 自己控制目标要清晰,因为 Agent 脚本只关心自身的观测和行动,不关心全局布局。
Agent 对象上需要挂两个组件:Agent脚本(自己写)和Behavior Parameters。Behavior Parameters 里,Behavior Name决定了你在训练命令行中用的名称,比如MyAgentBehavior。Vector Observation的Space Size要与你CollectObservations()中添加的数值数量严格一致,这个绝对不要写错,错一位都不行。Actions部分选择Continuous Actions还是Discrete Actions,取决于你的动作空间,下面会详细说明。
2.3 编写第一个 Agent 脚本:核心方法逐个解析
这是我的练习环境脚本,先给你看整体:
using UnityEngine; using Unity.MLAgents; using Unity.MLAgents.Sensors; using Unity.MLAgents.Actuators; public class SeekTargetAgent : Agent { public Transform target; public float moveSpeed = 2f; private Rigidbody rBody; public override void Initialize() { rBody = GetComponent<Rigidbody>(); } public override void OnEpisodeBegin() { // 重置 Agent 自己的位置和速度 rBody.angularVelocity = Vector3.zero; rBody.velocity = Vector3.zero; transform.localPosition = new Vector3(0f, 0.5f, 0f); transform.localRotation = Quaternion.identity; // 重置目标的位置到一个随机坐标 if (target != null) { target.localPosition = new Vector3(Random.Range(-8f, 8f), 0.5f, Random.Range(-8f, 8f)); } } public override void CollectObservations(VectorSensor sensor) { // 位置和方向:6 个 float sensor.AddObservation(transform.localPosition); sensor.AddObservation(transform.localRotation); // 目标相对位置:3 个 float sensor.AddObservation(target.localPosition - transform.localPosition); // Agent 自身速度:3 个 float(从 Rigidbody 获取) sensor.AddObservation(rBody.velocity); // 距离:1 个 float sensor.AddObservation(Vector3.Distance(target.localPosition, transform.localPosition)); // 总计 13 个观测变量 } public override void OnActionReceived(ActionBuffers actions) { // 连续动作:第一个控制 x 方向速度,第二个控制 z 方向速度 float moveX = actions.ContinuousActions[0]; float moveZ = actions.ContinuousActions[1]; Vector3 velocity = new Vector3(moveX, 0f, moveZ) * moveSpeed; rBody.velocity = velocity + new Vector3(0f, rBody.velocity.y, 0f); // 奖励逻辑:碰触目标给 +1 并结束回合 // 每步给一个小惩罚,迫使 Agent 尽快到达 AddReward(-0.01f); } public override void Heuristic(in ActionBuffers actionsOut) { // 手动控制测试用 var continuousActions = actionsOut.ContinuousActions; continuousActions[0] = Input.GetAxis("Horizontal"); continuousActions[1] = Input.GetAxis("Vertical"); } private void OnTriggerEnter(Collider other) { if (other.CompareTag("Target")) { AddReward(1.0f); EndEpisode(); } } }然后讲每个方法的设计思路。
OnEpisodeBegin:每一轮训练(episode)开始时调用。它做两件事:一是重置 Agent 状态,二是重置目标任务状态。如果你希望目标位置是随机但又不是每次都换,可以加一个episodeCount字段控制每隔 N 回合换一次目标,这样可以观察 Agent 对固定目标的记忆能力。
CollectObservations:这是让环境能被学习的关键。你的观测空间就是“给神经网络的输入特征”。设计观测空间时,要遵循“相对而非绝对”的直觉:绝对坐标对 Agent 没有意义,因为它不知道世界原点是哪里;相对位置才是决策依据。所以我在上面把目标相对位置单独放进去。注意,位置 + 速度 + 相对位置一共 9 个维度,再加上一个距离,神经网络的学习压力已经比较大了。实际项目中,我一般会先用一两个维度验证训练能收敛,再逐步增加特征,而不是一开始就堆几十个变量。
OnActionReceived:动作空间设计。连续动作直接使用ContinuousActions数组。如果动作是离散的,比如上下左右四个方向,就需要用DiscreteActions。但我要提醒你:对于移动类任务,连续动作通常比离散动作学到更流畅的策略,因为离散动作在转向时会形成突变,不利于平滑控制。
Heuristic:这是调参神器。训练前先用键盘手动控制 Agent,观察传感器数据是否合理、碰撞检测是否正常、环境是否太简单或太难。如果手动控制都拿不到正奖励,那你不能指望强化学习能凭空学会。官方文档也建议在实际训练前启用 Heuristic 测试。
2.4 Behavior Parameters 与决策频率设置
Behavior Parameters 组件上有几个关键参数容易忽视:
- Behavior Name:必须是一个合法的 Python 标识符,训练命令行中要完全一致。比如
SeekTargetBehavior。 - Vector Observation > Space Size:和
CollectObservations中添加的总数一致。我在上面是 13,如果你想加入射线感知,Space Size 会变成numRays * (1 + detectionLevels),这个公式很多人记错,后面我会在常见问题里讲。 - Vector Observation > Stacked Vectors:控制历史观测堆叠数量。通常设为 1,如果你希望 Agent 感知到速度或方向的变化,可以设为 3-5,但堆叠会显著增加输入维度,导致训练更慢。
- Actions > Continuous Actions / Discrete Actions:这里选择的模式必须和
OnActionReceived中读取的 action 类型匹配。你选了 Continuous,代码里用DiscreteActions读取就会报错。 - Behavior Type:默认为 Default(训练时由 Python 决定)。如果你暂时不想训练,只想在编辑器里用 Heuristic 模式测试,可以临时切换为
Heuristic Only。但务必记得训练前改回来,否则 Python 端会一直看待超时。
决策频率(Decision Frequency)在 1.9.1 版本中已经放到了 Behavior Parameters 里,不再需要单独的DecisionRequester脚本。默认值 5 意味着每隔 5 个 FixedUpdate 会请求一次决策。对于简单的控制任务,5 够用;对于需要快速反应的任务,可以设为 1,但会成倍增加训练时间。我个人经验是:除非任务对实时性非常敏感,否则尽量保持默认或稍微调高到 5-10,反而更容易学到一个稳定的策略。
3. 核心实现细节:观察、动作、奖励与代码实战
3.1 向量观察与视觉观察怎么选
ML-Agents 提供了三种感知方式:向量观察(Vector Observation)、视觉观察(Camera Sensor)、射线感知(RayPerception)。
public override void CollectObservations(VectorSensor sensor) { sensor.AddObservation(...); // 向量 }如果你的 Agent 状态是可以直接量化的(位置、速度、角度),这是最方便、训练效率也最高的方式。因为它直接将原始数值作为神经网络输入,不需要做图像卷积。很多同学认为视觉观察看起来“更像 AI”,但实际效果往往不如精心设计的向量观察,因为相机传感器要求场景光照稳定、物体颜色对比度足够高,而且训练数据量需求大得多。
视觉观察适合那些“无法用简单数字描述”的任务,比如识别物体形状、判断方向、理解地图。一个典型的配置是在 Agent 上添加Camera Sensor组件,指定分辨率和灰度/彩色模式。我建议分辨率从 64x64 开始,不要一上来就用 128x128,参数量差距是平方级的。
射线感知是介于两者之间的方案,通过向多个方向发射射线探测物体距离,各个方向的探测结果拼接成一个向量。它比相机传感器计算开销小,也比纯手动设计特征更智能。常用的RayPerceptionSensor组件需要设置射线数量、角度范围和检测层。这里有个容易搞混的点:每条射线会对每个检测层返回一个 0-1 的距离值,外加一个是否命中的标记。所以空间大小的计算公式是:射线数 * (检测层数 + 1)。如果你设了 5 条射线、2 个检测层,那么空间大小应该是5 * (2+1) = 15。我在第一次配置时就没算这个,导致空间大小不匹配,报错排查了很久。
3.2 动作空间设计:连续、离散还是混合
ML-Agents 1.9.1 支持连续动作和离散动作,且可以同时存在。
连续动作空间,每个维度在 [-1, 1] 之间。为了让 Agent 更平滑地移动,我通常会把实际速度映射到动作值,比如动作值乘以最大速度。要注意的是,不要让输出直接作为速度,尤其是 Rigidbody 的速度,那样容易产生抖动,推荐在动作到实际物理操作之间加一个平滑层或限幅。
离散动作空间,比如Branch Size分别设定每个分支的离散选项数。一个典型例子:Discrete Branches = [2, 2, 2, 2]表示有四个分支,每个分支两个选项,可以编码“前进/后退”“左转/右转”“抓取/松开”“跳跃/静止”这四个独立能力。这个结构和 MultiDiscrete 空间是完全对应的,非常实用。
混合动作的常见场景是:一个分支控制方向(连续两个维度),另一个分支控制一个开关(离散两个选项)。在代码里,连续动作和离散动作分别通过ContinuousActions和DiscreteActions读取。
3.3 奖励设计的几条铁律
奖励设计是整个强化学习里最玄学的部分。我自己总结了几条经验:
规则一:先用稀疏大奖励,再用密集小奖励。如果 Agent 能通过一次碰撞直接获得 +1 奖励并结束回合,它会更快地学会“接近目标”这个行为。如果你一开始就给每一步的接近量进行微小奖励,Agent 很可能会学到原地抖动的作弊策略来骗奖励。我前几次试过用“距离减小一点给 +0.01”,结果 Agent 学会了曲线移动——因为它发现这样可以让距离反复变化,反复拿奖励。
规则二:惩罚不要太过,否则 Agent 选择提前结束。一个常见的错误是给每一步 -0.1 的惩罚,频繁碰撞再罚 -1。这样 Agent 会学到“站着不动”是最安全的。适当的做法是:步数惩罚设为 -0.01,让 Agent 在“快速完成”和“缓慢安全”之间找到平衡。
规则三:至少给一个终止条件和正奖励。如果整个训练过程中没有一个方法触发EndEpisode()并给正奖励,学习算法就会进入死循环,永远无法确认某个行为是好是坏。你的场景一定要确保存在“成功状态”,哪怕概率很低。
规则四:使用Reward Signals设置 External 和 Curiosity(可选)。如果你发现 Agent 在前几百 episodes 里毫无进展,可以尝试加入curiosity奖励信号,它会基于预测误差给出探索奖励,帮助 Agent 在早期找到稀疏奖励。但要注意,curiosity 过强会让 Agent 变成“乱跑狂魔”。YAML 里可以这样配:
reward_signals: extrinsic: strength: 1.0 gamma: 0.99 curiosity: strength: 0.01 gamma: 0.99 encoding_size: 128 learning_rate: 0.0003gamma决定了 Agent 对未来奖励的重视程度。0.99 左右是默认,如果任务步数很多,可以适当降到 0.95,减小未来奖励的累积误差。
3.4 训练配置文件 YAML 关键参数实战
ML-Agents 1.9.1 支持 PPO 和 SAC。我的推荐是:默认用 PPO,连续动作空间可以用 SAC,但 SAC 对超参敏感,新手慎用。
一个基础 PPO 配置长这样(假设行为名是SeekTargetBehavior):
behaviors: SeekTargetBehavior: trainer_type: ppo hyperparameters: batch_size: 1024 buffer_size: 10240 learning_rate: 0.0003 beta: 0.005 epsilon: 0.2 lambd: 0.95 num_epoch: 3 learning_rate_schedule: linear network_settings: normalize: false hidden_units: 128 num_layers: 2 reward_signals: extrinsic: strength: 1.0 gamma: 0.99 max_steps: 500000 time_horizon: 64 summary_freq: 10000 keep_checkpoints: 5 checkpoint_interval: 50000 threaded: true我解释几个容易踩坑的参数:
buffer_size要远大于batch_size,且能整除batch_size,否则训练时采样会报错或效率低下。time_horizon是收集多少步经验后进行 GAE 计算。对于步数较多的任务,可适当调大到 128-256,太短会导致价值估计不够稳定。normalize: false是对输入观测做归一化。如果观测值量纲差别很大(比如位置是 10,速度是 0.1),建议设为true。但要注意,设置normalize会增加一定训练开销。max_steps表示总训练步数。500000 是最低量级,推荐至少 2000000 才可能收敛。如果想快速验证,可以先用 50000 看趋势。
启动训练的命令行我一直记着一句口诀:“配置文件、场景、run-id 三位一体”。
mlagents-learn config/ppo_SeekTarget.yaml --run-id=seek_target_1 --port=5005训练开始前,会让你按 Play 键启动 Unity 场景。按下 Play,等到控制台出现 “Start training” 的字样,就开始正常跑了。如果卡在 “Waiting for trainer” 或半天不响应,多半是 Python 版本不匹配或端口被占用。
3.5 一次完整训练运行的观察记录
我以实际跑过一次的任务为例,展示你该关注哪些输出:
- 前 20 万步:Cumulative Reward 一直徘徊在 -100 左右,Agent 基本在乱动。
- 20-40 万步:偶尔出现几次正奖励,但方差很大,TensorBoard 上的奖励曲线开始有上升势头。
- 40 万步之后:策略逐渐稳定,Agent 开始直线走向目标,碰撞后获得正奖励的频次增加。
在这个阶段,你可以在 Unity 编辑器里看到 Agent 的移动逐渐从抽搐变为平滑,这是胜利前的信号。但不要急着庆祝,去 TensorBoard 看Entropy值:如果熵早早降到 0.01 以下,而奖励还没起来,说明策略过早收敛到局部最优,需要降低beta或增大初始化噪声。我踩过这个坑,最后把beta从 0.005 调到 0.02,释放了更多探索行为,才最终学到正确策略。
4. 新环境训练中的高频问题与排查技巧
4.1 训练不收敛、奖励曲线毫无起色时怎么查
遇到这种问题先别怀疑算法,99% 是这四类原因:
奖励设计落地有问题:你写的
AddReward真的执行到了吗?我没少犯这种低级错误,比如OnTriggerEnter里没有CompareTag匹配成功,导致奖励永远加不上。建议在代码里临时写一个Debug.Log("Reward +1")看看是否触发。观测数据没有区分度:如果不同状态下观测值几乎一样,神经网络学不出决策。你可以打印观测看看,例如用
VectorSensor的原始值,在Heuristic模式下人为制造两种状态差异,对比观测数值是否明显。动作空间与物理响应不匹配:比如你让动作直接控制速度,但物理引擎阻尼过大,实际运动幅度很小。可以用
Debug.Log(rBody.velocity)看速度变化。训练配置里
max_steps太小:我试过以为 200000 步足够了,结果 Agent 还在探索期。判断的标准是:TensorBoard 里的Cumulative Reward曲线是否在稳定步入上升通道。如果是乱撞,考虑加步数。
4.2 与 Unity 版本兼容的经典坑
ML-Agents 1.9.1 官方文档特别指明支持 Unity 2018-2020,但我实测发现 2020 版本中需要额外注意插件包中的Burst和Mathematics版本,如果不一致,会出现“socket exception”或TLS Allocator报错。解决办法是在 Package Manager 中把com.unity.burst降级到 1.4.0 左右。
另一个坑是:如果用MLAgents1.9.1,但 Python 端安装了 0.30.0 或更新版本,在启动训练时会出现Unable to connect to Unity或No brain in environment。这不是玄学,是因为 1.9.1 的通信协议版本与新版 Python 包不兼容。我的建议是严格锁定 Python 包版本为 0.28.0,并创建单独的虚拟环境,避免和别的项目混用依赖。
4.3 视觉观察全黑或轮廓不对
如果你用Camera Sensor或RayPerception,最典型的故障是视觉输入全黑。原因通常是相机Target Texture没有正确设置,或在训练模式下摄像机被 UI Canvas 遮挡。检查方法:在编辑器里调成一个空场景,把相机对准目标,查看相机预览是否正常。
关于射线感知,检查Detectable Tags列表是否设置正确。默认射线会检测所有标签,但如果你使用了自定义标签,一定要在Ray Perception Sensor 3D组件的参数里添加这些标签。官方文档里提到过,但很多人会翻来覆去找不到问题在哪。
4.4 利用 Heuristic 模式快速验证环境逻辑
这是我最推荐的基本功测试法。将 Behavior Type 切换为Heuristic Only,然后在OnActionReceived中通过Input.GetAxis控制动作。以我的经验,这个模式能帮你发现:
- Agent 的物理感受器是否正常,比如碰撞、摩擦、重力。
- 目标的产生和重置是否可靠。
- 奖励是否正确累加并触发回合结束。
如果你手动控制可以轻松完成目标,但切换回训练模式学时非常慢,那说明奖励信号或观测结构还有待改进。反之,如果手动都完不成,说明任务设计有硬伤,比如目标永远生成在 Agent 无法到达的地方。
4.5 一个替代思路:从简单环境递增难度
最后分享一个我自己常用的策略:新建环境时,先造一个简化版,只保留一个目标点、没有障碍物、没有随机干扰,训练到稳定收敛,然后再逐步加入障碍物、移动目标、风力等干扰。这比直接上一个复杂任务要省心得多。因为每次只改一个变量,出了问题你能立刻知道是哪个改动引起的。
官方文档里虽然没有详细写这种工作流,但我认为它本质上符合强化学习的“课程学习”(Curriculum Learning)思想。在 ML-Agents 中可以设置lesson和Curriculum文件来动态改变难度参数,但对新手来说,手动简化环境就已经能解决 90% 的学习问题。
5. 训练启动后的实战观察与优化策略
5.1 以 TensorBoard 为核心的第一轮分析
训练开始后,不要干等着。
正确流程是立刻打开 TensorBoard:
tensorboard --logdir results/seek_target_1重点关注这几个指标:
- Cumulative Reward:回合总奖励的均值。如果持续上升,说明策略在改善;如果横盘甚至下降,优先查奖励函数。
- Value Loss / Policy Loss:两者都应在一定范围内起伏。若 Value Loss 爆炸到 1e6 级别,可能是奖励数值范围太大,需要做尺度缩放,比如所有奖励乘以 0.1。
- Entropy:代表动作分布的随机程度。初始约 1.0,逐步下降是正常的,但如果过早降到 0.01,就要怀疑探索不足。
- Policy Gradient:看到它稳定为负且绝对值减小,说明策略在优化。
我第一次跑某个任务时,只看 Cumulative Reward 一直没涨,就想改网络结构。后来看了一眼 Entropy,才发现已经归零,其实是beta太小导致探索耗尽。这类案例说明指标之间要交叉验证,不能只看一个。
5.2 环境难度调节与课程表策略
官方文档提供了一个Curriculum文件的功能,可以按阶段改变环境参数,比如目标的运动速度、障碍物数量。我个人觉得 1.9.1 的 curriculum 配置稍显笨重,但确实有效。
它的核心思想是:从最简单的配置开始训练,当 Cumulated Reward 达到一个阈值后,通过lesson自动提高环境复杂度,让 Agent 渐进式学习。配置示例:
behaviors: SeekTargetBehavior: trainer_type: ppo curriculum: measure: reward threshold: 0.8 min_lesson_length: 100 signal_smoothing: true parameters: target_move_speed: - 0.0 - 1.0 - 2.0 - 3.0在代码里,通过Academy.Instance.EnvironmentParameters读取参数并根据 lesson 改变目标移动速度。我在某个项目中靠着这个方法把一个原先学不会的“追移动目标”任务变成了稳定收敛。
但注意:curriculum 只适合奖励信号明确的场景,如果你在早期阶段连简单版都无法稳定拿正奖励,加课程表只会让训练雪上加霜。
5.3 多环境加速与训练稳定性
ML-Agents 支持在 Unity 中同时运行多个环境副本(即多个独立场景实例),可以显著加速数据采集。在 1.9.1 中,你可以通过将场景中的 Environment 做成 Prefab,然后在训练时用代码创建多个实例。但这里有个关键细节:每个实例的 Agent 都必须有不同的Behavior Parameters或独立 arena,否则数据会混在一起。
我常用的做法是,在场景中放置一个Dummy空物体,上面挂一个 Manager 脚本,在 Awake 中复制 N 个环境 Prefab。这个脚本不参与训练,只负责初始化。
5.4 训练合约之外的后处理技巧
训练完成后,模型会保存为.onnx文件。拿到模型后不要直接扔到项目中,先在Agent代码里通过Model Override加载模型,并把 Behavior Type 切换为Inference Only,在手动控制环境下观察它的表现。这里常遇到的问题是:
- 模型推理速度正常但行为不符合预期,通常是训练时观测和推理时观测不一致,比如一个用了
Random乱数,一个用了固定值。 - 模型在编辑器里跑但发布到移动端后卡顿,可以考虑把
Model Override中的Deterministic Inference选项打开,降低随机性。
最后一点切身感受:训练环境并不是建完就能一劳永逸的。你每修改一次场景物理、Agent 步长或奖励值,意味着之前训练出的模型很可能不再适用。建议把每个跑过的配置都记录下来,包括 Unity 场景版本、YAML 参数、Python 版本、训练步数,方便复现。
我自己习惯在项目目录下放一个train_log.md,每次实验记录一句话结论。时间久了你会感激这个习惯的。ML-Agents 的坑永远是那种“看似小但特别磨人”的类型,比如全黑画面、奖励不触发、版本冲突,但只要你肯花时间把每一步拆开验证,这些都能很快绕过去。如果你在创建一个新环境时卡住了,不妨回到基础:用 Heuristic 模式手动操作,确认环境反馈合理,再做智能训练。磨刀不误砍柴工。