PenGym 单智能体 izumi 当日进展报告:medium family 统一接口打通,重复无增量动作约束显著改善泛化,但长训与强加权会导致分布偏置

一、今天这篇记录要说明什么

今天的主线不是继续讨论 reward3,也不是回到最初的单场景长训,而是围绕 medium family 的泛化 持续往下推进,并把“为什么泛化不稳定”一步一步拆出来。

今天真实完成并验证的事情可以概括为:

  1. 已实现 medium family unified observation adapter,并把 medium-multi-sitemedium-single-sitemedium 统一映射到固定 646 维输入。
  2. 已验证 unified observation 下的 resetstep、smoke train、单局推理都能正常运行,说明“统一接口 + PPO”这条链已经真正打通。
  3. 已确认:仅做 unified observation + single-scenario training 并不足以稳定泛化
  4. 已实现 medium family mixed training,把训练分布从单场景推进到 family-level。
  5. 已逐层定位 mixed training 中的主要失败模式:重复 process_scan、重复失败 exploit、重复失败 os_scan/service_scan、repeat mask 接线失效、mask 不足以彻底阻断重复动作等。
  6. 已实现一套更完整的 repeat-action guard
    • 成功扫描动作 block
    • 失败 exploit block
    • 失败 scan block
    • mixed env 中 repeat mask 接线修复
    • step 侧硬拦截 blocked repeat
  7. 已拿到一版关键正结果:某个 50k mixed + repeat-guard 版本在多 seed × 多 episode 的评估下,对 medium-multi-site / medium-single-site / medium 三个场景全部达到 15/15 成功。
  8. 已同时验证:更长训练(100k)或强加权采样(如 [1,1,4])并不天然更好,反而会造成偏科甚至整体崩坏。

今天最重要的阶段性结论不是“已经彻底解决泛化”,而是:

对 medium family 来说,真正显著提升泛化的关键,不是单纯继续堆 timesteps,也不是直接切 reward3,而是 在 unified observation + mixed training 的基础上,把 episode 内的重复无增量动作真正封死


二、今天的核心目标

今天的目标可以拆成三层:

1. 把 medium family 的 unified observation adapter 真正写出来并跑通

也就是把昨天已经在思路上定下来的 canonical observation schema,真正落到代码里,并且完成 shape 对齐与 smoke 验证。

2. 在统一接口上重新训练 PPO,确认它到底能不能学,以及学成后能不能跨场景迁移

换句话说,不只是证明 wrapper“看起来没问题”,而是真正让 agent 在 unified observation 上训练和评估。

3. 如果 mixed training 仍然失败,就继续往下追,直到把 failure loop 定位到可改动的具体机制

今天后半段的大量时间,其实都花在这第三层:持续定位并修正反复出现的“无增量动作循环”。


三、今天具体做了什么

1. 实现并验证 medium-family unified observation adapter

今天首先完成了 medium family 的统一 observation wrapper。

按当前确定的 canonical schema,统一设计为:

  • max subnet slots = 7
  • max host slots per subnet = 16
  • scalar fields = 6
  • os slots = 1
  • service slots = 5
  • process slots = 3

因此每一行宽度固定为:

  • 7 + 16 + 6 + 1 + 5 + 3 = 38

再结合当前 family 的总行数设计,最终固定输入变成:

  • 17 × 38 = 646 dims

今天已经实际写出并运行了:

  • prototype/rl/adapters/unified_medium_obs.py
  • prototype/rl/check_medium_obs_shapes.py

对三个场景的 shape smoke test 结果是:

  • medium-multi-site 原始 observation:493
  • medium-single-site 原始 observation:578
  • medium 原始 observation:459

统一后全部变成:

  • 646

这一步说明:

unified observation 不是停留在思路层面的设计,而是已经完成了代码实现,并在 medium family 三个目标场景上真实跑通。


2. 验证 unified observation 下的基础交互链路

在 shape 对齐之后,继续验证 unified observation 是否会破坏最基本的环境交互。

今天实际做了:

  • unified env reset()
  • unified env step()
  • 5k unified smoke train
  • 单局 deterministic eval

最开始遇到过一些工程问题,例如:

  • 把 Python 源码误直接敲进 shell;
  • prototype 模块导入路径不对;
  • nasim.make_benchmark() 本地版本接口与预想不一致;
  • flat action 采样出来的是 numpy.int64 而不是 Python int

这些问题今天都已逐步解决,最后确认:

  • unified observation wrapper 不破坏 reset
  • unified observation wrapper 不破坏 step
  • PPO 能在 unified observation 上启动训练
  • 模型也能在 unified observation 上正常推理

这一步把一个关键前提真正坐实了:

“统一输入接口以后还能不能继续用 PPO”——答案是可以。


3. 证明:single-scenario unified training 能学会,但 zero-shot 仍然不够

在 unified observation 的基础上,先做了 single-scenario 训练和评估。

结论是:

  • medium-multi-site 在统一接口下可以学会;
  • 但把同一个模型直接拿去 medium-single-sitemedium,依然会失败或表现明显不稳。

这说明:

unified observation 解决了输入空间不一致的问题,但没有自动带来 zero-shot 泛化。

换句话说,今天已经把问题进一步缩小到了:

  • 不是 PPO 根本学不会;
  • 不是 unified observation 完全没用;
  • 而是 unified observation 只是必要条件,还不是充分条件。

4. 实现 medium family mixed training

在确认 single-scenario unified policy 无法稳定迁移后,今天继续把训练分布推进到了 family-level。

新建并改造了:

  • prototype/rl/train_maskable_ppo_medium_family_mixed.py

实现思路是:

  • 通过 MixedScenarioEnv
  • 在每个 reset() 时从 medium family 中抽一个场景
  • 仍然保留已有 wrapper 链:
    • UnifiedMediumObservationWrapper
    • IntActionWrapper
    • ProgressRewardWrapper
    • SuccessActionMemoryWrapper
    • ActionMasker
    • Monitor

也就是说,今天已经从“统一 observation 的单场景 PPO”推进到:

统一 observation + medium family mixed training

这是后面所有泛化修复的真正训练载体。


5. 逐层定位 mixed training 中的真实失败根源

今天最有价值的一部分,不是第一次 mixed train 本身,而是后面一路往下追“mixed 为什么还是会崩”。

这个定位过程今天先后暴露出几个非常具体的 failure mode:

(1)重复 process_scan 循环

最早观察到 mixed training 容易被大量重复 process_scan 拖垮。
这说明 scan 类重复动作的约束不足,于是先把 process_scan 纳入 repeat block。

(2)重复失败 exploit 循环

修完 process_scan 后,又很快发现模型会在同一 host 上反复重试失败 exploit,例如反复 e_smtpe_ftp
这说明失败 exploit 没有被及时记忆并屏蔽,于是把:

  • connection_error
  • permission_error
  • undefined_error

这几类失败下的 e_* / pe_* 也纳入 repeat block。

(3)重复失败 os_scan/service_scan 循环

继续向下看,又出现了大量失败的 os_scanservice_scan 循环。
于是继续扩展规则,把失败的:

  • service_scan
  • os_scan
  • process_scan

也一起加入 block。

(4)规则写了,但 repeat mask 在 mixed wrapper 链里没有真正接上

后面又发现一个更深的工程问题:

  • SuccessActionMemoryWrapper.get_repeat_block_mask() 已经存在;
  • 但在 mixed env 的 wrapper 链里,mask_fn() 没有稳定拿到它;
  • 结果就是规则虽然写了,但最终 action mask 并没有真正用上 repeat block。

于是今天继续修:

  • MixedScenarioEnv 显式暴露 get_repeat_block_mask()
  • mask_fn() 改成通过 get_wrapper_attr("get_repeat_block_mask") 去拿 repeat mask

(5)mask 侧不够,step 侧还需要硬拦截

即便 repeat mask 接上之后,日志里仍然能观察到 blocked 动作偶尔真正落到了底层环境。
所以今天又继续做了更强的一步:

  • SuccessActionMemoryWrapper.step() 里加入 step 侧硬拦截;
  • 如果当前动作 key 已经进入 _blocked_action_keys,就不再真的发到底层 env,而是直接返回缓存 observation + 负奖励。

一开始还因为底层 PenGymEnv 没有 get_observation() 而报错,后来改成 wrapper 内缓存 _last_obs,最终跑通。

今天这一整轮推进,把失败根因从“mixed training 看起来不太行”逐步压缩成了:

关键瓶颈并不是 mixed 这个训练框架本身,而是 episode 内重复执行无增量动作的行为没有被彻底封死。


6. 拿到一版关键正结果:三场景 15/15 全成功

在 repeat-guard 逐步完善后,mixed training 的训练表现明显回稳:

  • ep_len_mean 显著下降
  • ep_rew_mean 回到正值
  • 训练日志不再被大量 process_scan / os_scan / failed exploit 循环主导

随后今天新建了系统化评估脚本:

  • prototype/rl/eval_medium_family_mixed.py

评估设置为:

  • 5 个 seeds
  • 每个 seed 3 个 episodes
  • 每个场景共 15

在一版 50k mixed + repeat-guard 模型上,得到今天最关键的正结果:

  • medium-multi-site15/15
  • medium-single-site15/15
  • medium15/15

这一步的意义非常大:

今天第一次不是只拿到“单次 rollout 成功”,而是拿到了 medium family 三场景在多 seed、多 episode 下全部成功的系统化评估结果。

这已经足够支撑一个比较有分量的阶段性正结论。


7. 同时验证:更长训练和强加权并不天然更好

在拿到上述关键正结果后,今天并没有停下,而是继续往前探索“能不能更进一步”。

(1)把 50k 拉到 100k

当 mixed + repeat-guard 从 50k 拉到 100k 时,出现了新现象:

  • medium-multi-sitemedium-single-site 进一步变强;
  • medium.yml 完全掉下去,出现 0/15

这说明:

更长训练可能会把策略推向某些子拓扑分布,导致选择性偏科,而不是均衡提升所有场景。

(2)尝试 weighted sampling

为了把 medium.yml 拉回来,今天又尝试了:

  • [1, 1, 2]
  • [1, 1, 4]

这种 scenario weighting。

得到的现象也很有价值:

  • [1,1,2] 只能略微改善 medium.yml 的负回报,但不能恢复成功;
  • [1,1,4] 则直接把三个场景全练坏,三场景都掉到 0/15

这说明:

强行提高某个失败场景的采样权重,并不会线性地修复泛化,反而可能把整体训练分布拉歪。

所以今天又得到了一个重要负结论:

  • 不是继续堆 timesteps 就会更好;
  • 也不是把失败场景权重拉高就会更好。

四、今天做对了什么

1. 没停在 unified observation “能跑”这一层

今天不是写完 wrapper 就算结束,而是一路推进到:

  • shape smoke
  • step smoke
  • train
  • eval
  • mixed env
  • failure analysis
  • repeat guard
  • 系统化评估

这让 unified observation 真正进入了策略层的验证。

2. 对 failure loop 一层一层往下追

今天最大的亮点之一,是没有停在“mixed 还是会失败”的泛泛描述,而是持续向下拆成:

  • process_scan loop
  • failed exploit loop
  • failed os/service scan loop
  • repeat mask 接线问题
  • step 侧硬拦截必要性

这使得后续修复都变成了可以落实到代码的小改动,而不是空泛猜测。

3. 拿到了多 seed × 多 episode 的关键正结果

今天最有分量的成果,不是某一局的 rollout,而是:

  • 50k mixed + repeat guard 版本
  • 在 medium family 三场景上全部 15/15

这已经把“可能有点效果”推进到了“有统计证据的阶段性突破”。

4. 证明了“更长训练 / 更强加权”不一定更好

今天的 100k 偏科现象与 [1,1,4] 的全面崩坏,都说明:

  • 泛化问题不是简单继续训练就能解决;
  • 训练分布和重复动作约束之间存在比较微妙的平衡。

这对后续研究方向判断很重要。


五、今天的问题与不足

1. 最优平衡版本没有第一时间固定保存

今天一度拿到了非常好的 50k balanced result,但当时没有立刻冻结成单独 checkpoint,就继续向更长训练和加权 sampling 往前试了。
这导致后面虽然看到了更多现象,但最稳的平衡版本没有及时单独归档。

2. 当前结论仍然局限在 medium family

虽然今天对 medium-multi-site / medium-single-site / medium 的 family-level generalization 推进很实质,但这并不代表:

  • small family
  • tiny family
  • 更大范围场景

也已经具备同样的迁移能力。

今天的结论范围仍然应当严格表述为:

当前方法在 medium family 内部已经出现了可验证的跨场景泛化能力,但其更大范围的可迁移性尚未验证。

3. 当前最强机制仍然偏工程性约束

今天真正拉起结果的关键,是 repeat guard / hard blocking 这一类行为约束。
这说明当前 agent 的进步仍然很大程度依赖于:

  • 合理的 action mask
  • 合理的 anti-loop 机制
  • 合理的 episode 内动作封锁

这不是坏事,但也说明当前的“通用策略能力”还没有强到完全脱离这些辅助约束。


六、今天得到的阶段性结论

今天真正能站住脚的结论有五条:

1. unified observation 对 medium family 是可行的

medium family 三个目标场景已经被成功统一到固定 646 维输入,并且 PPO 链路已经在这个统一输入空间上稳定运行。

2. 仅做 unified observation 并不足以自动带来泛化

single-scenario unified policy 依然会在 zero-shot 场景上失败,因此 observation unified 只是必要条件,不是充分条件。

3. medium family mixed training 是正确方向

把训练分布从单场景推进到 family-level 后,agent 的训练与评估问题才真正进入“泛化”层面,而不再只是单场景记忆。

4. 重复无增量动作约束是当前提升泛化的关键

真正显著提升结果的,不是 reward3,也不是简单长训,而是:

  • 重复 scan 屏蔽
  • 失败 exploit 屏蔽
  • 失败 scan 屏蔽
  • repeat mask 接线修复
  • step 侧硬拦截

这些约束共同抑制了 episode 内的无意义动作循环。

5. 更长训练与强加权都可能导致子分布偏置

100k 训练和强加权采样都证明了:

  • 泛化不一定随训练时长单调提升;
  • 过强地向某个子场景补偿,会破坏整体分布平衡。

七、下一步最合理的推进方向

今天的结果已经足够说明:继续盲目堆 timesteps 或继续提高场景权重,并不是当前最优路线。

更合理的后续方向有三类:

1. 先把当前最优平衡版本管理好

把真正表现最均衡的 checkpoint 和训练脚本单独固定下来,避免后续实验覆盖最强平衡版本。

2. 在保持 current repeat-guard 机制的基础上,进一步研究“如何在不偏科的前提下继续提升”

例如:

  • 更精细的 curriculum,而不是简单粗暴加权;
  • 在 50k 附近找更稳的训练区间;
  • 对不同 sub-topology 采用更细的阶段式训练。

3. 后续才考虑更大改动

例如:

  • unified action abstraction
  • 更抽象的 topology encoding
  • 更高层策略结构

但这些都应该建立在今天已经验证出来的有效机制之上,而不是推翻重来。


八、今天一句话总结

今天最重要的推进不是“把一个 PPO 多训练了一会儿”,而是:

把 medium family 的统一输入接口真正打通,并通过 mixed training + repeat-action hard guard,第一次在系统化评估下实现了 medium family 三场景全部成功;同时也实证发现,更长训练与强加权会带来新的偏科与退化。

这使得当前研究从“agent 能不能学”正式推进到了:

agent 在 family-level 上如何稳定泛化,以及泛化为什么会被分布偏置破坏。