テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】Unityで機械学習する「ML-Agent」を色々と試して得た知見とか

去年の9月頃、Unityで機械学習を行う ML-Agent(ver 0.01)が公開されました。

このライブラリを使用すると、Unity上で作成したゲームで機械学習によるAIを実行したり出来そうです。

f:id:tsubaki_t1:20180218210850j:plain

最近このML-Agentを使用して色々と試していたのですが、ある程度形になったので色々とこちらの記事にメモを残します。

f:id:tsubaki_t1:20180218210349g:plain

 

目次

 機械学習

機械学習は最近騒がれている技術の一つで、AIの一種です。

 

その特徴は、「明示的にプログラムされる事なく、経験から学習する」という点にあります。この学習のプロセスは、提供されたデータに基づいてパターンを抽出、観測を行い意思決定を行うというものです。

 

強化学習?

機械学習自体は様々なアルゴリズムがあるのですが、UnityのML-AgentではReinforcement Learning(強化学習)アルゴリズムが採用されています(緑色)。

f:id:tsubaki_t1:20180218211209j:plain

強化学習のサイクルは「Agentが何らかのアクションを行い、環境に反映、その結果をステート及び報酬として受け取る」というものです。

AIは「何をしたらどう変化する」という点を観測し抽象化、「良い報酬を受ける/悪い報酬を受けない為には何をすればよいか」という事を学習していく訳です。

f:id:tsubaki_t1:20180218212654j:plain

  • Action : AI様の指示
  • State : AI様が観測しているゲーム内情報
  • Reward : 指示の結果、良くなったらご褒美、悪くなったらお仕置き

 

報酬は"結果"ではなく"連続した動作"に与えられる

面白いのが、報酬を常に与える必要は無いという点です。

最終的な結果に対して「成功した・失敗した」といった情報を与えておくと、連続した動作を逆算して動作を評価してくれるみたいです。

 

例えば「ボールを落とさない」といった目的の場合、普通に動作の評価を行う場合 落ちるキッカケとなる動作を探し・評価するコードを記述する必要がありました。例えば「上に反射すると+0.1」や「打ち返す角度が良ければ+0.1」「生存時間で+0.1」等々…

f:id:tsubaki_t1:20180218214123j:plain

強化学習の場合「ボールが落ちたら-1」という情報を元に「どうやったら落ちるのか」を逆算・学習し、自分で重みを設定してくれます。逆に「ボールが落ちなければ+0.1」とされていた場合、「落とさない為に行うべき動作」に+評価を与えるよう学習します。

 

この辺りが、自分でプログラムを組んでAIを考えた場合のアプローチと強化学習の違いです。なお、報酬の情報が少ないと「生存のために必要なアプローチ」を大量に試さなければいけなくなるので学習効率は落ちるみたいです。

 

Stateの変化を数式で表現出来ないものは上手く学習出来ない印象です。

例えばブロック崩し等の「動きが連続しているもの」は少ない学習でもかなり精度を出していますが、STGのようなランダムに敵や弾出現する(Stateのデータがアッチコッチに飛ぶ)モノは、時間をかけた割に今ひとつ上手く動いてくれていないです。ソレっぽくはなるんですが…

f:id:tsubaki_t1:20180218223613g:plain

Unityと強化学習

UnityのML-Agentが用意した強化学習は、下のような構成になっています。

基本的にAgentがAIの指示の元、Environment(環境)に対して何らかのアクションを行い結果を云々は変わりません。

Agentは動作の定義、BrainはAIの動作モードPython(Tensorflow)はAIを制御しています。

f:id:tsubaki_t1:20180218220321p:plain

重要なポイントは、BrainとAgent、そしてPython(Tensorflow)で構成されているという点です。ツナギ方や数に意味はそれ程ありません。

AgentがBrainに接続できて、BrainがExternal(外)の場合に学習を外部Python上で動作するTensorflowで行う点だけ覚えておけば良さそうです。

 

なお学習結果を元にPython上のTensorflowから指示させる事も出来ますし(External)、TensorFlowSharpをUnityに対応させたTSFUnityPluginを組み込むことでゲーム内に組み込む事も出来ます(Internal)。

 

独自のプロジェクトを作ってみる

導入方法は、多くの方が紹介しているので割愛します。

 

今回は、下のようなモノを作ってみました。

f:id:tsubaki_t1:20180218230957g:plain

元となる振る舞いは、下の動きと同じようなものです。

http://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160115/20160115232211.gif

tsubakit1.hateblo.jp

ただし、下のような鬼畜難易度になっています。

  • 赤いパーツが上方向に推進力を出す(傾くと一気に横転する)
  • 赤いパーツの位置は毎回微妙に違う(両方同時にブーストかけると即転ぶ)
  •  外周に板が接触したらゲームオーバー
  • 玉を落としてもゲームオーバー

最早これは手動では飛ばせませんし、コレを飛ばす計算式を考えるのも面倒です。そんな状態からスタートしてみます。

 

 

シーンのセットアップ

まずやることは、シーン内にAcademyとBrainコンポーネントを登録することです。

Academyが親オブジェクト、Brainは子オブジェクトとして登録します。特にAcademyに何もしないのであればTemplate Academyとか設定しておけばOKです。

なお、BrainはAgentの設定によってちょくちょく設定が変更されます。

f:id:tsubaki_t1:20180218225245j:plain

 

後はAgentを作り動作を定義すれば良いです。

  • アクションの設定
  • Stateとして何を送るか
  • 報酬の取得方法

アクションの設定とStateの送信は、Agentクラスを継承したクラスに記述していきます。簡単な穴埋め問題です。

gist.github.com

作ったAgentには、使用するBrainを登録しておきます。

f:id:tsubaki_t1:20180218225359j:plain

 

アクションの設定

まずはアクションの設定を考えてみます。アクションは、機械学習AIからの指示です。どういった指示を行うべきなのか、よく考えて設定します。

BrainのAction Sizeに、受け取る指示の数を設定します。

 

ここで一つ面倒な所が、BrainのActionStateTypeのDiscrete と Continuous で受け取る情報が変化する点です。

f:id:tsubaki_t1:20180218230631j:plain

Discreteの場合、配列には1つの要素しか含まれません。つまりact[0]に値が格納され、Action Sizeの内のいずれかの値が来ます。

Continuousは複数の要素を受け取れます。配列の長さはAction Sizeと同じで、複数の支持を同時に受け取れます。

 

要するに上のようにAction Sizeが2の場合、Discreteなら1か2、Continuousならfloat{ 0.2f, 0.1f };のような値を受け取ります。

単純に色々したいならContinuousの方が色々と出来ますが、Discreteの方が報酬とアクションが結びつきやすく、結果が出るのが速いです。

 

後はAgentStepの中身を定義します。
ロケットからInput系のコードを全部剥ぎ取って、AgentStepから指示を出しています。ちなみにAgentStepはFixedUpdateのタイミングらしいので、ソコんとこ注意

 

AgentStepを設計したら、BrainTypeをPlayerにしてちゃんと動くか確認するの、超オススメです。この手順をスキップすると、後で思ったように動かなかったりします。

f:id:tsubaki_t1:20180218232014j:plain

ちなみに今回作ったデモでは、左右のブースターのON/OFFしてるだけです。

コードにするとこんな感じ

    public override void AgentStep(float act)
    {
        rocket.Power( act[0] * 20, act[1] * 20);
    }

 

Stateの設定

次に、アクションの結果ゲームがどのように変化するのかを観測します。
このグラフを元に関数を作成し、学習を行うっぽいので、重要な要素です。

 

まず登録するステートの数をBrainに設定します。

f:id:tsubaki_t1:20180218232432j:plain

これは、CollectStateに中身を埋めたリストを返してやれば良いです。Listの要素はState Sizeと同じ数です。ここ自分よく見落とします。

for等で一括収集する場合は、そのあたり注意です。

 

また数が変動する場合は最大数で登録し、適当な数で埋めます。その際、値の連続性が失われないように注意する必要がある気がします。
この辺り、オブジェクトにユニークIDを割り振るのが良いかもしれません。

 

数は抑えた方が良いかなーと思ってましたが、観測するのが面倒ですが膨大でも案外行けるんじゃないかな感があります。カメラ画像すら使えるみたいですし(流石に80x80白黒のような低解像度ですが)。

 

Reward(報酬)の設定

後は報酬です。報酬は特に学習に強く影響するので、よく考える必要があります。

報酬+1のアイテムを散らすだけだと、物凄い慎重にアイテムを回収しようとします。そこに常に-0.05の報酬を与えると、少し急かせます。急かしすぎると自殺する事があります。

 

どの程度報酬を貰っているかはMonitorクラスで確認すると良いです。
CollectStateにRewardを観測する感じの処理を記述しておけば、ゲーム上に値が表示されます。

Monitor.Log(key:"reward",value: reward, target:transform );

f:id:tsubaki_t1:20180218235742j:plain

なお、RewardとDoneは別にAgentクラス内でなくとも良いです。例えば接触した外周のOnCollisionEnter2Dで、接触対象がAgent持ってたらreward -= 1とかしても良いです。
特にCollisionのようなコールバック系はAgentStepやCollectState内で設定するのは面倒くさいので、コレが出来る事は知っておいたほうが良いです。

 

今回作った奴では、報酬は「(玉もしくはロケットが)落ちたら-1」「生存してるなら毎フレーム+0.1」だけです。

 

終了とリスタート

最後にエージェントが目標を達成もしくは失敗したら、done = true;を呼んでやります。これも別にAgentクラス内でなくとも良いです。

done=true;が通るとAgentResetが呼ばれるので、ここでキャラクターの座標や玉の位置等、ゲーム内情報をリセットしておきます。

 

学習

後はpython ppo.py <env_name> --trainとかでトレーニングします。

 

途中で学習を中断したいなら、一旦止めて --load を足して学習を再開したり、--max-stepsで回数を増やしたりも出来ます。

ただしnormalizeやnum-layers等の設定が変わるとレジューム出来ないです。

  • 途中から再開 : python ppo.py <env_name> --train --load
  • 試行回数を増やす:python ppo.py <env_name> --train --max-steps=すごい数

 

ちなみに、--trainを除くとトレーニングではなく再生モードになります。

一々3DBall.bytesのようなものを作って云々しなくとも動作が確認出来るのは良いです。

  • python ppo.py <env_name> --load

その他

  • 100倍速でトレーニングを行う訳ですが、確認すると割と上手く動いてない事が多いです。なので最低1回は1~3倍速で動かしてみるのオススメです。報酬やStateが変だったりします。
  • betaを上げると、トレーニング時のアクションがもっとランダムになるらしく、答え?に到達しやすくなるかもしれません

関連

f:id:tsubaki_t1:20180220122751j:plain

Unity ConnectのML-Agents Challenge 1での作品と実装例です。
いくつかはプロジェクトが公開されているので参考になります。

connect.unity.com

blogs.unity3d.com一番納得した強化学習についての解説

qiita.comMacでML-Agentを試すときに分かりやすいです

qiita.com

Windowsはこちら

am1tanaka.hatenablog.comAWSでやりたい場合

towardsdatascience.com

書いてて段々と面倒くさくry