ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python+模仿学习+深度强化学习:AI掼蛋系统构建实战

Python+模仿学习+深度强化学习:AI掼蛋系统构建实战 简介面向毕业设计、课程设计或项目开发场景这套基于 Python 构建的 AI 掼蛋系统将模仿学习与深度强化学习结合提供完整源码、模型训练流程与项目文档适合需要从零搭建棋牌 AI 项目的学习者。压缩包共含 55 个文件以 45 个 Python 代码文件为主体另有 YAML 配置、Markdown 文档、PDF 说明、服务端脚本与可执行程序等整体 15.86MB目录结构清晰便于按模块定位、阅读和二次扩展。目前已有 160 人学习浏览常被用作高校毕设与课程设计的参考项目。资源覆盖掼蛋规则、智能体决策、模仿学习预训练、深度强化学习调优等关键环节并提供训练脚本、启动指引和二次开发思路可在此基础上继续优化策略或迁移到其他牌类 AI 任务实用价值较强。1. 基于Python模仿学习深度强化学习构建AI掼蛋系统先解决“能赢”再解决“会赢”这套标题对应的不是一个简单规则AI而是一条完整的训练流水线用Python、模仿学习和深度强化学习三层技术先让模型从人类牌谱里学会出牌的基本手感再把它丢进自对弈环境里反复试错、持续提优最终交付的产物包括可复现的源码、训练好的模型权重、项目文档和使用教程。它适合三类人做毕业设计想选一个有技术纵深的题目做课程设计想把机器学习理论落到具体玩法上或者做AI应用开发想验证一套决策推理管线的同学。判断这个方向值不值得投入先看一个标准你要的是一堆能跑的代码还是一套能解释、能换场景复用的智能体训练框架——后者才是这个项目真正的价值。2. 掼蛋AI的问题拆解与方案选型为什么模仿学习要打头阵深度强化学习殿后2.1 掼蛋的任务特性不完全信息、千级动作空间与组队配合掼蛋是四人两两组队的升级类牌戏一手27张出牌逻辑比斗地主多了同花顺、钢板、三带二和逢人配还要求队友之间互相配合。第一次接触这个项目的人通常会低估两件事一是动作空间太大二是信息不完全。动作空间大体现在出牌组合上。每轮你都要从手牌里挑一个子集构成合法牌型单张、对子、三带二、顺子、钢板、炸弹、同花顺再叠加当前级牌的动态变化一个常规回合的动作候选往往上千个。这就意味着你不能像做普通分类那样固定输出几百个类得动态生成合法动作列表。不完全信息则更麻烦。你只能看到自己的手牌、公共出牌记录和队友出过的牌对手手里剩什么全靠推理。斗地主还能根据“剩一张”做简单风险判断掼蛋里27张手牌带来的隐藏信息足够让贪心策略翻车。更隐蔽的是配合维度队友出大牌可能是在传递“我这路走完了”的信号你盲目压队友的牌反而会打乱整个节奏。所以掼蛋AI的核心难点不是单步决策而是要在“不知道别人手里有什么”的前提下结合队友行为做带博弈性质的序列决策。这也是为什么很多人直接用深度强化学习从头训结果训练了几十万局模型还在乱出单张——探索空间太大了没有一个好的起点随机策略根本摸不到赢牌的门。2.2 两段式路线模仿学习先学人类牌感深度强化学习再自对弈提优对抗这种大探索空间的标准做法是先模仿学习、后深度强化学习的两段式训练。把AI当agent看模仿学习就是给agent注入初始策略让它先学会“像人一样出手”深度强化学习则是让agent在自对弈中自我博弈把“像人”升级成“比人更稳”。模仿学习在实践中通常就是行为克隆Behavior Cloning收集大量人类牌谱每一条样本记录“当前局面 → 人类出了什么”然后监督学习训练一个策略网络。它解决的问题很直接人类牌谱里天然包含隐性规则什么时候拆炸弹、什么时候让队友走、什么时候主动引牌。模型不需要自己摸索这些规律背下来就行。但它也有三个明显短板。第一模型学到的是牌谱的平均行为而不是最优行为遇到牌谱没覆盖的局面就会露出破绽。第二人类牌谱有大量垃圾局如果不过滤模型会把坏习惯一起学走。第三行为克隆的训练分布是固定的模型永远不会主动探索出超越人类的出牌方式。所以第二步要把预训练好的策略丢进深度强化学习环境里微调。自对弈的好处是训练分布由模型自己生成对手越来越强模型被迫跟着进化。这个阶段不是从零学打牌而是在“人类基础策略”附近做局部搜索把胜率不高的边缘决策改掉收敛速度比纯强化学习快一个数量级。2.3 深度强化学习算法选型DQN与PPO的对比以及你该抄哪个把常见的深度强化学习算法列表对比一遍就会发现DQN、DDQN、Dueling DQN、PPO、SAC、TD3这些算法各有适用场景但落到掼蛋这种离散大动作空间、不完全信息的任务上DQN和PPO是最常被拉来对比的两个候选。DQN系算法依赖价值网络对每个动作输出一个Q值决策时取最大Q值对应的动作。它在动作空间几十个的场景表现优秀比如CartPole、围棋简化版。但掼蛋每轮合法动作上千个你不可能预先固定动作列表Q网络输出层的维度根本没法设计。硬要做的做法是用“动作掩码全量动作枚举”但每个动作都要过一遍网络训练速度让人崩溃。PPO走的是另一条路它输出的是动作概率分布而不是动作价值。先通过策略网络算每个候选动作的logit再把非法动作的logit用掩码置成负无穷最后softmax得到概率分布。动作列表动态生成完全不是问题策略梯度天然适配离散大动作空间而且对超参不那么敏感clip机制限制了每次更新的步长训练稳定性远好于传统策略梯度。所以我的选型建议很简单课程设计想控制工作量就用PPO别犹豫如果你想用DQN写进论文做对比实验那就把动作空间裁剪成“只枚举单张、对子、三带二、炸弹”这四类做一个简化可解的版本但这不是完整掼蛋。PPO配合行为克隆预训练是这个标题下最稳、最容易复现且论文里站得住脚的方案。3. 从环境到训练用Python把掼蛋规则包装成AI能学习的接口3.1 工程骨架与Python环境准备先让训练脚本能跑起来拿到这个项目的第一步不是写模型而是把工程骨架搭好。我一般会把代码拆成环境、模型、训练、评估、文档五个模块训练代码和游戏逻辑严格分离否则后面调试你的头会炸。工程结构参考下面这样guandan_ai/ ├── env/ │ ├── game.py # 对局流程与回合控制 │ ├── cards.py # 牌型识别与比较 │ └── action_mask.py # 合法动作掩码生成 ├── model/ │ ├── network.py # 特征编码 策略/价值网络 │ └── bc_train.py # 模仿学习训练入口 ├── rl/ │ ├── ppo_train.py # 深度强化学习微调入口 │ ├── self_play.py # 自对弈数据采集 │ └── reward.py # 奖励函数 ├── data/ │ └── records.json # 人类牌谱样本 ├── eval/ │ └── vs_rule_bot.py # 与规则AI对战评估 └── docs/ ├── 项目文档.md └── 使用教程.md环境准备这一步VS Code里配好Python环境或者用PyCharm打开项目根目录都可以核心是把解释器指向虚拟环境避免依赖冲突。命令如下# Python 3.10 或更高版本建议用虚拟环境隔离依赖 python -m venv .venv source .venv/bin/activate # Linux/macOS # Windows: .venv\Scripts\activate pip install numpy torch # 训练可视化可选 pip install tensorboard这里逻辑很简单numpy负责所有向量和矩阵运算torch提供自动求导和GPU训练能力。选PyTorch不选TensorFlow是因为动态图机制在动作掩码这种不规则张量操作上调试起来更直观你可以在forward里随意打印中间结果。用AI编程工具补项目框架可以但训练逻辑你必须自己把关因为你得清楚每一步在算什么。需要特别注意的是records.json这个牌谱文件里每一条样本至少要包含当前手牌、队友与对手的历史出牌记录、级牌、最终动作的牌型编码。没有真实人类牌谱的话常见做法是让规则AI先互相打生成一批质量尚可的伪牌谱作为预训练数据后面再用深度强化学习微调来纠偏。3.2 牌型识别与合法动作掩码AI随意出牌的“法律红线”牌型识别是整个环境里最容易出错、也最影响训练正确性的模块。一个合格的牌型识别函数输入是一组牌的字符串输出是牌型类别、主rank和长度。主rank用于后续压牌比较长度用于顺子这种变长牌型。from collections import Counter CARD_RANK {3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9, T:10, J:11, Q:12, K:13, A:14, 2:15, S:16, B:17} def parse_hand(hand): 把 333444555666777 解析成 rank 计数列表S小王B大王 counts [0] * 18 for c in hand.upper(): counts[CARD_RANK[c]] 1 return counts def detect_type(hand): 返回 (牌型, 主rank, 长度)供后续动作比较使用 counts parse_hand(hand) n len(hand) kinds [r for r, c in enumerate(counts) if c 0] # 基础判断单张 / 对子 / 三带二 / 顺子 / 炸弹 / 同花顺等 if n 1: return (single, kinds[0], 1) if n 2 and len(kinds) 1: return (pair, kinds[0], 2) if n 5 and len(kinds) 2 and max(counts) 3: return (three_two, kinds[0] if counts[kinds[0]] 3 else kinds[1], 5) # ... 其余牌型分支按规则展开 return (None, None, n)这个函数的逻辑说明就一句话它把出牌字符串转成rank计数再根据长度和counts分布判断牌型。主rank是压牌比较的唯一依据顺子和同花顺的主rank取最大那张炸弹比较先比主rank再比牌数。这里有两个血泪教训一是逢人配红桃配千万别在牌型识别里动态展开成任意牌否则合法动作列表会爆炸正确的做法是把“是否使用逢人配”作为一个额外的one-hot特征输入网络二是级牌会随着对局推进变化牌型识别必须接收当前级牌作为参数否则模型学到的级牌策略全是错的。有了牌型识别合法动作掩码就顺理成章了。合法动作掩码是让策略网络只能从合法出牌里选择的“法律红线”它和状态一起输入网络把非法动作的logit置为负无穷让softmax后的概率接近零。def build_action_mask(hand, legal_actions): legal_actions 是从当前局面枚举出的所有合法动作列表 mask np.ones(len(legal_actions), dtypenp.float32) for i, act in enumerate(legal_actions): if not can_play(hand, act): mask[i] 0.0 return mask参数mask的维度必须和legal_actions的长度严格对应训练时动作索引是从这个动作列表里取的推理时也从这里取一旦错位模型就会学到张冠李戴。这个错误不会报错但胜率会诡异地在某个值附近震荡排查起来极其折磨。3.3 状态编码把手牌、出牌记录和对手动向装进张量模型的输入不能是字符串得把整个局面编码成一个固定维度的浮点向量。我常用的一种编码方式是“手牌计数 三家历史 级牌”全部拼成一维向量。def encode_state(hand, played_history, teammate_history, opponent_history, level): feats [] # 当前手牌按点数归一化到 [0, 1] hand_vec [0.0] * 18 for r, c in enumerate(parse_hand(hand)): hand_vec[r] c / 8.0 feats.extend(hand_vec) # 队友和对手的历史出牌聚合 for history in (played_history, teammate_history, opponent_history): vec [0.0] * 18 for rank, count in history: vec[rank] count feats.extend(vec) # 当前级牌one-hot level_vec [0.0] * 18 level_vec[level] 1.0 feats.extend(level_vec) return np.array(feats, dtypenp.float32)这个编码方式不要求多深的Python基础语法就能看懂核心是把离散的牌局转成网络能吃的连续向量。手牌用计数除以8做归一化是因为一手牌里同一点数最多8张逢人配存在时可能更多归一化后数值范围稳定网络收敛快。历史出牌聚合用的是加法它不是精确的时序信息但足够表达“对手出过多少张A、队友还剩什么花色”这种统计特征。级牌用one-hot是避免让网络误以为级牌之间存在大小顺序。这个编码的缺点也很明显它丢掉了出牌顺序信息。比如对手最后一张打的是单张K还是开局打单张K语义完全不同。如果你想在这个骨架上升级可以加一个“最近8手出牌类型序列”的编码用一个固定长度数组记录最近出的牌型和主rank这对判断对手是否在打信号很有帮助。3.4 模仿学习预训练用人打牌谱做行为克隆状态和动作掩码准备好之后模仿学习训练就很简单了。行为克隆的实质是一个带掩码的多分类问题输入状态和合法动作掩码输出每个合法动作的概率目标是让模型预测的人类动作概率最大。import torch import torch.nn as nn import torch.nn.functional as F class BCNet(nn.Module): def __init__(self, state_dim, n_actions): super().__init__() self.fc nn.Sequential( nn.Linear(state_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, n_actions) ) def forward(self, x, mask): logits self.fc(x) # 关键一步非法动作的 logit 置为负无穷 logits logits.masked_fill(mask 0, -1e9) return F.softmax(logits, dim-1) def train_bc(model, loader, epochs20, lr1e-4): opt torch.optim.Adam(model.parameters(), lrlr) loss_fn nn.CrossEntropyLoss() for epoch in range(epochs): total 0.0 for state, mask, action in loader: prob model(state, mask) loss loss_fn(prob.log(), action) opt.zero_grad() loss.backward() opt.step() total loss.item() print(fepoch {epoch}: loss{total / len(loader):.4f})代码逻辑很直接网络输出所有动作的logitsmasked_fill把非法动作屏蔽掉softmax得到概率分布CrossEntropyLoss强迫模型把概率集中在人类出牌的那个动作上。参数里epochs给20是个起点参考太少了拟合不足太多会死记硬背。lr给1e-4是Adam在监督学习阶段的常见值调到1e-3以上你会看到loss降得飞快但验证集表现极差——过拟合了。这里要盯着验证集的top-1准确率看我一般要求达到35%以上再进入强化学习阶段。掼蛋合法动作上千个随机猜一个的准确率连0.1%都不到35%已经说明模型掌握了基本牌型选择逻辑。如果准确率死活上不去问题基本在状态编码丢了关键信息先检查级牌和逢人配处理。3.5 深度强化学习微调在自对弈里把胜率练上去BC训练完的模型已经有了“能出牌”的基础接下来用PPO在自对弈里微调。PPO的actor和critic共享同一份状态输入actor输出动作概率critic输出状态价值训练目标是最大化期望回报同时限制策略更新幅度。class PPOAgent(nn.Module): def __init__(self, state_dim, n_actions): super().__init__() self.actor nn.Sequential( nn.Linear(state_dim, 256), nn.Tanh(), nn.Linear(256, 256), nn.Tanh(), nn.Linear(256, n_actions) ) self.critic nn.Sequential( nn.Linear(state_dim, 256), nn.Tanh(), nn.Linear(256, 256), nn.Tanh(), nn.Linear(256, 1) ) def act(self, x, mask): # 选择动作阶段返回动作和对数概率 logits self.actor(x).masked_fill(mask 0, -1e9) dist torch.distributions.Categorical(logitslogits) action dist.sample() return action, dist.log_prob(action), self.critic(x) def evaluate(self, x, mask, action): # 更新阶段重新计算动作概率和状态价值 logits self.actor(x).masked_fill(mask 0, -1e9) dist torch.distributions.Categorical(logitslogits) return dist.log_prob(action), dist.entropy(), self.critic(x)actor和critic分离的设计是有讲究的actor只管“出什么牌”critic只管“这个局面值多少分”两者如果共享网络训练后期会出现梯度冲突。PPO更新时用GAE计算优势函数然后用clip限制策略更新的步子完整更新逻辑如下def ppo_update(agent, batch, clip_eps0.2, gamma0.99, gae_lambda0.95): # batch 里包含 state, mask, action, old_logp, returns, advantages for _ in range(4): new_logp, entropy, value agent.evaluate(batch.state, batch.mask, batch.action) ratio (new_logp - batch.old_logp).exp() pg_loss -torch.min( ratio * batch.advantages, ratio.clamp(1 - clip_eps, 1 clip_eps) * batch.advantages ).mean() vf_loss nn.MSELoss()(value.squeeze(-1), batch.returns) loss pg_loss 0.5 * vf_loss - 0.01 * entropy.mean() optimizer.zero_grad() loss.backward() optimizer.step()ratio是新旧策略的概率比值clip_eps限制比值在0.8到1.2之间防止一次更新太猛把策略改坏。vf_loss让critic学会预测局面价值entropy项鼓励探索0.01是常见起点太小模型会过早固化出牌风格。整个训练流程是加载BC预训练权重用当前策略和它自己历史版本打对局收集一批数据后做4轮PPO更新再继续采样循环往复。每打完1万局保存一次checkpoint后面评估会用到。4. 避坑与排查五个逼我重训模型的坑每一条都有后悔药4.1 掩码漏掉“过牌”导致训练直接失效现象模型在别人出牌之后永远不会“不要”一直硬出结果经常因为非法动作被环境拒绝训练状态卡死胜率纹丝不动。原因合法动作掩码只枚举了手牌组合没有把“过牌”作为一个合法动作加进去。行为克隆的牌谱里过牌样本倒是存在但模型的动作表里根本没有这个候选它只能被迫选择出牌逻辑直接错乱。解决把“过牌”显式加入动作表同时区分两种局面。轮到你先手出牌时过牌掩码必须置0轮到你要压牌或选择不压时过牌掩码置1。训练样本构造时也要对应打标签否则模型分不清该不该过。4.2 模仿学习学成死记硬背现象BC训练loss下降很快验证集loss也正常但放到自对弈里模型遇到没见过的牌型组合就开始瞎出把三带二拆成单张打甚至把炸弹拆对子。原因牌谱数据极度不平衡单张、对子这类高频动作占了大头炸弹、同花顺、钢板这些低频但重要的动作被淹没了。模型学到的是“用高频动作回答一切”而不是真正理解牌型约束。解决按牌型分类做样本重采样把炸弹、同花顺、钢板这类低频样本的权重提高3到5倍。训练前先统计牌谱覆盖了多少种合法动作类型覆盖率低于80%就补数据或者用规则AI生成伪牌谱填补空缺。没有这一步后续强化学习怎么调都救不回来。4.3 自对弈越练越菜策略坍缩现象PPO训练中胜率提升到某个点后开始回落模型开始只出某一两种牌型比如永远打单张或者永远拆对子局面稍微复杂就无脑出最大牌。原因对手池只有当前策略的旧版本多样性严重不足再加熵正则系数取得太小策略过快收敛到局部最优。这种现象在自对弈里非常典型叫策略坍缩模型学会的不是打赢而是赢过另一个同样畸形的自己。解决把训练对手池扩大。常见做法是每5000局保存一个checkpoint训练时随机从最近10个子代里选对手而不是只和当前策略打。熵正则系数提到0.05让模型保持一定随机性前5万局之后再逐步衰减。4.4 评估显示高胜率换对手就崩现象和同一个规则AI对打胜率85%换成另一个风格不同的规则AI后胜率掉到35%再换成真人直接崩盘。原因训练和评估用的都是同一个对手模型学到的是针对这个对手出牌习惯的漏洞而不是通用的掼蛋策略。这种情况在强化学习里叫过拟合对手和模型过拟合训练集是一回事。解决评估时准备至少三个不同风格的规则对手激进型、保守型、随机型报告平均胜率而不是挑最高分。更严格的做法是拿模型和它自己的不同训练子代互打取胜率中位数。判断一个模型能不能用看的是最低胜率不是最高胜率。4.5 训练中断两天白跑现象训练到一半显存溢出或者电脑休眠导致进程被杀checkpoint没保存所有训练全部重来。原因训练脚本里没写定时保存或者只保存了模型权重没保存优化器状态重启后优化器状态丢失学习率和动量都被重置了。解决每1000次PPO更新保存一次完整checkpoint至少包含actor和critic的参数、优化器状态、当前epoch、随机数种子用torch.save打包成一个字典。显存溢出时先把batch_size减半同时截短状态特征里的历史序列长度不要一上来就缩小网络那样你的最终效果会肉眼可见地变差。5. 把训练调到能打的水平奖励设计、超参速查与终局评估5.1 奖励函数设计稀疏胜负奖励与稠密过程奖励怎么权衡深度强化学习对奖励函数极其敏感掼蛋这种一局几十手的游戏更是如此。如果只用稀疏的胜负奖励赢1、输-1打完一局才有信号有效训练信号太少收敛慢到怀疑人生。如果奖励设计得太稠密模型又会学会刷分比如疯狂出炸弹只为了拿炸弹奖励把胜负完全抛在脑后。我的做法是稀疏为主、稠密为辅奖励函数写成加权累加的形式。终局胜负作为主奖励过程奖励作为辅助引导奖励项值触发条件设计意图终局胜利1.0本队获得一局胜利主信号终局失败-1.0本队落败主信号出牌压制0.05单次出牌压过上家鼓励主动出击队友走牌0.1队友出完手牌鼓励配合炸弹剩余-0.1对局结束时手中还有炸弹阻止死攥炸弹过度拆牌-0.02把三带二拆成单张打保护牌型完整性最终奖励是各项加权求和权重可以先用默认值跑50万局。总的原则是全局奖励贡献至少占70%过程奖励只是让模型“走对方向”的拐杖训练后期可以把过程奖励项逐步调小。如果你发现模型胜率上不去但过程奖励刷得很高说明稠密奖励喧宾夺主了砍掉一半辅助项再试。5.2 训练超参速查表各阶段参数的推荐区间不同阶段的超参不能混用我整理了一份常用区间表覆盖模仿学习和PPO微调两个阶段。阶段参数推荐区间说明模仿学习学习率1e-4 ~ 3e-4高于1e-3极易过拟合牌谱模仿学习batch_size256 ~ 1024取决于单条样本状态维度模仿学习epochs10 ~ 30以验证集准确率不再上升为准深度强化学习gamma0.98 ~ 0.995掼蛋是长对局gamma太低会短视深度强化学习GAE lambda0.95偏差方差平衡的标准值深度强化学习clip epsilon0.2PPO标准值一般不用调深度强化学习entropy coef0.01 ~ 0.05出现策略坍缩就提高到0.05深度强化学习PPO更新轮数3 ~ 5超过5轮容易过拟合当前batch这些参数里最容易被忽略的是gamma。很多人在小游戏上习惯用0.99但掼蛋一局几十手有效回报跨度很长gamma低于0.98会让模型只看眼前几步不会为了最终的胜利去牺牲当前的出牌机会。如果你的模型总是不肯留大牌配合队友先把gamma调高。5.3 终局评估用规则AI做对照看胜率而不是看热闹训练不是跑完就结束得有一套可复现的评估流程。我会准备三个不同风格的规则AI作为基准每个基准打80局统计平均胜率、平均剩余手牌、炸弹使用率三个指标。命令长这样python eval/vs_rule_bot.py \ --model checkpoints/ppo_final.pt \ --episodes 80 \ --opponent aggressive \ --seed 0不要只跑一次就下结论换seed再跑两三遍。牌类游戏方差很大一局胜负受发牌影响严重80局的胜率标准差通常在5个百分点上下。正确的评估方式是每个对手跑3个不同seed取平均值然后和BC预训练模型做对比。如果你的PPO模型胜率还不如BC模型说明深度强化学习微调阶段出了问题大概率在奖励设计或者对手池设置上。6. 进阶技巧把AI掼蛋系统从“能打牌”升级成“会打牌”6.1 给模型加记忆队友历史出牌编码基础状态编码把队友出过的牌聚合成一个计数向量丢失了顺序信息。一个投入产出比很高的改进是给模型加一个“最近8手队友出牌”的序列特征。做法很简单用一个长度固定为8的滑动窗口记录队友最近8次出牌的牌型和主rank分别做embedding后拼进状态向量。这个改动不大但模型就能感知到“队友连续出小单张可能在引牌”这种配合信号策略表现会有可感知的提升。6.2 对手建模打风格标签比猜手牌更实用很多初学者想的是用神经网络去预测对手的具体手牌这个方向投入巨大且效果一般。更务实的做法是对对手做风格分类统计过去20手对手的出牌特征比如平均出牌牌型、炸弹使用频率、过牌率再用规则或者一个小分类器打上“激进、保守、均衡”的标签。训练时把风格标签作为一个one-hot特征输入模型模型就能学会针对不同风格调整策略——打保守型对手多拆牌试探打激进型对手则留硬牌反制。6.3 用残局库验证比全盘胜率更细的体检方式最后推荐一个我在这个项目上用过最有效的验证技巧构造残局库。从完整对局里截取最后10手牌的局面人工标注标准答案比如“只剩一手顺子加一对怎么出能保证赢”。用残局库跑模型你不需要打完整局就能快速定位策略缺陷而且残局样本可以批量生成自动核对结果。全盘胜率告诉你“模型强不强”残局库告诉你“模型哪一步蠢”。我做这个项目时最后悔的事就是第一版奖励函数堆了二十多项模型为了刷过程奖励学会了畸形打法白白浪费了两周训练时间。后来狠心砍到一张表能写完的规模训练反而开始稳定。所以如果你想把这个方向做成毕业设计或课程设计我的习惯是先跑通最小闭环再逐步加复杂度——不论你是要做源码级复现还是想换到斗地主、升级这类玩法上做迁移先把这条基线跑稳后面的一切都顺理成章。希望帮到你。本文还有配套的精品资源点击获取
返回列表