返回列表

1st Place Solution

631. UM - Game-Playing Strength of MCTS Variants | um-game-playing-strength-of-mcts-variants

开始: 2024-09-05 结束: 2024-12-02 游戏AI 数据算法赛
第一名解决方案 - James Day
作者: James Day (Grandmaster)
发布时间: 2024-12-04
竞赛排名: 第 1 名

第一名解决方案

首先,我要感谢 Kaggle 和马斯特里赫特大学(Maastricht University)举办了如此有趣的比赛——我学到了很多。我也要祝贺所有排在排行榜前列的人。你们让我直到最后都捏了一把汗!

概述

我认为让我获胜的主要技巧是在每个游戏的起始位置上运行几秒钟的树搜索,以计算描述游戏平衡性以及搜索执行速度的额外特征(这与竞赛组织者提供的 AdvantageP1MovesPerSecondPlayoutsPerSecond 列半冗余)。然后将补充特征(以及大多数其他竞争对手使用的所有特征)输入到 CatBoost、LightGBM、TabM 和保序回归(isotonic regression)模型的堆叠集成中。下图说明了这一点。

模型架构 diagram

每个 GBDT/NN + 保序模型堆栈的 RMSE 分数如下所示。

基础模型 交叉验证 (CV) 公共排行榜 (Public LB) 私有排行榜 (Private LB)
CatBoost 0.362 0.415 0.421
LightGBM 0.357 0.419 0.424
TabM 0.352 0.420 0.424
全部(集成) 0.344 0.413 0.417

集成权重是使用 Nelder-Mead 算法针对我的交叉验证(CV)进行调整的。

从上表可以看出,我的本地交叉验证有些不稳定,但在 CV 中得分最好的集成恰好在公共和私有排行榜上也取得了最好的分数。

起始位置评估

AdvantageP1 列是基于做出随机移动的智能体之间的对弈计算得出的,因此它与非随机对弈中玩家 1 拥有的优势并不完全相关。通过模拟非随机智能体之间的游戏来计算玩家 1 真正 拥有多少优势对于提交来说太消耗 CPU 了,而训练语言模型根据规则预测玩家 1 有多少优势(或劣势)也没有产生非常准确的结果,所以我改为通过在游戏的起始位置上短暂运行树搜索来生成游戏平衡性的补充指标。我认为这与完全模拟非随机智能体之间的游戏相比, accuracy 相似但速度快约 250 倍。就像模拟游戏一样,速度和准确性之间存在可配置的权衡,15 秒的搜索大致相当于模拟非随机智能体之间匹配 1 小时的效果。

在早期实验中,向模型提供通过在每个游戏起始位置运行 15 秒树搜索计算出的补充游戏平衡指标,使我的分数提高了约 0.045 CV,0.012 LB。这最初让我在比赛大约一个月后登上了公共排行榜的第 1 名。

我用来计算游戏平衡指标的树搜索算法等同于竞赛组织者用来玩部分游戏的 UCB1Tuned-0.6-Random200-true 算法(我的代码链接到 Ludii 玩家的 MCTS 实现作为依赖项)。我认为该配置对此工作负载最重要的方面是 selectionplayout 值,explration_constscore_bounds 似乎无关紧要。下面包含了我早期实验中的数据可视化,大致说明了这一点。正如您所看到的,表现“良好”的配置要么使用了 UCB1 或 UCB1Tuned 选择策略,要么使用了随机或 NST 模拟。也就是说,NST 在排行榜数据上的表现似乎比竞赛组织者提供的训练数据要差,而 UCB1Tuned 在 CV 中似乎比 UCB1 略好(不确定 LB 情况),因此获胜 submission 使用了 UCB1Tuned 和随机模拟。

MCTS 配置可视化

补充搜索速度指标

竞赛组织者提供的 MovesPerSecondPlayoutsPerSecond 列中存在一些随机噪声。因此,在计算上述起始位置评估/游戏平衡指标时,我的代码还报告了有关探索游戏树速度的信息,即每秒的动作数和搜索迭代次数。将这些特征输入我的模型使我的 CV 分数提高了约 0.005,因此它们被用于获胜解决方案,但它们似乎对 LB 分数没有任何显著影响(影响在随机运行间方差范围内)。

额外训练数据

我总共生成了 14,365 行额外训练数据,包含 484 个额外的唯一规则集。额外规则集是通过两种方法的混合生成的:

  1. 实现并运行我自己版本的 GAVEL
  2. 普通指令微调的 LLM(Llama 3.1 70B & Qwen 2.5 32B)配合 few-shot 提示。

在 484 个注释规则集中,391 个是使用方法 #1 生成的,93 个是使用方法 #2 生成的。我使用方法 #2 生成的游戏要多得多(超过 1 万个),但由于质量问题,最终丢弃并大量下采样了它们。

使用方法 #1 生成的规则集质量通常比 #2 高得多(至少根据 GAVEL 选择的指标,即平衡性和战略深度),但 #1 生成“可玩”游戏的速度比 #2 慢得多,即使考虑到使用方法 #2 生成的规则集中约 95% 因 Ludii 编译或运行时错误而不得不丢弃的事实。我不记得具体的吞吐量或错误率统计数据了,但认为每种方法生成可玩规则集的速率存在数量级差异。

至于数据注释部分,我试图通过让每个(有序)智能体对每个规则集只玩 4 场比赛来节省 CPU 时间(例如,4 场智能体"A"先走,然后另外 4 场智能体"B"先走),所以我的标签与竞赛组织者提供的相比相当脏,但使用额外数据仍然使我的分数提高了约 0.004 CV,0.001-0.002 LB。我通过给额外训练样本的权重低于竞赛组织者提供的样本来减轻标签噪声。某些模型似乎对噪声比其他模型更敏感,所以我为每种类型的模型单独调整了额外数据的权重。获胜解决方案中使用的权重如下 outlined。这些是针对我的本地交叉验证调整的,而不是排行榜。

模型 额外数据权重
CatBoost 0.25
LightGBM 1.0
TabM 0.5

扩大补充数据的数量似乎产生了严重的收益递减(例如,第一批约 7k 额外训练行使我的 CV 分数提高了约 0.003,但第二批仅产生了约 0.001 的改进),随着我扩大规模,最佳额外数据权重下降,所以我怀疑随着规模扩大,标签噪声成为了一个越来越大的问题。事后看来,我怀疑如果我用双倍的计算量注释一半的游戏,可能会得到更好的结果。

数据增强

我使用了两种形式的数据增强:

  1. 训练示例的所有树搜索相关特征都计算了 10 次。训练期间输入模型的每一行中包含的值是从 9 次未保留用于交叉验证的运行中随机选择的,因此模型策略性地暴露于特征中的随机噪声,而不是在每个规则集的所有行中复制相同的错误。
  2. 竞赛提供的数据中的所有非确定性特征都重新计算了 5 次,并用于计算特征“真实”值的更准确(噪声更少)估计。训练期间输入模型的值是在这些估计值与竞赛期间或我的初始数据生成运行期间提供的值之间均匀采样的。

方法 1 使我的 CV 和 LB 分数都提高了约 0.002,CV 和 LB 之间有良好的相关性。方法 2 的影响有点模糊,因为它取决于我是否丢弃了受下一节讨论的(疑似)Ludii 玩家版本差异影响的一些特征,但通常在 <= 0.002 的范围内。

特征选择

在重新注释竞赛组织者提供的规则集时,我注意到有 43 个特征的值在我机器上生成时从一次运行到另一次运行完全一致,但与竞赛组织者提供的值不同。我最好的猜测是这可能是由某种 Ludii 玩家版本差异引起的(我使用的是版本 1.3.13),但我没有超级具体的证据来证明这一点。有趣的是,当我尝试丢弃具有不一致值的列时,我的 CV 分数 consistently 提高了 0.001-0.002,这让我很惊讶,因为没有其他特征选择策略在多个随机种子和模型中 consistently 导致分数改进。似乎需要一次性丢弃所有这些特征才能产生可测量的 benefit(不是一次一个),基于重要性的过滤不能很好地识别它们为不重要/有毒的。

获胜 submission 没有使用任何受疑似版本差异影响的特征。这对 LB 的影响有点不清楚,因为我在最后 12 小时内一次性提交了多个看似有助于 CV 的更改,而没有测试增量版本——稍后会有更多说明。

保序回归 (Isotonic regression)

这主要是受到我在一些公共 notebooks 中看到的一个技巧的启发,人们将他们的原始预测乘以某个系数,然后 clipping 到比 -1 到 1 更窄的范围内,但似乎产生了更好的 CV-LB 相关性。更具体地说,使用针对我的 CV 调整参数的 scale + clip 技巧在排行榜上弊大于利,但拟合使用 OOF 预测作为特征并在后处理步骤中运行的保序回归模型,使我的 CV 和 LB 分数都提高了约 0.002。

对于大多数基础 + 保序模型堆栈,我使用了 centered isotonic regression,它在交叉验证中的得分比 scikit-learn 的“传统”保序回归实现略好,可能是由于严格的单调性约束使 CIR 模型不易过拟合。所谓“略好”,是指小数点后第 4 位的差异。然而,对于 CatBoost,它导致了一些崩溃,因为缺乏类似于 scikit-learn 的 out_of_bounds='clip' 参数的功能,我没有properly 为 CatBoost 解决这一问题(如果在推理时将原始预测 clipping 到 -1 到 1 的范围再输入 CIR,如果训练示例的范围比那更窄,则无济于事!),所以 CatBoost 模型堆栈使用了 scikit-learn 的非 centered 保序回归实现。

TabM

在比赛的最后一个月,Yandex(CatBoost 的作者)发布了一个新的表格深度学习库和研究论文来描述它,TabM (https://github.com/yandex-research/tabm, https://arxiv.org/pdf/2410.24210)。基于深度学习的解决方案在我早期的一些实验中似乎没有很有希望,但他们论文中的基准结果令人印象深刻,所以我决定试一试。它效果出奇的好!更具体地说,事实证明它在排行榜上与 LightGBM 具有竞争力,并且是我在交叉验证中最强的单一模型。在早期实验中,将其添加到我之前的最佳集成中使 LB 分数提高了约 0.001,但我需要进行一些消融测试才能说明它在最终集成中有多大帮助。

超参数调优

获胜解决方案的超参数是通过为每种模型类型运行 Optuna 半打左右次,使用 5 折交叉验证和仅单个随机种子选择的,然后使用 10 折交叉验证和 3 个种子重新检查每个“最佳”配置。CV 是基于规则集名称 partitioned 的 GroupKFoldShuffle

关于获胜配置,我发现以下几点相对值得注意:

  • TabM 用于连续数值特征的分段线性嵌入似乎帮助巨大。我怀疑这是它能够击败相对传统 MLP 的主要原因。
  • TabM 的最佳配置略低于原始 TabM 研究论文中描述的搜索范围。我的最佳结果是通过相对宽的隐藏层、小批量大小和低学习率实现的。
  • 将 LightGBM 的 boosting 参数设置为 dart 对于使其工作得足够好以有用至关重要。这使我的 LightGBM 单一分数提高了约 0.004 CV,0.003 LB,但代价是训练运行时间增加了 5-10 倍。我的超参数搜索是在有和没有 dart 的情况下运行的。

集成学习 (Ensembling)

上图中的每个模型块实际上是一个 20 个模型的集成,这些模型是在 10 折交叉验证期间训练的,重复了 2 个随机种子。CatBoost 和 TabM 依赖早停来正常工作,所以单个模型不能真的在所有数据上训练——集成是利用所有数据的最佳方式。我通常将 CV 运行重复 3 个种子,但只使用其中 2 个的模型,以便在推理 notebook 中节省内存(30 GB RAM 中,18 GB 用于树搜索,10 GB 用于模型,所以我真的无法扩展到第三个种子)。使用的 2 个种子是任意选择的,没有以特定方式调整。

“信任交叉验证”vs“信任排行榜”

在比赛的最后一周左右,我并行 Pursued 两种创建最终集成的策略。

  1. “信任排行榜” - 我尝试选择 Optuna 提出的候选配置,这些配置在使用“版本 4"的额外数据(约为获胜解决方案的一半)训练时随机在排行榜上表现良好,并将集成权重调整到排行榜。在此方法调整到排行榜时使用的候选模型没有充分利用上述特征选择和数据增强方法,因为我的大部分硬件都集中在方法 #2 上...
  2. “信任交叉验证” - 我尝试选择 Optuna 提出的候选模型超参数配置,通过使用多个种子运行 10 折 CV 进行双重检查,并更彻底地将数据增强、特征选择和额外数据权重选择调整到我的 CV。此过程是使用“版本 6"的额外训练数据集(即上述数据集)完成的。在此过程中做出的选择在比赛的最后 12 小时或比赛结束后的 late submissions 之前没有在排行榜上进行测试。

令人惊讶的是,“信任交叉验证”在公共和私有排行榜上都击败了“信任排行榜”。考虑到公共排行榜似乎多么讨厌针对我的 CV 调整权重的集成,额外训练数据量的最终 2 倍扩展似乎多么没有帮助(在早期实验中它使我的 LB 分数变差!),以及我的 CV 分数之前与公共 LB 的相关性多么弱,我真的没想到这一点。“信任交叉验证”策略主要是为了避免在公共和私有排行榜彼此相关性很差的情况下遭受重大震荡,我 Pursued 它时并没有期望能够击败针对公共 LB 调整的集成的公共 LB 分数。

策略 交叉验证 (CV) 公共排行榜 (Public LB) 私有排行榜 (Private LB)
“信任排行榜” 0.3554 0.4142 0.4190
“信任交叉验证” 0.3442 0.4137 0.4178

尝试过但效果不佳的方法

  • 使用未分割的智能体字符串作为分类特征
  • TF-IDF & LSA
  • MLPs
  • XGBoost
  • 各种基于深度学习的方法来生成文本嵌入作为额外特征
  • 训练语言模型根据 lud 规则预测每个游戏的平衡性
  • OpenFE
  • 我在公共 notebooks 中看到的额外特征
  • 我在公共 notebooks 中看到的模型堆叠方法(OOF 预测作为特征)
  • 基于重要性的特征选择
  • 迭代前向和后向特征选择
  • 训练模型预测基于 MCTS 的智能体的平均 elo 评级,其中 MCTS 配置的每个部分都以特定方式设置(例如,对于特定游戏使用随机模拟的智能体的平均 elo),并将这些估计的 elo 评级用作补充特征。
  • 训练表格分类模型来识别总是以特定方式结束的严重不平衡和平局游戏(智能体 1 赢,智能体 2 赢,或平局),并将 resulting 预测纳入集成。
  • 向某些特征添加随机噪声以用于数据增强目的
  • 使用 CatBoost 强制执行单调性约束
  • 使用 CatBoost 的微调功能来“改进”其他模型的预测(类似于过去比赛的获胜解决方案)
  • 使用多个 MCTS 算法的集成来产生起始位置评估。充其量,这似乎等同于让单个算法每个规则集运行更长时间。

我认为上述某些项目经过额外努力后可能会效果很好。当早期结果看起来没有希望时,我经常在没有超级坚定地努力让它们正常工作之前就继续前进。

进一步改进的途径(我遗漏的地方)

我完全忽略了几乎所有其他顶尖团队都使用的翻转/反转数据增强技巧!查看他们的 writeups,我怀疑这是其他人能够在不在游戏起始位置上运行树搜索的情况下得分几乎和我一样高的主要原因。如果 @goldenlockwriteup 中的统计数据是正确的,那么我相信混合解决方案可能在私有排行榜上得分在 0.407 左右(比我的第 1 名解决方案好 0.01 🤯)。

同比赛其他方案