アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

強化学習で三目並べ版の「AlphaZero」を作ったのでその仕組みまとめ【機械学習】

DeepMindが作った世界最強のゲームAIに使われているアルゴリズムが「AlphaZero」の仕組みを利用して、今回は「三目並べ」というゲーム版のAlphaZeroを作ってみた。

今回は備忘録として「三目並べ版-AlphaZero」の仕組みを淡々とまとめていく。


目次
1.AlphaZeroの仕組み
2.「三目並べ」の環境
3.AlphaZeroで使うニューラルネットワーク
4.自己対戦(Self-Playing)部分
5.パラメータ更新部分
6.パラメータ評価部分
7.パフォーマンス


1.AlphaZeroの仕組み


1.はじめに「best player」(いわゆるAgent)を作る

2.戦略と価値を更新するニューララネットワーク(Dual NN)でモンテカルロ法を使いながら自己対戦を繰り返して、学習データを貯めていく

3.その学習データをもとに「latest player」という、この時点で最強のpalyerをもう一つ作る。

4.「best player」と「latest player」を対戦させ、より強い方を「best player」として採用し、また自己対戦をさせる~

5.2〜4を繰り返す


というサイクルを繰り返して強くなっていく。

f:id:trafalbad:20200408173249j:plain


三目並べは単純なのでGPUが1つでも半日もあれば、十分に強くなる。





2.「三目並べ」の環境

ここでは三目並べ用の環境(Environment)を定義するコードが必要で適宜関数とかクラスを用意する。


「三目並べ」というゲームの詳細な説明は参考サイトを参照。
いわば。超simpleな簡易版の対戦ゲーム。






3.AlphaZeroで使うニューラルネットワーク

AlphaZeroで使うニューラルネットワーク(Dual NN)は

入力:「相手と自分の状態(S)」

出力戦略(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ごとに学習データに

・相手と自分の盤上の駒の状態:累積s(累積状態)

・1 playの戦略のscore:policy score

・1 playの価値の合計:累積v(累積価値)

の3つをlist形式で貯めていく。


f:id:trafalbad:20200408174339j:plain


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を学習。

・説明変数が「状態s」

・ターゲット変数が「戦略(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はかなり汎用性がある、対戦用のメカニズムになっているなと感じた。

ハイエンド脳無もこんな感じで作れたらなとか思う今日この頃。

f:id:trafalbad:20200408173342j:plain