DeepMindが作った世界最強のゲームAIに使われているアルゴリズムが「AlphaZero」の仕組みを利用して、今回は「三目並べ」というゲーム版のAlphaZeroを作ってみた。
今回は備忘録として「三目並べ版-AlphaZero」の仕組みを淡々とまとめていく。
目次
1.AlphaZeroの仕組み
2.「三目並べ」の環境
3.AlphaZeroで使うニューラルネットワーク
4.自己対戦(Self-Playing)部分
5.パラメータ更新部分
6.パラメータ評価部分
7.パフォーマンス
1.AlphaZeroの仕組み
2.戦略と価値を更新するニューララネットワーク(Dual NN)でモンテカルロ法を使いながら自己対戦を繰り返して、学習データを貯めていく
3.その学習データをもとに「latest player」という、この時点で最強のpalyerをもう一つ作る。
4.「best player」と「latest player」を対戦させ、より強い方を「best player」として採用し、また自己対戦をさせる~
5.2〜4を繰り返す
というサイクルを繰り返して強くなっていく。
三目並べは単純なのでGPUが1つでも半日もあれば、十分に強くなる。
2.「三目並べ」の環境
ここでは三目並べ用の環境(Environment)を定義するコードが必要で適宜関数とかクラスを用意する。「三目並べ」というゲームの詳細な説明は参考サイトを参照。
いわば。超simpleな簡易版の対戦ゲーム。
3.AlphaZeroで使うニューラルネットワーク
AlphaZeroで使うニューラルネットワーク(Dual NN)は
出力:戦略(Policy)と価値(Value)
の出入力構成で「戦略と価値」の2つ出力するDual NN。
まずDual NNを動かして一番はじめの「best player」を作る。
optimizerはDeepmind本家のはSGDだけど、今回はスピード重視なのでAdamにした。
def dual_network(): # if trained model, nothing to do if os.path.exists('./model/best.h5'): return input = Input(shape=DN_INPUT_SHAPE) x = conv(DN_FILTERS)(input) x = BatchNormalization()(x) x = Activation('relu')(x) # resblock x 19 for i in range(DN_RESIDUAL_NUM): x = residual_block()(x) x = GlobalAveragePooling2D()(x) p = Dense(DN_OUTPUT_SIZE, kernel_regularizer=l2(0.0005), activation='softmax', name='pi')(x) v = Dense(1, kernel_regularizer=l2(0.0005))(x) v = Activation('tanh', name='v')(v) model = Model(inputs=input, outputs=[p,v])
4.自己対戦(Self-Playing)部分
best player(Dual NN)同士を戦わせて互いに腕を上げていく部分。GANの仕組みとほぼ同じ。
戦わせながら1 playごとに学習データに
・1 playの戦略のscore:policy score
・1 playの価値の合計:累積v(累積価値)
の3つをlist形式で貯めていく。
SELFPLAY_GAME_COUNT = 500 def self_play(): # train data list history = [] # load best player model = load_model('./model/best.h5') # multi play for i in range(SELFPLAY_GAME_COUNT): # 1 play h = one_episode_play(model) history.extend(h) print('\rSelfPlay {}/{}'.format(i+1, SELFPLAY_GAME_COUNT), end='') print('') # save train data save_state_policy_value(history) K.clear_session() del model
5.パラメータ更新部分
自己対戦(Self-Playing)で貯めた学習データを使って、Dual NNを学習。・ターゲット変数が「戦略(p)」と「価値(s)」
def create_latest_player(): def train_dual_network(): # load train data xs, y_policies, y_values = load_policy_value() # load best player model = load_model(best_weight) model.compile(loss=['categorical_crossentropy', 'mse'], optimizer='adam') lr_decay, print_callback = call_back() # train model.fit(xs, [y_policies, y_values], batch_size=128, epochs=RN_EPOCHS, verbose=0, callbacks=[lr_decay, print_callback]) print('') # save latest player model.save('./model/latest.h5') # delete model K.clear_session() del model return train_dual_network
最後に「latest player」として学習済みモデルを保存する。
「latest player」は500戦した学習データをもとに、報酬(勝率)が最大になるように作り上げた一番強いplayer。
これではじめの「best player」と「latest player」という2人のplayer(正確にはAgent)ができた。
6.パラメータ評価部分
「best player」と「latest player」を10回戦わせて、一番強いplayerを新たな「best player」として採用し、弱い方は捨てる。def total_episode_play(): # load latest player model0 = load_model('./model/latest.h5') # load best layer model1 = load_model('./model/best.h5') # select action by PV MCTS next_action0 = select_action_by_pv_mcts(model0, BOLTZMAN_TEMP) next_action1 = select_action_by_pv_mcts(model1, BOLTZMAN_TEMP) next_actions = (next_action0, next_action1) # multi play total_point = 0 for i in range(GAME_COUNT): # 1 play if i % 2 == 0: total_point += one_episode_play(next_actions) else: total_point += 1 - one_episode_play(list(reversed(next_actions))) print('\rEvaluate for update player {}/{}'.format(i + 1, GAME_COUNT), end='') print('') # caluculate average point average_point = total_point / GAME_COUNT print('AveragePoint', average_point) # delete models K.clear_session() del model0 del model1 if average_point > 0.5: print('change best player') update_best_player() return True else: return False
これでまた始めに戻り、自己対戦(Self-Playing)~パラメータ評価を繰り返してplayerの強さを上げていく。
7.パフォーマンス
以下がAlphaZeroのサイクルを回してplayerを強くしていくアルゴリズムのコード。
途中の「新パラメータ評価部」で、別のアルゴリズムと対戦させて、best playerを評価(強さを確かめてる)してる。
train_network = create_latest_player() # create dual NN dual_network() for i in range(10): print('Train',i,'====================') # セルフプレイ部 self_play() # パラメータ更新部 train_network() # 新パラメータ評価部 update_best_player = total_episode_play() # ベストプレイヤーの評価 if update_best_player: evaluate_best_player() >>>>> Train 0 ==================== SelfPlay 500/500 Train 100/100 Evaluate for update player 10/10 AveragePoint 0.95 Change BestPlayer Evaluate 10/10 VS_Random 1.0 VS_AlphaBeta 0.25 VS_MCTS 0.5 <略> Train 10 ==================== SelfPlay 500/500 Train 100/100 Evaluate for update player 10/10 AveragePoint 0.95 Change BestPlayer Evaluate 10/10 VS_Random 1.0 VS_AlphaBeta 0.55 VS_MCTS 0.6
三目並べでほぼ最強のアルゴリズム「alpha-beta法」に互角以上の成績を叩き出した。
AlphaZeroはかなり汎用性がある、対戦用のメカニズムになっているなと感じた。
ハイエンド脳無もこんな感じで作れたらなとか思う今日この頃。