☰
从零搭建深度学习围棋AI:特征编码、网络训练与MCTS联调实战
2026/10/17 7:42:05 网站建设 项目流程

把特征、网络、搜索这三件事从"听说过"变成"跑得通",是我写这篇续篇最想解决的问题。上一篇写深度学习与围棋的结合思路时,有不少读者留言说原理能看懂,但一到自己动手搭系统就卡壳:棋盘状态怎么编码?策略网络和价值网络到底怎么分工?训练好的网络又如何跟蒙特卡洛树搜索接上?这些问题单独拎出来都不算难,但串在一起就容易顾此失彼。

所以我这篇直接换个写法,不聊太多理论铺垫,而是按"从零搭一个能下棋的深度学习围棋程序"这条路走,把每一步的关键逻辑、参数选择和踩坑经历都摊开讲。我自己在这个项目里也反复折返过好几轮,有些坑纯粹是经验问题,文档里根本不会写。

这篇的内容适合两类人:一类是接触过深度学习、想找一个完整项目练手的开发者;另一类是懂围棋规则、想搞清楚现代围棋AI内部机理的爱好者。无论你属于哪一类,这篇文章都能给你一条清晰可执行的路径。

1. 整体设计思路:为什么围棋AI不能只用深度网络

1.1 围棋的复杂性决定了单模型撑不住场面

很多刚入门的人会有一个直觉:既然深度学习这么强,训练一个网络直接学会下棋不就行了吗?真实情况远没有这么简单。围棋的合法局面数量级在10的170次方左右,棋盘上任意一个落子都会连锁改变整盘的攻防态势。单靠一个神经网络直接输出最终胜率,中间会丢失掉大量计算信息,尤其在对杀、打劫、收官等精确计算场景里,纯网络的直觉判断很不可靠。

所以在现代围棋AI的架构里,深度学习从来不是单打独斗。策略网络负责提供"当前局面下哪些点值得考虑"的先验概率,价值网络负责给出"当前局面谁占优"的整体评估,而具体的落子决策还需要借助蒙特卡洛树搜索把这些信息组合起来,通过多次模拟推演找到相对靠谱的那手棋。

1.2 双网络分工与搜索协同

我最初设计系统时也犹豫过,能不能只用策略网络加简单搜索?后来发现效果很一般。原因在于策略网络擅长模仿"人类会下哪里",但它并不真正理解"下这里之后会发生什么"。价值网络则相反,它不关心具体哪手棋好,只关心局面发展到最后谁赢。

这两个网络单独拿出来都有短板,合在一起刚好互补。策略网络裁剪搜索分支,只沿着有意义的路径展开;价值网络在搜索叶子节点给一个整体评估,避免每次都要把棋局下到终局才知道胜负。搜索过程则像一个"慢思考"模块,把两个网络的判断反复组合验证,形成最终的落子决策。

1.3 我踩过的第一个坑:搜索深度与网络速度的平衡

刚开始联调时,我陷入了一个误区:为了追求更强的棋力,不断增大搜索模拟次数。结果每步棋思考时间从1秒涨到10秒,棋力提升却不明显。后来分析才发现,问题出在网络推理速度上——搜索的每一轮都要调用策略网络和价值网络,网络推理太慢,模拟次数再高也跑不动。

这个阶段的教训是:先把网络的推理速度优化到可接受的范围,再考虑增加搜索次数。一般来说,单步搜索控制在100到200次模拟之间,配合一个不太深的网络,就能达到一个相对可用的棋力水平。

2. 棋盘状态编码:从二维棋盘到特征平面的转换

2.1 基础特征设计:19×19的N层张量

把围棋棋盘送进神经网络,首先要解决的是"怎么用数字表示盘面"。最直接的做法是构造一个形状为19×19×N的特征张量,其中N是特征通道数。每个通道对应一种棋盘信息的二值或连续值表示。

我实际用的特征平面包括:当前方棋子位置、对手棋子位置、当前方棋子的气数、对手棋子的气数、劫争位置、上一手落子位置,以及当前方最近两步落子位置。这些通道叠加起来,网络的输入层就是一个完整的局面快照。这里有一个容易被忽略的点:特征表示必须始终以当前行棋方为视角。换句话说,轮到黑棋时,黑棋是"当前方",轮到白棋时,白棋是"当前方"。如果不做这个视角转换,网络学到的规律会被镜像盘面干扰,模型泛化能力大打折扣。

2.2 气的计算与合法落子掩码

围棋的"气"是棋块存活的根基。计算气时需要找出与棋子直接相连的空的交叉点。我常用的方法是并查集加上邻居统计:先对棋盘做连通块标记,然后每个连通块统计周围空点的数量。

除了气,还要维护一个合法落子掩码。围棋的合法落子有两个硬约束:不能落在已有棋子的位置;落子后不能造成自己无气(自杀),以及不能违反打劫规则。这个掩码在训练和推理阶段都要用。训练时,网络输出的所有落子概率乘以掩码,把非法位置遮掉再计算损失;搜索时,掩码直接决定哪些子节点可以展开。

提示:打劫判断的细节很容易出错。我的做法是维护一个全局的劫争位置标记,每次检测到提子后,如果形成的局面跟上一手之前完全相同,就认定这个落子属于打劫,需要禁手。

2.3 棋谱数据预处理与增强

训练数据如果没有处理好,后面网络怎么调都很难有起色。我自己用的是公开的棋谱数据集,预处理时有三个重点:切分局面、标准化视角、数据增强。

切分局面是遍历每一手棋,截取当时的棋盘状态和对应的落子位置。标准化视角是上面提到的,遇到黑棋落子时当前方是黑棋,遇到白棋落子时当前方是白棋。数据增强相对简单:围棋具有旋转和镜像对称性,一张棋谱可以扩展出8种等价变换,我在训练时随机选取一种变换输入网络,相当于免费扩充了8倍训练数据。

3. 网络结构选型与训练细节

3.1 主干网络:卷积加残差块的组合

处理围棋这种网格状数据,卷积神经网络是天然的选择。基础的卷积层可以捕捉局部棋形特征,比如小飞、跳、挂角这些基本棋形,深层的卷积核堆叠后则能感知全局态势。

我的网络主干部分采用了一个相对轻量但稳定的结构:第一层是一个3×3卷积,输出32个通道;中间堆叠了5个残差块,每个残差块包含两个3×3卷积、批归一化和ReLU激活。选择残差块而不是普通卷积块的原因很直接——残差连接能让梯度在深层网络中传播得更顺畅,训练起来更稳定,收敛速度也快不少。

3.2 策略头与价值头:共享主干,独立输出

网络在主干之后分成两个头。策略头负责输出每个落子点的概率分布,它先经过一个1×1卷积降维,然后接一个全连接层,输出维度是棋盘大小加1——多出来的那1维是"停一手/跳过"(在某些规则中存在)的选择;在实际训练中,如果不考虑停一手,输出维度就直接是361。

价值头负责输出当前局面的预估胜率。它同样先经过1×1卷积,再接两个全连接层,最后通过tanh激活函数把输出压缩到-1到1之间,对应黑棋视角的胜率。负值表示黑棋劣势,正值表示黑棋优势。

3.3 监督学习的损失函数与优化器选择

在第一阶段,我用人类棋谱做监督学习。策略网络部分用的是交叉熵损失,让网络输出的落子概率尽量贴合人类高手的落子选择;价值网络部分则用均方误差损失,训练目标是棋谱对应的最终对局结果。

我把两个损失按一定权重相加,作为总的优化目标。权重设置我有一些心得:最开始我让价值损失占比太高,结果网络只顾着评估局面,落子选择变得很"平庸";后来把策略损失权重调高到大约4比1,棋风才正常起来。优化器选的是带动量项的SGD,学习率初始设置为0.01,训练过程中用了学习率衰减策略。Adam在围棋任务上没有SGD加动量的效果稳定,这一点我实测下来比较明显。

3.4 训练过程中的过拟合应对

围棋棋谱数据量虽然不小,但用卷积网络训练仍然存在过拟合风险。我采用的办法包括:每轮训练随机抽取棋谱变换组合,把Dropout加在策略头和价值头的全连接层之间,以及在训练中监控验证集上的损失变化。

一旦发现验证集损失开始回升而训练集损失还在下降,就果断降低学习率并减少训练轮数。过拟合的另一个信号是,网络在验证集上预测的落子概率分布变得"过于自信",也就是某个点的概率接近1而其他点几乎为0。这时候我会增强数据增强的强度,同时抑制网络容量。

4. 强化学习阶段:自我对弈与数据生成

4.1 为什么需要自我对弈

监督学习阶段得到的网络水平,最高也只能逼近人类棋谱所覆盖的棋风。它有一个明显短板:面对人类棋谱中很少出现的局面,网络会变得很茫然。解决这个问题的标准做法是引入强化学习,让当前网络自己跟自己对弈,不断生成新的棋局数据来迭代改进。

自我对弈的价值在于,网络能在探索中碰到大量"非人类式"的局面,并学会应对。这些经验反过来强化网络对局面的理解,逐步突破监督学习的上限。正式训练中,自我对弈最终产生的棋力贡献往往超过监督学习阶段。

4.2 自我对弈的奖励设置与样本组织

自我对弈中,每盘棋结束后会得到一个最终结果,通常用1和-1分别表示当前视角的胜与负。整盘棋的每一个中间局面都标注该盘的最终结果,这些"局面-结果对"就是价值网络的训练样本。

策略网络的强化信号相对间接。我采用的方法是计算搜索后的访问次数分布,并以其作为目标概率来训练策略网络——因为高访问次数的落子点往往是搜索推演后被认为更好的着法。这种方式比直接使用最终胜负做策略梯度更平滑,训练也更稳定。

这里有一个组织样本的细节:虽然棋盘上每一步的局面差异巨大,但相邻几步之间高度相关,如果按时间顺序连续抽取样本,会让网络在局部反复拟合同一盘棋的走势。我的做法是把自我对弈生成的棋局拆成"局面组",每组抽样时打乱局面顺序,再随机抽取训练批次,这样能有效降低相关性。

4.3 一个简化版的自我对弈循环

我自己实验时用的循环可以概括成下面这个流程:

for iteration in range(total_iterations): # 第一步:用当前网络进行自我对弈,生成棋谱 games = self_play(current_network, num_games=200) # 第二步:从棋谱中提取训练样本 train_samples = extract_samples(games) # 第三步:用样本训练网络,更新参数 train_network(train_samples) # 第四步:每隔若干轮评估一次新网络的棋力 evaluate_network(current_network, best_network)

每次迭代后生成的棋谱会存入一个样本池,训练时从中随机抽取。样本池的容量我控制在最近50到100次迭代生成的棋谱总量,太老的数据会干扰新策略的学习。

4.4 评估策略:防止强化学习震荡

强化学习训练中经常出现"能力回退"现象:某个迭代的网络在自我对弈中表现很好,但真实对弈却输给了上一个版本。为了防止这种震荡,我每次迭代结束后会固定地让新旧网络对弈若干局,只有新网络的胜率达到一定标准,才更新原有网络。

这个评估机制的代价是会额外消耗一些算力,但在训练早期非常值得。如果省略评估直接更新,我遇到的情况是网络策略在几轮迭代中反复横跳,最终收敛到很糟糕的状态。评估机制相当于一个保险阀,确保每一步更新都是稳定向前的。

5. 与蒙特卡洛树搜索的联调

5.1 MCTS的基础流程:选择、扩展、评估、回传

蒙特卡洛树搜索的四个阶段值得重新理一遍。首先是选择,从根节点出发,按照一定的评分公式不断选择子节点,直到到达一个叶子节点。然后是扩展,在叶子节点处根据策略网络的输出创建子节点,并为每个子节点记录先验概率。

然后是评估,调用价值网络对叶子节点局面做胜率预测,同时也可以使用快速走盘做一个补充估计。最后是回传,把评估得到的价值沿着路径逐层向上更新累计访问次数和累计价值。这个过程循环执行,直到搜索预算用完,最终根据根节点子节点的访问次数分布来选择落子。

5.2 UCT公式与先验概率的融合

选择阶段是整个搜索的核心。常用的评分公式是UCT变体,其中关键部分是:

score(a) = Q(a) + C_puct * P(a) * sqrt(sum_N) / (1 + N(a))

这里Q(a)表示子节点a的平均价值,P(a)是策略网络给出的先验概率,N(a)是该子节点的访问次数,C_puct是一个平衡探索与利用的常数。我实验中C_puct取1.0到2.0之间比较合适,取值过小会导致搜索过早收敛到局部最优,取值过大则会让搜索过于分散,棋风变得杂乱。

这里有一个细节值得注意:搜索阶段使用的先验概率需要做一个温度处理。监督学习阶段网络输出的概率分布往往偏尖锐,直接用于搜索会让某些分支几乎不被探索。我会对策略网络的输出取对数并除以温度系数再做归一化,让概率分布适度平滑,这样可以显著提升搜索的多样性。

5.3 联调时的三个关键调试点

第一个调试点是根节点落子策略。搜索结束后,可以直接选择访问次数最多的子节点,也可以根据访问次数分布按概率采样。访问次数最多相对稳健,适合正式对弈;概率采样增加了随机性,适合自我对弈数据生成阶段,可以让棋谱更丰富。

第二个调试点是价值网络与搜索深度的配合。如果搜索深度很浅,价值网络的评估就承担了过多压力;如果搜索深度很深,价值网络的作用会被稀释。我自己在9路棋盘上实验时,把搜索模拟次数设到100左右,价值网络的作用刚刚好体现出来。

第三个调试点是搜索线程与网络推理的并发。网络上每个局面推理都是独立的,可以并行处理。我在实现时用了批推理的方式:搜索过程中把多个局面积攒成一个批次,一次性交给网络处理,能有效利用GPU并行能力,整体速度能提升好几倍。

5.4 开局库与搜索的衔接

纯搜索在开局阶段会非常"慢热",因为开局分支太多,而策略网络给出的先验概率又不稳定。一个省力的做法是维护一个简易开局库,在开局前若干手直接走库内着法。开局库可以由职业棋谱中常见布局整理而来,也可以由自我对弈高频选点自动统计生成。

我倾向于先用棋谱高频选点生成开局库,等到强化学习训练到一定阶段再把开局库逐渐关闭,这样可以让搜索在开局阶段也有充分探索。这个过渡需要小心,不要突然关闭,否则网络在开局会有一段明显的棋力下滑期。

6. 常见问题与排查思路实录

6.1 特征对齐错误导致训练崩溃

我在最早训练策略网络时遇到过一个非常隐蔽的bug:特征平面中的当前方视角与训练标签中的落子方不一致。具体来说,有些局面我按黑棋视角编码,但标签却是白棋的落子,结果训练损失始终降不下去。排查了很久才发现数据管道里一个索引偏差。

排查这种问题有一个笨但有效的方法:在训练循环中定期把网络输入的可视化图打印出来,把特征平面还原成棋盘图,然后人工检查几盘棋的标签是否符合棋谱实际走子。一旦发现视角不一致,就能快速定位问题。

提示:特征编码是整个系统的地基。地基出错,后面网络设计和搜索调参再怎么折腾都没用。动手前先仔细核对特征平面的维度、视角和取值范围。

6.2 损失不下降或下降极慢

损失不下降的原因通常有几个方面。一个常见原因是学习率设置不合理,可以尝试把学习率从0.01降到0.001,或者反过来调大,观察损失的响应。另一个原因是网络结构太浅或太深:太浅无法拟合数据规律,太深在数据量不够时难以优化。

还有一个容易被忽略的原因是批归一化的momentum参数。默认的0.1在小批量训练时会让均值方差估计抖动过大,影响收敛。我把momentum调到0.2左右之后,训练稳定性有了明显提升。

6.3 MCTS访问次数分布极端化

搜索结束时如果几乎所有访问次数都集中在某一个子节点上,并不一定是好事。这可能意味着先验概率的温度参数设置过低,策略网络在搜索早期就过度引导搜索到单一分支,导致搜索缺乏多样性。

我遇到过的一个具体场景是训练后期网络变得"过于自信",先验概率几乎变成了one-hot。把温度参数从1.0调到1.5之后,搜索重新恢复了探索能力,棋力反而提升了。这个反直觉的调整说明搜索的多样性有时比即时准确率更重要。

6.4 对弈中反复走同一块棋

如果搜索在局部反复走棋,形成"打将"式的循环,最常见的原因是搜索模拟次数太少,网络价值评估对局部战斗的判断不准确,导致搜索反复认为某一手棋能扭转局面。增加搜索次数通常能缓解,但更根本的办法是优化价值网络的评估质量。

我自己的排查顺序是:先增加搜索模拟次数看是否改善,如果改善说明问题在搜索深度;如果没有改善,则需要回头检查价值网络在局部战斗局面的评估是否过于极端,必要时在训练数据中加强对杀局面的采样比例。

写在最后的实践建议

这几轮项目做下来,我最大的体会是:深度学习围棋系统的难点不在于某一个单独的模块,而在于模块之间的衔接和数据流转。特征编码、网络训练、自我对弈、蒙特卡洛树搜索,任何一环出问题,整体棋力都会断崖式下跌。

如果你也准备动手做类似的项目,我给一个最实际的建议:先从小棋盘开始。9路棋盘的局面复杂度远低于19路,迭代速度快、实验成本低,特别适合验证网络结构和训练流程。在9路上把整套流程跑通、把坑都踩过一遍之后,再迁移到19路,难度会小很多。

另外一个小技巧:训练过程中定期保存网络权重文件和对应的评估结果,方便回滚到历史最优状态。强化学习训练中网络会经历起伏,手动记录关键节点的棋力表现,能帮助你更清晰地判断哪些改动是真正有效的。

围棋AI这条路很长,但每一步都很有收获。希望这篇内容能帮你少走一些弯路,也期待你跑出自己的结果。

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

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

立即咨询