こちらのページでは、ML-Agentsの公式サンプルに入っています、「3DBall」についての解説ページになります。
「3DBall」は「Agent」(Unityマークの付いた平板)の傾きを上手く調節して「Ball」を落とさないようにキープしよう!というゲームになります。前回ご紹介しました「Basic」のサンプルに比べると繊細な操作が要求され、ルールはシンプルですが自分で遊んでみても意外と難しく、これを機械学習するとどんな感じになるのか…??というゲームになっています。
ちなみに、パッと画面を見ると12個の同じゲームが並んでいて???となるかもしれませんが、これは1つのBrainに複数(今回は12個)のAgentが紐づいており、機械学習時に効率良く学習するためにこうなっております。
なお、本ページはML-Agents(ver0.5)に基づいて解説していきます。2018年12月17日に、ML-Agents(ver0.6)が新規にリリースされました。ver0.6では、Brain Typeの設定方法が大きく変更されております。設定方法についてはこちらのページにまとめましたので、ML-Agents(ver0.6)でサンプルをいじる際は本ページと併せて参考にして下さい。
・「↑」:vectorAction[1]に値「1」を返す
・「↓」:vectorAction[1]に値「-1」を返す
・「←」:vectorAction[0]に値「1」を返す
・「→」:vectorAction[0]に値「-1」を返す
です。やはりこれだけでは何のことかさっぱり分かりませんが、後で解説しますようにスクリプト(Ball3DAgent.cs)を見ると、以下のように対応していることが分かります。…と言っても、パッとスクリプトを見るだけでは、どのように動くのか正直分かりづらいと思います。キチンと考えたい方は右図の平板の座標系を参考にして下さい(赤矢印:x軸、緑矢印:y軸、青矢印:z軸です)。
・「↑」:「Agent」(平板)が少し奥に傾く(←平板のx軸中心に+2°回転)
・「↓」:「Agent」(平板)が少し手前に傾く(←平板のx軸中心に-2°回転)
・「←」:「Agent」(平板)が少し左に傾く(←平板のz軸中心に+2°回転)
・「→」:「Agent」(平板)が少し右に傾く(←平板のz軸中心に-2°回転)
また、上でも書きましたように1つのBrainに12個のAgentが紐づいているので、「Brain Type:Player」の場合は、12個のAgentを同時操作することになります。ただ、スクリプトを読むと分かりますが、ボールが最初に落ちてくる位置は完全にランダムで、12個のゲーム中でもバラバラな位置に落下してきます。なので、人力では12個ともボールをキープするのは不可能です。なので、本気で遊ぶ場合も1個のゲームに対してだけ集中して遊びましょう(笑)。
ちなみにスクリプトより、ボールを落とすと報酬-1、ボールをキープし続けると各ステップ報酬+0.1、が入る仕組みになっており、これからいかに長い時間ボールを落とさずキープし続けられるか、というゲームになっていることが分かります。
実際プレイしてみた感じはいかがでしょうか?私ワガハイは10秒ももちませんが下手くそなのでしょうか(笑)?だって、板に摩擦とか無いからボールがツルツル滑るんですもの…orz
では次に、「Brain Type:External」にしてこのゲームを機械学習させてみましょう(実行のための詳細手順はコチラのページを参照)。
機械学習の最初の方では↓のように、ボールをボロボロ落としまくっております。ワガハイの方が上手いやんけ!(傲慢)
次に、5万ステップ程学習した後の動きを見てみましょう。↓のようになります。「Agent」はゲームの目的を充分理解しており、さらにちゃんとボールをキープ出来ております。すごいバランス感覚ですね…(・0・。)(たまにこぼしてるのはご愛嬌…(笑))。
この「Basic」には、4つのスクリプト(Ball3DAcademy.cs、Ball3DAgent.cs、Ball3DDecision.cs、Ball3DHardAgent.cs)が付いております。各スクリプトの概要についてはサンプル集解説0にて書いた通りですが、今回のスクリプトの主役は「Agent」に実装している「Ball3DAgent.cs」になりますので、これの中身を解説していきます。
今回もそこまで複雑なスクリプトではありませんので、やはりML-Agents特有のクラス「Agent」の基本に重点を置いて解説していきます!
サンプル集解説0にて書きましたが、「Agent」に付けるスクリプトは「Agent」クラスを継承しており、5つの(オーバーライド)メソッドを持っているんでしたね。では、各メソッドの詳細を見ていきましょう。
今回は、GetComponentメソッドでRigidbodyコンポーネントにアクセスしております。やはり、通常のUnityで出てくるMonoBehaviorクラスのStartメソッドのような使われ方ですね。
エピソード終了時(④よりBallが落下した時に対応)に呼び出されます。今回も、エピソード終了時には場面がリセットされ、各オブジェクトが初期配置に戻される手続きがなされています。ただし、ボールの初期位置と平板の初期傾き具合はランダムに設定されるようですね。どんな初期配置にも対応出来るように機械学習させたい!という意図でしょうか…?
④のメソッドで「Agent」が行動した結果の状態をAddVectorObs()メソッドを使ってBrainに教えるメソッドでしたね。今回のゲームでは、「Agent」(平板)の状態だけでなく、Ballの状態もBrainに返しています。
機械学習を上手に行うには、どういう状況でゲームが上手く進み(=報酬がプラス)、どういう状況だとゲームが上手く進まない(=報酬がマイナス)のかをAI様がきちんと理解しておく必要があります。AI様は人間のように目を使ってゲーム全体を把握しているしている訳では無いので、AddVectorObs()メソッドを介した情報以外のことは何も知りません。
確かに、「Agent」(平板)の状態だけ分かっても、Ballがその上に乗っているか落下しているかでゲームの成功or失敗は大きく変わりますよね。ですので、Ballの状態も知ることが機械学習には必須になります。
public override void CollectObservations() { AddVectorObs(gameObject.transform.rotation.z); AddVectorObs(gameObject.transform.rotation.x); AddVectorObs(ball.transform.position - gameObject.transform.position); AddVectorObs(ballRb.velocity); }
具体的には、上のように「板のz軸周り回転角」「板のx軸周り回転角」「板に対するBallの位置座標(相対座標)」「Ballの速度」で、float値を合計1+1+3+3=8成分返しています。右下図の「Space Size」が「8」になっているのはこれが理由です。
if (brain.brainParameters.vectorActionSpaceType == SpaceType.continuous) { var actionZ = 2f * Mathf.Clamp(vectorAction[0], -1f, 1f); var actionX = 2f * Mathf.Clamp(vectorAction[1], -1f, 1f); if ((gameObject.transform.rotation.z < 0.25f && actionZ > 0f) || (gameObject.transform.rotation.z > -0.25f && actionZ < 0f)) { gameObject.transform.Rotate(new Vector3(0, 0, 1), actionZ); } if ((gameObject.transform.rotation.x < 0.25f && actionX > 0f) || (gameObject.transform.rotation.x > -0.25f && actionX < 0f)) { gameObject.transform.Rotate(new Vector3(1, 0, 0), actionX); } }
また、本メソッド中での報酬が与えられる仕組みは次のようになります。AddReward(○○)で○○の報酬を「Agent」に与えるんでしたんね。これを見ると、最初に書きましたように、ボールを落とすと報酬-1、ボールをキープし続けると各ステップ報酬+0.1、であることが分かります。
Done()はエピソードの完了を表しAgentReset()メソッドが発動、でしたね。下のスクリプトからも、Ballが落下する(又は板から大きく離れる)とエピソード完了であることが分かりますね。
if ((ball.transform.position.y - gameObject.transform.position.y) < -2f || Mathf.Abs(ball.transform.position.x - gameObject.transform.position.x) > 3f || Mathf.Abs(ball.transform.position.z - gameObject.transform.position.z) > 3f) { Done(); SetReward(-1f); } else { SetReward(0.1f); }
今回は使用していません。
なお、このゲームにはハード版があります。Sceneのフォルダーを見ると、「3DBallHard.unity」なるファイルがありますよね。何がハードなのかと言いますと、別にゲーム自体の難易度が上がっている訳では無く、機械学習の難易度が上がっているんです。下のスクリプト(Ball3DHardAgent.cs)抜粋をご覧下さい。
public override void CollectObservations() { AddVectorObs(gameObject.transform.rotation.z); AddVectorObs(gameObject.transform.rotation.x); AddVectorObs(ball.transform.position - gameObject.transform.position); }
上と違うのは、「Ballの速度」が無くなりましたね。もしかしたら、上の③CollectObservationsメソッドの所で「Ballの速度情報なんている??」と思われた方がいらっしゃるかもしれませんが、例えば「板の端っこでBallが超スピード」な状況と「板の端っこだけどBallは止まりかけ」な状況では前者の方が絶望感がスゴいですよね(笑)。なので、Ball速度情報は無くても良いですが、有った方がより効率的に機械学習できるということです。