这将《反事实后悔最小化》(CFR)应用于库恩扑克。
Kuhn Poker 是一款双人三张牌的下注游戏。玩家从 A、K 和 Queen 中各获得一张牌(无花色)。包中只有三张牌,所以遗漏了一张牌。Ace 击败 King 和 Queen,King 击败 Queen ——就像普通卡牌排名一样。
两个玩家都押注筹码(盲注筹码)。看完牌后,第一个玩家可以传球或下注筹码。如果第一个玩家通过,则拥有较高牌的玩家赢得底池。如果第一个玩家下注,第二个玩家可以下注(即跟注)筹码或过关(即盖牌)。如果第二个玩家下注,而拥有较高牌的玩家赢得底池。如果第二个玩家通过(即弃牌),则第一个玩家获得底池。这个游戏是反复玩的,一个好的策略将针对长期效用(或获利)进行优化。
以下是一些示例游戏:
KAp
-玩家 1 获得 K 玩家 2 获得 A. 玩家 1 传球。玩家 2 没有下注机会,玩家 2 赢得筹码底池。QKbp
-玩家 1 得到 Q。玩家 2 得到 K。玩家 1 下注一个筹码。玩家2传球(弃牌)。玩家 1 拿到底池是因为玩家 2 盖牌。QAbb
-玩家 1 得到 Q。玩家 2 得到 A. 玩家 1 下注一个筹码。玩家2也下注(跟注)。玩家 2 赢得底池。他__init__.py
用Kuhn Poker的具体内容扩展了中定义的InfoSet
History
类别和等级。
37from typing import List, cast, Dict
38
39import numpy as np
40
41from labml import experiment
42from labml.configs import option
43from labml_nn.cfr import History as _History, InfoSet as _InfoSet, Action, Player, CFRConfigs
44from labml_nn.cfr.infoset_saver import InfoSetSaver
库恩扑克的动作是 pass (p
) 或 bet (b
)
47ACTIONS = cast(List[Action], ['p', 'b'])
游戏中的三张牌分别是 Ace、King 和 Queen
49CHANCES = cast(List[Action], ['A', 'K', 'Q'])
有两个玩家
51PLAYERS = cast(List[Player], [0, 1])
不支持保存/加载
59 @staticmethod
60 def from_dict(data: Dict[str, any]) -> 'InfoSet':
62 pass
返回操作列表。终端状态由History
类处理。
64 def actions(self) -> List[Action]:
68 return ACTIONS
人类可读的字符串表示——它给出了下注概率
70 def __repr__(self):
74 total = sum(self.cumulative_strategy.values())
75 total = max(total, 1e-6)
76 bet = self.cumulative_strategy[cast(Action, 'b')] / total
77 return f'{bet * 100: .1f}%'
80class History(_History):
历史
94 history: str
使用给定的历史字符串进行初始化
96 def __init__(self, history: str = ''):
100 self.history = history
历史记录是否终止(游戏结束)。
102 def is_terminal(self):
玩家尚未采取行动
107 if len(self.history) <= 2:
108 return False
最后一个玩过的玩家通过(游戏结束)
110 elif self.history[-1] == 'p':
111 return True
两位玩家都叫(下注)(游戏结束)
113 elif self.history[-2:] == 'bb':
114 return True
任何其他组合
116 else:
117 return False
计算玩家的终端实用程序,
119 def _terminal_utility_p1(self) -> float:
如果玩家 1 有更好的牌或者其他
124 winner = -1 + 2 * (self.history[0] < self.history[1])
第二个玩家通过了
127 if self.history[-2:] == 'bp':
128 return 1
两位玩家都被召唤,持有更好牌的玩家将赢得筹码
130 elif self.history[-2:] == 'bb':
131 return winner * 2
第一个玩家通过,持有更好牌的玩家将赢得筹码
133 elif self.history[-1] == 'p':
134 return winner
历史不是终结的
136 else:
137 raise RuntimeError()
获取玩家的终端实用程序
139 def terminal_utility(self, i: Player) -> float:
如果是玩家 1
144 if i == PLAYERS[0]:
145 return self._terminal_utility_p1()
否则,
147 else:
148 return -1 * self._terminal_utility_p1()
前两个事件是卡牌交易;即机会事件
150 def is_chance(self) -> bool:
154 return len(self.history) < 2
在历史记录中添加操作并返回新的历史记录
156 def __add__(self, other: Action):
160 return History(self.history + other)
当前玩家
162 def player(self) -> Player:
166 return cast(Player, len(self.history) % 2)
采样一个机会动作
168 def sample_chance(self) -> Action:
172 while True:
随机挑选一张牌
174 r = np.random.randint(len(CHANCES))
175 chance = CHANCES[r]
看看之前有没有发过牌
177 for c in self.history:
178 if c == chance:
179 chance = None
180 break
如果之前没有发过牌,则归还该牌
183 if chance is not None:
184 return cast(Action, chance)
人类可读的表示
186 def __repr__(self):
190 return repr(self.history)
当前历史记录的信息集关键字。这是一串仅对当前玩家可见的动作。
192 def info_set_key(self) -> str:
获取当前玩家
198 i = self.player()
当前玩家看到她的牌和投注动作
200 return self.history[i] + self.history[2:]
202 def new_info_set(self) -> InfoSet:
创建新的信息集对象
204 return InfoSet(self.info_set_key())
用于创建空历史对象的函数
207def create_new_history():
209 return History()
配置扩展了 CFR 配置类
212class Configs(CFRConfigs):
216 pass
设置库恩扑克create_new_history
的方法
219@option(Configs.create_new_history)
220def _cnh():
224 return create_new_history
227def main():
创建一个实验,我们只写跟踪信息sqlite
来加快速度。由于算法的迭代速度很快,而且我们在每次迭代时都会跟踪数据,因此写入其他目标(如 Tensorboard)可能相对耗时。SQLite 对于我们的分析来说已经足够了。
236 experiment.create(name='kuhn_poker', writers={'sqlite'})
初始化配置
238 conf = Configs()
装载配置
240 experiment.configs(conf)
开始实验
242 with experiment.start():
开始迭代
244 conf.cfr.iterate()
248if __name__ == '__main__':
249 main()