三目並べをアルファベータ法で解いていきます。
アルファベータ法は、ミニマックス法から計算しなくても同じ計算結果になる部分を読まない処理(枝刈り)を実装し、高速化したものです。探索手順はミニマックス法と同じです。
まず、三目並べを実装します。(前回のミニマックス法で定義したものと同じです。)
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 randomclass 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 ): 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 ): if state.is_lose(): return -1 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 ): if state.is_lose(): return -1 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 深層学習・強化学習・探索 人工知能プログラミング実践入門 サポートページ