YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

QOJ 斗地主训练与测试

2026-10-04 修正后的续训

上一轮扩容实验完成,但验证最好权重仍是起点,最新模型反而退化。 新配置由 python3 -u tools/run_corrected_qoj_training.py 启动, 先完成 96 局历史/48 局 PPO 的流程对照,再自动启动全量运行。 输出 runs/resnet-guarded-20261004,旁侧 .job.json 记录阶段和进程, .log 记录实时训练进度。恢复正式训练使用同一命令追加 --resume。

具体变化:

  • 从上一轮 best.pt 保留宽度 256、两路各 4 层的共享表示,切换冻结先验为 指定 ResNet best;重置四个残差策略头和门控,使新起点的出牌/叫分 argmax 与该基准一致。这是更强先验的接入,不是已经学出了超过基准的棋力。
  • 关闭历史动作模仿 (--history-weight 0) 及历史终局收益对价值/胜负头的监督。 历史公共状态用于 ResNet 的动作蒸馏、KL 和真实暗牌标签辅助训练; 价值头使用实际生成的教师轨迹和新鲜 PPO 轨迹。
  • 离线只训练 1 轮,学习率 1e-5,强教师 KL 权重 .5、最佳动作蒸馏权重 .05。 PPO 从验证 best.pt 开始,学习率 5e-6,教师 KL 权重 .15,每轮 96 局、1 轮更新。
  • 通常 75% 对局直接针对目标 ResNet,剩余为自博弈和快照。快照随机选择, 避免按偶数索引漏用一半联盟成员;锚点从全部教师预热状态中固定随机抽取。
  • 更新后计算整轮所有状态、全部合法动作上的精确 old||new KL。 超过 .01 则恢复该轮前的模型/优化器并减半学习率。 每 8 轮验证,综合或任一模式得分比最佳低超过 .02 时, 恢复最佳模型及相应优化器、清空退化快照、减半学习率。 这是退化保护,不能保证真实棋力单调提升。
  • 正式预算 256 轮,共 24,576 局 PPO;最终测试另保留 256 副发牌/3,072 局, 并用独立 JavaScript 规则回放。新训练/验证/测试种子区间均避开旧实验。

强先验检查点使用格式 2,记录 teacher_model_type 和权重哈希,加载时自动选择 ResNet best;旧格式 1 的 LSTM 检查点仍可部署。新训练配置和源码改变后应使用 新的输出目录;旧实验检查点不能通过 --resume 混入新的目标和优化器设置。 教师原始权重、历史输入和上一次训练产物不被覆盖。

这里实现一套独立于原 ResNet 训练目录的模型。教师和默认评测对手固定为 models/douzero-baselines/douzero_ADP 中的三角色 LSTM, 不依赖 qojbot 当前默认模型,也不修改任何原权重。

2026-10-03 扩容续训

支持从旧 best.pt 扩大宽度和深度后继续训练:旧参数复制到更宽网络, 保持每个注意力头的维度,新层从恒等残差开始,保留扩容前的预测。 128 → 256、4 → 8 个注意力头、两路各 2 → 4 层时, 可训练参数从 949,951 增至 6,335,807(6.67 倍,不含冻结教师)。 新增层的随机内部投影在训练中打破复制通道的对称性。

冻结 LSTM 先验与训练/评测对手可以独立设置。 --opponent-dir 使指定的 ResNet 成为 PPO 联盟对手和验证/最终测试对手, best.pt 因而按对该 ResNet 的真实 QOJ 得分选出。 原 LSTM 先验继续保留,以兼容旧策略权重。

上述正式配置也封装在 tools/run_large_qoj_training.py,可直接执行 python3 -u tools/run_large_qoj_training.py;恢复时追加 --resume。 监督进程用锁避免重复启动,保存旁侧 .log 和 .job.json, 并在训练/测试正常结束后用独立 JavaScript 规则检查器回放最终测试。 2026-10-03 已启动的后台运行使用 caffeinate -i 防止空闲休眠。

python3 -u QOJDoudizhuModel/train.py --device mps \
  --width 256 --heads 8 --rank-layers 4 --history-layers 4 \
  --init-checkpoint QOJDoudizhuModel/model/best.pt \
  --opponent-dir douzero-resnet-2.0/Douzero_Resnet/baseline/best \
  --output QOJDoudizhuModel/runs/large-resnet-20261003 \
  --max-history-games 0 --offline-epochs 3 --warmup-games 128 \
  --rounds 1024 --games-per-round 12 --batch-states 32 \
  --checkpoint-interval 250 --learning-rate 5e-5 --ppo-learning-rate 2e-5 \
  --ppo-start latest --validation-deals 64 --validation-interval 64 \
  --test-deals 256 --seed 20261003 \
  --validation-seed 302610030 --test-seed 402610030

这次运行固定启动时 data/qoj-history/games 的全部输入, 继续按整场比赛/重复发牌分组保留 80%/10%/10% 划分。 “全量”表示验证并纳入全部牌谱;验证/测试分组不参与梯度更新,非法牌谱拒绝入库。 默认排除托管动作。dataset/progress.json 报告准备阶段的进度; launch.json 记录训练配置,initial.pt 保留扩容后的起点。 离线阶段每 250 批保存一次模型、优化器和已完成批数; 恢复使用同一整条命令追加 --resume,以确定性的顺序跳过已提交的批次。 数据准备完成前中断尚不能用 --resume,应改用新输出目录重新准备。

续训的旧检查点已经见过部分历史数据;新快照中的离线 holdout loss 不能证明对旧检查点而言的数据独立性。棋力结论只依据未与旧实验和 小规模流程测试重叠的新合成发牌测试种子。

最终测试共 3,072 局,使用 256 副独立发牌,包含固定地主/真实叫分及三座位双向轮换。 test.json 保存整体按发牌聚类的 95% bootstrap 区间、角色分项和对手权重哈希。 positive_score_supported 仅在至少 100 副牌、整体区间下界大于 0、 且各模式平均得分均大于 0 时为真。扩大参数或完成训练本身不代表已经超过对手。

单独评测:

python3 QOJDoudizhuModel/evaluate.py --device mps \
  --checkpoint QOJDoudizhuModel/runs/large-resnet-20261003/best.pt \
  --opponent-dir douzero-resnet-2.0/Douzero_Resnet/baseline/best \
  --deals 256 --seed 502610030 \
  --output QOJDoudizhuModel/results/large-resnet-independent.json

后文的旧起步实验未传 --opponent-dir,仍使用 LSTM 对手。

架构

模型以不完全信息决策为边界,只接收自己的手牌、公共出牌与叫分历史、 未见牌总集合、公开底牌、角色和剩余张数。

15 个点数 token [自己的牌/未见牌/公开底牌/当前出牌/三角色已出牌]
    -> 点数 embedding + 角色 embedding -> 2 层点数 Transformer

最近 96 个公开事件 [牌/牌型/角色/叫分] + 状态汇总 token
    -> 时间 embedding -> 2 层历史 Transformer

两路编码 + 15 维状态量
    -> 状态上下文 + 两个对手的逐点数暗牌分配预测
    -> 各合法动作的牌、余牌、牌型、主点数、长度等编码
    -> 动作对点数 token 的交叉注意力
    -> 角色条件残差策略 / 51 原子得分分布 / 胜负辅助头

状态上下文 -> 真实得分 critic

默认隐藏宽度 128、4 个注意力头、点数/历史各 2 层;地主、下家、上家、叫分 共享表示,分别使用策略输出头。LayerNorm 不依赖候选数量,不使用 BatchNorm 或 dropout。 一次编码状态,再分块评分动作;不会为每个候选重新编码完整历史。

策略为:

pi(a|s) = softmax(g_role * standardized_DouZero_value(a) / 0.6
                 + 6 * tanh(residual(s,a)))

残差头零初始化,g_role = 2 * sigmoid(parameter) 初始为 1。 因此最初的出牌 argmax 与指定 DouZero 基准一致,叫分 argmax 与原启发式一致。 门控可学习,教师先验不必永久主导策略。部署仍需要三份原 LSTM 权重。

合法动作来自现有 qojbot.rules,保留同一组牌的不同牌型解释。 叫分直接枚举 QOJ 合法的 0/1/2/3 候选及 must_bid 约束, 不是把二分类叫牌输出硬映射成叫分。

少数据时如何提升

  1. 弱离线监督:完整验证历史牌谱,记录动作的交叉熵权重为 0.15; 真实终局收益用于 critic、得分分布和胜负辅助头。默认排除托管动作, 不把所有历史玩家都当专家,也不把未出动作强行标成负例。
  2. 无限可生成的教师轨迹:随机发牌,三角色基准对局,产生合法动作偏好、 真实 QOJ 得分和暗牌辅助标签。默认先生成 32 局,之后还持续接触教师对手。
  3. 新鲜轨迹 PPO:每轮采样后更新,轮末丢弃旧 PPO 数据。记录全部合法动作 的实际采样概率,clip=0.2、最多两轮更新、KL>0.04 时停止当前轮的后续更新。 熵奖励 0.01;对教师的轻量 KL 约束默认 0.02。
  4. 混合对手联盟:基准、历史快照、现有公共信息搜索策略和新模型自博弈, 覆盖一新模型对两对手、两新模型对一对手及纯自博弈。 农民共享同方向终局收益,同时保留上下家的不同策略头。
  5. 暗牌辅助学习:训练时从已结束牌谱或已结束模拟对局恢复暗牌标签。 推理只使用预测结果;特征编码器不接收各对手真实手牌。 公开地主底牌是分配预测的硬下界,但不声称已实现精确贝叶斯后验。

PPO 的收益为每位玩家的真实 QOJ delta / 6,gamma=1; 这是线性单位变换,仍优化真实得分。按角色标准化优势以平衡地主双倍分。 51 原子辅助头使用 symlog 支撑区间 [-5,5];对数变换只用于辅助任务。 主优化不会把爆炸/叫分的得分倍率压成纯胜负。

训练

在仓库根目录执行。使用已经安装 PyTorch 的系统 python3; 当前项目 .venv 不含 PyTorch。依赖只有现有 torch、numpy; 联网游玩另需要原 bot 的网络依赖。

# 完整起步实验:全量现有牌谱,1536 局 PPO,固定地主/真实叫分交替
python3 QOJDoudizhuModel/train.py --device mps

# 恢复并扩展到 12288 局 PPO,不重新预热
python3 QOJDoudizhuModel/train.py --device mps --resume --rounds 1024

默认输出 QOJDoudizhuModel/runs/main,训练局数为 rounds * games-per-round。默认设置是起步实验,不承诺足以达到高水平; 可以持续扩展轮数,不会自动无限占用 GPU。 所有配置除 rounds 与最终测试开关外,恢复时必须保持不变。 准备长期追加训练时可加 --skip-final-test,只在冻结最终选定模型后做独立测试; 不要依据反复查看同一测试集的结果决定后续配置。

# 较大容量消融,必须使用新输出目录
python3 QOJDoudizhuModel/train.py --device mps --width 192 \
  --warmup-games 128 --rounds 1024 --output QOJDoudizhuModel/runs/w192

# 仅验证流程,不能用于宣称棋力
python3 QOJDoudizhuModel/train.py --device mps \
  --output QOJDoudizhuModel/runs/smoke-new \
  --max-history-games 6 --warmup-games 2 --offline-epochs 1 \
  --rounds 1 --games-per-round 3 --batch-states 8 \
  --validation-deals 1 --test-deals 1 --validation-interval 1 --mode bidding

当前机器的沙箱内检测不到 MPS,沙箱外已实际验证可用。 显式 --device mps 会在不可用时失败,不静默退回 CPU,也不启用算子 CPU fallback。 模型和教师都在同一 MPS 设备;环境与规则枚举在 CPU。 训练为同步单进程,不声称具备分布式吞吐。

数据与恢复

  • 首次训练固定输入文件和 SHA256,按整场比赛与重复发牌的连通分组划分 80%/10%/10% train/validation/test,防止相邻轮次或重发同一副牌泄漏。
  • 后续抓取新增文件不会改变已开始的实验;使用新输出目录才会纳入新数据。
  • 牌谱完整回放和终局计分不一致的记录拒绝入库,并在 manifest 中列出原因。
  • 数据以 256 状态分片,公共特征压成 CPU float16,前向/反向使用 float32。 不保存每个动作重复的公共历史。
  • 离线蒸馏默认每状态最多 64 个候选,保留实际动作及教师最优。 这是条件子集监督;--candidate-cap 0 可关闭。PPO 和推理始终使用全部合法动作。
  • Ctrl+C 会恢复到最近完成的 epoch/轮次,包括模型、优化器及随机状态, 重跑未提交的部分,并对齐逐局日志。缓存、教师、源码或预热数据变化时拒绝恢复。
  • 小样本可能没有验证/测试分组,此时离线 loss 为空;合成配对评测仍可运行, 但不能用小样本结果证明能力提升。

评测

训练只用固定验证种子选模;初始化基准也是候选。 只有平均得分严格提升才覆盖 best.pt,所以不保证它一定包含新训练权重。 latest.pt 始终保留最新训练权重。最终独立测试不参与选模。

# 每种模式 100 副牌,各 600 局,共 1200 局
python3 QOJDoudizhuModel/evaluate.py \
  --checkpoint QOJDoudizhuModel/runs/main/best.pt --device mps \
  --deals 100 --output QOJDoudizhuModel/results/douzero-test.json

# 搜索对手消融,使用同一独立测试种子
python3 QOJDoudizhuModel/evaluate.py \
  --checkpoint QOJDoudizhuModel/runs/main/best.pt --device mps \
  --opponent search --deals 100 \
  --output QOJDoudizhuModel/results/search-test.json

同副牌在两种模型分配方向上各轮换三个 singleton 座位。 报告包含真实 QOJ 得分、胜率、三角色/一对二与二对一分项、叫分次数、 推理 p50/p95/p99 和按原始发牌种子聚类的 bootstrap 区间。 报告复用原评测器的 lstm/resnet 键;labels 明确对应 DouZero 对手/QOJNet, 这里的 QOJNet 并不是 ResNet。

正式比较至少应增加到数百副牌,并结合区间、角色短板、叫分收益和运行耗时, 不能只比较一个随机种子的点估计。固定地主测试隔离出牌能力; 真实叫分测试评估叫分与出牌的组合效果。 评测仍不等于线上 Rating 或六局积分赛名次。

Bot 接入

# 离线分析
python3 bot.py analyze examples/opening.json --policy qoj --device mps \
  --checkpoint-dir QOJDoudizhuModel/runs/main/best.pt

# 只有明确执行这条命令才会联网并产生真实对局
python3 bot.py play --policy qoj --device mps --mode match \
  --checkpoint-dir QOJDoudizhuModel/runs/main/best.pt

也可将 latest.pt 指定为 checkpoint 做研究比较。使用 best.pt 更保守。 加载时校验教师权重哈希,并在登录前预热模型;运行中不会热更新权重。 推理沿用现有实体牌转换、合法动作校验与显式错误 fallback。 本次实现和测试没有登录 QOJ 或提交动作。

产物与测试

best.pt、latest.pt 为可部署权重;resume.pt 额外保存优化器、联盟和采样状态; dataset/manifest.json 保存输入快照和划分;report.json 保存训练概要; warmup_games.json、train_games.jsonl、validation-*.json、test.json 为可回放对局。 二进制与训练结果放在被 Git 忽略的 runs/、results/。

python3 -m unittest discover -s QOJDoudizhuModel/tests -v
python3 run_tests.py

优先继续做的实验

现有实现是可运行、可验证的起点,不是已经证实最强的架构。 建议先固定测试协议,增加自博弈预算,再分别消融历史/暗牌辅助/教师门控/ 快照联盟/候选子集和更大宽度。每个结论使用独立实验种子重复。

后续高收益方向是公共信息约束下的搜索改进数据与少牌残局专家, 以及具有固定手牌总数约束的更强暗牌后验。当前仅将已有搜索策略作为对手, 没有把完全信息搜索最优动作当作可直接部署的“不泄漏”策略, 也没有实现完整 ReBeL/CFR 或声称 Nash 保证。

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support