このブログはML-Agents 0.5.0aのものです。ML-Agents 0.6.0a用のものをこちらにアップしました。
ML-Agentsの動作環境は整えられました。次は、新しいプロジェクトにML-Agentsを組み込む手順を確認します。以下のドキュメントを読んでいきます。
github.com
以下は、元記事から変更や補足したところです。
- ブレインの名前の変更
- ターゲットに近づくとリワードがもらえる機能のコードの追加
- 学習手順と再生手順とまとめを追加
目次
動作環境
以下で確認しました。
- Windows10
- Unity2018.2.10
- ML-Agents Beta 0.5.0a
ML-Agentsのリポジトリーをクローンしていなかったり、環境構築ができていない場合は、先に以下の記事を完了させてください。
am1tanaka.hatenablog.com
ランダムな場所に登場するキューブを取るようにボールを制御するAIをトレーニングします。また、ボールが地面から落下しないようにもします。
- Unity上でAIをトレーニングするための環境(environment)を作ります。環境は、いくつかのオブジェクトによるシンプルな物理シミュレーションから、ゲームやエコシステム全体まで含みます
- Academy(アカデミー)サブクラスを実装してゲームオブジェクトにアタッチして、環境を組み込んだUnityのシーン内に配置します。このゲームオブジェクトは、シーン内の様々なブレイン(Brain=AIの脳)の親として動作します。アカデミークラスには、AIエージェントとは別にシーンを更新するいくつかのオプションのメソッドを実装できます。例えば、環境内に、AIエージェントやその他のオブジェクトを追加、移動、削除したりできます
- 1つ以上のAIのブレインオブジェクトを、アカデミーの子供としてシーンに追加します
- エージェントサブクラスを実装します。エージェントサブクラスは、環境を観察するためのコードを定義して、行動を割り当てて、強化学習のための報酬(Reward)を計算します。また、学習が終了したり、タスクが失敗した時に、エージェントをリセットするメソッドを実装します
- 作成したエージェントサブクラスをゲームオブジェクトにアタッチします。アタッチしたオブジェクトは、シミュレーション内のエージェントとしてシーン内で行動します。エージェントには必ずブレインオブジェクトを割り当てる必要があります
- Brain TypeをExternalで学習させます
- Brain TypeをInternalにして、学習してできたモデルデータを使ってUnityでAIを動かします
Unityプロジェクトの作成
まずは、新規でUnityプロジェクトを作成して、ML-Agentsを組み込みます。
- Unity2017.4以降を起動して、
RollerBall
という名前で新しいプロジェクトを作成します
- エクスプローラーで、ダウンロード(あるいはクローン)して展開したml-agentsフォルダーを開きます
UnitySDK/Assets
フォルダー内のML-Agents
フォルダーをドラッグして、UnityのProject
ウィンドウにドロップしてインポートします
Unityのバージョンによって表示は変わりますが、おおよそ以下のようになります。
ML-Agentsツールキットを有効にする
公式ドキュメントだと、ML-Agentsの設定の手順が抜けているので、以下のようなエラーが発生すると思います。
こちらの「この段階ではまだエラーが発生します。設定を続けます。」という文の続きを設定します。以下、簡単な手順です。
- Editメニューから、Project Settings > Playerを選択します
以下を、利用したいプラットフォームごとに実施します
- Other Settingsを開きます
- Scripting Runtime VersionがExperimental、あるいは.NET 4.6 Equivalent、あるいは.NET 4.x Equivalentにします
- Restartするかのウィンドウが表示されたら、Restartします
Unityが一度閉じて再起動するのを待ってください。これでエラーが出なくなります。
環境(Environment)を作る
ML-Agentsの環境用のシーンとして、エージェントが乗っかる床となるPlane、エージェントがゴールや目標として探索するCube、エージェントを表すSphereを作成します。
床のPlaneを作る
- HierarchyウィンドウのCreateをクリックして、3D Object > Planeを選択します
- 作成したPlaneの名前を
Floor
にします
- 作成したFloorをクリックして選択します
- InspectorウィンドウのTransform欄を以下の通り設定します
- Position =
0, 0, 0
- Rotation =
0, 0, 0
- Scale =
1, 1, 1
- InspectorウィンドウのMesh Renderer欄にあるMaterialsの左の三角をクリックして開いたら、Element 0の右の〇をクリックして、ウィンドウからFloorマテリアルを選択して割り当てます
以上で床は完成です。
取るためのキューブを作る
- HierarchyウィンドウのCreateをクリックして、3D Object > Cubeを選択します
- 作成したCubeの名前を
Target
にします
- 作成した
Target
をクリックして選択します
- InspectorウィンドウのTransform欄を以下の通り設定します
- Position =
3, 0.5, 3
- Rotation =
0, 0, 0
- Scale =
1, 1, 1
- InspectorウィンドウのMesh Renderer欄にあるMaterialsの左の三角をクリックして開いたら、Element 0の右の〇をクリックして、ウィンドウからBlockマテリアルを選択して割り当てます
- HierarchyウィンドウのCreateをクリックして、3D Object > Sphereを選択します
- 作成したSphereの名前を
RollerAgent
にします
- 作成した
RollerAgent
をクリックして選択します
- InspectorウィンドウのTransform欄を以下の通り設定します
- Position =
0, 0.5, 0
- Rotation =
0, 0, 0
- Scale =
1, 1, 1
- InspectorウィンドウのMesh Renderer欄にあるMaterialsの左の三角をクリックして開いたら、Element 0の右の〇をクリックして、ウィンドウからChekerGoalマテリアルを選択して割り当てます
- Add Componentをクリックします
- PhysicsからRigidbodyを選択してアタッチします
後程、このオブジェクトにエージェントサブクラスをアタッチします。
アカデミーとブレインを持たせる空のゲームオブジェクトを作る
- HierarchyウィンドウのCreateをクリックして、Create Emptyを選択します
- 作成したGameObjectの名前を
Academy
にします
- 作成した
Academy
オブジェクトを右クリックして、Create Emptyを選択して、子供に空のゲームオブジェクトを作成します
- 作成した子供のGameObjectの名前を
RollerBallBrain
にします(元記事では単にBrain
でしたが、後の設定のため変更しました)
以上でシーンが完成です。Main Cameraの座標や角度を調整して、Gameウィンドウで見やすくなるようにしてください。
アカデミーの実装
アカデミーオブジェクトは、シーン内のML-Agentsを動かして、観測-意思決定-行動というシミュレーションループを制御します。ML-Agentのシーンごとに1つのアカデミーのインスタンスが必要です。アカデミークラスのベースクラスはabstractなので、メソッドを実装する必要がなくてもサブクラスを作成する必要があります。
まずは、先に作成したアカデミーオブジェクトに新しいスクリプトを追加します。
- HierarchyウィンドウからAcademyオブジェクトをクリックして選択します
- Inspectorウィンドウで、Add Componentをクリックします
- New Scriptを選択して、
RollerAcademy
という名前で作成します
- 作成したRollerAcademyスクリプトをダブルクリックして、エディターで開きます
- MLAgentsの機能を使うために、
using MLAgents;
を定義します
- クラスの継承元を
MonoBehaviour
からAcademy
に変更します
Start()
とUpdate()
メソッドを削除します
基本的な機能はベースクラスのAcademyが持っているので、実装は不要です。以下の通りにしたら、上書き保存をしてUnityに切り替えます。
using MLAgents;
public class RollerAcademy : Academy { }
デフォルトの設定のまま使います。Inspectorウィンドウで変更する項目はありません。
ブレイン(Brain)を作る
ブレインオブジェクトは、行動を決める処理を担当します。エージェントは自らが観測した情報をブレインに送ります。ブレインは送られてきた観測情報を元に行動を決定してエージェントに伝えます。エージェントは、ブレインから受け取ったデータに従って行動します。Brain Type設定で、ブレインがどのように意思決定を行うかを選択することができます。アカデミーやエージェントクラスと違い、ブレインは既存のスクリプトをそのまま利用します。
- Hierarchyウィンドウから、Academyの子供のRollerBallBrainオブジェクトを選択します
- Inspectorウィンドウで、Add Componentをクリックします
- Scripts > MLAgents > Brainを選択して、コンポーネントをアタッチします
設定は改めて行います。現時点ではBrain TypeはPlayerのままにしておきます。
エージェントの実装
エージェントを作成します。
- Hierarchyウィンドウから、RollerAgentオブジェクトをクリックして選択します
- Inspectorウィンドウで、Add Componentをクリックします
- New Scriptを選択して、
RollerAgent
という名前で作成します
- 作成したRollerAgentスクリプトをダブルクリックして、エディターで開きます
- Academyの時と同じように、
using
を設定して、ベースクラスをMonoBehaviour
からAgent
に変更します
以下のようなコードになります。
using MLAgents;
public class RollerAgent : Agent { }
上書き保存をして、Unityに戻ります。
ここまでの手順は、あらゆるUnityのプロジェクトにML-Agentsを加える基本的な手順です。次に、強化学習を使ってSphereのエージェントがキューブを取るように動かすためのロジックを加えていきます。
今回のシンプルなシナリオでは、アカデミーオブジェクトに環境の変更は実装しませんが、シミュレーション前やシミュレーション中に、床の大きさを変えたり、エージェントやその他のオブジェクトを追加したり削除したりしたい場合は、アカデミーにメソッドを自由に追加して実装することができます。ここでは、エージェントが成功したり失敗した時に、エージェントや目標物をリセットすることだけを行います。
エージェントの初期化とリセット
RollerBallのエージェントは、Targetに触れたら自分自身でそのことを記録して、Targetをランダムな座標に配置し直すリセット関数を呼び出します。また、RollerBallが地面から落下したら、リセット関数によって地面に戻します。
Targetを追うためには、エージェントはTargetのTransform
を知っている必要があります。そのために、RollerAgent
クラスにpublic
なTransform
を定義します。これにより、Inspectorウィンドウで、ターゲットを設定できるようになります。エージェントの速度をリセットしたり、エージェントを動かすための力を与えるために、Rigidbody
コンポーネントの参照が必要です。Rigidbody
は、Unityで物理シミュレーションをするための基本的なエレメントです。スクリプトのゲームオブジェクトにアタッチされているRigidbody
への参照は、Start()
メソッド内で、GameObject.GetComponent<T>()
を使って取得します。
RollerAgent.cs
は以下のようになります。
using System.Collections.Generic;
using UnityEngine;
using MLAgents;
public class RollerAgent : Agent {
Rigidbody rBody;
private void Start()
{
rBody = GetComponent<Rigidbody>();
}
public Transform Target;
private float previousDistance = float.MaxValue;
public override void AgentReset()
{
previousDistance = float.MaxValue;
if (this.transform.position.y < -1.0)
{
this.transform.position = Vector3.zero;
this.rBody.angularVelocity = Vector3.zero;
this.rBody.velocity = Vector3.zero;
}
else
{
Target.position = new Vector3(
Random.value*8-4,
0.5f,
Random.value*8-4
);
}
}
}
次は、Agent.CollectObservations()
関数(情報を収集する関数)を実装します。
環境を観測する(Observing the Environment)
エージェントは、収集した環境の情報をブレインに送ります。ブレインは送られてきた情報を使って行動を決めます。エージェントをトレーニングする時、あるいは、トレーニングされたモデルを利用する時に、観測したデータをfeature vector(観測結果を表すベクトルデータ)としてニューラルネットワークに与えます。エージェントの学習を成功させるためには、正しい情報を与えなくてはなりません。どの情報を与えるのが正しいかを決めるよい指標は、問題を解決するために何が必要かを分析して考えることです。
今回の場合、エージェントは以下の情報を集めます。
- ターゲットの座標。一般的に、絶対的な座標よりも、エージェントから見た相対的な座標を使う方がよいでしょう。今回は、Plane上を移動するだけでY座標は変化しないので、X-Z座標のみ利用します
Vector3 relativePosition = Target.position - this.transform.position;
AddVectorObs(relativePosition.x / 5);
AddVectorObs(relativePosition.z / 5);
- 床の四隅からのエージェントの距離。エージェントを床に留まらせるための情報です
AddVectorObs((this.transform.position.x + 5) / 5);
AddVectorObs((this.transform.position.x - 5) / 5);
AddVectorObs((this.transform.position.z + 5) / 5);
AddVectorObs((this.transform.position.z - 5) / 5);
- エージェントの速度。ターゲットを通り過ぎたり、床から転落しないように速度を学習させるための情報です
AddVectorObs(rBody.velocity.x / 5);
AddVectorObs(rBody.velocity.z / 5);
全ての値は5
で割って、正規化をしています。これは、ニューラルネットワークが受け取る値が-1
~1
の範囲だからです。何故5
かというと、今回の床のサイズが10
なので、-5
~5
でどこにいるかが表されるからです。
(補足)
この処理では1を越えることがあるため、正規化としては不十分です。後程、正規化フラグを設定します。
(補足ここまで)
観測した状態(state observation)の数は8つです。それをAddVectorObs()
関数でブレインに渡します。
以上をまとめると以下のようになります。RollerAgent.cs
に加えてください。
public override void CollectObservations()
{
Vector3 relativePosition = Target.position - this.transform.position;
AddVectorObs(relativePosition.x/5);
AddVectorObs(relativePosition.z/5);
AddVectorObs((this.transform.position.x + 5)/5);
AddVectorObs((this.transform.position.x - 5)/5);
AddVectorObs((this.transform.position.z + 5)/5);
AddVectorObs((this.transform.position.z - 5)/5);
AddVectorObs(rBody.velocity.x/5);
AddVectorObs(rBody.velocity.z/5);
}
エージェントコードの最後は、Agent.AgentAction()
関数です。ブレインが決定した行動を受け取ります。
Actions
ブレインが決めた行動は、アクションの配列としてAgentAction()
関数に渡されます。この配列の要素数は、エージェントのブレインの設定であるVector Action Space TypeとVector Action Space Sizeによって決まります。RollerAgentでは、Continuous Vector Action Space(連続的な行動空間)で、ブレインから2つのcontinuous control signals(連続的な制御信号)を必要とします。よって、Brain Vector Action Sizeには2
を設定します。最初のエレメントであるaction[0]
はX軸方向に加える力で、2つ目のaction[1]
はZ軸方向に加える力とします(もし、Y方向の移動もあった場合は、Vector Action Sizeは3
になります)。ニューラルネットワークから返される値は、-1
~1
の間の値になります。ブレインは、これらの値がどのようなものなのかは把握しません。トレーニングは、観測した入力に対して返す値を調整した結果、どのようなリワード(報酬)が得られるかということだけに注目して行われます。
RollerAgentはaction[]
配列の値をRigidbodyコンポーネントであるrBody
のAddForce()
で反映させます。
Vector3 controlSignal = Vector3.zero;
controlSignal.x = action[0];
controlSignal.z = action[1];
rBody.AddForce(controlSignal * speed);
実際のコードは後述します。
Rewards(リワード=報酬)
強化学習ではリワードの設定が必要です。リワードはAgentAction()
関数内で割り当てます。学習アルゴリズムは、シミュレーションのステップごとにエージェントにリワードを与えて、エージェントにとって最適な行動が何かを学習するのに利用します。エージェントが目的のタスクを達成したら(今回ならTargetオブジェクトに辿り着いたら)報酬を与えて、望まない行動をしたら(今回なら床から転落したら)報酬を取り上げます。
トレーニングを短縮するために、エージェントが最終的なタスクを達成するのを助けるための補助的なリワード(sub-rewards)を設定する方法があります。例えば、Targetとの距離が前回より近づていたらリワードを少し与えつつ、毎ステップごとに少しリワードを減らすような設定を加えることで、エージェントはTargetに近づくことが有利だと学習でき、Targetに到着しやすくなります。
RollerAgentは、Targetに辿り着いたかを検出するために距離を計算します。算出した距離が到達したと判断する値以下になっていたら、エージェントに1.0
のリワードを与えて、完了したことを報告するためにDone()
を呼び出します。
float distanceToTarget = Vector3.Distance(this.transform.position,
Target.position);
if (distanceToTarget < 1.42f)
{
AddReward(1.0f);
Done();
}
メモ: Done()
を呼び出してエージェントが完了したことを知らせると、リセットするまで動作が停止します。Agent.ResetOnDone
プロパティーをインスペクター上でTrueに設定しておくことで、完了したらすぐに自動的にエージェントをリセットすることもできますし、アカデミーがMaxStepに到達して、環境をリセットするのを待つこともできます。今回はResetOnDoneプロパティーをTrueに設定してリセットします。MaxStepは0
のままにして、アカデミーが環境をリセットしないようにします。
(補足)
Targetへの距離が縮まったら少しリワードを与えて、学習効果を高めるためのコードです。元記事には変数は定義されていたものの、リワードを与えるコードがなかったので補足します。
if (distanceToTarget < previousDistance)
{
previousDistance = distanceToTarget;
AddReward(0.03f);
}
(補足ここまで)
以下は、ステップごとにリワードを少し減らすことで、タスクをより手早く完了できるようにするための工夫です。
AddReward(-0.05f);
最後に、プラットフォームから転落した時にリワードを大きく減らす処理と、Done
を呼び出して次のステップで自分自身をリセットさせる処理です。
if (this.transform.position.y < -1.0)
{
AddReward(-1.0f);
Done();
}
AgentAction()
行動とリワードの考え方は以上の通りです。AgentAction()
関数の完成形は以下のようになります。RollerAgent.cs
に加えてください。
public float speed = 10;
public override void AgentAction(float[] vectorAction, string textAction)
{
float distanceToTarget = Vector3.Distance(
this.transform.position,
Target.position);
if (distanceToTarget < 1.42f)
{
AddReward(1);
Done();
}
if (distanceToTarget < previousDistance)
{
previousDistance = distanceToTarget;
AddReward(0.03f);
}
AddReward(-0.05f);
if (this.transform.position.y < -1.0)
{
AddReward(-1);
Done();
}
Vector3 controlSignal = Vector3.zero;
controlSignal.x = vectorAction[0];
controlSignal.z = vectorAction[1];
rBody.AddForce(controlSignal * speed);
}
仕上げのエディター設定
全てのゲームオブジェクトとML-Agentコンポーネントの設定が完了したので、データを接続していきます。
- HierarchyウィンドウのAcademyオブジェクトの左の三角をクリックして開いて、子供のRollerBallBrainオブジェクトを表示します
- HierarchyウィンドウのRollerAgentゲームオブジェクトをクリックして選択します
- Hierarchyウィンドウから、RollerBallBrainオブジェクトをドラッグして、InspectorウィンドウのBrainフィールドにドロップします
- Inspectorウィンドウで、Dicision Frequencyを
1
から5
に変更します
- エージェントは、この設定のステップ数ごとにブレインへ観測データを送信して意思決定を要求します。行動(Action)は1ステップごとに行われます(バランスボールのサンプルもこの値は5でした)
また、HierarchyウィンドウからTargetオブジェクトをドラッグして、RollerAgentのTargetフィールドにドロップします。
最後に、RollerBallBrainゲームオブジェクトをクリックして選択したら、Inspectorウィンドウで以下を設定します。
- Vector Observation Space Size = 8
- ブレインに送る観測データベクトルの次元数(要素数)
- Stacked Vectors = 1
- Visual Observation = 0
- 観測データとして利用するグラフィックの数。今回は未使用なので0
- Vector Action Space Type = Continuous
- 行動結果を表すベクトルを、整数(Discrete)で返すか、小数(Continuous)で返すか
- Vector Action Space Size = 2
- Action Descriptions = 2
- Brain Type = Player
- 意思決定の種類
- Player = 人が操作する
- Heuristic = 行動決定のためのアルゴリズムを
Decition
インターフェースを実装したクラスを作成して、自前で実装する
- External = Pythonと通信して、TensorFlowなどに意思決定させる
- Internal = TensorFlowを使って学習済みのモデルで意思決定する
以上で、トレーニングを開始する前に、環境を試す準備ができました。
環境を試す
実際にトレーニングを開始する前に、手動で環境を試すのはよい手順です。Brain TypeをPlayerのままにしてあるので、キー入力でエージェントを動かすことができます。前後左右の4方向に4つのキーを割り当てます。
- HierarchyウィンドウでRollerBallBrainオブジェクトをクリックして選びます
- InspectorウィンドウのKey Continuous Player Actions欄を開きます(この設定は、Brain TypeがPlayerの時のみ表示されます)
- Sizeを
4
にします
- 以下の通り、設定します
Element |
Key |
Index |
Value |
Element 0 |
D |
0 |
1 |
Element 1 |
A |
0 |
-1 |
Element 2 |
W |
1 |
1 |
Element 3 |
S |
1 |
-1 |
Indexの値は、AgentAction()
関数に渡す配列のインデックスを表します。Valueの値は、Keyが押された時にvectorAction[Index]
に渡される値です。
Playすると、[W][A][S][D]キーで、Agentを操作することができます。エラーが発生しないことと、Targetに触ったり床から落ちたらエージェントがリセットされることを確認してください。ML-Agents SDKには、ゲームウィンドウにエージェントの状態を簡単に表示させることができるMonitor
クラスが用意されています。より詳細な状態が見たい場合は利用するとよいでしょう。
(ここではやりませんが)作成した環境とPython APIが想定通りに動くかどうかを、Jupyter notebookを使って試しておくことができます。確認のためには、notebooks/getting-started.ipynb
を利用します。ノートブックのenv_nameに、この環境をビルドして生成した実行ファイル名を設定します。
学習の準備が整ったら、ブレインのBrain TypeをPlayerからExternalに変更します。ここから先の手順は、Training ML-Agentsと同じです。
シーンレイアウトを振り返る
最後に、作成した環境でエージェントがどのように動作するかを振り返っておきましょう。
今回のトレーニングをUnity ML-Agentsで実行するために、3つのゲームオブジェクトを作成しました。
約束事です。
- 1つのシーンには、Academyゲームオブジェクトを1つだけ配置できます
- Brainは1つのシーンに複数配置できますが、それらはすべてAcademyゲームオブジェクトの子にする必要があります
以下、シーンのHierarchyの見え方の例です。
(https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Learning-Environment-Create-New.md より)
元記事には学習手順と学習結果を利用する手順が省略されているので、以下に補足します。
学習手順
前提
Unity ML-AgentsをWindows10で使う 2018年11月版 - tanaka's Programming Memoが完了していることを前提にします。
準備
まずはブレインを外部に変更します。
- HierarchyウィンドウでRollerBallBrainをクリックして選択します
- Inspectorウィンドウで、Brain TypeをPlayerからExternalに変更します
学習の設定をします。デフォルトの5万ステップだと、あまり学習の成果が見られません。3D Ballのサンプルは12個のエージェントを同時にトレーニングしていたので2万ステップ程度で効果が表れましたが、こちらは同時に1つのエージェントしかトレーニングしないので、その10倍の20万ステップは最低でも学習させたいところです。余裕をもって、50万ステップに設定します。
また、ニューラルネットワークは-1
~1
の範囲のデータを受け取る仕様になっています。ところが、今回のサンプルでは状況に応じてこの範囲を超えてしまうことがあり、学習の妨げになります。そこで、正規化フラグを有効にすることで範囲外の数値が来たら-1
~1
の範囲に収めるように設定します。
- ML-Agentsのリポジトリーからダウンロードした
ml-agents-master
フォルダー内のconifg/trainer_config.yaml
を何らかのテキストエディターで開きます
- 22行目の
curiosity_enc_size: 128
という行と、24行目のBananaBrain:
の間に、以下を追加します
24:
RollerBallBrain:
normalize: true
max_steps: 5.0e5
これで、UnityのRollerBallBrain
という名前のブレインをトレーニングする場合、以下が有効になります(このためにBrainの名前をRollerBallBrain
に変更しました)。
- 入力データを正規化(normalize)する
- ステップ数を50万回(
5.0e5
は、5x10^5
の意味)
学習させる
Pythonを使って学習を開始します。
- Windowsの検索バーに
ana
と入力して、Anaconda Promptを起動します
cd
コマンドで、ml-agents-masterのフォルダーに移動します。作成したUnityProjectではなく、ML-AgentsのGitHubリポジトリーをダウンロードして展開したフォルダーの方です
- 以下を入力します
mlagents-learn config/trainer_config.yaml --run-id=RollerBall --train
- Unityのアスキーアートが表示されて、
INFO:mlagents.envs:Start training by pressing the Play button in the Unity Editor.
という表示が出たら、Unityに切り替えて、Playすれば、学習が始まります。
ログの見方
コンソール画面に謎の文字が表示されますが、ここから学習がどのように進んでいるかを読み取ることができます。このログは、設定した回数ごとの集計になっています。デフォルトでは千回に1回、集計されて以下のように表示されます。
Step以降を見ていきます。上の例ではStep: 360000.
になっています。359001
回~360000
回のステップのトレーニングが完了した時の情報ということです。
大切なのは、それに続くMean RewardとStd of Rewardの2つの数値です。
Mean Reward
リワードの平均値です。今回の例なら、36万ステップまで千ステップの平均リワードが1.149
だったということです。この値は、最初は-15
ぐらいでしたので、かなりの学習成果が上がっているということになります。
この値は、上がったり下がったりを繰り返しつつ、徐々に大きくなっていくはずです。5万~10万ステップぐらいまでは変化しているように見えない場合もありますので、慌てずじっくりと眺めてください。
20万ステップしても変化がないようなら、学習効果が得られない設定になっている可能性があります。プログラムや考え方を見直してみてください。
Std of Reward
リワードの標準偏差です。標準偏差とは、データのバラつきを表す値です。全てのリワードが同じ値ならこの値は0
になり、バラけているほど大きな値になります。
AIがめちゃくちゃに動いている最初の方はリワードがバラバラになるので、大きな値になりがちです。頭が良くなるにつれて行動が似てくるのでリワードのバラつきが減り、この値は小さくなっていくのが一般的です。
いきなりこの値が小さい場合、めちゃくちゃに動いてもリワードが変わらないということなので、エージェントの設定を失敗している可能性があります。
条件によっては学習が進むほどこの値が大きくなることもあります。この値が小さくなれば十分に学習できた、と単純には言えないということです。バラつきを表す値ということを考えて、値を読み取ってください。
学習は何ステップがよいか
条件によって結構変化しますが、とりあえず2Dのものなら50万ステップ程度が目安のように感じています。学習をさせすぎると成績が悪化する過学習という現象が起きる場合があります。学習後にMean Reward
を確認して、スコアが悪化していくようなら、丁度良さそうなステップ数を確認して、その回数で学習し直すとよいでしょう。
(ここから、2019/1/19追記)
0.6.0aのドキュメントを参照したところ、trainer_config.yaml
のbatch_sizeを10
、buffer_sizeを`100*にすることで、2万ステップ程度でも十分に学習効果が上がることが分かりました。
batch_size: 10
buffer_size: 100
今回のようなシンプルな学習の場合、バッチやバッファサイズを減らすことで、学習を早めることができました。
また、座標や速度などは、Y
を使わなかったとしても、ベクトルでそのままブレインに渡した方が学習効果が高いようでした。
(追記ここまで)
まだスコアが伸びそうな場合は、trainer_config.yaml
のmax_stepsの値を増やして上書き保存をしてから、Anaconda Promptで以下を実行すれば、先ほどのデータの続きから学習を再開できます(すでに学習ステップ数がmax_stepsに達していたら、学習を再開させてもすぐに終わってしまいます。学習回数を増やす場合は、trainer_config.yaml
のmax_stepsも増やしてください)。
mlagents-learn config/trainer_config.yaml --run-id=RollerBall --train --load
今回のプロジェクトだと、30万ステップぐらいでほぼ安定して、50万ステップまで少しずつ精度が上がっていった(Std of Rewardが小さくなっていった)感じでした。
考察(おまけ)
前よりターゲットに近づいた時に与えるリワードを0.1
にして、毎回の減点より大きい数値にして試すと、100万ステップ学習させてもスコアが伸び続けました。面白いのが、標準偏差も大きくなっていったことです。近寄るごとにリワードがもらえる設定だと、Targetをすぐに取るよりも、ゆっくり近づいて取った方がリワードが多く得られるようでした。そうなると、どこにTargetが出るかによって点数に差が出るようになります。リワードが出現位置の乱数に影響を受けるようになるため、標準偏差の値が大きくなっていたものと思われます。
なるべく早くTargetを取るAIにしたかったので、リワードを見直して今回のパラメーターにしました。
再生手順
最後に、学習したモデルデータをUnityのプロジェクトに組み込んで再生してみましょう。
TensorFlowSharpプラグインをプロジェクトにインポートします。ダウンロードしていない場合は、こちらからダウンロードして、インポートしてください。PC上で試すだけなら、AndroidやiOSのプラグインは不要なので、チェックを外した方が容量やインポート時間を節約できます。
インポートが完了したら、TensorFlowを有効にするための設定をします。
- Editメニューから、Project Settings > Playerを選択します
以下を、利用したいプラットフォームごとに実施します
- Other Settingsを開きます
- Scripting Defined Symbols欄に、
ENABLE_TENSORFLOW
を入力して、[Enter]キーを押します
- [Ctrl]+[S]キーを押して保存します
以上で、Brain ModeでInternalが利用できるようになります。
学習したモデルデータをプロジェクトに読み込みます。モデルデータはml-agents-masgter
フォルダーの中に、models
> run-idで設定した名前のフォルダーの中に入っています。
- エクスプローラーなどで
ml-agents-master
フォルダー内の、models/RollerBall
フォルダーを開きます
.bytes
ファイルをドラッグして、UnityのProjectウィンドウにドロップします
最後に、ブレインの設定を切り替えて、モデルデータを設定します。
- Hierarchyウィンドウで、RollerBallBrainをクリックして選択します
- Inspectorウィンドウで、Brain TypeをInternalに変更します
- Projectウィンドウからモデルデータをドラッグして、InspectorウィンドウのGraph Model欄にドロップします
以上で完了です。Playをすれば、学習させたAIがRollerAgentを操作してTargetを取っていくのが確認できます。
まとめ
新規プロジェクトから学習用の環境を構築して、学習、結果の利用ができました。入力や出力を当てはめられるものであれば、自前のプロジェクトに組み込めそうです。
学習させる上で、以下の知見が得られました。
- ベクトルの観測データは、使っていない要素があってもベクトルのままブレインに渡した方がよい(2019/1/19追記)
- 学習設定のbatch_sizeとbuffer_sizeを小さい値にして学習を試してみると、早く学習が完了する可能性がある(2019/1/19追記)
- 学習は50万ステップ程度は見込んでおく
- 同時に複数のエージェントを学習できるように、3DBallのように親オブジェクトからの相対座標で動く環境を作るとよい(この記事の作り方だと、同時に学習できないのでステップ数が多くなるので)
- サブリワードの設定は、学習の回数を減らしたりイメージに近い行動をさせるのに大いに有効
- 正規化も重要。コード側で正規化をしていない場合は、設定の
normalize
フラグを利用する
一年前はこの辺りで他のことを初めてしまってとん挫したので、今回は自分で作った環境でAIを鍛えたいです。
参考URL