強化学習 AlphaZero 6 (三目並べ アルファベータ法)

三目並べをアルファベータ法で解いていきます。

アルファベータ法は、ミニマックス法から計算しなくても同じ計算結果になる部分を読まない処理(枝刈り)を実装し、高速化したものです。探索手順はミニマックス法と同じです。

まず、三目並べを実装します。(前回のミニマックス法で定義したものと同じです。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# 三目並べの作成
import random

# ゲームの状態
class State:
# 初期化
def __init__(self, pieces=None, enemy_pieces=None):
# 石の配置
self.pieces = pieces if pieces != None else [0] * 9
self.enemy_pieces = enemy_pieces if enemy_pieces != None else [0] * 9

# 石の数の取得
def piece_count(self, pieces):
count = 0
for i in pieces:
if i == 1:
count += 1
return count

# 負けかどうか
def is_lose(self):
# 3並びかどうか
def is_comp(x, y, dx, dy):
for k in range(3):
if y < 0 or 2 < y or x < 0 or 2 < x or \
self.enemy_pieces[x+y*3] == 0:
return False
x, y = x+dx, y+dy
return True

# 負けかどうか
if is_comp(0, 0, 1, 1) or is_comp(0, 2, 1, -1):
return True
for i in range(3):
if is_comp(0, i, 1, 0) or is_comp(i, 0, 0, 1):
return True
return False

# 引き分けかどうか
def is_draw(self):
return self.piece_count(self.pieces) + self.piece_count(self.enemy_pieces) == 9

# ゲーム終了かどうか
def is_done(self):
return self.is_lose() or self.is_draw()

# 次の状態の取得
def next(self, action):
pieces = self.pieces.copy()
pieces[action] = 1
return State(self.enemy_pieces, pieces)

# 合法手のリストの取得
def legal_actions(self):
actions = []
for i in range(9):
if self.pieces[i] == 0 and self.enemy_pieces[i] == 0:
actions.append(i)
return actions

# 先手かどうか
def is_first_player(self):
return self.piece_count(self.pieces) == self.piece_count(self.enemy_pieces)

# 文字列表示
def __str__(self):
ox = ('o', 'x') if self.is_first_player() else ('x', 'o')
str = ''
for i in range(9):
if self.pieces[i] == 1:
str += ox[0]
elif self.enemy_pieces[i] == 1:
str += ox[1]
else:
str += '-'
if i % 3 == 2:
str += '\n'
return str

ミニマックス法で状態価値を計算します。(前回と同じです。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# ミニマックス法で状態価値計算
def mini_max(state):
# 負けは状態価値-1
if state.is_lose():
return -1

# 引き分けは状態価値0
if state.is_draw():
return 0

# 合法手の状態価値の計算
best_score = -float('inf')
for action in state.legal_actions():
score = -mini_max(state.next(action))
if score > best_score:
best_score = score

# 合法手の状態価値の最大値を返す
return best_score

# ミニマックス法で行動選択
def mini_max_action(state):
# 合法手の状態価値の計算
best_action = 0
best_score = -float('inf')
for action in state.legal_actions():
score = -mini_max(state.next(action))
if score > best_score:
best_action = action
best_score = score

# 合法手の状態価値の最大値を持つ行動を返す
return best_action

アルファベータ法で状態価値を計算計算します。

引数のalphaは親ノードの親ノード(つまり自分)のベストスコアで、betaは親ノードのベストスコアです。

  • alphaは自分のベストスコアなので、alpha以上のスコアを取得したらalphaを更新します。
  • betaは親ノードのベストスコアなのでalpha以下だったら探索を終了します。

メソッドを再帰的に呼び出すとき、alphaとbetaを左右入れ替えてるのがポイントです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# アルファベータ法で状態価値計算
def alpha_beta(state, alpha, beta):
# 負けは状態価値-1
if state.is_lose():
return -1

# 引き分けは状態価値0
if state.is_draw():
return 0

# 合法手の状態価値の計算
for action in state.legal_actions():
score = -alpha_beta(state.next(action), -beta, -alpha)
if score > alpha:
alpha = score

# 現ノードのベストスコアが親ノードを超えたら探索終了
if alpha >= beta:
return alpha

# 合法手の状態価値の最大値を返す
return alpha

アルファベータ法で状態に応じて行動を返す関数を実装します。
ミニマックス法との違いは次の2点です

  • best_scoreをalphaとする。
  • betaの初期値に-float(‘inf’)を設定する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# アルファベータ法で行動選択
def alpha_beta_action(state):
# 合法手の状態価値の計算
best_action = 0
alpha = -float('inf')
str = ['','']
for action in state.legal_actions():
score = -alpha_beta(state.next(action), -float('inf'), -alpha)
if score > alpha:
best_action = action
alpha = score

str[0] = '{}{:2d},'.format(str[0], action)
str[1] = '{}{:2d},'.format(str[1], score)
print('action:', str[0], '\nscore: ', str[1], '\n')

# 合法手の状態価値の最大値を持つ行動を返す
return best_action

最後に、アルファベータ法とミニマックス法で対戦します。
先手はアルファベータ法、後手はミニマックス法を使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# アルファベータ法とミニマックス法の対戦

# 状態の生成
state = State()

# ゲーム終了までのループ
while True:
# ゲーム終了時
if state.is_done():
break

# 行動の取得
if state.is_first_player():
print('----- アルファベータ法で行動選択 -----')
action = alpha_beta_action(state)
else:
print('----- ミニマックス法で行動選択 -----')
action = mini_max_action(state)

# 次の状態の取得
state = state.next(action)

# 文字列表示
print(state)

結果は次の通りです。

結果
アルファベータ法の方が実行速度が上がりましたが、強さは互角になりました。

(Google Colaboratoryで動作確認しています。)

参考

AlphaZero 深層学習・強化学習・探索 人工知能プログラミング実践入門 サポートページ