AlphaZero五子棋实战:从蒙特卡洛树搜索到策略价值网络实现解析
2026/9/11 17:37:08 网站建设 项目流程

简介:一套可直接运行的完整项目,基于蒙特卡洛树搜索与策略价值网络实现AI五子棋,面向希望借助实战代码理解强化学习博弈训练的开发者,适合作为算法实验、课程设计与毕设参考。压缩包共24个文件、约1.64MB,其中10个Python脚本覆盖游戏逻辑、纯蒙特卡洛树搜索、AlphaZero式搜索、策略价值网络、训练与人机对弈等环节,并带有预训练模型权重,解压后即可体验棋力表现。策略网络提供多种深度学习框架版本,可横向比较不同框架的训练效果,也方便读者按需调整网络结构、学习率与搜索参数。代码即跑即用,已有1940人学习,可在短时间内启动人机对弈,也可根据说明文档从零训练,逐步走通自我对弈、数据采样、模型更新与策略优化的完整强化学习流程。

1. 一个跑得起来的 AlphaZero 五子棋:reGomoku 项目拆解

拿到一份能直接跑的Python五子棋AI项目,第一反应是怀疑——AlphaZero类算法涉及蒙特卡洛树搜索(MCTS)、策略价值网络和自对弈强化学习,代码量通常不低。但这个reGomoku压缩包确实做到了即跑即用:解压后,human_play.py直接打开人机对战,train.py可以重新训练,best_policy_8_8_5.model2best_policy_6_6_4.model两种模型文件让你不必先花半天调环境,立刻就能观察一个从零开始、没有人类棋谱标注的AI,如何通过自我对弈学会攻防。

适合谁看?如果你是深度强化学习入门者,可以在这份代码里读到MCTS与神经网络如何互相提供训练信号;如果你已经跑过其他RL项目,正好可以对比纯MCTS和策略价值网络引导的棋力差距。下文按原理、代码、训练、验证四层递进拆开讲。

2. 蒙特卡洛树搜索与策略价值网络:从随机模拟到有偏引导

AlphaZero五子棋的核心不是一个孤立模型,而是两个模块闭环交互。MCTS负责在当前局面下做结构化搜索,策略价值网络给MCTS提供先验概率和局面优势估计,两者通过PUCT公式结合。先理解这个闭环,再看代码才会顺。

2.1 为什么不需要人类棋谱也能学会下棋

传统监督学习需要大量标注棋谱,而强化学习的自对弈流程是让当前模型自己跟自己下。每走一步,MCTS搜索得到的访问次数分布作为策略标签,最终胜负作为价值标签,两者共同更新网络。这正好对应摘要里所说的“从零开始学习五子棋博弈”。

五子棋规则简单、奖励稀疏,但状态空间又大到无法用查表法枚举。策略价值网络把局面映射成两个输出:每个落子位置的概率,以及当前玩家的胜率估计。MCTS则用这些估计来引导搜索,避免盲目的随机展开。相比早期围棋AI用随机rollout到终局,网络引导让每次模拟的信息量更大,训练效率更高。

2.2 四步循环:选择、扩展、评估、回溯

MCTS的每次模拟递归地选择树中的一个节点。在AlphaZero变体中,每个节点保存三个统计量:访问次数N(s,a)、累计价值W(s,a)、平均价值Q(s,a),以及策略网络给出的先验概率P(s,a)。选择阶段使用PUCT公式:

U(s,a) = Q(s,a) + c_puct · P(s,a) · sqrt(N(s)) / (1 + N(s,a))

其中c_puct是探索常数。当子节点访问次数少时,先验概率主导选择;访问次数增多后,Q值权重变大,算法从探索逐渐过渡到利用。

阶段动作关键信息
选择递归选择PUCT值最大的子节点直到叶子节点
扩展为叶子节点的所有合法落子创建子节点使用合法动作掩码
评估用策略价值网络输出(value, prob)纯MCTS则随机rollout到终局
回溯将value沿路径累加到每个节点更新N、W、Q

扩展阶段并不是一次性创建所有子节点。为了节省内存,通常只有节点被选中后才会创建其全部合法子节点。而评估阶段,AlphaZero不再执行随机模拟,而是直接调用网络返回价值。这里有个容易忽略的点:value的视角。网络输入以“当前玩家”为第一视角,如果轮到对手走棋,输入的特征必须切换,否则价值符号会颠倒,训练时梯度直接错乱。

2.3 PUCT选择与回溯的代码简化

下面是一段简化的选择与回溯逻辑,对应mcts_alphaZero.py中的核心流程:

def select_child(self): best_score = -float('inf') best_action = -1 best_node = None for action, child in self.children.items(): # child.n 访问次数,child.w 累计价值,child.p 先验概率 score = child.q + self.c_puct * child.p * \ math.sqrt(self.n) / (1 + child.n) if score > best_score: best_score, best_action, best_node = score, action, child return best_action, best_node def backup(self, value): self.w += value self.n += 1 self.q = self.w / self.n

逻辑说明:select_child()遍历当前节点的所有子节点,用PUCT分数选择待探索的分支。child.q是平均价值,math.sqrt(self.n) / (1 + child.n)是访问频率项:父节点访问次数大而子节点访问次数小时,该值会放大探索倾向。backup(value)在递归返回后执行一个关键操作:累计价值写入w,访问次数n加一,再重新计算q

这段代码里必须注意value的视角切换。AlphaZero通常约定:节点保存的价值始终是“当前轮到该节点行动玩家”的胜率。因此在递归回溯时,如果本层选了子节点,返回的value要取反,才能表示父节点视角的价值。很多复现版本棋力不稳,问题就出在这个取反被漏掉。reGomoku的mcts_alphaZero.py对这个细节做了处理,但自己改代码时很容易弄丢。

mcts_pure.py和这段逻辑的区别在于:纯MCTS没有child.p先验,PUCT退化为UCB1,且评估阶段执行随机落子到终局。两个版本用同一个game.py对弈,分别跑200次模拟,就能直观看到网络引导带来的棋力差距。

3. 代码结构逐个拆解:从 game.py 到 policy_value_net

reGomoku的项目布局一眼识别出AlphaZero-Gomoku血统。.idea目录和iml文件之外,真正参与运行的只有不到十个Python文件。先把文件间的关系理清,后续改训练、调参才不容易迷路。

3.1 文件分工一览

文件职责关键接口
game.py棋盘规则、落子、胜负判断Game,Board
mcts_pure.py纯MCTS,无神经网络MCTS,MCTSPlayer
mcts_alphaZero.pyMCTS + 策略价值网络MCTSPlayer
policy_value_net.py神经网络定义与训练接口PolicyValueNet
policy_value_net_numpy.py基于NumPy的极简版本PolicyValueNet
policy_value_net_keras.pyKeras实现PolicyValueNet
policy_value_net_pytorch.pyPyTorch实现PolicyValueNet
policy_value_net_tensorflow.pyTensorFlow实现PolicyValueNet
train.py自对弈训练循环train()
human_play.py人机对战入口命令行交互

从文件名能看出作者想演示神经网络框架的可替换性。同一个PolicyValueNet接口,在Keras、PyTorch、TensorFlow、NumPy下分别实现,对外暴露的方法统一是policy_value_fn(state)train_step(state_batch, mcts_probs, winner_batch)。这样mcts_alphaZero.py不依赖具体框架,只要传入一个符合接口的网络对象即可。

3.2 棋盘与状态表示

game.py把棋盘建模为width * height的整数矩阵,0表示空位,1表示黑棋,2表示白棋。当前玩家是谁由current_player字段维护。get_all_available_moves()返回所有值为0的位置索引,而神经网络期望的输入是(1, height, width, 4)的张量。常见做法是叠加4个特征平面:当前玩家的棋子位置、对手的棋子位置、当前玩家最近一步、对手最近一步。6x6棋盘就是一个6x6x4的浮点矩阵。

# game.py 中状态到特征的常见转换 def get_state(self): features = np.zeros((self.height, self.width, 4), dtype=np.float32) # 当前玩家棋子 features[:, :, 0] = (self.board == self.current_player) # 对手棋子 features[:, :, 1] = (self.board == 3 - self.current_player) return features

逻辑说明:3 - self.current_player在玩家1和2之间切换,因为两者相加为3。布尔矩阵转为float后保留空间分布。这里最常见的误用是直接把原始盘面丢进去,不显式区分“当前玩家”和“对手”,导致网络无法感知要下的是黑还是白。特征平面的顺序必须固定,PyTorch和Keras预训练模型的通道顺序不一致时,加载后要检查是否需要permutetranspose

3.3 策略价值网络到底输出什么

policy_value_net.py定义的网络结构通常是几层卷积加两个head:一个head用softmax输出所有格子的落子概率,另一个head用tanh输出当前玩家的胜率。训练好的best_policy_8_8_5.model2就是这两个head的权重集合。

def policy_value_fn(self, board): legal_positions = board.availables # 前向传播得到原始 logits 和价值 act_probs, value = self.policy_value_net(board.get_state()) # 构建合法位置掩码,非法位置概率置零 legal_mask = np.zeros_like(act_probs, dtype=bool) legal_mask[legal_positions] = True masked_probs = act_probs * legal_mask masked_probs = masked_probs / np.sum(masked_probs) return masked_probs, value

这里的关键是掩码操作。模型会对所有位置输出概率,但已经被占用的格子不能作为落子候选,必须把那些位置的概率强制置零再归一化。否则MCTS选择子节点时,可能建议AI走一个已被占用的位置,导致搜索直接崩溃。reGomoku的MCTSPlayer在调用policy_value_fn后,会把返回的概率向量直接作为PUCT里的P(s,a),所以保证概率和为1是搜索稳定的前提。

3.4 人机对战入口:human_play.py

human_play.py的启动方式很简单:

python human_play.py --model best_policy_8_8_5.model2

脚本内部会创建棋盘、加载模型、构造MCTSPlayer,然后进入命令行循环。常见流程是玩家输入坐标,比如5, 5,代码解析后落子,再让AI搜索落子,打印棋盘。如果是6x6棋盘,需要把model参数换成best_policy_6_6_4.model。这个文件同时也是调试模型加载路径的好起点:如果模型文件路径不对,程序会直接报错提示找不到权重,而不是静默启动一个随机网络。

# human_play.py 中加载模型并构造AI的常见写法 net = PolicyValueNet(width=8, height=8, model_file='best_policy_8_8_5.model2') player = MCTSPlayer(net, simulate_steps=400)

逻辑说明:PolicyValueNet的构造函数根据棋盘宽高初始化网络结构,再从model_file读取权重。MCTSPlayer传入网络和模拟次数simulate_steps,之后每次调用get_action(state)都会执行若干次MCTS模拟再返回落点。simulate_steps是调节AI棋力和耗时的关键参数,400步在CPU上大概需要一秒,改成100步会明显变弱但响应更快。

4. 训练流程与模型参数调优:从自对弈到策略提升

train.py是整个项目的发动机。AlphaZero的核心迭代是“自我对弈收集数据 → 训练网络 → 新网络评估 → 更新模型”,reGomoku把这条闭环压缩到了单台笔记本能跑完的规模。默认配置在6x6棋盘上效果明显,8x8则要更久。下面重点看训练数据格式和损失函数。

4.1 自对弈数据是怎样产生的

每局self-play中,两个MCTSPlayer使用同一个网络参数对弈。每一回合,当前玩家在MCTS搜索结束后,根据子节点访问次数N(s,a)生成策略输出。这里有一个温度参数temp控制随机性:温度高时概率分布更均匀,鼓励探索;温度低时倾向于最大访问次数的落子。

# mcts_alphaZero.py 中生成 MCTS 策略 def get_action_probs(self, state, temp=1e-3): for _ in range(self.n_playout): state, _ = self.mcts.playout(state) # 一次完整模拟 act_visits = [(act, node.n) for act, node in self.mcts.root.children.items()] if temp == 0: # 贪心选择最大访问次数 best_act = max(act_visits, key=lambda x: x[1])[0] probs = np.zeros(len(act_visits)) probs[best_act] = 1.0 else: # 温度参数软化后归一化 visits = np.array([v for _, v in act_visits]) ** (1 / temp) probs = visits / visits.sum() return probs

这段代码里,temp在训练中通常设为1.0以上,让访问次数分布比较平滑,增加数据多样性;评估对局时设为0,直接选最优手。n_playout是每次决策的MCTS模拟次数,它决定搜索深度,也直接影响训练时长。如果把temp固定为常数,会出现两个问题:训练前期太贪心导致探索不足,或训练后期太随机导致策略不收敛。所以要把temp放在参数里随训练进度调整。

4.2 训练循环与损失函数

训练数据的每一条由三部分组成:当前局面state,MCTS概率分布mcts_probs,以及本局结束时的胜者视角值winner。注意winner需要转换为“当前玩家视角”:如果执黑赢了,黑棋是当前玩家,value为1;白棋视角则取反为-1。Keras版本的损失定义通常类似:

# policy_value_net_keras.py 中部分编译代码 model.compile( loss=['categorical_crossentropy', 'mean_squared_error'], optimizer=keras.optimizers.Adam(learning_rate=0.001) )

策略头用多分类交叉熵,价值头用MSE。训练时可以打印两个loss,策略loss通常前几百步下降很快,价值loss则随着自对弈质量提高而缓慢下降。如果发现价值loss一直不降,先检查winner符号是否按当前玩家视角做了翻转,再检查数据增强是否引入了不一致的标签。

4.3 一个简化的train.py主循环

完整训练循环比注释看起来复杂,因为要处理经验池淘汰、定期保存模型、打印对局结果等。核心流程可以简化如下:

# train.py 核心自对弈训练循环(简化版) for epoch in range(epochs): player = MCTSPlayer(policy_value_net, c_puct=5, n_playout=400) data = run_self_play(player, temp=1.0) # 自对弈一局 buffer.extend(data) if len(buffer) > buffer_size: buffer = buffer[-buffer_size:] # 只保留最近数据 mini_batch = random.sample(buffer, batch_size) policy_value_net.train_step(mini_batch, learning_rate=0.001) if epoch % check_freq == 0: policy_value_net.save_model('best_policy_%d_%d.model' % (width, height))

这种写法把每个epoch都执行一局自对弈和一次训练,优点是代码短、容易跑通;缺点是数据相关性高,容易过拟合到最近几局。改进方式是先积累若干局数据再统一训练,或者跨局洗牌采样。reGomoku中buffer_size默认不会特别大,所以调整时要看显存/内存是否能承受。

参数推荐值说明
c_puct5.0探索常数,越大越偏向先验概率
n_playout400MCTS每次决策的模拟次数
buffer_size10000经验池容量,过大会拖慢训练
batch_size512每次update使用的样本数
learning_rate0.001过高会导致损失震荡,过低收敛慢
temp1.0→0.1训练前期高,后期降低

训练时如果发现模型总在同样的局部策略上打转,优先减小buffer_size,让新数据更快影响网络;或者调高n_playout增强搜索强度,产生更强的新策略。best_policy_8_8_5.model2这个文件名里的“8_8_5”对应8x8棋盘、5个通道,和6x6模型不通用,加载尺寸不匹配会直接报维度错误。

5. 验证模型棋力与实战调参技巧

拿到压缩包后,先跑一局再谈调参,顺序很重要。用命令启动人机对战:

python human_play.py --model best_policy_8_8_5.model2

如果不带--modelhuman_play.py一般会退回使用纯MCTS,正好可以做参照。先和纯MCTS下一局,再换成best_policy模型,会立刻发现风格差异:纯MCTS在开局时经常下出互不关联的散点,而网络引导的MCTS会有意识抢占角部附近的要点,因为策略网络已经学到了结构化的先验。

量化验证棋力时,可以写一个快速评估脚本,固定双方各跑一定模拟次数,统计胜率:

python eval_models.py --black best_policy_6_6_4.model --white best_policy_8_8_5.model2 --games 20

这个脚本不在压缩包里,但能直接通过导入MCTSPlayerGame拼装出来。测试时要注意黑棋的先手优势,每局结束后交换黑白,否则胜率统计会被先手偏移污染。建议每轮至少跑20局,五子棋偶然性虽然不如围棋大,但单局方差依然可观。

真正有效的调参技巧有三个。第一,temp不是越小越好。降低温度会让AI只走当前搜索中的最优落子,但如果搜索深度不够,这种“贪心”会陷入局部陷阱。模拟次数低于200时,建议temp保持0.5以上,避免过早收敛到坏策略。

第二,数据增强。训练时可以对局面做水平、垂直、对角线翻转,把每步棋扩成8个样本,但特征平面的旋转必须和落子位置索引同步。很多复现者训练很久不涨,就是因为翻转后没有重新映射落子坐标,网络学到了错误的对应关系。

第三,观察PUCT分数。你可以在select_child里临时打印得分最高的几个子节点,看看网络给出的先验概率是否和局面直觉一致。如果最高分的位置经常不是正常的攻防要点,说明策略网络没收敛,继续训练前先检查特征输入和损失函数是否匹配。

如果想让AI在正式对局中稳定发挥,把n_playout提高到800,temp设到0即可。这个组合会让每一次落子都经过更深的搜索,同时排除随机性干扰,是验证最强棋力的最直接方式。

本文还有配套的精品资源,点击获取

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

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

立即咨询