本文首發於行者AI
引言
自2017年AlphaGo打敗世界圍棋冠軍柯潔后,人工智能徹底進入大眾視野,一時間棋牌類的AI在人工智能界掀起了一股大風。其實早在AlphaGo之前,人們就對棋牌類的人工智能發起了挑戰,從簡單的跳棋、五子棋,到更加復雜的中國象棋、國際象棋,以及最近非常熱門的圍棋和德州撲克,數十年間也是碩果累累。而相對於跳棋、象棋等完全信息游戲,德州撲克不僅要根據不完全信息進行復雜決策,還要應付對手的虛張聲勢、故意示弱等招數,其對應的博弈樹無論是廣度還是深度都十分龐大,它也一直都是科學家們想要攻克的高山。而在AlphaGO打敗柯潔的同年,德撲AI DeepStack和Libratus也先后在 “一對一無限注德州撲克” 上擊敗了職業撲克玩家,在不完全信息博弈中做出了里程碑式的突破,而他們所采用的的核心算法就是Counterfactual Regret Minimization(CFR)。
1. Regret Matching
1.1算法原理
CFR算法的前身是regret matching算法,在此算法中,智能體的動作是隨機選擇的,其概率分布與 positive regret呈正比, positive regret表示一個人因為過去沒有選擇該行動而受到的相對損失程度。
這里對Regret Matching算法中的符號做出若干定義:
-
\(N=\left\{1,2,...,n\right\}\) 表示博弈玩家的有限集合。玩家\(i\) 所采用的的策略為\(\sigma_i\) 。
-
對於每個信息集\(I_i∈\xi_i,\sigma_i(I_i):A(I_i)→[0,1]\),是在動作集\(A(I_i)\)上的概率分布函數。玩家\(i\)的策略空間用\(\Sigma_i\)表示 。
-
一個策略組包含所有玩家策略,用\(\sigma=(\sigma_1,\sigma_2,...,\sigma_n)\).
-
在博弈對決中,不同玩家在不同時刻會采取相應策略以及行動。策略下對應的動作序列發生概率表示為\(\pi^\sigma(h)\),且\(\pi^\sigma(h)=\prod_{i∈N}\pi_i^\sigma(h)\)
這里的\(\pi^\sigma_i(h)\)表示玩家\(i\)使用策略\(\sigma_i\)促使行動序列\(h\)發生的概率,除了玩家\(i\)以外,其他玩家通過各自策略促使行動序列\(h\)發生的概率為:\(\pi^\sigma_{-i}(h)=\prod_{i∈N/{i}}\pi_j^\sigma(h)\)。
-
對於每個玩家\(i∈N,u_i:Z→R\),表示玩家的收益函數。
-
計算玩家在給定策略下所能得到的期望收益:\(u_i(\sigma)=\Sigma_{h∈Z}u_i(h)\pi^\sigma(h)\)。
-
納什均衡:策略組\(\sigma=(\sigma^*_1,\sigma^*_2,...,\sigma^*_n)\)是納什平衡當且僅當對每個玩家\(i∈N\),滿足條件:\(u_i(\sigma)\geq max_{\sigma_i^`}(\sigma^*_1,\sigma^*_2,...,\sigma^*_n)\)。
-
遺憾值:玩家在第T次采取策略的遺憾值為:
\[R_i^T(a)=\Sigma_{T=1}^T(\mu_i(a,\sigma_{-i}^t)-\mu_i(\sigma_i^t,\sigma_{-i}^t)) \] -
策略:根據遺憾值更新策略
\[\sigma_i^{T+1}(a)=\frac{R_i^T(a)}{\Sigma_{b∈A_i}R_i^T(b)} \] -
平均遺憾值:假設博弈能夠重復進行,令第次的博弈時的策略組為,若博弈已經進行了次,則這次博弈對於玩家的平均遺憾值定義為:
\[{Regret_i^M}=\frac{1}{M}max_{\sigma_i^*∈\Sigma_{i=1}^M}(u_i(\sigma_i^*,\sigma_{-t}^t)-u_i(\sigma^t)) \]
Regret matching算法流程為:
-
對於每一位玩家,初始化所有累積遺憾為0。
-
for from 1 to T(T:迭代次數):
a)使用當前策略與對手博弈
b)根據博弈結果計算動作收益,利用收益計算后悔值
c)歷史后悔值累加
d)根據后悔值結果更新策略
-
返回平均策略(累積后悔值/迭代次數)
1.2實例
石頭剪子布是最為簡單的零和博弈游戲,是典型的正則式博弈,其payoff table如下:
Regret matching算法流程在本例中為:
a)首次迭代,player1和player2都以\(\frac{1}{3}\)概率隨機選擇動作,假設player1選擇布,player2選擇剪刀。
b)以player1視角,首次博弈結果收益為:\(u_{(r,s)}=1、 u_{(p,s)}=-1、 u_{(s,s)}=0\)。
c)根據結果收益計算后悔值,
d)進行歸一化處理更新player1的行動策略:\((\frac{2}{3}R,0P,\frac{1}{3} S)\).
e)根據更新后的策略選擇動作進行多次博弈,直至達到納什平衡
f)返回平均策略
核心代碼如下(具體代碼戳這兒):
1)獲得策略方法:1.清除遺憾值小於零的策略並重置策略為0;2.正則化策略,保證策略總和為13.在某種情況下,策略的遺憾值總和為0,此時重置策略為初始策略。
def get_strategy(self):
"""
Get current mixed strategy through regret-matching
:return:
"""
normalizing_sum = 0
# add this overcome initial best response opponent
# if min(self.strategy) < 0:
# min_val = min(self.strategy)
# self.strategy = [_ - min_val for _ in self.strategy]
for i in range(NUM_ACTIONS):
self.strategy[i] = max(self.regret_sum[i], 0)
normalizing_sum += self.strategy[i]
# normalize
for i in range(NUM_ACTIONS):
if normalizing_sum > 0:
self.strategy[i] /= normalizing_sum
else:
self.strategy[i] = 1 / NUM_ACTIONS
self.strategy_sum[i] += self.strategy[i]
return self.strategy
2)訓練方法:1.玩選擇策略進行博弈,根據博弈結果計算動作效益;2.根據動作效益計算后悔值。
def train(self, iterations: int):
action_utility = [0] * NUM_ACTIONS
opponent_stats = [0] * NUM_ACTIONS
cum_utility = 0
for i in range(iterations):
# Get regret-matched mixed-strategy actions
strategy = self.get_strategy()
# strategy = self.get_average_strategy()
my_action = self.get_action(strategy)
other_action = self.get_action(self.opp_strategy)
opponent_stats[other_action] += 1
# Compute action utilities
action_utility[other_action] = 0
action_utility[(other_action + 1) % NUM_ACTIONS] = 1
action_utility[other_action - 1] = -1
# Accumulate action regrets
for a in range(NUM_ACTIONS):
self.regret_sum[a] += action_utility[a] - action_utility[my_action]
# self.regret_sum[a] = opponent_stats
cum_utility += action_utility[my_action]
print(f'opponent_stats: {opponent_stats}, {[_ - min(opponent_stats) for _ in opponent_stats]}')
print(f'cum utility: {cum_utility}')
實驗結果:
1)當固定對手策略為{0.4, 0.3, 0.3}時
2)當玩家和對手都采用Regret Matching更新策略時
2. Counterfactual Regret Minimization
2.1算法原理
石頭剪子布是典型的“一次性”博弈,玩家做出動作即得到結果。而生活中顯然許多的博弈屬於序列化博弈,博弈由一系列的動作組成,上一步的動作可能會導致下一步的動作選擇變更,最終的動作組合形成博弈結果。這種序列游戲我們不再使用payoff table表示,而是使用博弈樹的形式。博弈樹由多種狀態組成,邊表示從一個狀態到另一個狀態的轉換。狀態可以是機會節點或決策節點。機會節點的功能是分配一個機會事件的結果,因此每個邊代表該機會事件的一個可能結果以及事件發生的概率。在決策節點上,邊代表行動和后續狀態,這些狀態是玩家采取這些行動的結果。
同樣地,對CFR算法中的符號進行若干定義:
-
每個信息集\(I\)發射部分的概率\(\pi^\sigma(I)=\Sigma_{h∈I}\pi^\sigma(h)\),表示所有能到達信息集的行動序列的概率累加。
-
當采取策略\(\sigma\)下,施加博弈行動序列\(h\)后達到最終局勢\(z\)的概率為:\(\pi^\sigma(h,z)\)。
-
當采用策略\(\sigma\)時,其所對應的行動策略的虛擬收益:\(u_i(\sigma,h)=\Sigma_{z∈Z}\pi^\sigma(h,z)u_i(z)\)。
-
玩家\(i\)采取行動\(a\)所得到的的虛擬后悔值:\(r(h,a)=v_i(\sigma_{I→a},h)-v_i(\sigma,h)\)。
-
行動序列\(h\)所對應的信息集\(I\)后悔值為:\(r(I,a)=\Sigma r(h,a)\)。
-
玩家\(i\)在第\(T\)輪次采取行動\(a\)的后悔值為:\(Regret_t^T(I,a)=\Sigma_{t=1}^Tr_i^t(I,a)\)。
-
同樣地,對於后悔值為負數不予考慮,記:\(Regret_t^{T,+}(I,a)=max(R_i^T(I,a),0)\)。
-
在\(T+1\)輪次,玩家\(i\)選擇行動\(a\)的概率計算如下:
\[\sigma_i^{T+1}(I,a)= \begin{cases}\frac{Regret_i^{T,+}(I,a)}{\Sigma a∈A(I)Regret_i^{T,+}(I,a)} &\text{if $\Sigma_{a∈A(I)}Regret_i^{T,+}(I,a)>0$}\\ \frac{1}{A(I)},&\text{otherwise} \end{cases} \]
算法流程:
- 針對每個信息集,初始化后悔值和策略
- 使用當前策略與對手博弈
- 計算在本次博弈中所訪問到的每個信息集的收益和后悔值
- 通過Regret Matching算法更新策略
- 多次迭代,直到納什平衡
- 返回平均策略(累積后悔值/迭代次數)
2.2實例
庫恩撲克(Kunh’s pocker)是最簡單的限注撲克游戲,由兩名玩家進行游戲博弈,牌值只有1,2和3三種情況。每輪每位玩家各持一張手牌,根據各自判斷來決定加定額賭注過牌(P)還是加注(B)。具體游戲規則如下:
以玩家α視角構建庫恩撲克博弈樹:
CFR算法流程在本例中為:
a)初始策略為隨機策略,假設玩家α抽到的牌值為:3
b)第一輪迭代時,節點選擇動作P的虛擬收益計算方法為:\(u_A^\sigma(3,P)=\Sigma_{i=1}^3\pi_B^\sigma(3P)\pi^\sigma(z_i|3P)u_A(z_i)\)。結合博弈樹求解得到:\(\pi_B^\sigma(3,P)=1\)、\(\pi^\sigma(z_1|3P)=0.5\)、\(\pi^\sigma(z_2|3P)=0.25\)、\(\pi^\sigma(z_3|3P)=0.25\);\(u_A(z_1)=1\)、\(u_A(z_2)=1\) \(u_A(z_3)=2\) \(。∴u_A(3,P)=1.25\)。同理,計算節點選擇動作B的虛擬收益為:\(u_A(3,B)=0.5\)
c)利用虛擬收益更新后悔值:\(R^1(3,P)=R^0(3,P)+u_A^\sigma(3,P)-(\sigma(3,B)u_A^\sigma(3,B)+\sigma(3,P)u_A^\sigma(3,P))=0+1.25-(0.5\times 0.5+0.5\times 1.25)=0.375\)
d)利用后悔值更新策略:\(\sigma_A^1(3,P)=0.375\div (0.375+0)=1\),\(\sigma_A^1(3,B)=1\div |A(3)|=0.5\)
e)歸一化策略:\(\sigma_A^1(3,P)=\frac{2}{3}\),\(\sigma_A^1(3,B)=\frac{1}{3}\)
f)多次迭代,直至達到納什平衡
核心代碼實現:
class KuhnTrainer:
"""
P = pass
B = bet
payoff table:
P P higher player +1
P B P player2 +1
P B B higher player +2
B P player1 +1
B B higher player +2
"""
def __init__(self):
self.node_map: Dict[str, Node] = {}
def train(self, iterations):
"""
Train Kuhn poker
:param iterations:
:return:
"""
cards = [1, 2, 3]
util = 0
for i in range(iterations):
# shuffle cards
self.shuffle_cards(cards)
cur_util = self.cfr(cards, "", 1, 1)
util += cur_util
print("Average game value: ", util / iterations)
for k, v in self.node_map.items():
print(v)
def shuffle_cards(self, cards):
random.shuffle(cards)
def cfr(self, cards, history: str, p0, p1):
"""
Counterfactual regret minimization iteration
P P higher player +1
P B P player2 +1
P B B higher player +2
B P player1 +1
B B higher player +2
:param cards:
:param history:
:param p0:
:param p1:
:return:
"""
plays = len(history)
player = plays % 2
opponent = 1 - player
# Return payoff for terminal states
if plays > 1:
terminal_pass = history[plays - 1] == 'p'
double_bet = history[plays - 2: plays] == 'bb'
is_player_card_higher = cards[player] > cards[opponent]
if terminal_pass:
if history == 'pp':
return 1 if is_player_card_higher else -1
else:
return 1
elif double_bet:
return 2 if is_player_card_higher else -2
info_set = str(cards[player]) + history
# Get information set node or create it if nonexistant
node = self.node_map.get(info_set, None)
if node is None:
node = Node()
node.info_set = info_set
self.node_map[info_set] = node
# For each action, recursively call cfr with additional history and probability
strategy = node.get_strategy(p0 if player == 0 else p1)
util = [0] * NUM_ACTIONS
node_util = 0
for i in range(NUM_ACTIONS):
next_history = history + ("p" if i == 0 else "b")
util[i] = -self.cfr(cards, next_history, p0 * strategy[i], p1) if player == 0 else \
-self.cfr(cards, next_history, p0, p1 * strategy[i])
node_util += strategy[i] * util[i]
# For each action, compute and accumulate counterfactual regret
for i in range(NUM_ACTIONS):
regret = util[i] - node_util
node.regret_sum[i] += regret * (p1 if player == 0 else p0)
return node_util
實驗結果:
3.引申
CFR算法出現時就已經能夠解決德州撲克,但面對52張底牌、加注、過牌、河牌等復雜多變的情況使得德撲的博弈樹無論是深度還是廣度都十分的龐大,對計算資源和儲存資源上的開銷過於巨大,使得僅僅靠CFR算法攻克德撲十分困難。而CFR后續的研究者們都在費盡心力優化CFR算法的效率,致力於提高計算速度和壓縮存儲空間。在此,筆者簡單介紹幾種CFR變種算法,僅做了解。
3.1 CFR+:
與CFR算法不同的是,CFR+算法對累計平均策略做折減,對迭代的策略進行平均時,給近期迭代的策略賦予更高的權重;直觀上,越到后期,策略表現越好,因此在都策略做平均時,給近期策略更高的權重更有助於收斂。
在CFR+算法中,counterfactual utility被定義為以下形式:
在的基礎上,CFR+算法定義了一個\(regretlike value\),此時CFR+算法中的\(regret\)為一個累加值,而CFR算法定義\(Regret\)的為平均值,因此CFR+算法中的regret計算方式為:
另外,在CFR+算法中,最后輸出的平均策略為一下形式:
3.2 MCCFR:
MCCFR(Monte Carlo Counterfactual Regret Minimization)是蒙特卡洛算法和CFR算法的結合,其核心在於:在避免每輪迭代整棵博弈樹的同時,依然能夠保證 \(immediate\space counterfactual\space regret\) 的期望值保持不變。將葉子節點分割為不同的\(block:Q=\{Q_1,Q_2,...,Q_n\}\),且保證覆蓋所有的葉子結點。
定義\(Q_j\)是在當前迭代中選擇\(Q_i\)的概率:\(\Sigma_{j=1}^rQ_j=1\)。
定義\(Q(z)\)表示在當前迭代中采樣到葉子節點的概率:\(Q(z)=\Sigma_{j:z∈Q_j}Q_j\)
那么在選擇\(Q_j\)迭代時,得到的采樣虛擬值為:\(v_i(\sigma,I/j)=\Sigma_{z∈Q_j}∩Z_i\frac{1}{Q(z)}u_i(z)\pi^\sigma_{-i}(z[I],z)\)
通過一定的概率選擇不同的block,得到一個基於采樣的CFR算法。
3.3結語
除了上述介紹的兩個算法外,CFR算法的優化數不勝數,有提高計算速度的Discount-CFR、Warm Start、Total RBP,也有壓縮存儲空間的CFR-D、Continue-Resolving、Safe and Nested Subgame Solving等。
機器博弈是人工智能領域的重要研究方向。非完備信息博弈是機器博弈的子領域。非完備信息博弈中存在隱藏信息和信息不對稱的特點,和完備信息博弈相比,非完備信息博弈更加貼近現實生活中。例如,競標、拍賣、股票交易等現實問題中都存在隱藏信息和信息不對稱。因此,研究非完備信息博弈問題更有現實意義。德州撲克博弈包含了隱藏信息、信息不對稱和隨機事件等重要特性,它是典型的非完備信息博弈。對其的研究具有非常重大的意義,感興趣的讀者可深入了解。
4.參考文獻
[1] Brown, N., Kroer, C. and Sandholm, T.: Dynamic Thresholding and Pruning for Regret Minimization, AAAI Conference on Artificial Intelligence (2017).
[2] Lanctot, M., Waugh, K., Zinkevich, M. and Bowling, M.: Monte Carlo Sampling for Regret Minimization in Extensive Games, Advances in Neural Information Processing Systems 22 (Bengio, Y., Schuurmans, D., Lafferty, J. D., Williams, C. K. I. and Culotta, A., eds.), Curran Associates, Inc., pp. 1078–1086 (2009).
[3] Gibson, R. 2014. Regret Minimization in Games and the Development of Champion Multiplayer Computer PokerPlaying Agents. Ph.D. Dissertation, University of Alberta. Gilpin, A., and Sandholm, T. 2007. Lossless abstraction of imperfect information games. Journal of the ACM 54(5).
我們是行者AI,我們在“AI+游戲”中不斷前行。
前往公眾號 【行者AI】,回復【虎步龍行】,即可領取限量款紅包封面。快來和我們一起探討技術問題吧!