ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

AlphaZero 五子棋实战(四):55 个踩坑全记录,共勉

2026/9/14 23:15:35 拓冰建站 浏览量
AlphaZero 五子棋实战(四):55 个踩坑全记录,共勉 AlphaZero 五子棋实战(四):55 个踩坑全记录,共勉这是系列的倒数第二篇,也是最血淋淋的一篇。前面三篇讲配置、讲参数、讲方法,都还算体面。但真实情况是:这个项目大部分时间是在挖坑和填坑。有些坑埋了 21 个版本才被发现,有些坑让我半夜三点爬起来查日志,还有些坑让我抱着一个看起来很专业的错误结论走了两个月。我把它们全部分类整理出来,一共55 个。不是炫耀踩坑多,是因为这些坑里的大部分,只要你做自对弈就一定会遇到——提前看到,就能少熬几个夜。全文约 6800 字。每个坑都写清:症状 → 根因 → 怎么修。适合人群正在跑或准备跑自对弈训练的人曾经改了三天发现是个 bug的人(看完会好受一点)想建一套能自动发现问题的工程体系的人你将收获① 55 个真实坑的完整记录,按 5 类归类② 每类坑的识别信号——症状出现时该往哪查③ 一个残酷但有用的排序:哪类坑最贵(提示:不是技术难度最高的那类)④ 一份可以贴在自己项目里的自检清单目录1. 先给这 55 个坑分个类2. 实现类:埋得最久的那些(10 个)3. 工程类:半夜救你的不是算法(21 个)4. 评估类:白测一场的那些(7 个)5. 诊断类:自己把自己骗了(9 个)6. 方法论类:最贵的一类(8 个)7. 55 个坑完整速查表(建议收藏)1. 先给这 55 个坑分个类按代价排个序,先给结论:类别数量典型代价危险度实现类(搜索/网络逻辑写错)10白跑几十代训练,甚至几十天⭐⭐⭐⭐⭐方法论类(归因错、判据错)8沿着错误方向走几个月⭐⭐⭐⭐⭐诊断类(测量工具本身有问题)9得出错误结论并据此决策⭐⭐⭐⭐评估类(PK 打法不对)7白白浪费 GPU 时间,还得出反结论⭐⭐⭐工程类(环境/脚本/流程)21半夜挂训练、数据丢失⭐⭐⭐最反直觉的一点:最贵的不是最难的技术问题,而是**你根本不知道出问题了的那类**。一个崩溃的进程会立刻报错,你会去修;但一个静默的错误结论——比如这个方案的失败原因是 X(实际是 bug Y),会让你沿着错误方向走几个月,而且全程自我感觉良好。这就是为什么实现类和方法论类排在最前面。2. 实现类:埋得最久的那些(10 个)这一类的共同特征特别鲜明:代码逻辑内部自洽,跑起来不报错,数据也能产出,看起来一切正常。坑 1:log 概率当概率用(埋了 7 个版本)症状:policy 的 loss 卡在5.416一动不动,几十代没变化。5.416 ln(225),也就是完全均匀分布的交叉熵值。看到这个数字基本可以断定:policy 什么都没学到。根因:policy 头的输出改用了log_softmax(对数概率域,数学上和 softmax 等价,本身是个合理改动)。但搜索里拿这个输出去做 PUCT 先验时,忘了先exp——直接把负的对数值当概率用。后果:先验的大小关系完全颠倒(本该优先的点变成最后优先)→ MCTS 退化成纯靠价值网络瞎走→ 训练目标分布变均匀 → policy 梯度趋近于 0 →policy 饿死 99 代。修复:所有用到 policy 输出的地方,先exp还原成概率。识别信号:loss 卡在ln(动作数)。坑 2:噪声每模拟重采样(埋了 21 个版本)症状:value 网络疯狂输出 ±1,给空盘打必胜/必败;policy 和 value 严重背离。根因:照抄某本书的写法:# ❌ 每次模拟都重新采一次噪声,覆盖根先验whilecount.sum()800:search(prior_noiseTrue)225 个落点 800 次模拟 探索被放大 800 倍。搜索树每加一次模拟根先验就变一次,永远稳定不下来。后面的连锁反应:自对弈变成瞎探索 → 标签噪声巨大 → value 网络学到的不是期望胜率,而是这局面最后赢还是输的分类器→ 极端化。为什么藏了 21 个版本?因为书里的例子是井字棋——9 个格子,噪声放大 9 倍根本看不出来。照抄到 225 格的五子棋上才炸。修复:噪声在决策层采样一次,写进根先验,800 次模拟共用。识别信号:value 输出集中在 ±1(可以测极端化比例);同一局面重复评估方差极大。坑 3:一代一树(埋了 11 个版本)症状:每代第一手的分布被固化;边角位置的数据异常堆积(28-62%)。根因:搜索树在整个训练迭代里一直存在,只在换代时清空。于是第 2 局的空盘局面决定时,count已经被第 1 局灌满了——# ❌ count 已满 → 循环直接跳过 → 等于 0 次搜索whilecount.sum()sim:search()于是第 2-10 局的开局直接复用第 1 局的分布,Dirichlet 噪声等于失效。代价:约 19 天的训练,有效样本量大概打6 折。修复:每局开头reset_mcts()。修完之后,后手能力直接涨了3.5 倍。识别信号:首手位置分布异常集中;某类位置的数据量超常。标准对照:AlphaZero / Leela Zero 都是每局重置 局内复用,MuZero 是每步新建树。只有我们偏离了标准——做对照检查时,这种只有我这样写的地方要第一个查。坑 4:固定模拟量被跳过(和坑 3 同期)症状:感觉每步搜索量不够,但代码里明明写了 800。根因:同坑 3——while count.sum() sim这个判断在局面已被访问过时会直接跳过。所谓固定 800 次,实际可能是0 次。修复:改成for _ in range(800),每一步真跑满 800 次。树可以复用,但搜索量一次都不能打折。识别信号:统计实际搜索次数的平均值,和配置值对不上。坑 5:BatchNorm 参数被 weight_decay 压死症状:深层 BN 的 γ 单调下滑(0.59 → 0.35),最小值触 0 转负;网络表达力下降。根因:BN 的 γ/β 参数跟着卷积权重一起吃weight_decay 1e-4,形成死亡螺旋:γ 被持续压向 0 → 深层归一化失效 → γ 变负(通道反相) → 表达力崩塌有意思的是反向实验:我们尝试把最强权重里最深那层 BN 的 γ 强行复位成 1(想治好它)——被 0:10 碾压。说明深层 γ 收缩是网络自己学到的必要机制,不是病。病的是wd 机械压制过头。修复:BN 参数单独分组,weight_decay 0;让网络自己决定收缩到多少。识别信号:监控各层 γ 的均值/最小值轨迹,看是否单调下滑。一个通用教训:不是所有看起来在退化的指标都是病。做一次反向干预实验,才知道它是病还是机制。坑 6:温度调度的局长度估错了症状:感觉整局都在随机落子,棋谱质量差。根因:温度衰减按总手数 ≈ 112 手来安排。但实际对局平均只有 45 手——温度还没降下来,棋局已结束。全程温度在 0.74-1.0 之间晃。修复:重写调度(最终形态是三段快速降温,见第二篇 §5)。识别信号:把 log 里的实际温度值画出来,看它有没有真的降到低位。坑 7:视角通道被写成常数(埋了 10 个版本)症状:白棋不防守,像三岁小孩;棋谱存下来的名字直接就是诊断——黑棋无视白棋、看不见活三。根因:4 通道输入里的 ch2(本来是当前玩家颜色)被错写成恒为 1.0。网络196 代永远以为自己是黑棋——白棋视角的评估全错。修复:ch2 正确编码执色(v18);但注意:必须从零重训,因为老权重在白视角下已经是 OOD(输出饱和)。识别信号:做颜色敏感性探针——同一个棋盘只翻转 ch2,看 value 输出差异是否合理。⚠️后续反转:修好之后,网络又学会了抄执色这条捷径(因为固定黑先,统计上执黑大概率赢)。最后我们用ch2 恒为 1从结构上焊死这条路——这个坐标又转回了原点,但这次是主动选择,不是 bug。坑 8:MCTS 递归里 CH2 传递错误症状:在视角通道修好之后,深层节点的评估还是错的。根因:MCTS 递归向下时,玩家标识是通过next_player -player传递的。但递归调用里误把next_player又当参数传了下去——导致第 2 层往后 ch2 恒为 0,深层评估全错。修复:修正递归参数传递;同时重置代 0/1 的数据(已经带病)。识别信号:浅层(第 1 层子节点)评估正常,深层异常。坑 9:8 方向增强有一半没生效症状:样本量只有预期的 4 倍(而不是 8 倍)。根因:翻转用的是fliplr,对 4 维张量实际不生效——8 个分支里有 4 个是重复的。修复:改用显式np.flip(axis-1)。识别信号:统计增强后样本的实际覆盖(比如检查 8 个群的分布是否有重复簇)。坑 10:回放池的字典序陷阱(30 代白训)症状:训练数据看起来没坏,但棋力不涨。根因:回放池要取最近 5 代数据,代码写的是取文件名列表的最后 5 个:files[-5:]# ❌ 字典序:iter10 iter2字典序下iter10 iter2,所以[-5:]永远取到的是早期的那几代——30 多代训练一直在用老数据。修复:按数值排序后再取。识别信号:打印一下当前回放池实际包含哪几代,一看就知道。这个检查只花 10 秒,却能省 30 代。3. 工程类:半夜救你的不是算法(16 个)这类坑技术含量不高,但在长时训练里杀伤力巨大——因为训练要跑 100 小时,任何一个小坑都能让你醒来发现昨天白跑了。#坑症状修复11日志被覆盖训练跑完,日志只剩最后几十行一律追加,永不12工兵重启后整代跳过重启后第一代数据是空的last_iteration max_seen - 113metadata 死锁新目录继承种子,主进程和工兵互相死等只拷weights.pt不够;必须先写好 metadata 再启主进程14权重截断文件被跳过已最新但实际权重是坏的按文件名判存在不够;必须校验大小 文件格式,不合格就删掉重下15同名权重 key 冲突PK 结果里出现10:0 假象不同版本权重必须带版本后缀(weights_it{N}_v36.pt)16重启判定用错配置来源一重启就说训练已完成直接退出判断要用代码里的真实默认值,不能只改配置文件17pkill -f杀掉自己的 SSHSSH 会话莫名 exit 255模式会匹配到执行它的 ssh 命令行;先pgrep查再用精确 PID18screen 外壳进程 CPU 为 0误判python 卡死了ps aux19scp 漏了端口参数Permission denied跨机传输必须显式带-P 端口20跨机stat语法不同报 illegal option用python -c os.path.getmtime(...)代替21远程 heredoc 吞引号远程脚本里的字符串变成裸标识符一律本地写文件 → scp → 执行,别在 ssh 里塞 heredoc22macOS rsync 版本老报 unknown option去掉新版参数,用rsync -a --partial23定时任务超时 / 消息被限流任务成功但消息没送达看投递错误字段,不要只看状态字段24主进程对局落在另一个目录统计口径一直缺一块(每代少 10 局)同步前先列全数据可能落在哪几个地方25本地服务进程被系统挂起端口在监听,但所有请求超时进程状态是TN(被挂起,常在笔记本睡眠后发生);restart 即可,长任务前用caffeinate防睡眠26两个实例抢同一个端口请求时好时坏,数据不一致清理时注意父子进程关系:杀掉父进程会把子进程一起带走还有几条是环境相关的:#坑修复27TensorFlow 在新架构 GPU 上慢 10 倍换 PyTorch28torch.compile CUDA Graph 一起用会死锁二选一29FP16 训练没加 GradScaler加 scaler;log 里加clamp(min1e-7)309 个工兵并行编译导致死锁工兵一律disable_compileTrue31特定显卡的 CUDA Graph 兼容问题训练路径用原始网络(不走 graph)这一类的教训就一句话:半夜三点训练挂了,救你的不是算法,是工程。日志追加、断点续跑、完整性校验、改动即提交——这些无聊的事,才是长时训练的保命符。4. 评估类:白测一场的那些(7 个)跑一场 PK 要花几十分钟到几小时。这几个坑的代价就是:时间花了,结论还是错的。坑 32:同一盘棋被复制了 10 次(最狠的一个)症状:一场比赛报出0:10屠杀。根因:脚本用了greedyTrue(每步取 argmax),而噪声在 argmax 下完全失效——于是每局走出完全一样的棋。我们怎么发现的?看步数分布:5 局全是 84 手,5 局全是 59 手 → 局 1 局 2 ... 局 1010:0 的统计效力,等于 1 局。修复:强权重必须用采样模式(greedyFalse),让每局的 seed 真正驱动随机选择。→ 规矩:读比分之前,先看步数和首手分布。坑 33:忘了选模式,结果被拉近了症状:差点把一场 14:4 的比分读成 11:9。根因:评估脚本默认是概率采样模式,弱方靠运气能多赢几局,比分被拉近。忘传参数就会用默认值。修复:报告里必须写明用了哪个模式,别让默认值决定结论。坑 34:10 局就下结论症状:同一对权重,10 局说 A 赢,20 局说 B 赢。根因:10 局口径的噪声在 ±1~2 局。实测 6 场 10 局制比赛,1 场结论被反转。修复:每场至少 20 局。坑 35:评估用的 sim 比训练低症状:同代权重,低 sim 评估时 A B,正常 sim 评估变成 B A。根因:搜索量不足时,评估测的是policy 的锐度,而不是真实棋力。修复:评估 sim ≥ 训练 sim。坑 36:没开换先手规则症状:测了半天,感觉都在测谁运气好。根因:标准规则下两个强权重对打,执黑 100% 必胜(我们实测过)。修复:必须开 ring 规则(每两局交换先手)。坑 37:最强的链条推理失效症状:“A 赢 B、B 赢 C,所以 A 肯定赢 C”——被打脸。根因:自对弈里存在非传递环。我们实测出了一个完整的环:it190 it160 it259 it240 it230 it210 it190 ⟲修复:放弃链式推理,改用多对手矩阵(固定几个基准权重,测平均胜率)。坑 38:回退权重后 PK 脚本路径失效症状:FileNotFoundError。根因:回退时清理了部分权重文件,但 PK 脚本里还写着旧路径。修复:每次回退后,grep 一遍所有引用权重路径的脚本。5. 诊断类:自己把自己骗了(9 个)这一类最阴险:不是训练错了,是你的测量工具错了——然后你基于错误的测量做了一个正确的决策。坑 39:探针喂错输入(假病态)症状:探针报空盘价值 0.74,严重病态!根因:探针直接拿全零张量喂进网络,而网络的输入是 4 通道构造的(ch0 当前方 / ch1 对手 / ch2 常量 / ch3 上一步)。全零张量根本不是合法的空盘局面。按正确方式构造后实测:-0.001,完全正常。修复:探针必须走和训练完全一样的输入构造路径。→ 这个坑的危险在于:它给你一个看起来很专业的错误结论。坑 40:探针局面是 OOD(训练分布外)两种经典假警报:探针为什么假五连局面训练数据里从来没有已经成五连的棋盘(成五即终局,不进训练数据)→ 测出来虚高“纯色盘”训练里也从来没有整盘一种颜色的局面 → 探针必须用黑白交替的真实局面原则:探针用的局面,必须在训练分布里有对应物。坑 41:单通道统计量的百分比是假警报症状:“某层 BatchNorm 统计量膨胀 262%!”,吓一跳。根因:那一层输出只有1 个通道,均值是个标量——拿它跟接近 0 的基线算百分比,当然夸张。修复:看绝对值轨迹(±0.3 算健康、±5 以上才病态),别用相对百分比。坑 42:做题口径混用症状:“奇偶题集黑侧 94%,历史新高!”——但混合题集只有 76.2%,没超过历史最好的 81%。根因:两个题库口径不同,单口径新高不代表整体进步。修复:多个口径同时涨,才算真的涨。坑 43:把设计当 bug 改症状:发现winner 字段和棋盘最后一步不一致,判定是 bug,准备动手改。根因:实际是设计机制——成五即终局,不写入 trajectory,所以棋盘最后一帧是成五前一步。修复:动手改之前,先查清这是设计还是 bug——对比最初版本的原始写法,是最快的判断方式。教训:误判为 bug 而去修一个正常的东西,比漏掉一个真 bug 更危险。坑 44:假赢误报症状:统计脚本报黑胜,但人工看棋谱发现是和棋。根因:统计脚本只数了白棋的子,把其余情况全当黑胜——而满盘无五连其实是和棋。修复:winner 要分三态(黑/白/和)统计。坑 45:轨迹表的最后一帧缺一手症状:用轨迹表分析终局时,发现最后一步没有落子记录。根因:trajectory 里存的是落子前的棋盘状态。修复:终局分析时要补上最后一手。坑 46:静态先验指标被当成棋力指标症状:发现空盘局面下,网络对黑棋第一手的先验几乎为 0,判断为病态,准备干预。根因:当前版本的第一手是强制随机的,policy 根本不学第一手——所以空盘先验是无监督漂移,不是训练塑造的结果,更不是棋力指标。修复:搞清楚这个指标在当前配置下有没有意义。别拿一个被配置架空的指标去判断健康度。坑 47:配置文件和数据打架症状:config_hash 失效,重启后从 0 开始训练,导致权重被随机权重覆盖。根因:改了代码后 hash 变了,恢复逻辑判定为新配置,于是从头训练。修复:关键参数(batches/batch_size)移出 hash;恢复时用带代数后缀的权重文件(weights_it{N}.pt),别只依赖weights.pt。6. 方法论类:最贵的一类(8 个)前面几类坑的代价是时间,这一类的代价是方向——它不会报错,只会让你在错误的路上走几个月。坑 48:loss 是安慰剂症状:loss 全场最低的那个权重,实战是三个里最菜的。根因:loss 只衡量对见过数据的拟合程度。网络可能只是越来越熟悉自己的棋风(包括坏习惯),loss 自然降,棋力不涨。修复:loss 只作为参考,PK 才是判定。坑 49:静态指标平坦被误判为训练无效症状:内部对抗测下来,100 代训练没有任何提升(搜索深度跨 117 代零位移、有效秩单调走低、白率平坦)。根因:内部对手是移动靶。同一批权重拿去跟固定外部标尺打,116 代之间从重大失误走到了稳赢。修复:必须有一个不随你变化的固定参照物,定期测。→ “内部对抗饱和” ≠ “训练无效”。这一条是整个项目最重要的方法论发现。坑 50:在 4 个 bug 同时存活的环境里做归因症状:一堆早期结论(“架构决定论”“某类头数据效率低”“白棋窗口瓶颈”),事后复盘发现全部诞生于4 个重大 bug 同时存活的窗口期。根因:观察到的现象是真的,但归因错了。比如我们曾经花一整个版本得出某类 value 头数据效率低,1.8 万局都带不动,而同期白棋的视角通道是坏的——白棋本来就防守不了,换什么头都救不了。修复:给结论标可信度( 干净环境 / 方向可信 / 存疑)觉得怎么调都不行时,停止调参,去查实现——要对照标准实现逐项核对(噪声注入位置和次数、树的生命周期、探索常量、温度曲线、搜索计数)只有我这样写的地方优先查剩下 4 个方法论坑(简表)#坑说明51体检低估实战影响一次干预实验:体检只掉 4.6 分,实战直接被 0:10 打崩52中间局面价值全卡中性新初始化的 value 头会让中间局面的 Q 值全为 0,MCTS 拿不到训练信号,死锁53神经元死亡深层激活率掉到 50% 以下 神经元坏死;根因通常是数据多样性不足(不是模拟量不够)54让手教学样本不迁移让手局教出来的白棋优势下怎么赢,和实战要的白棋劣势下怎么防是两个技能,前者学得再好也不会迁移55只有我这样写的地方偏离标准实现(MCTS 树生命周期、噪声注入、搜索计数)的代码是最可疑的,优先查7. 55 个坑完整速查表(建议收藏)A. 实现类#坑一句话修复1log 概率当概率用先验反转 → policy 饿死 99 代;用前必须exp2噪声每模拟重采样探索放大 800 倍;噪声只在根节点注入一次3一代一树(局间不重置)空盘 0 次搜索复用旧分布;每局必reset_mcts()4while countsim判搜索量局面访问过就跳过 0 次;改for _ in range(sim)5BN 参数吃 weight_decayγ 死亡螺旋、通道反相;BN 参数分组 wd06温度调度估错局长度112 手 vs 实际 45 手 → 全程随机采样;按实际重写曲线7视角通道写成常数网络 196 代永远执黑;正确编码执色 从零重训8MCTS 递归传递错误第 2 层起 ch2 恒 0;修正递归参数 重置带病数据9fliplr对 4D 张量无效增强只生效一半;改np.flip(axis-1)10回放池取[-5:]字典序取错代,30 代白训;按数值排序B. 工程类#坑一句话修复11日志被覆盖一律追加12工兵重启整代跳过last_iteration max_seen - 113metadata 死锁先写 metadata 再启主进程,别只拷 weights.pt14权重截断文件校验大小 格式,不合格删掉重下15同名权重 key 冲突文件名带版本后缀16重启判定用错来源用代码真实默认值,不只看配置17pkill -f杀到自己先pgrep查,按精确 PID 杀18screen 外壳 CPU 0 误导看 Python 本体的 CPU 时间19scp 漏带端口必须显式-P20跨机stat语法用 python 取 mtime21远程 heredoc 吞引号本地写文件 → scp → 执行22macOS rsync 参数不支持去掉新参数23定时任务超时/消息限流看投递错误字段24主进程数据落别处同步前先列全数据目录25服务进程被系统挂起(STATTN)重启 长任务前caffeinate26双实例抢端口注意父子进程,别一把杀27TensorFlow 在新架构 GPU 上慢 10 倍换 PyTorch28torch.compile CUDA Graph 同时用死锁二选一29FP16 训练缺 GradScaler加 scaler;log 加clamp(min1e-7)309 个工兵并行编译死锁工兵一律disable_compileTrue31特定显卡 CUDA Graph 兼容问题训练路径走原始网络C. 评估类#坑一句话修复32同一盘棋复制 N 次读比分前看步数分布33忘选模式报告写明用了哪个模式3410 局下结论至少 20 局35评估 sim 低于训练评估 sim ≥ 训练 sim36没开换先手必须 ring37链条推理非传递环存在,改用多对手矩阵38回退后路径失效grep 所有引用路径的脚本D. 诊断类#坑一句话修复39探针喂全零张量必须走正规输入构造40纯色/五连探针 OOD用训练分布内的真实局面41单通道量算百分比看绝对值轨迹42做题口径混用多口径同涨才算涨43把设计当 bug 改改前先查是设计还是 bug44和棋误报为黑胜winner 分三态统计45轨迹最后一帧缺一手终局分析补最后一手46被配置架空的指标当棋力先确认指标在当前配置下有没有意义47config_hash 导致权重被覆盖关键参数移出 hash 用带代数的权重恢复E. 方法论类#坑一句话修复48loss 低 ≠ 棋力强PK 才是判定49静态指标平坦 训练无效?换固定外部标尺再测50在 bug 环境里归因给结论标可信度;查实现优先于调参51体检低估实战至少两个指标交叉验证52中间局面 Q 全卡中性新 value 头会导致训练信号死锁53神经元死亡根因是数据多样性,不是模拟量54让手样本不迁移教的技能和实战要的技能不是一回事55“只有我这样写”偏离标准实现的地方优先查写在最后写完这 55 个坑,我自己重新读了一遍,最大的感受是:这个项目里,真正难的不是想出一个好方案,而是知道自己现在到底在哪。算法是公开的,论文就那几页;但我这步改动有没有用“这个现象背后是什么原因”“我现在的棋力到底什么水平”——这些问题没有现成答案。你只能自己建指标、做对照、然后怀疑自己的指标。前面那些坑里,最贵的从来不是技术难度最高的,而是那些让你在错误方向上自我感觉良好地走几个月的:在 4 个 bug 同时存活的环境里得出一堆归因——花了 21 个版本才发现噪声注入是错的内部对抗测出100 代没进步,差点就停训了——结果是标尺的问题探针喂错输入报空盘严重病态——差点去改一个完全正常的网络所以如果这几篇能让你的弯路短一点,那这些记录就有价值了。最后一个共勉吧:我 2017 年 AlphaZero 出来的时候,在 Leela Zero 的众筹里当算账的——收钱、租机器、记账,技术上插不上话。当时特别想知道纯自对弈到底能不能长出棋力从 v1 到 v36,踩了这 55 个坑,最后的结论是:能。而且过程中踩的每一个坑,都比最后的配置文件更有价值。如果你也在做类似的事,祝你少踩几个坑。踩到了也别灰心——它们后来都会变成你的经验。四篇看完的朋友,给个赞吧⭐这 55 个坑的速查表,强烈建议收藏评论区聊聊:你踩过最离谱的一个坑是什么? **代码 全部对局记录在 Gitee:alpha zero gomoku下一篇预告评估工具请在错的地方掉头共勉。