MCTS + CNN 自对弈强化学习实战总结

MCTS + CNN 自对弈强化学习实战总结

本文是对一个五子棋自对弈强化学习项目(GomokuCnn)的完整复盘。 项目在一台 RTX 3060 Laptop 上,用 AlphaZero 式"自对弈 → 训练 → 评估" 循环从零训出强 AI:最终模型对战传统 NNUE 引擎 rapfi,先手执黑基本保持 100% 胜率。 训练方案参考了 zhoukangyang233/Gomoku (nn_012 管线),在此之上做了大量机制研究与工程优化。

项目已开源:https://github.com/YorickFin/GomokuCnn

这个项目最有价值的部分不是"训出了一个能下棋的 AI",而是过程中沉淀的 几条反直觉结论——它们大多来自失败的 run,每一条都有实测数据背书:

  1. 标签质量不来自 sims 数,来自标签生成机制;棋力不来自单轮精度,来自总轮次;
  2. 双盲期不是要"解决"的问题,而是要"穿越"的阶段;
  3. 物化 D4 对称增强是强 AI 的信息论硬约束,Online D4 + 经验回放无法替代;
  4. 防杀能力不生活在搜索里,它生活在训练标签里;
  5. 跨轮持久的低学习率是棋力退化的隐形杀手。

下面按管线逐一展开。

一、管线总览

整体是标准 AlphaZero 架构的轻量版:

▼
text
复制代码
自对弈(MCTS + 当前模型生成对局数据) ↓ 标签生成(全节点收集 + prior/Q 分层重分配) ↓ 物化 D4 八倍对称增强 ↓ 训练(每轮重建 Adam + ReduceLROnPlateau 轮内调度) ↓ arena 竞技评估(对 champion 胜率 ≥52% 晋升) ↺ 回到自对弈

关键配置(低 sims 三支柱,缺一不可):

支柱配置作用
低算力搜索30 sims/步单轮快 → 总轮次多
标签机制全节点收集 + 分层重分配低 visits 数据可用
小网络32 通道 / 5 残差块容量匹配噪声标签

三支柱是绑定的。只搬其中两根会得到最差组合:首次启动时曾用 128ch/6blk 大网络配 30 sims,单轮 8 分钟(大网络吃不动 28 万全收样本), 且大容量在噪声标签上只会过拟合——128ch 的训练段计算量是 32ch 的 19 倍。 换 32ch 后全程平均约 22 秒/轮(自对弈 ~6s + 训练 ~15s)。

二、低 sims 的经济学:总轮次 > 单轮标签精度

选择 30 sims 是一场明示的赌注。自对弈训练的总学习信号可以分解为:

▼
text
复制代码
总学习信号量 ≈ 总轮次 × 每轮数据量 × 每样本信息密度

高 sims 管线的每样本信息密度高,但每轮慢;低 sims 管线的前期标签 基本是噪声(30 次模拟摊在 20 个子节点上,每个 1~3 次访问,毫无分辨力), 但换来的是 7 倍以上的轮次吞吐。

一组真实的对照实验:

  • run_8:800 sims,268 轮——arena 胜率停在 59% 天花板,且其 base 本身停滞后,新 run 只能摸到 base 的水平;
  • nn_012(参考管线):30 sims,约 20000 轮——训出强 AI,对弈仅需 200 sims 就能稳定防杀。

轮次差 7.5 倍,sims 差 27 倍,结果完全倒挂。单轮标签精度的短板 可以靠机制补(见下文重分配),总轮次的短板没有任何东西能补。

代价也要有预期:低 sims 的双盲期更长(30 sims 树深约 3 层,搜不出 战术序列,战术能力只能等网络从数据里"背会"棋形)。这是设计内风险, 不是 bug。

三、双盲期:定义、诊断与穿越

什么是双盲期

自对弈冷启动阶段,对局双方(同一模型的自我复制)都弱到数据分布中 不含任何有意义的对抗行为——双方都不堵、不攻,谁先随手连五谁赢。 模型从这样的数据里学不到进步方向,形成自我强化的低水平均衡:

▼
text
复制代码
模型弱 → 搜索树里没有战术信号 → 训练数据里没有"防守收益"的正例 → 模型学不会防守 → 模型依然弱

诊断指标(不要靠肉眼感觉)

run_1 实测的双盲期指纹:

特征实测值说明
对局极短~3.5 s/100 局折合每局仅 ~10 手搜索
胜率暴涨假象arena 对随机 base 99:1赢随机对手不需要会下棋
value/policy 分离val value 0.088→0.029,policy 反而恶化终局信号先教会 value 头;policy 无信号可学

三个关键判据:

  1. 对局长度(最灵敏):防守涌现 = 互堵 = 对局拉锯变长。自对弈 耗时台阶式上升且不回落才是涌现(平台期单次冲高不算数);
  2. arena 对 champion 的持续区分度:5060% 震荡且晋升不断 = 代际 进步;长期 4550% 停滞 = 警惕;
  3. val value loss 在降:说明逃逸引擎在转。注意 val policy loss 不能当判据——重分配标签是移动靶(value 变准 → 标签本身变锐), run_1 全程 policy loss 没有掉头向下,模型照样逃逸了。

穿越手段

按"重要性 × 容易被忽视"排序:

  • 随机开局(opening_max_moves=20,保留空盘开局概率):纯空盘自 对弈的开局标签退化成"先验 top-k 摊平",且所有对局开局段高度相似。 随机预落子把对局起点推进中盘,并用网络估值筛选最均势局面。踩坑 提醒:每个候选必须独立采样——参考实现曾把 shuffle 放在循环外, 1000 个候选实际只有 ~11 种不同局面;
  • 温度:每局随机 0.1~0.8,让整个数据集覆盖从贪心到乱下的行为谱 (固定两阶段温度会导致中后期轨迹趋同,中盘残局偏弱);
  • 根节点 Dirichlet 噪声(α=0.1,混合比 0.25):只加在根节点, 防止搜索被先验锁死;
  • 层次重分配标签:这是低 sims 可行的支点——30 sims 的原始访问 分布是噪声,但每个子节点下方的树内 Q 是连续信号,把访问概率 按"Q 对比 + prior 层级"重新分配给树认为更好的分支,配合全节点 收集(visits 门槛设 0,可信度进样本权重 √(visits/sims)), 低 visits 数据不用丢弃。双盲期里它救不了命(Q 也是噪声),但 value 头一旦变准,policy 标签立刻变准,不用等数据分布先改变 ——这把逃逸正反馈链的长度砍了一半;
  • value 头是逃逸的正反馈引擎:终局胜负 z 是唯一永远真实的监督 信号。逃逸链条:终局 z → value 变准 → 树内 Q 变准 → 重分配标签 变准 → policy 学会堵活三 → 对局拉锯 → 数据出现真战术 → 正循环。

run_1 完整逃逸时间线(1200 轮,约 12 万局,8.4 小时)

阶段轮次自对弈耗时/轮arena 表现状态
双盲期r0~99~3.6 s频繁晋升(假进步)随手连五
长平台r100~7993.6→9.6→5.5 s 冲高回落晋升频繁但幅度小行为在变
深平台r800~1049~3.4 s胜率 50% 无区分全程最像"失败"
逃逸启动r1050~11493.5→5.0 s场均 82.5%,连续大胜突破
巩固r1150~1379稳定 7~8 s54~63% 震荡晋升中等 AI 强度

最深刻的教训:深平台段(胜率 50% 无区分 + 耗时回落到双盲水平)是 全程最像"该止损"的阶段,但它恰恰是逃逸前夜。防守是模式识别问题, 网络需要"见过足够多活三/冲四被惩罚"的对局——run_1 逃逸时累计约 12 万局,与参考实现的 20 万局同一数量级。数据量门槛真实存在,管线 优化只是压低它,没有取消它。

四、物化 D4:强 AI 的信息论硬约束

逃出双盲期之后,下一个瓶颈是自对弈闭环的对称性破缺:

▼
text
复制代码
模型有微小方位偏置(如左上角着法略多) → 自对弈生成的局面里,左上方战术出现频次更高 → 训练后偏置被巩固甚至放大 → 下次自对弈继续生成偏置数据 → 闭环正反馈:偏置永久固化,盲点永远存在

关键在于这个偏置发生在数据生成端。训练端的 Online D4(取样时 随机翻一个方位)只翻已有的偏置数据,改变不了模型下次下棋的偏置。

硬约束 vs 软约束

  • 物化 D4:每个原始局面物化 8 份方位副本,8 个方位在同一次 forward/backward 里一起反传——梯度直接竞争,模型没有任何机会 选择性忽略某个方位;
  • Online D4 + replay_rounds=7:数字上等效(每样本训练 24 次, 方位覆盖 ~96%,Coupon Collector 问题),但 8 个方位分散在不同 epoch、不同轮次,每次训练步模型权重都不同——梯度永远不直接 竞争,偏置有充分的生存空间。且 replay 旧数据的重分配标签是 历史 value 头生成的,已经过时。

分阶段收益

阶段核心瓶颈物化 D4 收益性价比
冷启动期标签全是噪声8 份噪声平均,信噪比 +√8差(每轮样本 ×8)
逃出冷启动后闭环对称性破缺打破偏置正反馈,消除盲点极高

所以正确的姿势是:没学会防守之前 Online D4(省钱熬双盲期), 学会防守之后必须物化 D4(强 AI 的关键)。这个约束与 sims 数 无关——高 sims 管线标签干净后,同样会被闭环偏置卡在强 AI 门槛前。

五、value 标签前缀重分配:防杀能力住在标签里

这是全项目最硬的一条结论,证据来自一次搜索预算完全倒挂的对抗测试:

  • 参考管线:约 2 万轮训练,对弈 200 sims——稳定防杀;
  • 新管线:6000 轮训练,对弈 3600 sims——反复失守。

18 倍搜索预算差距。修复前的新管线对强算杀对手呈现出两个不对称:

▼
text
复制代码
对手的杀点落子之前:树里完全搜不到 10~20 步杀(3600 sims 如同不存在) 对手的杀点落子之后:立刻判断对面胜率 99%(一次前向就完成)

"下之后"只是模式识别(威胁已成型,CNN 一眼可见);"下之前"需要的 是前瞻推理,MCTS 能算深杀的唯一前提是 value 头在杀路早期节点上 给出强烈负值信号,引导搜索分配模拟。而嗅杀能力是训练标签教出来的。

问题出在 value 标签的"半套重分配"

原管线只重分配了 policy 标签,value 标签仍用访问量加权树 Q:

▼
text
复制代码
子节点 A:prior=0.7(正常回应),Q=+0.2,访问 7 次 子节点 B:prior=0.2(潜伏杀点),Q=+0.99,访问 3 次 访问量加权 Q:(7×0.2 + 3×0.99)/10 = 0.437 → 杀点信号被访问占比稀释,再被平庸回应平均掉 前缀 max 重分配:value ≈ 0.551,杀点权重 0.44,远超其 0.3 访问占比 → max 结构保证极端值不被平均掉

更尖锐的情况:杀点只被访问 1 次时(30 sims 常态),旧标签里它的 权重是 1/30 ≈ 3%,信号基本消失;前缀 max 里只要它 prior 靠前, 包含它的层级价值就全额等于它的极端价值。

修复方法:value 标签与 policy 标签从同一个分层循环产出——按 prior 降序逐层差分切分概率预算,每层的前缀 max 价值以同源权重累入 value 标签。数学上有 telescoping 恒等式保证权重和恰为已访问 prior 之和,标签是子节点取反 Q 的凸组合,恒在 [-1,1],与 tanh 输出域对齐。

为什么堆数据补不上这块?自对弈闭环会系统性饥饿这类样本:双方都 看不见长杀 → 树内 Q 没信号 → 对局在杀路成型前就结束了 →"被正确 标注的潜伏威胁"样本极其稀疏 → 模型更看不见。与 D4 闭环偏置同构: 数据生成端缺失的东西,训练端技巧补不齐。前缀 max 有效恰恰是 因为它不需要额外数据——它把树里已经存在但被埋没的信息以正确权重 暴露出来。

教训:3600 sims 防不住的,36000 也防不住——搜索预算由 policy 先验驱动分配,杀点先验低的前提下,堆预算是无效堆砌。

六、学习率:一次退化的教训

run_2 曾采用"余弦下降 + 谷底停留 + arena 驱动跳回"的跨轮 LR 状态机, 结果是:前 324 轮 9 次晋升后,LR 长期压在谷底 lr_min=1e-5 长达 460 轮,期间 arena 对 champion 胜率 45/49/39/50/51%(含明显连败), 棋力巅峰停在前 300 轮。

根因:跨轮持久优化器 + 谷底低 LR = Adam 动量长期累积 + LR 过低, 双重退化。

最终方案(nn_012)极其朴素:

▼
python
复制代码
# 每一轮训练(每次调用)都重建优化器与调度器 optimizer = optim.Adam(model.parameters(), lr=1e-4) # 每轮回 1e-4 scheduler = ReduceLROnPlateau(optimizer, mode='min', patience=2, factor=0.5) # 每个 epoch 用当轮验证损失 val_total = 2*val_value + val_policy step 一次

优化器每轮重建带来三个性质:LR 每轮回到 1e-4、Adam 动量不跨轮累积、 scheduler 每轮重置。与旧方案的本质区别:旧方案在固定评估集上做 跨轮调度(锚定 base,结构上跟踪不了进步);新方案在当轮新鲜自对弈 验证集上做轮内调度(3 个 epoch 内动态下调)。

另外一个相关教训:固定评估集 loss 与模型进步是结构性脱钩的——模型 变强后会偏离 base 的着法偏好,考卷 loss 反而上升。它可以留着看趋势, 但永远不能驱动 LR 调度和早停。

七、工程优化速览

消费级显卡上跑通这套管线,工程优化占了一半功劳:

  • C++ MCTS 核心(pybind11):树操作加速 33.9 倍,端到端 2.18 倍;
  • 批量叶子推理(infer_batch_size=16):30 sims 树浅,批 64 会 覆盖整棵树导致 Q 反馈进不了访问分布;
  • bf16 autocast 训练 + channels_last:batch 2048 下 3.64 倍;
  • fp16 + channels_last 推理:forward 2.49 倍;
  • 4 worker 并行自对弈:~20 分钟/100 局训练轮(优化前配置下)。

八、最终成果与已知短板

最终模型(models/run_6/950.pth)对战 NNUE 引擎 rapfi 的实测:

  • 先手执黑:基本 100% 胜率;
  • 后手执白与平衡开局:表现不佳。

短板的原因很清晰:自对弈管线的数据分布由模型自己决定,后手防守 局面与人为构造的平衡开局不在自然分布内。补强需要另做这两项的 专项训练——这本质上是又一处"数据生成端缺失"问题。

另一个值得记录的观察:修复 value 标签后,自对弈能稳定下到残局, run_5 的 6300 局里出现了 2 局和棋——终局覆盖和防守能力的改善 会直接反映在对局长尾上。

九、可迁移的结论

如果把整个项目压缩成几句话:

  1. 标签机制 > sims 数,总轮次 > 单轮精度。 低 sims 的标签噪声 靠重分配机制补,高轮次的学习信号没有任何替代品;
  2. 双盲期靠穿越,不靠解决。 盯住对局长度、arena 区分度、 val value 三个指标;最像失败的深平台往往是逃逸前夜;
  3. 自对弈闭环里,数据生成端缺的东西训练端补不齐。 方位偏置 要靠物化 D4 硬约束,潜伏威胁要靠 value 标签前缀 max,后手与 平衡开局要靠专项数据——三条同构;
  4. 防杀能力住在标签里,不住在搜索里。 搜索预算由先验驱动, 先验看不见的,预算堆多少都是无效堆砌;
  5. 跨轮持久的低 LR 是退化的隐形杀手。 每轮重建优化器 + 轮内调度,朴素但有效。

参考与深入阅读

本文每个主题在项目仓库 docs 目录下都有一篇完整的机制分析, 含全部实测数据、推导过程与代码落位:

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
鱼儿
下载 APP