零和ゲームのナッシュ均衡

quantecon.game_theoryscipy.optimize.linprog で求めてみる


In [1]:
import numpy as np
from scipy.optimize import linprog
import quantecon.game_theory as gt

じゃんけん・ゲームを例に計算してみる.

あとあと便利なので,NumPy array としてプレイヤー0の利得行列を定義しておく:


In [2]:
U = np.array(
    [[0, -1, 1],
     [1, 0, -1],
     [-1, 1, 0]]
)

quantecon.game_theory でナッシュ均衡を求める


In [3]:
p0 = gt.Player(U)
p1 = gt.Player(-U.T)

プレイヤー1の行列は -U の転置 (.T) であることに注意.


In [4]:
g = gt.NormalFormGame((p0, p1))

In [5]:
print(g)


2-player NormalFormGame with payoff profile array:
[[[ 0,  0],  [-1,  1],  [ 1, -1]],
 [[ 1, -1],  [ 0,  0],  [-1,  1]],
 [[-1,  1],  [ 1, -1],  [ 0,  0]]]

In [6]:
gt.lemke_howson(g)


Out[6]:
(array([0.33333333, 0.33333333, 0.33333333]),
 array([0.33333333, 0.33333333, 0.33333333]))

In [7]:
gt.support_enumeration(g)


Out[7]:
[(array([0.33333333, 0.33333333, 0.33333333]),
  array([0.33333333, 0.33333333, 0.33333333]))]

scipy.optimize.linprog で線形計画問題を解くことでナッシュ均衡を求める

主問題: $$ \min u $$ subject to $$ U y - \mathbf{1} u \leq \mathbf{0},\quad \mathbf{1}' y = 1,\quad y \geq \mathbf{0} $$

これを scipy.optimize.linprog の形式 $$ \max c' x $$ subject to $$ A_{\mathit{ub}} x \leq b_{\mathit{ub}},\quad A_{\mathit{eq}} x = b_{\mathit{eq}},\quad l \leq x \leq u $$ に合わせると, $$ x = \begin{pmatrix} y \\ u \end{pmatrix},\ % c = \begin{pmatrix} \mathbf{0} \\ 1 \end{pmatrix},\ % A_{\mathit{ub}} = \begin{pmatrix} U & -\mathbf{1} \end{pmatrix},\ % b_{\mathit{ub}} = \mathbf{0},\ % A_{\mathit{eq}} = \begin{pmatrix} \mathbf{1}' & 0 \end{pmatrix},\ % b_{\mathit{eq}} = \begin{pmatrix}1\end{pmatrix} $$ $$ l = \begin{pmatrix} 0 & \cdots & 0 & -\infty \end{pmatrix}',\ % u = \begin{pmatrix} \infty & \cdots & \infty \end{pmatrix}' $$

両プレイヤーの戦略の数をそれぞれ $m$, $n$ とする:


In [8]:
m, n = U.shape

各入力を定義する:


In [9]:
c = np.zeros(n+1)
c[-1] = 1
c


Out[9]:
array([0., 0., 0., 1.])

In [10]:
A_ub = np.hstack((U, np.full((m, 1), -1)))
A_ub


Out[10]:
array([[ 0, -1,  1, -1],
       [ 1,  0, -1, -1],
       [-1,  1,  0, -1]])

In [11]:
b_ub = np.zeros(m)
b_ub


Out[11]:
array([0., 0., 0.])

In [12]:
A_eq = np.ones((1, n+1))
A_eq[0, -1] = 0
A_eq


Out[12]:
array([[1., 1., 1., 0.]])

In [13]:
b_eq = np.ones(1)
b_eq


Out[13]:
array([1.])

In [14]:
bounds = [(0, None)] * n + [(None, None)]
bounds


Out[14]:
[(0, None), (0, None), (0, None), (None, None)]

scipy.optimize.linprog に渡して解かせる:


In [15]:
res_p = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds)

結果:


In [16]:
res_p


Out[16]:
     con: array([-6.61914967e-13])
     fun: -3.2190916599006414e-13
 message: 'Optimization terminated successfully.'
     nit: 4
   slack: array([-3.21909166e-13, -3.21964677e-13, -3.21853655e-13])
  status: 0
 success: True
       x: array([ 3.33333333e-01,  3.33333333e-01,  3.33333333e-01, -3.21909166e-13])

プレイヤー1の均衡戦略:


In [17]:
res_p.x[:-1]


Out[17]:
array([0.33333333, 0.33333333, 0.33333333])

ゲームの値:


In [18]:
res_p.x[-1]


Out[18]:
-3.2190916599006414e-13

scipy.optimize.linprog は双対解を返してくれないようなので,双対問題も定式化して解かせる: $$ \min -v $$ subject to $$ -U' x + \mathbf{1} v \leq \mathbf{0},\quad \mathbf{1}' x = 1,\quad x \geq \mathbf{0} $$


In [19]:
c = np.zeros(m+1)
c[-1] = -1

A_ub = np.hstack((-U.T, np.full((n, 1), 1)))
b_ub = np.zeros(n)

A_eq = np.ones((1, m+1))
A_eq[0, -1] = 0
b_eq = np.ones(1)

bounds = [(0, None)] * m + [(None, None)]

In [20]:
res_d = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds)
res_d


Out[20]:
     con: array([-6.61692923e-13])
     fun: -3.2213121059498917e-13
 message: 'Optimization terminated successfully.'
     nit: 4
   slack: array([-3.19910765e-13, -3.22741833e-13, -3.23741034e-13])
  status: 0
 success: True
       x: array([3.33333333e-01, 3.33333333e-01, 3.33333333e-01, 3.22131211e-13])

プレイヤー0の均衡戦略:


In [21]:
res_d.x[:-1]


Out[21]:
array([0.33333333, 0.33333333, 0.33333333])

ゲームの値:


In [22]:
res_d.x[-1]


Out[22]:
3.2213121059498917e-13

微妙は誤差はある:


In [23]:
res_p.x[-1] - res_d.x[-1]


Out[23]:
-6.440403765850533e-13

関数としてまとめてみる


In [24]:
def solve_zerosum_lemke_howson(U):
    g = gt.NormalFormGame((gt.Player(U), gt.Player(-U.T)))
    NE = gt.lemke_howson(g)
    return NE

In [25]:
def solve_zerosum_linprog(U, method='revised simplex'):
    U = np.asarray(U)
    m, n = U.shape
    
    # Primal problem
    c = np.zeros(n+1)
    c[-1] = 1
    A_ub = np.hstack((U, np.full((m, 1), -1)))
    b_ub = np.zeros(m)
    A_eq = np.ones((1, n+1))
    A_eq[0, -1] = 0
    b_eq = np.ones(1)
    bounds = [(0, None)] * n + [(None, None)]
    
    res_p = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds, method=method)
    
    # Dual problem
    c = np.zeros(m+1)
    c[-1] = -1
    A_ub = np.hstack((-U.T, np.full((n, 1), 1)))
    b_ub = np.zeros(n)
    A_eq = np.ones((1, m+1))
    A_eq[0, -1] = 0
    b_eq = np.ones(1)
    bounds = [(0, None)] * m + [(None, None)]
    
    res_d = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds, method=method)
    
    NE = (res_d.x[:-1], res_p.x[:-1])
    
    return NE

In [26]:
solve_zerosum_lemke_howson(U)


Out[26]:
(array([0.33333333, 0.33333333, 0.33333333]),
 array([0.33333333, 0.33333333, 0.33333333]))

In [27]:
solve_zerosum_linprog(U)


Out[27]:
(array([0.33333333, 0.33333333, 0.33333333]),
 array([0.33333333, 0.33333333, 0.33333333]))

ランダムに行列を発生させて計算させてみる


In [28]:
m, n = 4, 3
U = np.random.randn(m, n)
U


Out[28]:
array([[-1.10268043,  0.66543958,  1.50531492],
       [-0.4179199 , -1.13812095,  1.11085131],
       [-0.47009829,  0.70588343,  1.57764838],
       [ 1.32278522,  0.29501517, -1.48349366]])

In [29]:
solve_zerosum_lemke_howson(U)


Out[29]:
(array([0.        , 0.        , 0.57813434, 0.42186566]),
 array([0.63063987, 0.        , 0.36936013]))

In [30]:
solve_zerosum_linprog(U)


Out[30]:
(array([0.        , 0.        , 0.57813434, 0.42186566]),
 array([0.63063987, 0.        , 0.36936013]))

In [ ]: