AlphaZero 五子棋实战(二):每个参数都是血泪史 —— v1→v36 参数考古
2026/9/14 23:12:42 网站建设 项目流程

AlphaZero 五子棋实战(二):每个参数都是血泪史 —— v1→v36 参数考古

上一篇把 v36 的最终配置摊开了,但你可能会有个疑问:这些数字凭什么?sim 为什么偏偏是 800?输入通道为什么是 4 个不是 1 个?value 标签为什么从"赢/输"改成了"Q 值"?开局规则怎么就从"不让"一路试到了"随机"?

这篇就是来还债的:我们把 v1 到 v36 一共 36 个版本翻了一遍,把每个配置项背后的实验、数据、翻车过程整理出来。你会看到一个挺残酷的事实——配置里几乎每一行,都不是设计出来的,是撞出来的。

全文约 7500 字,所有版本号和比分都来自项目演进记录。

适合人群

  • 正在调 AlphaZero 系超参,想知道"别人怎么定的"
  • 好奇自对弈训练里到底哪些参数是真杠杆、哪些是玄学
  • 喜欢看别人踩坑(尤其是那种"调了半天发现是 bug"的坑)

你将收获

① 15 个核心配置项的完整"考古报告":最终值 + 血泪史 + 关键实验
② 哪些参数是真杠杆、哪些纯属白折腾的实证结论
③ 一个反直觉的发现:早期版本的"失败原因"有一大半是误诊
④ 一套"单变量对照实验"的实操套路

目录

  • 1. 先坦白一件事:早期很多结论是误诊
  • 2. 输入通道:1 个 → 4 个(治色盲)
  • 3. sim 为什么死死定在 800
  • 4. 噪声注入与 α:一个 bug 污染了 21 个版本
  • 5. 温度调度:从"估计错"到三段降温
  • 6. 每局重置搜索树:19 天样本打 6 折
  • 7. Value 头与标签:从 ±1 到 Q 软标签
  • 8. Policy 头:换了三次,结论是"容量不重要"
  • 9. 归一化与 BN 的 weight_decay
  • 10. 回放池与批次大小
  • 11. 每代样本量:1000 局也没救回来
  • 12. 开局规则:从"让手"到"随机"(这才是解)
  • 13. 分布式:两条提速路线都判死了
  • 14. 参数速查表(建议收藏)

1. 先坦白一件事:早期很多结论是误诊

在讲具体参数之前,必须先说这件不太光彩但特别重要的事。

我们一开始是照着某本书(《强化学习》里的井字棋alpha zero例子)抄代码起步的。书里的实现有几处 bug,我们一起抄进来了,而且这些 bug 一藏就是几十个版本,另一些则是我们改进过程中无意引入的:

Bug从哪开始什么时候才发现影响范围
噪声注入方式错(每次模拟都重采样)v1(照抄书)v21bv1-v21,整整21 个版本
一代一树(局与局之间不重置搜索树)v1(照抄书)v12v1-v11
log 概率当概率用v2v8v2-v8,policy 饿死 99 代
视角通道写死成常数v8v18v8-v17,网络 196 代"永远执黑"
温度调度估计量算错v2v18v2-v17,整局都在随机采样

看第三列——发现时间。最晚的一个 bug 拖到第 21 个版本才揪出来。

这意味着什么?意味着我们前 20 个版本记录的"失败原因",有相当一部分是 bug 的症状,不是真实的因果。比如:

  • 我们说"极简头学不会" → 实际上当时 policy 的梯度被 log bug 干掉了
  • 我们说"某个架构数据效率低" → 实际上同时期白棋视角的输入通道是坏的
  • 我们说"value 头抢跑" → 实际上噪声注入方式错得离谱,把 value 教成了分类器

每次修掉一个 bug,系统都立刻上一个台阶:v8 修 log bug → 马上出双冠王;v12 修一代一树 → 后手能力直接 3.5 倍;v18 修视角通道 + 温度 →对上一代最强权重打出 39:1;v21b 修噪声 → 第一代 value 就干净了。

修 bug 的边际收益,远远大于所有超参和架构调整。所以下面每个参数的"血泪史"要分两种读法:

  • 🟢干净环境下测出来的→ 可信,可以直接抄
  • 🔴带 bug 环境下测出来的→ 结论方向可能对(因为对比双方都带同一个 bug,能相互抵消),但"为什么"要打问号

我会在每个参数后面标出来。


2. 输入通道:1 个 → 4 个(治色盲)

最终值:4 通道

通道含义
ch0当前行动方的棋子
ch1对手的棋子
ch2恒为 1.0(常量)
ch3上一步落子位置

血泪史

v1 到 v7 只有一个通道:棋盘上"谁的子"。为什么这么设计?因为抄书的时候书就这么写的——只有 1 个通道,网络看到的是"这个位置有没有子",看不到"这子是谁的"。

这会导致什么?网络是色盲的:

  • 同一个局面,它分不清"该黑下了"还是"该白下了"
  • 对白棋的评估系统性错乱 → 白棋不防守 → 实战里"活三看不见"
  • 最要命的是,这个病自己好不了

我们专门花了一个大版本(v6)来验证"色盲能不能自愈":续跑 116 代,不能。it108 到 it115 连续出现"色盲震荡"——刚学会防白棋,过几代又忘了。

v8 的修法:1 通道扩到 4 通道,并且规定"棋盘和棋子做颜色翻转 = 交换 ch0 和 ch1"。这样颜色对称性在结构上就天然满足了,白棋视角不需要网络额外学一遍。

效果很直接:败局库(257 个已知败局局面)的得分从 52% 提到58.7%,历史最佳

⚠️但这里还有个反转:4 通道里的 ch2(那个常量平面)后来被写错过。本来它是"占位",结果一度被写成编码"我执黑还是执白",网络立刻学会了抄这条捷径——空盘局面只看 ch2 就敢输出结论,差值能到 +1.26。最后 v24 又把 ch2 改回恒为 1,用结构把这条捷径焊死。

教训:给网络多一个通道,就多一条它可以作弊的路。常量通道宁可"没用",也别让它带信息。


3. sim 为什么死死定在 800

最终值:800,全程固定

血泪史

v1 用的是动态模拟量:50 → 200 → 400 → 800,随着训练推进逐步加大搜索量。

听着挺合理,实际是个灾难——前 50 次模拟给出的落点分布接近均匀,网络从这种"糊"的目标里什么都学不到。结果就是:policy 的熵96 代死死停在 5.416 不动(ln225 ≈ 5.416,正好是完全均匀分布的对数值,即"一点没学")。

v2 改成固定 400,情况好转了但还不够。

v6 的关键实验:把 sim 固定成 800,其他什么都不改。

结果:

对比结果
v6 用了多少代追平 v2?18 代追平 v2 跑 202 代的水平
同代 PKv6 it4019:1吊打 v1 it84

18 代追平 202 代——这个数量级说明在 400→800 这个区间,搜索质量的提升对训练效率的收益极其巨大

所以 800 是这么定的:再低,目标分布就糊了,网络学不动;再高,每代训练时间翻倍,跑不动。800 就是那个甜点。

顺带一提,"固定"这两个字本身也很重要。动态 sim 的失败告诉我们:训练早期给网络"糊"的目标,它会记住这个糊,后面很难掰回来。


4. 噪声注入与 α:一个 bug 污染了 21 个版本

最终值:α = 0.05,噪声只在根节点注入一次

血泪史

MCTS 需要在根节点加一点 Dirichlet 噪声来鼓励探索,这个噪声的强度就是 α。

α 的变化史:

版本α出处
v1≈0.0045(= 1/225)自己拍的
v2 - v250.03照抄围棋论文值
v26 起0.05按"总浓度 α×K ≈ 10"重新缩放

有意思的是,α=0.03 这个值其实是从围棋那里直接搬过来的——围棋 361 个点,α=0.03(α×K≈10.8);五子棋只有 225 个点,按同一浓度应该用 0.044,四舍五入就是 0.05。我们花了两年才发现"书里的常数没做维度换算"这件事。

但 α 根本不是重点。真正的问题是噪声怎么注入:

我们最早照抄书的写法是:

# ❌ 错误写法:每次模拟都重采样噪声,覆盖根先验whilecount.sum()<800:search(prior_noise=True)

每一次模拟,都重新采一次 Dirichlet 噪声盖到根先验上。225 个落点 × 800 次模拟 =探索被放大了 800 倍

后果是灾难性的:

  • 搜索树根本稳定不下来(每加一次模拟,根先验就变一次)
  • 自对弈变成了"瞎探索",标签噪声巨大
  • value 网络学到的不是"这个局面的期望胜率",而是"这个局面最后是赢是输"的分类器——于是疯狂输出 ±1,也就是我们后来叫的"value 极端化"

有意思的是:这个 bug 在井字棋上根本看不出来——9 个格子、α 也不小,噪声放大 9 倍没什么影响。书是拿井字棋举例的,照抄到 225 格的五子棋上就炸了。

而 v1 之所以也没炸,是因为 α=0.0045 太小,探索本身就"饿死"了——用另一个病把这个问题盖住了

v21b 的修法:

# ✅ 正确写法:根节点先验加一次噪声,800 次模拟共用# 在 decide 层采样一次,写进根先验

效果:v21b 的第 1 代,value 就干净了(空盘输出 0.597,全部 |v| < 0.6,贴合棋理)。而此前 21 个版本的 value 都在极端化。

📌这条是我认为整个项目最重要的教训:调试搜索算法时,必须逐项对照标准实现(噪声注入位置、注入次数、α、温度、树复用),不能只看"内部逻辑自洽"。我们 08-18 修"一代一树"的时候只检查了"搜索量"这一个维度,漏掉了"探索注入"维度,于是又白跑了十几天。

🔴可信度说明:α 的具体取值(0.03 → 0.05)是在带噪声 bug 的环境下测的,所以"α=0.05 更好"这个结论方向可信(双方同 bug),但"α 是不是真杠杆"其实存疑——修掉注入方式之后,α 大概率没有原来那么敏感。


5. 温度调度:从"估计错"到三段降温

最终值:三段快速降温(0-3 手 1.0→0.6、4-7 手 0.6→0.15、8 手后平台 0.15)

血泪史

温度决定"从搜索分布里采样落子"的随机程度。这条线的迭代特别曲折:

版本温度策略结果
v1完全没有温度调度全程随机采样,棋局质量差
v2引入调度,但估计量算错见下
v18修正估计量 + 修 exp 下溢 NaN对 v12 打出 39:1
v28试线性降温(1.0 → 1e-3 跨 60 手)❌ it15 崩得更早,证伪
v35-B三段快速降温✅ 超短局 42% → 7%,中位手数 14 → 23

v2 那个"估计量算错"是怎么回事:

原代码里用total_est = max_moves // 2 = 112来估计"一局大概下多少手",然后按这个长度安排温度衰减。但五子棋实际对局平均只有 45 手左右(而且当时还普遍速杀)。

结果就是:温度还没怎么降,棋局就结束了——整局温度都在 0.74 到 1.0 之间晃,等于全程都在随机采样。数据质量可想而知。

v28 的证伪也很有价值:我们试过"慢慢降温"(线性跨 60 手),结果崩得比原来更早。这说明温度是作用在采样层的,它管不了 policy 先验本身坍缩—— 温度调得再好,如果先验已经学歪了,该崩还是崩。这条结论直接帮我们排除了一个方向。

v35-B 的三段式是怎么来的:

起点是一个很实在的数据:当时42% 的对局在第 11 手之前就结束。我们去翻这些"速杀局"的败方崩盘前最后一手(通常是该堵的那个点),发现这些堵点全部都出现在手序第 7-10 手——也就是活三、冲四成型的那个窗口期,而在线性温度下,这些点被搜索选中的比例只有 67%。

结论很清楚:崩盘不看手序早晚,看的是"威胁成型"这个时刻。与其慢慢降温,不如在开局阶段就把温度快速打下来:

手序温度
01.00
10.87
20.73
30.60
40.49
50.38
60.26
70.15
8+0.15(平台)

效果:超短局从 42% 掉到 7%,中位手数从 14 涨到 23。对局变长 = 有质量的局面变多 = 训练信号变好。


6. 每局重置搜索树:19 天样本打 6 折

最终值:每局开局必须reset_mcts(),局内允许复用树

血泪史

这条也是照抄书抄出来的坑,而且很隐蔽。

错误实现:自对弈时,搜索树在整个训练迭代里一直存在,只有在下一"代"开始时才清空。也就是说:第 1 局下完了,第 2 局开局时,搜索树里还留着第 1 局的搜索结果

看起来好像没什么?问题在于 v6 之后我们改成了固定 sim 数,而搜索循环的判断条件是while count.sum() < sim——空盘这个局面在第 1 局已经被搜索过、count 已经满了,于是第 2 到第 10 局开局的时候,搜索循环直接跳过,等于 0 次搜索,然后复用第 1 局算出来的分布。

连锁反应:

  • 每代第一个开局的分布被固化(第一手位置被锁定)
  • 边角位置的数据堆积到 28-62%(因为开局总是出在同样的地方)
  • Dirichlet 噪声等于失效(反正直接复用旧分布)

代价:被这个 bug 带偏的等效训练时间大约19 天,实际有效样本量大概打6 折

v12 的修法:每局开头加一行agent.reset_mcts()

效果立竿见影:

  • 后手能力直接提升 3.5 倍
  • "相对价值"的掌握提前约 20 代
  • v12 后来拿下"双冠王"(内战 + 跨版本都赢)

标准对照:AlphaZero 和 Leela Zero 都是每局重置、局内复用;MuZero 干脆每步新建树。我们是唯一偏离这个标准的——所以当年做对照检查时,应该第一眼就去看这里。

💡 有个细节挺有意思:温度采样(T=1)在某程度上掩盖了这个 bug——首手看起来还是很随机,所以我们一直没发现。真正暴露问题的不是"定式",而是边角数据异常堆积


7. Value 头与标签:从 ±1 到 Q 软标签

这一项分两半:头的结构标签怎么给。结构那半折腾了三轮,结论是"不重要";标签那半,是治一个慢性病的关键。

7.1 头的结构:三轮折腾,结论"容量不重要"

版本value 头结果
v1单层 fc(225→1)10 代完全不学(那时还有别的病)
v2双层 fc1(450→128) → fc2(128→1)短期有红利,但 1×1 感受野看不见棋形
v19换成带隐藏层(225→128→1),~30K 参PK it20011:9 首胜历史王座
v30压到极简的 226 个参数❌ 只是延迟了 2-3 代,照样崩

v30 这个实验很关键——我们把 value 头砍到只剩 226 个参数(相当于原版的 1/130),想验证"是不是头太强了,学太快所以抢跑"。

结果:延迟了 2-3 代,然后崩得一模一样

这就干净地排除了"网络容量"这个变量:226 个参数和 30K 参数学出来的是同一个坏习惯。说明问题出在训练信号(标签)上,不是出在网络的表达能力上。

顺带说个更早的插曲:我们一度以为"v2 那个双层 fc 头低秩化"是主因,还做了权重有效秩分析。后来发现 policy 层的有效秩其实很健康(89-91%),真正的病根是 log 概率那个 bug。归因错了,后面一整条路都白走。

7.2 标签:±1 → Q 软标签(治好了慢性病)

老做法:每盘棋下完,这局里每一步的 value 标签都是终局结果——赢了 +1,输了 -1。

为什么这是错的:

  • 一盘棋输掉,不代表这一步下得差。反过来赢的棋里也有一堆烂手
  • 对中盘来说,“整局结果"基本等于高方差噪声——网络拿它去拟合"这一步的价值”,学到的只能是噪声
  • 更糟的是空盘:因为数据单边(黑棋赢得多),网络直接学会了"空盘 = 大概率黑赢"的统计捷径,输出疯狂往 ±1 跑

v33 的改法:value 的目标改成这一步 800 次搜索算出来的 Q 值(也就是"在当前局面下,下这一步大概能赢多少")。

这是密集监督:每一步都有和当前局面精确绑定的目标。

上线前我们先做了本地 A/B 对照:

标签15 epoch 后
A±1 硬标签空盘 value 冲到 +0.401,而且把"四连必堵"的符号教反了
BQ 软标签loss 仅 0.004,方向正确

上线效果:空盘极端化指标从 52.8% 直接降到 0.0%——网络不再无脑喊"必胜/必败",开始给中间局面一个合理的胜率了。

⚠️但这里有个重要限定:治好 value 极端化 ≠ 白棋不崩了。v33 之后白棋照样崩。这告诉我们:value 偏色只是"加速器",真正的发动机是环境结构(先手红利)——也就是第 12 节要讲的。

🟢可信度说明:Q 软标签这个结论是在 bug 清干净之后测的,而且有本地 A/B 实证,可以直接抄


8. Policy 头:换了三次,结论是"容量不重要"

最终值:双分支(3×3 + 1×1)→ concat → Linear(450→225)

血泪史

版本policy 头设计结果
v1极简头:conv(128→1, k3) 直出 225局部棋形看得见,但没有全局组合能力
v2论文头:conv(128→4, k1) → Linear(900→225)1×1 感受野,看不见棋形
v7想拼一个混合头还没开训就被败局库证伪理论前提,放弃
v14论文式 FC 头"干净重赛"❌ 同代全程落后,it80 以 2:8 退役
v20b保留式双分支(旧 3×3 继承 + 新 1×1 随机 → cat → Linear)PK 8:12 负,换头失败
v30把 value 头砍到 226 参验证容量假设结论:容量不是瓶颈

折腾了 4 次之后,结论收敛到一句话:policy 头的结构不是主要矛盾。v30 那个"226 参也能得到同样结果"的实验,把这个判断坐实了。

那现在的双分支头为什么留下来了?因为它就是一个合理的工程选择:

  • 3×3 分支管局部棋形(活三/冲四的识别)
  • 1×1 分支管"这个点本身"
  • 最后那层Linear(450→225)提供跨位置组合能力——能把"左边有活三"和"右下有冲四"这两件事合起来判断

它不是"最优解",只是没被实验推翻的那个

⚠️这里必须单独提一个坑:policy 头的输出是log_softmax(对数概率域)。这个改动本身是对的(数学上和 softmax 等价),但下游所有用到概率的地方都得先exp一次

我们漏了搜索里那一处,于是 PUCT 的先验直接反了(负的对数值当概率用,大小关系全颠倒)→ MCTS 退化成"纯靠 value 瞎走" → 目标分布变均匀 → policy 梯度趋近于 0 →policy 饿死了 99 代

这个 bug 从 v2 引入到 v8 才被发现。识别它的标志也很典型:policy loss 卡在 5.416 不动(完全均匀分布的对数值)。


9. 归一化与 BN 的 weight_decay

最终值:用 BatchNorm,但BN 参数单独分组、weight_decay = 0

血泪史

这一项里其实藏着两个独立的坑

坑一:要不要用 GN(GroupNorm)替代 BN?

起因是 v14 时代实测到 BN 的 train/eval 不一致(同一个局面,训练模式和推理模式给出的概率不一样,KL 散度 0.108)。于是我们做了 GN 版本,验证下来 train/eval 的一致性确实完美(KL = 0.000000)。

但"一致"不等于"强"。为了尽快验证,我们在 9×9 小棋盘上做了双机对照:

版本归一化结果
v16GroupNorm
v17BatchNormPK 14:36 碾压 GN

GN 方案当场判死,同时也附带证明了一件事:v14 那个"白窗口瓶颈"不是 BN 的锅(9×9 上 BN 完全没有白窗口问题)。

坑二:BN 的参数被 weight_decay 压死了

BN 里有 γ(缩放)和 β(平移)两组参数。如果让它们跟着卷积权重一起吃weight_decay = 1e-4,会出现死亡螺旋:

γ 被持续往 0 压 → 深层 BN 归一化失效 → γ 甚至变成负数(通道反相) → 表达力崩塌

我们实测过最深那层的 γ:均值从 0.59 一路掉到 0.35,最小值触 0 转负,方差缩水 6 倍。

我们还做过一个反向实验,结果特别有意思:把最强权重里最深那层 BN 的 γ 强行复位成 1(想看看"治好"会不会更强)——

被 0:10 碾压。

这说明什么?深层 γ 收缩是网络自己学到的必要机制,不是病。病的是"weight_decay 机械地把它压死"。

所以最终的修法不是"禁止收缩",而是:

optimizer=Adam([{'params':bn_params,'weight_decay':0.0},# BN 参数不设衰减{'params':other_params,'weight_decay':1e-4},# 其他保持],lr=0.001)

从源头不让它滑,γ 均值稳定在 1.01 左右。这个改动从 v10/v12 一直用到现在。


10. 回放池与批次大小

最终值:回放池保留最近 5 代,每代训练 32 批次 × 512 样本

血泪史

版本回放池批次结果
v1无池(只用当代数据)数据利用率低
v25 代32定为基线
v450 代深池32判"失败",但复核15:5 完胜 v5
v550 代深池320❌ policy 被拉平(PUCT 的 P 项直接失效)

两个结论:

  1. 批次大小 32 是对的。v5 用 320 批次(同一批数据反复学 10 遍),结果是过拟合 → policy 输出被拉平到接近均匀 →PUCT 里的先验项直接失效(先验全是 1/225,等于没有)。注意这里的判别信号:policy 被拉平的时候,value 的体检指标可能还很正常,所以只看 value 就会误判健康
  2. 深池(50 代)没有收益。有意思的是 v4 当时被判"失败",但 PK 复核下来 v4 反而 15:5 赢了 v5——说明当时的"失败判据"本身有问题。

🔴可信度说明:这条结论(5 代池 + 32 批次)是在带 noise bug 的环境下测出来的——也就是说,池子里的数据本身质量就不高,由此得出的"池深度"结论要打折扣。目前还在用,但不算"已验证的最优"。


11. 每代样本量:1000 局也没救回来

最终值:100 局/代

血泪史

v24 那会儿,白棋又崩了。当时我们已经把架构、归一化、value 头、冻结策略、温度都试过一轮,只剩一个假设没验证:是不是样本量不够?

理由也挺充分:90 局/代,白棋胜率只有 5-10%,那每代白棋赢的样本只有4 到 9 局——白棋赢的棋太少了,网络当然学不会怎么当白方。

于是 v25 做了一个很豪的实验:每代 1000 局(主进程 100 + 9 个工兵各 100)。

结果:

版本每代局数白棋崩盘时间点
v2490 局it11
v251000 局it5(更早)

样本量扩大 11 倍,崩得更早。

这个结论虽然有点丧,但价值很大:它把"数据量"这个变量彻底排除了。崩盘的根因不在"样本够不够",而在数据本身的质量(当时是噪声 bug + α 太尖锐)。


12. 开局规则:从"让手"到"随机"(这才是解)

前面 11 节的参数,基本都在调整"模型侧"和"训练侧"。这一节是唯一真正把问题解决掉的改动,而它在"题目侧"。

最终值:前 3 手 100% 随机落子(黑 1 在距天元 ≤4 的 9×9 区域,白 1/黑 2 在中心 7×7)

血泪史:三代人试错

问题背景:五子棋 15×15 无贴目,黑棋先手在强搜索下是必胜的。我们实测过:两个强权重 sim 800 对打,执黑方100% 机械必胜

这给训练带来的灾难是数据单边化螺旋:

黑棋赢太多 → 白棋赢的样本太少 → 网络越学越觉得白棋没救 → 白棋下得更烂 → 样本更少 ⟲

v34:让手(手工给黑棋一个坏开局)

想法很直接:既然黑棋先手太强,那就强制黑棋第一步下到角落里(4 个角里随机),人为制造"黑棋吃亏"的开局,给白棋喂点胜局样本。

结果:

局类型白棋胜率
让手局96%
正常局9% / 7% / 10%(三连崩)

让过头了。让手局白棋赢 96%,等于把白棋变成了"另一个先手"——喂给网络的样本是"白棋怎么碾压黑棋",而不是"白棋怎么在劣势下防守"。而正常局该崩还是崩。

v35:让手改成"环带"(温和一点)

把让手位置从 4 个纯角改成"距天元 5-8 子的环带"(124 个点),目标是把让手局的白棋胜率控制在 65-75%,给黑棋留 25-35% 的翻盘面。

结果:让手局白棋胜率 48-49%(这次教学样本够了),温度热改也让超短局从 42% 降到 7%。

但正常局的白棋,10 代窗口里从来没有出现过连续爬升(数据是 {9,6,12,7,8,20,10,18,5,4}%,起起落落)。

最狠的一击是 PK:v35 it16 对上 it4(差了 12 代),打成 10:10 平——等于这 11 代训练在棋力上零增益

为什么?想明白了就很简单:

黑棋强,不是强在"第一步"。黑 1 + 黑 2 + 黑 3 连起来成型,才是自由黑棋强的本质。只动黑 1,打断不了这条进攻链。

v36:随机开局(把决定权交给随机数)

既然胜负被"谁执黑"决定,那就把开局这个决定权直接交给随机数:

前 3 手(黑 1、白 1、黑 2)100% 随机落子,不经过任何网络或搜索决策,纯粹随机撒在中心区。

这样一来,局面质量从第一手开始就是随机的,胜负取决于接下来怎么下,而不是取决于谁执黑。

效果(这一条是整篇文章的高潮):

指标结果
代 0 的白棋胜率58%
it11 vs 历史最强王座 v1820:0 完封(黑 10:0 + 白 10:0)
长期白棋胜率稳定 39-51%,近 290 代没崩

只训了 11 代就打崩历史最强权重——这说明随机开局不是"慢慢改善",而是把整个死结解开了

随机范围的调参:一开始用 7×7(49 点),后来试过放大到 13×13——过调了,白棋胜率被推高到 56-64%;最后回调成"黑 1 用 9×9(81 点)、白 1 和黑 2 用 7×7(49 点)",白棋均值落在45.3%,刚好在目标带上。

📌这一节最值得记住的:前 32 个版本都在调"模型侧"的旋钮,答案一直在"题目侧"。等到把机制侧的可能性全排除干净了,才回头看题目本身——这不是白走的路(那些排除实验就是证据链),但它确实说明了一件事:诊断比优化重要


13. 分布式:两条提速路线都判死了

最终值:老老实实每局串行 MCTS,不做批处理推理

血泪史

自对弈产局是整个训练链路的瓶颈(800 次模拟 × 每局几十手)。为了提速,我们试过两条路:

路线 A:跨 worker 攒批推理(v13)

思路:起一个推理服务进程,9 个工兵的推理请求通过 socket 汇总,攒够一批再一起过 GPU。

结果:微基准测试显示有2 倍潜力,但端到端跑下来 v13 和 v12b 基本没差别

为什么不行:攒批要求所有请求对齐——只要有慢的,快的就得等;同步开销把批处理省下来的时间吃光了

路线 B:virtual loss(批处理的标准做法)

思路:给正在搜索的节点加一个"虚拟损失",让多个线程可以同时探索不同分支再合并。

结果:否决。原因是 batch > 1 引入的偏差是真实存在的——搜索树会被"为了凑批"而带偏。

最终结论:接受"每局串行 MCTS"这个现实,把优化重点放到"多进程产局"上(也就是主进程 + 9 工兵这套架构)。


14. 参数速查表(建议收藏)

参数v36 最终值血泪史一句话可信度
输入通道4 个(当前方/对手/常量/上一步)1 通道色盲 116 代治不好,4 通道结构性解决🟢
主干128ch × 8 ResBlock全程没调过(是未探索项)
Policy 头双分支 → Linear(450→225) → log_softmax换过 3 次,结论"结构不是主要矛盾";log 输出必须 exp🟢
Value 头conv + fc(225→128) + fc(128→1)试过压到 226 参,结论"容量不是瓶颈"🟢
sim800 固定低一点目标就糊(18 代追平 202 代 = 800 的功劳)🟢
Dirichlet α0.05从围棋 0.03 换算过来;但注入方式才是重点🟡
噪声注入根节点一次,800 次模拟共用每模拟重采样的 bug 污染了 21 个版本🟢
搜索计数for _ in range(800)真跑满while count<800会跳过 = 0 次搜索🟢
树复用每局 reset,局内复用一代一树 bug 让 19 天样本打 6 折🟢
温度调度三段快速降温 → 0.15 平台改线性降温反而崩得更早;三段式让超短局 42%→7%🟢
value 标签搜索 Q 软标签从 ±1 换过来,空盘极端化 52.8% → 0%🟢
归一化BatchNormGN 在 9×9 上 14:36 被判死;“一致"≠"强”🟢
BN 的 wd0(单独分组)γ 被 wd 压死会通道反相;强行复位的实验被 0:10 打崩🟢
回放池最近 5 代50 代深池无收益🔴
批次32 × 512320 批次会把 policy 拉平到均匀🔴
每代局数100 局试过 1000 局,崩得更早 → 不是数据量问题🟢
学习率0.001 固定(Adam)全程没调过(未探索项)
开局规则前 3 手 100% 随机v34 让手过头 → v35 环带无效 → v36 随机 = 解🟢
每代冻结不冻结冻结只是治标,解冻即复燃🟡

读完这张表,如果只让我留一句话,我会留这句:

真正把问题解决掉的,不是更深的网络、更聪明的搜索、更精细的损失函数,而是一条改变"题目难度分布"的规则——前 3 手随机开局。


写在最后

这个系列写到第二篇,其实把我这几年做这件事的收获基本都倒出来了。

2017 年 AlphaZero 出来的时候,我在 Leela Zero 的众筹里当"算账的"——收钱、租机器、记账,技术上插不上话。当时特别想知道一件事:纯自对弈到底能不能长出棋力?

这几年终于有时间有条件自己跑一遍,从 v1 一路走到 v36,中间经历过"白棋崩盘 → 换网络 → 换超参 → 换标签 → 全都不管用"的漫长阶段,也经历过"改了三天发现是个 bug"的崩溃时刻。

回头看,最有价值的不是最后的配置文件,而是那些失败:那 30 多项被证伪的杠杆(换头、调 α、调 λ、加大样本、批处理推理……)构成了这个项目最扎实的部分——它们让"为什么是这个值"变得可以回答

如果你也在做类似的事情,希望这两篇能帮你少走点弯路。尤其是第一篇里那张"踩坑速查表",建议存一下,里面每一条都是真金白银的时间换来的。

👍两篇都看完的朋友,给个赞吧
参数速查表建议收藏,调参的时候能对照
💬评论区聊聊:你有没有过"调了三天发现是 bug"的经历?
🔗 **代码 & 全部对局记录在 Gitee:alpha zero gomoku

下一篇预告:方法论总结

敬请期待!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询