はじめに
どうも、y-tetsuです。
本記事では、Pythonで自作したリバーシ(オセロ)AIを、Edax(強豪AIの1つ)と対戦させて、世界レベルの強さを体感してみる!というものです。
3年ほど前、日経ソフトウェア様のリバーシAI特集をきっかけに、Python(Cython)を使ってリバーシAIを自分なりに作り、少しずつを改良を加えてきました。そしてついに機械学習は用いないまでも、古典的でヒューリスティックな手法にて自分を超えるリバーシAIを作るに至りました。
こうして出来上がった私のAIは、結構いい線いってたりしないかな??ということを確かめるべく、今回、Edaxさんにご協力いただくことにしました。
Edaxとは
Edaxとは最強クラスと目されているリバーシAIの一つです。対戦相手として不足なし、…というより恐れ多い。
(以下のページで、紹介されていました)
https://scorenow.mystrikingly.com/blog/edax
ソースコードや実行できるアプリは大変有難いことに、githubで公開されています。
https://github.com/abulmo/edax-reversi
また、様々な方がEdaxに関連する記事を書かれているのをお見掛けしました。
https://choi.lavox.net/edax/start
https://qiita.com/tanaka-a/items/6d6725d5866ebe85fb0b
自作AIについて
PythonとCython(高速化のため)を使って作りました。いろいろなサイトを漁りながら、強さに寄与しそうな評価指標を見つけては実装し、うまくいったものを寄せ集めた、というビットボードを使った代物です。これでも人間相手にはそこそこ頑張っているのではと自負しています。
序盤
簡単な定石を登録していて、登録されている局面においては定石どおりに打ちます。
- 鼠定石
- 牛定石
- 虎定石
- 兎定石
- 猫定石
など。(干支とは限らないんですね…)
中盤
以下の観点で重み付けした評価関数を用いて、8手先の局面を評価し、最も評価の良い手を選びます。
- 残りのマスが空いていても、勝ちが見えた手は評価を上げる
- 手を打つことができる着手可能数が、自分は多く相手が少なくなるほど評価を上げる
- 盤面の角、角の横や"X"といわれる場所など、8x8のマスの位置による優劣を重みづけして、現在の盤面から総合的に判断する
- 角から連続する、決してひっくり返されることがない確定石が多いほど評価を上げる
- 自分の石がなるべく空いているマスに接しない(相手の内側に入り込む)ほど評価を上げる
終盤
残り16手からは終局まで読み切り、石差の最も多くなる手を選びます。探索深さ16手は、動作確認をしてみて、恐らくこれなら現実的な時間に結果を返すだろうという深さを経験的に設定しました。(20手以上は当たり前のように読む世界レベルのハードルに既に達してませんが…)
このような自作AIを、勝手ながら「Blank8_16」と名付けてかわいがっていました。
対戦の準備
手前味噌で申し訳ないのですが、「PythonにてリバーシAIを自作したり、自作したAI同士を対戦させたりする」というのを割と簡単に行える、というのが売りのPythonのライブラリ(自作)を用いて、Edaxさんにお手合わせいただきました。
私のPCの関係上、Windows環境での説明となります。あらかじめご了承下さい。
Edaxのダウンロード
まずは、本家サイトの以下より、Edax本体のアプリケーションと評価値算出用のデータをダウンロードして下さい。
上図の下線で示したファイルをダウンロードして下さい。
reversiをインストールする
Python3.7をインストールし、以下のコマンドを実行して、reversiライブラリをインストールしてください。
$ py -3.7 -m pip install git+https://github.com/y-tetsu/reversi
Python3.7以降でも問題なく動くと思いますが、その際はCythonのコンパイルの関係で、Microsoft Visual C++のインストールを併せて実施願います。
EdaxをPythonから扱うためのコードを用意する
reversiライブラリを使って、PythonからEdaxを実行するためのコードです。オリジナルのEdaxの実行とその出力をラップして、reversiライブラリのAIとして扱うためのインターフェースに準拠させました。
import subprocess
from reversi import strategies
from reversi.strategies import AbstractStrategy
from reversi.move import Move
class Edax(AbstractStrategy):
def next_move(self, color, board):
if board.size != 8:
return strategies.Random().next_move(color, board)
with open('./board.txt', 'w') as f:
f.write(board.get_board_line_info(color))
cmd = "./edax-4.4 -solve ./board.txt"
output_str = subprocess.run(cmd, capture_output=True, text=True).stdout
move = output_str.split('\n')[2][57:].split()[0]
return Move(move)
盤面のサイズ制限
reversiのアプリケーションは盤面のサイズを4~26まで変えることができます。今回は8に限定するため、それ以外の場合はランダムに打つようにしています。
if board.size != 8:
return strategies.Random().next_move(color, board)
盤面のファイル出力
reversiの盤面情報を、Edaxの入力となる盤面情報へ変換しファイル(board.txt*)に出力しています。
with open('./board.txt', 'w') as f:
f.write(board.get_board_line_info(color))
Edaxの実行
subprocessによりEdaxを実行し、Edaxの標準出力をテキストで受け取っています。
cmd
変数にEdax実行時のコマンドを記載しています。
cmd = "./edax-4.4 -solve ./board.txt"
output_str = subprocess.run(cmd, capture_output=True, text=True).stdout
-solve <ファイル名>
で盤面情報を読み込み、打ち手を標準出力で返してくれるようです。(細かなオプションは調べ切れておりません、申し訳ありません)
打ち手を返す
Edaxが標準出力した打ち手('A1'などの文字列)を取り出し、Move
オブジェクトに変換することでreversiで扱える打ち手に変換しています。
move = output_str.split('\n')[2][57:].split()[0]
return Move(move)
(Edaxの標準出力例)
# | depth|score| time | nodes (N) | N/s | principal variation
---+------+-----+--------------+-------------+----------+---------------------
1|21@73% +01 0:00.547 2141770 3915484 B3 c2 B4 c6 D2 e6 B5
------+-----+--------------+-------------+----------+---------------------
./board.txt: 2141770 nodes in 0:00.547 ( 3915484 nodes/s).
1 positions; 0 erroneous move; 0 erroneous score; mean absolute score error = 0.000; mean absolute move error = 0.000
(コード処理内容)
- 上記の2行目(0はじまり)を取り出す
- 1.からさらに、57文字め以降(B3 c2 …の部分)を取り出す
- 2.から、スペース区切りで配列に変換する
- 3.の最初の要素をとりだし、Edaxの次の打ち手とする
- 4.を
Move
オブジェクトによりreversiライブラリで扱える打ち手の形式に変換
reversiで扱える打ち手の形式は、(x, y)
のタプル形式となっています。
対戦を観戦するためのアプリ
tkinter製のアプリケーションに、自作AI(Blank8_16)とEdaxを追加したものです。
# ↓↓↓↓↓ Add players here ↓↓↓↓↓
のところに参加させたいAIを記載します。
from reversi import Reversi, strategies
from edax_wrap import Edax
if __name__ == '__main__':
Reversi(
# ↓↓↓↓↓ Add players here ↓↓↓↓↓
{
'Blank8_16': strategies.SwitchJ_Blank8_EndGame16(),
'Edax': Edax(),
}
# ↑↑↑↑↑ Add players here ↑↑↑↑↑
).start()
シミュレータコード
AI同士、先手と後手を入れ替えて、複数回の対戦結果を集計するためのコードです。
観戦アプリ同様、# ↓↓↓↓↓ Add players here ↓↓↓↓↓
のところに参加させたいAIを記載します。
import timeit
from reversi import Simulator, strategies
from edax_wrap import Edax
if __name__ == '__main__':
simulator = Simulator(
{
# ↓↓↓↓↓ Add players here ↓↓↓↓↓
'Blank8_16': strategies.SwitchJ_Blank8_EndGame16(),
'Edax': Edax(),
# ↑↑↑↑↑ Add players here ↑↑↑↑↑
},
'./simulator_setting.json',
)
elapsed_time = timeit.timeit('simulator.start()', globals=globals(), number=1)
print(simulator, elapsed_time, '(s)')
if simulator.processes == 1:
keys = strategies.Measure.elp_time.keys()
for key in keys:
print()
print(key)
print(' min :', strategies.Measure.elp_time[key]['min'], '(s)')
print(' max :', strategies.Measure.elp_time[key]['max'], '(s)')
print(' ave :', strategies.Measure.elp_time[key]['ave'], '(s)')
また、シミュレーション時の設定内容を合わせて以下の様に用意します。
{
"board_size": 8,
"board_type": "bitboard",
"matches": 10,
"processes": 1,
"prallel": "player",
"random_opening": 8,
"player_names": [
"Blank8_16",
"Edax"
]
}
よく触るところだけピックアップします。(詳細は後述のreversiのページをご参照下さい)
- matches : 先手番、後手番それぞれの対戦回数
- random_opening : 開始からランダムに打つ手数。今回は8手までランダムに打った後、実AIで対戦させました。
- player_names : 参加AI
フォルダ構成
これまでに、ダウンロードしたファイルや、用意したPythonコードは以下の配置として下さい。
C:.
│ edax-4.4 … Edax実行アプリケーション
│ edax_wrap.py … PythonからEdaxを扱うためのPythonコード
│ reversi_app.py … AIの対戦を観戦できるPythonアプリケーション
│ reversi_simulator.py … AIの対戦をシミュレーションできるPythonアプリケーション
│ simulator_setting.json … シミュレータ設定ファイル
│
└─data … Edaxの評価値とBook
book.dat
eval.dat
これにて、対戦の準備は整いました。いよいよ結果を見てみましょう!
対戦の様子を観戦する
以下を実行すると、tkinterで作ったGUIアプリケーションが起動します。
py -3.7 reversi_app.py
実行の様子を以下に示します。
Black"と"White"それぞれのAIを指定し、"Click to start"を実行して対戦の様子をご観戦下さい。(AIに"User"を指定した場合は人が操作できます)
複数回戦わせた結果を出す
以下を実行すると、シミュレータ設定に応じたシミュレーションを実行します。
py -3.7 reversi_simulator.py
先手・後手を10試合ずつ、開始8手はランダムに打つ条件での対戦結果は以下となりました。
Blank8_16 Edax
- Blank8_16 Edax 5
- Blank8_16 Edax 10
Edax Blank8_16
- Edax Blank8_16 5
- Edax Blank8_16 10
Size : 8
| Blank8_16 Edax
-------------------------------------------------------------------------------
Blank8_16 | ------ 0.0%
Edax | 100.0% ------
-------------------------------------------------------------------------------
| Total | Win Lose Draw Match
------------------------------------------------------------
Blank8_16 | 0.0% | 0 20 0 20
Edax | 100.0% | 20 0 0 20
------------------------------------------------------------
先手・後手それぞれ10回ずつの結果です。…現実は厳しい。気持ちいいくらいですね。お疲れ様でした!!
おわりに
まあ、お恥ずかしいことに完敗でした。
このような圧倒的強さのEdax様よりも、さらに強いと言われるEgaroucid5様もまだ控えておられるという事ですので、オセロAIの世界もまだまだ上には上がいるんだなと、感慨深いものがあります。
自分で作ったAIを、世界的に強いAIと一緒に戦わせることができる環境が、ものすごく簡単に用意できるという事に、素直に有難さを感じます。
Python以外のコードでも、インターフェースさえ合わせれば、オセロAIの熱き戦いに参加できますので、もしよかったらPython製ライブラリreversiを何卒よろしくお願いします。
それでは皆様、よきリバーシ・ライフを!
補足 : Edaxのビルド
Edaxのアプリケーションについて、本家からダウンロードしたものが動かない事があり(原因は不明ですが)、ビルドしなおしてうまくいったケースがありましたので、覚書を残しておきます。
1. MinGWのインストール
以下を参照しました。
https://qiita.com/kzrashi/items/4e0ab5949b69d4b333dd
2. makeのインストール
以下を参照しました。
https://qiita.com/avimigo/items/f501f44a8d44e3fd6987
3. Edaxのソースをダウンロードと、zip解凍
上図の下線で示したファイルをダウンロードして下さい。
4. binフォルダ作成
アプリケーションの実行体作成用にbinフォルダを用意します。
edax-reversi-4.4
bin/ ← 作る
include/
src/
.hgignore
README.md
5. srcフォルダに移動
scrフォルダに移動してください。
6. ビルド実行
以下を実行してください。(Windows用)
make build ARCH=x64 COMP=gcc OS=windows
5. アプリケーションの生成
binフォルダ以下に、"wEdax-x64.exe"が生成せれると思います。
本記事のコードで動かすためには、"wEdax-x64.exe"を"edax-4.4"にファイル名を変更(拡張子も削除)した後、所定のフォルダに置いて下さい。