前回の記事ではML-Agentのカリキュラム学習を、–initialize-from==RUN_IDオプションを使って行った。

https://kajindowsxp.com/ml-agents-initialize-learn/

今回はUnityのSceneをいくつも作成し、学習の進捗に応じてSceneを自動的に切り替えていくことで、カリキュラム学習を実装していく。

C#スクリプトと、Unity, ML-agentsの関係はこんな感じ。

青いAgentが黄色いTargetを3秒間見つめ続けられたらクリア(=プラス報酬)となっている。詳しいルールとステージ解説は前回の記事を参照してほしい。

↓githubのソースコード

https://github.com/kajikentaro/ml-agents-curriculum

環境

  • Windows10 64bit home
  • python3-mlagents 0.23.0
  • Unity 2019.4.17f1
  • mlagents release12 1.7.2

yamlファイルの記述

yamlファイル進捗状況に応じたパラメーターを記載する。

ML-curriclumu.yaml

behaviors:
  SearchTarget:
    trainer_type: ppo
    summary_freq: 10000
    time_horizon: 64
    max_steps: 10000000
    keep_checkpoints: 5
    checkpoint_interval: 500000
    hyperparameters:
      learning_rate: 3.0e-4
      batch_size: 512 
      buffer_size: 10240
    network_settings:
      normalize: false
      hidden_units: 128
      num_layers: 3
environment_parameters:
  stage_number:
    curriculum:
      - name: FirstTrain
        completion_criteria:
          measure: reward
          behavior: SearchTarget 
          min_lesson_length: 100
          threshold: 0.999
        value: 1.0
      - name: SecondTrain
        completion_criteria:
          measure: reward
          behavior: SearchTarget 
          min_lesson_length: 100
          threshold: 0.999
        value: 2.0
      - name: ThirdTrain
        completion_criteria:
          measure: reward
          behavior: SearchTarget 
          min_lesson_length: 100
          threshold: 0.98
        value: 3.0
      - name: ForthTrain
        completion_criteria:
          measure: reward
          behavior: SearchTarget 
          min_lesson_length: 100
          threshold: 0.95
        value: 4.0

C#スクリプトから、yamlの環境変数を取得する

この様なメソッドで取得できる。第一引数にenvironment_parametersの子要素で設定した文字列、第荷引数に存在しなかった場合のデフォルト値を設定する。

戻り地はfloat型

Academy.Instance.EnvironmentParameters.GetWithDefault("stage_number", -1.0f);

MainMenu.csで組み立ててみるとこんな感じ。

floatをintにキャストして、その値に応じたsceneNameを辞書型クラスから参照できるようにした。

Dictionary<int, string> sceneName = new Dictionary<int, string> { { 1, "TrainingArea1" },{ 2, "TrainingArea2" },{ 3, "TrainingArea3" },{ 4, "TrainingArea4" } };
void newGame()
{
//yamlから学習の進捗に応じたステージ番号を取得
int newStageNumber = (int)Academy.Instance.EnvironmentParameters.GetWithDefault("stage_number", -1.0f);

//ステージ番号からステージをロードする。
SceneManager.LoadScene(sceneName[newStageNumber]);
}

Scene推移が起こったときに、Agentを殺す

これをしないとSceneが放棄されているのにも関わらず内部のAgentへアクセスが続き、エラーが出まくる。

次のステージへ進む前に、今のステージで蠢いているAgentちゃんと、それにくっついているDecisionRequesterをDestroyしておくこと。

DestroyImmediate(GetComponent<DecisionRequester>());
DestroyImmediate(this.gameObject);

その他

あとは今までと一緒の通り、実装を行えばいいわけだが、効率を上げるためAgentを複数同時に学習させるときには少しややこしい。

MainMenuをDontDestroyOnLoad(this)で放棄しないように設定し、Agentが死んだタイミングでカウンター(aliveAgent)をデクリメント、もしそれが0に達したらMainMenuから次のステージを呼び出すという方針にした。

なにかもっといいアイデアがあれば教えてほしい!

static int aliveAgent= 9;
static int nowStageNumber;
void Start()
{
//MainMenuを放棄しないよう(常に裏で動き続けるよう)にする。
DontDestroyOnLoad(this);
newGame();
}
void Update()
{
if (aliveAgent == 0)
{
    aliveAgent = 9;
    newGame();
}
}
//Agentから呼び出される関数
public static bool stageContinue()
{
int newStageNumber = (int)Academy.Instance.EnvironmentParameters.GetWithDefault("stage_number", -1.0f);
if (nowStageNumber == newStageNumber) return true;
return false;
}
//Agentから呼び出される関数
public static void agentDestroyed()
{
aliveAgent--;
}

実際の学習の様子

https://youtu.be/65i0LzR47rk