これは反事実に基づく後悔最小化 (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 InfoSetSaver
Kuhn ポーカーのアクションはパス (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 pass
Kuhn 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 experiment.add_model_savers({'info_sets': InfoSetSaver(conf.cfr.info_sets)})
実験を始める
244 with experiment.start():
イテレーションを始める
246 conf.cfr.iterate()
250if __name__ == '__main__':
251 main()