写完了斗地主的AI之后,我就开始着手写一个麻将的人工智能算法。使用了20万条数据进行训练之后,发现机器人水平已经明显比普通人的胜率要高。下面我详细介绍一个这个方案,如果有不妥之处,请与我联系。
这个方案提供一种机器学习的新思路,使模型可以根据当前游戏的情况,进行智能判断更加准确的打出哪张牌,使手上的牌可以更快的胡牌。
为此,本方案提供了一种麻将游戏的机器学习实现方法。所述方法包括:
- 如何设计模型数据与标签。
- 如何建立模型。
- 如何训练模型。
在开始设计模型数据之前,有必要先介绍一下麻将的玩法,理解玩法之后,对于理解数据的设计有很大帮助。
麻将介绍
麻将是一种易学难精的一种游戏,麻将的玩法因各地方不同而各有不同,这里介绍的是一种广东地区的一种癞子玩法,它需要4个人一起打,每个人开始的时候都有13张牌。然后逆时针由庄家开始先摸一张牌,庄家要么胡牌,要么从手上打出一张牌。庄家打出一张牌后,其它玩家可以选择碰牌,如果玩家都不碰牌那么就轮到下一个玩家摸牌。
麻将牌:
麻将牌共分为条子,万子,筒子,字牌4类。
条子分别是1条,2条,3条,4条,5条,6条,7条,8条,9条
万子分别是1万,2万,3万,4万,5万,6万,7万,8万,9万
筒子分别是1筒,2筒,3筒,4筒,5筒,6筒,7筒,8筒,9筒
字牌分别是东,南,西,北,中,发,白; 注意这里红中,白板为癞子
以上每一种牌都有4张。其中条子9 * 4 = 36张, 万子9 * 4 = 36张, 筒子 9 * 4 = 36张 以及字牌7 * 4 = 28张,一共36 + 36 + 36 + 28 = 136张
条子9种牌,万子9种牌,筒子9种牌,字牌7种牌,总共9+9+9+7 = 34种不同的牌。
分别用0~33表示每一种牌。
0,1,2,3,4,5,6,7,8分别表示:
1筒,2筒,3筒,4筒,5筒,6筒,7筒,8筒,9筒
9,10,11,12,13,14,15,16,17分别表示:
1条,2条,3条,4条,5条,6条,7条,8条,9条
18,19,20,21,22,23,24,25,26分别表示:
1万,2万,3万,4万,5万,6万,7万,8万,9万
27,28,29,30,31,32,33分别表示:
东,南,西,北,中,发,白
例如0表示1筒,9表示1条,18表示1万,27表示东,
胡牌
当玩家把手上的牌形成m * ABC + n*AAA + DD 的形式,就是胡牌。
其中ABC表示顺子,条子,万子,筒子这3条牌相连的3张牌,称为顺子。例如1条,2条,3条,形成顺子。 4筒,5筒,6筒形成顺子。但是8万,9万,1万,不是顺子,东,南,西,也不是顺子。
其中AAA表示坎,相同的3张牌形成坎。例如1万,1万,1万是坎;西,西,西是坎。
其中DD表示一对,相同的2张牌就是一对。例如2条,2条。
其中m与n 表示个数,可以有任意的顺子个数,任意的坎个数。
以下牌形就是胡牌
1条,2条,3条,7条,8条,9条,4筒,5筒,6筒,1万,2万,3万, 9万,9万.
这里有4个顺子,没有坎,9万,9万形成一对。
以下牌形也是胡牌
1条,1条,1条,3条,3条,3条,5条,5条,5条,7筒,7筒,7筒,东,东。
这里没有顺子,有4个坎,东,东形成一对。
以下牌形也是胡牌
1条,2条,3条,5条,5条,5条,7条,8条,9条,7筒,7筒,7筒,8筒,8筒
这里有2个顺子,1,2,3条以及7,8,9条;有2个坎5条,5条,5条,7筒,7筒,7筒
两个8筒形成了一对。
癞子
癞子也叫鬼牌;癞子是指可以变成任意牌的牌,这里我们指定红中,白板为癞子,手上有这两种牌时,它们可以变成任意的牌。有了癞子可以更方便凑成胡牌。
例如
1条,2条,3条,7条,8条,9条,4筒,6筒,1万,2万,3万, 9万,9万,中
可以胡牌,这里面的“中”可以变成5筒形成胡牌。
听牌
玩家手上只需要再加上一张牌就形成胡牌,这种牌型,我们称之为听牌。
例如1条,2条,3条,7条,8条,9条,4筒,5筒,1万,2万,3万, 9万,9万
是听牌,正在听3筒和6筒。
设计模型数据与标签
我们建立了一个称之为FoxThinker的神经网络系统,可以根据手中的牌形,来选择打出哪张牌后,可以更快的形成胡牌牌形。
在麻将数据中,我们设定了一个N * 70的数据,其中N表示数据的条数,我们训练的时候,取N为20万,也即是我们使用了20万条数据进行训练。
这里列出了10条训练数据,表示在某种场合下,应该打什么牌。
其中前34位表示手上有什么牌,第35位~68位表示哪些牌为癞子,第69位表示应该打出什么牌,第70位表示,打出牌之后听几张牌。
我们以第一行的数据来具体解释每一位的含义。
0,0,1,0,0,0,0,1,1,0,0,0,0,2,0,1,0,0,1,0,1,0,0,1,0,0,1,0,1,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,28,0
前34位表示手上有什么牌例如
0,0,1,0,0,0,0,1,1,0,0,0,0,2,0,1,0,0,1,0,1,0,0,1,0,0,1,0,1,1,1,0,0,1
共34位,每一位代表一种牌的数量。
从左到右分别代表1筒,2筒, 3筒, 4筒, 5筒, 6筒, 7筒, 8筒,9筒, 1条,2条, 3条, 4条, 5条, 6条, 7条, 8条,9条, 1万,2万,3万,4万,5万,6万,7万,8万,9万, 东,南,西,北,中,发,白。
0,0,1,0,0,0,0,1,1,0,0,0,0,2,0,1,0,0,1,0,1,0,0,1,0,0,1,0,1,1,1,0,0,1
这34位中,第1位为0表示没有1筒,第2位为0表示没有2筒,第3位为1表示有一个3筒. 如此类推,上面这串数字代表的牌型为:
第35到68位用于表示什么牌将会作为癞子。
这里0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1
只有2个位置为1, 代表红中与白板为癞子。
第69位表示玩家打出什么牌,这里是28, 28表示南。
第70位表示,打出南之后,听几张牌,这里是0,表示听0张牌。也即没有听牌。
这里再举一个例子:
0,1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,1,0,0,0,1,1,1,3,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,18,4
表示手牌为:
打出18, 18表示1万。
打出1万之后,可以听4张以上的牌,听的牌分别是1筒,2筒,3筒,4条,北。
听牌个数中,0表示没听牌,1表示听1张牌,2表示听2张牌,3表示听3张牌,4表示听4张及以上的牌。
值得注意的是,红中白板是癞子,它可以变成任意一张牌。
数据输入层,我们使用68位数组,分别表示手牌是什么,癞子是什么。
69位是标签,表示应该打什么牌。
70位也是标签,表示打出牌之后,可以听多少张牌。
建立模型
我们使用了20 万条游戏数据,从中抽取出胜利玩家的打牌习惯,输入到神经网络中,进行训练。然后把训练好的模型来预测手上有某些牌型时,应该打出什么牌。
模型的流程如下图所示:
上面模型,我们采用了Tensorflow 2.0的版本进行训练,代码参考如下图所示
训练模型
我们采用了监督学习的方法,让网络训练20万条游戏数据。
FoxThinker里面有2个模型,一个是选牌模型,另一个是听牌模型。结合两者的结果,最终决定打出什么牌。
第一层网络层,我们采用了1024个神经元,采用了Tanh激活函数。
第二层网络层, 我们采用了1024个神经元,采用了relu激活函数。
第三层网络层,我们采用了128个神经元,采用了Tanh激活函数。
第四层全连接层,我们采用了34位输出,用于表示从1筒到白板34种牌中,打出牌的概率。
经过测试我们发现1024个神经元是最为稳定的,当神经元过少的时候,训练速度比较快,但是预测的准确率比较低。当神经元比较多的时候,例如2048, 训练速度非常的慢,并且预测的准确率并没有显著的提高。
测试模型
测试效果,我们以其中一局牌为例子,在本局中,一个真实玩家,对3个机器人玩家。
以其中一个最终胡牌的机器人手牌为展示。
打出了4筒,没有打南风,软件正在打算做混一色。
摸一张西之后,软件打出5条,混一色动机已经非常明显。
摸一张6万之后,打出西。
当摸一张白板后,打出南,形成听牌,最后该机器人摸3万自摸。
实施过程中,我们使用Mac 系统10.13.6版本,T采用ensorflow2.0版本,使用了1个cpu进行训练。20万条游戏数据,我们一共花费了4个小时就训练完成了。
Java Script环境中,我们采用Node JS 8.9.3; Tensorflow JS 2.0