ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Unity ML-Agents 代码解读:Agent 的 CollectObservations 与 VectorSensor 设计

Unity ML-Agents 代码解读:Agent 的 CollectObservations 与 VectorSensor 设计 1. 从一次维度对不上的训练报错说起CollectObservations 到底在传什么如果你在 Unity 里跑 ML-Agents第一次自己改CollectObservations之后大概率会撞上这么一类报错训练能启动但日志里 observation 的维度和你脑子里算的对不上或者干脆在Behavior Parameters面板上看到Vector Observation Space Size是 0策略网络拿到的输入全是空的。这个问题的根子往往不在神经网络而在 Agent 往VectorSensor里塞观测值的方式。CollectObservations(VectorSensor sensor)是 ML-Agents 里 Agent 向策略网络传递状态的核心入口。它做的事情很朴素每个决策步Agent 调用这个方法你在这个方法里把当前环境里“想让智能体看到”的信息一条一条通过sensor.AddObservation(...)加进去。这些值会被拼成一个定长浮点向量交给策略网络当输入。所以它本质上是一个“状态编码器”你写什么网络就只能看到什么你少写一个网络就瞎一块。这里有两个容易混淆的概念要拆开。一个是VectorSensor它是 ML-Agents SDK 里的一个传感器类负责收集和缓存这些浮点观测值AddObservation是它最常用的方法支持传 float、Vector2、Vector3、Quaternion 等类型内部会按分量展开。另一个是Behavior Parameters组件上的Vector Observation Space Size它必须等于你实际AddObservation出来的浮点总数否则训练时维度校验就会失败。很多人改完代码忘了同步这个数字报错就出在这。这篇会沿着官方Learning-Environment-Design-Agents文档里的三段典型代码往下拆手写AddObservation的经典写法、用[Observable]特性配合反射传感器的写法、以及分类信息用 one-hot 编码的写法。每一段我都会给出可复制的配置片段、VectorSensor 的挂载步骤以及在编辑器里怎么验证观测维度和训练日志一致。适合已经在用 Unity ML-Agents 做自定义环境、但被观测维度折磨过的开发者。2. 前置准备TaoToken 接入与 ML-Agents 环境对齐在动手改观测代码之前先把两件事理顺一是训练侧的大模型/推理服务接入二是 Unity 工程里 ML-Agents 包的版本对齐。这两件事看起来不相关但实际调试时经常互相干扰——比如你一边调观测维度一边又要确认推理服务能不能正常返回环境不干净会浪费大量时间。先说接入侧。如果你打算把训练好的策略或者调试过程中的模型对话能力接到一个统一的入口可以用 TaoToken 来做。它的 API 地址是https://taotoken.net/api官网入口在https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。需要先拿到 API Key再去控制台确认你要用的模型 ID。这一步和 Unity 本身没关系但后面验证请求、排查 401 的时候会用到。具体操作路径是这样打开官网进控制台在 API Keys 页面创建一个 Key记下来然后在模型对话页面确认你要调用的模型名称。如果你是长期做编码和 Agent 相关的工作可以看下 Coding Plan 的入口它更适合持续性的开发场景。文档在接入文档页里面有完整的请求示例。{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model_id: 你控制台里确认的模型ID }这三件套——Base URL、Key、Model ID——在后面任何一次请求验证里都要用到缺一个就会报错。特别是 Model ID很多人复制了别人的示例但没换成自己控制台里的结果一直 404 或者 model not found。再说 Unity 侧。ML-Agents 的包版本要和你的 Unity 版本匹配release_19分支对应的是比较新的 ML-Agents 版本。在 Package Manager 里确认com.unity.ml-agents已经装好然后检查你的 Agent 脚本是否继承自Agent并且正确重写了CollectObservations。如果用的是反射传感器还要额外引入Unity.MLAgents.Sensors.Reflection命名空间。一个常见的坑是工程里同时存在旧版和新版的 ML-Agents 引用导致VectorSensor类型解析到两个不同的程序集编译能过但运行时行为诡异。解决办法是清理Library和Packages缓存后重新导入。这一步做完再开始改观测代码能省掉一半的玄学问题。3. 可复制配置三种观测写法的完整片段与 VectorSensor 挂载这一节是核心把官方文档里的三种观测写法拆成可以直接抄进工程的片段并说明 VectorSensor 在编辑器里怎么挂、Behavior Parameters怎么配。3.1 经典写法手动 AddObservation这是最直观的一种。Agent 持有对场景中其他物体的引用在CollectObservations里逐条添加。using UnityEngine; using Unity.MLAgents; using Unity.MLAgents.Sensors; public class CubeBallAgent : Agent { public GameObject ball; private Rigidbody m_BallRb; public override void Initialize() { m_BallRb ball.GetComponentRigidbody(); } public override void CollectObservations(VectorSensor sensor) { // 立方体自身朝向2 个浮点 sensor.AddObservation(gameObject.transform.rotation.z); sensor.AddObservation(gameObject.transform.rotation.x); // 小球相对立方体的位置3 个浮点 sensor.AddObservation(ball.transform.position - gameObject.transform.position); // 小球速度3 个浮点 sensor.AddObservation(m_BallRb.velocity); } }数一下浮点总数2 3 3 8。所以Behavior Parameters里的Vector Observation Space Size必须填 8。AddObservation传Vector3时会自动展开成三个分量传 float 就是一个这个展开规则要记牢否则手算维度很容易错。挂载步骤把脚本挂到场景里的 Cube 上在 Inspector 里把ball字段拖上场景中的小球对象。然后给 Cube 加Behavior Parameters组件Behavior Type选DefaultVector Observation Space Size填 8。再确认 Cube 上有Decision Requester组件Decision Period按需设置一般默认 5 就行。3.2 反射写法用 [Observable] 特性如果你不想手写一堆AddObservation可以用反射传感器把属性标记成[Observable]SDK 会自动收集。using UnityEngine; using Unity.MLAgents; using Unity.MLAgents.Sensors.Reflection; public class Ball3DHardAgent : Agent { public GameObject ball; [Observable(numStackedObservations: 9)] public Vector3 PositionDelta { get { return ball.transform.position - gameObject.transform.position; } } }这里PositionDelta是一个Vector3展开是 3 个浮点numStackedObservations: 9表示把最近 9 步的值堆叠起来所以实际贡献的浮点数是 3 × 9 27。这个堆叠机制对需要历史信息的任务很有用比如判断小球运动趋势。用反射写法时Behavior Parameters的Vector Observation Space Size要填 27。注意反射传感器需要 Agent 上挂ReflectionSensorComponent或者通过Behavior Parameters的传感器配置来启用。挂载方式和经典写法类似但维度计算要把堆叠倍数乘进去这是最容易算错的地方。3.3 分类信息one-hot 编码当观测里有“当前持有哪种物品”这类离散分类信息时直接传整数会让网络误以为 0 和 2 的距离是 1 和 2 的两倍所以要用 one-hot。using UnityEngine; using Unity.MLAgents; using Unity.MLAgents.Sensors; public enum ItemType { Sword, Shield, Bow, LastItem } public class HeroAgent : Agent { public ItemType currentItem; private const int NUM_ITEM_TYPES (int)ItemType.LastItem; public override void CollectObservations(VectorSensor sensor) { sensor.AddOneHotObservation((int)currentItem, NUM_ITEM_TYPES); } }NUM_ITEM_TYPES是 3Sword、Shield、BowAddOneHotObservation会生成一个长度 3 的向量比如当前是 Shield就得到[0, 1, 0]。所以Vector Observation Space Size填 3。如果你用[Observable]标记枚举字段SDK 也会自动做 one-hot 处理[Observable] public ItemType m_CurrentItem;这种情况下维度同样是枚举成员数量。挂载时记得把m_CurrentItem在 Inspector 里设好初始值否则运行时可能是默认的 Sword。三种写法可以混用但维度要累加。比如经典写法 8 个浮点 one-hot 3 个浮点总共 11Vector Observation Space Size就填 11。混用时建议在代码里写注释标出每段贡献的维度方便后面排查。4. 验证请求与成功结果观测维度与训练日志对齐配置写完怎么确认它真的对了分两步编辑器内验证和训练日志验证。编辑器内验证选中挂了 Agent 的物体看Behavior Parameters面板。如果Vector Observation Space Size和你手算的不一致训练启动时会直接报维度错误。更稳的办法是在CollectObservations里临时加一行日志把sensor的观测数量打出来public override void CollectObservations(VectorSensor sensor) { // ... 添加观测 Debug.Log($Observation count: {sensor.ObservationSize()}); }运行场景看 Console 输出的数字是否等于你填的Vector Observation Space Size。这一步能抓出绝大多数维度错误。训练日志验证启动训练后看终端输出的 observation 相关信息。ML-Agents 在初始化时会打印每个 Behavior 的观测空间大小。如果日志里显示的大小和你配置的一致说明观测链路是通的。训练过程中如果出现The observation space size does not match之类的报错就回到上一步重新核对。如果你同时用 TaoToken 做推理验证可以发一个请求确认服务正常curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d { model: 你控制台里确认的模型ID, messages: [{role: user, content: ping}] }返回正常说明接入侧没问题可以把注意力完全放回 Unity 观测调试上。成功的结果是编辑器里ObservationSize()输出和面板一致训练日志里观测空间大小一致训练能正常跑起来不报维度错误。5. 本篇常见错排查401、维度不匹配与反射传感器失效调试观测代码时报错往往集中在几个固定位置。下面按真实报错对照排查。401 Unauthorized这个和 Unity 无关是接入侧的问题。检查你的 API Key 是否复制完整、有没有多余空格Base URL 是否是https://taotoken.net/api。如果 Key 刚创建确认控制台里它是启用状态。Model ID 写错有时也会返回类似鉴权失败的提示所以三件套要一起核对。observation space size does not match最典型的维度错误。回到CollectObservations把每个AddObservation贡献的浮点数加起来和Behavior Parameters里的数字对比。注意Vector3是 3 个、Quaternion是 4 个、AddOneHotObservation是枚举成员数量、numStackedObservations要乘上去。混用多种写法时尤其容易漏算。local proxy failed / connection refused如果你在训练时通过本地代理转发请求检查代理进程是否还在、端口是否被占用。这类错误通常出现在训练脚本尝试连接外部服务时和观测代码本身无关但会打断训练流程让人误以为是观测配置的问题。reading choices 相关报错这类错误一般出现在模型输出解析阶段说明策略网络拿到的观测虽然维度对了但数值范围异常比如出现了 NaN 或极大值。检查你的观测里有没有未初始化的变量、有没有除以零的操作。AddObservation传 NaN 不会立刻报错但会在训练几步后炸掉。反射传感器不生效用了[Observable]但维度还是 0。检查是否引入了Unity.MLAgents.Sensors.Reflection命名空间Agent 上是否挂了反射传感器组件以及属性是否是public且有get访问器。私有字段加[Observable]有时不会被收集改成 public 属性最稳。OAuth 相关报错如果你在接入侧用了需要 OAuth 的流程确认 token 没有过期。这类错误和观测调试无关但会干扰你判断问题出在哪一层建议先把接入侧调通再动 Unity。排查顺序建议先确认接入侧三件套正确再确认 Unity 观测维度最后看训练日志。一层一层来不要同时改多个地方。6. 继续往下走把观测调稳之后做什么观测维度调稳之后你会发现 ML-Agents 的调试重心会转移到奖励设计和课程学习上。但在那之前有几个小习惯能帮你少走弯路。第一把观测维度的计算写成注释放在CollectObservations上方每次改完立刻更新。第二用Debug.Log打印ObservationSize()只在调试时开训练前记得删掉否则日志会被刷爆。第三反射传感器虽然省代码但堆叠观测会让维度快速膨胀numStackedObservations不要盲目调大先从小值试起。如果你后面要把训练好的策略接到实际应用里或者需要长期跑 Agent 相关的编码任务可以看下 Coding Plan 的入口它更适合持续性的开发场景。需要验证模型对话能力时模型对话页面可以直接试。接入文档里有完整的参数说明遇到请求格式问题先去那里对照。观测这块调通之后整个训练链路就顺了。剩下的就是奖励函数和超参的反复打磨那是另一个话题了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进