采用全连接前馈神经网络实现-叫地主AI

在学习《神经网络与深度学习》的第一、二章之后,整理了学习笔记,有兴趣可以看看链结如下。

玩乐儿童编程:《神经⽹络与深度学习》-自学笔记01

光看别人写的代码是不能真正学会的,所以决定自己尝试一下,用学到的知识解决遇到的问题。在斗地主游戏中,是否叫地主,在很大程度上决定了最后的胜负,能不能用前馈神经网络来模拟真人叫地主的过程呢?顺着这个思路,玩家在发完牌之后,得到17张手牌。可以把手牌排序后想象成一张图片,每张手牌对应一个输入,而通过神经网络后的输出则是叫地主的分数

游戏逻辑是,起手每个玩家拿到17张牌时,然后从庄家(随机庄家或上局地主)开始,依次叫分。分值为0、1、2、3,分数从小到大代表抢地主的意愿,0分表示不叫地主,3分表示必叫地主。如果有玩家叫3分则后续玩家不再叫分。注意,没有拿到3张2或王就必须叫,或者大家都不叫就由庄家开始的规则。也就是说,第一个叫地主的玩家用于决策的信息就只有17张手牌。

采用全连接前馈神经网络实现“庄家叫地主AI”。庄家是每个牌局中第一个叫地主的,掌握的信息仅有17张手牌。准备使用真人斗地主对局数据,训练神经网络实现叫地主AI。之所以只模拟庄家,是因为第二、三家叫地主的时候,还要参考前面玩家的叫地主情况,可以从一定程度上根据前面玩家叫地主的情况,推断出自己手牌的相对好坏。所以第二、三家不是纯粹以17张手牌作为输入决策是否叫地主,这里暂不考虑。


训练数据的包括:

  • 输入样本数据:17张手牌
  • 期望输出:玩家叫地主的分数

一、准备训练数据

使用真人斗地主对局数据源,提取叫分手牌(17张)、庄家身份、地主身份、叫地主分数以及输赢结果等原始数据来生成训练数据。不同于图像识别,训练数据的期望输出99%都是确定的。很多情况下,叫地主AI训练数据的期望输出并没有明显的对错,因为底牌、其他玩家的手牌都是未知数,存在一定的随机性(说白了就是运气)。那么,从原始数据中生成合理的期望值就有很多种选择。

1.训练数据生成预期值的逻辑(一)

第一家叫地主
叫0分
胜,可能是自己牌好,或者友方牌好,或者地主牌不够好。(将期望结果改为1分,作为训练数据)
负,较大可能确实牌不好,叫0分避免了输更多,是正确的选择。(以0分,作为训练数据)

叫1分
胜,说明选择正确,自己牌够好(以1分,作为训练数据)
负,说明选择不正确,自己牌不够好,或者对手牌太好。(将期望结果改为0分,作为训练数据)

叫2分
胜,说明选择正确,自己牌较好(以2分,作为训练数据)
负,说明选择不正确,自己牌还不够好,或者对手牌太好。(将期望结果改为1分,作为训练数据)

叫3分
胜,说明选择正确,自己牌非常好(以3分,作为训练数据)
负,说明选择不正确,自己牌还不够好,或者对手牌太好。(将期望结果改为2分,作为训练数据)

2.训练数据生成预期值的逻辑(二)

选取打满30局数的,并且综合胜率达到50%的用户的牌局。
第一家叫0分,期望值为0
第一家叫1、2分,如果当地主并且获胜,期望值为1;否则为0
第一家叫3分,期望值为1

3.训练数据生成预期值的逻辑(三)

直接将第一家用户的叫分作为期望值,以此逻辑产生的测试数据

4. 训练数据生成预期值的逻辑(四)

第一家,叫0分,就将0分作为预期值
第一家叫1分,当地主且获胜,就将1分作为预期值,否则还是以0分作为预期值
第一家叫2分,当地主且获胜,就将2分作为预期值,否则还是以0分作为预期值
第一家叫3分,就将3分作为预期值

5. 训练数据生成预期值的逻辑(五)

第一家,叫0分,就将0分作为预期值
第一家叫1分,当地主且获胜,就将3分作为预期值,否则还是以0分作为预期值
第一家叫2分,当地主且获胜,就将3分作为预期值,否则还是以0分作为预期值
第一家叫3分,就将3分作为预期值

为了简单起见,开头首先使用逻辑(三)生成训练数据。

二、准备测试数据

测试数据也采用相同的方式提取,只是原始数据与训练数据不同。

三、设计神经网络

网络各层神经元数量为17、30、2。

  • 输入层:17个神经元对应起手的17张牌
  • 隐藏层:30个神经元,可能需要根据后续训练结果进入评估,先暂定30
  • 输出层:4个神经元,分别0、1、2、3叫地主分

代价函数为:二次代价函数

训练周期为50个,学习速率为3,小批量样本数量10(根据实际情况调整)

四、开始训练

采用不同预期值生成方式获得的正确率

  • 训练数据生成预期值的逻辑(三):60%
  • 训练数据生成预期值的逻辑(四):73%
  • 训练数据生成预期值的逻辑(五):75%
  • 训练数据生成预期值的逻辑(二):85%
从预期值的选择发现训练数据对神经网络的影响是非常之大,神经网络的正确率与训练数据本身正确率有密切的关系。

经过一番尝试,目前比较合理的参数是,采用[17,20,4]的神经网络,隐藏层20个神经元。学习速率1,小批量样本数量20,能获得85%左右的正确率。

>>> net=network.Network([17,20,4])
>>> net.SGD(trainingdata, 40, 20, 1, test_data=testdata)
Epoch 0 : 6525 / 8564 = 0.7619103222793088
Epoch 1 : 7077 / 8564 = 0.826366184026156
Epoch 2 : 6651 / 8564 = 0.7766230733302195
Epoch 3 : 7136 / 8564 = 0.8332554880896778
Epoch 4 : 7017 / 8564 = 0.8193601120971509
Epoch 5 : 7059 / 8564 = 0.8242643624474545
Epoch 6 : 6876 / 8564 = 0.8028958430639888
Epoch 7 : 7037 / 8564 = 0.8216954694068193
Epoch 8 : 7031 / 8564 = 0.8209948622139187
Epoch 9 : 7167 / 8564 = 0.8368752919196637
Epoch 10 : 7082 / 8564 = 0.8269500233535731
Epoch 11 : 7092 / 8564 = 0.8281177020084073
Epoch 12 : 7069 / 8564 = 0.8254320411022886
Epoch 13 : 7180 / 8564 = 0.8383932741709481
Epoch 14 : 7070 / 8564 = 0.825548808967772
Epoch 15 : 7078 / 8564 = 0.8264829518916395
Epoch 16 : 7039 / 8564 = 0.8219290051377861
Epoch 17 : 7080 / 8564 = 0.8267164876226063
Epoch 18 : 6971 / 8564 = 0.8139887902849136
Epoch 19 : 7085 / 8564 = 0.8273003269500233
Epoch 20 : 7207 / 8564 = 0.8415460065390005
Epoch 21 : 7139 / 8564 = 0.833605791686128
Epoch 22 : 7093 / 8564 = 0.8282344698738907
Epoch 23 : 7132 / 8564 = 0.832788416627744
Epoch 24 : 7077 / 8564 = 0.826366184026156
Epoch 25 : 7054 / 8564 = 0.8236805231200374
Epoch 26 : 7078 / 8564 = 0.8264829518916395
Epoch 27 : 7229 / 8564 = 0.8441148995796357
Epoch 28 : 7233 / 8564 = 0.8445819710415694
Epoch 29 : 7165 / 8564 = 0.8366417561886969
Epoch 30 : 7235 / 8564 = 0.8448155067725363
Epoch 31 : 7240 / 8564 = 0.8453993460999533
Epoch 32 : 7235 / 8564 = 0.8448155067725363
Epoch 33 : 7197 / 8564 = 0.8403783278841663
Epoch 34 : 7235 / 8564 = 0.8448155067725363
Epoch 35 : 7223 / 8564 = 0.8434142923867352
Epoch 36 : 7226 / 8564 = 0.8437645959831854
Epoch 37 : 7246 / 8564 = 0.8460999532928538
Epoch 38 : 7167 / 8564 = 0.8368752919196637
Epoch 39 : 7241 / 8564 = 0.8455161139654367

五、开始测试

采用随机生成手牌的方式,测试100次的数据如下。可以看出,作为第一家叫地主,基本上只有0和3两个选择。这也符合博弈策略,第一家叫地主如果手牌确实好就叫3分,如果手牌不够好就叫0分。因为如果手牌不够好叫1分或者2分,有可能被第二、三家做套给坑了。

334566678999JKKXD
3
3345578SQQKKAAA2X
3
334456677788SJQA2
0
4566889SSSJQQQQA2
0
3446778999SSJJQKA
0
35666889SQQQKAA22
0
34458889SJJJQA2XD
3
345677789SJJQQA22
0
335556779SJQQQA22
0
344557779SSJKK22X
3
344556689SSJJAA22
0
3344556688SQQQKA2
0
34557789SSJKKAA2X
0
33445677889SJQKKA
0
34578889SSJQQQKKA
0
336667899SSJJQKAX
0
33466778999SSSJQD
0
345677899SJJJQAA2
0
334568899SSJKKAA2
0
45678889JJQQKAA2X
3
345578899SSJJKAA2
0
3446677899SSJQQA2
0
3344566889QQKAA2D
3
45566789SSSJQKK22
0
344467899SSSAA222
0
4566777889SSJQKA2
0
34567788899SSJQKA
0
444556667789JQAA2
0
34556789SSJQQQK22
0
333456789SJJQQQKA
0
56778899SSJQQKA22
0
3445567789JJQKA2D
3
33445667889SSQQKK
0
456678899SSJKA222
0
344566777899QKK2D
3
467789SSJJQKKKKA2
0
35566789SSJJJQKAA
0
4445666777SSQKK2D
3
347778899JJQKKAA2
0
5556999SSJQKKAAA2
0
4555567SJQQQA222X
3
33446667789SSJQKA
0
3445556689JQKK22D
3
345678899SSJQK222
0
34455689SSJQKA2XD
3
34566699SJJQKKA2D
3
3455577899SSQKA2D
3
34566778SSJQKKAAA
0
334555667889JQQK2
0
3455667788SJJQQAA
0
33455677889SSKAAD
0
44577999SSQQKAA22
0
3444566688899SQQ2
0
556677788SSSJJQ2X
0
444588999SJJQKKAA
0
34445666779SJKA2X
0
44577899SJJQKAAA2
0
4455566689999SJQK
0
3567788999QQKA22X
3
44667779SSJJQQKA2
0
4567789SSJJQKAA2D
3
3457888999JJQKA2X
0
333346677999SSJAD
0
4677889SSJQKA222D
3
33455667889SJQKAA
0
333566788QKKKAAA2
0
44666899SSJKA22XD
3
4666778899SQQKA2X
0
44667788SJQQKAAAD
3
3466777SSJKKAA222
3
344467788SSJQAA2X
0
3455777888SJQQQQA
0
3344455778SQKAA2X
0
334567789SJJQQKAA
0
3445569SJJJQQAA22
0
335566779SSJQKA22
0
334455677999SJJAX
0
44568889SSJJQQAA2
0
4566799SJQKKAA22X
3
356699SSJJQQKAA2X
0
3344555668889QK22
0
45567899SJJQKKA2D
3
356667999JJKKAA2X
0
444556678SJQQQKKA
0
34555667789SJQQ22
0
344555678899SKA22
0
344566778SSAAA22X
3
3345588SSJQQK22XD
3
34556679SSSJKK22X
3
34666778889SSQKAX
0
334555678SJQKKAAA
0
3355556799SSJQAAD
0
455566688899SJQ2D
3
44455667889SJJQKA
0
334567899SSJJKKA2
0
334567789SJQKA2XD
3
344557899SSSSQKKX
0
35666778SSJQKA22D
3
3345778SSSJQKA22D
3

六、相关python代码

使用《神经⽹络与深度学习》中的network.py为基础,增加了一些数据格式转换和打印输出。

import network
import trans
import numpy as np
import test

trainingdata = trans.trans_data("../traindata/td30w")
testdata = trans.trans_data("../traindata/test")
net=network.Network([17,20,4])
net.SGD(trainingdata, 40, 20, 1, test_data=testdata)

生成随机手牌逻辑

def shuffleTest():
    copyCards = origCards[:]
    # start shuffle
    for idx in range(len(origCards)):
        randIdx = np.random.randint(0,54)
        if idx != randIdx:
            copyCards[idx], copyCards[randIdx] = copyCards[randIdx], copyCards[idx]
    player01Cards = copyCards[:17]
    player01Cards.sort()
    cardStr = genStrByCardValue(player01Cards)
    print(cardStr)
    cardRes = genIdxByCardValue(player01Cards)
    for idx in range(len(cardRes)):
        cardRes[idx] = cardRes[idx]/14
    return np.reshape(cardRes,(17,1))

随机生成100副手牌测试

for i in range(1,100):
    td = test.shuffleTest(); print(np.argmax(net.feedforward(td)))

七、叫分AI效果分析

采用随机生成手牌的方式,调用叫地主AI进行叫分,从感观上看,基本掌握了叫地主的逻辑,但是正确率一直在85%左右。分析几个可能的原因:

  1. 训练数据本身不是绝对正确的,有些叫分可能是因为底牌好赢了,有些叫分本来牌很好可能是因为底牌烂输了,还受另外两家农民手牌情况的影响。
  2. 神经网络采用的技术简单,训练样本和测试样本数量有限

下一步优化方向:

  1. 提高训练数据集的正确率,可以筛选更高胜率的用户作为某种意义上的高手。用高手的数据来训练网络。
  2. 采用更多神经网络技术优化网络本身。

后续继续研习《神经网络与深度学习》希望能做到正确率90%以上的叫地主AI。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值