让 AI 玩天津麻将?

前言

今天是 2026 年 6 月 3 日,距离这个项目已经过去了五个月,由于一些原因,又回来看了一下这个项目,感觉可以写一篇博客记录一下,于是有了下面的内容。当然,这篇博客的内容几乎完全是 AI 根据我的代码生成的,也就是做一个记录罢了。

正文

1. 项目概览

这是一个 天津麻将 AI 的完整训练项目,实现了从 游戏引擎 → 数据生成 → 监督学习(模仿学习) → PPO 强化学习(Self-Play) → 模型评估 → 在线对战 的完整管线。

项目流水线

C++/C 麻将引擎
      ↓
Pybind11 / CFFI → Python 绑定
      ↓
启发式策略 → 生成训练数据 (Imitation Learning)
      ↓
监督学习 → 预训练 Actor 网络
      ↓
PPO RL → 自对战强化学习 (Self-Play)
      ↓
模型评估 / 在线 Web 对战

麻将规则特点 (天津麻将)

  • 混儿牌 (万能牌):随机翻牌确定,可替代任何牌
  • 起胡 2 番​​:不足 2 番不能胡牌
  • 番种​​:素(没混)×2、混吊(单吊混)×2、捉五(胡五万)×3、一条龙×4、杠开×2、本混龙×2 等
  • 不允许打混儿牌​​:混牌不能打出、不能碰、不能杠
  • 庄家输赢×8(有两个版本: 有庄家倍率 / 无庄家倍率)

2. 核心引擎 (C++)

文件: _00_TianjinMahjong_00_TianjinMahjong_noDealer

这是整个项目最核心的部分——用 C++ 实现的天津麻将完整游戏引擎(单个文件包含声明和实现,约 1148 行)。

版本 差异
_00_TianjinMahjong apply_gang_scores 中庄家倍率 = 8
_00_TianjinMahjong_noDealer apply_gang_scores 中庄家倍率 = 1(去掉了庄家翻倍)

核心数据结构

TianjinMahjong 类
├── Player players[4]        — 四名玩家
│   ├── hand_counts[34]      — 手牌计数 (每种牌 0~4 张)
│   ├── hand_total           — 手牌总数
│   ├── melds[]              — 副露 (碰/杠)
│   ├── last_drawn_card      — 最后摸的牌
│   └── score                — 累计分数
├── deck[136]                — 牌墙 (4×34=136张)
├── wall_idx / wall_end_idx  — 牌墙指针
├── hun_cards[2]             — 两张混牌
├── is_hun[34]               — 混牌快速查询
└── state                    — 游戏状态
    └── WAIT_ACTION / WAIT_RESPONSE / GAME_OVER

牌编码

范围 花色 示例
0~8 万 (Wan) 0=一万, 4=五万
9~17 筒/饼 (Bing) 9=一筒, 13=五筒
18~26 条 (Tiao) 18=一条, 22=五条
27~30 风 (东南西北) 27=东, 28=南, 29=西, 30=北
31~33 箭 (中发白) 31=红中, 32=发财, 33=白板

动作编码 (0~105)

区间 动作 说明
0~33 出牌 打出 0~33 对应的牌
34~67 暗杠 暗杠 0~33 对应的牌
68~101 加杠 补杠 (碰后摸到第 4 张)
102 目标牌 = last_discard
103 明杠 目标牌 = last_discard
104 和牌
105 Pass

核心算法

和牌判断 (backtrack_hu):回溯法递归检测 3N+2 牌型,支持混牌替代,先试将牌→刻子→顺子。

算番 (calculate_tianjin_score): 遍历 8 种番种组合 (混吊 hd × 捉五 zw × 龙 l),取最高番数。

启发式策略 (get_heuristic_action):

  1. 能胡必胡
  2. 能杠则杠
  3. 80% 概率碰
  4. 评估手牌每张牌的价值,出价值最低的牌

预期运行结果(编译测试)

// 简单测试 main 函数(未在文件中,但逻辑清晰)
int main() {
    TianjinMahjong env;
    TianjinMahjong::init_rand(42);
    env.start_game();
    // 输出混牌信息和手牌
    printf("混牌: %d, %d\n", env.hun_cards[0], env.hun_cards[1]);
    printf("庄家: %d\n", env.dealer_idx);
    for (int i = 0; i < 34; i++)
        if (env.players[0].hand_counts[i] > 0)
            printf("玩家手牌: %d x%d\n", i, env.players[0].hand_counts[i]);
    // 输出: 随机初始化的牌局,混牌2张,庄家随机,每人13/14张牌
}

编译指令:

g++ -std=c++11 -O3 _00_TianjinMahjong -o _00_TianjinMahjong_test

预期输出: 无 main 函数,需要另外编写测试。但逻辑上可以从 _01_score_calc_check.cpp 看到测试效果。


3. 核心引擎 (C 版本)

文件: game/game.hgame/game.cgame/rule.h

这是用纯 C 实现的第二个版本引擎,与 C++ 版本功能等价。

rule.h 是完整的规则实现(含详细中文注释),game.c 则是对外导出版本,用于 CFFI→Python。

关键差异

对比项 C++ 版 C 版
文件组织 单文件(声明+实现) 拆分为 .h + .c
Python 绑定 Pybind11 CFFI
性能 较高 相同
数组管理 std::vector/array 定长 C 数组 + count 变量

测试程序

game/game_rule_test.c — 规则状态一致性测试:

  • 断言检查:手牌计数 ≤ 4、手牌总数匹配、副露数量 ≤ 4
  • 预期输出: 运行通过无输出,或输出测试名 + "PASS"

game/score_calc_test.c — 算番单元测试:

  • 8 个测试用例:素平胡、素捉五、素一条龙、本混龙、混吊、双混吊、素杠开、暗杠、有混平胡(死胡)
  • 预期输出: 每个测试打印 [TEST n] name → expected=X, got=Y ✓/✗

编译:

gcc -O3 game/score_calc_test.c -o game/score_calc_test -lm
./game/score_calc_test

预期输出示例:

[Test 0] 素平胡 -> 预期 2 番,实际: 2
[Test 1] 素捉五 -> 预期 6 番,实际: 6
[Test 2] 素一条龙 -> 预期 8 番,实际: 8
[Test 3] 本混龙 -> 预期 8 番,实际: 8
[Test 4] 混吊 -> 预期 2 番,实际: 2
[Test 5] 双混吊 -> 预期 2 番,实际: 2
[Test 6] 素杠开 -> 预期 4 番,实际: 4
[Test 7] 有混平胡(死胡) -> 预期 0 番,实际: 0
[Test 8] 乱牌 -> 预期 0 番,实际: 0

4. 算番测试程序

文件: _01_score_calc_check.cpp

C++ 版本的算番单元测试 vs C 版本的 game/score_calc_test.c

测试用例​​(共 9 个):

编号 测试名 牌型 摸牌 混牌 杠开 预期番数
1 素平胡 123 万,123 筒,123 条,567 筒,东东 东风 发财,白板 2
2 素捉五 456 万,123 筒,123 条,567 筒,东东 五万 发财,白板 6
3 素一条龙 1~9 万,123 条,东东 东风 发财,白板 8
4 本混龙 1~9 万(含混),123 条,东东 东风 一万,二万 8
5 混吊 123 万,123 条,123 筒,456 筒,东风,白板(混) 东风 白板,发财 2
6 混吊(反向) 123 万,123 条,123 筒,456 筒,东风,白板(混) 白板 白板,发财 0
7 双混吊 123 条,123 筒,456 筒,东东,红中(混) 东风 发财,红中 2
8 素杠开 同素平胡 东风 发财,白板 4
9 有混平胡(死胡) 123 万(混代 3 万),123 条,123 筒,567 筒,东东 东风 白板,发财 0

编译 & 预期运行:

g++ -std=c++11 -O3 _01_score_calc_check.cpp _00_TianjinMahjong -o _01_score_calc_check
./_01_score_calc_check

预期输出:

============= 天津麻将和牌算番核心逻辑测试 =============
[测试 1] 素平胡 -> 预期 2 番,实际: 2
[测试 2] 素捉五 -> 预期 6 番,实际: 6
[测试 3] 素一条龙 -> 预期 8 番,实际: 8
[测试 4] 本混龙 -> 预期 8 番,实际: 8
[测试 5] 混吊 -> 预期 2 番,实际: 2
[测试 5] 混吊 -> 预期 0 番,实际: 0
[测试 5] 混吊 -> 预期 2 番,实际: 2
[测试 6] 素杠开 -> 预期 4 番,实际: 4
[测试 6] 素杠开 -> 预期 2 番,实际: 2
[测试 7] 有混平胡(死胡) -> 预期 0 番(不足2番不能胡),实际: 0
[测试 8] 乱牌 -> 预期 0 番,实际: 0
[测试 8] 混吊 -> 预期 6 番,实际: 6
============= 所有测试通过!逻辑完全正确 =============

5. Python 绑定 (Pybind11)

文件: _02_generate_lib.cpp_02_generate_lib_noDealer.cpp

使用 Pybind11 将 C++ 引擎导出为 Python 模块,分别对应 _02_TianjinMahjong_02_TianjinMahjong_noDealer

从 C++ 暴露给 Python 的类/结构体:

  • tm.Env — 核心游戏环境类
  • tm.ActionType — 动作枚举
  • tm.GameState — 状态枚举
  • tm.Meld — 副露结构体
  • tm.NNFeatures — 神经网络特征

编译指令:

g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) \
    _02_generate_lib.cpp -o _02_TianjinMahjong$(python3-config --extension-suffix)

预期结果: 生成 _02_TianjinMahjong.cpython-311-x86_64-linux-gnu.so

如果编译成功,Python 可以:

import _02_TianjinMahjong as tm
env = tm.Env()
tm.Env.init_rand(42)
env.start_game()
print(env.get_ui_huns())       # (牌索引, 牌索引)
print(env.get_ui_hand(0))      # [0,0,1,3,...]
print(env.get_heuristic_action(0))  # 0~105

6. Python 绑定 (CFFI)

文件: game/build_game.pygame/_game_engine.c

使用 CFFI 将 C 版本引擎包装为 Python 模块 _game_engine

build_game.py 是构建脚本:

python game/build_game.py

预期输出:

generating ./_game_engine.c
compiling _game_engine.c...
linking...
done

生成 game/_game_engine.cpython-311-x86_64-linux-gnu.so

文件: game/bridge_test.py

CFFI 绑定的连接测试:

python game/bridge_test.py

预期输出:

Current Player: 0
Remaining Cards: 120
Player 0 Hand: [3, 7, 8, 12, 14, 15, 16, 20, 21, 25, 26, 31, 33]  # 示例,随种子变化

7. 库测试程序

文件: _03_lib_test.py

测试 Pybind11 编译好的 .so 文件是否能正常加载和运行:

python _03_lib_test.py

预期输出:

当前活跃玩家: 0
本局混牌是: (12, 13)
玩家手牌列表: [0, 1, 2, 3, 4, 7, 10, 11, 17, 19, 20, 26, 30]  # 示例值
手牌特征(hand): [1, 1, 1, 1, 1, 0, 0, 1, 0, ...]  # 长度34的数组
动作掩码(前10位): [True, True, True, True, True, False, False, True, False, ...]
启发式推荐打出的动作编码: 5  # 示例值,表示打五万

8. 监督学习预训练 (IL)

文件: _04_preTrain_noPPO.py

功能: 模仿学习(Imitation Learning)—— 用启发式策略生成大量对局数据,训练神经网络模型(Actor-Critic 架构)模仿启发式的决策。

流程:

多进程 (5 workers) × 10000 局游戏
  → 每局调用 env.get_heuristic_action() 记录 (状态, 动作) 对
  → 写入 temp/worker_*/cards.bin, histories.bin, masks.bin, actions.bin
  → 合并数据 → ChunkedMahjongDataset (IterableDataset)
  → DataLoader → 训练 TianjinMahjongNet (CrossEntropyLoss)
  → 保存 models/tianjin_mahjong_pretrained.pth

网络架构 (TianjinMahjongNet):

输入: cards(2×34) + histories(3×20×102) + mask(106)
  │
  ├─ ResNet1D(2→64→128) → [手牌特征: 128维]
  ├─ TransformerEncoder ×3(102→128) → [历史特征: 128×3]
  │
  └─ 拼接(128+128*3=512) → MLP(512→256) → 输出
       ├─ Actor(256→106) + Mask  → 动作概率分布
       └─ Critic(256→1)          → 局面估值

预期运行输出:

Using device: cpu
Starting 5 processes to generate data...
[Worker 0] Processing: 1000/2000 games. Total valid samples so far: 15234
[Worker 1] Processing: 1000/2000 games. Total valid samples so far: 14890
[Worker 2] Processing: 1000/2000 games. Total valid samples so far: 15123
[Worker 3] Processing: 1000/2000 games. Total valid samples so far: 14987
[Worker 4] Processing: 1000/2000 games. Total valid samples so far: 15045
[Worker 0] Completed! Total samples generated: 30456
...
Data generation complete! Collected 75000 samples.
Starting pretraining...
  Batch 100, Loss: 2.8912
  Batch 200, Loss: 2.6543
  ...
Epoch 1/10, Average Loss: 2.4512
Epoch 2/10, Average Loss: 2.3012
...
Epoch 10/10, Average Loss: 1.8923
Pretraining completed and model saved.

9. 增强预训练 (大规模/多 GPU)

文件: _06_preTrain_noPPO_powerful.py

_04_preTrain_noPPO.py 功能相同,但针对大规模训练优化:

参数 _04 版本 _06 增强版
对局数 10,000 100,000
进程数 5 14
Batch Size 256 8,192
Chunk Size 20,000 1,000,000
学习率 1e-5 3e-3
Epochs 10 20
GPU 单卡 多卡 (DataParallel)
预训练模型 1 个 每 epoch 保存 1 个 (ep1~ep20)

使用 _02_TianjinMahjong_noDealer(无庄家倍率版本)。

预期运行输出​​(简化):

Using Master Device: cuda
Data insufficient or missing. Starting multiprocess generation...
Starting 14 processes to generate data...
[Worker 0] 1000/7142 games. Total valid samples: 15234
...
Data generation complete! Collected ~1,500,000 samples.
--- Initializing God-Mode DataLoader ---
🔥 Detected 8 GPUs! Enabling nn.DataParallel 🔥
Starting Multi-GPU Pretraining...
  Epoch 1 - Batch 20, Loss: 3.1247
  Epoch 1 - Batch 40, Loss: 2.8912
  ...
Epoch 1/20 Completed, Average Loss: 2.3123
Epoch 2/20 Completed, Average Loss: 2.1012
...
Epoch 20/20 Completed, Average Loss: 1.2345
Pretraining completed perfectly.

10. PPO 强化学习训练

文件: _06_train_withPPO.py

功能: 在监督学习预训练基础上,使用 PPO (Proximal Policy Optimization) 进行 Self-Play 强化学习。

关键技术:

  • 后台缓冲池: 使用 multiprocessing.Pool 在 CPU 上持续收集对战数据,同时 GPU 进行训练
  • GAE (Generalized Advantage Estimation): 计算优势函数,γ=0.99, λ=0.95
  • PPO Clip: ε=0.2,防止策略更新过大
  • 历史模型池: 从 models/ 目录随机抽取旧模型作为对手 (30% 对旧模型)
  • Critic Warmup: 可选先单独训练 Critic 再联合训练

训练流程:

循环 iteration=1..10000:
  1. CPU后台收集: N个worker并行对弈,收集(s, a, r, logprob, value)
  2. GPU训练: PPO更新4个epoch,mini-batch=1024
  3. 每100轮保存一次模型

超参数:

PPO_EPOCHS=4, MINI_BATCH=1024, GAMES_PER_ITER=256
CLIP_COEF=0.2, ENTROPY_COEF=0.0001, VF_COEF=0.5
LR=1e-4 (actor), 1e-3 (critic warmup)
MAX_ITERATIONS=10000, WORKERS=14

预期运行输出:

[cuda] 正在初始化主控模型...
已成功加载预训练模型。
将严格启动 14 个进程并行进行数据收集。
正在进行预热收集:触发第一轮后台收集任务...
Iter 1 [Joint PPO]: 等待 CPU 后台数据就绪...
Iter 1 [Joint PPO]: 数据已获取!触发下一轮 CPU 收集,同时开始 GPU 训练...
Iter 1 结果 => Actor: 0.4523 | Critic: 0.8912 | Entropy: 3.4521

Iter 2 [Joint PPO]: 等待 CPU 后台数据就绪...
...
★★★ 模型已保存至 models/tianjin_mahjong_iter_100.pth ★★★
...
★★★ 模型已保存至 models/tianjin_mahjong_iter_1000.pth ★★★
...

11. 增强 PPO 训练

文件: _06_train_withPPO_powerful.py

_06_train_withPPO.py 逻辑几乎相同,区别在于:

参数 普通版 增强版
预训练模型 cirticWarmup_iter_500.pth iter_600.pth
起始 iteration 1 600
Mini-Batch 1024 8192
Games/Iter 256 512
Critic LR 1e-3 1e-5
PPO ε 1e-5 1e-6

预期输出与普通版类似。


12. PPO 并行向量化训练 (C 引擎版)

文件: PPO_RL/train_RL.py

不同架构的 PPO 训练​​:使用不同的网络模型(更复杂的 MahjongAIModel)和 C 引擎(libmahjong.so)。

关键特点:

  • 64 局游戏完全并行(向量化环境)
  • Batch 推理:将 64 个状态合并为一个 Batch 喂给 GPU
  • 所有玩家使用相同模型
  • 不使用子进程,单进程内 64 个 C 环境指针轮转
  • 使用 CNN + Transformer 的混合网络

网络架构 (MahjongAIModel):

输入: cards(2×34) + hist_p(50) + hist_a(50) + mask(106) + dealer + hun1 + hun2
  │
  ├─ CNN: Conv1d(2→128) ×4 ResBlock → FC(32*34→256)
  ├─ Transformer: Embed(hist) + POS + TransformerEncoder×4 → CLS[128]
  │
  └─ 融合: 256 + 128 + 32×3 = 452 → MLP(1024→512) → Actor(106) + Critic(1)

预期运行输出:

已加载预训练策略网络!
🚀 开始向量化并行训练 (并行度: 64 局) ...

[1] 收集够 256 局,数据量 38124 条,启动 PPO 训练...
更新完成! Loss: 0.8523 (Actor: 0.4521, Critic: 0.7812)
模型已保存!继续收集下一批对局...

[2] 收集够 256 局,数据量 37987 条,启动 PPO 训练...
...
模型已保存!

13. PPO League 训练

文件: PPO_League/train_league.pytrain_ppo.pyenv_wrapper.pymyModel.py

训练方式: 使用 League (联盟) Self-Play,维护一个历史模型池(20 个历史模型),当前模型以 80% 概率对阵最新模型、20% 概率对阵历史模型。

文件分工:

文件 功能
myModel.py 网络结构定义 (同 PPO_RL 的 MahjongAIModel)
env_wrapper.py CFFI 环境封装 (MahjongEnv 类, gym-like API)
train_ppo.py 多进程 PPO 训练 (8 workers × 10 games)
train_league.py League 制 Self-Play 主循环

env_wrapper.py 的功能:

  • MahjongEnv.reset(): 开始新游戏
  • MahjongEnv.step(p_idx, action): 执行动作, 返回 (state, reward, done)
  • MahjongEnv.get_state(): 提取 NN 特征 (cards, histories, mask, dealer, hun 等)
  • 使用 _game_engine (CFFI) 与 C 引擎交互

预期运行输出:

[League] 主进程初始化... device=cuda
[Worker 0] 已启动,开始收集 10 局数据...
[Worker 1] 已启动,开始收集 10 局数据...
...
[更新 #1] 收集完毕,总样本数: 4523 条
   Actor Loss: 0.3421, Value Loss: 0.7821, Entropy: 3.2451
   模型已保存至 models/mahjong_rl_league_1.pth
...
[更新 #50] 模型已保存至 models/mahjong_rl_league_50.pth
...

14. 自我对弈评估 (启发式基线)

文件: _07_evaluate_self_play.py

使用纯 C++ 引擎的启发式策略(get_heuristic_action),让 4 个 AI 对弈 1000 局,统计胜负和得分。

对每个 AI 位置统计:

  • 胜场数 (Wins)
  • 胜率 (Win Rate)
  • 累计得分 (Total Score)
  • 平均得分 (Avg Score)

预期运行输出:

Starting Heuristic Baseline Test for 1000 games...
Played 100/1000 games | Draw Rate: 8.00% | Time: 1.23s
Played 200/1000 games | Draw Rate: 7.50% | Time: 2.45s
...
Played 1000/1000 games | Draw Rate: 7.80% | Time: 12.34s

==================================================
HEURISTIC BASELINE EVALUATION RESULTS
==================================================
Total Games Played  : 1000
Total Time Taken    : 12.34 seconds
Average Steps/Game  : 45.2
Total Draws (荒庄)  : 78
Draw Rate           : 7.80%
--------------------------------------------------
Player 0:
  Wins        : 230 (23.00%)
  Total Score : 1523
  Avg Score   : 1.52
Player 1:
  Wins        : 241 (24.10%)
  Total Score : 1489
  Avg Score   : 1.49
Player 2:
  Wins        : 219 (21.90%)
  Total Score : 1512
  Avg Score   : 1.51
Player 3:
  Wins        : 232 (23.20%)
  Total Score : 1498
  Avg Score   : 1.50
==================================================

注:由于 4 个 AI 使用相同的启发式算法,长期看胜率会趋近于均等 (~23% 每人,~8% 荒庄)。


15. 静态模型评估

文件: _07_evaluate_static_model.py

加载不同训练阶段的 4 个模型,让它们互相比赛,评估训练效果。

模型配置​​(示例):

Player 0: tianjin_mahjong_actor_pretrained.pth  (刚预训练)
Player 1: tianjin_mahjong_iter_100.pth           (100轮PPO)
Player 2: tianjin_mahjong_iter_500.pth           (500轮PPO)
Player 3: tianjin_mahjong_iter_1000.pth          (1000轮PPO)

使用 _02_TianjinMahjong_noDealer 引擎,贪心策略 (argmax) 选择动作。

预期运行输出:

Loading 4 models to cpu...
Starting evaluation for 1000 games...
Played 100/1000 games...
Played 200/1000 games...
...
Played 1000/1000 games...

========================================
EVALUATION RESULTS
========================================
Total Games Played: 1000
Draws (荒庄数): 65 (6.50%)
----------------------------------------
Player 0 (Model: tianjin_mahjong_actor_pretrained.pth):
  Wins:  180 (18.00%)
  Score: 1120
Player 1 (Model: tianjin_mahjong_iter_100.pth):
  Wins:  210 (21.00%)
  Score: 1350
Player 2 (Model: tianjin_mahjong_iter_500.pth):
  Wins:  270 (27.00%)
  Score: 1680
Player 3 (Model: tianjin_mahjong_iter_1000.pth):
  Wins:  275 (27.50%)
  Score: 1720
========================================

关键预期: 训练轮数越多的模型 → 胜率越高,得分越高。说明 PPO 训练有效提升了 AI 水平。


16. Web 对战游戏 (Flask)

文件: WebGame/app.py + WebGame/templates/index.html

Flask Web 应用,人类玩家可以与 AI 在线对战天津麻将。

后端 (app.py):

  • 使用 C 引擎 (libmahjong.so) + ctypes
  • 加载预训练模型: mahjong_rl_ppo_3000.pth
  • API 接口:
    • POST /start — 开始新游戏
    • GET /state — 获取游戏状态 (手牌、弃牌、动作掩码等)
    • POST /action — 人类玩家出牌
    • POST /ai_step — AI 自动走一步
  • 使用相对位置编码 (get_relative_idx)

前端 (index.html):

  • 绿色麻将桌布风格 UI
  • 显示 4 个玩家的手牌(AI 手牌盖住)、弃牌、副露
  • 混牌发光效果
  • 可点击出牌、碰、杠、胡按钮
  • AI 走一步延迟 600ms

启动方式:

pip install flask torch
python WebGame/app.py

使用: 浏览器访问 http://localhost:5000

预期页面效果:

[顶部] 混牌: [一饼发光] [二饼发光]
[AI 3]  副露: [碰三张]  弃牌: [牌图片...]
[AI 2]  副露: [杠四张]  弃牌: [牌图片...]
[AI 1]  副露:            弃牌: [牌图片...]

[信息面板] 剩余牌墙: 85 | 你的回合,请出牌或操作

[我(玩家)]
弃牌: [...]
副露: [...]
手牌: [一万][一万][三筒][四筒][五筒][东风][发财] ... [刚摸的牌]
       [胡] [碰] [过]       ← 操作按钮

17. CGI 版本对战 UI

文件: _05_index.html

较简单的 Web UI,通过 fetch API 与 WebGame/app.py 的后端交互(两者共享代码,但部分接口略有不同)。

关键差异:

  • 使用 /start, /state, /action 三个接口
  • 渲染逻辑在 JavaScript 中
  • 支持暗杠/加杠按钮

预期运行​​:与 WebGame 类似,需要 Flask 后端启动后在浏览器中查看。


18. 早期 noPPO 项目

目录: noPPO_notModifiedSinceRefactor/

这是项目重构前的旧代码,保留作为参考,不被当前管线使用。

文件 功能
train.py 使用 CSV 训练数据的监督学习(pandas + sklearn)
evaluate_1AIvs3ST_noPPO.py 1 个 AI vs 3 个启发式 AI 的评估
evaluate_4AI_noPPO.py 4 个 AI 自我对弈评估
statisic_data_generation.c C 程序生成统计数据?
train_data.csv / val_data.csv 训练/验证数据
mahjong_training_data_flat.csv 平面格式的训练数据
model_noPPO.pth / tianjin_mahjong_basic_ai.pth 旧模型文件

19. 模型文件列表

models/ 目录下包含历次训练的模型:

文件名 说明 大小(约)
tianjin_mahjong_pretrained.pth 监督学习预训练 (第 1 版) ~5MB
tianjin_mahjong_actor_pretrained.pth 预训练 Actor ~5MB
tianjin_mahjong_ep20.pth 增强预训练第 20 轮 ~5MB
tianjin_mahjong_cirticWarmup_iter_100~500.pth Critic 预热模型 (5 个) 各~5MB
tianjin_mahjong_iter_100~2000.pth PPO 训练各阶段 (11 个) 各~5MB

noPPO_notModifiedSinceRefactor/ 下的旧模型: | model_noPPO.pth | 旧监督学习模型 | ~5MB | | tianjin_mahjong_basic_ai.pth | 旧版本基线模型 | ~5MB |


20. 训练蓝图总结

                              数据流方向 →
================================================================
 阶段0: C++/C 麻将引擎
        规则实现 (+ 启发式AI)
        │
 阶段1: Pybind11/CFFI → Python 绑定
        │
 阶段2: 多进程生成启发式对局数据
        │  _04: 10,000局 × 5 workers
        │  _06_powerful: 100,000局 × 14 workers
        │
        ▼
 阶段3: 监督学习 (模仿学习)
        │  CrossEntropyLoss
        │  网络输出 → 模仿启发式策略
        │
        ▼
 阶段4: PPO Self-Play 强化学习
        │  后台数据收集 + GPU 训练
        │  M = 10000 iter, GAE, PPO-Clip
        │  (可选择先 Critic Warmup)
        │
        ▼
 阶段5: 模型评估
        │  _07_evaluate_self_play: 启发式基线
        │  _07_evaluate_static_model: 不同阶段模型对战
        │
        ▼
 阶段6: Web 在线对战
        Flask + C 引擎 + Pytorch 模型

 并行分支: PPO_RL (C引擎 + ctypes + 向量化64局)
          PPO_League (CFFI + 联盟制 Self-Play)
================================================================

后续工作

虽然上面的冤枉是好的,但在非监督学习阶段,模型效果显而易见的开始倒退。遇到了训练不发散的问题,具体解决方法还不清楚,以后再来研究吧。(叹气

AI 助手
AI
你好!我可以根据当前文章内容回答你的问题。