棋牌AI(RL-Card)

RL-Card

起因:最初由于斗地主需要引入AI使用,由于不是由我负责的,也没太了解过这方面东西。

后来做麻将的时候需要进一步优化AI逻辑,需要做一些优化尝试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
2024.01.23
======================================

麻将第三版本优化:
采用机器强化学习方式

RL-Card:

1. pth文件如何在go中调用 使用C++打开pth文件,Go嵌入C++代码实现
2. pth文件数据如何使用
3. 对应规则生成pth文件

2024.01.30
======================================

麻将AI优化思路:

1. 策略细分; 效果不太好客观分辨,此后不做修改
2. 麻将查表向听分析;
3. 本地存储出牌表数据,由用户操作进行更新
4. RL-Card 生成数据

2024.02.18
======================================

复工

RL-Card
mahjong state

1. table 桌子上已出的牌
2. current_hand 当前手牌
3. players_pile 玩家吃碰杠牌
4. action_cards 操作牌


1. ProjectId
2. ETCD 匹配开关
3.


curl -H 'content-type:application/json' -X POST -d "{\"Param\":{\"ID\":60771863,\"Desc\":\"test\",\"PropChanges\":[{\"PropID\":2,\"AfterCount\":108,\"ChangeCount\":10}]}}" "http://127.0.0.1:8888"


{\"hand\":[{\"bamboo\":1},{\"bamboo\":2},{\"characters\":3},{\"characters\":9},{\"characters\":9}],\"piles\":[[[{\"bamboo\":5},{\"bamboo\":5},{\"bamboo\":5}]]],\"tables\":[{\"characters\":5},{\"characters\":5},{\"characters\":5}]}


curl -H 'content-type:application/json' -X POST -d "{\"hand\":[\"bamboo-1\",\"bamboo-2\",\"bamboo-3\",\"characters-9\",\"characters-9\"],\"piles\":[[\"bamboo-5\",\"bamboo-5\",\"bamboo-5\"],[],[],[]],\"table\":[\"characters-5\",\"characters-5\",\"characters-5\"]}" "http://192.168.1.106:8888"


run_rl.py --env mahjong --num_episodes 10000 --log_dir experiments/mahjong_dqn_result_10000/ --load_checkpoint_path experiments/mahjong_dqn_result_10000/checkpoint_dqn.pt


训练过程:

1. 5000
2. 10000
3. 20000

set GOOS=linux
set GOARCH=amd64

以上是较早版本的优化思路

  1. 仍旧是对源代码中的出牌策略进行细分调整,优化过程中发现,优化的策略参数调整往往达不到满意的程度(各种对局情况下,出牌效果不稳定,会出现这局这样优化是好的,下一局使用这个策略算法却是负优化)于是,在调整一段时间后,确定一个相对稳定的版本,便不再在此思路上费功夫。
  2. 麻将向听前进算法,思路源于麻将胡牌中最早接触的日式查表法。想着是不是可以计算需要几手才能胡,于是查找相关资料,确有人这么尝试过。理论上来讲,此算法若是稳定的话,时间复杂度上会好很多。结果是不稳定,具体过程不太记得了,就此略过。
  3. 思路参考于:机器自学习。想着是不是可以实时获取用户出牌数据,动态调整出牌数据库。止步于理论思考阶段,并未真正实现此逻辑(数据量会与日俱增,用户数据不可控,单一从胜率来看,数据浮动太大)
  4. RL-Card代码训练模型,然后加载模型提供接口,程序中调用。

关于RL-Card最早了解于项目中斗地主的AI,源于斗零(DouZero)快手出品。

后续了解到RL-Card棋牌AI训练。

最初的时候使用run-rl.py 执行1万次,训练出模型加载代入已有麻将使用,效果感觉和版本1的策略细分差不多,甚至有时候还不如,后续因为别的工作安排便暂停了这方面研究。

最近又有大把时间可以研究下其他的,于是又开始学习线性代数(学的头大,不想学了)、了解大模型知识(也挺头大,专业名次太多)、刷题(刷不动,只好跟着官解一步步写程序逻辑)、跑RL-Card程序,如下记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2025.07.03
===================================
RL-Card

run_rl.py --env mahjong --cuda 0 --num_episodes 100 --log_dir experiments/mahjong_dqn_result_1000000/ --load_checkpoint_path experiments/mahjong_dqn_result_5000/checkpoint_dqn.pt


2025.07.10
===================================
系统更新重启 导致程序中断 功亏一篑

run_rl.py --env mahjong --cuda 0 --num_episodes 100000 --log_dir experiments/mahjong_dqn_result_1000000/ --load_checkpoint_path experiments/mahjong_dqn_result_5000/checkpoint_dqn.pt

使用加载模型参数 二次训练方式
单次跑10万次
累计10次达到百万训练次数

以下详细介绍一下过程:

  1. 运行run_rl.py程序;

    1
    2
    3
    4
    /**使用python3执行
    查看报错,安装对应的引用包程序
    执行命令,大概这样子*/
    run_rl.py --env mahjong --num_episodes 10000 --log_dir experiments/mahjong_dqn_result_10000/
  2. 加载模型,实现python接口(以下代码依赖RL-Card代码);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80


    from http.server import HTTPServer, BaseHTTPRequestHandler
    from examples.experiments.mahjong_dqn_result.mahjong import MahjongModel as Model
    from rlcard.games.mahjong.card import MahjongCard as Card

    # hand = []
    # hand.append(Card('bamboo','1'))
    # hand.append(Card('bamboo','2'))
    # hand.append(Card('characters','3'))
    # # hand.append(Card('bamboo','4'))
    # # hand.append(Card('bamboo','5'))
    # # hand.append(Card('bamboo','6'))
    # # hand.append(Card('bamboo','7'))
    # # hand.append(Card('bamboo','8'))
    # # hand.append(Card('bamboo','9'))
    # # hand.append(Card('bamboo','1'))
    # # hand.append(Card('bamboo','2'))
    # # hand.append(Card('bamboo','3'))
    # hand.append(Card('characters','9'))
    # hand.append(Card('characters','9'))

    # pile = []
    # pile.append(Card('bamboo','5'))
    # pile.append(Card('bamboo','5'))
    # pile.append(Card('bamboo','5'))

    # piles = [[pile],[],[],[]]

    # table = []
    # table.append(Card('characters','5'))
    # table.append(Card('characters','5'))
    # table.append(Card('characters','5'))

    # action = Model().eval(hand,piles,table)
    # print("action:",action)




    class Request(BaseHTTPRequestHandler):
    #通过类继承,新定义类
    timeout = 5
    server_version = 'Apache'

    def do_GET(self):
    #在新类中定义get的内容(当客户端向该服务端使用get请求时,本服务端将如下运行)
    self.send_response(200)
    self.send_header("type","get") #设置响应头,可省略或设置多个
    self.end_headers()

    msg = 123 #要返回给客户端的信息
    msg = str(msg).encode() #转为str再转为byte格式

    self.wfile.write(msg) #将byte格式的信息返回给客户端

    def do_POST(self):
    #在新类中定义post的内容(当客户端向该服务端使用post请求时,本服务端将如下运行)
    data = self.rfile.read(int(self.headers['content-length'])) #获取从客户端传入的参数(byte格式)
    json_data = data.decode() #将byte格式转为str格式
    print(type(json_data),json_data)

    model = Model()
    hand, piles, table = model.decode_str(json_data)
    action = model.eval(hand,piles,table)
    action_str = model.action2str(action)
    print("action",action,action_str)

    self.send_response(200)
    self.send_header("type","post") #设置响应头,可省略或设置多个
    self.end_headers()

    msg = action_str
    msg = str(msg).encode() #转为str再转为byte格式
    self.wfile.write(msg) #将byte格式的信息返回给客户端


    host = ('0.0.0.0',8888) #设定地址与端口号,'localhost'等价于'127.0.0.1'
    server = HTTPServer(host, Request) #根据地址端口号和新定义的类,创建服务器实例
    server.serve_forever() #开启服务
    1
    2
    // 测试接口
    curl -H 'content-type:application/json' -X POST -d "{\"hand\":[\"bamboo-1\",\"bamboo-2\",\"bamboo-3\",\"characters-9\",\"characters-9\"],\"piles\":[[\"bamboo-5\",\"bamboo-5\",\"bamboo-5\"],[],[],[]],\"table\":[\"characters-5\",\"characters-5\",\"characters-5\"]}" "http://192.168.1.106:8888"
  3. cuda模式全面启用;

1
2
3
4
5
6
7
8
9
10
11
12
1. Nvidia更新驱动程序 https://www.nvidia.cn/geforce/drivers/
2. 在命令行中输入【nvidia-smi】可以当前显卡驱动版本和cuda版本
Driver Version:设备版本
CUDA Version:CUDA能安装的最高版本
根据显示,电脑的显卡配置为NVIDIA,显卡驱动版本为:Driver Version: 527.47,CUDA 的版本为:CUDA Version 12.0。因此能安装的Cuda最高版本是12.0,也可以安装12.0以下版本。
3. CUDA版本选择
根据显卡驱动 和 CUDA版本对应关系选择 CUDA
文档地址:https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-driver
4. CUDA版本下载
下载地址:https://developer.nvidia.com/cuda-toolkit-archive
5. 命令行输入 nvcc --version 查看安装版本

  1. 加参数 -cuda 0 启动GPU加速
1
run_rl.py --env mahjong --cuda 0 --log_dir experiments/mahjong_dqn_result_3/ --save_every 1000

​ 发现速度较慢,nvidia-smi dmon 命令实时查看sm(GPU占用率)一直在10%左右波动。

​ 证明:未充分释放GPU性能。

​ 解决办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
1. 原因:没有安装torchvision 猜测CPU和GPU无法正常协作运行
安装后,GPU利用率有提升,但波动幅度变大

一、确保Pytorch与TorchVision是CUDA(GPU)版本
参考链接:【Xiang哥避坑指南】YOLOV5只在CPU跑不在GPU跑的问题。
在Python终端下操作:
1、Pytorch

import torch
print(torch.__version__)
#上方的_是两个 杠杠
AI写代码
python
运行
1
2
3
2、TorchVision

import torchvision
print(torchvision.__version__)
#上方的_是两个 杠杠
AI写代码
python
运行
1
2
3
输出的结果是+cu就是CUDA(GPU)
输出的结果是+cpu就是CPU
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_45831414/article/details/135556280

代码中添加如下设置:

1
2
3
4
5
6
7
8
9

from torch.utils.data import DataLoader

# 启用多进程并行读取数据
DataLoader.num_workers=8
# 设置共享内存 pin_memory
DataLoader.pin_memory=True
DataLoader.prefetch_factor=2
DataLoader.batch_size=32

最终使用命令训练(Gpu初始利用率在50左右浮动,后续在50、60、70波动)

1
/d/soft/python36/python run_rl.py --env mahjong --cuda 0 --num_episodes 1000000 --log_dir experiments/mahjong_dqn_result_4/ --evaluate_every 10000 --save_every 1000