人狼知能大会で勝つために、機械学習を使ってエージェントの役職を推定するモデルを作る
数年前から、趣味で人狼知能大会に参加しているが、成績が芳しくない。自分たちのチームも、そろそろ、ルールベースを脱却して、機械学習を使った人狼知能エージェントを作成する時期に来ていると思う。
そのための第一ステップとして、「人狼知能で学ぶAIプログラミング」を参考に、過去の対戦ログから特徴量を抽出して、人狼か否かを判定する分類器を作ってみる。ただし、作成したモデルの人狼知能エージェントへの組み込みはまだやらない。
実験のしやすさを考慮して、過去の対戦ログからの特徴量抽出には(公式の配布しているサンプルコードを微修正した)Javaコード、学習・推定にはscikit-learnを用いることにした。なお、この文書の作者は機械学習の専門家ではないため、誤りや勘違いがあったら指摘いただけると大変ありがたい。
参考にしたサイトや書籍は文末にまとめてある。
今回行ったタスクは以下の通りである。
- 対戦ログのダウンロード
- 対戦ログからラベルをつけて、特徴量抽出をする(ほぼ公式のJavaコード)
- 人狼か否かの2値分類問題の学習・推定(Pythonのscikit-learn)
対戦ログのダウンロード
人狼知能プロジェクトの開発者向け情報の"過去大会ログ"から好きな大会をダウンロードする。今回はCEDEC2017をダウンロードした。※学習データとして用いる際には、小規模な大会よりも、開発者たちが本気でチューニングしてきた大会の結果を用いる方が望ましいと思われる。
このログを解凍する。
find cedec2017/ -type f -name "*.log.gz" -exec gunzip -d {} \;
対戦ログを解凍すると数字がついたディレクトリとその中に含まれる実際のログファイル(*.log
)が見つかる。人狼知能大会は5人戦と15人戦があるが、いずれも参加するエージェント5体/15体を固定して役職を変えながら100戦するので各ディレクトリ中のログはその参加するエージェントの組み合わせが同一の100戦に対応している。今回は、15人戦の参加するエージェントの組み合わせが同一の100戦の一部を学習データに、残りをテストデータに用いて機械学習をしてみる。
今回は小規模なデータで機械学習を試すため、cedec2017_small
というディレクトリを作成して、cedec2017
中の004
のディレクトリをコピーしてきて用いる。
ls cedec2017_small/004
// 0300.log ... 0399.logまで100個のファイルが表示される
対戦ログからのラベルをつけて、特徴量抽出する
- 人狼知能公式の
LogdataToVector
はそのままでは使いづらかったため、少しだけ改修した- 公式GitHub https://github.com/sonodaatom/aiwolfBook
- 解凍したCEDEC2017のログを読み込もうとするとゴミファイルが含まれていてプログラムが止まる
- 最近の人狼知能大会は予選では15人村と5人村が行われるがLogdataToVectorは15人村にしか対応していないと思われたので5人村の対戦ログを除外する処理が必要
- 上の2点を少し改修したコード https://github.com/sunmoonStern/aiwolfBook/tree/mybranch
- (コメントアウトされている部分で新しい特徴量を追加してみたがあまり性能の改善が見られなかった)
- 公式GitHub https://github.com/sonodaatom/aiwolfBook
- 以下のようにして実行するとlibSVM形式のデータができた。
data/
ディレクトリはmkdir
しておく。- ラベルづけは-1が人狼、1が人狼以外
java -jar /{path_to_jar}/AIbook.jar book.LogdataToVector /{path_to_log}/cedec2017_small/ data/
- 上で作成した100個のファイルを1つにまとめる(不要かもしれない)
import os
import subprocess
import sys
if __name__ == '__main__':
if len(sys.argv) != 3:
print('Usage: # python %s input_dir output_file' % argvs[0])
quit()
dir_name = sys.argv[1] # '/{path_to_data}/data/'
files = os.listdir(dir_name)
out_file = sys.argv[2] # '/{path_to_outfile}/new.log.txt'
os.system('rm ' + out_file)
for fin in files:
files_with_path = dir_name + fin
subprocess.call('cat ' + files_with_path + ' >> ' + out_file, shell=True)
- できあがったlibSVMのファイルはこんな感じ
- 各特徴量の意味については、
LogdataToVector
のソースコードを読んでもらうのが早い。 - 独自の特徴量を追加する際に留意する点は、ログの中から人狼ゲームの最中にプレイヤーに公開されていないシステム情報を用いてはいけない、という点である。例えば、ログから人狼だけが使えるwhisper行に着目すれば、人狼を確実に当てられるが、これは、プレイヤーに公開された情報から他のエージェントの役職を当てられる強いエージェントが作りたい、という目的に反する。
- 各特徴量の意味については、
$ head -5 mini.log.txt
1 1:0 2:0 3:0 4:0 5:0 6:0 7:0 8:0 9:0 10:0 11:0
1 1:0 2:0 3:0 4:0 5:0 6:0 7:0 8:0 9:0 10:0 11:0
1 1:0 2:0 3:0 4:0 5:0 6:0 7:0 8:0 9:0 10:0 11:0
1 1:0 2:0 3:0 4:0 5:0 6:0 7:0 8:0 9:0 10:0 11:0
-1 1:0 2:0 3:0 4:0 5:0 6:0 7:0 8:0 9:0 10:0 11:0
学習・推定
- scikit-learnをインストールしておく
pip install numpy
pip install scipy
pip install -U scikit-learn
- scikit-learnのドキュメントを参考に、とりあえずブラックボックス的に使ってみる
from sklearn.datasets import load_svmlight_file
from sklearn.model_selection import train_test_split
from sklearn import svm
from sklearn.metrics import classification_report, accuracy_score
x,y = load_svmlight_file('/{path_to_log}/new.log.txt')
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)
clf = svm.SVC(cache_size = 2000)
clf.fit(x_train, y_train)
y_pred = clf.predict(x_test)
print accuracy_score(y_test, y_pred)
print classification_report(y_test, y_pred)
- こんな結果が出た
- 正例・負例が偏っているため、precision 0.82という結果を額面通り受け取っていいのか不明。また、エージェントが人狼の際のrecallがひときわ悪いのが気になった。
precision recall f1-score support
-1.0 0.74 0.20 0.31 626
1.0 0.84 0.98 0.90 2614
avg / total 0.82 0.83 0.79 3240
やり残したこと
- 特徴量をもっと追加して実験する
- データの30%をテストデータにする、という雑な方法ではなくcross-validationでモデルを評価する
- CEDEC2017のログ全体を使って学習しようとしたら動かなかったからスケールさせる方法を探す
- random forestを使ってみたら早かったけど性能は少し悪かった
- いろいろなアルゴリズムを試して実験し、適切なパラメータを探す
- 対戦ログのうち強いエージェントだけが参加すると思われる決勝のログだけを用いる(ログから予選と決勝の100戦を見分けられるのか不明)
参考
-
人狼知能で学ぶAIプログラミング
- 人狼知能開発のための入門本
- 機械学習を用いた人狼知能エージェントの作り方については特に5章参照
-
人狼知能プロジェクト
- 人狼知能大会の日程やセミナーや合宿などの関連イベントの告知、過去の大会のログや過去の大会のソースコードなどがダウンロード可能
- scikit-learnのSVMのドキュメント