三目並べを原始モンテカルロ探索で解いていきます。
原始モンテカルロ探索はランダムシミュレーションによって状態価値を計算する手法です。 何回もランダムプレイを行い、勝率の高い手を価値が高いと評価します。
まず、三目並べを実装します。(前回のアルファベータ法で定義したものと同じです。)
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 def random_action (state ): legal_actions = state.legal_actions() return legal_actions[random.randint(0 , len (legal_actions)-1 )]
アルファベータ法で行動を選択する関数を定義します。対戦相手用です。
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 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 def alpha_beta_action (state ): best_action = 0 alpha = -float ('inf' ) for action in state.legal_actions(): score = -alpha_beta(state.next (action), -float ('inf' ), -alpha) if score > alpha: best_action = action alpha = score return best_action
いよいよ原始モンテカルロ探索の実装にはいります。 まずはプレイアウト関数を定義します。プレイアウトとはたんに現在の局面からゲーム終了までプレイすることです。
1 2 3 4 5 6 7 8 9 10 11 12 def playout (state ): if state.is_lose(): return -1 if state.is_draw(): return 0 return -playout(state.next (random_action(state)))
原始モンテカルロ探索で行動選択する関数を定義します。 合法手ごとに10回プレイアウトした時の状態価値の合計の計算し、その合計が最も大きな行動を選択します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def mcs_action (state ): legal_actions = state.legal_actions() values = [0 ] * len (legal_actions) for i, action in enumerate (legal_actions): for _ in range (10 ): values[i] += -playout(state.next (action)) return legal_actions[argmax(values)] def argmax (collection, key=None ): return collection.index(max (collection))
原始モンテカルロ探索とランダム・アルファベータ法で対戦してみます。
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 EP_GAME_COUNT = 100 def first_player_point (ended_state ): if ended_state.is_lose(): return 0 if ended_state.is_first_player() else 1 return 0.5 def play (next_actions ): state = State() while True : if state.is_done(): break next_action = next_actions[0 ] if state.is_first_player() else next_actions[1 ] action = next_action(state) state = state.next (action) return first_player_point(state) def evaluate_algorithm_of (label, next_actions ): total_point = 0 for i in range (EP_GAME_COUNT): if i % 2 == 0 : total_point += play(next_actions) else : total_point += 1 - play(list (reversed (next_actions))) print ('\rEvaluate {}/{}' .format (i + 1 , EP_GAME_COUNT), end='' ) print ('' ) average_point = total_point / EP_GAME_COUNT print (label.format (average_point)) next_actions = (mcs_action, random_action) evaluate_algorithm_of('VS_Random {:.3f}' , next_actions) next_actions = (mcs_action, alpha_beta_action) evaluate_algorithm_of('VS_AlphaBeta {:.3f}' , next_actions)
結果は次の通りです。 ランダムとの対戦では勝率93%と圧勝ですが、アルファベータ法との対戦では勝率23.5%となかなか厳しい結果となりました。
(Google Colaboratory で動作確認しています。)
参考
AlphaZero 深層学習・強化学習・探索 人工知能プログラミング実践入門 サポートページ