強化学習による自動運転AIを、ML-AgentsのRayPerceptionSensor3Dで作成

![test5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/834797/c0dae65e-00d2-4fb8-4ec7-0331bbb3f3b3.gif)


ブログからの転記のため、以下の元記事の方が見やすいかも知れません。
**[強化学習による自動運転AIを、ML-AgentsのRayPerceptionSensor3Dで作成 | Genki Tech](https://genkitech.net/unity-ml-agents-raycast-car)**


以前、箱を追いかけるボールのAIを作りました。

[Unity + ML-Agentsで流行りの強化学習(ディープラーニング)を始める【2020年最新】1.6.0-preview
](https://genkitech.net/unity-ml-agents-deep-learning-start)

その時は座標の絶対値を渡していましたが、今回はもうちょっと現実世界寄りにするため視覚情報を活用して、ある程度操作が難しい車の自動運転に挑戦していきます。

この通りにやったら同じものが出来る、と言うチュートリアル記事ではないためご了承ください。



### 課題

1. タイヤの回転と角度で曲がる正しい物理挙動の車をコントロールする。（UnityのチュートリアルのKarting Microgameのようではなく）
2. 加速出来るところでは加速し、壁にぶつかる事が無く、無駄な動きをしない。
3. コースが変わっても再学習なく走れる。

今回は、車同士は接触しない事としています。



## Unity世界の準備

### コースの準備

コースはUnityのチュートリアルにあるKarting Microgameから拝借し、縮尺だけ調整しました。

自分で作ってもいいですが、だだっ広い平面があるとそこでグルグル回り続けるように学習してしまいそうな気がします。

### 車体の準備

タイヤ部分が車体と分離されている適当なアセットを探すか、自分で作ります。

車体の重心を、Rigid.centerOfMassをコードから変更して少し下げ前方に設定。

### 車体に動力を追加

車体のルートオブジェクト部分に適当なCollider（Mesh Collider等）を追加し、そのオブジェクトに追加した子オブジェクトに、「Wheel Collider」を追加して半径を調整し、オブジェクトの位置を調整します。

複製して4つ分のタイヤを配置します。（画像は見やすくタイヤ判定のオブジェクトをずらしています）

![image-20201203092609988.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/834797/1d165ff4-7d19-1e31-2b07-cb5ee88595de.png)

#### タイヤメッシュの回転同期

タイヤメッシュは「Wheel Collider」の状態に合わせてコードから姿勢制御するため、本体のCollider部分の外に配置しておきます。（メッシュがオブジェクトの中心に配置されている必要があります）

コードからの姿勢制御は以下の様に行いました。（面倒なので、ブレーキパッドなども一緒に回転する仕様です。別にしたい場合はブレーキなどのパーツ部分のオブジェクトを分け、同じように姿勢制御した後、 localEulerAngles.x を 0にすればいいと思います）

```
Quaternion q;
Vector3 p;
wheelCollider.GetWorldPose(out p, out q);
wheelMesh.transform.position = p;
wheelMesh.transform.rotation = q;
```



### 車体にセンサー「Ray Perception Sensor 3D」を追加

今回は絶対座標情報は与えず視覚情報に頼ってもらうため、視線を複数飛ばしたヒット判定（距離情報）を、ブレインに入力するセンサーとして使用します。

視線を飛ばすには、「Ray Perception Sensor 3D」を使用します。

![image-20201203093627604.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/834797/38297d97-baa3-7bfa-9a89-1d686638bbed.png)

#### 配置と設定

Agentのコンポーネント「Behavior Parameters」の「Use Child Sensors」をONにしておくと、自分や子のコンポーネントにある「Ray Perception Sensor 3D」の値をセンサー値として、自動でブレインに渡してくれます。方向を変えて複数設置できるため、前後2つの解像度の違うRay Sensorを設置しました。 （前方の解像度が低いと蛇行運転気味になります）

この際「Behavior Parameters」の「Vector Observation」のサイズは変更する必要がなく、別で与えたい情報の数だけ設定し、コードからAddObservationします。（今回は車体のZ軸速度、Y軸回転速度、ハンドル角度の3つだけAddObservationしたため、単に「3」と指定しています）

「Stacked Vector」や「Stacked Raycasts」は 3程度に設定しています。

#### センサーの水平を保つ

また、車オブジェクトに直接取り付けると加速やブレーキで前後に傾いたときに水平を保てないので、外部にAgent用オブジェクトを配置し、コードでAgentの位置とY軸角度を車に合わせています。

### 操作による挙動を実装

AgentにHeuristicを実装し、左右でハンドリングし、前後で前進後退するようにコーディングします。

この時出来るだけ操作性をよくすることで、学習結果のスコアも上がると思います。

今回は現実に似せるため、ハンドリング操作は瞬時に目的の角度にするのではなく、スムーズに移行するようにしたり、後退時のトルクは前進時より落としたり、スピードが上がるとトルクがあまりでなくなるように調整しました。

## 報酬設定

以下の様に報酬を設定しています。

* 速度報酬

  早ければ早いほど高い報酬を与えます。割合：中

* 衝突罰

  激しくぶつかるほど高い罰を与えます。割合：大

* ハンドリング罰

  ハンドルを中心から外側に動かすときに少量の罰を与えます。割合：小

### 序盤の対策

ハンドルとアクセルワークで曲がれることを理解するのに少し時間がかかるため、序盤で壁で詰まり続けて学習が進まなくなります。それを避けるため、一定時間でエピソードを終了して初期位置に戻します。

### 不要な操作の対策

ハンドリング罰は最初与えていなかったところ、細かくハンドルを震わせながら微妙に蛇行運転していてあまり速度が出ていなく、ハンドリングのせいで速度が落ちていることも理解できていないようだったので微弱な罰を与えたところ、ハンドル切るのをさぼり始め、さぼると速度が上がることに気が付き始めたようで、結果スコアが一気に上がりました。

### コースの記憶への対策

単一コースで学習した後にコースを変えたところ、以前のコースで曲がっていた場所で曲がって壁に突っ込んでいく現象がみられたため、以下のように色々な形や道幅のコースの色々な場所にAgentを配置して学習させました。応用力を付けるためにバリエーションは大事な様です。（画像ではサムネを取るためにAgentを並べています）


![image-20201203103652825.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/834797/aabce4dc-904b-a0cb-0e5d-9e709d1a351a.png)



## 学習の進行

学習の設定は以下の様にシンプルなものとしました。レイヤー数を3に増やしています。

```
behaviors:
  CarAgent:
    max_steps: 10000000
    network_settings:
      num_layers: 3
      hidden_units: 512
```

様子を見ていると、報酬の割合の大きい順に習得していくようです。

1. 絶対に壁にぶつからないよう頑張り始め、壁付近で前後するようになる。
2. ハンドルワークを理解し始め、壁で詰まらずゆっくり進めるようになる。
3. ある程度速度が出はじめ壁にぶつかって、減速タイミングを掴み始める。
4. ハンドリングを省略し始め、さらに速度を上げていく。

期待している結果を出すために色々調整したものの、調整したのは報酬くらいで、最終的には以下の様にとても満足のいく結果を得ることが出来ました。かなりギリギリを攻めてもぶつからない自信があるようです。


![test5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/834797/292a5dc9-ffb5-c6b3-1ad2-34afb0506bbc.gif)