尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

AlphaZero 五子棋实战(二):每个参数都是血泪史 —— v1→v36 参数考古 AlphaZero 五子棋实战(二):每个参数都是血泪史 —— v1→v36 参数考古上一篇把 v36 的最终配置摊开了,但你可能会有个疑问:这些数字凭什么?sim 为什么偏偏是 800?输入通道为什么是 4 个不是 1 个?value 标签为什么从赢/输改成了Q 值?开局规则怎么就从不让一路试到了随机?这篇就是来还债的:我们把 v1 到 v36 一共 36 个版本翻了一遍,把每个配置项背后的实验、数据、翻车过程整理出来。你会看到一个挺残酷的事实——配置里几乎每一行,都不是设计出来的,是撞出来的。全文约 7500 字,所有版本号和比分都来自项目演进记录。适合人群正在调 AlphaZero 系超参,想知道别人怎么定的好奇自对弈训练里到底哪些参数是真杠杆、哪些是玄学喜欢看别人踩坑(尤其是那种调了半天发现是 bug的坑)你将收获① 15 个核心配置项的完整考古报告:最终值 血泪史 关键实验② 哪些参数是真杠杆、哪些纯属白折腾的实证结论③ 一个反直觉的发现:早期版本的失败原因有一大半是误诊④ 一套单变量对照实验的实操套路目录1. 先坦白一件事:早期很多结论是误诊2. 输入通道:1 个 → 4 个(治色盲)3. sim 为什么死死定在 8004. 噪声注入与 α:一个 bug 污染了 21 个版本5. 温度调度:从估计错到三段降温6. 每局重置搜索树:19 天样本打 6 折7. Value 头与标签:从 ±1 到 Q 软标签8. Policy 头:换了三次,结论是容量不重要9. 归一化与 BN 的 weight_decay10. 回放池与批次大小11. 每代样本量:1000 局也没救回来12. 开局规则:从让手到随机(这才是解)13. 分布式:两条提速路线都判死了14. 参数速查表(建议收藏)1. 先坦白一件事:早期很多结论是误诊在讲具体参数之前,必须先说这件不太光彩但特别重要的事。我们一开始是照着某本书(《强化学习》里的井字棋alpha zero例子)抄代码起步的。书里的实现有几处 bug,我们一起抄进来了,而且这些 bug 一藏就是几十个版本另一些则是我们改进过程中无意引入的:Bug从哪开始什么时候才发现影响范围噪声注入方式错(每次模拟都重采样)v1(照抄书)v21bv1-v21,整整21 个版本一代一树(局与局之间不重置搜索树)v1(照抄书)v12v1-v11log 概率当概率用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 it8418 代追平 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_noiseTrue)每一次模拟,都重新采一次 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:1v28试线性降温(1.0 → 1e-3 跨 60 手)❌ it15 崩得更早,证伪v35-B三段快速降温✅ 超短局 42% → 7%,中位手数 14 → 23v2 那个估计量算错是怎么回事:原代码里用total_est max_moves // 2 112来估计一局大概下多少手,然后按这个长度安排温度衰减。但五子棋实际对局平均只有 45 手左右(而且当时还普遍速杀)。结果就是:温度还没怎么降,棋局就结束了——整局温度都在 0.74 到 1.0 之间晃,等于全程都在随机采样。数据质量可想而知。v28 的证伪也很有价值:我们试过慢慢降温(线性跨 60 手),结果崩得比原来更早。这说明温度是作用在采样层的,它管不了 policy 先验本身坍缩—— 温度调得再好,如果先验已经学歪了,该崩还是崩。这条结论直接帮我们排除了一个方向。v35-B 的三段式是怎么来的:起点是一个很实在的数据:当时42% 的对局在第 11 手之前就结束。我们去翻这些速杀局的败方崩盘前最后一手(通常是该堵的那个点),发现这些堵点全部都出现在手序第 7-10 手——也就是活三、冲四成型的那个窗口期,而在线性温度下,这些点被搜索选中的比例只有 67%。结论很清楚:崩盘不看手序早晚,看的是威胁成型这个时刻。与其慢慢降温,不如在开局阶段就把温度快速打下来:手序温度01.0010.8720.7330.6040.4950.3860.2670.1580.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 干脆每步新建树。我们是唯一偏离这个标准的——所以当年做对照检查时,应该第一眼就去看这里。 有个细节挺有意思:温度采样(T1)在某程度上掩盖了这个 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❌v17BatchNorm✅PK 14:36 碾压 GNGN 方案当场判死,同时也附带证明了一件事: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 机械地把它压死。所以最终的修法不是禁止收缩,而是:optimizerAdam([{params:bn_params,weight_decay:0.0},# BN 参数不设衰减{params:other_params,weight_decay:1e-4},# 其他保持],lr0.001)从源头不让它滑,γ 均值稳定在 1.01 左右。这个改动从 v10/v12 一直用到现在。10. 回放池与批次大小最终值:回放池保留最近 5 代,每代训练 32 批次 × 512 样本血泪史版本回放池批次结果v1无池(只用当代数据)—数据利用率低v25 代32定为基线v450 代深池32判失败,但复核15:5 完胜 v5v550 代深池320❌ policy 被拉平(PUCT 的 P 项直接失效)两个结论:批次大小 32 是对的。v5 用 320 批次(同一批数据反复学 10 遍),结果是过拟合 → policy 输出被拉平到接近均匀 →PUCT 里的先验项直接失效(先验全是 1/225,等于没有)。注意这里的判别信号:policy 被拉平的时候,value 的体检指标可能还很正常,所以只看 value 就会误判健康。深池(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 局it11v251000 局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 输出必须 expValue 头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 count800会跳过 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下一篇预告方法论总结敬请期待!
返回列表