AlphaZeroでオセロのボットを作った話

AlphaZeroでオセロのボットを作成し、人類(クソデカ主語)では勝てないほどの棋力を得たので、そこに至るまでの2ヶ月に渡るあれこれなど。


 

0〜14日目:AlphaGoの論文だけを読んでしまう

AlphaZeroとその前身であるAlphaGoは基礎的なアルゴリズムが大きく違い、かつAlphaZeroの方がかなりシンプルである。それに気づかずAlphaGoの論文を読み実装を始めてしまったため、かなりの時間を無為に浪費するところから始まった。

その後、Arxivに上がっていたAlphaZeroの論文を読み実装に勤しんだが、実はもっと詳細なバージョンの論文が存在するという罠にも引っ掛かっており、これがかなり尾を引いた。

 

14~16日目: UIを作る

Golangなるモノを聞いたことがあった。時代についていけていないので、これまで触れもせず遠目に眺めているだけであったが、意を決してGolangで実装することにした。結果、特にさしたるドラマもなく動いた。よい。

作成したUIには相手のターンや自分のターンといった概念はなく、自分で好きなところに置くかボットに打たせるかを常に任意で選べるようにした。好きな盤面を作ってからボットに打たせたりできるのでこれもよい。

 

17~20日目: AlphaZero実装

Pythonで実装した。動きはしたが速度に難有りだったので、その改善がメインとなっていった。

 

21~22日目: C++で高速化

石を置けるかなどの判定をPythonで実装していると非常にスピードが遅くなってしまうのでC++に移植した。Boost.Pythonを使った。以前、BoostやCythonを使おうとした時は非常に苦労した覚えがあったので身構えていたが、わりと簡単に実装できて驚いた。年々Stack Overflowなどに知見が溜まっていき嵌りどころが解消されていっている。集合知

 

23~27日目: Tensorflow Servingを使う

Tensorflowを複数のPythonスレッドで走らせることに難儀した結果、Tensorflow Servingという独立で走る推論サーバーを使用することにした。Tensorflow Servingを使えば、複数のスレッドから送られたデータをバッチ化してスコアリングしてくれるので速度的にも有り難い。

TCPリクエストにconnection=closeを付与せずに連続で送り続けると、何らかのリソースを使い切ってしまいRESTリクエストが一定時間送れなくなるという罠にずっとハマっていた。何もわからずgRPCを使っていたのでgRPCでも同じ問題に陥っていた。ただ、どちらのケースも一度開いたチャンネルを再利用すれば問題を回避できるし速度面でも有利ということに後に気づいた。

 

28日目: GCPを使ってみる

GCPを使ったことがなかったので使ってみた。無料で数万円分使えるという太っ腹。基本的には24CPU1GPUのマシンで回していた。

 

29日目: トレーニングが遅い

如何ともし難く遅かった。主な原因としてはAlphaZeroのアルゴリズムを勘違いしていたことがある。AlphaGoとは違い、AlphaZeroはMCTSにおいてロールアウトをせずNNの出力をノード評価値として使用するのだが、そのことに最終盤まで気づかなかった。ロールアウトは非常に時間がかかるので致命的なミスであった。

 

29日目: ボットが弱い

レーニングが遅いから弱いという単純な理由だった。ただ時間をかければ強くなっていっている感じもした。

 

28~30日目: 全てをC++に移植し並列MCTS

可能な限りC++に移植しようとした。TensorflowでモデルをトレーニングするのはC++では厳しいのでPythonに残しておいたが、残りはほぼ全てC++に移植した。同時にMCTSを並列アルゴリズムに変えた。もともと並列に複数の自己対戦を行い棋譜を生成していたので、トレーニングの高速化に寄与したかは怪しい。ただ1戦1戦を高速に回せるのでNNの世代を速く回せるという利点はあるのかなと思った。ローカルでボットと対戦する際は非常に役に立った。

 

31~33日目: C++でのTensorflow Serving gRPCコールが難しすぎる

C++への移植に伴いgRPCコールもC++から呼ぼうと思った。これがあまりにもキツかった。そもそもの難易度が高く、ネット上でも苦しんでいる人々が散見された。それにLinuxC++への理解の低さもかなり響いた。おそらくではあるが、適切なprotoファイルをTensorflowのレポジトリから選び出し、C++用のコードを生成して使えば何とかできるのだろうと思った。ただ、自分の場合はそこからBoost.Pythonを使い.soファイルを作りPythonから呼ばなければならず、更に難易度が上がってしまった。結果、うまく動かすことができずRESTコールでお茶を濁すこととなった。たぶん、このRESTコールが最終的なボトルネックになったと思う。PythonからC++を呼ばず、C++からPythonを呼ぶようにしておけば話は違ったと思う。

 

34日目: GCPの無料分が尽きる。

案外すぐに無料分を使い果たしてしまった。ここまではわりとのんびりと進めていたのだが、ここから凄いスピードで進捗を出していくことになった。良くも悪くも身銭を切ると緊張感が変わってくる。

 

35~36日目: 再びAlphaZeroに関する情報をチェックする

一通りのことはやったので再び元の論文を読み、野良ブログなども読み漁った。ロールアウトがいらないと気づいたのもこの時であった。詳しい方の論文にだけMCTSの実装詳細が書いてあってそこで気づいた。その他にも小さな改善を繰り返していった。

 

37日目: ボットが最強になった

この時点でわりと高速に自己対戦し棋譜を生成できるようになっていた。正確には24CPU/1GPU(P4)のマシンで、Resnet(channel=72, depth=13)を使用し、MCTS(n=800)で毎分12棋譜を生成できた。

夜にトレーニングを開始して朝に対戦してみても、自分(私は人間です)では勝てなくなった。ネット上にCodinGame1位のボットが公開されているが、十分な持ち時間のハンディキャップ(MCTS[n=6400])があれば、1日訓練してたまに勝てるくらいにはなった。ただ、もう1日訓練してから戦ってもあまり勝率は上がらなかった。理由はわからない。小さめのモデルを使っていたのがダメだったのか、実は棋力の差が思った以上に大きかったのか。

 

最終日: ブログとGCPの請求額

折角2ヶ月も掛けたので、ブログを書いたり動画を作ってみてもよいのではと思い、今このブログを書いています。日記などは付けていなかったので、夏休みの宿題方式で朧気な記憶を掘り起こしつつ書いています。しかし、少しばかりブログの締めくくり方に難儀しています。

なので最後にGCPの請求額を書いて終わろうと思います。
無料分が切れてからは怖くて請求額は見てなかったです。
たぶん1.5万円くらいだとは思いますが。
ということで今からGCP開いてきます。
.
.
.
13,536円でした。ん〜妥当。