2021年8月30日月曜日

深層強化学習AIガオガエン FAQ

Q: これは何?

A: 機械学習の一種である深層強化学習を用いて、スマブラ(ガオガエン)をプレイするコンピュータプログラムを作る実験です。

Q: 何でこんなことしてるの?

A: 趣味です。人智を越えたスマブラのスーパープレイ、見てみたくないですか…? 開発の経緯が見たければブログの過去の記事をここから辿って見てください。

Q: どうやって動いてるの?

A: Arduino をコントローラ代わりにしてスイッチにコマンドを入力し、出力はキャプチャーボードでパソコンに取り込みます。ゲームは無改造のSwitchの実機で実行されています。詳しい説明や写真などはセットアップの記事を参照してください。ダメージや撃墜の計算は取り込んだ画像を解析して行われています。

Q: どうしてガオガエン…?

A: 作成した人のメインだからです(このファイターのいい動きをよく知っている、というのが大きい)。あと、ガオガエンは強いです。

Q: どうしてマルス…?

A: 飛び道具がなく、単発攻撃が多い近距離ファイターから適当に選びました。

Q: どうしてエレクトロプランクトン(ステージ)…?

A: 一番シンプルで動きも少なかったので。グリーンバックみたいであまり見栄えはよくないかもしれません…。ピクトチャットも考えたけど背景が意外と動いたので却下しました。

Q: 右上のグラフの意味は?

A: Reward は直近20試合程度の報酬の平均値です。報酬は1ストックあたり2ポイントで、ダメージはその部分点がもらえます(100%で1程度)。どういう倒し方をしても最終的に1ストックで2ポイントになるように設定されています。Qは報酬の予測値みたいなものだと思っていただければいいかと思います。直近10000ステップ程度の平均を出しています。

Q: 最新鋭の強化学習アルゴリズム使ってんのに弱くない?

A: どんなアルゴリズムを使っても強化学習にはかなり多くのトレーニングデータが必要になります。実機でデータを出している以上、強くなるまでにはそれなりに時間がかかります。この世に生まれてからまだ1ヶ月くらい、と考えるとそれなりにがんばってる気がしませんかね…?あと、コードにバグが入ってないとも限りません。

Q: 自分もやってみたいんだけど、簡単にできる?

A: デジタル回路とプログラミングの経験が多少あればできると思います。プログラムのセットアップが少々ややこしいですが、ちゃんと数日かけてやることをやればできるはずです。実行に必要なコードと解説はこちらに置いておきました。README_ja.md が日本語での説明になっています。

Q: 本当に成長してるの? 弱くなってない…?

A: 実のところわかりません…。Rewardが上昇傾向にあれば成長していると言えますが、かなり振れ幅があるので、数日単位で見て確認するのがよさそうです。そのうち勝率の上昇の統計的優位性等も出せたらいいなとは思っています。

Q: 自滅しまくるのは何で?

A: 正直なところよくわかりません。ただ、最初よりはかなりましになっています。もしかしたら、この強化学習エージェントは一般化が苦手で、ありとあらゆる状況で自滅してみないとわからないのかもしれません。

Q: どうしてダメージの初期値が12%なの?

A: ダメージ認識を数字ではなく色を使って認識しているからです。0から12%までは色が白で変わらないので、最初から小さな変化を認識できる12%から始めています。

Q: CPUのマルスを倒せるようになったらどうするの?

A: 今のところは、他のCPUともやってみるとか、マルスも深層学習にして互いを相手に練習させるとかを考えています。いいアイディアがあれば Twitter の DM などで教えていただけると嬉しいです。

Q: 最新情報やまとめはどこで見たらいい?

A: 新しい情報は Twitter で発信しています。ここで答えられていない質問等にも答えます。こちらからどうぞ。

深層強化学習AIガオガエンはこういう感じで動いている!

 線がごちゃごちゃなのは許してくれ。


ちなみに配線図はこんな感じ。

以下、材料の詳細。
  • M1 Mac mini (2020) RAM 8GB, SSD 256GB (80,000円くらい)
    • メモリがギリギリなので余裕があれば16GBの方がいいかも。
    • たくさんのメモリとGPUを積んだLinuxが理想的とは思うけど、動作の保証はできない。
  • Elgato HD60 S+ (23,000円くらい)
    • +のないモデルはMacで読めないのでだめ。
    • UVCプロトコルを用いたモデルであればこれでなくても使用可能かも。
  • Nintendo Switch (33,000円くらい)
    • ドックが必要なのでLiteはだめ。有機ELモデルはわからない(多分OKとは思う)。
  • シリアルポートデバイス (1,500円くらい)
    • FTDIチップが乗ってれば他のでもいけるかもしれないけど、挙動が違うかも。
  • Arduino UNO R3 (3,000円くらい)
    • 互換機でもいけるかもしれないけれど挙動が違うかも。
その他インターフェイス系(モニタ、マウス、キーボード)などは多分何でもいいので適当なものを使ってください。ElgatoのOut端子から出てくる信号を表示するサブモニタがあると動作確認に便利。

2021年8月24日火曜日

Lv3マルスが倒せないよ

想像以上の強敵だった…。

Lv3のマルスは強敵であった。色々環境を整え、配信も可能にし、新しい計算機を用意して氷嚢の必要性をなくし、2週間ほど放置しても、クリアライン(報酬の移動平均 >= 1.5)を越えることはなかった…。

これがその戦いの歴史である。

1エピソードあたり2,3分かかるので6500エピソードとなるとざっと270時間程度か。空き時間もあったが、約二週間分のデータでこれである。4000エピソード目くらいまでは成長を感じていたが、その後無慈悲にも下降した。3500くらいまではLossとQが並んで増加していたが、4000あたりから乖離が見られるような気もする。これがまずかったんだろうか…?
ガオガエン自体は別段特に壊れた様子もなく、復帰も徐々にうまくなっているし、ロープスイングのラリアット(タイミングよくボタンを押さないといけないやつ)もだんだん出せるようになってきているので、成長自体はしているのかもしれない。しかしやはり自滅が治らないのである…。明らかに復帰できる状況で復帰しなかったり、余計な入力を入れて落ちていったりしてしまう。

今後どうやってマルスと付き合っていくか考えようと思い、生放送で意見を募ることにした。こちらの放送である。
内容をざっとまとめる。まず上記のような問題を説明し、こちらから色々案を出した。

  1. ネットワーク・アルゴリズムをいじる
  2. Lv3をとばしてLv4にしてみる
  3. ファイターを変えてみる(自分、あるいは相手)
  4. 何もしないでこのまま続ける
  5. 入力を変える(画面のサイズ、HD振動の情報、音声など)
  6. 報酬関数を変える(自滅にさらにペナルティなど)
いつも来ていただいている視聴者さん達からさらに、「生存点を与える」等のアイディアを頂き、まずは簡単にできそうな案2から始めてそれでも駄目なら案5、6(生存点も含む)へと進む提案をいただいたので、そうすることにした。

配信の後にAtariのゲームの場合だとゲームオーバーをエピソードの終わりにするのではなく、ライフを1つ失った時点で終わりにした方がエージェントの成長が早いといデータがあったことを思い出した。これをスマブラにあてはめると、3ストック制ではなく、1ストック制にした方が成長が早まるかもしれないと思いそれも同時にやることにした。

Lv3を倒せないまま飛び級をしたようなものだが、果たしてどうなることか…。
ガオガエンの今後はなるべくライブ配信していこうと思っているので、気になる方は気が向いたときに時々見に来ていただけるとありがたい(コメント欄でのご意見等も歓迎)。

チャンネルはこちら。

2021年8月11日水曜日

Mac を カフェイン漬けにしてフレーム落ちを防ぐ

人も計算機も怠ける。

Lv3マルスは手強く、学習も進展が見えづらい。そんな状況で兼ねてから懸念していた事に取り組んだ。取得しているビデオフレームが具体的にどうなっているかの検証である。

キャプチャーカードのフレームレートは基本的に60Hzなのだが、開発初期の検証によって、ビデオフレームが均一のタイミングで来ているわけではない事がわかっていた。具体的にはだいたい12msの整数倍の間隔で、最終的に60Hzになるようなタイミングで来ている。例としては (12, 12, 24, 12, 24, 12, 12, 24) といった間隔である。周期性もわかりづらく、12msが1回だけ挟まる回もあれば2回挟まる回もある。24msが連続で来ることは(たぶん)ない。20Hzでの動作を目的としているので、3フレームごとに画像を取得したいのだが、その間隔は48ms から 60 ms (実測した時もだいたいこの間隔になる。23msのやつが2回くるか1回来るかで変わってしまう)と、安定しないものになる

今まではあまり気にせずに1ステップごとにタイマーを設定して、「一周したあとで46ms経っていない場合は46msになるまで待つ」というアルゴリズムでやっていて、大体平均して20Hzのデータを作り出していた。しかし、この方法だと実際に何フレームスキップしたのかもよくわからないし、遅延でフレームドロップがあった際に認識することもできない。

もし全てのフレームが問題なく読めるのであれば、3フレーム取得して2フレーム分は捨てる、という選択もできるかもしれないが、今までの実装ではコントローラへの出力も同時に行うと60Hzで画面を読み込むことができない、という問題があった。

そこで、コントローラの方をコードを見てみると、コマンドを出力したあと、スイッチ側からの入力を確認するようにできている。これがだいたい一周期あたり16msで、ビデオ出力の60Hz (16.666...Hz)と微妙に異なる値になっていた。この周期に一致しないとコマンド出力が完了しないことになっていたのでビデオフレーム取得と同時にやると双方が干渉して60Hzの取得は不可能、という形になっていた。

少し検証してみた結果スイッチ側からの入力を無視してもコントローラ操作は可能らしく、フレームレートが十分に遅ければほぼコマンド入力を落とすことなく操作できることがわかった。(本来はスレッドを立ててやるべきなのかもしれないが…)これでスイッチ側からの入力を待つ時間を削除できるので、コマンド入力の時間が最大16msかかっていたものを1ms以下に抑えることができた。

コントローラ操作の時間を削減できたので、他の障害がなければビデオフレームをだいたい60Hzで入力できるようになった。他の障害がなければ、と書いた。障害になるものがいくつかあったのである。

まずは学習を行うスレッドである。CPUを100%近く使う計算を常時行っているので、これが稼動しているときは60Hzが厳しくなる時もある。これについては60Hzで取得するのを諦めて、タイミング測定とビデオフレーム取得を繰り返すことで解決した。具体的にはフレームを取得できた時点で前回取得時の時間と比べて、40ms以下だった場合はまだ3フレームスキップされていないと判断してもう一度取得し、そうでなければ受け入れる、というものである。そのときに時間を記録しておき、もし65ms経過していた場合はフレーム落ちの可能性があるとして報告するようにした。この処理をすることで、前よりも強く3フレームごとに取ることの保証が可能になり、問題がある際も認識できるようになった。

もう1つ問題になったのが、Macの謎の挙動である。スクリーンセイバーや、ディスプレイをオフにする設定に関係なく、1、2分ユーザからの入力がないと勝手にフレーム落ちを初めるのである(怠けモードと呼ぶことにする)。この現象があることは以前から気がついていたのだが、落としたフレームの数を数え始めたことによってより顕著に問題視されるようになった。怠けモードに入る前はフレーム落ちは0.1%以下程度(一試合3000ステップで最大2フレーム程度)なのに対し、怠けモードに入ると10%程度も落とすようになる(一試合100-500フレーム!)。これはまずいと思い対策を考えることにした。

まずはソフトウェアでの対処を考え、無理なようならUSBの疑似HIDを作って一定間隔で何か入力をしてもらうようにしようと思い、まずはソフトを探してみた。どうやら caffeinate というコマンドが元々 Mac に入っているらしく、これが一時的にスクリーンセイバー、ディスプレイの消灯、ディスクの回転停止などの電源関係の機能を抑制してくれるらしいことがわかった。

試した結果、caffeinate では怠けモードは解消されないことがわかった。他にも色々調べた結果、怠けモードは他の省エネ機能 (App Napやら何やら)とも関係ないこともわかった。次に試そうと思ったのがマウスやキーボード入力を勝手に定期的にしてくれるソフトで、 Caffeine がそれに該当するらしいことがわかった (少なくとも昔は、「本来存在しない F15 というキー」を押し続けるソフトだったらしい)。導入し、Karabiner Element のインスペクタで確認しても、F15なるキーの入力がなかったので、不安に思いながらもしばらく放置した結果、無事に怠けモードに入るのを回避できたのである!

これらの試行錯誤の結果、フレーム落ちは合計で0.1%以下にまで抑えることが可能になり、今までよりもずっと綺麗なデータを取得することが可能になった。データのクオリティは学習に直接影響を与えるので、これは大きな変化かもしれない。今まで配信ソフトが起動しているときどんだけフレーム落としてたんだ…? 検証の必要がある…。

色々直した結果、現在の学習状況はこんな感じである。

上のグラフが報酬(青、左Y軸)とロス(橙、右Y軸)で、下がQ値(青、左)と試合の長さ(橙、右Y軸)である。X軸は共通して試合数である。報酬もロスも一定のパターンが見えづらい変化をしているが、Q値はある時期(400試合目くらいか)から着実に伸びてきている。このパターンはLv2の時も見られたので、勝ちパターンだと思いたい。グラフを用意したのが途中からで、これはLv3の最初からではないのだが、ここまででもだいたいLv2のときの2倍くらいの時間がかかっている気がする。ちなみに本稿の修正を施したのはグラフの1000試合目くらいである。

これで直すべきところは直した。あとはAIガオガエンを信じて練習を続けてもらうだけだ。

2021年8月9日月曜日

冷凍庫の安堵の溜息

冷凍庫は安心したかもしれない。

Mac Miniを買ってしまった。ベースモデルで、M1, RAM:8GB, SSD:256GB という構成である。$699で、現状これより安いMacはない。現行のMacはどうせ全部M1だし、強化学習専用マシンにするつもりだったので、今のMacbook Airと同じベースモデルで十分だろうと思いこのモデルにした。オンラインのアップルストアで注文してから1時間半で届いたのには驚いた。将来もっとパワフルなMacが出る際にはこれを売ってアップグレードするかもしれない。

TimeMachineで今の環境を移したところ、ほぼ全てのソフトが作業することなくそのまま動いた。neovimがちょっとだだをこねたのと、USBシリアルポートデバイスのデバイス番号が変わったこと以外は何事もなく、強化学習環境の移行は2時間程度で終わった。

Mac Miniには排熱ファンがついている。MacBook Air の時はパフォーマンスの低下を抑えるために筐体を氷嚢の上に置いていたが(1日3回変える必要がある。アンパンマンのシステムに似ているかもしれない)、もうその必要はない。冷凍庫は毎日3つのなまぬるくなった氷嚢を再冷凍する必要がなくなったのだ。(なお、冷凍庫が冷却の再に排出する熱を溜息にたとえるのなら、溜息の回数は減ることになる。)

余も強化学習のために奪われていたパソコンを返してもらえて御満悦である。やはりパーソナルな計算機は手近なところに必要である…。

Lv2クリア!

 まっすぐな道ではなかった。

レベル1のマルスと比べて動きの種類は増えたものの、まだ何もしない時間も長くスマッシュに勝手に入ってくるため、最初のうちは難なく勝っているように見えた。目標とする報酬の移動平均を1.5に設定したが、開始間も無く1.46程度まであがり、そこからしばらく0.5付近をさまよってから1M(学習)ステップ後に到達した。かかった時間は1日半程度だったか。多少雑だがLoss, Q値, 報酬のグラフを下に掲載する。



いつでも上がれそうな雰囲気でもあったが、そうでなかったのは偶然なのだろうか。最初上がった報酬が下がったときはネットワークが壊れたのかとも思ったが、どうやらそうではなく単純にLv1とLv2の違いに合わせようとしただけなのかもしれない。Lossが増えていったのは多少不安だったが、Q値がほぼ線形に伸びていっていたのでこれはおそらく正しい変化をしているのだと信じて先に進んだ結果、無事到達することができた。

Lv3への到達がなんと配信中に起こったのでアーカイブを残すことができた。クリアするのは動画の最後の方である。

次はLv3…。ここからCPUも少し強くなってくる。

2021年8月5日木曜日

The cat is under training...

 AIガオガエンは成長を始めた。

前回投稿した時から小さな変更(学習率を小さくし、変化幅も狭めた)を加えたが、それ以外はそのままでしばらくトレーニングを続けている。どうやら学習は無事に行われているようだ。変更を加えてから半日程度でLv1のマルスと五分の勝負をするようになり、もう半日程度で7割程度勝つようになった。自滅の頻度も減った。

復帰はLv1マルス相手だと練習する回数が少ないためまだちゃんとできるわけではない。攻撃もスマッシュを溜めて相手が入ってくるのを見て殴るという、Lv1にしか効果がないような戦術をとっているが、これは想定内だ。Lv1を相手にしているのでそれに最適な行動をとるようになったのはむしろ喜ばしい。相手のレベルに応じて適応していく可能性を秘めている。

これがLv1マルスを倒すようになった時の記録である。


勝率が高すぎても低すぎても学習の効率が悪くなるだろうからマルスのレベルを上げることにした。前回の反省をふまえて今回は1つずつ上げていく。今はLv2のマルスと戦っている。Lv1とは違いジャンプ、ダッシュ、DA、掴み、ガードなどを使ってくる。(今思えばLv1はこれらのどれもしなかった…。)攻撃の頻度はまだ低いので似た戦術が通用しなくはないはずだ。

初期の勝率は五割弱。丁度よさそうである。これがまた上がってきて、報酬の移動平均が1.5程度になったら再度レベルを上げるつもりである。

2021年8月3日火曜日

変化する学習率と exploration-exploitation balance

昨日淡々と学習させると言ったが、あれは嘘だ。

結局翌日になっても何の成果も挙げられなかった素振りを見せるガオガエン。このあいだ学習率を下げたときにはすぐに勝率を上げていったのだが、今回はまた同様に停滞した後で学習率を下げても何もおこらなかった。まあ、色々変更があったし、前回よりも学習したステップの数が少なかったので、そのせいかもしれないが、よくなることを期待していたためにがっかりした。

結局試行回数を稼がないといけないのかもしれないが、ここでまた別のアイディアを着想してしまう。学習率が正しいのかよくわからないのであれば、時間変化させてしまえばいい。学習率を周期的に変動させる方法は実は一般的に使用されていて、成果も出しているのだが、失念していた。AtariのKangarooで試してみたところ、学習が劇的に早まったので、とりいれてみることにした。値は広くとって基本の値の10倍から1/10まで、周期はとりあえず5万ステップにしてログスペースでcosで周期振動させてみる。長く停滞する期間があるようならこの方法は有効なはずだ。

というのも強化学習には長く exploration-exploitation balance (探索と収束のバランス)の難しさというものがある。探索をしすぎると解答に収束できないし、収束を早くしすぎると最適解に辿りつけなくなる可能性がある。そのバランスをとるにはいい妥協点を見つけないといけないのだが、これが難しいのである。

周期的に変化する学習率は高いときは探索を促進し、低いときは収束を促進する。うまくやればいいとこどりになりうる。失敗する可能性があるとすれば、低いときに収束した点から遠ざかってしまうことが考えられるが、これが起こっている場合は報酬やQ値に反映されるだろうから、少なくとも観測は可能と考える。

さて、明日からは淡々と学習させる日々が続くはずだ。

2021年8月2日月曜日

マルチスレディングと氷嚢

 ガオガエンはまだLv1のマルスと戦っている。前回戦わせた時よりも成長速度が速い気がする。明日くらいまでには勝率5割程度になっているかもしれない。

昨日は本質的なところではいところに変更を加えた。学習を別スレッドで実行することによって学習のために割かなければいけない時間でもデータ集めに使えるようにした。これによってCPU使用率をほぼ常時最大にできる。一応の欠点は、学習のステップ数がデータ収集のステップ数と合わなくなってくることか。特に大きな問題ではないだろう。

昨日は実験的に配信もした。https://www.youtube.com/watch?v=b5iezNghvoE 配信をするとOBSがリソースを食うので学習効率が落ちるのだが、これに放熱の問題が絡んでいることが発覚した。M1 の Macbook Air には排熱ファンがなく筐体内部に熱がこもるようになっている。M1の消費電力は低いので通常の使用には問題ないが、強化学習のようにCPU使用率を常に高い状態にするようなものだとこれが問題になることもある。配信中の学習は氷嚢なしのときは 3.5 it/s 程度だったが、氷嚢の上にMacbookを乗せると9.5 it/sまで上昇した。約3倍である…。想像以上の効果だった。

できることの大半はやった。ここからしばらくは淡々と学習を続けることになるだろう。次に変更があるとしたらリカレントネットワークか。

Lv8のマルスは強すぎたのか…?

学習率を下げて挑んだものの、その後ガオガエンがLv8のマルスを倒すことはなかった…。 正確には1%程度の確率で勝利することはあるものの、その状態がずっと続き、成長が見られなかった。 

いくつか気になる点があったので変更してみた。 まずは報酬の遅延。画像認識のアルゴリズムの関係上、ダメージを認識するまでに遅延が生じる。ダメージが入っている間、数字が動くアニメーションがあってダメージ部分を読みとれないのだ。通常のダメージのときはおそらく15フレーム(強化学習エージェントのフレームなので20Hzで1フレーム50ms)程度、撃墜の際は30フレーム程度の遅延があるものと思われる。エージェントとしては、この間に入力したどのコマンドに報酬を関連づけてよいのかわかりにくくなっているはずだ。これに加えて、コントローラの入力遅延(測定すると6フレーム程度であった)もあるので、問題は更に深刻である。

これを修正するために、ダメージ部分がどの時点で変わり始めたかを記録しておき、各試合が終わった後に記録を書き変えて報酬のタイミングをダメージが入る入力をした瞬間に近づけるという措置をとった。具体的にはダメージ変更アニメーションの開始部分から6フレーム前に報酬を入れることにした。これでエージェントが報酬と関連づけるコマンドの数をぐっと減らせるはずだ。

もう1つ気になったことは、エージェントが自分の過去の入力を認識していないことである。入力遅延や硬直がある都合上、コマンド入力は即座に画面に反映されない。エージェントは画面の情報しかアクセスできないため、自分がどのようにコマンドを入力したのかわからずに次のコマンドを入れることになる。これでは適切なコマンドが入れられるとは思えないので、入力情報に過去のコマンドも含めることにした。この変更はかなりプログラム面ではかなり大規模なものとなり、パフォーマンスも低下してしまったので、後に最適化の必要があるかもしれない。

パフォーマンスの低下にともなって試合中に直接学習するとフレーム落ちするようになってしまった。これを修正するため、学習時間を試合時間とは別に設けることにした。こうすることによって、報酬の遅延の書き換えが行われていないメモリ区間を学習に使用することもなくなった。他の利点は、試合中のリソース消費が大幅に削減されたことによって配信ソフトなどもフレームを落とすことなく同時に起動できるようになったこと、より大規模なネットワークが使えるようになったこと。欠点は、学習時間中に試合ができないためにデータ取得の効率が落ちることである。この欠点に関しては適切なスレッディングをしてバックグラウンドで学習をさせることで解決できる可能性がある。

より大規模なネットワークを使えるようになったので、 data-efficient の Rainbow から canonical の Rainbow に戻すことができた。大体のパラメータは canonical のものを用いるようにして、history-length と multi-step だけを長めに(10)とるようにした。これによって入力遅延の問題もある程度緩和されるのではないかと思われる。

様々な変更があったのでまたしばらくは様子見をしてどのようにふるまうかを確認する必要がある。またLv1マルスからやりなおしである。一晩かそこらのテストでは結果は出なさそうなので気長に考えていきたい。