はじめに
前回は 足し算ゲームをChainerを使って強化学習できるか? で単純な足し算ゲームの強化学習をやってみました。
一応基本はわかってきたつもりなので、ゲームっぽいものに挑戦したいと思います。
そこでゲーム環境を作り、その上でゲームを実装し、そのゲームをChainerにやらせるというアプローチをとることにしました。
ゲーム環境
- イメージはASCII文字がシンボルで、画面の大きさが横40x縦24 のゲーム環境
- 入力は「上下左右」と「Aボタン」「Bボタン」。それぞれ独立として、2^6=64パターンの入力がある。
- ゲーム開発時用として、Terminalからのキー入力(ikjl + xz)でプレイできるデバッグモードがある。
- でも、押しっぱなしにすると入力溜まってしまって辛い・・・ 同時押しできないから辛い・・・
- 学習の様子を見るために、TCP/IPで通信して、直前のプレイの様子を表示する クライント・サーバがある。
という感じです。簡単に言うと、学習中にサーバに接続すれば、以下の様なものがTerminal上で見れるというものです。
AWS上などで実行しておいて、様子を見たい時にクライントを接続して眺めることができるので、暑い夏にPCをホッカホカにしたくない人向けです。
ちなみに ttygif
などで上記のようなGif Animeも作れます(ttygifすごい!)。
お題: Jumpで穴を飛び越えろゲーム(自作)を強化学習できるか?
上記のGif Animeみたいなゲームです(足し算ゲームよりは進化した・・・)。
ルール
- 左右のキーで左右に動く
- AボタンでJumpする。高さ制限あり(3マスくらい)。Jump中でも左右に自由に動ける。
- P がプレイヤーで 穴に落ちたらゲームオーバー (報酬 -1)
- ターン毎に 報酬 +0.05
- 自分の真下が穴の場合 報酬 +0.1
学習用に設けたルール
- 無効なキーを押したら 報酬 -0.1
- 例: 上や下、 Bボタン。 左右を同時に押す、等。
- 一応、何が有効なキーか、くらいは教えてあげてもいいかな、と(収束しないし・・・)。
- こうしておくと、暫くしたらちゃんと押さなくなりますw
- e-Greedyで選択するActionは、上記無効なキーを含まないように調整
- つまり、それは事前知識として知っている、ということになります(それくらいいいだろう・・・?)。
実験
学習モデル
DQNっぽく、 入力が画面の表示データのみ(40*24のでchar codeが 32〜127 まで)。前3フレームと現在のフレームの4フレーム分を入力とします。
ネットワーク構造は何が良いのか全くわからないので、こんな感じになっています。
HISTORY_SIZE = 4
# Convolution2DのOutputサイズは (screen_size - ksize) / stride + 1 となる(たぶん)。
chainer_model = FunctionSet(
l1=F.Convolution2D(HISTORY_SIZE, 3, ksize=1, stride=1), # 履歴を3つのW*Hのチャネルにする
l2=F.Linear(JumpGame.WIDTH*JumpGame.HEIGHT*3, 800), # ので、ここのINは W*H*3になる
l3=F.Linear(800, 500),
l4=F.Linear(500, 300),
l5=F.Linear(300, 64),
)
Ugo-Nama さんの
DQNをPython + Chainerで書いたよ(コード公開しました)
も参考にさせてもらい、過去の4フレームを F.Convolution2D でまとめたところまでは良いんじゃないかなと思っていますが、その後3channelで良いのかとか、中間層の構成はこれで良いのかとか、まだ完全に手探りです。中間層ちょっと多いような気がするなぁ(ピクセルの処理みたいなのが一切無いわけだし・・)。
実装
実験に使ったソースコードはこちらになります。
今回からは、サーバ機能などもありましたので、機能毎に分割して作りました。
今後ゲームを追加するときは、 jump_game.py
の部分だけ作れば良いようになっている、、、つもりです。
ゲームオーバーになる度に、学習状態を保存しているので、途中でBreakしても再開すれば前のModelを自動的にLoadします。ただ、Modelの構造を変更するとLoadでエラーになるので、その時は手動で ~/.game/model/
の下にあるファイルを消すような仕様。。
過去のプレイ結果を取っておいて、バックグラウンドでバッチ学習する仕組み(Experience Replay?)もおいおい作っていきたいと思います。
結果
まだよい結果は出てないです。
単に時間がかかるのか、やり方が間違っているのか。。
非常に単純な足し算ゲームでも最適な行動を学習するまでには100万回以上(学習回数でいうと5000万回以上だった気もする・・)かかったしなぁ。。
前回のような厳密な最適行動は不要だとは思いますが、穴に落ちなくなるようになってくれないかなぁと思っています。
結果が出るまでじっと待つ必要があるというのは、何か本当に生物の実験をしているようですねw
追記:12時間後
初期の頃とモデルを変えて12時間後くらいには上記のようになっていました。
少し賢くなってますねー。
HISTORY_SIZE = 4
PATTERN_SIZE = 100
def calc_output_size(screen_size, ksize, stride):
return (screen_size - ksize) / stride + 1
nw = calc_output_size(JumpGame.WIDTH, 8, 4) # 9
nh = calc_output_size(JumpGame.HEIGHT, 8, 4) # 5
chainer_model = FunctionSet(
l1=F.Convolution2D(HISTORY_SIZE, PATTERN_SIZE, ksize=8, stride=4),
l2=F.Linear(nw * nh * PATTERN_SIZE, 800),
l3=F.Linear(800, 400),
l4=F.Linear(400, 64),
)
F.Convolution2D(4, 3, ksize=1, stride=1)
から
F.Convolution2D(4, 100, ksize=8, stride=4)
にしています。
さいごに
次はもう少し違うゲームを作ってみようと思います。
Experience Replayはその後くらいにやってみようかな。
次: Chainerで機械学習と戯れる: JumpゲームとTreasureゲームをChainerで強化学習した過程と結果のメモ