基于深度神经网络与MCTS的Python五子棋博弈引擎实现

发布时间:2026/10/25 10:49:45
基于深度神经网络与MCTS的Python五子棋博弈引擎实现 简介基于Python深度神经网络的五子棋博弈引擎设计资料面向人工智能、通信、自动化、电子信息、物联网等计算机相关专业的在校学生与教师适用于课程设计、毕业设计、项目初期立项演示也可作为博弈算法方向的学习进阶素材。项目经导师指导认可答辩评审分达到九十五分代码已全部测试运行成功。资源包共三十九个文件核心包含十五个源码文件、十三个字节码文件、四个已训练模型、三个部署文档、两个对弈演示动图以及一个训练日志整体大小约2.49MB。源码覆盖阿尔法零系列、蒙特卡洛树搜索、基于PyTorch与NumPy的双版本策略价值网络并附八乘八与十乘十两种棋盘的最佳及当前策略模型Django与Flask两套部署文档和说明文件可帮助快速启动项目训练日志便于观察模型收敛过程。已有134人学习适合复现完整博弈AI项目并在此基础上做二次功能扩展。1. 深度神经网络做的五子棋引擎为什么不靠搜索取胜五子棋棋盘只有15×15可一旦进入博弈树穷举依然是天文数字传统α-β剪枝在没有好评估函数时又慢又弱。标题里的方案——基于Python深度神经网络的五子棋博弈引擎——把“棋感”交给CNN网络看过大量自我对弈局面后学会给出落子概率和胜率估计MCTS在小范围内做推演两者配合让一台普通笔记本也能在几百次搜索内走出有章法的棋。它适合两类人做深度学习课程设计或毕业设计、需要现场演示项目的学生想低成本复现AlphaZero思路却不想碰C的棋类AI爱好者。下面按架构、核心实现、训练、避坑和部署一条线讲透。2. 引擎架构与网络选型策略-价值双头网络为什么是五子棋的主流答案2.1 把15×15棋盘编码成神经网络输入通道设计与特征提取神经网络只认数字张量棋盘上一颗黑子、一颗白子怎么变成输入最直接的做法是给每类棋子开一个通道channel有棋子的位置填1、空白填0得到一个15×15×2的张量。真实引擎里我一般会再加两个信息位当前轮到谁、最后一手落在哪。通道数从2加到4信息量会明显改善——网络能分辨“优势局面”与“刚被反打”的区别。import numpy as np def encode_board(board, current_player, last_moveNone): # board: 15x15 的 numpy 数组0 空 / 1 黑 / -1 白 # current_player: 1 或 -1表示当前准备落子的一方 # last_move: (row, col) 或 None记录刚落下的一手 ch np.zeros((4, 15, 15), dtypenp.float32) # 通道0当前玩家的棋子 ch[0][board current_player] 1.0 # 通道1对手的棋子 ch[1][board -current_player] 1.0 # 通道2最后一手位置辅助网络理解哪颗子刚落下 if last_move is not None: ch[2][last_move[0], last_move[1]] 1.0 # 通道3轮次信息整层填 1告诉网络当前该谁走 ch[3].fill(1.0) return ch # 形状 (4, 15, 15)这段代码的关键是通道0和通道1永远以“当前玩家”为基准来填而不是固定把黑子填通道0。原因很实际网络学到的黑白规则需要可迁移先手后手交换后特征表示必须跟着换。如果你把黑子固定填通道0模型每次自我对弈时会看到“黑子总是通道0、白子总是通道1”可轮到白方走时这个语义就错位了棋力会明显下降。另一个容易忽略的点是使用float32而不是int型。PyTorch的卷积输入要求浮点张量int数组进去会直接报类型错误float32还能让后续BatchNorm的统计更稳定。想省内存可以转成 uint8 在训练时再转但五子棋15×15的尺寸实在太小没必要动这个脑筋。如果你还要叠加历史局面常见做法是把上面4个通道按最近的步数向后堆叠比如保留最近两步棋盘、两个last_move、一个轮次通道变成(7, 15, 15)。没有历史局面时last_move通道只做最后一手就够用我实际测试中第2个历史通道带来的棋力提升很有限。2.2 双头CNN结构设计卷积宽度、参数量与算力权衡AlphaGo系列把双头网络带成了主流共享一个CNN主干提取特征然后分两个头一个输出落子概率长度225一个输出局面价值一个标量。五子棋棋盘比围棋小网络可以做得非常轻。用PyTorch写一个能直接跑的版本import torch.nn as nn class DualNet(nn.Module): def __init__(self, channels32): super().__init__() # 共享卷积主干3层3x3卷积通道数先升后降 self.conv nn.Sequential( nn.Conv2d(4, channels, 3, padding1), nn.BatchNorm2d(channels), nn.ReLU(), nn.Conv2d(channels, channels * 2, 3, padding1), nn.BatchNorm2d(channels * 2), nn.ReLU(), nn.Conv2d(channels * 2, channels, 3, padding1), nn.BatchNorm2d(channels), nn.ReLU(), ) # 策略头1x1卷积降维后展平输出225个落子 logit self.policy_head nn.Sequential( nn.Conv2d(channels, 16, 1), nn.BatchNorm2d(16), nn.ReLU(), nn.Flatten(), nn.Linear(16 * 15 * 15, 225), nn.LogSoftmax(dim1), # 配合交叉熵数值更稳定 ) # 价值头输出范围限制在 [-1, 1] self.value_head nn.Sequential( nn.Conv2d(channels, 8, 1), nn.BatchNorm2d(8), nn.ReLU(), nn.Flatten(), nn.Linear(8 * 15 * 15, 64), nn.ReLU(), nn.Linear(64, 1), nn.Tanh(), ) def forward(self, x): feature self.conv(x) return self.policy_head(feature), self.value_head(feature)这个模型参数量在30万上下CPU推理单次前向只要几毫秒GPU上更快。通道数不必贪大五子棋信息密度比围棋低很多32通道足够把通道数翻到64不见得提升棋力训练时间却翻倍。策略头最后接LogSoftmax而非Softmax因为后面损失函数用交叉熵PyTorch的交叉熵要求log概率输入省一次指数运算还更稳。价值头用Tanh把输出压到[-1, 1]1代表当前玩家胜-1代表当前玩家负自我对弈的最终胜负0/1经过这个映射正好对齐。1×1卷积先降维再接全连接比直接把feature展平接Linear少很多参数也方便以后调通道数。主干网络里要不要加深我的结论是五子棋不需要太深。3层卷积已经能覆盖半径3以内的棋形而活三、冲四这类关键棋型半径也就2到3格。网络再深边际收益会被训练时间成本抵消。真正决定棋力上限的不是网络深度而是MCTS的模拟量和自我对弈数据量。2.3 MCTS神经网络的分工先验概率决定探路方向双头网络单独不能直接下棋它给出的概率分布缺少“多步推演”能力。于是MCTS登场它维护一棵搜索树树里每个节点记录五项数据——访问次数N、累计价值W、平均价值Q、先验概率P、子节点列表。模拟时按PUCT公式选子score Q c_puct * P * sqrt(parent_N) / (1 child_N)网络提供的P负责“哪些位置值得优先看”Q负责“已经看过的位置胜率如何”。五子棋杀棋节奏快一个漏判的冲四可能在几步内直接反杀。网络再强也有盲区但只要模拟次数上去叶子节点的价值头能帮MCTS把这类短杀手“捞回来”。反过来只靠网络贪心地选概率最大点常常在局部对冲中原地打转因为全局归一化的概率会把局部的紧迫性稀释掉。方案搜索效率盲区实现成本5秒内的实战效果纯DFS/α-β剪枝低评估函数难写评估不准时越搜越偏高能稳定处理局部杀棋全局棋型判断差纯策略网络贪心高局部紧迫性被概率归一化淹没低常走出“训练集里没见过”的怪棋MCTS双头网络中受模拟量和网络先验双重限制中棋型完整攻防转换自然MCTS真正消耗的性能在节点展开和网络推理。五子棋每手合法落点最多225个树宽可控用400次模拟在CPU上也就一两秒。后面第3.3节会给完整的PUCT实现和参数配置。这里先记住一个原则模拟次数是把网络“想法”落实成“棋力”的放大器网络先验差时靠搜索兜底网络先验好时搜索也不用太多。3. 从零实现五子棋引擎三个核心模块与必调参数3.1 棋盘特征提取与胜负判定两个能直接运行的Python函数编码函数在第2.1节已经给出这一节再补一个最关键的函数五子连珠判定。五子棋博弈最怕逻辑错误判胜判错会污染整个自我对弈数据导致训练样本全是错标签。判定其实很简单每次落子后从落点沿四个方向横、竖、两条对角线数同色棋子任何方向连续5颗即胜。def check_win(board, row, col, player): # board: 15x150 空 / 1 黑 / -1 白 # row, col: 刚落下的棋子坐标 # player: 1 或 -1当前落子方 directions [(0, 1), (1, 0), (1, 1), (1, -1)] board_size 15 for dr, dc in directions: count 1 # 当前落子本身算一颗 # 正方向数 r, c row dr, col dc while 0 r board_size and 0 c board_size and board[r][c] player: count 1 r dr c dc # 反方向数 r, c row - dr, col - dc while 0 r board_size and 0 c board_size and board[r][c] player: count 1 r - dr c - dc if count 5: return True return False逻辑就是双向延伸数棋型四方向任一方向凑满5颗即返回胜。这里不需要把整个棋盘全盘扫描每手只检查落点附近四条线最坏也就80次比较。还有个隐藏知识点五子棋有“长连”概念某些规则下超过5颗算赢或算禁手。上面这个函数只判定“存在至少5颗连珠”在自由规则下够用如果你要支持禁手规则还需单独判断黑棋双三、双四、长连禁手代码会翻一倍但博弈引擎的主干逻辑不用动。另一个性能细节board别用Python的list套list逐格访问int比numpy数组慢很多。用numpy数组后还能直接做board player产生布尔掩码配合np.where生成合法落点列表。合法落点过滤也很重要直接把网络输出的225个位置全部当合法候选会浪费MCTS模拟也容易把子下到已有棋的位置上。常见做法是把空位索引收集成数组PUCT选子时只在这些合法索引里比较。3.2 策略-价值网络的完整定义一份能直接复制的PyTorch模型第2.2节的DualNet可以直接用但我在实际跑迭代时会加一点小改进把第三层普通卷积换成残差块。残差连接能缓解网络加深后的梯度衰退让前几轮迭代的棋力增长更平滑。完整版本import torch.nn as nn class ResBlock(nn.Module): def __init__(self, channels): super().__init__() self.conv1 nn.Conv2d(channels, channels, 3, padding1) self.bn1 nn.BatchNorm2d(channels) self.conv2 nn.Conv2d(channels, channels, 3, padding1) self.bn2 nn.BatchNorm2d(channels) def forward(self, x): out torch.relu(self.bn1(self.conv1(x))) out self.bn2(self.conv2(out)) return torch.relu(out x) # 残差相加 class EngineNet(nn.Module): def __init__(self, channels32, num_res3): super().__init__() self.stem nn.Sequential( nn.Conv2d(4, channels, 3, padding1), nn.BatchNorm2d(channels), nn.ReLU(), ) self.blocks nn.Sequential(*[ResBlock(channels) for _ in range(num_res)]) self.policy_head nn.Sequential( nn.Conv2d(channels, 16, 1), nn.BatchNorm2d(16), nn.ReLU(), nn.Flatten(), nn.Linear(16 * 15 * 15, 225), nn.LogSoftmax(dim1), ) self.value_head nn.Sequential( nn.Conv2d(channels, 8, 1), nn.BatchNorm2d(8), nn.ReLU(), nn.Flatten(), nn.Linear(8 * 15 * 15, 64), nn.ReLU(), nn.Linear(64, 1), nn.Tanh(), ) def forward(self, x): f self.blocks(self.stem(x)) return self.policy_head(f), self.value_head(f)推理时注意两点一是加model.eval()并包在torch.no_grad()里否则BatchNorm会根据当前batch重新算统计量导致同样的局面不同结果这是个很隐蔽的坑。二是一次推理多盘局面比循环推理单盘快得多。运行时引擎会经常被MCTS打几百次前向请求最大开销不是计算而是Python调度所以把get_action做成batch接口def predict_batch(self, states): # states: numpy数组形状 (batch, 4, 15, 15) self.model.eval() with torch.no_grad(): x torch.tensor(states, dtypetorch.float32) policy, value self.model(x) return policy.exp().numpy(), value.numpy()在MCTS里攒满一批比如32个再统一推理策略头输出经过exp还原成概率。推理粒度对总体耗时有决定性影响按单盘随机抽样的方式调用400次模拟可能要3秒batch化后同样的模拟量能压到1秒以内。这一点在后面的避坑章还会展开讲。3.3 MCTS节点与PUCT参数模拟次数、温度与c_puct的配置MCTS是引擎里最不能“随手写”的部分。这里给出一个结构清晰的实现节点保存N、W、Q、P和子节点字典import math class Node: def __init__(self, parent, prior): self.parent parent self.prior prior self.N 0 self.W 0.0 self.Q 0.0 self.children {} def select(self, c_puct): # 在现有子节点里按PUCT分数选最大 best_score -float(inf) best_child None parent_N self.N for child in self.children.values(): score child.Q c_puct * child.prior * math.sqrt(parent_N) / (1 child.N) if score best_score: best_score score best_child child return best_child def expand(self, action_priors): # action_priors: [(action, prob), ...]概率来自策略头 for action, prob in action_priors: if action not in self.children: self.children[action] Node(self, prob) def backup(self, value): # 自底向上回传价值当前玩家视角翻转 self.N 1 self.W value self.Q self.W / self.N if self.parent: self.parent.backup(-value)四个字段的含义是N是访问次数W是累计价值Q是平均价值prior是网络给的先验概率。PUCT公式里c_puct是探索系数可取1.5到5.0我发现五子棋场景取4.0比较稳太低会让搜索过早偏向当前最优分支错过潜在的反杀路线。backup里价值翻转是重点一方的胜率对另一方就是负值所以向上传递时取-value节点在搜索树里的层级每往上走一层视角就换一次。漏掉这个负号会让引擎赢了不懂为什么赢输了也学不到教训。完整模拟流程分四步选择select到叶子、扩展叶子非终局则展开子节点、评估用价值头或快速走子得价值、回传backup。五子棋不需要快速走子rollout因为杀棋路径短价值头直接估比随机走到底准得多。模拟次数是引擎最核心的参数参数建议值说明模拟次数 sims400低于200会漏冲四高于800对CPU耗时翻倍c_puct4.0探索与利用平衡棋力弱时调大到5.0多探索温度 temp对局前15手1.0之后0.1测试对局0高温多探索低温倾向最优batch大小32MCTS攒批推理CPU耗时明显下降MCTS结束后落子频率就是策略目标统计每个子节点被访问的次数N做一次温度缩放得到落子概率分布。temp1时直接用N算softmaxtemp0时取N最大的那个位置。自我对弈前15手保持高温是为了让早期局面多样化避免模型永远走前几步被固定套路带偏在人机对战时直接temp0引擎会用最稳的一手。4. 自我对弈与训练流程如何让AI自己当对手提升棋力4.1 自我对弈数据是怎么产生的从对局到训练样本引擎开局时是个随机网络不能靠人类棋谱因为它没有棋谱可学。AlphaZero的做法是自我对弈同一份模型同时执黑执白互相下每步用MCTS加高温采样产生落子下到终局后赢家给整局所有局面打1分输家打-1分。每个局面存成一条样本(state, pi, z)pi是MCTS落子分布策略目标z是终局价值价值目标。def play_self_play_game(engine, sims400, temp_threshold15): board np.zeros((15, 15), dtypenp.int8) current_player 1 # 黑先 last_move None states, policies, players [], [], [] move_count 0 while True: legal_moves get_legal_moves(board) # 返回合法坐标列表 if not legal_moves: break # 棋盘满判和 # 取到 action 索引后把 MCTS 的访问计数转成策略分布 pi action, pi engine.get_action(board, current_player, last_move, simssims, temp1.0 if move_count temp_threshold else 0.1) row, col legal_moves[action] # 记录样本 states.append(encode_board(board, current_player, last_move)) policies.append(pi) players.append(current_player) board[row][col] current_player last_move (row, col) move_count 1 if check_win(board, row, col, current_player): break # 回传终局价值黑胜 1白胜 -1 z 0 if len(states) 15 * 15: z current_player # 当前玩家就是赢家 samples [] for state, pi, player in zip(states, policies, players): # 价值要换成“当时该走的一方”的视角 value z if player current_player else -z samples.append((state, pi, value)) return samples这个函数的关键在于最后一个循环终局胜负判断的是整个对局但每步样本的价值视角不同。黑方某一手领先该样本的z可能是1紧接着白方回应手它的z就必须是-1。样本格式统一成“当前玩家的视角”后训练时价值头和策略头才能对齐。get_action返回的pi是MCTS访问次数的温度归一化结果它比单纯用网络策略头输出更接近真实棋力——这也是为什么说“数据由搜索产生、不含硬编码棋型”。数据产生后要落盘不能只在内存里自嗨。常见做法是每个迭代周期生成几百局按npz格式存到一个data/目录训练时随机抽样多个历史块一起训。这么做的好处是样本多样性更大避免只学最新模型的近亲繁殖。4.2 训练循环与Loss设计策略交叉熵和价值MSE的权重数据攒够一池子后进入训练。损失函数是策略交叉熵和价值均方误差的加权和不用加花活import torch import torch.nn.functional as F def train_one_epoch(model, optimizer, dataset, batch_size256): model.train() total_loss 0.0 for state, pi, z in dataset.batches(batch_size): state_t torch.tensor(state, dtypetorch.float32) pi_t torch.tensor(pi, dtypetorch.float32) z_t torch.tensor(z, dtypetorch.float32).view(-1, 1) optimizer.zero_grad() policy_pred, value_pred model(state_t) # 策略损失交叉熵pi是MCTS产生的概率分布 policy_loss -torch.sum(pi_t * policy_pred, dim1).mean() # 价值损失MSE value_loss F.mse_loss(value_pred, z_t) loss policy_loss value_loss loss.backward() optimizer.step() total_loss loss.item() return total_loss / len(dataset)注意policy_pred来自网络输出的LogSoftmaxpi_t是概率分布交叉熵直接用-sum(pi_t * log_p)写比F.cross_entropy更直观。策略损失和价值损失的权重都设为1.0不需要像AlphaGo论文那样把价值权重压到0.3——五子棋价值信号足够明确双方权重1:1即可。如果训练过程中loss震荡明显把batch调大或学习率降一半。优化器用Adam初始学习率1e-3跑前几个迭代后面降到1e-4。训练数据池建议累计4000到8000个局面再开始训练数据太少网络会迅速过拟合到最近对局。每轮训练3到5个epoch就好训练过多会破坏模型已经学会的泛化能力。一个“后悔药”技巧是每次训练后把模型参数复制一份存成model_last.pt评估翻车时随时回滚——深度学习项目里备份永远不会嫌多。4.3 评估关让新旧模型先打一百局再决定换血训练迭代最忌“训练完直接替换模型接着自我对弈”。损失在降下棋棋力未必在涨更常见的是价值头暂Noisy新模型打着打着突然集体走昏招。因此必须有一道评估关让挑战者新训练模型和当前最佳模型打几十到一百局黑白交替、模拟次数相同统计挑战者胜率胜率达到60%上下才算通过。def evaluate(engine_best, engine_challenger, games100, sims400): best_wins challenger_wins draws 0 for game_idx in range(games): # 黑白交替偶数是挑战者执黑奇数是挑战者执白 if game_idx % 2 0: black, white engine_challenger, engine_best else: black, white engine_best, engine_challenger winner play_two_engines(black, white, simssims) if winner 1: # 黑胜 if game_idx % 2 0: challenger_wins 1 else: best_wins 1 elif winner -1: # 白胜 if game_idx % 2 0: best_wins 1 else: challenger_wins 1 else: draws 1 return challenger_wins / games黑白交替统计不能省。五子棋先手优势很明显只让挑战者执黑测出的胜率虚高。如果挑战者胜率大于0.55就把它设为新的最佳模型继续下一轮自我对弈否则丢弃新模型沿用旧模型继续产生数据。这个“赢者通吃”机制是整个训练管线不回退的保险丝。部署文档里那张棋力上涨曲线其实就是这个胜率在迭代轮次上的变化——从0.3一路爬到0.6再趋于平稳比任何loss曲线都有说服力。5. 五子棋博弈引擎的常见坑现象、原因与解决方案5.1 Loss下降但棋力变差价值头被自我对弈噪声带偏现象训练loss一路走低看起来“学得很努力”但拉出来和人对战时引擎开局乱走中盘也不会主动做棋甚至把棋子往角上堆。原因自我对弈的早期数据全是随机网络互殴产生的终局胜负跟具体某一步棋的因果关系极弱。价值头把这些噪声当真值拟合优化器让价值输出拼命逼近±1可局面评估早就扭曲了。策略头也跟着遭殃因为样本里的pi分布来自一个低质量MCTS它输出的先验概率本身就不靠谱。解决不要“训练完就换血”。每轮的模型必须过第4.3节的评估关胜率不够就丢弃。训练目标以对局胜负为唯一标准loss是黑匣子胜负曲线才直观。此外自我对弈前面十几手强制高温随机增加早期局面多样性能有效稀释单局噪声对价值头的影响。5.2 模拟次数不足导致错过冲四AI走出“人类看不懂”的棋现象引擎明明能赢的局它不下冲四直接取胜反而去堵对手一个无关紧要的眠二你复盘时发现它“漏看了”整条杀棋线。原因MCTS模拟次数太少搜索树根本来不及展开到发现杀棋的深度。网络先验概率即使给出了冲四位置0.52、其他位置0.48PUCT公式里探索项sqrt(parent_N)/(1child_N)会反复把模拟分流到其他候选点冲四那条分支的Q值还没来得及更新到足够高就被冷落。解决把模拟次数提到400以上而不是先怀疑网络结构。五子棋的杀棋往往就在两条线内400次模拟已经足够让MCTS发现大多数3步以内的胜法。还有一个配套调整合法落点过滤时除了空位可以额外把所有能形成“活四”“冲四”“活三”的点排到候选列表前面——这类启发式不算硬编码棋型只是给MCTS导航我用过之后发现引擎在场面上变果断了很多。5.3 先手优势导致自我对弈数据一边倒现象模型执黑时胜率显著高于执白但你观察白方的防守质量很差——它根本不知道怎么防只会照着黑方的棋型模仿。原因自我对弈数据黑白比例天然不均衡。黑方先手优势大赢了整局后黑方的所有样本都标记1白方所有样本都标记-1。久而久之模型学会了“主动成棋”却没学会“阻断对手”因为它的防守经验全被终局-1的标签淹没。解决评估阶段强制黑白交替第4.3节已经做了训练阶段同样要保证数据的黑白角色平衡。另外可以在每次自我对弈时随机让黑方第一手落在不同位置而不是固定天元开局逼迫双方打不同格局。数据池抽样时按“当前玩家视角”统一采样别按黑/白切分——模型需要学的是“当前玩家如何决策”不是“我永远执黑”。5.4 数据生成太慢训练等待时间全花在自我对弈上现象训练一小时发现80%的时间都在生成数据GPU利用率不到10%CPU单核跑MCTS跑到冒烟。原因MCTS每模拟一次就要一次网络前向如果写成对单局面逐个推理Python进程的开销远大于GPU计算本身。生成数据和训练串行执行时GPU等CPU算完一批数据才开工全是空闲时间。解决拆分两条流水线。生成数据的进程只负责self-play推理用小batch32或64批量前向训练进程只读落盘好的样本。这里用一个小技巧MCTS内部把待评估的叶子节点攒满一个batch统一喂给网络再把价值回填各节点比每次模拟单独调一次网络快2到3倍。如果你有GPU可以让生成进程也用GPU推理但要小心显存冲突——多进程共用一张显卡时给每个进程分配独立的torch device上下文才行。5.5 部署时模型加载与并发状态串扰现象本地测试一切正常部署成HTTP服务后两个玩家同时请求引擎有时会返回已经占用位置的落子或者响应时间突然飙高。原因MCTS的搜索树是每次请求单独创建的但如果引擎实例不小心把搜索树、节点状态写成类属性两个请求共用一个引用树的访问计数和价值叠加就会全乱。模型推理也要锁或独立上下文否则并发前向会读到彼此半成品结果。解决把MCTS做成无状态函数每次请求新建搜索树根节点模型加载一次后只读调用。如果仍要并发共享模型加一把线程锁保证前向推理原子性。这部分在第6章给出一个干净的部署结构。6. 部署与验证把你训练好的引擎跑成能对战的程序6.1 用Flask把引擎包成HTTP接口训练完的模型不能只在训练脚本里自嗨要让人能对弈。常见做法是把引擎封装成一个HTTP服务前端无论写网页还是桌面客户端只需要发棋盘状态、收落子坐标from flask import Flask, request, jsonify import numpy as np app Flask(__name__) engine Engine.load(model_best.pt, sims400) # 服务启动时只加载一次 app.route(/move, methods[POST]) def move(): body request.get_json() board np.array(body[board], dtypenp.int8) # 15x15 player body[player] # 1 黑 / -1 白 last_move tuple(body[last_move]) if body.get(last_move) else None # 引擎内部保证无状态每次请求新开搜索树 action engine.get_action(board, player, last_move, temp0) return jsonify({action: action}) if __name__ __main__: app.run(host0.0.0.0, port8080)部署时把引擎类拆成两个部分模型推理只读加载一次和搜索过程每次请求新建。Flask默认是单进程多线程如果搜索树被写成类属性并发请求一定会互相污染用第5.5节的办法即可。接口越简单越好——前端只管发board和player别让前端算特征特征提取留在Python端。6.2 用黑白交替对局验证棋力与稳定性部署上线前要跑一轮自动化验证让部署的引擎和训练时的最佳模型互下100局黑白交替统计胜率、平均响应时长和非法落子次数。这个验证有三个目的确认部署版和训练版没有任何行为差异比如推理精度被float16改变确认并发压力下不产生非法落子确认单步响应时间符合人机体验1到2秒内可接受。验证通过后引擎才真正算“可用”。我踩过最大的坑就是把引擎做得太“重”。最初图省事把所有逻辑塞进一个文件训练、搜索、部署全耦合在一起想调一个c_puct参数必须重跑整个流程后来自我对弈、模型、MCTS、服务端四个模块拆开后调参和排错都变成分钟级的事。这个“轻模块重搜索”的思路延续到部署阶段后我再也没被人类对手催着等响应。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询