1. 程式人生 > 其它 >博弈論與強化學習實戰——CFR演算法——剪刀石頭布

博弈論與強化學習實戰——CFR演算法——剪刀石頭布

博弈論與強化學習實戰——CFR演算法——剪刀石頭布

目錄

感謝:

淺談德州撲克AI核心演算法:CFR - 掘金 (juejin.cn)

虛擬遺憾最小化演算法(CFR)基礎知識詳解 - 知乎 (zhihu.com)

一 遊戲介紹

  1. 有兩個參與者,

  2. 每個參與者有三個可選動作 剪刀石頭布 ,分別用0,1,2表示

  3. 獎勵:獲勝獎勵為1,失敗獎勵為-1,平局沒有獎勵,收益矩陣如下

  4. 博弈過程用博弈樹進行描述:

    第二個玩家在決策的時候有三個可能的狀態\(h1,h2,h3\),但由於三個狀態在同一個資訊集中,所以玩家2在決策的時候並不知到具體處於哪個資訊集,所以玩家2的決策並不依賴於玩家1的行動結果,從效果上來看就等同於兩者同時划拳。

  5. 玩家的策略即玩家選擇三個不同動作的概率,

使用程式碼將遊戲流程

#遊戲設定
NUM_ACTIONS = 3  #可選的動作數量
actions = [0,1,2] # 0代表剪刀scissors , 1代表石頭rock ,2 代表布 paper
actions_print=['剪刀','石頭','布']
#動作的收益 ,兩個人進行博弈,結果
utility_matrix = np.array([
                [0,-1,1],
                [1,0,-1],
                [-1,1,0]
])


"""基本資訊初始化"""
# 玩家,初始化
#策略
player1_strategy = np.array([0,0,1])
player2_strategy = np.array([0.4,0.3,0.3])
#動作收益
player1_utility = np.zeros(3)
player2_utility = np.zeros(3)

"""1局遊戲的過程"""

    print(f'----------------遊戲開始-------------------')
    # 使用當前策略 選擇動作
    action_p1 = np.random.choice(actions, p=player1_strategy)
    action_p2 = np.random.choice(actions, p=player2_strategy)
    print(f'玩家1 動作:{actions_print[action_p1]} ,玩家2 動作:{actions_print[action_p2]} .')
    # 得到收益
    reward_p1 = utility_matrix[action_p1, action_p2]
    reward_p2 = utility_matrix[action_p2, action_p1]
    # 輸出遊戲結果
    print(f'----遊戲結束-----')
    print(f'玩家1 收益{reward_p1}  ,玩家2 收益{reward_p2}.')

    # 更新玩家的收益
    player1_utility[action_p1] += reward_p1
    player2_utility[action_p2] += reward_p2
    # 輸出一局遊戲後的動作收益矩陣
    print(f'收益更新---------動作:{actions_print[0]}        {actions_print[1]}         {actions_print[0]}')
    print(f'玩家1的累計收益   收益:{player1_utility[0]};      {player1_utility[1]};      {player1_utility[2]} ')
    print(f'玩家2的累計收益   收益:{player2_utility[0]};      {player2_utility[1]};      {player2_utility[2]} ')

二 問題引出

假定現在有一個玩家(玩家1)的策略(動作集合上的概率分佈)為 0.4,0.3 ,0.3 ,那麼玩家2的策略應該是怎樣的呢?

方法一 :求解期望獎勵最大的策略

假定玩家2的概率分別為a,b,(1-a-b)

那麼其期望收益(獎勵乘以發生的概率)為:

\[[(0.4* a) *0+(0.3* a) *-1+(0.3* a) *1]+ \\ [(0.4* b) *1+(0.3* b) *0+(0.3* b) *-1]+ \\ [(0.4* 1-a-b) *-1+(0.3*1- a-b) *1+(0.3* 1-a-b) *0] \\ =0.2b+0.1a-0.1 \]

要想使得收益最大,結果為\(b=1\),

所以玩家2的策略應為\([0,1,0]\)

,此時能夠獲得的期望獎勵為\(0.1\)

方法2 : 使用CFR演算法求解

方法3 :使用強化學習方法求解

擴充套件問題:

  • 當對戰雙方都使用相同的演算法進行學習,最終結果會不會達到均衡?
  • 當雙方使用不同的學習演算法進行學習,哪個演算法達到均衡速度更快?

三 CFR演算法求解

1 Regret matching 演算法

1 遺憾值的定義

\[R^{T}(a)=\sum_{t} a \cdot r^{t}-\sum_{t} \sigma^{t} \cdot r^{t} \]

含義: 選擇動作a和事實上的策略(概率\(\sigma\))產生的收益的差別 ,也就是遺憾值(本可以獲得更多) ;

遺憾值大於0表示動作\(a\)比當前策略更好,遺憾值小於0表示動作\(a\)不如當前策略

2 Regret matching 演算法

\[\sigma^{T}(a)= \frac{R^{T-1}(a)^{+}}{\sum_{b \in A} R^{T-1}(b)^{+}} , \\ where\ x^+ = max(x,0) \]

上式中 \(R^{T-1}(a)\)表示動作\(a\)的歷史遺憾值,然後對其和0取最大值。

和0取最大值目的是要得到累計正的遺憾值,因為只有正的遺憾值對應的動作才是改進的方向。

這個結果就是得到歷史遺憾為正的動作,在所有的正的歷史遺憾對應的動作計算其分佈(也就是概率)然後作為下一次博弈的策略

3 演算法流程

Regret matching演算法流程為:

  • 對於每一位玩家,初始化所有累積遺憾為0。

  • for from 1 to T(T:迭代次數):

    ​ a)使用當前策略與對手博弈

    ​ b)根據博弈結果計算動作收益,利用收益計算後悔值

    ​ c)歷史後悔值累加

    ​ d)根據後悔值結果更新策略

  • 返回平均策略(累積後悔值/迭代次數)

作者:行者AI
連結:https://juejin.cn/post/7057430423499964424
來源:稀土掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

4 程式碼實現

完整程式碼:

# -*- coding: utf-8 -*-

"""
@author     : zuti
@software   : PyCharm
@file       : rock_cfr.py
@time       : 2022/11/21 9:26
@desc       :

"""
import numpy as np

#動作設定
NUM_ACTIONS = 3  #可選的動作數量
actions = [0,1,2] # 0代表剪刀scissors , 1代表石頭rock ,2 代表布 paper
actions_print=['剪刀','石頭','布']
#動作的收益 ,兩個人進行博弈,結果
utility_matrix = np.array([
                [0,1,-1],
                [-1,0,1],
                [1,-1,0]
])


"""基本資訊初始化"""
# 玩家,初始化
#策略
player1_strategy = np.array([0.4,0.3,0.3])
player2_strategy = np.array([1/3,1/3,1/3])
#動作收益
player1_utility = np.zeros(3)
player2_utility = np.zeros(3)
#遺憾值
player2_regret = np.zeros(3)
#每一局策略(動作的概率分佈)之和
player2_strategy_count = np.zeros(3)


for i in range(10000):
    """1局遊戲的過程"""
    #對策略進行計數
    player2_strategy_count += player2_strategy
    print(f'----------------遊戲開始-------------------')
    # 使用當前策略 選擇動作
    action_p1 = np.random.choice(actions, p=player1_strategy)
    action_p2 = np.random.choice(actions, p=player2_strategy)
    print(f'玩家1 動作:{actions_print[action_p1]} ,玩家2 動作:{actions_print[action_p2]} .')
    # 得到收益
    reward_p1 = utility_matrix[action_p2, action_p1]
    reward_p2 = utility_matrix[action_p1, action_p2]
    # 輸出遊戲結果
    print(f'----遊戲結束-----')
    print(f'玩家1 收益{reward_p1}  ,玩家2 收益{reward_p2}.')

    # 更新玩家的收益
    player1_utility[action_p1] += reward_p1
    player2_utility[action_p2] += reward_p2
    # 輸出一局遊戲後的動作收益矩陣
    print(f'收益更新---------動作:{actions_print[0]}        {actions_print[1]}         {actions_print[2]}')
    print(f'玩家1的累計收益   收益:{player1_utility[0]};      {player1_utility[1]};      {player1_utility[2]} ')
    print(f'玩家2的累計收益   收益:{player2_utility[0]};      {player2_utility[1]};      {player2_utility[2]} ')
    #
    """遺憾值更新"""
    # 根據結果收益計算所有動作的遺憾值
    for a in range(3):
        # 事後角度 選擇別的動作的收益
        counterfactual_reward_p2 = utility_matrix[action_p1,a ]  # 如果選擇動作a(而不是事實上的動作action_p1) ,會獲得的收益
        regret_p2 = counterfactual_reward_p2 - reward_p2  # 選擇動作a和事實上的動作action_p1產生的收益的差別 ,也就是遺憾值(本可以獲得更多)
        # 更新玩家的動作遺憾值,歷史遺憾值累加
        player2_regret[a] += regret_p2

    print(f'遺憾值更新--------動作:{actions_print[0]}         {actions_print[1]}          {actions_print[0]}')
    print(f'玩家2的累計遺憾值     {player2_regret[0]};      {player2_regret[1]};         {player2_regret[2]} ')

    """根據遺憾值更新策略"""
    """遺憾值歸一化"""
    # 歸一化方法: 1 只看遺憾值大於0的部分,然後計算分佈
    palyer2_regret_normalisation = np.clip(player2_regret, a_min=0, a_max=None)
    print(f'遺憾值歸一化')
    print(f'玩家1歸一化後的累計遺憾值     {palyer2_regret_normalisation [0]};      {palyer2_regret_normalisation [1]};         {palyer2_regret_normalisation [2]} ')
    """根據歸一化後的遺憾值產生新的策略"""
    palyer2_regret_normalisation_sum = np.sum(palyer2_regret_normalisation)  # 求和
    if palyer2_regret_normalisation_sum > 0:
        player2_strategy = palyer2_regret_normalisation / palyer2_regret_normalisation_sum
    else:
        player2_strategy = np.array([1 / 3, 1 / 3, 1 / 3]) #否則就採取平均策略



"""最終結果:得到平均策略"""
print(f'-----迭代結束,得到最終的平均策略---------')
#根據累計的策略計算平均策略
average_strategy = [0, 0, 0]
palyer2_strategy_sum = sum(player2_strategy_count)
for a in range(3):
    if palyer2_strategy_sum > 0:
        average_strategy[a] = player2_strategy_count[a] / palyer2_strategy_sum
    else:
        average_strategy[a] = 1.0 / 3
print(f'玩家2經過迭代學習得到的平均策略為')
print(f'玩家2的動作 \n 動作:{actions_print[0]} 概率:{average_strategy[0]};動作:{actions_print[1]} 概率:{average_strategy[1]};動作:{actions_print[2]} 概率:{average_strategy[2]} ')


2 CFR演算法

1 博弈樹中間結點的收益

概念

基於終止狀態的收益\(u\)對博弈樹中的每個節點都定義一個收益。

最主要的目的是給出博弈樹中的中間非葉子結點的收益。

當玩家\(p\)遵循策略\(σ\)時,對於博弈樹中任意的一個狀態\(h\),該狀態的收益定義為:

\[u_{p}^{\sigma}(h)=\sum_{z \in Z, h \sqsubset z} \pi^{\sigma}(z) u_{p}(z) \]

式子中,\(u_p(z)\) 按著前面的定義即為 玩家\(p\)到達終止狀態\(z\)(葉子節點)所獲得的收益;

前面的\(\pi^\sigma(z)\)表示從初始狀態出發,當所有玩家都遵循策略\(σ\)時,到達終止狀態\(z\)的概率;

求和即表示從初始狀態開始把所有包含路徑\(h\)到達終點\(z\)的序列進行求和

這個收益即表示 玩家\(p\) 從博弈起點到中間狀態\(h\) 再根據策略\(\sigma\)到達終點\(z\)得到的收益。

可以將右端前一項根據概率式1 進行拆分 ,得到

\[\begin{aligned} u_{p}^{\sigma}(h) &=\sum_{z \in Z, h \sqsubset z} \pi^{\sigma}(z) u_{p}(z) \\ &=\sum_{z \in Z, h \sqsubset z} \pi_{p}^{\sigma}(z) \pi_{-p}^{\sigma}(z) u_{p}(z) \ 參與者拆分(1) \\ &=\sum_{z \in Z, h \sqsubset z} \pi^{\sigma}(h) \pi^{\sigma}(z \mid h) u_{p}(z) \ 路徑拆分(3) \\ &=\pi_{p}^{\sigma}(h) \sum_{z \in Z, h \sqsubset z} \pi_{-p}^{\sigma}(h) \pi^{\sigma}(z \mid h) u_{p}(z) 參與者+路徑拆分(1)+(3) \end{aligned} \]

根據此定義,整局遊戲的收益即為博弈樹根節點的收益 $ u_{p}{\sigma}=u_{p}{\sigma}(\varnothing) $

當玩家\(p\)遵循策略\(σ\)時,對於博弈樹中的一個資訊集\(I \in \mathcal{I}\)的收益定義為:

\[u_{p}^{\sigma}(I)=\sum_{h \in I} u_{p}^{\sigma}(h) \]

算例

這裡給出第二個問題作為一個計算的例子:

玩家\(p\)(為玩家2),其策略\(\sigma\)\([a,b,1-a-b]\) ,其他玩家\(-p\)(也就是玩家1)的策略\(\sigma\)\([0.4,0.3,0.3]\),博弈樹見上。

根據上述定義,我們來嘗試計算博弈樹中間結點\(h1\)的收益

首先,包含中間結點\(h1\),從遊戲開始到達最終結果\(z1,z2,z3\)的路徑總共3條。

根據定義式:

第二項:玩家\(p\)玩家2在最終結果的收益分別為

\[u_{p2}(z1)=0 , u_{p2}(z2)= 1, u_{p2}(z3)= -1 \]

第一項:從起點出發,經過中間結點\(h1\),到達最終結果\(z1,z2,z3\)的概率,根據玩家\(p2\)\(-p\)(也就是玩家1)的策略\(\sigma\)計算為

\[0.4a,0.4b,0.4(1-a-b) \]

概率乘以收益再求和便得到了博弈樹中間結點\(h1\)的收益

\[\begin{aligned} u_{p2}^{\sigma}(h1) &= \sum_{z \in Z, h \sqsubset z} \pi^{\sigma}(z) u_{p2}(z) \\ &= 0.4a * 0 + 0.4 b* 1+0.4(1-a-b) *-1 \\ &= 0.4a+0.8b-0.4 \end{aligned} \]

同樣的方法還可以得到博弈樹中間結點\(h2,h3\)的收益

\[\begin{aligned} u_{p2}^{\sigma}(h2) &= \sum_{z \in Z, h \sqsubset z} \pi^{\sigma}(z) u_{p}(z) \\ &= 0.3a * -1 + 0.3 b* 0+0.3(1-a-b) *1 \\ &= -0.6a-0.3b+0.3 \end{aligned} \begin{aligned} u_{p2}^{\sigma}(h3) &= \sum_{z \in Z, h \sqsubset z} \pi^{\sigma}(z) u_{p}(z) \\ &= 0.3a * 1 + 0.3 b* -1+0.3(1-a-b) *0 \\ &= 0.3a - 0.3b \end{aligned} \]

資訊集\(I\)包含三個結點\(h1,h2,h3\),因此資訊集\(I\)的收益為

\[\begin{aligned} u_{p2}^{\sigma}(I) &=\sum_{h \in I} u_{p2}^{\sigma}(h) \\ &=u_{p2}^{\sigma}(h1)+u_{p2}^{\sigma}(h2)+u_{p2}^{\sigma}(h3) \\ &= 0.1a+0.2b-0.1 \end{aligned} \]

理解:資訊集\(I\)的收益是基於玩家\(-p\)(玩家1)的策略\(\sigma\) 和 從開始到達最終結點的各條路徑。

如果玩家\(p\)(玩家2)想使在資訊集\(I\)的收益最大,那麼玩家\(p\)(玩家2)的策略(動作集合上的概率)為\([0,1,0]\),能夠獲得的期望收益為\(0.1\)

這個結果和我們之前的計算是一致的。由於資訊集,所以遍歷這個博弈樹和矩陣博弈的效果是完全相同的。

2 反事實值

概念

\[v_{p}^{\sigma}(h)=\sum_{z \in Z, h \sqsubset z} \pi_{-p}^{\sigma}(h) \pi^{\sigma}(z \mid h) u_{p}(z) \]

看這個式子的定義:

右端第一項\(\pi_{-p}^{\sigma}(h)\) 表示 其他玩家\(-p\)選擇策略\(\sigma\) 從起點到達中間結點\(h\)的概率 ;

第二項\(\pi^{\sigma}(z \mid h)\) 表示路徑 經過中間結點\(h\),然後根據策略\(\sigma\)到達最終結點\(z\)的概率 ,

右端第三項 表示 玩家\(p\)在最終結點\(z\)的收益 , 然後對所有經過中間結點\(h\)到達最終結點\(z\)的路徑進行求和。

結合第2小節中關於\(π\)概率的三個等式,我們可以很容易地推匯出狀態\(h\)的收益值與反事實值之間的關係:

\[\begin{aligned} u_{p}^{\sigma}(h)&=\Sigma_{z \in Z, h \sqsubset z} \pi^{\sigma}(z) u_{p}(z) \\ &=\Sigma_{z \in Z, h \sqsubset z} \pi^{\sigma}(h) \pi^{\sigma}(z \mid h) u_{p}(z) \\ & =\Sigma_{z \in Z, h \sqsubset z} \pi_{p}^{\sigma}(h) \pi_{-p}^{\sigma}(h) \pi^{\sigma}(z \mid h) u_{p}(z) \\ & =\pi_{p}^{\sigma}(h) \Sigma_{z \in Z, h \sqsubset z} \pi_{-p}^{\sigma}(h) \pi^{\sigma}(z \mid h) u_{p}(z) \\ & =\pi_{p}^{\sigma}(h) v_{p}^{\sigma}(h) \\ \end{aligned} \]

玩家p在結點\(h\)的期望收益既與其他玩家\(-p\)的策略\(\pi_{-p}^{\sigma}(h)\)和到終點玩家\(p\)的收益\(u_{p}(z)\),又和玩家p的策略\(\pi_{p}^{\sigma}(h)\)有關。

當終點收益和其他玩家的策略等其他因素是一定的時候,玩家\(p\)在結點\(h\)的期望收益就只與玩家\(p\)的策略有關,這時候把除玩家\(p\)的策略以外的因素(其他玩家的策略和收益的乘積),即不考慮玩家\(p\)的策略影響下玩家\(p\)在結點\(h31\)收益期望 稱之為反事實值。

當除玩家\(p\)的策略以外的因素固定的情況下,玩家\(p\)在結點\(h\)的期望收益就只取決於玩家\(p\)的策略,當玩家選定自己的策略想要到達這個狀態時候,玩家可以獲得一個在這個狀態的期望收益,如果玩家\(p\)特別想要到達這個狀態,這時候\(\pi_{p}^{\sigma}(h)=1\),這個時候有兩個含義,一當結點\(h\)實在玩家選擇動作之前的結點,其含義為是玩家\(p\)的策略不影響這個中間狀態期望的收益,二當結點\(h\)實在玩家選擇動作之前的結點,其含義為玩家選擇策略\(\sigma\),想要盡力促成這個結果,獲得一個在結點\(h\)的收益。

\(\pi_{p}^{\sigma}(h)=0\)的時候,這時只有結點\(h\)在玩家\(p\)之後才有這個情況,這個時候玩家採取策略\(\sigma\)(動作分佈為\([0,a,\cdots,z]\))的目的是來儘量避免到達中間結點\(h\)

反事實值實際上就反映了不考慮玩家\(p\)採取策略\(\sigma\)對到達中間結點\(h\)的影響的時候,事實上玩家\(p\)的期望收益。

同樣的,將概念擴充套件到資訊集上有 the counterfactual value for player $p $ of an information set \(I \in \mathcal{I}_p\) is

\[v_{p}^{\sigma}(I)=\sum_{h \in I} v_{p}^{\sigma}(h) \\ v_{p}^{\sigma}(I \cdot a)=\sum_{h \in I \cdot a} v_{p}^{\sigma}(h) \]

算例

同樣給出第二個問題作為一個計算的例子:

玩家\(p2\)(為玩家2),其策略\(\sigma\)\([a,b,1-a-b]\) ,其他玩家\(-p\)(也就是玩家1)的策略\(\sigma\)\([0.4,0.3,0.3]\),博弈樹見上。

根據上述定義,我們來嘗試計算博弈樹中間結點\(h1\)的收益

首先,包含中間結點\(h1\),從遊戲開始到達最終結果\(z1,z2,z3\)的路徑總共3條,

根據定義式

右端第一項\(\pi_{-p}^{\sigma}(h)\) 表示 其他玩家\(-p\)(也就是玩家1)選擇策略\(\sigma\) 從起點到達中間結點\(h1\)的概率 :

\[\pi_{-p}^{\sigma}(h1) = 0.4 \]

第二項\(\pi^{\sigma}(z \mid h)\) 表示路徑 經過中間結點\(h\),然後根據策略\(\sigma\)到達最終結點\(z\)的概率 ;右端第三項 表示 玩家\(p\)(玩家2)在最終結點\(z\)的收益 ;兩者相乘表示經過中間結點的收益

\[u_p(z1)=0 , u_p(z2)= 1, u_p(z3)= -1 \\ \pi^{\sigma}(z1 \mid h1) = a,\pi^{\sigma}(z2 \mid h1) =b ,\pi^{\sigma}(z3 \mid h1) = 1-a-b \]

相乘求和就得到了中間結點\(h1\)的反事實值

\[\begin{aligned} v_{p2}^{\sigma}(h1) &=\sum_{z \in Z, h1 \sqsubset z} \pi_{-p}^{\sigma}(h1) \pi^{\sigma}(z \mid h1) u_{p2}(z) \\ &= 0.4a * 0 + 0.4 b* 1+0.4(1-a-b) *-1 \\ &= 0.4a+0.8b-0.4 \end{aligned} \]

這裡計算出來的反事實值與前面計算出來的收益值相等,而兩者其實是有如下關係的

\[u_{p}^{\sigma}(h) =\pi_{p}^{\sigma}(h) v_{p}^{\sigma}(h) \\ \]

在這裡也就是

\[u_{p2}^{\sigma}(h1) =\pi_{p2}^{\sigma}(h1) v_{p2}^{\sigma}(h1) \]

根據我們的計算又有

\[u_{p2}^{\sigma}(h1) = v_{p2}^{\sigma}(h1) \]

所以唯一的解釋就是

\[\pi_{p2}^{\sigma}(h1) =1 \]

這裡怎麼來理解呢:

玩家\(p2\)選擇策略\(\sigma\)到達中間結點\(h1\)的概率為1,也就是到達中間結點\(h1\)和玩家\(p2\)的策略無關。這是因為結點\(h1\)是在玩家\(p2\)採取行動之前的結點,所以玩家採取的策略不影響這個結點的期望收益。

只有當玩家\(p\)的策略選擇影響到後續中間結點\(h\)的時候,玩家\(p\)在中間結點\(h\)的收益和玩家\(p\)在中間結點\(h\)的反事實值會有差別,差別就是玩家選擇的策略\(\pi^\sigma_p(h)\)(動作概率),選擇該動作的概率越小,反事實值越大。下面給出一個示例進行說明.。

博弈樹如上圖所示,有三個參與者:玩家1,玩家2,玩家3 ,博弈的過程為玩家1,玩家2,玩家3依次行動。

玩家1有三個動作\([0,1,2]\),其策略(動作概率)為\([0.4,0.3,0.3]\)。玩家2有兩個動作,其策略為\([a,1-a]\)。玩家3有兩個動作,其策略為\([b,1-b]\)

可以參照上面的過程來計算玩家2在結點\(h31\)的收益\(u^\sigma_{p2}(h31)\)和反事實值\(v^\sigma_{p2}(h31)\)

收益計算:把所有從遊戲起點經過中間結點\(h31\)的路徑的概率乘以收益求和

\[\begin{aligned} u^\sigma_{p2}(h31) &= \sum_{z1,z2} \pi^{\sigma}(z) u_{p2}(z) \\ & = (0.4 \cdot a \cdot b) *r1 +(0.4 \cdot a \cdot 1-b) *r2 \\ \end{aligned} \]

反事實值計算:除玩家p2以外的人遵循策略到達中間結點\(h31\)的概率 乘以 從中間結點\(h31\)到結果\(z1,z2\)的不同路徑的分佈及收益

\[\begin{aligned} v_{p2}^{\sigma}(h31) &= \sum_{z1,z2} \pi_{-p}^{\sigma}(h31) \pi^{\sigma}(z \mid h31) u_{p2}(z) \\ &= 0.4 *b* r1 + 0.4 *1-b * r2 \\ \end{aligned} \]

兩者的差別就是

\[\pi_{p2}^{\sigma}(h31) = a \\ u^\sigma_{p2}(h31) = a * v_{p2}^{\sigma}(h31) \]

當其他玩家的策略和結點收益是既定的時候,後面這一項是事實既定,它不隨玩家\(p2\)的策略改變。

​ 當\(p2\)想要到達結點\(h2\)時,它可以提高選擇動作\(0\)的比重,即當策略為\([1,0]\)時,玩家\(p2\)在結點\(p2\)的收益為\(1 * v_{p2}^{\sigma}(h31)\)

​ 當\(p2\)不想要到達結點\(h2\)時,它可以降低選擇動作\(0\)的比重,即當策略為\([0,1]\)時,玩家\(p2\)在結點\(p2\)的收益為\(0\),這個時候也就是由於玩家\(p2\)的策略選擇,結點\(h2\)是永遠不可能到達的,即這個結點事實上是不存在的。

當玩家\(p2\)的策略和結點的收益是固定的時候,其他玩家的策略選擇就決定了玩家\(p2\)在結點\(h31\)的收益。這時侯,反事實值越大,反映其他玩家通過選擇策略,想要到達這個結點。反事實值越小,反映其他玩家選擇策略,想要儘量避免到達這個結點,其他玩家可以調整策略使得$v_{p2}^{\sigma}(h31) =0 \(,這個時候結點\)h31$就是不存在的。

當玩家2選擇動作0的概率a越大,玩家2在中間結點\(h31\)獲得的期望獎勵值就越大。因為只有到達結點\(h31\)才會有中間這個結點的獎勵,如果不到達結點\(h31\)(此時,動作a的概率為0),那麼自然玩家2在結點\(h31\)就不會有收益。

玩家p2在結點\(h31\)的期望收益既與其他玩家的策略和終點收益,又和玩家p2的策略有關。當其他因素是一定的時候,就只與玩家p2的策略有關,把從其他玩家的策略和收益的乘積即不考慮\(p2\)的策略影響下結點\(h31\)收益期望 稱之為反事實值。

把結點收益固定,那麼玩家\(p2\)選擇動作0的概率(也就是玩家2的策略)會影響反事實值的大小。

\(a\)越大,反事實值越小。如果\(a\)是1,此時反事實值和收益相等,就說明此時玩家2的動作是固定的或者玩家2的策略不影響狀態\(h2\)出現的概率,這個時候說明玩家2採取策略\(\sigma=[1,0]\)一定能夠到達狀態\(h31\)

\(a\)越小,反事實值越大。如果\(a\)是0.0001,此時反事實值比上述情況大得多,說明當玩家2採取策略\(\sigma=[0.0001,0.9999]\)時,能夠到達狀態\(h31\)的可能性很小。

反事實值\(v_{p2}^{\sigma}(h31)\)就說明了玩家\(p2\)選擇策略\(\sigma\)對到達狀態\(h31\)的可能性,反事實值越大,說明在玩家2使用策略\(\sigma\)時越不可能到達狀態\(h31\) ,當反事實值與收益相等的時候就說明玩家\(p2\)選擇策略\(\sigma\)不影響到達狀態\(h31\)的可能性或者所選擇的策略能夠一定到達狀態\(h31\)

3 反事實遺憾

概念

\[R^{T}(I, a)=\sum_{t=1}^{T} v_{p}^{\sigma^{t}}(I \cdot a)-\sum_{t=1}^{T} \sum_{a \in A(I)} \sigma^{t}(I, a) v_{p}^{\sigma^{t}}(I, a) \]

其定義是基於某個資訊集\(I\)和在這個資訊集上的特定動作來定義的。

右端後面一項,是對在該資訊集上動作期望遺憾值的累和,右端第一項選取該動作的遺憾值。

算例

同樣給出第二個問題作為一個計算的例子:

玩家\(p2\)(為玩家2),其策略\(\sigma\)\([a,b,1-a-b]\) ,其他玩家\(-p\)(也就是玩家1)的策略\(\sigma\)\([0.4,0.3,0.3]\),博弈樹見上。

根據上述定義,我們來嘗試計算博弈樹在第一次迭代時候,玩家\(p2\)在資訊集\(I=\{h1,h2,h3\}\)採取動作\(a=0\)的反事實遺憾\(R^1(I,a)\)

由於是第一次迭代,沒有歷史資訊

\[R^1(I,0) =R^0(I,0)+ v_{p2}^{\sigma^{t1}}(I \cdot 0) - \sum_{a \in [0,1,2]} \sigma^{t}(I, a) v_{p}^{\sigma^{t}}(I, a) \\ R^0(I,0) = 0(因為是第一次迭代,所以累計值為0) \]

首先計算反事實收益

\[\begin{aligned} v_{p}^{\sigma^{t}}(I, a) &=\sum_{h \in I \cdot a} v_{p}^{\sigma^{t}}(h) \\ &=\sum_{h \in I \cdot a} \sum_{z \in Z, h \sqsubset z} \pi_{-p}^{\sigma^{t}}(h) \pi^{\sigma^{t}}(z \mid h) u_{p}(z) \end{aligned} \] \[\begin{aligned} v_{p2}^{\sigma^{t}}(I, 0) &= v_{p2}^{\sigma^{t}}(h1 , 0)+v_{p2}^{\sigma^{t}}(h2 , 0)+v_{p2}^{\sigma^{t}}(h3 , 0) \\ &=[\pi_{-p}(h1,0)\pi(z1|h1,0)u_p(z1)]+ [\pi_{-p}(h2,0)\pi(z4|h2,0)u_p(z4)] +[\pi_{-p}(h3,0)\pi(z7|h3,0)u_p(z7)] \\ &=0.4 * 1* 0+ 0.3 *1 *-1 +0.3*1*1 \end{aligned} \]

同樣還可以得到

\[\begin{aligned} v_{p2}^{\sigma^{t}}(I, 1) &= v_{p2}^{\sigma^{t}}(h1 , 1)+v_{p2}^{\sigma^{t}}(h2 , 1)+v_{p2}^{\sigma^{t}}(h3 \cdot 1) \\ &=[\pi_{-p}(h1,1)\pi(z2|h1,0)u_p(z2)]+ [\pi_{-p}(h2,1)\pi(z4|h2,a)u_p(z4)] +[\pi_{-p}(h3,1)\pi(z8|h3,1)u_p(z8)] \\ &=0.4 * 1* 1+ 0.3 *1 *0 +0.3*1*-1 \end{aligned} \] \[\begin{aligned} v_{p2}^{\sigma^{t}}(I, 2) &= v_{p2}^{\sigma^{t}}(h1 , 2)+v_{p2}^{\sigma^{t}}(h2 , 2)+v_{p2}^{\sigma^{t}}(h3 \cdot 2) \\ &=[\pi_{-p}(h1,2)\pi(z3|h1,2)u_p(z3)]+ [\pi_{-p}(h2)\pi(z6|h2,2)u_p(z6)] +[\pi_{-p}(h3)\pi(z9|h3,2)u_p(z9)] \\ &=0.4 * 1*- 1+ 0.3 *1 *1 +0.3*1*0 \end{aligned} \]

在資訊集\(I\)上選擇動作\(0,1,2\)的概率分別為​

\[\sigma^1(I,0) = a\\ \sigma^1(I,1) = b\\ \sigma^1(I,2) = 1-a-b\\ \]

因此在資訊集\(I\)上的期望反事實值為

\[\begin{aligned} &\sum_{a \in [0,1,2]} \sigma^{t}(I, a) v_{p}^{\sigma^{t}}(I, a) \\ &= 0 * a + 0.1 *b + (-0.1) *(1-a-b) \\ &=0.1a+0.2b-0.1 \end{aligned} \]

經過上述計算我們會發現此時計算出來的玩家\(p2\)在資訊集\(I\)上的期望反事實值和在第一部分計算出來的資訊集\(I\)上玩家\(p2\)的期望收益是一樣的。其原因就是資訊集\(I\)的出現並不依賴於玩家\(p2\)的動作。

反事實遺憾為

\[R^1(I,0) =R^0(I,0)+ v_{p2}^{\sigma^{t1}}(I \cdot 0) - \sum_{a \in [0,1,2]} \sigma^{t}(I, a) v_{p}^{\sigma^{t}}(I, a) \\ = 0 +0 - (0.1a+0.2b-0.1) \]

因此。第一次迭代時,玩家\(p2\)在資訊集\(I\)上採取動作\(0\)的反事實遺憾為\(- (0.1a+0.2b-0.1)\)

4 原始CFR演算法

演算法步驟:

  1. Generate strategy profile σt from the regrets, as described above.

    根據regret-matching演算法計算本次博弈的策略組

    For all $ I \in \mathcal{I}, a \in A(I) $, and $ p=p(I) $:

    \[\sigma_{p}^{t}(I, a)=\left\{\begin{array}{ll}R^{t}(I, a)^{+} / \sum_{b \in A(I)} R^{t}(I, b)^{+} & \sum_{b \in A(I)} R^{t}(I, b)^{+}>0 \\ \frac{1}{|A(I)|} & \text { otherwise }\end{array}\right. \]

    因為動作\(a\)的遺憾值為正表示該動作正確,在下次迭代中無需更改,體現了遺憾匹配演算法“有錯就改,無錯不改”的特點。

    其中如果所有動作的遺憾值為0,則在下次迭代中採取每一種動作的概率相同。

  2. Update the average strategy profile to include the new strategy profile.

    使用上一步中新計算的策略組更新平均策略組

    For all $ I \in \mathcal{I}, a \in A(I) $, and $ p=p(I) $:

    \[\begin{aligned} \bar{\sigma}_{p}^{t}(I, a) &=\frac{1}{t} \sum_{t^{\prime}=1}^{t} \pi_{p}^{\sigma^{t}}(I) \sigma_{p}^{t^{\prime}}(I, a) \\ &=\frac{t-1}{t} \bar{\sigma}_{p}^{t-1}(I, a)+\frac{1}{t} \pi_{p}^{\sigma^{t}}(I) \sigma_{p}^{t}(I, a) \end{aligned} \]

    上式表示玩家\(p\)的平均策略\(\bar{\sigma}_{p}^{t}(I, a)\),即為前\(t\)次的即時策略的平均值

  3. Using the new strategy profile, compute counterfactual values.

    使用第一步計算的新策略組計算雙方參與者的反事實收益值

    For all $ I \in \mathcal{I}, a \in A(I) $, and $ p=p(I) $:

    \[\begin{aligned} v_{p}^{\sigma^{t}}(I, a) &=\sum_{h \in I \cdot a} v_{p}^{\sigma^{t}}(h) \\ &=\sum_{h \in I \cdot a} \sum_{z \in Z, h \sqsubset z} \pi_{-p}^{\sigma^{t}}(h) \pi^{\sigma^{t}}(z \mid h) u_{p}(z) \end{aligned} \]
  4. Update the regrets using the new counterfactual values.

    使用反事實收益值更新遺憾值

    For all $ I \in \mathcal{I}, a \in A(I) $, and $ p=p(I) $:

    \[R^{t}(I, a)=R^{t-1}(I, a)+v_{p}^{\sigma^{t}}(I, a)-\sum_{a \in A(I)} \sigma^{t}(I, a) v_{p}^{\sigma^{t}}(I, a) \]
  • 對於每一位玩家,初始化反事實遺憾值\(R^t(I,a)\)為0 平均策略\(\bar{\sigma}_p(I,a)\)為0 ,初始化策略為隨機策略

  • for from 1 to T(T:迭代次數):

    ​ a) 根據regret-matching演算法計算本次博弈的策略組\(\sigma_p^t(I,a)\)

    ​ a)使用當前策略更新平均策略\(\bar{\sigma}^t_p(I,a)\)

    ​ c)計算反事實收益值\(v^{\sigma^t}_p(I,a)\)

    ​ d) 使用反事實收益值計算遺憾值\(R^t(I,a)\)

  • 返回平均策略(累積後悔值/迭代次數)

虛擬碼:

演算法分析:

通過上述演算法步驟我們可以得到:

對於每個資訊集\(I\)和動作\(a\) , \(R\)\(\bar{\sigma}\)都相當於一個歷史列表,儲存了過去迭代過程中的累計遺憾值和累計平均策略 。 \(\sigma\)\(v\)是臨時列表,用來儲存當前的策略和反事實值。

值得注意的是,雖然 CFR 處理的都是行為策略(即在每個資訊集上動作的概率分佈),但求平均策略的過程,是在混合策略或序列形式策略的空間中進行的。使用序列形式進行描述, 維持一個玩家\(p\)的平均策略, 是通過在每個資訊集\(I \in \mathcal{I}\)和動作\(a \in A(I)\)上 增量地更新$ \bar{\sigma}{p}(I, a)=\sum{t=1}^{T} \pi_{p}^{t}(I) \sigma^{t}(I, a) $完成的。這裡,我們忽略了上面給出的演算法步驟第二種把和轉化為平均的形式,這是因為在將序列形式的策略轉化為行為形式的策略 其實是涉及到了 在每個資訊集上的概率的正則化。

通過在博弈樹的狀態深度優先遍歷中結合策略計算、平均策略更新和價值計算,可以提高 CFR 的實現效率。演算法在下一部分

程式碼實現

# -*- coding: utf-8 -*-

"""
@author     : zuti
@software   : PyCharm
@file       : rockpaperscissors_cfr_1.py
@time       : 2022/11/24 15:51
@desc       :
嘗試使用CFR演算法來實現剪刀石頭布遊戲
第一次嘗試,使用演算法流程進行

"""
import numpy as np

"""遊戲設定"""
# 動作設定
NUM_ACTIONS = 3  # 可選的動作數量
actions = [0, 1, 2]  # 0代表剪刀scissors , 1代表石頭rock ,2 代表布 paper
actions_print = ['剪刀', '石頭', '布']
# 動作的收益 ,兩個人進行博弈,結果
utility_matrix = np.array([
    [0, 1, -1],
    [-1, 0, 1],
    [1, -1, 0]
])

""" 遊戲基本情況"""
# 玩家1 策略固定 [0.4,0.3,0.3]
# 玩家2,初始化策略為隨機策略[1/3,1/3,1/3],的目的是通過CFR演算法,學習得到一個能夠獲得最大收益的策略
# 整個遊戲只有一個資訊集,其中包含三個結點,在這個資訊集合上可選的動作有3個

# 玩家,初始化
# 策略
player1_strategy = np.array([0.4, 0.3, 0.3])
player2_strategy = np.array([1 / 3, 1 / 3, 1 / 3])
# 玩家2在資訊集I上關於三個動作的累計的遺憾值
player2_regret_Information = np.zeros(NUM_ACTIONS)
# 玩家2在資訊集I上關於三個動作的累計的平均策略
player2_average_strategy = np.zeros(NUM_ACTIONS)



def RegretToStrategy(regret):
    """
    使用遺憾值匹配演算法 ,根據累計的遺憾值,來確定新的策略

    :return:  新的策略 strategy
    """
    # 歸一化方法: 1 只看遺憾值大於0的部分,然後計算分佈
    regret_normalisation = np.clip(regret, a_min=0, a_max=None)
    #print(f'歸一化後的累計遺憾值     {regret_normalisation[0]};      {regret_normalisation[1]};         {regret_normalisation[2]} ')
    """根據歸一化後的遺憾值產生新的策略"""
    regret_normalisation_sum = np.sum(regret_normalisation)  # 求和

    strategy = np.zeros( NUM_ACTIONS)
    if regret_normalisation_sum > 0:
        strategy = regret_normalisation / regret_normalisation_sum
    else:
        strategy = np.array([1 / 3, 1 / 3, 1 / 3])  # 否則就採取平均策略

    return strategy

def UpdateAverage(strategy , average_strategy ,count ):
    """
    根據本次計算出來的策略,更新平均策略
    進行歷史累計,然後對迭代次數進行平均
    :param strategy:
    :param average_strategy:
    :return:
    """
    average_strategy_new = np.zeros( NUM_ACTIONS)

    #不管玩家p2選擇哪個動作,資訊集I 的出現概率為 1
    for i in range(NUM_ACTIONS):
        average_strategy_new[i] =  (count -1) / count * average_strategy[i] + 1/count * 1 * strategy[i]

    return average_strategy_new


def StrategyToValues(strategy):
    """
    計算反事實收益值 v
    :param strategy:
    :return:
    """
    #首先計算資訊集I上所有動作的反事實收益 ,見第三節算例

    #計算每個動作的反事實收益
    counterfactual_value_action = np.zeros(NUM_ACTIONS)
    for  i in  range(NUM_ACTIONS) :

        counterfactual_h1 = player1_strategy[0] * 1 * utility_matrix[0][i]
        counterfactual_h2 = player1_strategy[1] * 1 * utility_matrix[1][i]
        counterfactual_h3 = player1_strategy[2] * 1 * utility_matrix[2][i]

        counterfactual_value_action[i] = counterfactual_h1 + counterfactual_h2 +counterfactual_h3


    return counterfactual_value_action


def UpdateRegret( regret , strategy , counterfactual_value_action):
    """
    更新累計反事實遺憾

    :param regret:
    :param strategy:
    :param counterfactual_value_action:
    :return:
    """

    # 每個動作的反事實值 乘以 策略(每一個動作的概率) 求和 得到 期望
    counterfactual_value_expect  = np.sum(counterfactual_value_action * strategy)

    for i  in range(NUM_ACTIONS):
        regret[i] = regret[i] +   counterfactual_value_action[i] - counterfactual_value_expect

    return  regret


def NormaliseAverage(average_strategy):
    """
    歸一化得到最後結果

    :param average_strategy:
    :return:
    """
    strategy_sum = sum(average_strategy)
    strategy = np.zeros(NUM_ACTIONS)
    for i in range( NUM_ACTIONS):

        strategy[i] = average_strategy[i] / strategy_sum

    return   strategy


#使用CFR求
for count in range(10):
    print(f'玩家2 當前策略 :{player2_strategy}')
    #2 根據當前策略,更新平均策略
    player2_average_strategy = UpdateAverage(player2_strategy , player2_average_strategy ,count+1 )
    print(f'累計平均策略 :{player2_average_strategy}')
    # 3 根據當前策略計算反事實收益
    player2_counterfactual_value_action = StrategyToValues(player2_strategy)
    print(f'當前策略對應的反事實收益 :{player2_counterfactual_value_action}')
    #4 更新累計反事實遺憾
    player2_regret_Information = UpdateRegret(player2_regret_Information, player2_strategy, player2_counterfactual_value_action)
    print(f'累計反事實遺憾 :{player2_regret_Information}')
    # 1 用遺憾值匹配演算法 ,根據累計的遺憾值,來確定新的策略
    player2_strategy = RegretToStrategy(player2_regret_Information)
    print(f'-------------迭代次數{count+1}------------')

result = NormaliseAverage(player2_average_strategy)
print(f'最終結果:{result}')