spdc-elm.github.io
➜~/blogs/newton-chess-rl-3
← back to blogs

牛顿棋RL 3:苦练先手的小AI

本文的 AI 一般指训练的下牛顿棋的这个神经网络,而 LLM 则指大家比较熟悉的大语言模型,也是平常经常说的 AI,比如 GPT-5.6-luna,GLM-5.3-flash 之类的模型

牛顿棋RL 2:先手孱弱的偏科生 上回说到,我们的 AI 训瘸了,后手很强但是先手孱弱。真正的棋圣,应当是技术较为均衡的才是。

比如还记不记得上一篇的图,它开局一般会下这里…先给对面送一分:

这个开局给我看懵了

但是这里需要区分,棋类规则本身导致的先手后不平衡,和 AI 的先后手能力不平衡,是两个问题。前者其实围棋、象棋、五子棋都有,甚至五子棋无禁手时黑子必胜,围棋先手也有几目的优势,等等。根据个人的体感,牛顿棋因为“推到边缘”的机制(关于规则可以看 牛顿棋RL 前言:我想训练出牛顿棋的第一个“AI棋圣” ),后手下暴露给敌人的弱点可能会相对更少,所以后手应该是有些先天优势在的。至少,前两个版本的 AI 训出来也支持这一点:两个从 0 开始训练的版本都出现了后手比先手强很多的情况。

分析问题:value 的学习目标太粗了?

回顾 牛顿棋RL 1:强化学习靠左脚踩右脚,真的可以上天? ,当时我们的 value 学习的目标定义是 如果“我方”赢了就记真实 value 为 1,输了就是-1,平局就是 0。但我跟 AI 指出模型瘸腿的问题之后,它一通分析,确实同意了我当时的直觉:

“其实我一开始就感觉,如果不只给输和赢的信息,而加上输多少赢多少的信息,可能会更好。”

其实并不难理解:如果游戏本身有先手优势,那么在最初菜鸟的 AI 自对弈中,后手可能更容易赢。后手“学习的难度”可能比较低,于是在后续进一步自对弈中后手赢得越来越多,先手则怎么计算下来,发现都是“-1”,都是输。如果怎么下都是得到 -1 的 真实 value 的话,就很难去真正比较每一步的价值了。因为,我们只给了它“输、赢、平”的反馈,却没有更细致的,比如分差多少的反馈。那输 1 个子也是输,输 10 个子也是输,先手 AI 没法区分“输 1 个子”的下法比“输 10 个”的更好,自然就没法学习了!

所以第一步,重塑 value 的学习对象。牛顿棋按外围的棋子多少来结算,自己的外围越少越好,在盘面结算时, 真实value 用 对方外围-自己外围的数量 来衡量。 这个 value 越大,说明赢得越多;value 越小(甚至负数),说明输得越多。这样一来,哪怕先手 MCTS 推演的很多局面都是输的情况下,value 也有输得多和输得少的区别。如此学出来的 value 评估网络,或许可以补足先手学不会的问题。

其实说到底,也很像人的学习。你让一个一直下不赢的人目标就是要赢,那他怎么下都下不赢只好摆烂了;但是如果要求是每天输少一点点,这种跳一跳能够着的目标,可能就能慢慢进步,找到自信了。

LLM 在重新设计 value 的时候,用了一个更细致的方法:做了归一化。所谓的归一化,就是把不同大小尺度的数据统一到同样的尺度去。

real value= (对方外围-自己外围的数量)/外围格子总数

它是在考虑未来可能扩展到更大的棋盘,仍然能用统一的value来衡量。比如变成了 10×10,总格子数从 9x9 的 32 变成了 36,那么假如同样是输 4 个子的情况下,显然是 9x9 这边输得更惨一点。直接比的话假如是同一个 value 网络,会以为局面 value相同呢。而归一化之后(除以外围格子总数)来比,这样就公平了。

这里留了一个秘密:为啥我们的网络在小棋盘训练还能直接拓展到更大的棋盘去用?怎么做到的?这里先卖个关子,如果以后有需要详细拆解网络结构设计再讲。

修改了 value 网络之后,因为我们对于 value 的定义都不一样了,原先我们训的那个模型的 value network 预测的是旧的 value,自然不能直接套过来让它继续预测新的 value,得重头训一个?但是之前那个模型学会的后手的那些经验咋办呢,难道都丢掉吗?

这就得看我们网络的结构是怎么设计的了。

极简主义的AI网络架构示意图

为了防劝退,这里我就直接隐去了大量的细节,只留下了这么个概念图。可以看到,整个网络由共享的部分与 Policy Head 和 Value Head 这两个分支部分组成。那么我们更换了 Value 的训练对象之后,需要重新初始化参数的只有 Value Head,Policy Head 和共享主干则可以沿用原有的参数,这样子就能尽可能地不浪费我们之前训练的成果啦~

数据增强

Data augmentation,数据增强,也是机器学习常用的手法。大致的意思就是训练用的数据不够的时候,可以通过一些简单的方式来造多几份。比如,如果某些训练的任务天然具有对称性的话,我们就可以将它对称对称、旋转旋转来把一个数据变成多个。

很明显,我们的棋盘是具有旋转对称性和镜面对称性质的,也就是说同一个对局,你把它旋转对称和镜面对称之后,仍然是等价的。所以一个对局的数据经过三次旋转(x4),加上轴对称(x2),就可以变成 8 份了。同一个 iter 造出来的 240 局的数据就可以扩成 8 份,那么每次 iter 训练之后进步或许会更大?(毕竟感觉 AI 训练很大程度就是优质 data driven 的)而且,数据里还天然教会 AI 棋盘是具有对称性质的道理。

够了吗?

每个 iter 内还是 240 局,换成这个新的 value 之后训练了 210 个 iter,之后跟旧版模型对弈测试:

候选模型对手候选胜和棋候选负
Margin-v2+D4@24Corrected PUCT@2101941205

嗯,跟之前版本差不多五五开了,说明新的 value head 可能差不多训回了原来的水平(适应了新的躯壳…)

那再看看自我对战,先手是否有所改变呢?

模型先手方胜和棋后手方胜总局数
Margin-v2+D4@2470193200
Corrected PUCT@21010199200

只能说,略有进步吧,但是感觉进步还是有点慢,虽然只训了 24 个 iter。再拷打 LLM ,让它想想还有没有什么招。

同样做一个体感测试,我让 AI 部署到网页预测一下第一步,实验发现,它一般会下在这个格子:

感觉…确实比之前下边缘好了,但是还是没什么理由,不太靠谱?

插一个拓展部分,感兴趣的可以看看

除了数据增强以外,AI 还发现,训练时的 loss 函数由 policy 距离目标的 loss 和 value 的 loss 共同构成 ( L=CE(π,p)+1⋅MSE(v,m)L=CE(\pi,p)+1\cdot MSE(v,m) ),但是这两个 loss 的大小量级不太一样,比如 policy loss 典型值是2.6 左右, value loss 则是0.005~0.008。

虽然 policy 用的是交叉熵 CE(cross entropy),value loss 用的是均方误差MSE(mean squared error),两个也不能直接量级去比较。但是,简单的说就是可能发生 policy 的 loss 在反向传播的时候对共享参数部分的影响大,value 的因为值本身比较小,反向传播对于共享参数部分影响小,有一种不平衡。

所以 AI 试着加了个系数,L=CE(π,p)+λ⋅MSE(v,m)L=CE(\pi,p)+\lambda\cdot MSE(v,m) ,并且测试之后选择了 λ=4\lambda =4 来训练。

这么做之后,在刚刚的模型基础上又训练了 60 iter 的结果:

候选模型对手候选胜和棋候选负
λ=4@60Corrected PUCT@2104200380

棋力上看,已经超越了 8-24 那个版本。那么内战呢?

模型先手方胜和棋后手方胜总局数
λ=4@6070193200

oh no,先手没啥进步,看来依旧没有解决啊。然后再体感测试一下第一步,发现还是下在刚刚那个位置,看来这个优化对先手提升并不大。

强制引导开局

有没有一种可能, AI 因为之前瘸腿,导致先验的 Policy 偏差一直第一手下在边边一些不好的位置。(毕竟复习一下 PUCT,在第一次选择的时候,很大程度就是由网络的 Policy 预测决定的)这样子就导致 AI 先手第一步一直不会下,然后就一直先手菜呢?

所以 LLM 给我想出了一个办法,就是强制在训练的时候,让它 15 种可能的第一手都下一遍(因为有对称所以不需要考虑全部 81 种)。

15种第一手可能的着法

在 λ=4\lambda =4 那个版本的基础上,就让它这么训了一个晚上,210 个 iter,第二日:

候选模型对手候选胜和棋候选负
opening15@210λ=4@602194177
opening15@210Corrected PUCT@2102510149

棋力确实有长进。还是看看自战:

模型先手方胜和棋后手方胜总局数
opening15@210212177200

看起来有点起色诶,但是训了 210 个 iter 才这样,感觉还是有点慢了。再看看第一步呢?

泪目了,小AI学会第一步下中间了

好消息!这个 AI 确实第一步下中间了,看上去更合理了一些。说明嘛,我们强制让他 15 个开局都走一遍之后,他在对比之下觉得确实走中间更好吧~符合直觉。

这个模型,也就是我挂在网页版的 8-25 的模型了,大家可以去体验体验。

看来…该进行一下文献调研,不能再闭门造车了。下一次,将会有质的飞跃!