专栏名称: 机器之心
专业的人工智能媒体和产业服务平台
目录
相关文章推荐
机器之心  ·  20万美元商业级视频生成大模型Open-So ... ·  昨天  
黄建同学  ·  深度长文《为什么MCP赢了》 (Why ... ·  昨天  
量子位  ·  Manus逼OpenAI开源智能体框架,网友 ... ·  2 天前  
AI前线  ·  团队“DeepSeek”化!字节 Seed ... ·  3 天前  
51好读  ›  专栏  ›  机器之心

All In! 我学会了用强化学习打德州扑克

机器之心  · 公众号  · AI  · 2017-10-15 14:58

正文

选自willtipton

机器之心编译

参与:Jane W、蒋思源


最近,强化学习(RL)的成功(如 AlphaGo)取得了大众的高度关注,但其基本思路相当简单。下面我们在一对一无限注德州扑克游戏上进行强化学习。为了尽可能清楚地展示,我们将从零开始开发一个解决方案,而不需要预设的机器学习框架(如 Tensorflow)。让我们用 Python3 Jupyter notebook 开始吧!


  • 问题设置

  • 强化学习

  • 特征: 的输入(下文使用 Q^表示 Q hat)

  • 关于 Q^ 的线性模型

  • 模拟扑克游戏

  • 学习:更新 Q^

  • 整合

  • 结果

  • 解释模型

  • 可视化策略

  • 结语


问题设置


规则提醒:该游戏是一个 2 人无限注的德扑游戏,其中:


1. 游戏开始,两名选手均有 S 筹码和随机发放的 2 张底牌。

2. BB(大盲注)玩家下 1.0 个盲注,SB(小盲注)玩家下 0.5 个盲注。

3. 小盲注玩家可以全押(all-in)或弃牌(fold)。

4. 如果小盲注玩家全押,那么大盲注玩家可以跟注(call)或弃牌。


我们可以将规则可视化为下图所示的决策树。游戏开始于 E,这时 SB 可以全押或弃牌。如果他弃牌,我们转移到状态 A,游戏结束。如果他全押,我们转移到状态 D,BB 必须在跟注和弃牌之间作出决定。如果一个玩家弃牌,另一个玩家就会得到盲注,如果两个玩家全押,则发放 5 张公共牌,并且金额按照扑克的正常规则进行分配。





这个游戏有著名的的解决方案(http://www.dandbpoker.com/preflop-charts),也有其它的方法,如虚拟对局(https://www.youtube.com/watch?v=MVMfDswjJE0)和直接优化(http://willtipton.com/coding/poker/2016/03/06/shove-fold-with-tensorflow.html)。这里,我们将使用强化学习估算解决方案。


这里有 种不重复的 2 张手牌组合数。因此,我们可以给所有牌排序,并从 0 到 1325 编号。只要前后编号一致,具体的顺序就不重要了。以下函数隐含地定义了这样一个排序,并创建了从牌的编号到相关决策信息的映射:牌的排序(牌面顺序/rank)和同花性(牌面花色/suitedness)。




请注意,输出元组中的第一个元素(代码中的 r2)始终排序靠前,如果有的话。例如,手牌编号 57 恰好是 6♦2♣,我们有:




当玩家全押时,他们平均获得的底池(「期望利益」)根据游戏规则决定。文件 pf_eqs.dat(http://willtipton.com/static/pf_eqs.dat)包含一个 numpy 矩阵 pfeqs(http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.savetxt.html),其中 pfeqs [i,j] 指当对手持有手牌 j 时持有手牌 i 的期望利益。


当然,有时候两人起始手牌有一张牌是相同的,在这种情况下,它们的期望不能同时计算,这时取得他们的期望利益也不合适。文件 pf_confl.dat(http://willtipton.com/static/pf_confl.dat)包含另一个 1326×1326 矩阵,其中每个元素为 0 或 1。A 0 表示两位玩家的起始手牌不一样,a 1 表示起始手牌一样。




例如,由于手牌 56 是 6♦2♣,57 是 6♥2♣,58 是 6♣2♠,于是我们有:




为什么结果不正好是 0.5 呢?


强化学习


下面进入 RL 教程。RL 问题有三个重要组成部分:状态(state)、动作(action)、奖励(reward)。它们合在一起如下:


1. 我们处于某「状态」(即我们观察到的世界的状态)。

2. 我们使用这个信息来采取某「动作」。

3. 我们会得到某种「奖励」。

4. 重复以上过程。


一遍又一遍地重复以上过程:观察状态、采取行动、获得奖励、观察新的状态、采取另一个行动、获得另一个奖励等。RL 问题只是找出如何选择行动的方案以获得尽可能多的奖励。事实证明这是一个非常普遍的框架。我们可以通过这种方式考虑许多问题,解决这些问题也有很多不同的方法。一般来说,解决方案涉及随机游走(wandering around),在不同状态选择各种行为,记住哪些组合能够获得什么奖励,然后尝试利用这些信息在未来做出更好的选择。


RL 如何用于德扑游戏呢?在任何决策点上,玩家知道他的 2 张底牌和他的位置,这就是状态。然后他可以采取行动:要么弃牌,要么 GII。(GII 对于 SB 意味着全押(shove),对于 BB 意味着跟注)。然后得到奖励——这是玩家赢得的钱数,在最后的手牌中我们将使用玩家的总筹码大小。例如,如果初始筹码大小为 S=10,SB 全押 BB 弃牌,则玩家的奖励分别为 11 和 9。


我们会通过模拟手牌组合来找到游戏的策略。我们会同时处理两个玩家的随机手牌,让他们做出关于如何玩的决策,然后观察他们每次结束时最终得到多少钱。我们将使用该信息来学习(估计)Q 函数 Q(S,A)。Q 的参数为状态 S 和动作 A,输出值为在该状态下采取该动作时得到的最终奖励值。一旦我们有 Q(或它的某种估计),策略选择就很容易了:我们可以评估每个策略,看哪一个更好。


所以,我们这里的工作是估计 Q,我们将使用 Q^(发音为「Q hat」)来指代这个估计。初始化时,我们将随机猜测一些 Q^。然后,我们将模拟一些手牌,两名玩家根据 Q^ 做出决定。每次手牌之后,我们将调整估计值 Q^,以反映玩家在特定状态下采取特定动作后获得的实际值。最终,我们应该得到一个很好的 Q^ 估计,这就是确定玩家策略所需的所有内容。


这里需要注意一点——我们要确保在所有状态采取所有动作,每个状态-动作组合至少尝试一次,这样才能很好地估计出最终每个可能的值。所以,我们会让玩家在一小段时间ε内随机地采取行动,使用他们(当前估计的)最佳策略。首先,我们应该积极探索选择的可能性,频繁地随机选择。随着时间的推移,我们将更多地利用我们获得的知识。也就是说,ε将随着时间的推移而缩小。有很多方法可以做到这一点,如:




Q 被称为「动作价值函数(action-value function)」,因为它给出了采取任何特定动作(从任何状态)的值。它在大多数 RL 方法中有重要的作用。Q^ 如何表示?如何评估?是否在每次手牌之后更新?


特征:Q^ 的输入


首先,Q^ 的输入:状态和动作。将这个信息传递给 Q 函数,作为位置(比如,SB 为 1,BB 为 0),手牌编码(0 到 1325),动作(比如,GII 为 1,弃牌为 0)。不过,我们将会看到,如果我们做更多的工作,会得到更好的结果。在这里,我们用 7 个数字的向量描述状态和动作:




由函数 phi 返回的向量φ将是 Q 函数的输入,被称为特征向量,各元素都是特征(φ发音为「fee」)。我们将看到,我们选择的特征可以在结果的质量上产生很大的不同。在选择特征(称为「特征工程」)中,我们利用了有关问题的相关领域知识。它和科学一样艺术化。在这里,我们将判断哪些为相关信息(在这种情况下)的知识用以下几种方式编码。让我们来看看。


为方便起见,第一个元素始终为 1。考虑接下来的四个元素。这些代表玩家的手牌。我们已经从手牌编码转换为 rank1、rank2 和 isSuited。这三个变量技术上给出与手牌编码相同的信息(忽略特定的组合),但是该模型将更好地利用这种格式的信息。除了原始排序,我们还包含了 (|rank1-rank2|)^0.25。我们碰巧知道 connectedness 是德扑的重要属性,正如其名。此外,如果所有特征都量纲一致,该模型的学习效果会更好。在这里,所有的特征大致介于 0 和 1 之间,我们通过将 rank 除以 numRanks 得到。


最后,如果 not isGII(即如果动作是弃牌),我们实际上将这些数字设置为 0。我们知道,当玩家弃牌时,特定的持有手牌对结果没有任何影响(忽略小概率的卡牌移除效果),所以我们在这种情况下删除无关的信息。


现在考虑最后两个元素。第一个直接编码玩家的位置,但第二个同时取决于 isSB 和 isGII。为什么会这样?稍后我们会显示这个「交叉项」的必要性。


关于 Q^ 的线性模型


我们将学习一个线性函数用于估计的 Q^ 函数。这意味着我们将真正学习一个参数向量,通常称为θ,它的长度(7)与特征向量相同。然后,我们将针对特定的φ来估计 Q^ :




这里,下标 i 指代向量的特定元素,并将参数列表写为 (φ;θ),其表示 Q^ 的值取决于φ和θ,但是我们可以认为是φ的函数,θ为固定值。代码很简单:




虽然这个函数普遍使用,但是这个算法没有什么特别之处,以使它成为这个问题的最佳选择。这只是其中一种方法:将某些学习参数与某些特征相结合以获得输出,并且完全由我们定义一个θ向量,使它产生我们想要的输出。然而,正确选择θ将为我们很好的估计在有特定的手牌时采取特定行动的价值。


模拟扑克游戏


我们接下来要「玩」手牌了。我们将在接下来的几个部分中进行,不过现在我们先构建三个重要的概念。这些概念与 RL 问题的三个重要组成部分相关:状态、动作和奖励。首先,状态——每次手牌,我们将以随机发牌的方式初始化每个玩家的状态。




第二点,采取动作。每个玩家将使用当前的模型(由 theta 给出)和已知的手牌和身份(为 SB)来选择动作。在以下函数中,我们估计 GII 和弃牌/FOLD(qGII 和 qFOLD)的值。然后选择当下的最优项(1-ε),否则随机选择动作。返回所采取的动作,以及相应的价值估计和特征向量,这两项我们之后会用到。




第三点,一旦我们知道每个玩家当下的手牌和动作,我们就模拟剩下的手牌来得到玩家的奖励。如果任何一个玩家弃牌,我们可以立即返回正确的奖励值。否则,我们参考玩家的状态和奖励期望(equity),在正确的时间段随机选择一个赢家。




在玩家全押的情况下,我们用小技巧规避了模拟。与通过使用 5 张公共牌实际模拟游戏并评估玩家的手牌来查看谁赢不同,我们现在根据预先计算的概率随机选择一个赢家。这在数学上是等价的(琐碎的证明忽略);这只是一个更方便和更有计算效率的方法。


最重要的是,我们的学习过程没有利用这些 equity 或有关游戏规则的信息。正如我们马上将要看到的那样,即使是完全模拟,学习过程也没有什么不同,甚至 智能体(agent)还会与外部黑盒的扑克游戏系统进行交互从而可能遵循不同规则!那么,学习过程究竟如何进行?


学习:更新 Q^


一次手牌结束之后,我们需要更新 theta。对于每个玩家,我们已知其状态和采取的动作。我们还有动作对应的估计价值以及从游戏中获得的实际奖励。从某种意义上说,实际获得的奖励是「正确解」,如果动作的估计价值与此不同,则我们的模型有误。我们需要更新 theta 以使 Q^(φ;θ) 更接近正确的答案。







请到「今天看啥」查看全文