Coalition Delivery 失败机制分析与改进路线
一、结论摘要
事实 1:任务大多能发现
64-seed eval 中,HATT v2.0 的 discovered_ratio=1.000,HATT v3.1a 为 0.994,MAPPO latest 为 0.992。失败主要发生在发现之后。
事实 2:后期动作分布异常
hard seeds 上,HATT v2.0 全发现后的 resupply agent 占比约 0.70,delivery 只有 0.10。不少 agent 满载或有载仍反复 resupply。
事实 3:终局惩罚太晚
未完成惩罚主要在 max step 或完成时触发。对 PPO 来说,这个信号离导致失败的错误动作太远,难以稳定反推。
因此,下一步不建议优先继续调学习率、加训练轮数或换探索扇区。更直接的改进路线是: 先修动作可行性约束和无效动作代价,再评估是否需要改 HATT decoder 或增加 agent-specific action features。
二、独立评估与 trace 证据
我对 MAPPO best/latest、HATT v2.0 latest、HATT v3.1a latest 做了 64-episode deterministic eval。
原始结果见
summary.csv。
然后对 hard seeds 8, 9, 41, 43, 49, 52, 60 重新跑 trace,记录逐 step 动作类型、完成数、发现数、剩余需求、死亡数和负载。
trace 输出见
trace_analysis/。
64-seed eval 汇总
| Checkpoint | Success | Completed | Sent | Discovered | Reward | Length |
|---|---|---|---|---|---|---|
| MAPPO eval_best | 0.703 | 0.944 | 0.958 | 0.989 | 40.3 | 621.0 |
| MAPPO eval_latest | 0.797 | 0.947 | 0.953 | 0.992 | 40.8 | 610.0 |
| HATT v2.0 eval_latest | 0.109 | 0.637 | 0.668 | 1.000 | 21.3 | 716.9 |
| HATT v3.1a eval_latest | 0.547 | 0.878 | 0.910 | 0.994 | 46.0 | 619.1 |
Hard seeds 后半程动作占比
统计窗口:从全发现之后开始;若没有全发现,则从 step 300 开始。分母为 living-agent steps。
| Checkpoint | Hard seed 完成数 | Sent | Dead | Delivery | Explore | Idle | Resupply | 终局剩余需求 |
|---|---|---|---|---|---|---|---|---|
| MAPPO eval_best | 7.14 | 0.765 | 0.71 | 0.19 | 0.26 | 0.11 | 0.44 | 767 |
| MAPPO eval_latest | 7.43 | 0.776 | 1.86 | 0.45 | 0.23 | 0.00 | 0.32 | 718 |
| HATT v2.0 eval_latest | 5.14 | 0.525 | 1.29 | 0.10 | 0.20 | 0.00 | 0.70 | 1541 |
| HATT v3.1a eval_latest | 6.43 | 0.687 | 1.29 | 0.13 | 0.24 | 0.16 | 0.46 | 1011 |
代表性失败
| 模型 / seed | 现象 | 解释 |
|---|---|---|
| HATT v2.0 seed08 | 发现 10/10,完成 5/10,终局 12 个 agent 全部 resupply,平均 load=60.5,zero_load=0,剩余需求 1535。 | 不是缺货,也不是任务没发现,而是有载 agent 选择了无意义补给循环。 |
| HATT v3.1a seed52 | 发现 8/10,完成 7/10;step 400 后基本 12 个 agent 全部 resupply,终局仍有剩余需求 1035。 | 探索未收尾,同时物流链被 resupply 吸走。特殊动作比实际配送动作更容易成为稳定选择。 |
| HATT v3.1a seed43 | 完成 4/10,终局 8 个 agent idle,1 explore,0 delivery,3 dead。 | 后半程进入 idle/explore 吸引子,未形成“有货去送、没货补给”的闭环。 |
| MAPPO latest seed08 | 完成 2/10,发现 5/10,dead=7。step 100 之后 12 个 agent 全空载但仍全部 delivery,直到陆续死亡。 | 这是另一类错误吸引子:empty-load delivery forever。说明 MAPPO 虽整体更稳,但仍会在个别场景崩掉。 |
三、代码机制证据
1. 特殊动作几乎长期合法
动作空间在 env_core.py:301-305 中被编码为:
delivery tasks + explore task + idle task + resupply task。nearest 模式下只有一个 explore task。
env_core.py:548-572 中,explore、idle、resupply 都被设为 discovered=True。
但 available action mask 只做了一件事:
见 env_core.py:616-622。这意味着:
resupply永远可选,因为它 discovered 且从不 completed。idle永远可选,因为它 discovered 且从不 completed。- nearest
explore也基本永远可选,因为它不是 survey,不会把 explore task 标为 completed。 - delivery task 只有 discovered 且未 completed 时可选。
所以策略面对的是一个很宽的动作空间:大量特殊动作始终合法,但它们在不同状态下并不总是物理或任务上合理。
2. mask 确实会被策略使用,但 mask 信息不足
MAPPO 的 Categorical 在 distributions.py:64-68 中将不可用动作 logit 设为 -1e10。
HATT v3.x 在 HATTV30Network.py:448-480 中也会 reshape available actions 并 mask logits。
因此问题不是 mask 没生效,而是 mask 只表达“任务是否发现/完成”,没有表达“当前 agent 是否应该做这个动作”。
3. active mask 会让 agent 延续上次动作
runner 在训练和 eval 中都会执行:
训练路径见 macf_runner.py:383-419,eval 路径见 macf_runner.py:481-526。 active mask 在 env_core.py:1005-1031 中只有在 key event、当前任务完成或 all_still 时才重新激活。
这本来是合理的“任务执行中不反复重决策”机制,但和永久可选的 resupply/explore/idle 结合后,会放大错误选择: 一旦 agent 选择了不合理 resupply,它会一直走到补给点;补给完成后如果策略再次选 resupply,就形成循环。
4. 当前奖励对无效特殊动作的即时惩罚过弱
coalition_delivery 奖励见 env_core.py:1298-1382。 当前已经有几项修正:全发现后 explore 小惩罚、empty-load 非 resupply 小惩罚、剩余需求时每步机会成本、终局 shortage penalty。 但 trace 显示仍不够:
- 满载/有载 agent 选择 resupply 没有显式负奖励。只有通用
-0.015以及距离变化项。 - empty-load delivery 的惩罚只有
-0.04每步,且常常要到抵达任务点才触发ag.delivery < 0的惩罚。 - 终局 shortage penalty 只在
step_count >= max_steps或 all completed 时给,信用分配距离太远。 - resupply 成功时有
0.8 * resupply_ratio奖励,但“没必要 resupply”没有对应的强负项。
5. HATT 的特殊动作候选更容易形成类型偏置
HATT 直接把 task candidates 解码成动作 logits。对 delivery task,它能看到位置、剩余 demand、deadline、priority、type; 对 explore / idle / resupply,它看到的是静态的特殊任务特征。 HATT v3.1a 在 coalition_delivery 中禁用了 capability residual HATTV31Network.py:214-233,这是合理的,因为新版需求不是旧 req-cap 对齐。 但 special actions 仍被当作普通 task candidate 参与解码,且没有 agent-specific feasibility mask。
这可以解释为什么 HATT v2.0/v3.1a 更容易掉进 resupply/idle/explore:结构化 decoder 在“特殊任务”上学到一种稳定类型偏置,而奖励不足以把它纠正到 logistics 闭环。
四、当前失败机制分型
| 失败类型 | 典型模型 | trace 表现 | 根因判断 |
|---|---|---|---|
| Resupply forever | HATT v2.0,HATT v3.1a | 发现完成后,agent 有载甚至满载,仍长期全体或大多数选择 resupply。 | resupply 永远合法;满载 resupply 无显式强罚;特殊动作候选容易被 decoder 稳定偏好。 |
| Idle / explore late drift | HATT v3.1a,MAPPO best 部分 seed | 还有剩余需求,但后半程 delivery agent 很少,idle 或 explore 占比高。 | 全发现后的 explore 只小惩罚;idle 只在剩余需求时多罚 0.03;终局惩罚太晚。 |
| Empty-load delivery forever | MAPPO latest seed08 | 所有 agent 空载后仍持续 delivery,既不补给也不探索,最终死亡。 | delivery 动作不按 load mask;空载走 delivery 的即时惩罚不足;active mask 延续错误动作。 |
| Hard geometry / danger seed | 所有模型 seed 8/9/41/43/49 | 多个模型同 seed 失败,但失败模式不完全相同。 | 场景本身较难,可能涉及危险点、任务空间分布和补给距离;但模型失败仍表现为动作语义错误。 |
五、改进路线
路线 A:先改上下文 action mask,风险最低,收益最大
建议在 EnvCore._get_available_actions() 中加入 agent-specific 条件:
- delivery task:仅当任务 discovered、未 completed、agent living、且
ag.load > 0时可选。 - resupply:仅当
load_ratio < load_threshold或battery_ratio < battery_threshold,且仍有剩余需求或 agent 需要续航时可选。 - explore:仅当仍有未发现 delivery task 时可选;全发现后 mask 掉。
- idle:仅当没有剩余 delivery demand,或 agent 无可行动作时可选。为了避免全 0 mask,最后保底打开一个最合理动作。
- 对于 dead agent,维持当前 active mask 逻辑,不通过 resupply 复活,除非明确设计“救援/维修”机制。
推荐伪代码:
注意:mask 使用的信息应限制在策略已经可以观测或任务语义允许公开的信息内。已发现/未完成、agent load、battery、全局 discovered count 当前已经在 obs 或 available_actions 逻辑中存在。
路线 B:补上无效动作的即时惩罚
即使加入 mask,也建议保留 reward shaping,避免边界状态变成新漏洞。
- 满载/高载 resupply:如果
ag.resupplied且resupply_ratio < 0.05,给负奖励,而不是 0。 - 有剩余已发现需求且 agent 有 load,但选择 resupply/idle/explore:增加机会成本。
- 空载 delivery:从“抵达后惩罚”为主,改成“选择并执行空载 delivery 每步惩罚”。
- 全发现后 explore:当前
-0.05太小,可按剩余需求和时间放大。 - 终局 shortage penalty 前移:每步加入
-lambda * remaining_demand / sent_goal或 potential-based shaping,例如奖励remaining_demand的下降。
路线 C:改善特殊动作建模,而不是把它们当普通 task
HATT 的 task decoder 对 delivery task 是合理的,但 explore/idle/resupply 是 control actions,不是普通空间任务。 可以考虑在 HATT actor 中拆成 typed action heads:
- delivery head:对 discovered unfinished delivery tasks 做 agent-task 解码。
- explore head:仅输出探索动作,或输出探索模式。
- resupply head:由 agent load/battery/context 决定。
- idle head:作为终局/无事可做动作,强依赖 remaining demand。
这比继续把特殊动作塞进 task candidate 更符合语义,也能减少 HATT 在特殊任务类型上的静态偏置。
路线 D:增强 eval 与诊断,而不是只看 reward 曲线
以后每组训练建议固定输出以下诊断:
- 64 或 100 seed deterministic eval,保留 best/latest。
- hard-seed suite:固定 seed 8/9/41/43/49/52/60。
- 全发现后动作占比:delivery / explore / idle / resupply。
- 终局剩余需求、零载荷 agent 数、平均 load、死亡数。
- 每个失败 seed 的 first discovery、all discovered、first completion、last completion step。
我已经添加了可复用脚本: scripts/analysis/trace_eval_failures.py。 后续可以直接拿它分析新 checkpoint。
六、不建议优先做的事
- 继续单纯拉长训练:错误动作是稳定吸引子,更多训练可能只是更稳定地学到错误循环。
- 只调学习率/熵系数:可能改变曲线形态,但不解决“满载 resupply 合法且几乎无罚”的机制问题。
- 重新引入 sector/survey 作为主方案:探索形式不是当前主瓶颈。主要瓶颈在发现后的物流闭环。
- 只增加 eval episode 数:评估能暴露问题,但不会修复环境机制。
七、建议实施顺序
-
实现 coalition_delivery 专用 contextual action mask。
先只改
_get_available_actions(),保持网络不变,跑 smoke + 64-seed eval。 - 加入无效动作即时惩罚。 尤其是高载 resupply、空载 delivery、全发现 explore、有载 idle。
- 重训 MAPPO / HATT v2.0 / HATT v3.1a 小规模对照。 先不用 mid-scale,确认 small-scale hard seeds 是否改善。
- 如果 HATT 仍明显偏 resupply/idle,再改 HATT typed action decoder。 这一步涉及模型结构,风险高于环境 mask,应放在第二阶段。
- 把 trace 指标纳入每次 sweep 的分析报告。 否则 reward 曲线和 success 曲线仍会掩盖“看似 reward 高但物流闭环断掉”的情况。
八、最终判断
当前 coalition_delivery 版本已经比旧 MACF 机制更接近“联盟运送”任务,但还没完全自洽。 主要缺口是:动作空间仍把 logistics control action 和 delivery task action 混在一起,且缺少 agent-specific 可行性约束。 rewardfix 让 MAPPO 和 HATT v3.1a 显著改善,但没有从机制上消除错误吸引子。
因此,下一版重点应是 action feasibility + invalid-action shaping。 只有这两项之后仍不能稳定完成任务,才有必要把主要精力转向 HATT 结构改造。