これは反事実に基づく後悔最小化 (CFR) をクーンポーカーにも適用します。
Kuhn Pokerは、2人用の3カードベッティングゲームです。プレイヤーには、エース、キング、クイーンのカードがそれぞれ1枚ずつ配られます(スーツなし)。パックにはカードが3枚しかないので、1枚のカードは残ります。通常のカードランキングと同じように、エースがキングとクイーン、キングがクイーンを倒します
。どちらのプレイヤーもアンティチップ(盲目的にチップをベットする)。カードを見た後、最初のプレーヤーはパスまたはチップをベットできます。最初のプレーヤーがパスした場合、高いカードを持っているプレーヤーがポットを獲得します。最初のプレーヤーがベットした場合、 2番目のプレーはチップまたはパス(つまりフォールド)をベット(つまりコール)できます。2人目のプレイヤーがベットし、高い方のカードを持っているプレイヤーがポットを獲得した場合。2人目のプレイヤーがパス (つまりフォールド) すると、最初のプレイヤーがポットを獲得します。このゲームは繰り返しプレイされるため、優れた戦略を立てることで長期的な効用(または賞金)を狙うことができます
。ゲームの例をいくつか示します。
KAp
-プレイヤー 1 は K、プレイヤー 2 は A プレイヤー 1 のパス。プレイヤー 2 にはベットチャンスがなく、プレイヤー 2  はポットのチップを獲得しますQKbp
-プレイヤー1はQ、プレイヤー2はK、プレイヤー1はチップをベットします。プレイヤー 2 パス (フォールド)。プレイヤー2がフォールドしたため、プレイヤー1はポットを獲得しますQAbb
-プレイヤー 1 は Q、プレイヤー 2 は A プレイヤー 1 がチップをベットします。プレイヤー 2 もベット (コール) します。プレイヤー2がポットを獲得します。そこで、InfoSet
History
クラスとクラスを Kuhn __init__.py
Pokerの仕様で拡張しています。
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 InfoSetSaverKuhn ポーカーのアクションはパス (p
) またはベット (b
)
47ACTIONS = cast(List[Action], ['p', 'b'])場に出ている3枚のカードは、エース、キング、クイーンの3枚です。
49CHANCES = cast(List[Action], ['A', 'K', 'Q'])プレイヤーは2人います
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])2 人目のプレーヤーが合格
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()最初の 2 つのイベントはカードディール、つまりチャンスイベントです。
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    passKuhn create_new_history
 ポーカーのメソッドを設定
219@option(Configs.create_new_history)
220def _cnh():224    return create_new_history227def main():実験を行います。追跡情報を書き込むのは、sqlite
処理をスピードアップするためだけです。アルゴリズムは反復処理が速く、反復のたびにデータを追跡するため、Tensorboard などの他の宛先への書き込みには比較的時間がかかります。私たちの分析にはSQLiteで十分です
236    experiment.create(name='kuhn_poker', writers={'sqlite'})構成を初期化
238    conf = Configs()設定をロード
240    experiment.configs(conf)保存するモデルを設定
242    experiment.add_model_savers({'info_sets': InfoSetSaver(conf.cfr.info_sets)})実験を始める
244    with experiment.start():イテレーションを始める
246        conf.cfr.iterate()250if __name__ == '__main__':
251    main()