631. UM - Game-Playing Strength of MCTS Variants | um-game-playing-strength-of-mcts-variants
感谢主办方和 Kaggle 带来如此精彩的体验。我非常享受这次比赛,尤其是与我优秀的队友 @cody11null、@leehann 和 @gauravbrills 一起合作。
终于在竞争了 3 年后,我获得了金牌并晋升到了 Master 层级!!!!
我们的解决方案包括修改后的 AdvantageP1 特征、CatBoost 和两个在不同数据/特征上训练的 LGBM Dart 模型之间的集成。
我们成功选择了我们在私有榜上表现第 2 好的提交,它在公共榜上也是第 2 名,并且交叉验证(CV)得分最高之一。

我们使用 8 折 GroupKFold 验证模型。令人惊讶的是,使用 8 折而不是 5 折或 10 折使得 CV 更加稳定,并且比任何其他策略的相关性更高。
我们探索了几种类型的关系,包括以下内容:
翻转 (Flip): Agent1=a, Agent2=b ==> Agent1=b, Agent2=a
我们翻转代理,将 AdvantageP1 改为 1-AdvantageP1,并将 utility_agent1 乘以 -1。
这是最有用的方法。它在训练和作为推理时的测试时增强(TTA)都效果很好。特别是如果你分别为原始数据训练一个模型,为增强样本训练一个模型然后集成。这在 CV(+~0.01)和 LB(+~0.003)上都给出了巨大的提升,而不是将它们合并到同一个模型中(+~0.001 LB)。
但在最后,经过许多实验,我们发现对其进行训练会使模型变得不稳定,因此我们从训练中 drop 掉了它,只使用了原始数据。事实证明这是正确的,因为我们最终没有基于增强数据训练的子模型在私有榜上表现更好。
自我对弈 (Self-play): Agent1=a, Agent2=b ==> Agent1=a, Agent2=a 和 Agent1=b, Agent2=b
我们通过让代理与自己对抗来创建新样本,将 Advp1 设为 0.5,utility_agent1 设为 0。这对 CV 帮助很大,但对 LB 没有帮助。
传递性 (Transitivity): Agent1=a, Agent2=b 和 Agent1=b, Agent2=c ==> Agent1=a, Agent2=c
很难为这个设置 AdvantageP1 和 utility_agent1 值。我采用了这种方法:
# 从第一个样本分配 num_wins_agent1
new_row['num_wins_agent1'] = row1['num_wins_agent1']
# 从第二个样本分配 num_losses_agent1
new_row['num_losses_agent1'] = row2['num_losses_agent1']
# 求和两个样本的 draws
new_row['num_draws_agent1'] = (row1['num_draws_agent1'] + row2['num_draws_agent1'])
diff = row1['num_losses_agent1'] - row2['num_wins_agent1']
因为如果关系中的中间代理(连接两个样本的代理)对第一个和第三个代理有相同的胜率,那么我们可以假设代理 1 和 3 没有技能差异,因此我们取第一个的胜率和第三个的胜率作为我们的新胜负数。但是如果有差异怎么办?我们应用这个:
# 计算技能差异
diff = row1['num_losses_agent1'] - row2['num_wins_agent1']
if diff < 0:
new_row['num_wins_agent1'] += abs(diff)
elif diff > 0:
new_row['num_losses_agent1'] += abs(diff)
我们将相同的技能差异思想应用于 AdvantageP1:
new_row['AdvantageP1'] = row1['AdvantageP1']
new_row['AdvantageP2'] = row2['AdvantageP2']
diff_adv = row1['AdvantageP2'] - row2['AdvantageP1']
clip_val = 1 - (new_row['AdvantageP1'] + new_row['AdvantageP2'])
if diff_adv < 0:
new_row['AdvantageP1'] += np.clip(abs(diff_adv), 0, clip_val)
elif diff_adv > 0:
new_row['AdvantageP2'] += np.clip(abs(diff_adv), 0, clip_val)
这实际上增加了整整 20 万个新样本。此外,经过上述修改,目标分布与原始训练数据非常相似:

但在最后,这个传递性想法在 CV 上没有显著差异,我也没有针对 LB 尝试它(在这个过拟合派对之后我应该做的😅)。我认为它需要更多的工作。
最终,我们决定从训练中 drop 掉所有的增强想法,因为它们使结果变得非常不稳定(我们只使用原始数据进行训练),仅在推理期间使用翻转增强进行 TTA。
例如,我们将训练数据和增强数据结合在 X_Train 中,并针对原始数据 X_Test 进行验证(以确保正确的比较),CV 从 0.405 跳到了 0.395。100% 没有泄漏。但 LB 从 0.416 到了 0.421。在那一点上,我们相信很容易在 CV 上过拟合,所以我们尝试只使用能同时改善 CV 和 LB 的想法,结果证明这是最后最好的主意。
无论如何,这些想法对于从现有样本生成新样本可能仍然有效,但我想需要一些改进。
我和 @cody11null 基于上述想法构建了 CatBoost 和 Dart 模型。我们没有使用任何特征工程或选择,因为它们没有帮助。
@leehann 和 @gauravbrills 基于相同的想法构建了 Dart 模型,但进行了特征工程和选择,基于相关性、方差和置换重要性选取前 300 个特征,这对他们的 Dart 模型有效。
第一个 CB 和 Dart 的 CV 为 0.412,LB 为 0.419。第二个 Dart 的 CV 为 0.416,LB 为 0.421。
结合两者给了我们 CV 0.406 和 LB 0.416。
@leehann 和 @gauravbrills 尝试了一些 tabnet、xgboost、聚类、嵌入的实验,但都没有起作用。
对于我和 @cody11null,我们采用了几种技巧:
.png?generation=1733206866485544&alt=media)
.png?generation=1733204797074179&alt=media)
train_df["AdvantageP1"] = (train_df["AdvantageP1"] - (1 - train_df["AdvantageP1"]))
使其匹配目标的构建方式。这给出了以下分布,与目标相似:
.png?generation=1733205163689424&alt=media)
这作为模型中的特征没有帮助,但将其添加到最终集成预测中将 CV 提高到 0.404,LB 提高到 0.415。
y = pd.concat([y, -y], axis=1)
然后预测使用:
pr = model.predict(test)
pred = (pr[:, 0] - pr[:, 1]) / 2
它在 CV 上提升了 ~0.002,但在 LB 上结果稍差为 0.416。我们选择这个作为我们的第二个提交,它在私有榜上也给了 0.424。