未完成,不代表最终质量。同时有大量的实验数据没有收集或者整理。
最终成绩#
第一次周赛,评测配置为 B500S700,QL 同学在周日的会议提出考虑引入 CNN(以及一些 SLAM 的想法),其他两位同学也提出了一些 insight。我将其实践,在第一次周赛拿到了 1300 分第五名的成绩。
第二次周赛,评测配置为 B300S500,QL 同学主张生存优先,宝箱其后,训练出了能够拿到满分的模型。我们在第二次周赛拿到了 1800 分第二名的成绩。
队友各自都有着重要的事情要做。但是我算是第一次实践这种 On-policy 的训练,感到非常新鲜。同时过往一段时间的 VLA 的学习也让我对在赛题环境中进行一些尝试有更多的兴趣,所以我一直在尝试一些新的想法,虽然我们没有关注到第三第四次周赛时评测环境逐步高压,保持通关已经是不可能,从而走了错误的优化方向,在注意到最终评测是 B100S300 后只是匆匆微调了一下 QL-v1 模型,最终只取得了中部赛区 10% 的入围成绩。
但是,我觉得这个过程还是非常有意义的。
赛题介绍#
去年的任务是在地图里面尽量找到更多宝箱并更快到达终点。官方默认 agent template 是 dpn 算法。
今年的任务是在地图里两只怪物的高压追逐上尽量达到生存分数的最优:
官方默认使用两层 mlp 的 ppo 算法。在默认配置(300 步生成第二只怪,500 步怪物狂暴,接下来我们采用 B300S500 表示这类环境配置)下取得了 600 分的成绩。
默认 agent pipeline 如下:
原始游戏数据
↓
[observation / feature] ← preprocessor.py + definition.py
↓
[model] ← model.py(神经网络,输出动作和价值)
↓
[action] ← 从模型输出里采样得到实际动作
↓
[environment] ← 游戏执行动作,返回 reward
↓
[sample] ← 把一步步数据打包成训练样本
↓
[algorithm] ← algorithm.py(计算损失,更新参数)
↓
[workflow] ← train_workflow.py(把所有模块串起来)
↓
[agent] ← agent.py(对外接口,kaiwudrl 框架调用的入口)text特征工程#
第一周,我们主要在做特征工程。
官方的初始特征只用到了
- 英雄局部状态
- 两只怪物的局部信息
- 局部地图的一小块
- 前 8 维合法动作
- episode 是否结束
没有用到的是环境提供的视野范围(),得分信息,观测信息和额外信息。
我们有理由相信,增加特征能够提高模型的表现。同时我们也必须考虑到:
- 特征该如何进行预处理?
- 用怎样的先验结构才能利用好这些特征?
模型设计#
我们尝试了 CNN 和 RNN 的结合来处理视觉信息和时间序列信息。
CNN 取得了很大的成功,但是 RNN 的收敛速度显著慢于 CNN,甚至在某些环境配置下完全不收敛。
此外最重要的,初赛只允许在 windows/mac pc 上进行训练而不能用服务器,我们没有那么大的算力和数据(纯 RL)去 scaling。 这一点我第二周才意识到。
训练环境#
最终的评测是 12 张地图,而我们在训练环境和赛中四次周赛都只能看到 10 张地图。
去年的选手马可波罗认为,为了观测模型是否过拟合,应该在训练环境里保留一些地图不让模型看到。我们在第一周也采用了这个策略,保留了两张地图不参与训练,而是作为测试集来评测模型的泛化能力。
在初赛并不具有明显意义,可能是因为我们的训练时长没有到达要担心过拟合的程度,或者是因为我们没有采用过于复杂的模型来导致过拟合。
RL 算法#
Reward 设计#
我坚决认为,我们的 Reward 需要和最终的 Score 优化目标对齐。
在早期为了防止模型因为惩罚过重,奖励系数而变得过于保守,我们在 reward 里面加入了探索奖励。
同时,我们为了引导模型吃到稀疏的奖励(宝箱),我们在 reward 里面加入了趋向宝箱的稠密奖励。
这些奖励在早期确实帮助模型学会了基本的生存技能和吃到宝箱的能力,但是在后期我们发现,这些奖励不适合乱加,我们的先验可能并没有考虑到所有的情况,模型在不健全的 reward 下会有 reward hacking 的风险。
出于以上原因,我先后引入了 SIL 自模仿学习和 Risk-Constrained RL 给 Actor-Critic 加入了一个 risk 预测头。
SIL 自模仿学习#
对于简单的 mlp + ting CNN 效果不明显,但是对于较大的 RNN 模型,能够显著加快早期训练速度。
一个显著的好处是,SIL 能够让模型在早期快速学会一些离散稀疏奖励的技能,比如说吃到宝箱的能力。
但是实际上,我们做游戏 RL 是在不停 rollout 发掘上限,而不是像 VLA 那样去提升下限。 SIL 并不适合我们这种场景,因为它会让模型过于依赖之前的经验,导致模型在后期很难突破局部最优。
最终加上了退火,甚至选择放弃它。
multi value heads#
如果撤掉所有非 score 相关奖励,模型很快就会陷入到疯狂吃宝箱但是不注重生存的局面,是高压阶段生存难度大的主要原因。
为此,我们在 Actor-Critic 模型的基础上加入了一个 risk 预测头,来预测当前状态下被怪物追上的短期风险。
然而这个 risk 预测头到底该预测什么?是被追上的概率?还是被追上的时间?还是被追上的分数损失?
以及我们要对多久之后的局面负责?是 10 步之后?还是 20 步之后?还是更长时间之后?GAE 的 lambda 是多少?
在初赛简单的场景下,risk head 并没有产生很好的优势,反而抑制了宝箱的获取。
PPO-Clip 调参#
PPO 的核心是一个 clipped surrogate objective:
其中 是当前策略和旧策略的概率比, 是优势函数。
初赛默认的 CLIP 参数是 0.1,我在卡在高压临界时间点的时候把它调到了 0.2,能够稍微加快探索。
可观测性指标#
最终的评测分数只和平均数有关,然而我们在训练过程中发现,平均数并不能全面体现一个 model 的表现。
e.g. 一个被我训崩了的 LSTM 模型,平均分在 B300S500 环境下只有 1000 分出头,但是实际上它分数低主要是因为高短死率,训了 45000 步仍然保持着 20% 以上的高短死率,虽然有 10% 的 1600 分样本,但是一但到了平均就全部被短死拉下去了。
所以我引入了 p40 p60 p80 这三个指标来分别衡量模型在不同分数段的表现,来更全面地评估模型的表现。
其实应该不太够来着,我们应该用一些统计学里面被广泛验证过的指标来评估模型的表现。
甚至,训练一些模型来打分?
可观测性是为了我们建立对 policy 质量的直观理解,来指导我们后续的模型设计和训练。
