这适用于库恩扑克的反事实后悔最小化(CFR)。
库恩扑克是一款双人三张牌博彩游戏。玩家将从Ace、King和Queen中各获得一张牌(没有花色)。包中只有三张牌,因此遗漏了一张牌。Ace 击败 King and 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
用库恩扑克的InfoSet
细节扩展了中定义的History
班级和班级。
38from typing import List, cast, Dict
39
40import numpy as np
41
42from labml import experiment
43from labml.configs import option
44from labml_nn.cfr import History as _History, InfoSet as _InfoSet, Action, Player, CFRConfigs
45from labml_nn.cfr.infoset_saver import InfoSetSaver库恩扑克的动作是 pass (p
) 或 bet (b
)
48ACTIONS = cast(List[Action], ['p', 'b'])游戏中的三张牌分别是 Ace、King 和 Queen
50CHANCES = cast(List[Action], ['A', 'K', 'Q'])有两个玩家
52PLAYERS = cast(List[Player], [0, 1])不支持保存/加载
60    @staticmethod
61    def from_dict(data: Dict[str, any]) -> 'InfoSet':63        pass返回操作列表。终端状态由History
类处理。
65    def actions(self) -> List[Action]:69        return ACTIONS人类可读的字符串表示——它给出了下注概率
71    def __repr__(self):75        total = sum(self.cumulative_strategy.values())
76        total = max(total, 1e-6)
77        bet = self.cumulative_strategy[cast(Action, 'b')] / total
78        return f'{bet * 100: .1f}%'81class History(_History):历史
95    history: str使用给定的历史字符串进行初始化
97    def __init__(self, history: str = ''):101        self.history = history历史记录是否终止(游戏结束)。
103    def is_terminal(self):玩家尚未采取行动
108        if len(self.history) <= 2:
109            return False最后一个玩过的玩家通过(游戏结束)
111        elif self.history[-1] == 'p':
112            return True两位玩家都叫(下注)(游戏结束)
114        elif self.history[-2:] == 'bb':
115            return True任何其他组合
117        else:
118            return False计算玩家的终端实用程序,
120    def _terminal_utility_p1(self) -> float:如果玩家 1 有更好的牌或者其他
125        winner = -1 + 2 * (self.history[0] < self.history[1])第二个玩家通过了
128        if self.history[-2:] == 'bp':
129            return 1两位玩家都被召唤,持有更好牌的玩家将赢得筹码
131        elif self.history[-2:] == 'bb':
132            return winner * 2第一个玩家通过,持有更好牌的玩家将赢得筹码
134        elif self.history[-1] == 'p':
135            return winner历史不是终结的
137        else:
138            raise RuntimeError()获取玩家的终端实用程序
140    def terminal_utility(self, i: Player) -> float:如果是玩家 1
145        if i == PLAYERS[0]:
146            return self._terminal_utility_p1()否则,
148        else:
149            return -1 * self._terminal_utility_p1()前两个事件是卡牌交易;即机会事件
151    def is_chance(self) -> bool:155        return len(self.history) < 2在历史记录中添加操作并返回新的历史记录
157    def __add__(self, other: Action):161        return History(self.history + other)当前玩家
163    def player(self) -> Player:167        return cast(Player, len(self.history) % 2)采样一个机会动作
169    def sample_chance(self) -> Action:173        while True:随机挑选一张牌
175            r = np.random.randint(len(CHANCES))
176            chance = CHANCES[r]看看之前有没有发过牌
178            for c in self.history:
179                if c == chance:
180                    chance = None
181                    break如果之前没有发过牌,则归还该牌
184            if chance is not None:
185                return cast(Action, chance)人类可读的表示
187    def __repr__(self):191        return repr(self.history)当前历史记录的信息集关键字。这是一串仅对当前玩家可见的动作。
193    def info_set_key(self) -> str:获取当前玩家
199        i = self.player()当前玩家看到她的牌和投注动作
201        return self.history[i] + self.history[2:]203    def new_info_set(self) -> InfoSet:创建新的信息集对象
205        return InfoSet(self.info_set_key())用于创建空历史对象的函数
208def create_new_history():210    return History()配置扩展了 CFR 配置类
213class Configs(CFRConfigs):217    pass设置库恩扑克create_new_history
的方法
220@option(Configs.create_new_history)
221def _cnh():225    return create_new_history228def main():创建一个实验,我们只写跟踪信息sqlite
来加快速度。由于算法的迭代速度很快,而且我们在每次迭代时都会跟踪数据,因此写入其他目标(如 Tensorboard)可能相对耗时。SQLite 对于我们的分析来说已经足够了。
237    experiment.create(name='kuhn_poker', writers={'sqlite'})初始化配置
239    conf = Configs()装载配置
241    experiment.configs(conf)设置要保存的模型
243    experiment.add_model_savers({'info_sets': InfoSetSaver(conf.cfr.info_sets)})开始实验
245    with experiment.start():开始迭代
247        conf.cfr.iterate()251if __name__ == '__main__':
252    main()