跳到主要内容

训练一个黑白棋模型

· 阅读需 7 分钟

之前用 LabVIEW 编写了一个黑白棋程序,作为学习 XControl 的示例。那个程序基本完整,但是缺少一个 AI 自动走子的功能。最近抽空尝试训练了一个网络模型,添加到了演示程序中。

所有AI下棋的思路都是非常类似的:根据当前的盘面,给每个可以落子的位置打分,选取分数最高的那个走法。这里的关键就是设计一个最为合理的打分算法。

黑白棋最终判定胜负的标准是看谁的子多,所以最符合直觉的打分方法是:在一个位置落子后,把己方的棋子数目作为这个位置的分数。这个算法有个缺陷,就是当前棋子数最多的走法不见得就能保证将来也棋子数最多。

解决这个缺陷的思路有两条:其一是,不要为当前这一步打分,而是向后预测几步。比如,把双方各下三步棋(也就是总够六步)后的所有可能出现的局面都列出来,然后看那个局面得分最高。考虑的步数越深,棋力也就越高,极端情况,如果预测的足够深,甚至可以找到必胜的下法。这个方法的缺点是预算量非常高,增加预测的深度,计算量会指数级增加。可以通过剪枝做一些优化,但效果有限。

第二条思路是采用更复杂的打分算法,如果只考虑棋子数量还不够好,那么就也同时考虑落子的位置,稳定的棋子的个数,周围空间的个数等等。在这众多的因素中哪些更重要,如何分配权重,对于我这种非专业棋手来说是很难做选择的。不过这部分可以利用机器学习算法,让电脑自己找到最优解。

当然以上两条思路可以结合使用:先找到一个最优的打分算法,再尽量预测到更深的步数。

我尝试训练了一个基于神经网路的打分模型。

我采用的是一个只有一层隐藏层,64节点的全链接神经网络。单隐藏层64节点的神经网络对于解决黑白棋来说,有点太小了。我也不能确定多大才合适,但估计至少也应该采用一个七八层的CNN,才能达到不错的效果。不过,我的目标不是最好的效果,而是只要试验一下可行性。并且,我需要把训练好的模型移植到 LabVIEW 代码上,复杂模型移植起来太麻烦。所以我选择了这个最简单的模型。

模型的输入数据是当前棋盘状态(每个位置棋子的颜色)和一个准备落子的位置,模型输出一个实数表示得分。

训练模型的大致思路是:让模型分别持黑子和白子,自己跟自己下棋,并且记录下每一步的走法。最终胜利一方所有走过的位置都获得正标签,失败一方所有走过的位置都是负标签。用这些标签训练模型,然后重复以上过程。

在训练模型的过程中,我遇到了一些以前从未考虑过的问题。比如,采用何种激活函数。我开始采用的 ReLU 函数,但模型始终无法被训练到预期效果。调试后发现,是 ReLU 的神经元坏死造成的。ReLU 在输入值小于零时,梯度永远为0,这个神经元很可能再也不会被任何数据激活了。这对于目前常见的有着几万乃至几百亿节点的大规模模型来说,根本不是问题。但是对于一个本来就没几个节点的小型模型来说,损失神经元是非常致命的。我把激活函数换成 Sigmoid 之后,效果就好了很多。

我训练模型的方法效率非常低下。在下棋过程中,大多数的步骤可能都不是太重要,不论走这还是走那,对最终结果影响都不大。关键的就那么少数几步,可以决定输赢。但是在训练时候,我也不知道每一步的权重应该有多大,只能假设所有步数都是一样的权重。那些垃圾步不但占用资源,也会影响训练结果。

模型训练好之后,我把它移植到了之前编写好的 LabVIEW 黑白棋 VI 中。棋力算不上很好,但至少是明显优于随机落子。等我空闲的时候,看看还能不能进一步优化下。

程序在: https://github.com/ruanqizhen/labview_book/tree/main/code/%E7%95%8C%E9%9D%A2%E8%AE%BE%E8%AE%A1/Xcontrol/Othello