[Project Bonsai/Moab]Bonsai育成記 (2)ドメインランダム化

# はじめに
ここではMicrosoftの[Project Bonsai](https://azure.microsoft.com/ja-jp/services/project-bonsai/)および[Project Moab](https://microsoft.github.io/moab/)のチュートリアルを紹介します。
[前回のチュートリアル1](https://qiita.com/Meeracker/items/8d39dc7907bef2650f98)の続きとなります。
BonsaiとMoabについて少し前置きします。飛ばしてチュートリアル見たい人は [こちら](#チュートリアル2) 。

## Project Bonsaiについて
Bonsaiはもともと強化学習のスタートアップ会社で、2018年6月に[Microsoftが買収](https://blogs.microsoft.com/blog/2018/06/20/microsoft-to-acquire-bonsai-in-move-to-build-brains-for-autonomous-systems/)しました。Project BonsaiのプラットフォームはすべてこのBonsai社の技術がもとになっています。強化学習はGoogleのDeepMindのアルゴリズムよりも45倍速い[CNRL(Concept Network Reinforcement Learning)](https://deepai.org/publication/deep-reinforcement-learning-for-dexterous-manipulation-with-concept-networks)というフレームワークをBonsai社が開発して、プラットフォームに組み込まれています。
AIや深層学習の知識がなくとも簡易に産業機械に対して目的に沿った動きを学習させることができるのが特徴です。

## Moabとシミュレーターについて
Moabはあまり詳しい情報がないのですが、[Fresh Consulting](https://www.freshconsulting.com/portfolio/moab/)がMicrosoftと共同でUI/UXを開発したそうです。Moab以外にもシミュレーターとして利用できるモデルは多くあります。2021年1月時点でサポートしている3Dソフトウェアは下記の通りです。

| シミュレーター| ソフトウェア |
|:-----------|:------------|
| Ansys | Digital Twin |
| AnyLogic | AnyLogic 7,8 |
| KBC | Petro-Sim |
| MathWorks | Simulink |
| MuJoCo | MuJoCo |
| OpenAI Gym | Python |
| Siemens | Amesim |
| Wood PLC | VPLink |


参考：https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE4MxrJ

# 前提
- Microsoftのアカウント取得済み　→未取得の場合は[こちら](https://account.microsoft.com/)
- Azureのサブスクリプションを導入済み　→未取得の場合は[こちら](https://azure.microsoft.com/ja-jp/free/)
- Bonsaiの事前準備とチュートリアル 1 は[こちら](https://qiita.com/Meeracker/items/8d39dc7907bef2650f98)
- Bonsaiは記事執筆時点でPreview版です。

# チュートリアル2
チュートリアル2のテーマは「ドメインランダム化」です。
簡単に言うと、もう少し現実に近い条件でやってみましょう、ということです。チュートリアル1でのパラメータ設定などを見直して、よりリアルな動きをするボールにもシミュレーターが対応できるようにします。

仮想空間でのシミュレーションは速く効率的な学習ができる一方で、あらゆる現実の状況に対処できるわけではないです。シミュレーションでは上手くいくけど実際デプロイしたらうまくいかない（sim-to-real gap）という課題を解決するためにランダムに変動するパラメータを入れるアプローチがあります。これが「ドメインランダム化」です。

## やってみる
まずは新しいBrainを作成します。左上のメニューから新しく作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94824/934ffa87-5795-7768-4049-56d226cc5bda.png)

名前は「Moab Tutorial 2」とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94824/2fd3807f-c4b7-7f20-18dd-3e29873977fb.png)

これでチュートリアル１と同じBrainができました。では、２で何をやるかを整理します。
現時点のコードでは「ボールの位置・速度などの初期条件」と「ボールの規格誤差」が考慮されていない点に着目します。
順番に見ていきながらコードの修正を行います。

## 初期条件の変更
ボールの初期条件は scenario に定義されています。
エピソードという反復単位の開始時にランダムに決定されるパラメータを持っています。

```ruby
    scenario {
        initial_x: number<-RadiusOfPlate * 0.5 .. RadiusOfPlate * 0.5>,
        initial_y: number<-RadiusOfPlate * 0.5 .. RadiusOfPlate * 0.5>,

        initial_vel_x: number<-MaxInitialVelocity * 0.02 .. MaxInitialVelocity * 0.02>,
        initial_vel_y: number<-MaxInitialVelocity * 0.02 .. MaxInitialVelocity * 0.02>,

        initial_pitch: number<-0.2 .. 0.2>,
        initial_roll: number<-0.2 .. 0.2>,
    }
```
速度（initial_vel_x,y）を見ると最大 0.02 m/s です。つまりリアルな機械で始めるとき、そっと置くような動きでないとシミュレートと同じ結果が出ない、ということになります。加えて、初期位置は RadiusOfPlate * 0.5 つまり皿の半径の半分より中央に近い位置に限定されています。

パラメータの範囲を拡張してみます。
初期位置をプレート半径の 0.6 倍まで、初期速度をMaxVelocityの40%(最大 0.4 m/s)まで大きくします。

```ruby
    scenario {
        initial_x: number<-RadiusOfPlate * 0.6 .. RadiusOfPlate * 0.6>,
        initial_y: number<-RadiusOfPlate * 0.6 .. RadiusOfPlate * 0.6>,

        initial_vel_x: number<-MaxInitialVelocity * 0.4 .. MaxInitialVelocity * 0.4>,
        initial_vel_y: number<-MaxInitialVelocity * 0.4 .. MaxInitialVelocity * 0.4>,

        initial_pitch: number<-0.2 .. 0.2>,
        initial_roll: number<-0.2 .. 0.2>,
    }
```

なお、ピッチ、ロールの範囲を増やすことも可能ですが、Moabが即座にピッチを変更するので、ドメインランダム化においてはあまり重要ではないようです。


## ボールの規格誤差のドメインランダム化
ピンポン玉のサイズの定義を見てみます。

```ruby
# Ping-Pong ball constants
const PingPongRadius = 0.020 # ボール半径(ピンポンボールの場合は0.02m)
const PingPongShell = 0.0002 # ボールのシェル厚さ(ピンポンボールの場合は0.0002)
```
ピンポン玉は半径20mm、厚さが0.2mmですが、材料を加工して作られるモノである都合上どうしても製造上の誤差が考えられます。使われるのがプロの卓球試合で使われるボールにしろ、100均で数個セットで売ってるボールにしろ、厳密にこれら規格通りの値である保証はありません。例えば厚みが大きいほど加速が速くなるため、シミュレーションとの誤差も大きくなります。

したがって、これらを初期条件のドメインランダム化と同様にある程度のランダム性を持たせてシミュレートすることで現実のボールにも対応できるようにしたいです。

Brainのシミュレータ設定 SimConfig にはこれらの定義がないですが、Moabのシミュレータには設定が存在します。

```ruby
    # Ball radius (m)
    ball_radius: number,
    # Ball shell thickness (m)
    ball_shell: number,
```

Brain側のSimConfigに含まれていない設定はシミュレータによってデフォルト値が自動で割り当てられます。今回はランダムな初期値を適用したいので、SimConfigにパラメータを追加し、scenarioに初期値設定を行います。

```ruby
type SimConfig {
    # ボールの初期位置 X, Y
    initial_x: number<-RadiusOfPlate .. RadiusOfPlate>, # in (m)
    initial_y: number<-RadiusOfPlate .. RadiusOfPlate>,

    # ボールの初速度 X, Y
    initial_vel_x: number<-MaxInitialVelocity .. MaxInitialVelocity>, # in (m/s)
    initial_vel_y: number<-MaxInitialVelocity .. MaxInitialVelocity>,

    # ハードウェア上で定義されたプレートの初期pitch/roll角度
    initial_pitch: number<-1 .. 1>,
    initial_roll: number<-1 .. 1>,

    # ピンポンボール半径とシェル厚さ
    ball_radius: number, # ボール半径 (m)
    ball_shell: number, # ボールのシェル厚さ (m), shell>0, shell<=radius
}
```

初期条件は定数で定義した半径と厚さを80%~120%の範囲でドメインランダム化します。

```ruby
    scenario {
        initial_x: number<-RadiusOfPlate * 0.6 .. RadiusOfPlate * 0.6>,
        initial_y: number<-RadiusOfPlate * 0.6 .. RadiusOfPlate * 0.6>,

        initial_vel_x: number<-MaxInitialVelocity * 0.4 .. MaxInitialVelocity * 0.4>,
        initial_vel_y: number<-MaxInitialVelocity * 0.4 .. MaxInitialVelocity * 0.4>,

        initial_pitch: number<-0.2 .. 0.2>,
        initial_roll: number<-0.2 .. 0.2>,

        ball_radius: number<PingPongRadius * 0.8 .. PingPongRadius * 1.2>,
        ball_shell: number<PingPongShell * 0.8 .. PingPongShell * 1.2>,
    }
```

コードの修正はこれで完了です！
さて、訓練もひと工夫してみましょう。

## Brainの複製
一般的に最終的に良いAIモデルを作成するには複数の実験が必要です。
Brainはバージョニングができます。パラメータを変更した異なるバージョンのBrainでの訓練も記録することができます。
左上のメニューから「Copy this version」を選択すると新しいバージョンが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94824/c775f59a-c0da-7f27-c0cc-a740e3910e85.png)

バージョン個別で目的や概要を記録したい場合は「Version notes」から記載することができます。

## シミュレーションインスタンスの変更
インスタンスの数やスペックを変更することが可能です。もちろん、高スペックなインスタンスや並列数を増やすとAzure上の料金は高くなるのでご注意ください。
Simulatorsの「Moab」をクリックするとInfoタブでインスタンスの上限下限数、CPUコア数とメモリサイズを指定できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94824/30062bcf-44cb-7f10-7d83-f5ce2bc86d81.png)


## 今回の結果
27分で全試行は30万回以上、精度は70%ほどでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94824/52771fef-0abe-8eb3-2dcf-31e6f6a85897.png)
0％と100％で極端に分布が分かれているので、どうしても拾えないような初期条件があるのかもしれません。まだ改善の余地はありそうです。

## 初期状態変更のイメージ動画
<iframe width="951" height="535" src="https://www.youtube.com/embed/apgVxW8-S7M" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# まとめ
チュートリアル２ではパラメータ調整や特徴量の追加など実施し、AIのデータ分析っぽくなってきました。コードの修正方法がわかることで遊び方、もといできることが増えたのはうれしいですね。


# おまけ：ProjectBonsai関連Youtubeまとめ
[Innovation Space: Autonomous systems](https://www.youtube.com/watch?v=3hSAFtWcui8)
Microsoft社がProject Bonsaiを発表した紹介動画

[How to build Intelligent control systems using new tools from Microsoft and simulations by Mathworks](https://www.youtube.com/watch?v=6mfUz4PDgOw)
Microsoft ResearchとMathworks社によるSimulinkをProjectBonsaiと連携して構築するチュートリアル動画

[Project Bonsai End-to-End Workflow](https://www.youtube.com/watch?v=-iTSZaG3lg8)
AnyLogic社の製品とProjectBonsaiへの連携・展開ワークフローの紹介

[Bonsai](https://www.youtube.com/channel/UCmAzclDAW1n9hHvoUyrWPcg/featured)
Project Bonsaiの前身となるBonsai社のチャンネル