不久前,我们开发了代码来模拟 21 点游戏。通过这些模拟,我们发现了赌场之所以具有优势的关键驱动因素。主要是以下 2 点:
赌场迫使玩家在庄家面前采取行动(面对的是不完整的信息),从而获得了 21 点玩家的优势。这首先会让玩家面临破产的风险(所以他们可能在庄家有机会采取行动之前就已经破产了)。
当玩家的手牌值总数在 12 到 16 之间时,他们处于尤其危险的情况中(他们可能会被下一张牌打爆),而庄家则显示出一张高数值牌。在这些情况下,假设庄家手中的牌总点数很多,则玩家要么拿牌要么不动。我们可以通过玩家在 12 到 16 之间获胜或平局的概率来直观地看到这一点。
获胜或平局的概率与玩家手中牌值总数(21 未显示,因为概率为 100%)
最后,我们观察到一个简单的策略:只有在没有机会打爆的情况下才拿牌,这大大提高了我们获胜的几率,因为它将打爆的风险完全转移到了赌场。
如果你不熟悉 21 点游戏,我之前的文章描述了玩这个游戏的规则。
但是深度学习能做得更好吗?
本文的目标是讨论我们是否可以用深度学习来找到一个比上面更好的策略。我们将:
使用我们上次编码的 BLUJACK 模拟器生成数据(通过一些修改,使其更适合于训练算法)。
编码训练神经网络玩 21 点。
如果你不熟悉神经网络,我在这篇文章中写过它们的简介。
一个简单神经网络的视觉缺陷
在我们开始训练之前,让我们退一步,快速讨论一下在这种情况下使用神经网络的优缺点。神经网络是高度灵活的算法——像软粘土一样,神经网络调整自己,以适应数据的轮廓。神经网络可以很容易地处理那些会给线性回归等带来麻烦的数据。此外,网络中的层和神经元将学习数据中可能存在的任何深度嵌入的非线性关系。
然而,这种功能是有代价的——神经网络是一个黑箱模型。与回归不同的是,我们可以通过观察回归系数来了解模型是如何做出决策的,神经网络并不具备这种透明性。此外,神经网络也有可能把我们的数据拟合得太好,以致于不能很好地概括样本外数据。在我看来,这些缺点值得牢记,并为其设计保护措施,但它们并不是回避使用神经网络的理由。
生成我们的训练数据
在训练神经网络之前,我们首先需要弄清楚如何构造训练数据,这样我们用它建立的模型才会是有用的。
我们想要预测什么?在我看来,我们的目标变量有两个:
输掉比赛的可能性。考虑到这种情况,我们可能希望模型告诉我们输的概率是多少。再说一次,只有当我们可以增加或减少赌注时,这才有用,而在 21 点游戏中我们不能这样做。
相反,我们希望我们的神经网络能够识别正确的动作,拿牌或不动。所以我们的目标变量应该是「正确的动作是拿牌还是不动」。
实际上我花了一段时间才找到最好的方法来设置这个。下面这是我想到的。
我们需要一种方法让神经网络知道给定的动作是否正确。它不需要万无一失,只需要大体正确。所以我决定一个动作是否正确的方法是模拟 21 点游戏:把牌交给玩家和庄家,检查是否有人有 21 点,只做一个动作(拿牌或不动),模拟游戏结束并记录结果。由于模拟玩家只做一个决定,我们可以通过他是赢还是输来评估这个决定的质量:
如果玩家拿牌并获胜,那么拿牌(y=1)是正确的决定。
如果玩家打输了,那么不动(y=0)是正确的决定。
如果玩家不动并获胜,那么不动(Y=0)是正确的决定。
如果玩家不动并输了,那么拿牌(y=1)是正确的决定。
这允许我们训练出一个模型,其输出是一个预测是拿牌或者不动。其代码与上一次类似,因此我不会在这里给出详细的概述(你也可以在我的 github 上找到它)。其主要特点是:
庄家的正面卡(另一张隐藏起来)。
玩家手上牌的总值。
玩家是否有王牌。
玩家的动作(拿牌或不动)。
目标变量是由上述逻辑定义的正确决策。
训练神经网络
我们将使用 keras 库训练我们的神经网络。我们需要导入的库文件为:
from keras.models import Sequential
from keras.layers import Dense, LSTM, Flatten, Dropout
现在我们设置输入变量来训练神经网络。变量 feature_list 是一个列表,其中列有我在上面列出的特征(x 个变量)的列名。dataframe model_df 是存储我运行的 21 点模拟的所有数据的地方。
# Set up variables for neural net
feature_list = [i for i in model_df.columns if i not in
['dealer_card','Y','lose','correct_action']
]
train_X = np.array(model_df[feature_list])
train_Y = np.array(model_df['correct_action']).reshape(-1,1)
实例化和训练神经网络的代码行非常简单。第一行(第 1 行)创建一个顺序型神经网络,它是神经网络层的线性序列。第一行之后的代码逐个地向我们的模型中添加层。
最后,对于最后一层,我们需要选择一个激活函数。这将神经网络的原始输出转换成我们可以解释的东西。在最后一层需要注意两件事。首先,它只包含一个神经元,因为我们正在预测两种可能的结果(二分类问题)。第二,我们使用 sigmoid 函数激活,因为我们希望我们的神经网络像逻辑回归一样,并预测正确的动作是拿牌(y=1)还是不动(y=0)——换句话说,我们想知道拿牌是否是正确的动作。
最后两行告诉我们的神经网络模型使用什么损失函数将模型匹配到我们的数据。我没有花太多的时间来调整层数或神经元的数量,但是如果有人玩我的代码,我会建议他对这些潜在的地方进行改进。
# Set up a neural net with 5 layers
model = Sequential() # line 1
model.add(Dense(16))
model.add(Dense(128))
model.add(Dense(32))
model.add(Dense(8))model.add(Dense(1, activation='sigmoid')) # final layer
model.compile(loss='binary_crossentropy', optimizer='sgd')
model.fit(train_X, train_Y, epochs=20, batch_size=256, verbose=1)
检查我们模型的性能
检查我们的模型是否增加任何值的一种快速方法是使用 ROC 曲线。ROC 曲线告诉我们,我们的模型在收益(真阳性率)和成本(假阳性率)之间的权衡情况——曲线下的面积越大,模型越好。
下面的图片显示了我们玩 21 点的神经网络的 ROC 曲线——神经网络似乎是增加了一个合理的猜测值(红色虚线)。曲线下面积(AUC)为 0.73,明显高于随机猜测的 AUC(0.50)。
21 神经网络的 ROC 曲线
我用我的训练数据绘制 ROC 曲线。通常我们希望使用验证或测试数据绘制它,但在这种情况下,我们知道只要样本足够大,它就能代表总体(假设我们继续使用相同的规则玩 21 点)。我们希望我们的模型能够很好地概括任何新的数据都与训练数据相同的基本统计特征。
玩游戏的时间到了!
在我们的神经网络正式开始工作之前,我们需要给它一个决策规则。记住,sigmoid 激活(从我们最后的神经网络层)使我们的神经网络输出一个正确的动作的概率。我们需要一个决策规则,在给定这个概率的情况下,我们决定是拿牌还是不动。
我写了下面的函数来实现这一点——model_decision 函数具有神经网络所需的特征,使用这些特征进行预测,并将预测与预定义的阈值进行比较,以决定是拿牌还是不动。我使用 0.52,因为我们已经从上一次就知道,对 21 点玩家来说,爆破是最大的风险。因此,使用 0.52 作为拿牌的截止值会使我们的模型爆破的可能性稍微降低,因此输掉的可能性稍微降低。
def model_decision(model, player_sum, has_ace, dealer_card_num):
input_array = np.array([player_sum, 0, has_ace,
dealer_card_num]).reshape(1,-1)
predict_correct = model.predict(input_array)
if predict_correct >= 0.52:
return 1
else:
return 0
现在我们只需要将上面的函数添加到代码中,在这里我们可以决定是否拿牌。因此,当决定该做什么时,神经网络将根据庄家出示的牌、自己牌的总面值以及是否持有王牌来做出决定。
我们的模型很好!
最后,我们将神经网络的性能与原始策略和随机策略进行比较。需要提醒大家的是:
我为每种策略类型(神经网络、简单和随机策略)运行了大约 300000 个 21 点模拟实验。
简单策略只有在没有破发机会的情况下才出手(手牌总数低于 12 时拿牌,手牌总数为 12 或更多时才出手)。
随机的策略就像是掷硬币——如果它出现头部则拿牌,否则不动。如果你拿牌了,但没有爆破,那么再掷硬币,重新开始整个过程。
让我们看看我们的神经网络是否能找到更好的策略。下表显示了每种策略类型的结果分布。我有两件事要做。首先,我们的神经网络在玩游戏时只损失了不到一半(49%)。我认为这是相当体面的游戏,赔率对你来说是固定的。第二,它实际上不会比简单的策略更容易获胜,相反,它能够产生更多的关系。
各种战略的结果
我们还可以看看策略如何利用我们的主要特征(庄家的牌数和玩家手牌总值)。首先,让我们来看看在我们的三个策略中,庄家出示的牌对获胜或平手概率的影响。在下面的图形中,如果庄家牌数很少,我们的神经网络执行效果。但是当经庄家牌数更高(7或更多)时,我们的神经网络表现得更好。
与庄家出示的牌平手或胜出的概率(牌数越多越好!)
我们可以看看赢或平手的概率是如何随玩家的初始牌数而变化的。这看起来很有希望——我们的神经网络也表现得很好或者更好。与简单的策略不同,它比玩家手牌值在 12 和 16 之间时的结果更坏,我们的神经网络表现更好。
平局或获胜的概率与玩家的初始手牌值的关系
上面的图展示了神经网络如何超越简单的策略。简单的策略不愿意冒险,甚至远离爆破风险。另一方面,神经网络经常命中 12、13、14 或 15。这是更细微的决策,而承担计算风险的能力似乎将它与简单的策略区分开来。
神经网络和简单策略随玩家初始手值的变化趋势
我们可以看看当玩家的手值总数在 12 到 16 之间时,神经网络会做些什么来改善我们的简单策略,以免损失太多的钱到赌场。
当庄家出示一张高值的牌(8、9 或 10)时,似乎有强烈的偏好。但是即使当庄家出示低值牌,如 3 时,神经网络仍然在 60% 的情况下选择拿牌——这是因为神经网络是考虑到所有的特征然后才作出决定。因此,我们似乎不能轻易地将它的决定提炼成几个简单的经验法则。
神经网络的拿牌频率与庄家出示的牌值
结论
希望这篇文章给了你一个关于如何使用机器学习辅助现实生活中决策的不错介绍。以下是当你训练自己的模型时要记住的一些事情(不管它们是决策树、回归或神经网络):
我的目标变量的结构是否能让我预测它,然后我就能解决我的问题?在你开始收集数据和构建你的模型之前,确保你的预测是正确的至关重要。
新的数据和我训练过的数据有什么不同?如果它变化很大,那么统计模型甚至可能不是你的问题的正确答案。至少,你必须认识到这一点,并建立保障措施,例如用正规化和严格的验证和测试集对模型进行基准测试。
无法理解模型是如何到达它的决策的,所以你没有办法去理解和检验你的模型在严格的测试之外做出的决策,而这些测试是在模型训练过程中进行的。
最后一句关于 21 点的话。我可能暂时不会再写关于赌博的文章了(我还有太多其他的话题想探讨)。但是,如果有人对使用或扩展我的代码有兴趣,这里有几个对这个项目潜在的有趣扩展:
尝试通过更优化的神经网络结构来改进模型,或者添加用于拆分 A 的代码(我没有把它构建到我原来的模拟器中),或者选择比我使用的基本特征更好的特征。
给模型计算卡片的能力,看看它对一副牌和六副牌的性能有什么影响(这是拉斯维加斯的标准)。
希望你和我一样发现它们的乐趣……干杯!
via:https://towardsdatascience.com/teaching-a-neural-net-to-play-blackjack-8ec5f39809e2
雷锋网雷锋网雷锋网