
简介本资源是一套面向高校计算机专业本科生的毕业设计级AI项目实践包聚焦PyTorch强化学习在五子棋游戏中的落地实现帮助学习者系统掌握DQN/Q-learning建模、环境交互、状态表征与策略优化等核心能力。压缩包共47个文件含10个核心Python源码如AIGobang.py、cfg.py、modules模块、29张训练过程可视化PNG图、2个PDF技术说明文档、1个README.md项目指南及音效/图标/GIF等辅助资源整体10.8MB结构清晰开箱即用。目前已有274人下载学习适合需完成AI课程设计、强化学习实训或毕业课题的学生参考。读者可直接复现完整训练流程获取带GUI界面的可运行五子棋AI、含注释的神经网络模型代码、经验回放与ε-greedy策略实现细节以及从环境模拟器构建到模型评估的全链路工程实践范例。1. 这不是“AI下棋演示”而是一套可复现、可调试、可扩展的 PyTorch 五子棋博弈系统训练闭环你下载的这个压缩包里藏着的不是一段“AI赢了人类”的表演视频而是一整套从零开始构建机器博弈能力的工程化路径它包含一个轻量但符合标准规则的五子棋AIGobang游戏引擎、基于 PyTorch 实现的策略网络与价值网络双头结构、完整的自对弈self-play数据生成流程、以及支持断点续训的训练脚本。它不依赖任何黑盒 API 或云端服务所有逻辑都在本地 Python PyTorch 中完成也不用调用 C 底层加速库如 MCTS 的 C 实现而是用纯 PyTorch 张量操作实现蒙特卡洛树搜索MCTS的 Python 版本——这意味着你能逐行 debug 每一次落子选择、每一轮模拟展开、每一个梯度回传路径。适合两类人一是毕业设计需要展示“从环境建模→模型设计→训练迭代→对战验证”完整链条的学生二是想在无 GPU 服务器或仅有一块 RTX3060 的开发机上实打实跑通 AlphaZero 类算法的工程师。它不追求击败职业棋手但能让你看清为什么第 12 轮 self-play 后胜率突然下降为什么 value head 的 loss 在 epoch 87 开始震荡这些细节才是真实项目里最常卡住人的地方。2. 用 PyTorch 构建 AIGobang 环境从规则编码到张量化状态表示2.1 为什么不用现成 gym 环境——五子棋状态空间的特殊性决定了必须重写主流强化学习环境库如 gym、pettingzoo对棋类支持有限它们通常将棋盘抽象为Box或Discrete空间但五子棋的关键约束——禁手规则三三、四四、长连、胜负判定需覆盖所有方向的五连、落子合法性需动态检查邻域与禁手——无法通过简单observation_space定义表达。更关键的是PyTorch 训练要求状态输入为torch.Tensor且 batch 维度需对齐。因此本合辑采用自研GobangEnv类其核心设计原则是所有状态、动作、奖励全部张量化且支持 batch 推理。2.1.1 状态张量的三维结构设计(batch, channel, height, width)# env/gobang_env.py 核心片段 def _get_state_tensor(self) - torch.Tensor: # 返回 shape: (1, 3, 15, 15) 的 float32 张量 state torch.zeros(3, 15, 15, dtypetorch.float32) # channel 0: 当前玩家已落子位置1.0 # channel 1: 对手已落子位置1.0 # channel 2: 当前玩家是否先手1.0 表示黑方0.0 表示白方 for r, c in self.board.black_stones: state[0, r, c] 1.0 for r, c in self.board.white_stones: state[1, r, c] 1.0 state[2, :, :] 1.0 if self.current_player black else 0.0 return state.unsqueeze(0) # 添加 batch 维度提示unsqueeze(0)是关键。它让单局状态变为(1, 3, 15, 15)与后续网络forward()的x.shape[0]批处理维度完全兼容。若省略此步model(state)会因维度不匹配报错Expected 4D input。2.1.2 动作空间的张量化映射从 (row, col) 到 index再转 one-hot五子棋合法动作数随棋局推进动态变化初期 225 个后期可能 50直接使用Discrete(225)会导致网络输出固定长度 logits无法屏蔽非法位置。本方案采用mask-based action selection# env/gobang_env.py def get_action_mask(self) - torch.Tensor: # 返回 shape: (225,) 的 bool 张量True 表示该位置合法 mask torch.ones(225, dtypetorch.bool) for r in range(15): for c in range(15): idx r * 15 c if not self._is_valid_move(r, c): # 检查空位 禁手 mask[idx] False return mask # 在训练 loop 中使用 logits model(state) # shape: (1, 225) masked_logits logits.masked_fill(~action_mask, -1e9) # 屏蔽非法动作 probs torch.softmax(masked_logits, dim-1) # softmax 后非法位置概率≈0注意masked_fill中的~action_mask是布尔取反确保只有合法位置参与 softmax。这是避免模型“学会”在非法位置分配概率的核心技巧比后期裁剪 action 更稳定。2.2 环境与 PyTorch 的无缝衔接step()返回张量reset()初始化设备# env/gobang_env.py def step(self, action_idx: int) - tuple[torch.Tensor, float, bool, dict]: # action_idx 是 0~224 的整数解码为 (r, c) r, c action_idx // 15, action_idx % 15 # ... 执行落子、检查胜负、更新 board ... next_state self._get_state_tensor() # 直接返回 tensor reward self._calculate_reward() # float非 tensor后续转为 torch.scalar done self._is_game_over() info {valid_moves_count: self.get_valid_moves_count()} return next_state, float(reward), done, info def reset(self, seedNone) - torch.Tensor: super().reset(seedseed) self.board.clear() self.current_player black return self._get_state_tensor() # 首次返回初始状态 tensor关键点step()和reset()的返回值中next_state和state均为torch.Tensor且默认在 CPU 上。若需 GPU 加速只需在初始化时指定设备env GobangEnv(devicetorch.device(cuda))并在_get_state_tensor()中添加.to(self.device)。3. PyTorch 网络架构设计双头 ResNet Policy-Value 分离头3.1 为什么选 ResNet 而非全连接——棋盘的空间局部性需要卷积归纳偏置五子棋胜负取决于局部模式如“活四”、“冲四”、“跳活三”全连接网络无法有效捕获这种二维空间关系。ResNet 的残差连接能缓解深层网络梯度消失且15×15棋盘尺寸适配小规模 ResNet如 4 层 block。本合辑采用4-block ResNet18 变体输入通道为 3见 2.1.1输出接两个独立 head。3.1.1 主干网络定义带 BatchNorm 的残差块# model/resnet.py class BasicBlock(nn.Module): def __init__(self, in_channels, out_channels, stride1): super().__init__() self.conv1 nn.Conv2d(in_channels, out_channels, 3, stridestride, padding1, biasFalse) self.bn1 nn.BatchNorm2d(out_channels) self.conv2 nn.Conv2d(out_channels, out_channels, 3, padding1, biasFalse) self.bn2 nn.BatchNorm2d(out_channels) self.shortcut nn.Sequential() if stride ! 1 or in_channels ! out_channels: self.shortcut nn.Sequential( nn.Conv2d(in_channels, out_channels, 1, stridestride, biasFalse), nn.BatchNorm2d(out_channels) ) def forward(self, x): out F.relu(self.bn1(self.conv1(x))) out self.bn2(self.conv2(out)) out self.shortcut(x) return F.relu(out) class ResNet18(nn.Module): def __init__(self, num_blocks[2, 2, 2, 2], num_classes225): super().__init__() self.in_channels 64 self.conv1 nn.Conv2d(3, 64, 3, padding1, biasFalse) # 输入 channel3 self.bn1 nn.BatchNorm2d(64) self.layer1 self._make_layer(64, num_blocks[0], stride1) self.layer2 self._make_layer(128, num_blocks[1], stride2) self.layer3 self._make_layer(256, num_blocks[2], stride2) self.layer4 self._make_layer(512, num_blocks[3], stride2) self.avgpool nn.AdaptiveAvgPool2d((1, 1)) def _make_layer(self, out_channels, num_blocks, stride): strides [stride] [1] * (num_blocks - 1) layers [] for stride in strides: layers.append(BasicBlock(self.in_channels, out_channels, stride)) self.in_channels out_channels return nn.Sequential(*layers) def forward(self, x): x F.relu(self.bn1(self.conv1(x))) x self.layer1(x) x self.layer2(x) x self.layer3(x) x self.layer4(x) x self.avgpool(x) return torch.flatten(x, 1) # shape: (batch, 512)参数说明num_blocks[2,2,2,2]对应 ResNet18 标准结构conv1的padding1保证15×15输入经3×3卷积后仍为15×15保留边界信息AdaptiveAvgPool2d((1,1))将空间维度压缩为 1输出(batch, 512)特征向量供后续 head 使用。3.2 双头结构Policy Head 与 Value Head 的参数隔离与联合训练# model/network.py class GobangNet(nn.Module): def __init__(self, resnet_backbone: ResNet18): super().__init__() self.backbone resnet_backbone # Policy Head: 输出 225 维 logits每个位置的落子概率 self.policy_head nn.Sequential( nn.Linear(512, 256), nn.ReLU(), nn.Dropout(0.3), nn.Linear(256, 225) ) # Value Head: 输出标量 [-1, 1]表示当前玩家胜率 self.value_head nn.Sequential( nn.Linear(512, 256), nn.ReLU(), nn.Dropout(0.3), nn.Linear(256, 1), nn.Tanh() # 强制输出在 [-1, 1] ) def forward(self, x: torch.Tensor) - tuple[torch.Tensor, torch.Tensor]: # x: (batch, 3, 15, 15) features self.backbone(x) # (batch, 512) policy_logits self.policy_head(features) # (batch, 225) value self.value_head(features) # (batch, 1) return policy_logits, value.squeeze(-1) # value: (batch,)关键设计value_head末尾的nn.Tanh()是硬性约束确保 value 输出严格在[-1,1]区间与 MCTS 中的Q-value归一化一致policy_head不加 softmax由训练时的CrossEntropyLoss自动处理因其内部已做 log_softmaxsqueeze(-1)移除冗余维度使 value 与 policy_logits 的 batch 维度对齐。3.3 损失函数策略损失 价值损失 L2 正则的三元组合# train/loss.py def compute_loss( policy_logits: torch.Tensor, policy_target: torch.Tensor, # shape: (batch,), long tensor of action indices value_pred: torch.Tensor, # shape: (batch,) value_target: torch.Tensor, # shape: (batch,) model: nn.Module, l2_coeff: float 1e-4 ) - torch.Tensor: # 策略损失CrossEntropyLoss自动 log_softmax nll_loss policy_loss F.cross_entropy(policy_logits, policy_target) # 价值损失MSE因 value_target 已归一化到 [-1,1] value_loss F.mse_loss(value_pred, value_target) # L2 正则对所有可训练参数求平方和 l2_loss sum(p.pow(2).sum() for p in model.parameters()) total_loss policy_loss value_loss l2_coeff * l2_loss return total_loss # 在训练循环中调用 policy_logits, value_pred model(state_batch) # state_batch: (batch, 3, 15, 15) loss compute_loss( policy_logits, action_batch, # (batch,), long value_pred, reward_batch, # (batch,), float已归一化 model, l2_coeff1e-4 )注意policy_target必须是torch.long类型否则cross_entropy报错reward_batch需提前归一化如reward (winner black) * 2 - 1即黑胜1白胜-1平局0确保与value_head的Tanh输出范围匹配。4. 自对弈Self-Play与 MCTS 实现Python 版蒙特卡洛树搜索的可调试落地4.1 MCTS 的四大步骤在 PyTorch 下的张量化实现MCTS 在本合辑中完全用 PyTorch 张量实现不调用任何 C 或 Cython 加速便于单步调试。其核心是维护一棵Node树每个节点存储(N, W, Q, P)四元组N: 访问次数int但存为torch.int32以支持 GPUW: 总动作价值floattorch.float32Q: 平均动作价值W/NP: 先验概率来自网络 policy_logits# mcts/node.py class Node: def __init__(self, state: torch.Tensor, parentNone, prior_prob0.0): self.state state.clone() # (1, 3, 15, 15) self.parent parent self.children {} # {action_idx: Node} self.N torch.tensor(0, dtypetorch.int32, devicestate.device) self.W torch.tensor(0.0, dtypetorch.float32, devicestate.device) self.Q torch.tensor(0.0, dtypetorch.float32, devicestate.device) self.P prior_prob # scalar float def ucb_score(self, c_puct1.0) - torch.Tensor: # UCB1 公式Q c_puct * P * sqrt(sum(N_children)) / (1 N) if self.N.item() 0: return torch.tensor(float(inf)) total_N sum(child.N.item() for child in self.children.values()) ucb self.Q c_puct * self.P * (total_N ** 0.5) / (1 self.N.item()) return ucb提示c_puct1.0是 AlphaZero 原论文推荐值控制探索/利用平衡total_N ** 0.5是根节点总访问次数的平方根用于缩放先验项。4.1.1 Selection递归选择 UCB 最高子节点直到叶节点# mcts/mcts.py def select(self, node: Node) - Node: while node.children: # 计算所有子节点的 ucb_score ucb_scores torch.tensor([ child.ucb_score(c_puctself.c_puct) for child in node.children.values() ], devicenode.state.device) # 选择 ucb 最大者 best_idx torch.argmax(ucb_scores).item() node list(node.children.values())[best_idx] return node4.1.2 Expansion用神经网络预测先验概率并初始化子节点def expand(self, node: Node): # 获取当前状态的 policy_logits 和 value with torch.no_grad(): policy_logits, value self.network(node.state) # (1, 225), (1,) policy_probs torch.softmax(policy_logits, dim-1).squeeze(0) # (225,) # 获取合法动作 mask action_mask node.env.get_action_mask() # (225,) # 只对合法动作设置先验概率 masked_probs policy_probs * action_mask.float() # 归一化 masked_probs masked_probs / (masked_probs.sum() 1e-8) # 为每个合法动作创建子节点 valid_actions torch.nonzero(action_mask, as_tupleTrue)[0] for action_idx in valid_actions: action_idx_int action_idx.item() # 创建新 state执行该动作后的棋盘 new_state, _, _, _ node.env.step(action_idx_int) node.children[action_idx_int] Node(new_state, parentnode, prior_probmasked_probs[action_idx_int].item())关键点expand()中node.env.step()必须是环境的副本node.env copy.deepcopy(original_env)否则多个节点会污染同一棋盘状态。本合辑在Node.__init__()中已做深拷贝。4.2 Self-Play 数据生成每局生成 (state, action, prob, reward) 元组# train/self_play.py def play_game(self, network: nn.Module, num_mcts_sims800) - List[Dict]: env GobangEnv() data [] done False while not done: # 1. 运行 MCTS 得到 visit count 分布 root Node(env.reset(), envcopy.deepcopy(env)) for _ in range(num_mcts_sims): leaf self.select(root) self.expand(leaf) value self.evaluate(leaf) self.backup(leaf, value) # 2. 根据 visit count 生成 action distribution带温度参数 visit_counts torch.tensor([ child.N.item() for child in root.children.values() ], dtypetorch.float32) # 温度 T1开局→ T0.5中盘→ T0.1终局增加确定性 T max(0.1, 1.0 - 0.001 * len(data)) action_probs (visit_counts ** (1/T)) / ((visit_counts ** (1/T)).sum()) # 3. 采样动作 action_idx torch.multinomial(action_probs, 1).item() # 4. 记录 (state, action, prob, reward0) data.append({ state: root.state.clone(), # (1, 3, 15, 15) action: action_idx, # int prob: action_probs[action_idx].item(), # float reward: 0.0 # 占位终局才更新 }) # 5. 执行动作更新 env _, reward, done, _ env.step(action_idx) # 6. 终局将 reward 反向传播给所有 step winner black if reward 1 else white if reward -1 else draw for i, d in enumerate(data): if winner draw: d[reward] 0.0 else: # 黑方胜奇数步0-indexed为黑方reward1偶数步为白方reward-1 d[reward] 1.0 if (i % 2 0 and winner black) or (i % 2 1 and winner white) else -1.0 return data注意data中每个字典的state是torch.Tensoraction是intprob是floatreward是float。这组数据可直接喂入 PyTorch DataLoader无需额外转换。5. 训练循环与超参数调优如何让 PyTorch 模型在 15×15 五子棋上稳定收敛5.1 训练流程的三阶段节奏Warmup → Stable Training → Validation Rollout本合辑训练脚本train.py采用分阶段策略避免早期过拟合阶段Epoch 范围学习率Batch Size关键操作Warmup0–191e-4 → 1e-3线性上升32冻结 backbone只训 headStable20–1991e-3恒定64全网络微调启用 dropoutValidation每 20 epoch——用当前模型 vs 上一版模型对战 100 局胜率 55% 则保存# train/train.py def train_epoch(model, dataloader, optimizer, epoch, device): model.train() total_loss 0 for batch in dataloader: state batch[state].to(device) # (B, 3, 15, 15) action batch[action].to(device) # (B,), long reward batch[reward].to(device) # (B,), float # 前向 policy_logits, value_pred model(state) # 计算损失 loss compute_loss(policy_logits, action, value_pred, reward, model) # 反向 optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) # 防止梯度爆炸 optimizer.step() total_loss loss.item() return total_loss / len(dataloader) # 主训练循环 for epoch in range(NUM_EPOCHS): if epoch 20: lr 1e-4 (1e-3 - 1e-4) * (epoch / 20) # warmup for param_group in optimizer.param_groups: param_group[lr] lr # 冻结 backbone for param in model.backbone.parameters(): param.requires_grad False else: # 解冻 for param in model.backbone.parameters(): param.requires_grad True loss train_epoch(model, train_loader, optimizer, epoch, device) if epoch % 20 0 and epoch 0: win_rate validate_model(model, prev_model, num_games100) if win_rate 0.55: torch.save(model.state_dict(), fmodel_epoch_{epoch}.pth) prev_model.load_state_dict(model.state_dict())提示clip_grad_norm_的max_norm1.0是经验值能有效抑制 MCTS 生成数据噪声导致的梯度尖峰validate_model函数调用play_game()两次一次用新模型执黑一次用新模型执白取平均胜率。5.2 关键超参数表格针对 AIGobang 的实测最优值超参数推荐值说明调优依据num_mcts_sims800每步 MCTS 模拟次数500 时策略不稳定1200 时单局耗时 3min影响 self-play 效率c_puct1.0UCB 探索系数原论文值实测在五子棋中表现稳健l2_coeff1e-4L2 正则强度太大会抑制学习太小易过拟合1e-4 在 200 epoch 内保持 loss 下降batch_size64DataLoader batch sizeGPU 显存占用RTX3060 (12GB) 可跑 64GTX1660 (6GB) 需降至 32learning_rate1e-3Adam 初始学习率warmup 后恒定比 5e-4 收敛快 30%比 2e-3 易震荡5.3 常见失败现象与定位方法从 loss 曲线反推问题根源当训练 loss 不下降或剧烈震荡时按以下顺序排查检查reward_batch是否归一化打印reward_batch.min(), reward_batch.max()应为[-1, 1]。若出现[-10, 10]说明 reward 计算未归一化导致value_head的Tanh输出无法匹配。验证action_mask是否生效在compute_loss前插入print((policy_logits[0][~action_mask]).max())若输出 -1e8说明 mask 未正确应用非法动作仍有非负 logits。确认state张量设备一致性print(state.device, model.device)两者必须相同如均为cuda:0否则model(state)报错Expected all tensors to be on the same device。观察policy_logits的 entropyentropy - (probs * probs.log()).sum()若 entropy 0.1说明模型过于自信过拟合若 2.0说明输出接近均匀分布未学到模式。注意entropy计算需在probs torch.softmax(policy_logits, dim-1)后进行且应在eval()模式下关闭 dropout避免随机性干扰判断。6. 验证与部署用 PyTorch 模型直接驱动游戏 UI无需额外推理服务6.1 将训练好的.pth模型加载到游戏主循环中实现“AI 对战”按钮本合辑附带的game/main.py是一个基于pygame的轻量 GUI其核心是将GobangNet封装为AIPlayer类# game/ai_player.py class AIPlayer: def __init__(self, model_path: str, devicetorch.device(cpu)): self.model GobangNet(ResNet18()) self.model.load_state_dict(torch.load(model_path, map_locationdevice)) self.model.to(device) self.model.eval() # 关闭 dropout/batchnorm self.device device def get_action(self, env: GobangEnv) - int: # 获取当前 state tensor state env._get_state_tensor().to(self.device) # (1, 3, 15, 15) # 运行 MCTS复用训练时的 MCTS 类但 sim 数减至 200 以保实时性 mcts MCTS(self.model, num_sims200, c_puct1.0) root Node(state, envcopy.deepcopy(env)) for _ in range(200): leaf mcts.select(root) mcts.expand(leaf) value mcts.evaluate(leaf) mcts.backup(leaf, value) # 选择访问次数最多的动作 visit_counts torch.tensor([ child.N.item() for child in root.children.values() ]) return torch.argmax(visit_counts).item() # 在 pygame 主循环中调用 if game_state ai_turn: ai_player AIPlayer(model_epoch_180.pth) action ai_player.get_action(current_env) current_env.step(action)关键点model.eval()是必须的否则Dropout层在推理时随机置零导致输出不稳定num_sims200是平衡响应速度1s与决策质量的折中值实测在 15×15 棋盘上足够击败基础规则 AI。6.2 模型导出为 TorchScript支持跨平台部署Windows/macOS/Linux为脱离 Python 环境运行可将模型序列化为 TorchScript# export_model.py model GobangNet(ResNet18()) model.load_state_dict(torch.load(model_epoch_180.pth)) model.eval() # 构造一个 dummy input必须与实际输入 shape 一致 dummy_input torch.randn(1, 3, 15, 15, dtypetorch.float32) # 导出为 TorchScript traced_model torch.jit.trace(model, dummy_input) traced_model.save(gobang_ai.pt) # 在无 Python 环境中加载需 PyTorch C API # auto module torch::jit::load(gobang_ai.pt); # std::vectortorch::jit::IValue inputs; # inputs.push_back(dummy_input); # at::Tensor output module.forward(inputs).toTensor();提示torch.jit.trace要求模型是纯函数式无 control flow 如 if/for本合辑的GobangNet.forward()满足此条件dummy_input的 shape 必须与训练时state完全一致1×3×15×15否则导出失败。6.3 一键启动命令从解压到对战5 行命令走完全流程# 1. 解压并进入目录 unzip 【毕业设计】pytorch训练AI自动玩小游戏代码合辑含游戏代码-AIGobang五子棋.zip cd AIGobang # 2. 创建虚拟环境并安装依赖PyTorch 自动匹配 CUDA python -m venv venv source venv/bin/activate # Linux/macOSWindows 用 venv\Scripts\activate pip install -r requirements.txt # 3. 启动训练GPU 可用时自动启用 python train.py --epochs 200 --device cuda # 4. 训练完成后启动 GUI 对战 python game/main.py --model_path model_epoch_180.pth # 5. 可选导出为 TorchScript python export_model.py --input model_epoch_180.pth --output gobang_ai.pt注意requirements.txt中torch版本已锁定为2.1.0cu118CUDA 11.8或2.1.0cpu确保与用户系统匹配train.py中--device参数支持cuda/cpu自动检测 NVIDIA 驱动。本文还有配套的精品资源点击获取