资讯动态

策略价值网络与MCTS改进:黑白棋AI的AlphaZero实践

发布时间:2026/9/15 20:06:42 来源:尧图企业网站定制
简介基于神经网络与强化学习结合的黑白棋AI系统改进方案完整覆盖蒙特卡洛树搜索优化、策略价值网络建模、自我对抗训练样本生成等核心环节并融入迪利克雷噪声增强、棋盘状态镜像翻转、UCB公式先验概率优化等具体改进策略适合有一定深度学习基础、希望动手实现棋类对战AI的开发者与研究者。资源包共15个文件以Python源码为主包含策略网络模型文件、训练与对弈脚本、说明文档及许可证压缩包整体仅853KB结构清晰便于直接阅读与复现。目前已有94人学习下载。除核心算法实现外资源还提供了从模型训练到实战博弈的完整代码链路与配套说明可帮助读者理解各类改进策略的落地方式并在此基础上扩展属于自己的黑白棋AI方案。1. 黑白棋AI的新打法从暴力搜索转向策略价值网络这个压缩包里的东西不是又一个用minimax加alpha-beta剪枝写出来的黑白棋程序。它走的是AlphaZero那条路用一个深度卷积网络同时输出走子概率和局面胜率再靠MCTS把这些概率组织成一次决策。解压之后能看到game.py、mcts.py、mcts_plus.py、policy_value_net.py、selfplay.py、train.py这一整套文件README里说明是按“策略价值网络 自我对抗训练 改进UCB”三条线做的还附带了一个current_policy.model和一个best_policy.model也就是说网络权重已经训练过一轮不是纯空架子。读这份代码的姿势应该是先顺着selfplay.py看数据怎么生产再钻进mcts_plus.py和policy_value_net.py看MCTS和网络怎么接最后看train.py理解模型怎么迭代。适合已经写过基础棋类AI想看看神经网络怎么替代评估函数的人。2. 策略价值网络输入、输出与网络结构设计2.1 棋盘状态如何编码成网络输入奥赛罗棋盘是8x8policy_value_net.py里定义了一个ReversiNet类输入形状是(4, 8, 8)。这4个通道把黑白棋的完整局面拆成了几个视角当前玩家的棋子、对手的棋子、空位、以及当前玩家可落子的合法位置掩码。前三个通道描述局面本身第四个通道把“合法动作”这个关键信息直接送给卷积层让网络在算策略头输出的同时天然能把非法位置的概率压低。# policy_value_net.py 中 board 到 tensor 的转换示意 def board_to_tensor(board, current_player): # 4 个 8x8 平面 planes np.zeros((4, 8, 8), dtypenp.float32) for r in range(8): for c in range(8): piece board[r][c] # 0 空, 1 黑, 2 白 if piece 0: planes[2][r][c] 1.0 # 空位通道 elif piece current_player: planes[0][r][c] 1.0 # 当前玩家棋子 else: planes[1][r][c] 1.0 # 对手棋子 # 第四个通道:合法着法掩码 for move in board.get_legal_moves(current_player): planes[3][move[0]][move[1]] 1.0 return planes这段代码在送入网络之前完成两件事一是把黑白棋的棋子颜色统一到“当前玩家视角”这样网络不需要知道自己是黑还是白只需学“我的子在哪、对方的子在哪”减少了一半的重复局面二是把合法动作作为显式特征喂进去这比在输出层靠mask掉非法动作更早地约束了模型。通道顺序是固定的训练和推理必须保持一致否则模型会学到错误的相关性。这里的4通道设计是一个很关键的工程决策。很多初次接触AlphaZero做法的同学会直接用(2, 8, 8)只装双方棋子把合法性完全交给MCTS去过滤。但奥赛罗转换性很强某一步落错位置会直接造成翻转大量棋子如果MCTS在大量非法动作上浪费探索次数搜索效率会明显下降。显式放入合法掩码等于帮MCTS圈定了探索范围。2.2 双头输出策略头和价值头如何共用卷积层ReversiNet的主体是4层卷积加BatchNorm和ReLU卷积层的通道数分别是32、64、128、256kernel size统一用3x3padding保持为1所以特征图一直是8x8下不来。棋盘只有64个格子信息密度比围棋361个点低很多但黑白棋的翻转规则导致一个落子会连锁影响多条对角线所以感受野需要覆盖全盘4层3x3卷积叠加后的感受野已经足够覆盖整个8x8区域了。# policy_value_net.py 中双头网络结构核心代码 class PolicyValueNet(nn.Module): def __init__(self, board_width8, board_height8): super().__init__() self.board_width board_width self.board_height board_height # 公共卷积层 self.conv1 nn.Conv2d(4, 32, kernel_size3, padding1) self.conv2 nn.Conv2d(32, 64, kernel_size3, padding1) self.conv3 nn.Conv2d(64, 128, kernel_size3, padding1) self.conv4 nn.Conv2d(128, 256, kernel_size3, padding1) self.bn1 nn.BatchNorm2d(32) self.bn2 nn.BatchNorm2d(64) self.bn3 nn.BatchNorm2d(128) self.bn4 nn.BatchNorm2d(256) # 策略头 self.policy_conv nn.Conv2d(256, 4, kernel_size1) self.policy_bn nn.BatchNorm2d(4) self.policy_fc nn.Linear(4 * 8 * 8, 64) # 价值头 self.value_conv nn.Conv2d(256, 2, kernel_size1) self.value_bn nn.BatchNorm2d(2) self.value_fc1 nn.Linear(2 * 8 * 8, 64) self.value_fc2 nn.Linear(64, 1)策略头经过1x1卷积降维到4个通道展平后经过一个全连接层输出64维也就是8x8棋盘64个位置的动作概率。价值头则是把特征图压到2个通道展平后接两层全连接最后用tanh把输出压到-1到1之间代表当前玩家的胜率估计。两条头共用前面4层卷积的特征提取能力这样的设计让网络在对局中同时完成“看到局势”和“想出动作”两件事在训练时策略梯度信息也会反向传播到共享卷积层帮助价值头学到更扎实的局面理解。在训练时这个双头结构会输出一个64维的策略分布和一个标量价值。策略分布的交叉熵损失让网络学会模仿MCTS探索后的动作分布价值头的均方误差损失让网络学会预测真实对局的胜负。两个损失相加再加上L2正则项控制权重衰减。2.3 为什么不用全连接网络或纯CNN如果只用全连接网络输入层就需要展开成256个节点然后接若干全连接层参数量非常大而且无法利用棋盘上“相邻位置的棋子存在空间相关性”这个结构先验。奥赛罗棋盘中角、边和中心区域的价值完全不同角落是全局争夺的焦点相邻格子的棋子配置对翻转方向有直接影响这种局部模式恰恰是卷积网络最擅长捕捉的。全连接网络需要从数据里硬学出这种位置关系样本需求会成倍增加。相比之下卷积核在整张棋盘上滑动同一组参数对不同位置做相同操作天然具备平移等变性。虽然奥赛罗不像围棋那样完全旋转对称但棋盘经过镜像和旋转后对弈规律是一致的这种对称性会在后面用镜像翻转扩展样本时充分发挥作用。压缩包里把网络设计成卷积双头结构就是奔着“吃少量样本、学到可泛化的棋形特征”这个目标去的。3. MCTS改进策略UCB公式先验概率优化与搜索流程3.1 从标准MCTS到加入策略价值引导的p-MCTS标准蒙特卡洛树搜索在模拟阶段随机走子到终局然后回传胜负结果这个过程中完全没有利用人类知识或领域特征纯靠大量随机模拟把胜率估准。但黑白棋的分支因子不小中盘阶段合法着法往往有20个左右随机走子到终局需要模拟几百次才能让一个根节点的胜率估计收敛。策略价值网络介入后模拟阶段的“随机走子”被替换成“用策略网络的概率分布指导选择”这样搜索深度大大减少同时每步的选择也更接近人类高手的直觉。mcts.py里的MCTS节点数据结构是标准的树节点实现每个节点保存着当前节点的访问次数、总价值、先验概率以及指向子节点的字典。节点的扩展是在搜索过程中动态进行的而不是一次性展开所有子节点这样可以节省内存并且把有限的搜索次数聚焦到那些看起来最有希望的着法上。# mcts.py 中的节点数据结构和选择逻辑 class TreeNode: def __init__(self, parent, prior_prob): self.parent parent self.children {} # 动作 - TreeNode self.n_visits 0 # 访问次数 self.total_value 0.0 # 累计价值 self.prior_prob prior_prob # 先验概率 def select(self, c_puct): # 遍历所有子节点用 UCB 公式选出价值最高的分支 best_score -float(inf) best_action None best_child None for action, child in self.children.items(): # 防止除以 0 u c_puct * child.prior_prob * math.sqrt(self.n_visits) / (1 child.n_visits) q child.total_value / (1 child.n_visits) score q u if score best_score: best_score score best_action action best_child child return best_action, best_child选择逻辑就是标准的pUCT公式Q(s,a) c_puct * P(s,a) * sqrt(N(s)) / (1 N(s,a))。其中Q是节点平均价值P是策略网络给出的先验概率N(s)是当前节点的访问次数N(s,a)是该子节点的访问次数。这里有一个代码级别的细节total_value除以的是(1 child.n_visits)而不是child.n_visits这样即使子节点一次都没被访问过Q值也不是无穷大而是0同时先验概率项占主导作用引导搜索先去试试网络认为最好的几个着法。3.2 UCB先验概率优化的具体做法mcts_plus.py是这次改进的重点文件。标准MCTS中先验概率在网络输出后就直接被当成常数使用。但这个项目在mcts_plus.py中针对UCB公式的先验概率项做了两处关键修正。第一处是引入“概率温度”操作当根节点基于策略网络得到64维输出时不是直接使用原始softmax概率而是经过一个温度系数压缩或放大概率分布的尖锐程度。温度大于1时概率分布变得更平滑鼓励探索温度小于1时分布更尖锐更偏向利用高概率的着法。# mcts_plus.py 中先验概率温度化处理 def apply_temperature(priors, temperature): # priors: 网络输出的 64 维概率向量 # temperature: 温度参数一般取 1.0 ~ 3.0 if temperature 1.0: return priors # 取对数后用温度缩放再重新 softmax log_priors np.log(priors 1e-10) scaled_log_priors log_priors / temperature # 减去最大值防止数值溢出 scaled_log_priors - np.max(scaled_log_priors) exp_priors np.exp(scaled_log_priors) return exp_priors / np.sum(exp_priors)温度参数控制的不仅是随机性它对搜索深度和广度的平衡有直接影响。温度较高时小概率着法被放大MCTS初期会尝试更多备选点温度较低时高概率着法的优势更突出搜索树会更早地深入最有希望的主线分支。这个项目在自我对弈阶段和正式对弈阶段采用不同的温度设置自我对弈时温度偏高以产生多样化的训练数据正式对弈时温度设为1甚至更低让模型发挥最强水平。第二个优化点是在UCB公式的先验概率项中引入了“legal mask归一化”。策略网络输出的是64维概率其中非法动作的概率理论上应该被掩码屏蔽但网络最后一层的softmax仍然给这些非法位置分配了很小的非零概率值。若直接使用这些概率MCTS在选择时可能会把搜索资源浪费在非法节点上。# mcts_plus.py 中合法动作掩码归一化 def mask_and_normalize_priors(priors, legal_moves): # 先复制一份原始概率 masked_priors np.zeros_like(priors) # 仅保留合法着法的概率 for move in legal_moves: idx move[0] * 8 move[1] masked_priors[idx] priors[idx] # 重新归一化让合法着法概率之和为 1 total np.sum(masked_priors) if total 0: masked_priors / total # 如果网络输出极端导致总和为0退化为均匀分布 else: masked_priors np.ones_like(priors) / len(legal_moves) return masked_priors这段代码在根节点使用核心作用是让每个合法着法获得一个合理的被探索概率同时彻底避免非法动作的干扰。很多实现会把这个mask逻辑放到损失函数里做但这个项目放在搜索端处理位置不同背后的动机也不一样。放在搜索端的好处是即使网络在训练时没有完全学会抑制非法位置推理时MCTS仍然能保证搜索不出非法动作。坏处是如果网络对合法位置的先验分布离谱归一化只会放大错误所以需要在训练时让网络输出尽量合理。3.3 搜索后的动作选择温度与访问次数的共同作用MCTS完成一次搜索后树的根节点积累了若干条访问计数。动作选择有两种常见策略贪婪选择访问次数最多的子节点按访问次数分布采样。这个项目在mcts_plus.py中实现了按访问次数的指数形式采样通过温度参数控制采样的集中度。# mcts_plus.py 中基于访问次数的最终动作采样 def select_action_by_visits(root, temperature): visit_counts np.array([child.n_visits for child in root.children.values()]) actions list(root.children.keys()) # 温度趋近于 0 时退化为贪婪选择 if temperature 1e-3: best_idx np.argmax(visit_counts) return actions[best_idx] # 按 visit 的 1/temperature 次方采样 exp_visits visit_counts ** (1.0 / temperature) probs exp_visits / np.sum(exp_visits) chosen_idx np.random.choice(len(actions), pprobs) return actions[chosen_idx]如果temperature取1采样分布与访问次数成正比temperature取0.1或更小时采样基本锁定访问最多的着法。在训练样本生产阶段前面30步使用temperature接近1的高探索模式后面逐步降温到0.1以下模拟人类棋手开局求变、残局求稳的特点。这样做的好处在于训练数据不是千篇一律的“最优着法”而是同一局面下带有多样性的合理着法能有效防止模型过早收敛到某种固定套路。4. 自我对抗训练从样本生成到数据增强4.1 完整对局数据的产生流程selfplay.py扮演“数据工厂”的角色。它的运行流程是加载当前策略价值网络权重让AI同时执黑执白进行一整局对弈每一步都用MCTS搜索决定落子。搜索完成后把当前棋盘状态、MCTS最终选择的动作概率分布、以及当前玩家信息记录下来。对局结束后根据胜负结果给每一步的数据打上奖励标签胜方视角下所有状态标记为1负方视角下所有状态标记为-1或1对应胜利方视角。# selfplay.py 中对局数据生成的核心循环 def selfplay_one_game(policy_value_net, mcts_handler, temperature_schedule): game Board() # 初始化 8x8 黑白棋棋盘 data [] # 存放 (state, mcts_prob, current_player) step 0 while not game.is_game_over(): # 根据当前步数计算温度系数 temp temperature_schedule(step) # 获取当前棋盘状态张量 state board_to_tensor(game.board, game.current_player) # MCTS 搜索返回动作概率分布 action_probs mcts_handler.get_action_probs(state, temp) # 存储训练样本 data.append((state, action_probs, game.current_player)) # 根据概率分布选择实际动作 action np.random.choice(64, paction_probs) game.do_move(action // 8, action % 8) step 1 # 对局结束判定胜负 winner game.get_winner() # 生成标注样本胜利者视角 1失败者视角 -1 labeled_data [] for state, probs, player in data: if winner player: label 1.0 elif winner 0: # 平局 label 0.0 else: label -1.0 labeled_data.append((state, probs, label)) return labeled_data这里有个需要注意的细节胜负标签是以每个样本记录时的current_player为视角的。同一局棋里黑方视角的样本label为1时白方视角的样本label一定是-1因为它们在同一个局面的评估是站在不同玩家的立场上。训练时网络输入包含当前玩家通道所以这种视角绑定是合理的。4.2 迪利克雷噪声注入的作用与参数选择迪利克雷噪声是AlphaZero论文里增强探索的关键机制作用对象是MCTS根节点的先验概率。每局对弈开始时MCTS会先调用策略网络得到根节点的先验概率分布然后把一个迪利克雷分布的随机噪声向量以一定比例混合进去。迪利克雷分布是“分布上的分布”它的输出是一个总和为1的概率向量。# mcts_plus.py 中向根节点先验概率注入 Dirichlet 噪声 def add_dirichlet_noise(priors, alpha0.3, epsilon0.25): # 生成一个 alpha 参数控制的 Dirichlet 分布 noise np.random.dirichlet([alpha] * len(priors)) # 混合原始先验和噪声 noisy_priors (1 - epsilon) * priors epsilon * noise # 重新归一化确保概率和仍为 1 noisy_priors / np.sum(noisy_priors) return noisy_priorsalpha参数控制噪声的“集中度”alpha越小生成的噪声向量越尖锐即随机选中少数几个动作赋予高概率其余动作概率几乎为0alpha越大噪声向量越均匀所有动作的概率相似。在奥赛罗场景下合法动作数量有限alpha取0.3比较合适既能保证探索到一些冷门但可能有力的着法又不至于让噪声完全覆盖网络先验。epsilon控制噪声的混合比例取0.25表示噪声占25%权重网络先验占75%。一开始会以为这个比例偏高但实践证明黑白棋开局阶段不同着法的胜率差异远没有围棋那么大更强的探索更有助于发现新的棋型套路。迪利克雷噪声只加在根节点子节点搜索时不再加噪声这个设计是有讲究的。根节点噪声影响的是整次搜索的分支选择方向如果每个子节点都注入噪声搜索过程会变得极不稳定价值估计方差过大。根节点加噪声既保证了对局多样性又不至于破坏搜索的稳定收敛。4.3 棋盘镜像翻转的样本扩展实现黑白棋棋盘是正方形天然具有8种对称等距变换旋转0度、90度、180度、270度以及沿水平轴、垂直轴、主对角线、副对角线镜像。一般不用这8种中的旋转90度或270度因为这类旋转会把黑白的初始位置关系改变但在棋盘状态和对应动作上直接做坐标映射是完全可以的。因为黑白棋的规则在旋转和镜像下是完全对称的。# train.py 中镜像翻转扩展样本的实现 def augment_symmetry(state, action_probs, label): # state: (4, 8, 8) # action_probs: (64,) augmented [] # 水平翻转: 行坐标不变列坐标 7-c state_h state[:, :, ::-1] probs_h action_probs.reshape(8, 8)[:, ::-1].flatten() augmented.append((state_h, probs_h, label)) # 垂直翻转: 行坐标 7-r列坐标不变 state_v state[:, ::-1, :] probs_v action_probs.reshape(8, 8)[::-1, :].flatten() augmented.append((state_v, probs_v, label)) # 转置主对角线镜像: 交换行列坐标 state_t state.transpose(0, 2, 1) probs_t action_probs.reshape(8, 8).T.flatten() augmented.append((state_t, probs_t, label)) # 再加水平翻转后的转置、垂直翻转后的转置共 8 种 # 这里省略其余组合的实现 return augmented这个函数每次从一条原始样本生成多条对称样本。注意state的四个通道必须同步做同样的坐标变换否则通道之间的对应关系就破坏了。动作概率和胜负标签也要做相应变换。数据增强在深度学习中的作用是让模型从有限数据中学习到对称性在测试时即使遇到一个镜像过的局面模型也能给出合理输出相当于免费扩充了多倍的训练集。另外一个网络结构上的配合点因为输入特意设计了“当前玩家棋子在第0通道”这种相对视角所以翻转样本时不需要考虑黑白颜色互换带来的影响只需要做几何坐标变换。如果当初设计时把黑棋固定在第0通道、白棋固定在第1通道那么翻转时还需要额外交换通道麻烦得多。这个设计是前后呼应的。5. 训练循环与模型迭代train.py 与模型评估机制5.1 训练数据集的组装与batch生成selfplay.py 持续生产数据后train.py 负责把这些数据组装成可训练的数据集。训练样本三元组分别是state、MCTS动作概率分布、胜负标签。训练时首先要做的是把这些Numpy数组转换成一个高效的PyTorch Dataset。# train.py 中数据集类与数据加载 class ReversiDataset(Dataset): def __init__(self, data): # data: list of (state, probs, label) self.states torch.FloatTensor([d[0] for d in data]) self.probs torch.FloatTensor([d[1] for d in data]) self.labels torch.FloatTensor([d[2] for d in data]) def __len__(self): return len(self.labels) def __getitem__(self, idx): return self.states[idx], self.probs[idx], self.labels[idx]由于整个数据可能很大一次性全部读入内存会造成浪费更好的做法是维护一个固定大小的经验池新对局数据不断追加旧数据逐步淘汰。这个项目里在selfplay.py中一次性生成一批数据后直接交给train.py训练属于比较简单的流程。如果要继续优化可以引入replay buffer机制保留最近N局的数据让模型不至于因为遗忘旧经验而在评估时出现剧烈波动。5.2 损失函数设计策略交叉熵加价值MSE训练的核心是优化策略头和价值头的联合损失。策略头的目标是让网络输出的动作概率分布尽量接近MCTS搜索后给出的动作访问分布价值头的目标是让网络输出的胜率尽量接近真实对局结果。# train.py 中训练步骤核心代码 def train_step(model, optimizer, batch_states, batch_probs, batch_labels): model.train() # 前向传播 pred_probs, pred_values model(batch_states) # 策略损失: 交叉熵 policy_loss -torch.mean(torch.sum(batch_probs * torch.log(pred_probs 1e-10), dim1)) # 价值损失: 均方误差 value_loss torch.mean((pred_values.view(-1) - batch_labels) ** 2) # 总损失 策略损失 价值损失 L2 正则 l2_lambda 1e-4 l2_reg sum(p.pow(2).sum() for p in model.parameters()) total_loss policy_loss value_loss l2_lambda * l2_reg # 反向传播与参数更新 optimizer.zero_grad() total_loss.backward() optimizer.step() return total_loss.item()这里值得说道说道的是策略损失。batch_probs来自MCTS的访问分布是每一步搜索完成后根节点各子节点的访问次数占比。这相当于用一个“更聪明的计算”来监督网络MCTS经过若干次模拟后认为哪些着法值得多探索网络就去拟合这个比例而不是简单地学最终选中的那一个动作。这样做的好处是即使某个动作没有被最终选中只要它在MCTS中被探索得较多网络依然能学到它的相对价值概率。5.3 最优模型与当前模型的评估替换机制训练过程中项目维护了current_policy.model和best_policy.model两份权重。每训练一轮后新模型不会直接替代旧模型而是先让新旧模型进行若干局对弈只有当新模型的胜率达到某个阈值通常取55%或60%时才会把当前模型备份为最优模型。这个机制防止了训练过程中的自我强化陷阱——如果新模型只是因为偶然波动表现好就立刻替代旧模型后续训练可能南辕北辙。这个评估机制对应着main.py中human_vs_ai或ai_vs_ai的模式。对弈时新模型和旧模型使用相同的搜索次数这样输赢主要反映网络权重的好坏而不受搜索量差异影响。通常评估8到16局胜率超过阈值就替换否则保留旧模型继续训练。这个方案在实际训练中的稳定性远高于直接每轮替换尤其在训练早期价值头还没收敛时盲目替换会让评估基线来回震荡。5.4 训练超参数的大致设置思路训练黑白棋AI不需要太大的batch64或128已经足够。学习率初始设置为0.01采用Adam优化器每若干轮训练后乘以0.1做衰减。奥赛罗局面数量远少于围棋但自我对弈生产的样本之间存在强相关性一局棋内前后几步的棋盘状态差异很小如果把这些样本直接按顺序训练模型会过拟合到最近几局棋的风格。项目在train.py里对所有样本做随机shuffle这是降低样本间相关性的一个基本手段。6. 验证MCTS改进效果的对照实验与终极参数清单6.1 消融实验如何证明每项改进真的有效拿到这份代码后第一个值得做的实验是用控制变量法验证改进是否有效。启动两台AI进行对弈测试第一台使用原始mcts.py第二台使用mcts_plus.py其他参数保持一致。建议跑200局棋胜率稳定在60%以上才能说明改进有效。如果只是贪心地、快活地看一下赛果几局棋的随机波动很大根本辨别不出改进的作用。另外还可以单独验证Dirichlet噪声的作用关闭噪声用固定策略训练开启噪声重训一版比较两个模型的K-L散度和最终棋力。这里贴一个常见的评估命令行示例# 用主程序让两个 AI 对战评估胜率 python main.py --mode evaluate --ai1 mcts_plus --ai2 mcts_original --games 200注意main.py里需要先确认是否支持这种命令行参数。如果不支持可以手动写一个evaluate.py加载两份模型权重后用相同的MCTS参数对弈统计胜负。6.2 关键参数速查与优先级参数推荐值影响线调参优先级c_puct5.0 ~ 8.0UCB中探索项与利用项的平衡值越大搜索越偏向探索高Dirichlet alpha0.3噪声向量尖锐程度alpha越大噪声越均匀中Dirichlet epsilon0.25噪声混合比例过大则先验被过度破坏中MCTS搜索次数400 ~ 800搜索越深棋力越强但速度越慢训练阶段可降低高温度参数开局1.0残局0.1控制落子多样性与确定性中训练batch size64影响收敛速度与稳定性低学习率0.01 衰减至 0.001影响训练振荡幅度中如果只想快速看到效果优先调MCTS搜索次数和c_puct。前者直接决定棋力上限后者决定搜索效率。Dirichlet噪声在训练阶段作用显著评估阶段则不应加否则会让AI的表现不够稳定。c_puct太大时MCTS会过度探索低概率着法搜索树会在无关分支上浪费次数太小时又过早收敛到网络先验最强的分支。6.3 模型自我对弈水平与训练步数的关系训练一段时间后怎么判断模型到底进步了没有一个直观的方式是把当前模型和自己历史的某个版本进行梯度对弈每训练500局就把当天的模型存档然后让最新模型与3天前的模型各执黑白打100局。如果最新模型胜率稳定超过60%说明自我对弈的迭代有效。如果始终在50%徘徊需要检查数据生产是否有bug、损失函数是否计算正确、MCTS的搜索次数是否太低导致生成的样本质量不足。黑白棋终局判定有一个需要留意的规则细节双方都无法落子时对局结束子多者胜。自我对弈样本里平局虽然少见但存在处理方式是label置0。训练时价值输出的tanh范围是-1到1label的0值对应平局在语义上是自洽的。检查一下selfplay.py里的get_winner逻辑确保它统计的是最终棋盘上的棋子数量差而不是提前结束时的占有状态。如果这里写错所有训练样本都会带错标签模型永远学不会正确的胜负判断。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价