前回の記事ではML-Agentのカリキュラム学習を、–initialize-from==RUN_IDオプションを使って行った。
今回はUnityのSceneをいくつも作成し、学習の進捗に応じてSceneを自動的に切り替えていくことで、カリキュラム学習を実装していく。
C#スクリプトと、Unity, ML-agentsの関係はこんな感じ。

青いAgentが黄色いTargetを3秒間見つめ続けられたらクリア(=プラス報酬)となっている。詳しいルールとステージ解説は前回の記事を参照してほしい。
↓githubのソースコード
環境
- Windows10 64bit home
- python3-mlagents 0.23.0
- Unity 2019.4.17f1
- mlagents release12 1.7.2
yamlファイルの記述
yamlファイル進捗状況に応じたパラメーターを記載する。
behaviors | 子要素に、機械学習で使用される設定値を記載していく。 |
SearchTarget | この名前はAgentのインスペクターで設定される「Behavior Name」と合わせる |
environment_parameters | 子要素に、Unityに渡す変数を設定していく。 |
stage_number | Unityから値を取得するときのキーとなる文字列を「stage_number」としている。 |
curriculum | 子要素に、カリキュラム学習で使用する変数を設定していく |
– name | リスト形式で何個でも追加できる |
completion_criteria | |
measure | 次のステージへ進むための、基準を何にするか。 「reward」は報酬の平均値 「progress」は steps/max_steps の比率 |
behavior | 次のステージへ進むためのAgentを何に選ぶか。 今回のAgentはSearchTarget 一匹だが、例えば鬼ごっこで「鬼Agentが人間Agentを80%捕まえられるようになったら次のステージへ」といったことが可能 |
min_lesson_length | 次のステージに進むまでに最低何回のエピソードをするか。 |
threshold | measureで設定した基準の値をここに入力する。 |
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--;
}
コメント