从零训练一个会下五子棋的AI

个人消费级 AlphaZero 五子棋实战总结:从零训练一个会下五子棋的 AI

摘要:本人在两张消费级 GPU(RTX 4090 + RTX 2080 Ti)上,从零训练出了一个 15×15 五子棋 AlphaZero AI。本文记录了完整实战过程:从框架选型、网络架构、分布式训练,到 14 个真实踩坑(每个都有症状+根因+修复)、一套可靠的评估方法论(loss 下降≠棋力上升),以及 v1→v2→v3 的对照实验设计与早期数据。全文 7000+ 字,全部结论均有实测数据支撑,无水分。

适合人群:对 AlphaZero / 强化学习 / 自对弈感兴趣,想自己动手训练棋类 AI 的开发者;被 loss 迷惑过、想知道"怎么判断模型真的变强了"的 RL 学习者。

你将收获:① 消费级硬件训 AlphaZero 的完整方案;② 14 个高频踩坑及修复;③ 一套"探针 + 激活率 + PK"三方互证的评估方法;④ 用对照实验归因"策略 vs 架构"的思考方式。
作者 : 个人实战项目

时间 : 2026-08

关键词: AlphaZero、五子棋、强化学习、MCTS、PyTorch、自对弈、价值网络、探针检测


目录

  1. 项目背景与目标
  2. [环境与架构:消费级硬件怎么撑起 AlphaZero](#环境与架构:消费级硬件怎么撑起 AlphaZero)
  3. 训练策略:关键超参数的设计与取舍
  4. [性能优化:从每代 4 小时到 30 分钟](#性能优化:从每代 4 小时到 30 分钟)
  5. 踩坑实录:那些让我差点放弃的问题
  6. [评估方法论:loss 下降≠棋力上升](#评估方法论:loss 下降≠棋力上升)
  7. [版本演进:v1 → v2 → v3 对照实验](#版本演进:v1 → v2 → v3 对照实验)
  8. 总结与展望

一、项目背景与目标

1.1 为什么做这件事

AlphaZero(DeepMind, 2017)是深度强化学习领域最优雅的算法之一:不需要任何人类棋谱,只靠"自己跟自己下棋"就能从零学会一项棋类游戏。它用一个神经网络同时承担两个角色:

  • Policy 网络:给定局面,输出每个落子位置的概率("我该下哪")
  • Value 网络:给定局面,输出胜率估计("我现在是赢是输")
  • MCTS 搜索:用网络指导的蒙特卡洛树搜索,在"直觉"和"深度推演"之间取得平衡

五子棋(15×15)是入门 AlphaZero 的最佳棋盘:规则简单(五连即胜)、动作空间适中(225 个落点)、单局时长短(几十步),非常适合个人开发者在一两张消费级 GPU 上完整跑通"从零训练到能下棋"的全流程。

1.2 项目目标

  1. 个人可及的硬件(1 张 RTX 4090 云 GPU 或 1 张 RTX 2080 Ti)从零训练一个 15×15 五子棋 AI
  2. 完整走通 AlphaZero 训练闭环:自对弈 → MCTS → 训练 → 评估 → 迭代
  3. 建立一套可靠的评估方法(loss 不可信,要用探针 + PK + 激活率三方验证)
  4. 通过对照实验弄清楚:训练策略和网络架构,到底哪个对最终棋力贡献更大

1.3 关键规模数字

项目 数值
棋盘 15×15(225 动作空间)
网络 128 通道 × 8 残差块
参数量 2.37M(简化版)/ 2.8M(论文版)
每代对局 100 局(主进程 10 + 9 工兵 90)
训练目标 100 代
单代耗时 25-30 分钟(4090) 或 45-50 分钟(2080Ti)
全程 v2 约 2 天(100 代,每代100局,MCTS-400模拟)

二、环境与架构:消费级硬件怎么撑起 AlphaZero

2.1 硬件与框架选型

老训练机 新测试机
GPU RTX 4090 24GB RTX 2080 Ti 22GB
算力 ~82.6 TFLOPS (FP32) ~13.4 TFLOPS (FP32)
相对算力 1.0 约 1/6
PyTorch 2.13 + CUDA 12 2.12.1 + cu130
角色 v2 主线训练 v3 对照实验

框架选择:PyTorch 而非 TensorFlow。 这是一个重要决策------TensorFlow 2.12 只为 sm_80(Ampere)编译了 CUDA kernel,在 RTX 4090(sm_89, Ada Lovelace)上回退到通用 kernel,完全用不上 Tensor Core ,单次前向 ~20ms;而 PyTorch 原生支持 sm_89,前向仅 ~1.9ms,快约 10 倍。这个坑的详细分析见 5.1 节。

2.2 网络架构

主干是标准的 ResNet 风格:Conv3×3 → BN → ReLU 后接 8 个残差块(每个含 2 层 3×3 卷积),128 通道。3×3 卷积堆叠 8 层的感受野是 17×17,恰好覆盖整个 15×15 棋盘,网络可以"看到"全局。

Policy/Value 头有两种设计,这是本项目后期对照实验的核心变量:

组件 简化版(v1/v3) 论文版(v2)
Policy 头 conv3×3 → 1ch → softmax 出概率 1×1conv → 4ch → BN+ReLU → Linear(900→225) → log_softmax
Value 头 conv3×3 → 1ch → Linear(225→1) 单层 1×1conv → 2ch → BN+ReLU → Linear(450→128) → Linear(128→1) 双层
参数量 2.37M ~2.8M

论文版的设计思想(AlphaZero 原文):1×1 conv 做逐位置特征提取(局部感知),FC 层做全局整合(全局决策) ;value 头用 128 维隐藏层表达非线性组合特征。简化版则是"单通道 + 单层线性",表达力弱一档------但这也正是我们做对照实验的意义:在同样策略下,架构差异对棋力的实际影响到底有多大?(见第七章)

2.3 分布式训练架构(1 主 + 9 工兵)

AlphaZero 训练的瓶颈不是 GPU 推理,而是 MCTS 自对弈 (单线程 CPU 循环)。单进程 100 局/代需要 4-5 小时,完全不现实。解决方案是分布式:

复制代码
┌─────────────────────────────────────────────────────┐
│  主进程 (Gomoku-v0_pt.py)                            │
│  ├── 自对弈 10 局 (MCTS)                             │
│  ├── 等待工兵: X/90局已到 (轮询 worker_data/)         │
│  ├── 收集齐 100 局 → agent.learn() 训练              │
│  ├── 存档 weights_it{N}.pt + 体检 health_check()     │
│  └── 杀旧工兵 → 重启 9 个新工兵 (下一轮)              │
└─────────────────────────────────────────────────────┘
        ▲ 文件 IPC (worker_data/iter{N}_{wid}_game_{M}.pkl)
        │
┌───────┴─────────────────────────────────────────────┐
│  工兵 × 9 (Gomoku-worker.py)                         │
│  每个: 加载最新权重 → 自对弈 10 局 → 逐局写 pkl → 退出 │
└─────────────────────────────────────────────────────┘

关键设计决策:

  • 文件即 IPC :工兵逐局保存 iter{N}_{wid}_game_{M}.pkl,crash-safe(训练崩溃不丢已产数据)
  • 主进程训练前杀工兵、训完重启:避免 9 工兵 + 主进程同时训练导致显存 OOM
  • 9 工兵并行:GPU 推理时间占比小,每个工兵分到算力即可,总量吞吐优先

三、训练策略:关键超参数的设计与取舍

AlphaZero 算法的"配方"里有几个关键超参数,直接决定训练数据的质量和多样性。这一节讲我们踩过的坑和最终的取舍。

3.1 MCTS 模拟量:固定 400 还是动态调度?

v1 的做法(动态调度)代1-19: sim=50 → 代20-49: sim=200 → 代50-70: sim=400 → 代71+: sim=800

理由看起来合理:早期网络弱,深搜浪费;后期网络强,需要深搜。但实际引入了问题------每次切换模拟量,数据分布就漂移一次,value 网络需要 3-5 代重新适应(value_loss 跳升),且日志里 50/200/400/800 混杂,不好追踪。

v2 的做法(固定 400):最终拍板固定 400 全程。理由:

  1. 追踪每代体检曲线更干净,不做模拟量对照的混杂变量
  2. 400 是质量/速度的甜点区(v1 阶段 200 的棋质量不够、800 局数受限)
  3. 数据量受速度限制------模拟量减半 → 同时间数据量翻倍,但质量下降

模拟量权衡实测:50 模拟 ≈ MCTS 只是 policy 直觉 (数据多样性高但质量差);200 = 质量/速度甜点400 = 质量高但局数受限800 ≈ 67min/代(100 局)。

3.2 Dirichlet 噪声 α:从 1/N 到 0.03

AlphaZero 在根节点加 Dirichlet 噪声促进探索。α 控制噪声的"尖锐度"------α 越小,噪声越集中到少数几个位置(探索更激进)。

v1 的坑 :用 α = 1/合法落子数 ≈ 0.0045(15×15 空盘 225 个合法点)。这比标准值 0.03 小 6 倍多,导致:

  • 开局乱下边角 :实测 v1 后期(代87-96)黑第一手边角占比高达 50.5% ,天元仅 1.5%
  • policy 没学会"占中"棋理:第一手分布近均匀,定式重复率仅 5%(无任何开局偏好)

v2 的修复 :固定 α = 0.03(AlphaZero 国际象棋标准值),噪声温和地集中在 ~10-15 个位置,既保证探索又贴近可信区域。

3.3 Temperature 调度

v1 :全程按概率采样(np.random.choice),无温度调度------中期棋力起来后仍然乱采样,数据质量上不去。

v2:标准 AlphaZero 式温度退火:

复制代码
前 15 手:  T=1.0   (均匀采样,保证开局多样性)
中段:      T 线性降到 0.25
最后 10 手: T=0.05 (近乎贪心,保证终局质量)

3.4 回放池:最近 5 代混合

v1:只训当前代 100 局------每代自对弈分布都在变,模型容易过拟合"这一代"的棋风。

v2 :保留最近 5 代数据混合训练。效果显著:v2 代12 就打出黑活四转正 + 冲四连续转正,价值判断的稳定性明显好于 v1 同期。

3.5 每代自动健康体检

v2 起每训练完一代,自动跑一次 health_check() 写入 health_log.json,无需手动拉权重:

  • 7 局面价值盲测:黑/白活四、黑/白活三、黑/白冲四、空盘(探针设计原则见 6.2 节)
  • 4 层激活率:res3-res5 的 BN 输出 >0 比例
  • BN 死神经元计数:γ 参数 <0.01 的数量

这套"每代自动体检"是后续所有评估工作的基础------不用等 100 代训完,每一代都能看到价值判断的健康状况


四、性能优化:从每代 4 小时到 30 分钟

这一节是纯工程干货。AlphaZero 训练的瓶颈链条:环境交互 → 棋盘编码 → MCTS 树遍历 → GPU 推理。每一环都有优化空间。

4.1 boardgame2 环境优化(端到端 ~3-5×)

boardgame2 是项目使用的 gym 环境库,原始实现有两个致命性能问题:

优化 优化前 优化后 提速
copy.deepcopy(board)board.copy() 深拷贝整个对象图 numpy 浅拷贝 ~100× per copy
get_winner 全盘扫描 → 只查落子点附近 遍历全部 1800 个起始点 4 方向检查 ~32 步 ~280× per call

get_winner 全盘扫描是隐藏杀手:15×15 空盘一次调用要 5.8ms ,一局 150 手就是 ~870ms。100 局/代 × 100 代 = 24 小时浪费在胜负判断上。改为只检查最后一手落子点周围 4 个方向后,开销降到可忽略。

4.2 MCTS Python 层优化(+33~64%)

当 GPU 推理已经极快(~2.5μs/次)时,真正的瓶颈在 Python 层。用 profiler 定位到两个巨慢组件:

组件 优化前 优化后 提速
strfboard() 棋盘→字符串 key 54.9μs 0.1μs (board.tobytes()) 402×
get_valid() 双重循环 77.7μs 1.3μs (向量化) 58×
  • strfboard 把 15×15 棋盘编码成字符串做 MCTS 字典 key,每步调用 ~800 次(800 sim);改用 board.tobytes()(225 字节二进制,可直接 hash)后近乎免费
  • get_valid 双层 for 循环遍历 225 格判断空位,改用 (board == 0).astype(np.float32) 向量化

实测加速(RTX 2080 Ti, 2.4M 模型):

配置 优化前 优化后 提升
MCTS(50) 49ms 33ms +33%
MCTS(200) 130ms 83ms +36%
MCTS(800) ~520ms 187ms +64%

4.3 torch.compile + FP16 + GradScaler(~3.4×)

优化 预期加速
torch.compile(mode='max-autotune') 1.3-2×
FP16 autocast 推理 1.5-2×
TF32 + cudnn.benchmark + channels_last ~1.2×

实测(RTX 4090, sim=400):优化版首局 595s → 173s (约 3.4×),每代从 ~100 分钟降到 ~30 分钟

关键工程决策 :主进程开 torch.compile(训练需要),工兵全部 disable_compile=True------因为 9 个工兵同时编译会互相抢 GPU 导致死锁(见 5.5 节)。

4.4 性能真相:重启 > 代码优化(重要教训)

这是本项目最反直觉的发现。某次"优化后 3 倍加速",拆解归因:

因素 贡献
杀死运行 27h 的残留进程(含 compile_worker 空转一天) ~50%
清除 15.7GB GPU 显存碎片化 ~30%
strfboard→tobytes + get_valid 向量化 ~20%

教训 :训练连续运行超 24h 后,进程内存碎片化、GPU 显存碎片、PyTorch 编译缓存膨胀会持续拖慢速度。每 10 代重启一次能恢复大部分性能损失。但不要盲目重启------"等到真的变慢了再说"。

五、踩坑实录:那些让我差点放弃的问题

这一节是全文最值钱的部分------14 个真实踩坑,每个都有症状、根因、修复。很多坑不亲历根本想不到。

5.1 TensorFlow 在 RTX 4090 上慢 10 倍

症状:128×8 残差网络前向推理 ~20ms/次,GPU 利用率极低,训练慢到无法接受。

根因 :TF 2.12 只为 CUDA 11.8 + sm_80(Ampere)编译了 kernel。RTX 4090 是 sm_89(Ada Lovelace),TF 回退到通用 kernel,Tensor Core 完全没用上。PyTorch 2.13 原生支持 sm_89,前向仅 ~1.9ms。

修复 :全面迁移 PyTorch。tf.sysconfig.get_build_info() 检查 cuda_compute_capabilities 即可确认。

5.2 batches 太低导致 96% 数据浪费

症状:policy_loss 几乎不动(5.40→5.33),value_loss 卡在 1.0,学习阶段 0-1 秒完成。

根因 :默认配置 batches=2, batch_size=128 → 每代只训练 256 条经验,而每代生成 ~6800 条(100 局 × ~68 步),利用率仅 3.8%。GPU 99% 时间在闲置。

修复batches = 6800/128 ≈ 50,让 batches × batch_size ≈ 每代样本量 × 90%。改后学习阶段 10-30 秒,value_loss 5-10 代内开始下降。

连带坑 :改 batches 会改变 config_hash,导致已有 30 代检查点全部失效从头训练!正确做法是把 batches/batch_size 从 config_hash 计算中移除(它们不影响网络结构)。

5.3 torch.compile + CUDA Graph 死锁

症状:进程无限挂起,CPU 100%、GPU 0%、无任何报错日志。

根因torch.compile(mode='max-autotune') 生成动态执行路径(按输入数据选 kernel),而 CUDA Graph 的 replay() 冻结了 capture 时的 kernel 配置。两者组合时,新数据触发编译函数调度到不同 kernel path,与冻结的 graph 冲突 → GPU 死锁。

修复 :二选一------只用 torch.compile(推荐),或只用 CUDA Graph。删掉 CUDAGraphCache 类即可。

5.4 FP16 缺 GradScaler,梯度下溢

症状:训练不收敛,policy softmax 输出的极小概率(~1e-5)在小棋盘上梯度消失。

根因:用了 FP16 autocast 但没配 GradScaler。policy 交叉熵在 15×15 上有 ~1e-5 量级的小值,FP16 下梯度直接下溢为零,部分参数永不更新。TF32 只保护 matmul/conv,不救 softmax/log。

修复

python 复制代码
self.scaler = torch.cuda.amp.GradScaler()
...
self.scaler.scale(total_loss).backward()
self.scaler.step(self.optimizer)
self.scaler.update()

同时 log(pred_p + 1e-8) 改为 pred_p.clamp(min=1e-7),精度提升 10×(1e-8 在 FP16 下接近最小正规数)。

5.5 9 工兵并行 torch.compile 编译死锁

症状:9 个工兵日志全部 0 字节,GPU <5%,重启后训练停滞 30 分钟以上。

根因 :每个工兵启动都做 torch.compile(mode='max-autotune'),各自穷举 kernel 配置(8-15 分钟),9 个进程互相抢 GPU → 全部超时。

修复 :Agent 加 disable_compile 参数,工兵推理不需要编译(编译只加速 ~20-30%,且工兵有 9 个并行实例,单实例速度无关紧要):

python 复制代码
# 主进程(需要训练速度)
agent = AlphaZeroAgent(..., disable_compile=False)
# 工兵(只做推理,立即启动)
agent = AlphaZeroAgent(..., disable_compile=True)

5.6 search() vs decide() 陷阱:MCTS 没跑满模拟量

症状:MCTS(50) 和 MCTS(200) 速度一样,PK 结果全部失效。

根因agent.search(board) 只做 1 次 递归遍历(到叶子→推理→回传),不是 sim_count 次循环。只有 agent.decide(observation)while count < sim_count: search()

自查 :MCTS(N) 和 MCTS(2N) 速度应差约 2 倍。如果一样,就是误用了 search()。后果:所有棋力对比用的实际是 1 次搜索而不是 N 次

5.7 日志被 > 覆盖,历史丢失

症状 :重启训练后,training.log 只剩几行,代0-11 的全部过程日志消失。

根因 :手动重启命令写成了 > training.log(覆盖)而非 >> training.log(追加)。

修复所有日志重定向一律 >>。损失数据(loss/体检/权重)还在 metadata.json 和 health_log.json 里,但过程日志永远找不回了。这个教训后来写进了所有部署文档。

5.8 工兵重启衔接 bug:整代跳过

症状:重启后工兵不补产缺失局,主进程永远卡在"等待工兵: 89/90 局已到"。

根因 :工兵启动时 last_iteration = max_seen(扫描到自己产出的最大代),于是判断"当前代已完成"整代跳过。但重启前可能只产了 19/90 局。

修复last_iteration = max_seen - 1,让工兵重新进入当前代,靠"局级跳过已存在文件"补产缺失局。这个 bug 让主进程等了 40 分钟才发现

5.9 假赢调查:90/90 局全误报

症状:审计训练数据时发现"90/90 局无五连却判胜",怀疑环境判胜 bug。

根因 :trajectory 里每条记录的 board 是落子前 局面(成五 → done → break,append 的是落子前棋盘)。直接拿 boards[-1] 当终局棋盘检查五连,必然找不到五连。

修复:在最后一行 board 上找胜方"差一手成五"的空位 = 最后一手,补上再数连子。修正后 100% 真五连赢、0 假赢、0 平局。

连带洞察 :训练数据里"已五连"棋盘状态数 = 0(成五即终局),所以 value 探针绝不能测终局局面(OOD)------这是探针设计的重要原则(见 6.2 节)。

5.10 config_hash 失效:30 代训练白费

症状 :修改代码后重启,训练从 0 开始,weights.pt 被随机权重覆盖。

根因config_hash 是训练参数的 SHA256。改任何参数(含 batches/batch_size)后 hash 变化,load_checkpoint() 静默返回 None,训练从随机权重重开。

修复 :① 从 hash 函数移除不影响网络结构的字段(batches/batch_size);② 灾难恢复:用 weights_it{N}.pt 覆盖 weights.pt 并验证 md5,更新 metadata 的 config_hash,把 iteration 设回上一代。

5.11 神经元死亡:激活率断崖(代52→53)

症状:中层 BN 激活率从 74%(代51 巅峰)断崖式跌到 52%(代53),棋力随之下降。

根因 :代50 切 MCTS 200→400 + 每代仅 10 局 → 数据多样性不足,中层神经元成批死亡(res4_bn2 从 80%→51%)。对比:代71 切 400→800 但有 90 局/代,无死亡。

结论 :模拟量翻倍不是死因,数据多样性才是。激活率 <50% = 神经元坏死,此时 loss 更低但实战更弱。

5.12 CUDAGraph 2080Ti 专属坑

症状RuntimeError: accessing tensor output of CUDAGraphs that has been overwritten by a subsequent run------4090 不触发,2080Ti 必现。

根因learn() 里用 torch.compile 后的 self.net 连续多次 forward,CUDAGraph 输出张量被覆盖。与 5.3 不同,这是编译网络的 CUDAGraph 内存复用问题。

修复learn() 里改用 self.raw_net(未编译网络)。优化器绑定 self.net 的参数,但 compile 与 raw_net 共享底层张量,backward 安全。

5.13 纯网络评估在 15×15 上不可靠

症状:纯网络 argmax 评估 100 局 100% 平局,价值网络看起来完全没学会。

根因 :15×15 上 policy 输出近均匀分布(top1 ≈ 0.7% vs 均匀 0.44%),argmax 选中哪个位置 ≈ 随机。纯网络评估只适用于小棋盘(井字棋 3×3、黑白棋 4×4)。

修复:评估必须用 MCTS(哪怕 10-20 sim),搜索放大微弱的 policy 差异。

5.14 模拟量不匹配导致排名反转

症状:400/800 模拟训练的模型,用 MCTS(50) 评估反而输给早期模型(代69 输给代31/41)。

根因 :高模拟量训练的 value 特征需要深搜才能发挥------在 50 模拟下,精细的 value 预测还没传播开就结束搜索,成了噪声。更好网络 + 不足搜索 < 较差网络 + 足够搜索

修复:评估模拟量 ≥ 训练模拟量。规则:训练 400 就至少用 400 评估;低模拟量排名与训练时序矛盾时,信高模拟量结果。

踩坑总纲 :这 14 个坑可以归成三类------环境兼容 (5.1/5.3/5.4/5.5/5.12)、逻辑 bug (5.2/5.6/5.7/5.8/5.9/5.10)、方法论陷阱(5.11/5.13/5.14)。前两类让训练"跑不起来或跑错",第三类让结果"看起来对其实错"------第三类最危险。

六、评估方法论:loss 下降≠棋力上升

这是整个项目最深刻的教训,单独成章。如果不读这一节,前面的训练都白做。

6.1 核心教训:loss 是自证预言

实测铁证 :v1 训练到 91/92 代时,value_loss 降到 0.41 新低 (看起来完美),但实战 PK 排序是 85代 > 51代 > 91代------91 代的 loss 比 85 代还低,棋力却垫底。

为什么? loss 是在自对弈生成的数据分布 上测的。随着训练进行,模型棋风固化,自对弈双方越来越像,输赢越来越接近随机------loss 自然下降,但这是"自我印证"的下降,不代表棋力提升。数据里已经学不到新东西了,loss 只是在一遍遍确认旧认知。

所以报告训练状态时,loss 趋势必须搭配盲测或 PK 验证,否则会给出错误的好消息。 这是本项目方法论的核心。

6.2 探针检测(价值盲测):每代的"健康体检"

既然 loss 不可信,我们就给网络做"体检"------构造几个确定性局面(谁胜谁负是明确的),看 value 网络预测的方向对不对。

7 局面探针(全部用训练中真实出现的中间局面,非终局):

探针 期望 value 含义
黑活四 +1.0 黑四连两端开放,必胜
白活四 -1.0 白四连,黑必败
黑活三 +0.6 黑三连两端开放,优势
白活三 -0.6 白三连,黑劣势
黑冲四 +0.8 黑四连一端被堵,威胁
白冲四 -0.8 白四连一端被堵
空盘 ~0 开局均衡

判读规则 :符号方向正确 + 幅度合理;空盘要求 |value| < 0.3

探针设计原则(重要)只用训练数据里真实出现的状态 。一开始我们用了"黑五连/白五连"当探针,结果 value 随代恶化(-0.35→-0.78)------后来才发现是探针无效而非网络退化:训练数据里"已成五连"的状态数 = 0(成五即终局,trajectory 记录的是落子前棋盘),五连局面是分布外数据(OOD),网络从未见过。

活四 vs 冲四的学习顺序(一个有趣的观察) :网络先学会冲四、后学会活四 。因为训练数据里活四是"瞬时状态"(一形成对手立刻堵一头变冲四),出现频率低;冲四是"稳定状态"(被堵的四连),出现频率高。所以早期活四探针判断反/震荡是正常的,要看冲四/活三是否转正作为早期信号

v2 实测(探针逐代转正的漂亮轨迹)

黑冲四 白冲四 黑活四 备注
12 +0.32 ✅ -0.53 ✅ -0.32 ❌ 冲四先转正
13 +0.46 ✅ -0.51 ✅ -0.34 ❌ 冲四持续强化
14 +0.85 +0.25 ✅ +0.40 爆发:三探针同时转正
15 +0.79 ✅ -0.25 ✅ -0.49 ❌ 波动但冲四稳定

v2 代14 黑活四转正------14 代就达到 v1 巅峰期(85 代)都未必有的探针强度,策略优化的效果非常显著。

6.3 激活率诊断:比 loss 更靠谱的健康指标

中层 BN 激活率(res3-5 的 ReLU 输出 >0 比例)是比 loss 更优的棋力预测指标

  • 代51(激活率 69%)在 PK 中 6:4 战胜代75(激活率 48%),尽管代75 的 value_loss 更低(0.473 vs 0.580)
  • 激活率 < 50% = 神经元成批坏死(见 5.11),此时 loss 越低实战越弱
  • 激活率 43-47% 是 v2 的健康稳态,0 死神经元

6.4 PK 实战验证:终极裁判

探针和激活率是"间接指标",PK 对弈才是终极裁判。但 PK 有严格的方法论:

  1. 分先赛制:先手 3 局 + 后手 3 局,控制先手优势
  2. 模拟量 ≥ 训练模拟量:训练 400 就用 400 评估(见 5.14 的反转陷阱)
  3. 控制变量:双方同 MCTS、同 greedy、同 noise
  4. 多局统计:3 局噪声极大(实测 85代 3-0 赢 51 代,但 91 代同设置 0-3 输),≥10 局才有意义
  5. 每局前 reset_mcts():防止残树污染

v2 早期 PK 结果(sim=400,分先 4 局)

对手 比分 解读
v1 代85(巅峰) 1-3 输但赢的局 45 步(稳)
v1 代51 2-2 打平
v1 代94(末期) 2-2 打平

才 12 代就打平了 v1 训练 100 代的 51/94 代,成长曲线健康。v2 赢的局偏长(稳扎稳打),v1 赢的局偏短(快攻连五)。

6.5 三方验证体系

最终我们建立了一套**"三方互证"评估体系**,任何结论必须至少两方支持:

复制代码
① Loss 趋势    ------ 快但不可信(自证预言)
② 探针 + 激活率 ------ 快、可信、每代自动跑(体检)
③ PK 实战      ------ 慢但终极可信(金标准)

诊断路径:loss 降但 PK 输 → 先跑探针(看是否全反)→ 再看激活率(是否坏死)。三步定位问题根源。

七、版本演进:v1 → v2 → v3 对照实验

前面所有铺垫,都是为了这一章------用对照实验回答一个核心问题:训练策略和网络架构,到底哪个对棋力贡献更大?

7.1 v1 的失败与诊断

v1 是第一个完整跑通 100 代训练的版本,但它暴露出一连串问题:

问题 证据
开局乱下 黑第一手边角 50.5%、天元仅 1.5%、定式重复率 5%
value 头退化 89 代短暂学会后,91/92 代盲测全反(且 -0.9 级信心满满地反)
神经元死亡 代52→53 激活率断崖(74%→52%)
loss 与棋力背离 91 代 loss 新低但 PK 垫底
实战防守失灵 代94 与人实战,对方中心连下 1-3-5-7-9 五连全程不拦截

根因诊断(逐条排查后):

  1. 无 temperature 调度------全程概率采样,中期棋力起来后仍乱采样,数据质量上不去
  2. Dirichlet α 过小(1/N ≈ 0.0045 vs 标准 0.03)------探索过强,开局乱下边角,policy 学不会占中
  3. 每代仅 10 局(v1 前期)------数据多样性不足,神经元死亡

7.2 v2:策略全面修复

v2 保留 v1 的网络结构思路,但把训练策略全面升级到 AlphaZero 标准:

策略 v1 v2
MCTS 模拟量 动态 50→200→400→800 固定 400
Dirichlet α 1/N ≈ 0.0045 0.03
Temperature 无(全程采样) 前15手 T=1 → 线性 0.25 → 末10手 0.05
回放池 只训当前代 最近 5 代混合
每代局数 10(前期) 100(主10+工兵90)
自动体检 每代 health_check

效果立竿见影

  • 探针检测:代12 冲四转正,代14 三探针同时转正(见 6.2 表)------14 代达到 v1 巅峰期水平
  • PK:代12 打平 v1 代51/94(2-2),才 12 代
  • 激活率:稳定 43-47%,0 死神经元

7.3 一个意外的发现:网络结构真相

v2 用的其实是论文版网络架构(policy 1×1conv→4ch→Linear(900→225)、value 双层 fc),而 v1 是简化版(conv3x3→1ch 直连 softmax、value 单层 fc)。这个差异是在做 PK 时发现的:v2 权重 120 个 key vs v1 的 116 个 key,无法互相加载。

这带来了一个混杂变量:v2 的进步到底是"策略优化"带来的,还是"架构升级"带来的?于是设计了 v3 对照实验。

7.4 v3:解耦"策略 vs 架构"

对照实验设计(控制变量法):

复制代码
v2 = 新架构(论文版)+ 新策略     ← 老机器 RTX 4090
v3 = 老架构(简化版)+ 新策略     ← 新机器 RTX 2080 Ti
      ↑ 策略相同,架构不同
  • 如果 v3 探针也转正策略优化是主因(架构贡献小)
  • 如果 v3 探针平庸而 v2 强架构是主因

v3 的部署:复制 v2 训练脚本,把网络换回 v1 简化版结构(2.37M 参数),其余策略参数完全不动。跑在 RTX 2080 Ti 新机器上(算力约为 4090 的 1/6,每代 ~45-50 分钟)。

v3 遇到的新坑 (2080Ti 专属):torch.compile 触发 CUDAGraph 输出覆盖错误(4090 不触发),修复为 learn() 用 raw_net(见 5.12)。

7.5 初步结果:v1/v2/v3 早期(代1-4)探针对比

v3 跑到代1、代3、代4 时分别拉权重与 v2 同代对比;同时补拉了 v1 的代3/4 权重(v1 与 v3 同为简化版网络,可直接对比)。三个版本都用完全一致的 7 局面探针,各自网络结构加载权重。

代1(v3 vs v2)

探针 期望 v3代1 v2代1
白活四 -1.0 -0.327 ✅ +0.298 ❌
白活三 -0.6 -0.310 ✅ -0.199 ✅
白冲四 -0.8 -0.324 ✅ +0.089 ❌
通过 3/6 1/6

代3/代4(三方)

v1 通过 v2 通过 v3 通过 v1 激活率 v2 激活率 v3 激活率
3 3/6 2/6 3/6 49.9% 40.0% 47.7%
4 3/6 0/6 3/6 53.0% 43.0% 47.7%

观察

  1. v1 和 v3(同为简化版网络)初代探针模式几乎一样:白方探针(白活四/白活三/白冲四)全对、黑方全错------同架构同代 → 学习轨迹相似
  2. v2(论文版)初代震荡更厉害:代3 通过 2/6,代4 全错 0/6(白方探针全部翻反)------论文架构参数更多、随机初始化影响更大,早期价值头更不稳定
  3. v1 的 value 输出幅度极小(-0.04~-0.13) ,接近初始化小值、符号只是碰巧对;v3 幅度明显更大(-0.25~-0.34),说明 v3 在代3 已形成"较确定的判断"(虽然黑方方向还错)

⚠️ 保守结论(重要)

  • 代1-4 阶段:v1 ≈ v3 > v2------早期差异主要来自网络结构 (简化版初代更稳),策略优化的红利要等几代才显现(v2 到代12-15 才爆发转正)
  • 单代/双代对比噪声大 ,两边自对弈数据完全独立,不能据此断言架构优劣
  • 这是"进行中的实验"而非定论:v2 的价值震荡是否会收敛、v3 的黑方探针能否像 v2 一样转正,要等 v3 跑到 5-10 代再做一次对比才能看清
  • 最初预期"策略优化是主因"------早期数据并不完全支持,但策略红利可能滞后显现;最终结论待 v3 中期数据

7.6 最终结论:v3 跑到代10,三时间点 PK 定案(2026-08-01)

v3 按计划跑到代10 收尾(新机停训),期间在代5/代8/代10 三个时间点与 v2 同代 PK(sim=400、分先10局、GPU 实战):

PK 结果 v3 胜率 性质
代5 0-4 0% 爆杀
代8 2-8 20% 爆杀(7局速杀)
代10 1-9 10% 爆杀(8局≤19步)

代10 探针(同场验证) :v3 黑活四 -0.31 ≈ 白活四 -0.34(镜像相等,颜色失明依旧);空盘 -0.24(悲观偏置依旧)。v2 空盘 +0.04、黑活四 +0.13(方向转对,幅度待涨)。v3 跑了 10 代,value 头依然"看不懂"局面。

最终定论

  1. "策略优化是主因"------被正式否决。v2/v3 共享完全相同的策略(固定 400 模拟、温度退火、α=0.03、回放池5代、100局/代),唯一变量是网络架构,结果棋力差出 0-4 / 2-8 / 1-9。
  2. 架构差异(value 头单层 fc vs 双层 fc)是价值判断能力的主要决定因素。v3 简化版 value 头 10 代训练未突破表达力天花板:颜色失明、悲观偏置、PK 全时段被速杀。
  3. v3 的"追赶"是假象:代8 的 2 胜全靠长盘苟局(35-43步,不犯错磨赢),从不主动杀;代10 回归 10% 胜率,且唯一胜局又是 35 步长盘。
  4. v2 的价值头在代14 前后转正 (黑方探针翻正、value_loss 代28 到 0.09),论文版双层 fc 的表达力红利随训练逐步兑现;简化版单层 fc 则从代1 到代10 纹丝不动------这是架构决定论最干净的证据。

一句话总结策略优化让"能学会"的架构学得更快更好,但救不了"学不会"的架构。想提升棋力,先确认你的网络结构有足够的表达力,再谈调参。

八、总结与展望

8.1 这一路最大的几个体会

1. 一张 4090 就够训出能下棋的 AI,真的不用 5000 块 TPU

128 通道 × 8 残差块(2.4M 参数),每代 25-50 分钟,两张卡 2 天跑完 100 代。DeepMind 的配置是给围棋准备的,五子棋这种小棋盘,个人玩家完全玩得转。

2. 策略和架构都重要,但架构决定天花板

同样的网络,把模拟量固定、加上 temperature 退火、Dirichlet α 调到标准值、回放池留 5 代------v2 用 14 代就追上了 v1 练 100 代的水平。但 v3 对照实验证明:这套策略救不了简化版 value 头 (单层 fc)------同样跑 10 代,v3 被 v2 打出 1-9,value 头依旧"看不懂"局面。策略优化让"能学会"的架构学得更快更好,架构表达力不够时策略只是锦上添花。

3. loss 降了别高兴太早

这是血泪教训。v1 的 91 代 loss 全场最低,实战却是三个代里最菜的。loss 低 ≠ 棋力强,它只是"模型对自己的棋风越学越熟"。真要判断强弱,探针、激活率、PK 对弈,至少两个对上了再下结论。

4. 半夜 3 点训练挂了,救你的不是算法是工程

日志覆盖、工兵不重启、重启后整代跳过......每一个"小问题"都能让 2 天的训练白跑。日志一律追加、脚本能断点续跑、改完代码记得 git------这些"无聊"的事,才是长时训练的保命符。

5. 想搞清楚"为什么有效",就做对照实验

v1 进步了,但说不清是策略的功劳还是架构的功劳,于是搞了 v3 对照实验(同策略、不同架构)。想归因就控制变量,别拍脑袋

8.2 踩坑清单速查表(建议收藏)

# 一句话修复
1 TF 在 4090 慢 10× 换 PyTorch(原生支持 Ada Lovelace)
2 batches 太低 batches × batch_size ≈ 每代样本量 × 90%
3 torch.compile + CUDA Graph 死锁 只用编译或只用 Graph,二选一
4 FP16 缺 GradScaler 加 scaler;log 用 clamp(min=1e-7)
5 9 工兵并行编译死锁 工兵 disable_compile=True
6 search() vs decide() 评估必须用 decide();速度差自查
7 日志被覆盖 一律 >> 追加
8 工兵重启整代跳过 last_iteration = max_seen - 1
9 假赢误报 trajectory 是落子前棋盘,补最后一手再验证
10 config_hash 失效 batches/batch_size 移出 hash;恢复用 weights_it{N}.pt
11 神经元死亡 数据多样性 > 模拟量;激活率 <50% 警告
12 CUDAGraph 2080Ti 坑 learn() 用 raw_net
13 纯网络评估不可靠 15×15 必须用 MCTS
14 模拟量不匹配排名反转 评估 sim ≥ 训练 sim

8.3 接下来想搞的事

  1. v3 跑到 3-5 代,三方拉出来对比 ------ 已完成(2026-08-01):v3 跑满代10,三时间点 PK(0-4 / 2-8 / 1-9)正式定案:架构(value 头深度)是主因。详见 7.6 节
  2. 试试更大的网络(256 通道 × 19 残差块,~10M 参数)------4090 应该带得动,看能不能再涨一截棋力
  3. 上 challenger-vs-best 淘汰机制------像 Leela Zero 那样,新代打赢旧代才上位,从根上防神经元死亡
  4. 部署到 Web 对战------Flask 应用已经写好了,手机浏览器就能跟 AI 下,欢迎来虐
  5. 换游戏接着玩------同一套代码(boardgame2)改个环境就能训黑白棋、井字棋,想试试

8.4 代码与资源

项目脚本在 Gitee:kid141212/alpha-zero-gomoku,主要文件:

  • Gomoku-v0_pt_v2.py / Gomoku-worker_v2.py --- v2 主线训练
  • Gomoku-v0_pt_v3.py / Gomoku-worker_v3.py --- v3 对照实验
  • pk_v2_12_vs_v1.py / compare_v2_v3_itN.py / pk_v2_v3_it5.py --- PK 与探针对比脚本(支持任意代)
  • verify_v3.py / net_analyze.py / value_test_real.py --- 体检分析工具

全文完。从一张 4090 开始,到一个会下五子棋的 AI,中间隔着的不是算力,是 14 个坑和一套可靠的评估方法。


💬 写在最后

这篇文章能写出来,靠的是一个星期里一局一局跑出来的真实数据,没有一个结论是拍脑袋的。

写到这里,想多说几句心里话。

2017 年 AlphaZero 横空出世那天,我还在读书,盯着那篇论文和它碾压围棋世界的棋谱看了很久。后来 Leela Zero 开源了,社区众筹买算力训练棋谱,需要志愿者在各自的电脑上跑自对弈。我挤进那个圈子,干的是最不起眼的活------收钱、租机器、算账。看着别人讨论棋谱、分析棋局,我心里想:也许有一天,我不是算账的那个,而是参与讨论的那个。

但那时候没有条件。学业、找工作、口袋里也没多少闲钱买算力。只能断断续续地学点深度学习,买一本强化学习的书放在桌上,慢慢地看。

这一看,就是七年。

直到今年,终于有了自己的时间和一点预算。我把书重新翻出来,租了两张 GPU,从 AlphaZero 五子棋开始,一局一局地跑。跑出了 v1、v2、v3,跑出了 0-4、2-8、1-9 这三组 PK 比分,也跑出了这篇长文。

所以这不仅仅是一篇技术总结。它是我和八年前那个深夜看 AlphaZero 论文的自己,之间的一次对话。

如果你也在折腾 AlphaZero / 自对弈 / 强化学习,欢迎:

  • 👍 点赞 + ⭐ 收藏:14 个坑的速查表在 8.2 节,遇到问题回来翻
  • 💬 评论区聊聊 :你训练时遇到过什么奇葩坑?或者对"策略 vs 架构"的对照实验结论有不同看法?三时间点 PK(0-4 / 2-8 / 1-9)已经给出答案:架构(value 头深度)决定天花板
  • 🔗 关注:后续会更新更大网络的尝试、Web 对战部署

项目代码在 Gitee:kid141212/alpha-zero-gomoku,感兴趣的可以拉下来自己跑。

相关推荐
mingo_敏2 小时前
DeepAgents : 检索(Retrieval)
人工智能·深度学习·langchain
Python私教2 小时前
Django 接入 AI 大模型实战:从零做一个流式聊天网站
人工智能·python·django
Python私教2 小时前
Django 接入 MCP 实战:让 AI 安全调用数据库和业务接口
人工智能·python·django
Python私教3 小时前
Django 搭建 AI 本地知识库:文档上传、向量检索与智能问答
人工智能·python·django
always_TT3 小时前
【Python 日志记录:logging 模块入门】
开发语言·python·php
LaughingZhu4 小时前
Product Hunt 每日热榜 | 2026-08-01
人工智能·深度学习·神经网络·搜索引擎·百度
lipku5 小时前
数字人直播开源项目LiveStream
python·开源·数字人·数字人直播
阿童木写作6 小时前
Python实现亚马逊商品图批量智能抠图教程
人工智能·python
axinawang6 小时前
第24课:while循环的应用
python