返回列表

FLG's Approach - Deep Reinforcement Learning with a Focus on Performance - 4th place

548. Lux AI Season 2 | lux-ai-season-2

开始: 2023-01-25 结束: 2023-05-08 游戏AI 数据算法赛
FLG的解决方案:以性能为导向的深度强化学习(第4名)
作者:flg (Kaggle Grandmaster)
比赛排名:第4名
发布时间:2023年5月3日

1. 引言

Lux 第二季

LuxS2是一个双人零和游戏,每位参与者控制一群机器人,比赛看谁能在火星上更快地进行地形改造。游戏在一个48x48的方形地图上进行,分为多个阶段:

  1. 同时出价阶段:决定谁先行动
  2. 顺序放置阶段:玩家放置2-5个工厂(数量随机选择)
  3. 同时行动主阶段

在主阶段中,玩家获取金属来制造机器人,获取水来种植地衣,并清理碎石为地衣农场腾出空间。在1000回合结束时,地衣数量更多的玩家获胜。

主阶段分为20个昼夜周期,每个周期持续50回合,其中30回合为白天,20回合为夜晚。

有三种类型的参与者:

  1. 工厂:种植地衣、生产机器人,并将原矿石/冰转化为可用的金属/水
  2. 重型单位:擅长战斗和资源收集,但在高碎石区域移动较为迟缓
  3. 轻型单位:擅长高效移动,但开采资源较少,在战斗中会被重型单位击败

单位执行几乎所有行动都需要能量。能量每回合通过工厂获得,白天所有单位缓慢获得,通过种植地衣也能获得。没有能量的单位无法执行任何行动,需要(缓慢)充能。

当两个单位碰撞(移动到同一目标位置)时,重型单位会摧毁轻型单位,同类型单位之间能量更高的获胜。这种碰撞处理也适用于你自己的单位!

除了这些机制外,还有一个规划组件:玩家可以向每个单位发送最多20个行动的队列。这很有用,因为每次更新行动队列时,受影响的单位都需要支付少量能量费用。例如:

  1. 一个重型单位挖掘20次(每次60能量),每回合发送一次挖掘行动(每次更新10能量):能量消耗:20 * 60 + 20 * 10 = 1400
  2. 相同情况但发送一个包含20个挖掘行动的队列:20 * 60 + 10 = 1210

在第二种情况下,该单位节省了约15%的能量。能量是最稀缺的资源,因此这种效率提升具有非常大的影响。

与上一赛季的比较

与去年相比,游戏复杂性显著增加:

  • 地图大了2.2倍
  • 比赛回合数增加了3倍
  • 有三个不同的游戏阶段
  • 对于学习方法,能量系统(特别是与需要清理碎石来收集资源和种植地衣相结合)使得随机探索变得更加困难
  • 行动队列系统将游戏从单步问题转变为多步规划问题

2. 方法

计划

比赛在Kaggle上使用仅CPU的虚拟机运行,这些虚拟机的性能似乎各不相同,但大多是单机处理器。因此性能是每项决策的主要考虑因素,我制定了一个以性能为重点的计划:

  1. 简化行动和观察空间至可管理的大小
  2. 处理碰撞和行动队列
  3. 使用模仿学习和小型地图强化学习来寻找结合CPU性能、大感受野和样本效率的神经网络架构
  4. 使用RL训练一个小型模型
  5. 使用小模型作为教师训练更大的主模型
  6. 优化最终模型以进行CPU推理

行动和观察空间

行动空间

我将模型结构化为七个不同的执行器:

  1. 出价:离散(10):[0, 10, 20, 30, 40, 50, 70, 100, 150, 200]
  2. 生成位置:离散(48*48)
  3. 水的生成资源量:离散(7):[50, 100, 120, 150, 200, 250, 300]
  4. 金属的生成资源量:离散(7):[50, 100, 120, 150, 200, 250, 300]
  5. 工厂:网格(4):[无操作, 建造轻型, 建造重型, 种植地衣]
  6. 轻型单位:网格(23):[5个方向移动(包括无操作), 挖掘, 自毁, 拾取所有可用能量, 向5个方向转移[矿石, 冰, 能量]]
  7. 重型单位:网格(23):与轻型单位相同

网格(k)表示一个48x48xk的行动空间,其中48x48地图的每个位置独立采样k个行动中的一个。在转移行动上,单位转移其持有的所有冰和矿石,以及当前90%的能量值。执行生成行动时,需要同时选择位置以及水和金属的起始资源量。我先采样位置,然后并行预测所有位置的水/金属量,并在选定的生成位置采样7个行动中的一个。注意:没有转移或拾取精炼资源(如金属和水)的行动。因此智能体只能使用未精炼资源进行长距离转移。

观察空间

我使用0/1变量编码所有参与者的位置,大多数其他量(能量、资源、碎石)采用连续变量,通常归一化到-1至1范围。我尝试对所有离散观察使用显式嵌入,但没有发现性能差异。唯一的例外是地衣所有权:每个工厂有自己的地衣菌株。每个玩家最多五个工厂,加上一个"无地衣"选项,共有十一个离散值。我使用16维嵌入层嵌入这些值,并与玩家ID的16维嵌入相加,以指示该地衣的所有者。

当前回合或剩余生成资源等全局特征编码为独立的特征层(48x48特征层的每个位置具有相同的值)。总共有54个不同特征。我添加了历史信息:过去三个回合所有参与者的位置和资源,以及棋盘上的地衣和碎石值(每回合17个特征)。最终观察空间形状为48x48x105(54 + 3*17=105)。

碰撞和行动队列

碰撞

本赛季自我碰撞的惩罚非常严重,因为除了一个单位外,所有碰撞单位都会死亡。在早期强化学习中,我发现我的智能体避免让单位并排工作,通常不喜欢制造很多单位。这似乎是由于需要探索的随机策略会导致许多有害的自我碰撞。为了应对这一点,我取消了所有会导致自我碰撞的移动,单位改为执行无操作。这需要重现相当多的游戏逻辑,但大大提高了早期学习速度。

行动队列

在推理时用多个行动填充行动队列,我使用了正向预测:智能体预测自己和对手的行动,然后步进环境并重复该过程。由于对手的行动队列是游戏公开观察的一部分,我在排行榜上有一个智能体使用它进行正向预测(如果没有队列中的移动可用,则回退到自己的预测)——其性能与仅使用预测相似。

训练期间我不使用行动队列,只让智能体预测其下一个行动。这种方法只有一个问题:更新行动队列所使用的能量在两种情况下不同。这导致智能体对其能开采多久、地图的哪些部分可以到达等的预期在较长的序列中会出现偏差。为了解决这个问题,我使用了预期的通信成本方法:以p=0.2的概率随机应用通信成本,模拟平均长度为5的行动队列(这与智能体在所有优化后实现的行动队列有效长度非常接近)。

神经网络架构搜索

我使用了一些测试智能体和早期提交来创建模仿学习(IL)的数据集。更具体地说,我:

  • 通过MetaKaggle下载了约2000场顶级智能体的比赛
  • 在创建数据集时将观察和行动转换为上述空间
  • 将所有内容存储为压缩的HDF5数据集(最终数据集约300GB,HDF5允许非常快速的随机访问,而无需将所有内容加载到内存中)

排除了使用我不支持的行动(一些智能体在工厂之间转移水)的提交。

我基于不同NN架构在此数据集上的单位准确率进行评估(我也训练和评估了工厂和生成位置准确率,但这些更容易学习)。我寻找的主要特性是:

  • CPU性能
  • 感受野("快速看到整个棋盘")
  • GPU内存使用
  • 样本效率

我得到的最佳结果是"DoubleCone"网络,下面展示了DoubleCone(4, 6, 4)。它在12x12的下采样分辨率(而非48x48)上执行了相当一部分计算,这有助于实现上述所有目标,除了样本效率。为了避免精细控制的损失,DoubleCone块周围有一个跳过连接。此外,在该块之前和之后有多个全分辨率ResBlocks。执行器头是简单的3x3卷积层,没有激活函数,唯一的例外是出价头,它使用评论家架构(少一个卷积层)。

DoubleCone(4, 6, 4)

评论家

评论家架构如上图所示,在我的测试中其架构似乎并不重要。略微不寻常的AdaptiveAveragePool存在的原因是,我早期的许多强化学习测试是在16x16和24x24地图上进行,我需要一个真正与分辨率无关的架构。

我还实验了层优化,特别是ResBlock设计。在这里,我测试了计算机视觉的最新进展(例如ConvNext)以及一些任务特定的优化。一些结果:

  • 我没有使用任何Bottleneck或InverseBottleneck块,因为1x1卷积在GPU上相当耗费资源,但不能提供感受野
  • 我使用了少得多的激活函数,特别是第二个卷积后完全没有激活函数,只有SqueezeAndExcitation
  • 在我的计算预算下,我无法在ReLU、LeakyReLU或GELU之间找到真正的差异
  • 3x3和5x5卷积性能相似,如果你在使用3x3时大致将深度加倍(更少的参数/感受野)
  • 没有归一化层,因为我发现只要存在SqueezeAndExcitation层,它们就没有任何益处。没有这些层时性能显著下降,但其中一部分可以通过LayerNorm恢复

我的最终架构与ResNet的比较可在此处看到:

模仿学习性能

强化学习

我的智能体使用自我对弈和强化学习进行训练。我使用了强烈关注游戏结果的塑形奖励,以尽可能避免对智能体产生偏见。

奖励

在设置阶段(约6500万步),智能体使用强烈关注资源收集的塑形奖励进行训练。它会获得以下奖励:

  • 向最近的资源移动
  • 从资源处清理碎石
  • 开采资源
  • 当携带>100资源时向最近的工厂移动
  • 在工厂上丢弃资源
  • 开采矿石和冰的奖励
  • 制造单位
  • 种植地衣
  • 在资源附近放置工厂

这些奖励对每个智能体都是"自私的"(非零和)。

在此阶段之后,我切换到零和奖励:

  • 游戏结果:{-1, 0, 1}
  • 游戏结束时相对地衣优势:[-1, 1]
  • 每个工厂:0.3
  • 相对单位优势:[-0.8, 0.8]
  • 相对地衣能量生产:[-0.1, 0.1]

每个奖励都相对于对手的表现给出,以产生[-1, 1]范围内的零和奖励。一个例外是工厂奖励:智能体每比对手多一个工厂就获得+0.3,少一个就获得-0.3。奖励仅在发生变化时给予(例如,当工厂丢失时),而不是之后每个回合都给予。这样,所有相对奖励的(未折扣)总和等于游戏结束时的相对优势。

在训练过程中,我测试了许多不同的奖励权重,智能体对变化似乎非常鲁棒,可能是因为游戏结果和地衣优势主导了奖励。

强化学习方法

我的强化学习方法使用单学习器多执行器方法。我使用Python的多进程实现所有功能:每个执行器和评估器都是独立的进程,所有进程通过队列进行通信。架构如下图所示。我对所有神经网络使用Pytorch和向量化计算,但不使用强化学习框架。

强化学习架构

一开始,我在16x16和24x24地图上测试了PPO和Vtrace进行优势计算,并取得了相似的结果。我选择了Vtrace。总的来说,我的智能体使用以下损失进行学习:

损失类型 方法 数量 主要参量
价值 TD(λ) 1 λ=0.95, γ=0.9995, 权重系数: 1.0
策略梯度/优势 Vtrace 7 (每个执行器1个) λ=1.0, γ=0.9995, 截断c/ρ = 1, 权重系数: 1.0
KL 7 权重系数: 1e-5 .. 1e-4, 取决于执行器
教师 KL 7 权重系数: ~5e-3

在训练过程中,我慢慢地降低了熵,并定期更新教师,使其保持在智能体约3000万步的后面。

我的方法的一个问题是出价和生成工厂是相当罕见的事件,它们只发生在约1%的所有步骤中。这意味着典型的约500批量大小包含0-2个这样的事件,这不允许稳定的学习。为了应对这一点,我通过拥有一个专用的生成执行器来"上采样"生成和出价。每16次更新步骤,这个执行器从起始位置进行大约十场比赛,每场约50步,然后放弃这些游戏。

训练更大模型

我原本计划只使用上面所示的DoubleCone(4, 6, 4)网络作为第二代和可能第三代更大模型的教师。然而,由于下面所述的运行时性能问题,我不得不在约1亿步后取消DoubleCone(6, 8, 6)模型的训练。然后我继续微调较小的模型。其训练性能可在此处看到:

训练进度

优化Kaggle性能

每个单位在更新其行动队列时必须支付能量成本,这意味着只预测少数几步的解决方案处于严重劣势。在比赛过程中,我进行了许多本地和排行榜测试,以评估能量效率的影响。结果相当一致:应该瞄准至少4-6步预测,低于此值性能会受到严重影响(特别是单步)。在我的测试中,获得更多步数只提供了适度的优势。一些结果可在此处看到:

预测步数对性能的影响

当你将这些结果与ResNet和DoubleCone架构的预测性能进行比较时,你会发现大模型很难使用。见下文。24块ResNet与去年获胜者使用的类似,它几乎无法预测一步。

推理速度

一个非常重要的细节是,Kaggle环境中没有快速的推理运行时(如上所述,带有平台特定执行提供程序如OpenVino的onnxruntime)。由于无法安装任何包,我和几位其他参赛者尝试自己打包onnxruntime,但据我所知没有人成功。像OpenVino这样的快速执行提供程序在竞赛环境中的某个地方无法工作(尽管它在其他地方包括Kaggle笔记本中都能工作)。调试也变得几乎不可能,因为所有错误日志都是404。

当查看上述性能数字以及OpenVino在我的所有测试中将性能翻倍/三倍的事实时,很明显这对于在Kaggle硬件上使用更大模型是成败攸关的问题。在 unsuccessful 调试了几天后,我决定放弃它,取消我正在训练的DoubleCone(6, 8, 6)模型,并回到微调较小的模型。

之后,我尝试了许多其他加速推理的方法:

  • 静态训练后量化
  • 动态训练后量化
  • 模型压缩
  • jit trace和script

其中大多数要么由于设置时间太长(在Kaggle硬件上)而失败,要么与性能相比,它们具有不利的准确率-性能权衡,使用一个微小的模型来填充行动队列:

填充行动队列

我的智能体通常使用DoubleCone(4, 6, 4)预测3-5步。为了改进这一点,我使用了一个DoubleCone(2, 6, 2)网络(只有64个通道,而不是128个)进行迁移学习。行动队列的构建如下:

  1. 使用DoubleCone(4, 6, 4)网络预测最多3步
  2. 用微小网络的预测填充,直到时间用完
  3. 使用模式匹配将队列填充至20个行动

始终进行1-3次慢速预测意味着微小网络在先前预测中犯的任何错误都会被纠正。这样智能体通常能管理6-8次预测,有效(已纠正)队列长度在5到6之间。

模式匹配使用常见行为来天真地填充队列:单位在战斗中不断来回移动,(轻型)单位拾取能量并将其转移到相邻的采矿单位以及挖掘。如果这些都不起作用,它只会复制最后一个行动。

对于在这些阶段之间切换,我使用了预测时间管理。这对于VM性能 somewhat 不可靠是必要的。

迁移学习使用(Q)Dagger启发的方法:首先在教师的轨迹上进行训练,然后切换到学生的轨迹以纠正剩余错误。在整个训练过程中,教师的强度被降低。

3. 结论

这场比赛对我来说有些起伏。一方面,缺乏神经网络的快速部署选项是一个巨大的失望,调试问题使得工作非常令人沮丧。另一方面,环境非常有趣且工作愉快。我的解决方案效果相当好,我有机会使用从模仿学习和架构搜索到强化学习、迁移学习和性能优化等一系列工具。但最重要的是,社区特别是在Discord上的社区非常棒。非常感谢Stone Tao、Bovard和赞助商举办这场比赛!感谢Agenlu帮助进行性能优化,感谢Tigga、ry_andy_、Harm和SiestaGuru作为我最常对战的人,最后感谢ry_andy_和loh-maa(无意中)提供了模仿学习数据集。

同比赛其他方案