TOPIC36|PLATEAUで構成したデジタルツインで避難シミュレーションする
PLATEAUの3D都市モデルを使うと、現実の都市に近いデジタルツインをコンピュータ上に作ることができ、各種シミュレーションに使うことができます。このトピックでは、そうした一例として、避難シミュレーションの構築を試みます。

PLATEAUの3D都市モデルを使うと、現実の都市に近いデジタルツインをコンピュータ上に作ることができ、各種シミュレーションに役立ちます。実際、PLATEAUのハッカソンやPLATEAU AWARDなどでは、3D都市モデルを避難シミュレーションに活用する事例がよく見られます。このトピックでは、そうしたシミュレーションを行うための環境をUnityで構築する方法を紹介します。
作った環境で動かすシミュレーションの実例として、避難率が高くなるような避難所を提案する機械学習モデルを作ります。Unityでは、ML-Agentsというライブラリを用いることで、比較的簡単に強化学習モデルが作れます。簡易的なシミュレーション条件下でのモデルであり、強化学習の詳細までは触れませんが、PLATEAUと強化学習を組み合わせる際の参考としてお届けします。
このトピックの内容は「PLATEAUで構成したデジタルツインで避難シミュレーションする」(2024年度PLATEAU Hands-onアーカイブ動画)でも制作方法をハンズオン形式で紹介しています。
【目次】
36.2 Unityプロジェクトの作成と必要なパッケージの追加
36.2.3 PLATEAU SDK for Unityの追加
36.2.6 Newtonsoft.Json for Unityパッケージの追加
36.3.2 スクリプトをアタッチするためのGameObjectを作る
36.5.5 機械学習ライブラリのML-Agentsから実行される処理
36.1 _ このトピックの見どころ
このトピックでは、PLATEAUの3D都市モデルをUnityで読み込んでデジタルツインを作り、都市のシミュレーション環境として使う試みを紹介します。
36.1.1 _ 避難シミュレーション環境を作る
このトピックでは、PLATEAUで構成したデジタルツインを用いて避難シミュレーション環境を作ります。
この避難シミュレーションの基本機能は、全体の避難率の算出です。いくつかの避難所を設定し避難者を配置して実行すると、(後述するUnityのナビゲーションメッシュの機能によって)最も近い避難所に避難を開始し、一定時間後に何パーセントが避難できたかがわかります。
このトピックでは、この基本的な機能をもとに「どの避難所を選ぶと避難率が最も向上するのかを提案する機械学習モデルの構築」を模索します。
どの避難所を選ぶと避難率が向上するかは、避難者の数や現在の位置、避難所までの道路の位置関係などによって異なります。そこで、これらの状態を観測する強化学習モデルを作って実現します。
この避難シミュレーションでは、完全にランダムな場所に避難者を配置するのではなく、あらかじめ決めた場所に避難者を配置することもできるようにしました。そのため、地域ごとに人口の分布が異なる場合や、花火大会などのイベントで人が集中するような場合もシミュレーションできます。

このトピックでは、次の手順でこの避難シミュレーション環境を構築していきます。
① 建物や道路などをインポートしてシミュレーションの舞台を作る
Unity上にPLATEAUの3D都市モデルを読み込んでデジタルツインを作り、シミュレーションの舞台を作ります。
② 避難所を作る
①に対して、避難所の候補となる場所に、目印のキューブを配置します。またそのキューブと道路を接続して、道路と接した状態にします。
③ 避難路をナビゲーションメッシュとして構成する
このトピックでは、Unityのナビゲーションシステム(Navigation System)機能を使って、避難者が避難所に向けて避難していく様子をシミュレーションします。
ナビゲーションシステムとは、あらかじめ指定した範囲内を通って、目的地に向かってキャラクタを自動で動かすことができる機能です。ゲームを作るときに、敵がプレイヤーを追いかけてくるような動きを作るときによく用いられます。このナビゲーションシステムの機能を使って、避難者を敵キャラクタに、避難所を自分(プレイヤー)に見立てて動かすことで、避難者が避難所に移動するシミュレーションを実行します。
キャラクタが通行できる領域のことを「ナビゲーションメッシュ(略してナビメッシュ:NavMesh)」と言います。今回のシミュレーションでは、3D都市モデルの道路(ならびに②で延長した避難所までの接道)が通行できる領域ですから、これらをナビゲーションメッシュとして構成します。
④ 避難者を配置して避難させ、強化学習する
こうして作成したシミュレーションの舞台に、人を模したオブジェクトを配置し、ナビゲーションシステムの機能を使って、実際に避難させます。
このとき、観測値として「避難所の位置や収容人数、人の位置(x,y,z)」、行動として「選択している避難所」、報酬として「避難時間を加味した避難率」を設定した強化学習モデルに対して、何度も繰り返し学習させます。
学習を終えると、人々の位置に応じて、最も多く避難できる避難所候補が出力される機械学習モデルが出来上がります。
⑤ 機械学習モデルを使って予測する
④の機械学習モデルを推論モードで実行することで、最適な(=最も多くの人が避難できると予想される)避難所候補が出力されます。
36.1.2 _ 見どころ
このトピックの見どころは、次のとおりです。
① PLATEAU 3D都市モデルのインポート
まずは、PLATEAUの3D都市モデルをUnityにインポートします。インポートの方法について解説します。
② ナビゲーションメッシュの設定やナビゲーションシステムの使い方
人を模したオブジェクトが道路上を動けるようにするため、道路に対して、ナビゲーションメッシュを設定します。ナビゲーションメッシュの構成方法や、ナビゲーションシステムを使ってオブジェクトを動かす方法について解説します。
③ 強化学習の方法
このトピックでは、強化学習モデルの構築に、ML-Agentsライブラリを用います。ML-Agentsライブラリを使った強化学習の方法について解説します。
④ 学習済みモデルの使い方
③で学習した学習済みモデルに推論させ、実際に適切な避難所を出力する方法を解説します。
なお、本チュートリアルのプロジェクトファイル一式は下記で提供しています。ソースの細かい部分については、実際にダウンロードしてご確認ください。
【メモ】
GitHubのプロジェクトをダウンロードしてUnity Editorで開くと、ML-Agentsパッケージが見つからないエラーが発生します。その場合は、「36.2.5 ML-Agentsパッケージの追加」を参考にML-Agentsパッケージ一式を追加したのち、プロジェクトを保存し、開き直してください。
[サンプルプログラム]
https://github.com/Rikkyo-MiyayuLab/PLATEAU-Tutorial
36.2 _ Unityプロジェクトの作成と必要なパッケージの追加
プロジェクトを新規作成し、必要なパッケージ一式を追加します。
36.2.1 _ Unityプロジェクトの作成
まずは、Unityプロジェクトを作成します。今回のプロジェクトでは、Unityの2023.2.19f1を使っています。プロジェクトは、「3D(Built-In Renderer Pipeline)」として作成します。
【メモ】
特定のバージョンを使いたいときは、Unityダウンロードアーカイブページから入手できます。

36.2.2 _ このトピックで必要なパッケージ
このトピックで必要なパッケージは、以下のとおりです。作成したプロジェクトに、これらのパッケージをインストールしていきます。
【メモ】
インストール順序は不同ですが、Consoleにエラーメッセージが表示されることがあります。その場合は、一度、プロジェクトを閉じて、開き直してください。
・PLATEAU SDK for Unity
PLATEAUの3D都市モデルを読み込んでUnityで使えるように変換したり、読み込んだ3D都市モデルの属性を扱えるようにしたりするためのライブラリです。
・AI Navigation
ナビゲーションシステムの機能を提供するパッケージです。
・ML-Agents
Unityで機械学習するときに用いる、定番のフレームワークです。このトピックでは、強化学習を用いた機械学習モデルの構築に用います。
・Newtonsoft.Json for Unity
JSONシリアライザー/デシリアライザーのライブラリです。このトピックでは、3D都市モデルの属性を扱う際のデータ書式の変換に用います。
【メモ】
シリアライザー(シリアル化)はJSON形式への変換機能、デシリアライザー(デシリアル化)はJSON形式から元の形式に戻す機能です。
36.2.3 _ PLATEAU SDK for Unityの追加
まずは、PLATEAUの3D都市モデルを扱うためのPLATEAU SDK for Unityを追加します。
インストール方法の詳細は、インストールマニュアルに記されているので、その手順に従います。このトピックでは、バージョン2.3.2を使います。
【手順】PLATEAU SDK for Unitを追加する
[1]Package Managerを開く
[Window]―[Package Manager]をクリックしてPackage Managerを開き、[+]ボタンから―[Add package from git URL]を選択します。

[2]PLATEAU SDK for Unityを追加する
PLATEAU SDK for UnityのGitHubのURLである、下記のURLを入力して追加します。「#以降」はバージョン番号(正確には、リリースされたタグ名)です。
[PLATEAU SDK for Unity バージョン2.3.2]
https://github.com/Project-PLATEAU/PLATEAU-SDK-for-Unity.git#v2.3.2

36.2.4 _ AI Navigationパッケージの追加
続いて、AI Navigationパッケージを追加します。このパッケージはUnity Registryに登録されているため、次の手順で追加できます。
【手順】AI Navigationパッケージを追加する
[1]Unity Registryに切り替える
左上のドロップダウンリストをクリックし、[Unity Registry]に切り替えます。

[2]AI Navigationパッケージを追加する
一覧から[AI Navigation]を探してクリックし、[Install]ボタンをクリックします。右上の検索ボックスに「AI」と入力すると、AIから始まるパッケージに絞り込まれるので、こうした方法で見つけるとよいでしょう。

36.2.5 _ ML-Agentsパッケージの追加
次に、機械学習ライブラリのML-Agentsパッケージをプロジェクトに追加します。ML-AgentsのGetting Started Guideに従って、次のようにします。
[ML-Agents Getting Started Guide]
https://github.com/Unity-Technologies/ml-agents/blob/develop/docs/Getting-Started.md
【手順】ML-Agentsパッケージを追加する
[1]ML-Agentsをダウンロードする
ML-Agentsのリリースページから、[Source code(zip)]をダウンロードし、適当なフォルダに展開します。
[ML-Agents リリースページ]
https://github.com/Unity-Technologies/ml-agents/releases

[2]Package Managerから追加する
Package Managerを開き、[+]ボタンから―[Add package from disk]を選択します。

[3]com.unity.ml-agentsを追加する
手順[1]でダウンロードしたフォルダの中のcom.unity.ml-agentsフォルダを開き、package.jsonを選択して追加します。

[4]com.unity.ml-agents.extensionsを追加する
同様にして、手順[1]でダウンロードしたフォルダの中のcom.unity.ml-agents.extensionsフォルダを開き、package.jsonを選択して追加します。

36.2.6 _ Newtonsoft.Json for Unityパッケージの追加
最後に、JSON形式データを扱うために使うNewtonsoft.Json for Unityを次の手順で追加します。
【手順】Newtonsoft.Json for Unityパッケージを追加する
[1]Package Managerを開く
Package Managerを開き、[+]ボタンから―[Add package from git URL]を選択します。
[2]Newtonsoft.Jsonを追加する
Newtonsoft.JsonのGitHubのURLである、下記のURLを入力して追加します。
[Newtonsoft.Json for Unity]
https://github.com/jilleJr/Newtonsoft.Json-for-Unity.git

36.2.7 _ Python環境の構築
ML-Agentsでモデル訓練を実行するには、Python環境を用意し、ML-Agentsのパッケージをインストールしておく必要があります。
Python環境のインストールは、使っているOSによって異なりますが、例えば、Python標準のパッケージインストールツールであるpipを用いる場合は、次のコマンドを入力してインストールします。
【メモ】
このトピックでは、Python 3.10.2で動作確認しています。Pythonの環境構築の詳細については、チュートリアル動画を参考にしてください。pyenvを用いた仮想環境の切り替え方法なども解説しています。
pip install mlagents==1.0.0
インストールができたかどうか確認するには、次のように入力します。
mlagents-learn
待ち受けの画面が表示されたら、インストールされています。あとで再度、使いますが、この段階では、[Ctrl]+[C]キーを押して、終了させてください。

【メモ】
終了には、しばらく時間がかかります。終了処理中に再度実行すると、「Previous data from this run ID was found.」というメッセージが表示されることがあります。この場合は、「mlagents-learn --resume」として前回のデータを引き継いで再実行するか、「mlagents-learn --force」で強制上書きして再実行してください(まだ何も学習していないときは、後者がよいでしょう)。
コラム:GPUを用いる場合
Nvidia社のGPUを搭載しているPCを使っている場合は、モデルの訓練においてGPUを使えます。GPUを使うことで、CPUよりも学習時間が早くなります。
GPUを使う場合は、CUDA Toolkitならびに対応するPyTorchをインストールします。例えば、次のようにすると、CUDA Toolkitのバージョン11.8の環境でモデルの訓練ができます。
【手順】CUDAならびに対応するPyTorchをインストールする
[1]CUDAをインストールする
下記のURLにアクセスし、CUDA Toolkit 11.8をインストールします。
[CUDA Toolkit 11.8ダウンロードページ]
https://developer.nvidia.com/cuda-11-8-0-download-archive
[2]CUDA版PyTorchをインストールする
下記のコマンドを入力し、CUDA Toolkit 11.8に対応したPyTorchをインストールします。
pip install torch==2.4.1+cu118 torchvision==0.19.1+cu118 --index-url https://download.pytorch.org/whl/cu118
36.3 _ 3D都市モデルの読み込み
必要なライブラリ等を準備したら、3D都市モデルを読み込みます。このトピックでは、横須賀市役所周辺のモデル(LOD2モデル)を使います。
【メモ】
PLATEAU SDK for Unityを使って3D都市モデルをインポートする詳細な手順については、「TOPIC 17|PLATEAU SDKでの活用[1/2]|PLATEAU SDK for Unityを活用する」を参考にしてください。以下では、手順のみ示します。
36.3.1 _ 3D都市モデルを読み込む
まずは次の手順で、3D都市モデルを読み込みます。
【手順】PLATEAUの3D都市モデルを読み込む
[1]PLATEAU SDKを起動する
Unity Editorの[PLATEAU]メニューから[PLATEAU SDK]を選択して起動します。
[2]横須賀市を選択する
[サーバー]を選択し、[神奈川県][横須賀市]を選択して、[範囲選択]をクリックします。

[3]範囲を選択する
マウスでクリックしてインポートする範囲を選択し、[決定]をクリックします。今回は、市役所周辺の沿岸地域(3次メッシュコード:52397533、52397534を中心とした範囲)を選択しました。

[4]オプションを指定してインポートする
インポートのオプションを指定して、インポートします。このトピックでは、次のオプションを指定し、[モデルをインポート]をクリックして、インポートします。
・テクスチャを含めない
シミュレーションの実行に際してテクスチャは必要ないため、[テクスチャを含める]のチェックを外しておきます。
・建築物、道路、土地起伏のみインポートする
建築物、道路、土地起伏のみにチェックを付けます。都市計画決定情報、災害リスク、土地利用は、シミュレーションの実行に際して使わないので、チェックを外しておきます。
航空写真や地図はシミュレーションには必要ありませんが、のちに建物と道路とを手作業でつなげる手順があり、そのときに、これらがあると接道の目安になるため、[航空写真または地図を貼り付け]にチェックを付けてインポートしています。

[5]読み込んだ3D都市モデルを確認する
読み込みが終わると、Hierarchyウィンドウに14201_yokosuka-shi_city_2020_citygml_6_opのような親ツリーが出来上がり(このツリー名は、地域や年度によって異なります)、その下に、CityGMLと同名のオブジェクトとして、3D都市モデルが読み込まれます。
横須賀市の3D都市モデルは原点から遠いため、読み込んだ直後は見えませんが、Hierarchyウィンドウ上で[14201_yokosuka-shi_city_2020_citygml_6_op]をダブルクリックすると、視点が移動して見えるようになります。

36.3.2 _ スクリプトをアタッチするためのGameObjectを作る
3D都市モデルをプログラム(C#スクリプト)から操作できるようにするため、読み込まれたツリー(14201_yokosuka-shi_city_2020_citygml_6_op)の親として、空のGameObjectを作ります。
このトピックでは以降、こうして作成した空のGameObjectに、メインとなる処理をするC#スクリプトをアタッチして、強化学習などの処理を実装していきます。
【手順】親となる空のGameObjectを作る
[1]空のGameObjectを作成する
Hierarchyウィンドウで最上位のツリーの右側の[・・・]をクリックし、[GameObject]―[Create Empty]を選択して、空のGameObjectを作ります。

[2]名前を付ける
作成した空のオブジェクトに名前を付けます。ここではFieldという名前にします。

[3]読み込んだ3D都市モデルのツリーを配下に入れる
作成したGameObjectの配下に、読み込んだ3D都市モデルのツリー(14201_yokosuka-shi_city_2020_citygml_6_op)をドラッグ&ドロップして、配下に入れます。

36.4 _ シミュレーションを実施するシーンの構築
続いて、このトピックでシミュレーションを実施するシーンを構築します。
36.4.1 _ シーン構築の流れ
シミュレーションを実施するにあたり、次の手順でシーンを作ります。
① 避難所の設定
まずは、避難所を設定します。プログラムから区別できるよう、これらの避難所となる建物にタグ(Tag)を設定します。
また建物の内部に目印となるキューブを設置し、道路と接するようPlaneオブジェクト(板状のオブジェクト)を設置します。
【メモ】
タグとは、Unity上でオブジェクトを分類する仕組みです。Unity Editor上から該当のタグが付いているオブジェクトだけを選択して操作するほか、プログラム(C#スクリプト)からも、オブジェクトのタグを確認したり、特定のタグが設定されているオブジェクト一覧を取得したりする操作ができます。
② ナビゲーションメッシュの構成
3D都市モデルとして読み込んだ道路と、①の接道であるPlaneオブジェクトをナビゲーションメッシュとして構成して、避難路とします。
③ 避難者のオブジェクトをつくる
シミュレーションで配置する避難者のオブジェクトを作り、プレハブ化します。
④ 避難者の生成ポイントを構成する
避難者を道路上にランダムに配置するのではなく、あらかじめ決めた場所から生成したい場合(地域の人口密度の違いや花火大会などの局所分布を考慮したい場合)は、避難者を作成するポイントを作成します(ランダムに配置する場合は省略できます)。
⑤ その他必要なオブジェクトの構成
その他、必要なオブジェクトを構成します。このトピックでは、機械学習モデルが「避難所候補として選んでいる建物」と「選んでいない建物」を色分けして表示します。それを実現するため、色が異なる2つのメッシュを用意しておきます。
36.4.2 _ 避難所の設定
まずは、避難所を設定していきます。下記の手順を避難所の数だけ繰り返します。
今回は、次の建物IDを持つ建物を避難所候補として構成しました。参考までに、地図上の位置関係を示します。今回は建物自体が大きく人目につきやすいもの、大通り沿い、観光スポットなどを中心に選定しました。
bldg_1cb68f34-4475-406a-8c4e-0dad0f232076
bldg_9b6fcc29-9a45-45bd-9695-78ec78ad9088
bldg_53a93573-5cab-4227-a10d-00e0d1a900bd
bldg_93b527c3-3729-468b-be94-5be133113946
bldg_899ae826-8e26-4b3f-8f49-6819b9974da9
bldg_88329f89-3493-4194-9021-e84d68dade4c
bldg_c07de3cc-4614-4aff-92dd-78c1862b4a78
bldg_db5a8ba1-b4fe-4a8d-ab5f-f04957a1409e
bldg_e387b9a6-9cba-414f-b94f-53fa39525f31
bldg_ec8306fb-24ed-4d78-b434-b86ad5d29e15
bldg_f926e31f-4ae1-4f88-acdd-30eab5ac4485

【手順】避難所を設定する
[1]避難所用のタグを作る
プログラム側で、建物が避難所なのかどうかを判別するためのタグを作ります。タグ名は「Shelter」とします。
Inspector上部の[Tag]をクリックし、[Add Tag]を選択して、Shelterというタグを追加します。

[2]避難所とする建物にShelterタグを設定する
地図上で、避難所としたい建物(「bldg_オブジェクトID」のオブジェクト)をクリックして選択し、Inspectorから、手順[1]で追加したShelterタグを設定します。
【メモ】
このトピックでは触れませんが、常設の避難所を構成することもできるようにしてあります。常設の避難所を構成したいときは、その建物に対して、(Shelterタグの代わりに)ConstShelterタグを設定してください。
【メモ】
このトピックのサンプルでは、それぞれの建物オブジェクトにShelterタグを指定するほか、後述するShelterAgent.csのShelterCandidatesに、避難所のリストを設定する方法でも、避難所を設定できるようにしてあります。

[3]トリガーがかかるようにする
このあとの手順で、避難者をこの建物を目標として動かす(避難させる)のですが、この建物に到達したときにトリガー(Trigger)がかかるようにして、到達したことをプログラム(C#)で知れるようにします。
そのために、Mesh ColliderのConvexのIs Triggerにチェックを付けます。
【メモ】
トリガーとは、Unityにおいて、衝突を検知する機能です。Is Triggerにチェックを付けておくと、衝突した旨のイベントが発生するようになります。

[4]道路と建物をつなげる
PLATEAUの3D都市モデルの道路は仕様上、標高0メートルの地点に設置されているため、建物と道路とがつながっていません(LOD3以降は、道路にも高さがあるため、建物の高さと道路の高さはそろいます)。道路の標高を加味して高さを調整すれば道路と建物の高さは合いますが、仮にそのように調整したとしても、外構(敷地内の私道)はPLATEAUの道路として含まれないため、建物と道とがつながるとは限りません。こうした理由から、道路にナビゲーションメッシュを構成しても、避難所である建築物にたどり着けません。
そこで道路と建築物とが接続するように、Planeオブジェクトを配置してつなぎます。航空写真や地図を参考にしながら、建物の入口としてPlaneオブジェクトを作成します。その名称はSubTranとします。

[5]目標点とするキューブオブジェクトを配置する
手順[4]で作成した道路上、かつ、建物の内部に、キューブオブジェクトを配置し建物オブジェクトの子要素として配置します。
のちに示すプログラムでは、ナビゲーションシステムで避難者を避難させる際、このキューブの座標を目標値として使います。
【メモ】
「36.3 3D都市モデルの読み込み」の手順で読み込んだ建物オブジェクトは、すべて、X、Y、Zの座標が0に設定されています。そこで建物内にキューブオブジェクトを配置して、その座標を目標値として使います。

36.4.3 _ 道路にナビゲーションメッシュを構成する
続いて、道路ならびに配置した接道に、ナビゲーションメッシュを構成します。
【手順】道路ならびに配置した接道にナビゲーションメッシュを構成する
[1]除外範囲を設定する
このままナビゲージョンメッシュを作成すると、フィールドの地形や他の建物にも適用されてしまい、避難者が通行可能になってしまうため、事前に、これらの除外設定をします。

① 地形オブジェクト「dem_<オブジェクトID>」を除外設定に追加する
Hierarchyの検索ボックスに「dem」と入力して絞り込み、地形オブジェクトを選択します。
[Add Component]をクリックしてNavMesh Obstacleコンポーネントを追加します。追加したらチェックマークを外して、非アクティブ化します。

② 建物オブジェクト「bldg_<オブジェクトID>」を除外設定に追加する
Hierarchyの検索ボックスに「bldg」と入力して絞り込み、建物オブジェクトを選択します。[Add Component]をクリックしてNavMesh Obstacleコンポーネントを追加します。追加したらチェックマークを外して、非アクティブ化します。

[2]ナビゲーションメッシュを適用する
道路ならびに配置した接道に対してナビゲーションメッシュを適用します。
Hierarchyウィンドウから道路に相当するtranならびに接道として作成したSubTranを選択します。そして、Inspectorで[Add Component]をクリックし、[NavMesh Surface]を追加します。
NavMesh Surfaceを追加したら、[Bake]をクリックし、Bakeします。これでナビゲーションメッシュが作成されます。
【メモ】
道路はtran全体に対して、NavMesh Surfaceを適用してください。その配下にあるtran_オブジェクトIDのそれぞれに適用することもできますが、数が多いため、動作が重くなります。


36.4.4 _ 避難者のオブジェクトを作る
次に避難者のオブジェクトを作ります。
【手順】避難者のオブジェクトを作る
[1]オブジェクトをインポートする
適当な人間の形のキャラクタを避難者として使います。今回は、Asset Storeにある、下記の無料のキャラクタを使いました。
[Easy Primitive People]
https://assetstore.unity.com/packages/3d/characters/easy-primitive-people-161846
Asset Storeからインポートして、プロジェクトに取り込みます。いくつかのキャラクタがありますが、今回は、青色のキャラクタを使いました。このキャラクタは、わかりやすいようにAssetフォルダ直下にコピーし、名前をEvacueeとしておきます。
[2]必要なコンポーネントの追加
Projectウィンドウで手順[1]でコピーしたEvacueeをクリックして選択し、Inspectorで[Add Component]をクリックし、下記の3つのコンポーネントを追加します。
・Nav Mesh Agent
ナビゲーションシステムによって自動で動かすためのコンポーネント。
・Rigidbody
物理演算の適用を提供するコンポーネント。
・Capsule Collider
キャラクタの当たり判定をするためのコンポーネント。

[3]Nav Mesh Agentの設定
Inspectorで、Nav Mesh Agentの設定を変更し、移動速度ならびに回転速度を変更します。ここでは、移動速度を3.5m/s、回転速度を120度/sとしました。この速度は、緊急避難時に人が全力で走る速度(自転車でゆっくり走る程度の速度)を想定しています。

36.4.5 _ 避難者の生成ポイントを構成する
このトピックのサンプルでは、避難者の初期位置として、道路上(ナビゲーションメッシュを選択した範囲上)のランダムな場所を選ぶことも、あらかじめ指定した場所から半径何メートルかの場所を選ぶこともできます。後者において、「あらかじめ指定した場所」のことを、以下、「避難者の生成ポイント」と呼びます。
避難者の生成ポイントは、SpawnPosという名前のタグを付けたCapsuleオブジェクトとして作成します。下記の手順で、必要なだけシーン上に作成してください。今回のサンプルでは、道路上の適度な7箇所を設定しています。実際の場所については、提供しているサンプルプロジェクトを参考にしてください。

【手順】避難者の生成ポイントを構成する
[1]Capsuleを作成する
避難者を作成する場所として、道路に接するようにCapsuleを作成します。[Window]メニューの[3D Object]―[Capsule]を選択すると作成できます。作成したCapsuleオブジェクトは、SpawnPoint(2つめ以降は、SpawnPoint(1)、SpawnPoint(2)・・・)とします。

[2]識別用のタグを付ける
プログラムから、この避難者の生成ポイントを参照できるようにするため、識別用のタグを付けます。タグは、SpawnPosという名前にします。
まずは、Inspectorビューのtagプルダウンを開き、[Add Tag]をクリックして、「SpawnPos」というタグを追加します。そして、いま作成したCapsuleオブジェクトに対して、このSpawnPosタグを設定します。

36.4.6 _ その他の必要なオブジェクトの構成
その他、プログラムから使用するのに必要なオブジェクトを構成します。
以降で掲載するプログラムでは、避難所として採用した建物と、そうでない建物を視覚的に色分けします。そのために、SelectedとNonSelectedという名前のマテリアルを作成しておきます。今回は、前者は緑、後者は黒に設定しました。

36.5 _ シミュレーションのプログラムを作る
以上で舞台が出来上がったので、シミュレーションのプログラムを作っていきます。
36.5.1 _ シミュレーションを構成するプログラム
シミュレーションを構成するプログラムは、下記の7本です。すべてを詳細に解説することはできないため、詳しくはGitHubで公開されているソースコードを参照してください。
以下では、理解するのに必要な箇所のみを解説します。
アタッチ先 | プログラム名 | 概要 |
---|---|---|
なし | CityObjectTypes.cs | PLATEAUの属性情報のJSONデータをデシリアライズするための型定義 |
Assets/Evacuee | Evacuee.cs | 避難者を制御するクラス。 避難所一覧の中から、最も距離が近い避難所を目的地と設定し、ナビゲーションシステムを使って移動させる |
避難者の生成ポイント(SpawnPosタグを設定した)として構成した各オブジェクト | EvacueeSpawnPoint.cs | 避難者の生成ポイントに設定するプログラム。 場所を半透明で示したり、当たり判定(コライダ)を無効にしたりする |
避難所として設定した(Shelterタグを設定した)各オブジェクト | Shelter.cs | 避難所の収容人数を保持したり、避難所に入ってきた(=トリガーイベントによって衝突した)避難者をカウントしたりする |
新規GameObject(後述) | ShelterAgent.cs | 機械学習ライブラリのML-Agentsから実行する学習のためのプログラム |
Field (全体の親オブジェクト。「36.3.2 スクリプトをアタッチするためのGameObjectを作る」で作成したもの) | ShelterEnvManager.cs | シミュレーション全体を管理するクラス |
なし | Utils.cs | 内部で使用しているユーティリティ関数。 避難者のランダムスポーン範囲を描画するDrawWirteCircle関数と、任意のデータをCSV出力するSaveResultCSV関数がある |
36.5.2 _ 避難者の動き
避難者を動かすのは、Evacuee.csです。ProjectのAssetsにあるEvacueeに対してアタッチします。

Evacuee.csについては、下記のソースファイルを参照してください。
このプログラムでは、シミュレーションが始まったときに、ShelterもしくはConstShelterというタグが付いた建物(避難所)の一覧を取得して距離順に並べます。
private List<GameObject> SearchTowers(List<string> excludeTowerUUIDs = null) {
GameObject[] towers = GameObject.FindGameObjectsWithTag("Shelter");
GameObject[] constShelters = GameObject.FindGameObjectsWithTag("ConstShelter");
List<GameObject> Iterates = new List<GameObject>();
foreach (var shelter in towers) {
Iterates.Add(shelter);
}
foreach (var shelter in constShelters) {
Iterates.Add(shelter);
}
List<GameObject> sortedTowerPoints = new List<GameObject>();
foreach (var tower in Iterates) {
if(excludeTowerUUIDs != null && excludeTowerUUIDs.Contains(tower.GetComponent<Shelter>().uuid)) {
continue;
}
GameObject point = tower.transform.GetChild(0).gameObject;
sortedTowerPoints.Add(point);
}
// NOTE: エピソード更新時にgameObjectがnullになることがあるので、nullチェックを行う
if(this != null) {
sortedTowerPoints.Sort((a, b) => Vector3.Distance(a.transform.position, transform.position).CompareTo(Vector3.Distance(b.transform.position, transform.position)));
}
return sortedTowerPoints;
}
そして、これらのうち最も近い場所(上記のリストの先頭)を目的地として、ナビゲーションシステムで移動させます。
private List<string> excludeTowers; //1度避難したタワーのUUIDを格納するリスト
void Awake() {
NavAgent = GetComponent<NavMeshAgent>();
excludeTowers = new List<string>();
_env = GetComponentInParent<EnvManager>();
_env.Agent.OnDidActioned += () => {
// エージェントが建物を選択したことを検知して最短距離の避難所を探す
if(this != null && this.gameObject.activeSelf) {
List<GameObject> towers = SearchTowers();
if(towers.Count > 0) {
Target = towers[0]; //最短距離のタワーを目標に設定
NavAgent.SetDestination(Target.transform.position);
}
}
};
}
36.5.3 _ 避難者の生成ポイント
避難者の生成ポイント――「36.4.5 避難者の生成ポイントを構成する」でSpawnPosタグを付けたCapsuleオブジェクト――のすべてに対して、EvacueeSpawnPoint.csをアタッチします。

現在選ばれている避難者生成ポイントを可視化するためのメソッドの実装が中心ですが、設定値として、SpawnRadius、SpawnSizeという値があります。これらは、それぞれ「生成半径」「生成する人数」を設定するプロパティです。必要に応じて設定します。
public float SpawnRadius = 10f;
public int SpawnSize = 50;
36.5.4 _ 避難所における避難者の受け入れ
各避難所では、避難者を受け入れるために、Shelter.csで処理します。これは後述のShelterEnvManager.csにて、Shelterタグが設定されたすべてのオブジェクトに対して自動的にアタッチされるため、手動でアタッチする必要はありません。
リスト36-1 Shelter.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 避難所に関するスクリプト(オブジェクト1台分)
/// 現在の収容人数や、受け入れ可否等のデータを用意
/// </summary>
public class Shelter : MonoBehaviour{
public int MaxCapacity = 10; //最大収容人数
public int NowAccCount = 0; //現在の収容人数
public int currentCapacity; //現在の受け入れ可能人数:最大収容人数 - 現在の収容人数
public string uuid; //タワーの識別子
private string LogPrefix = "shelter: ";
/**Events */
public delegate void AcceptRejected(int NowAccCount) ; //収容定員が超過した時に発火する
public AcceptRejected onRejected;
private EnvManager _env;
void Start() {
_env = GetComponentInParent<EnvManager>();
_env.OnEndEpisode += (float _) => {
NowAccCount = 0;
};
}
void Update() {
currentCapacity = MaxCapacity - NowAccCount;
if (currentCapacity <= 0) {
onRejected?.Invoke(NowAccCount);
}
}
void OnTriggerEnter(Collider other) {
//Debug.Log("OnTriggerEnter Tower");
bool isEvacuee = other.CompareTag("Evacuee");
//Debug.Log("isEvacuee?" + isEvacuee);
if (isEvacuee) {
Evacuee evacuee = other.GetComponent<Evacuee>();
evacuee.Evacuation(this);
}
}
}
Shelter.csには、避難所の最大人数、現在の収容人数、現在の受け入れ可能人数を示すプロパティがあります。
最大収容人数については、クラス上の定義は次のように10となっていますが、アタッチされる際にシミュレーション環境全体を管理するShelterEnvManager.csによって、PLATEAUの属性情報の床総面積から自動で算出しています。床総面積は、PLATEAUのuro:totalFloorAreaを使用しています。
【メモ】
PLATEAUの属性情報については、「3D都市モデル標準製品仕様書」を参照してください。
public int MaxCapacity = 10; //最大収容人数
public int NowAccCount = 0; //現在の収容人数
public int currentCapacity; //現在の受け入れ可能人数:最大収容人数 - 現在の収容人数
使用している式は、多くの地方公共団体では1人当たり1.65平方メートルのスペースを確保することを基準としていることから、次のようにしています。総面積に0.8をかけているのは、通路などの非収容スペースのぶんです(このコードはShelterEnvManager.csにあります)。
収容可能人数=総面積×0.8÷1.65×スケール値
スケール値は、後述のShelterEnvManager.csに設定するパラメータ値で、シミュレーションの際に調整します。現実は何千人もの避難者が避難するのですが、PC上ではそんなにたくさんの避難者を動かすのは困難です。そこでシミュレーションでは、数十~数百人の規模で実行することにします。収容可能人数もそれに伴い、10分の1や100分の1などに調整する必要があるので、そのためのスケールです。
避難所となる建物には、Is Triggerを設定しているので、避難者が到達すると、OnTriggerEnterメソッドが呼び出されます。この処理では、到達した避難者のEvacuationメソッドを呼び出すことで、前述の避難者側のEvacuationメソッドを実行するようにしています。
void OnTriggerEnter(Collider other) {
bool isEvacuee = other.CompareTag("Evacuee");
if (isEvacuee) {
Evacuee evacuee = other.GetComponent<Evacuee>();
evacuee.Evacuation(this);
}
}
ここには示しませんが、避難者側のEvacuationメソッドでは、収容人数を超えていなければそこで避難完了、人数を超えていればその次に近い避難所を探す処理をしています。
36.5.5 _ 機械学習ライブラリのML-Agentsから実行される処理
機械学習ライブラリのML-Agentsから実行される処理は、ShelterAgent.csに記述しています。このプログラムは、新しいGameObjectとして作成します。作成場所は、「36.3.2 スクリプトをアタッチするためのGameObjectを作る」で作成したFieldsオブジェクトの子要素とします。
Shelter Management Agentは強化学習のエージェントです。以下にその設定と処理内容を記載します。

■ エージェントの観測値と行動
このトピックでは強化学習を用いて、どの避難所を選択すると、避難率が最大になるかがわかるモデルを作ります。
① 観測値
観測値として、次の値を与えます。
「それぞれの避難所候補の座標(x,y,z)ならびに現在の収容人数」「(避難済みの人を除く)現在避難中の人数」「(避難済みの人を除く)現在避難中の人の現在の座標(x,y,z)」
CollectObservationsメソッドに、その処理を実装しています。
リスト36-2 CollectObservationsメソッド
/// <summary>
/// ここでは環境内の状態をエージェントに観測させるための処理を記述します。
/// モデルへの入力として使用されます。
/// 今回は観測情報として以下の情報をモデルに入力します。
/// 1. 避難所候補地の位置情報(x, y, z)
/// 2. 避難所候補地の収容可能人数
/// 3. 環境内の避難者数
/// 4. 避難者の位置情報(x, y, z)
/// CollectObservations内で観測できるデータの種類については下記docsを参照してください。
/// https://docs.unity3d.com/Packages/com.unity.ml-agents@3.0/api/Unity.MLAgents.Agent.html#Unity_MLAgents_Agent_CollectObservations_Unity_MLAgents_Sensors_VectorSensor_
/// </summary>
/// <param name="sensor">この仮引数にあるAddObservationメソッドを通じて観測を行います</param>
public override void CollectObservations(VectorSensor sensor) {
// 避難所候補地のリストを巡回し、各避難所候補地の位置情報と収容可能人数を入力
foreach(GameObject shelter in ShelterCandidates) {
sensor.AddObservation(shelter.transform.GetChild(0).gameObject.transform.position);
sensor.AddObservation(shelter.GetComponent<Shelter>().currentCapacity);
}
// NOTE : 観測のタイミングで避難者が避難してGameObjectが消えることがあるので、ここでコピーを作成
List<GameObject> evacuees = new List<GameObject>(_env.Evacuees);
sensor.AddObservation(evacuees.Count); // 環境内の避難者数
// 避難者のリストを巡回し、各避難者の位置情報を追加
foreach(GameObject evacuee in evacuees) {
if(evacuee != null) {
sensor.AddObservation(evacuee.transform.position);
} else {
// NOTE : 観測サイズは固定でなければならないため、万一取得に失敗した場合は0ベクトルを追加
sensor.AddObservation(Vector3.zero);
}
}
}
② 行動
避難所候補地のリストのなかから、それぞれの建物を避難所とするか否か――する場合は「1」、しない場合は「0」――をエージェントの行動とします。
OnActionReceivedメソッドに、その処理を実装しています。その建物を選択した場合はShelterタグ、そうでない場合はUntaggedタグを、それぞれ設定しています。また、マテリアルもそれぞれSelectedMaterialとNonSelectMaterialに変更することで、シミュレーションの実行中に目視でわかるようにしてあります。
リスト36-3 OnActionReceivedメソッド
/// <summary>
/// エージェントの行動を実装するメソッドです。
/// 今回は避難所候補地のリストの中から、それぞれの建物を避難所とするか否かを選択します。
/// 選択する場合は1、選択しない場合は0を選択します。
/// https://docs.unity3d.com/Packages/com.unity.ml-agents@3.0/api/Unity.MLAgents.Agent.html#Unity_MLAgents_Agent_OnActionReceived_Unity_MLAgents_Actuators_ActionBuffers_
/// </summary>
/// <param name="actions">モデルの行動出力を受け取るための仮引数で、この値を元に環境に行動を反映させます</param>
public override void OnActionReceived(ActionBuffers actions) {
var Selects = actions.DiscreteActions; //エージェントの選択。環境の候補地配列と同じ順序。[<建物1の避難所選択結果 0 or 1>, <建物2の避難所選択結果 0 or 1>, ...]
List<bool> selectList = new List<bool>();
if(Selects.Length != ShelterCandidates.Length) {
Debug.LogError("Invalid action size : 避難所候補地のサイズとエージェントの選択サイズが不一致です");
return;
}
// モデルの行動出力リストを巡回し、0の場合は避難所として選択しない、1の場合は選択する
for(int i = 0; i < Selects.Length; i++) {
int select = Selects[i]; // 0:非選択、1:選択
GameObject Shelter = ShelterCandidates[i];
if(select == 1) {
_env.CurrentShelters.Add(Shelter);
Shelter.tag = "Shelter";
Shelter.GetComponent<MeshRenderer>().material = SelectedMaterial;
selectList.Add(true);
} else if(select == 0) {
_env.CurrentShelters.Remove(Shelter);
Shelter.tag = "Untagged";
Shelter.GetComponent<MeshRenderer>().material = NonSelectMaterial;
selectList.Add(false);
} else {
Debug.LogError("Invalid action");
}
}
// 行動ログを記録(episode, step, 各避難所候補の選択状況のリスト(true or false))
ActionLogs.Add(new Tuple<int, int, List<bool>>(_env.currentEpisodeId, _env.currentStep, selectList));
OnDidActioned?.Invoke();
}
■ 学習する行動の設定
こうした行動を実現するため、InspectorにてBehavior Parametersの項目にあるActionsの項目を次のように設定します。
・Continuous Actions
エージェントの行動に連続値をとる出力値(-1.0~1.0の小数値)を必要とする場合に指定するプロパティです。OnActionReceivedメソッドの処理で示したように、今回のエージェントは、該当の建物を選択するか否か(0 or 1の離散値)とし連続値を使用しないため、0を指定します。
・Discrete BranchesならびにBranch 0 Size、Branch 1 Size・・・
行動空間を示します。Discreate Branchesには、避難所の数を設定します。設定すると、その数だけ、配下にBranch 0 Size、Branch 1 Size・・・という項目が現れるので、それぞれに「2」を設定します。
今回の強化学習モデルでは、Branch 1つは1つの避難所候補地の行動空間を示します。したがって、配下にあるBranch Sizeは、その行動空間のサイズを示します(モデルが取りうる値の範囲とも言い換えられます(例えば、値:1=(0のみ)値:2=(0,1)値:3=(0,1,2))。よって、Branch <数字> Sizeには、その建物を避難所に指定するか否か(0 or 1)の2値です。

さらに、Shelter Management Agentの項目にて、エピソード終了(シミュレーション1回あたりのエージェントの行動選択回数)を指定するMax Stepsプロパティを0(無制限)に設定します。今回のプログラムでは制限時間が経過した、もしくは全員が避難したときという条件で、プログラム側でエピソードを終了しているためです(ShelterEnvManagerクラスのFixedUpdateメソッドに現れる、OnEndEpisode?.Invoke(EvacuationRate);というコードです)。

36.5.6 _ シミュレーション全体の構成
最後に、「36.3.2 スクリプトをアタッチするためのGameObjectを作る」で作成したFieldオブジェクトに対して、ShelterEnvManager.csをアタッチします。

■ 報酬の設定
ShelterEnvManagerのOnEndEpisodeHandlerメソッドにて、1回のエピソードが完了したときに報酬を与えています。報酬は避難率をベースとし、経過時間が短いほどボーナスを与えるようにしました。
リスト36-4 ShelterEnvManagerのOnEndEpisodeHandlerメソッド
private void OnEndEpisodeHandler(float evacuateRate) {
// 1. 避難率による報酬
float evacuationRateReward = GetCurrentEvacueeRate();
// 2. 経過時間によりボーナスを与える
float timeBonus = (MaxSeconds - currentTimeSec) / MaxSeconds;
// 総合報酬
float totalReward = evacuationRateReward + timeBonus;
Debug.Log("Total Reward: " + totalReward);
Agent.AddReward(totalReward);
if(IsRecordData) {
Utils.SaveResultCSV(
new string[] { "Time", "EvacuationRate" },
evaRatePerSec,
(data) => new string[] { data.Item1.ToString(), data.Item2.ToString() },
$"{recordID}/EvaRatesPerSec_Episode_{currentEpisodeId}.csv"
);
}
/**エピソード終了の発行*/
Agent.OnEndEpisode();
Agent.EndEpisode();
currentEpisodeId++;
}
■ 学習や推論の設定
ShelterEnvManagerにはさまざまな設定パラメータがあり、学習と推論の切り替え、動作モードなどを指定します。Inspectorから次のように設定します。
・Mode
学習するか(避難者を置き、繰り返し避難させて、適切な避難所はどういう関係にある場所なのかを学習していく)、それとも推論(避難者を置き、適切と思わしき避難所の場所を出力する)のか、どちらの動作をするのかを決めるパラメータです。前者の場合はTrain、後者の場合はInferenceを選択します。
・Evac Spawn Mode
避難者の作成モードを指定します。Random(ランダム)またはカスタム(Custom)です。
① Randomモード
避難者はSpawn Radiusプロパティで選択した範囲(単位はメートル)内の道路上に、ランダムに生成されます。この範囲は、エディタ上で、赤い円で表示されます。
Spawn Radius : 10
② Customモード
あらかじめSpawnPosタグを設定したCapsuleオブジェクトをシーンに配置しておき、そこに避難者を作成します。作成条件は、アタッチしたEvacuee Spawn Pointの設定値によります。設定値は、以下のとおりです。
Evacuee Prefab : 避難者のプレハブ
Spawn Radius : 10 (避難者の生成半径)
Spawn Size : 50 (避難者の生成人数)
Customモードでは、配置したCapsuleオブジェクトごとに避難者の生成条件を変えることができるため、例えば、ある場所には人数を多く別の場所には人数を少なくといったように、地域ごとに避難者の生成条件を変えることもできます。
Customモードで実行する際には、設定された生成ポイントのうち、まず1つがランダムに選択され、1回目のシミュレーション(エポック)が実行されます。それが終了すると、次の場所がまたランダムに選択され、学習が進んでいきます。
・Acc Simulate Scale
避難所の収容人数を算出するときのスケーリング係数です。前述したように、それぞれの避難所の収容人数は、PLATEAUの属性から、床総面積×0.8÷1.65で算出していますが、それを補正する倍率です。
避難者の生成数に応じて、例えば0.01などを指定します。
・Max Seconds
シミュレーションの最大時間(秒)です。例えば、1800などです。
【メモ】
Max Secondsは実時間であり、避難者が逃げ切るだけの時間が必要です。あまりに小さな値だと、避難者は、どの建物にも到達できません。
・Spawn Evacuee Size
ランダムモードにおける生成する避難者の数です。数が多いと処理が重くなるため、適宜調整してください。例えば、50などとします(カスタムモードの場合は、生成ポイントごとのSpawn Evacuee Sizeでそれぞれ設定します)。
・Agent
Shelter Management Agentを指定してください。

36.6 _ 学習の実行
以上で、ひととおりの構築が終わりました。この環境を使って、強化学習をはじめていきましょう。
36.6.1 _ 学習環境
本シミュレーションは、避難所は自由に設定できますし、避難者の出現位置、人数などもカスタムできます。以下では、次の環境で実行した結果を示します。
■ 避難所候補の位置および避難者の位置
避難所候補は、「36.4.2 避難所の設定」で設定した11箇所。出現位置はCustomモードとし、下図のように7箇所の避難者の生成ポイントを配置しました。

■ 各設定値
下記のように設定し、1つの生成ポイントからは50人が生成されるようにし、1800秒の制限時間で学習させました。
【ShelterEnvManagerの設定値】
Evac Spawn Mode : Custom
Acc SImulate Scale : 0.01 #各避難所候補地の収容人数が数十人程度となるよう調整
Max Seconds : 1800 #制限時間(秒)
【SpawnPosの設定値】
Spawn Radius : 10
Spawn Size : 50 # シミュレーション 1回あたりの避難者出現人数
【Shelter Management Agentの設定値】
Discreate Branches : 11
各 Branch Size : 2
【避難者のパラメータ設定】
NavMeshAgent
Base Offset : 1
Speed : 3.5
Obstacle Avoidance
Radius : 0.5
Height : 2
Quality : None
36.6.2 _ ハイパーパラメータの作成
学習するには、ハイパーバラメータを作成します。
プロジェクトの任意の場所に、次のようなニューラルネットワークのハイパーパラメータの値を記載する yamlファイルを作成します。例えば、Assets/Configフォルダの下に、Tutorial-1.yamlなどとして配置します。
リスト36-5 ハイパーパラメータの例(Tutorial-1.yaml)
behaviors:
ShelterSelect:
trainer_type: ppo # 使用するトレーナーの種類(ここではProximal Policy Optimization)
hyperparameters:
batch_size: 512 # 一度にトレーニングするサンプルの数
buffer_size: 409600 # 経験バッファのサイズ
learning_rate: 1e-3 # 学習率
beta: 0.01 # エントロピー正則化の強さ
epsilon: 0.3 # クリッピング範囲のパラメータ
lambd: 0.99 # GAE(Generalized Advantage Estimation)のパラメータ
num_epoch: 5 # 各バッチのトレーニングエポック数
learning_rate_schedule: linear # 学習率のスケジュール(ここでは線形減衰)
network_settings:
normalize: false # 入力データの正規化を行うかどうか
hidden_units: 512 # 各隠れ層のユニット数
num_layers: 3 # 隠れ層の数
vis_encode_type: simple # 視覚エンコーダのタイプ
reward_signals:
extrinsic:
gamma: 0.80 # 割引率
strength: 1.0 # 報酬信号の強さ
keep_checkpoints: 5 # 保存するチェックポイントの数
max_steps: 500000 # トレーニングの最大ステップ数
time_horizon: 2048 # エージェントの時間的ホライゾン
summary_freq: 5 # サマリーを記録する頻度
全パラメータについては、公式ドキュメントを参考にしてください。
適切なハイパーパラメータは環境によって異なるので、実際に学習結果をTensorBoardで見るなどして(後述)、「累積報酬が安定的に増加しているか」「エントロピーの大小、減少具合」を加味して、決めていきます。
参考までに、今回調整した主なパラメータの設定の指針について下記に示します。
・-trainer_type : ppo/sac
利用する強化学習アルゴリズムの種類です。ML-Agentsでは、PPO(Proximal Policy Optimization)またはSAC(Soft Actor-Critic)を使用できます。
パラメータをいくつか検証した結果、エントロピーの大きさはさほど変わりませんでしたが、PPOのほうは累積報酬が高く安定していたため、PPOとしました。SACの場合、累積報酬が不安定になりました。
・buffer_size
モデルの学習や更新を行う前に収集すべき経験数です。PPOの場合の一般的な値は、2048 ~ 409600です。通常buffer_sizeが大きいほど、より安定した学習につながります。
10240、12000、409600の各値で学習させた結果、409600が、最も累積報酬が高く、安定していたので、この値としました。ただし、後述のbatch_sizeの値によって、最適な値が変化する可能性があります。
・ batch_size
勾配降下の各反復における経験値数です。これは常にbuffer_sizeの数倍小さくなければなりません。離散行動でPPOの場合、一般的な値は、32~512です。
512、2024、3024の組み合わせで試行した結果、512が一番累積報酬は高く安定的であったため、これを選択しました。
・ beta
エージェントが訓練中にランダムな行動選択を行う強さです。一般的な値は、0.0001~0.01です。
実際にシミュレーションを行い、その結果で報酬を獲得するという強化学習の特性上、訓練中は、さまざまな行動選択の組み合わせを試し経験を積むことが望ましいです(そこから報酬が最大化する法則を導き出す)。具体的には、TensorBoardのグラフから、累積報酬の増加とともに、エントロピーの大きさがゆっくりと下降するようになる値が望ましいと言えます。
0.001、0.005、0.01の各値で学習させた結果、0.01が最も低いエントロピーの値を示しかつ累積報酬の安定が見られたため0.01としました。
【メモ】
今回は、各条件ともエントロピーの下降がみられなかった(原因不明)が、最終的にエントロピーの値が低かったものを採用しています。
・network_settings > normalize
観測の入力を正規化するか否かの設定です。今回のモデルにおいては、避難所の位置座標や避難者の位置座標などモデルへの入力を正規化するか否かを設定する値です。
エージェントの行動空間が連続値をとり、かつ、複雑なタスク(ロボットの歩行制御などの複雑な動き)の場合は、正規化すると学習を収束させるのに効果的です。しかし今回のように単純な離散値だけを行動空間にとる場合は、むしろ学習を発散させる傾向があるため正規化しないほうが推奨されます。
実際に、false(正規化しない)と true(正規化する)の両設定を試した結果、falseのほうは累積報酬が安定的かつエントロピーも低い値であったため、学習の収束があったと判断し、falseを採用しています。
・network_settings > hidden_units
モデルのニューラルネットワークの隠れ層のノード数です。一般に複雑なタスクにおいては値を大きくすることが求められますが、簡易なタスクにおいては比較的小さい値に設定することが望ましいです。
今回は、128、512、666の3つの値の組み合わせのうち、512が最も累積報酬が高く安定したため、この値を採用しました。
・network_settings >num_layers
モデルのニューラルネットワークの隠れ層の数です。一般に複雑なタスクではこの値は大きくする必要がありますが、簡易なタスクにおいては小さくすることが望ましいです。
今回は、2、3、4の3つの値の組み合わせのうち、2が最も累積報酬が安定したため、この値を採用しました。
36.6.3 _ 学習の実行
ハイパーパラメータを設定したyamlファイルを作ったら、次のように学習を実行します。
【手順】学習の実行
[1]mlagents-learnを実行する
Unityプロジェクトのルートディレクトリをカレントディレクトリにして、次のようにmlagents-learnを実行します。このプロジェクトでは、学習結果はプロジェクトルート配下のresults以下、--run-idで指定した名前のフォルダ名の中に保存されます。
mlagents-learn Assets/Config/Tutorial-1.yaml --run-id=ShelterAgent
すると画面は以下のようになり、待機状態になります。

[2]Unity上で実行する
Unity上で、実行ボタンをクリックして実行します。すると、mlagents-learnとUnityとが連動し、学習が始まります。


36.7 _ 学習の確認
学習が完了すると、resultsディレクトリに、学習した結果のニューラルネットワークモデルが保存されます。
次のコマンドを入力すると結果を確認するTensorBoardが起動します。
tensorboard --logdir=./results
画面には、次のように表示されるので、ブラウザでこのURLを開くと、グラフとして確認できます。
さまざまなグラフがありますが、以下では、主要なものについて解説します。
TensorFlow installation not found - running with reduced feature set.
Serving TensorBoard on localhost; to expose to the network, use a proxy or pass --bind_all
TensorBoard 2.19.0 at http://localhost:6006/ (Press CTRL+C to quit)

36.7.1 _ 累積報酬の推移を確認する
学習結果の成否を判断する上で、最も重要なのが、累積報酬での推移です。

このグラフは、エージェントが環境とやりとりする中で得た、報酬の合計を示しています。
強化学習は、エージェントが環境の状態を観測し、行動を出力、その行動の結果として得られる報酬を最大化するように学習を進めます。そのため、学習初期段階から比較して、学習が進むにつれて、累積報酬が増加していることが望ましいです。報酬が、ある一定程度の値まで増加し、収束している場合は、エージェントは、何らかの法則に基づいて、安定した報酬が得られるような行動の出力を学習できたと言えます。
今回作成したモデルの累積報酬は、学習初期段階では低い値を示しているものの、学習の進行に伴い右肩上がりになっていることが確認できます。 学習回数が20回(今回のシミュレーションでは20step目)あたりまで増加傾向を続け、その後は大きく減少することなく、横ばいとなり、シミュレーション結果が安定していることを示しています。
この結果から、今回作成したモデルは、学習回数が20回(今回のシミュレーションでは20step目)あたりまでに、観測した情報から、安定した報酬(避難率)を維持するための何らかの法則をつかみ、その結果に基づいて、避難所の選択を行ったと考えられます。
36.8 _ 学習済みモデルを使って適切な避難所を提案させる
こうして学習したモデルを使用して、避難率が最も高くなると思われる適切な避難所を選ばせるには、次のようにします。
【手順】学習済みモデルを使用して適切な避難所を選ばせる
[1]学習済みモデルをコピーする
学習済みモデルをAssetsフォルダにコピーします。今回のサンプルでは、プロジェクトルートの下のresultsフォルダのなか、--run-idで指定したフォルダに、そのモデルがあります。
[2]学習済みモデルを適用する
シーンのShelter Select AgentのInspectorを開き、Modelから、手順[1]でコピーしたモデルを選択します。

[3]シミュレーションを開始する
Unity上で、実行ボタンをクリックして実行します。すると、mlagents-learnとUnityとが連動し、実行が始まります。

[4]結果が表示される
避難所として選択された建物が、色づいて表示されます。いくつの建物が提案されるのかは決まっていません。

強化学習モデルによって選択された建物が適切かどうか、モデルがどの程度の性能を持っているかなどは、モデルの途中過程をCSV形式ファイルにして状況を確認したり、ランダムに選んだ建物の結果を比較したりすることなどで評価できます。
その詳細については、筆者の考察「チュートリアル① モデル分析編」を参照してください。
コラム:学習済みモデルの転用について
モデルへの観測(入力)サイズと出力サイズが同じ都市モデルであれば、学習済みモデルを転用できます。すなわち、 ShelterManagementAgentクラスのCollectObservationsメソッドの実装内容が同じであること、インスペクタ上のBehavior Parametersの値が同じ(避難所候補地の個数)であることを満たすなら、ある都市の学習結果を別の都市に適用して、その都市で適した避難所候補を出力するのに使えます。
ただし、実際に避難シミュレーションを行なってその避難率を学習基準としている特性上、モデルの過剰適合や過学習が要因で、精度が低迷する恐れがあります。この問題は、実装を改良し、学習段階において、1つの都市でのみ学習させるのではなく、他の都市でも学習させるようにすれば、改善できる可能性があります。
36.9 _ まとめ
このトピックでは、PLATEAUの3D都市モデルを使った都市シミュレーションの一例を紹介しました。
Unityには、ナビゲーションシステムがあるため、避難者が自律的に動くようなシミュレーション環境を構築するのは容易です。ML-Agentsを使うことで、その動きを学習していくことができます。
今回はその実例として、避難者が、ある場所に登場して、そこから最寄りの避難所に行き着くだけの簡易な強化学習モデルを示しましたが、より複雑な状況――例えば、車両や信号などを配置して現実に近い交通状況を再現したりすることで、より実践的な避難シミュレーションの構築――を目指すこともでき、そうすれば、現実世界での結果の再現性を高められます。
また、モデルのチューニングや、PLATEAUで利用可能な他の属性情報と組み合わせた分析(例えば緊急輸送道路など)を行うことで、より効果的な避難計画の策定に役立つ可能性があります。
さまざまな都市シミュレーションに、PLATEAUの3D都市モデルを活用していくことができるでしょう。
【文】
高林秀(立教大学大学院 人工知能科学研究科 三宅研究室)
大澤文孝
【監修】
三宅 陽一郎(立教大学大学院 人工知能科学研究科 特任教授)