俺のブログ

学習メモや日記をつらつらと書きます。

MLエンドポイントのためのライブラリを雑に比較する

お仕事でリアルタイムのMLエンドポイントを立てる事がなく、知見があまりない。
単純に、各フレームワークでの実行速度が気になったから雑に比較してみた。

実装したコードはこちらのGitHubリポジトリ

雑なまとめ

Pythonを使う環境ならFastAPIを使えば問題ない。欲を言えば、onnxにモデルを変換しておくのが理想。
モデルをonnxに変換できる かつ Rustを利用できる環境 なら、Rustでエンドポイントを立てるとより高速な推論が期待できる。

比較する条件

フレームワーク

※ RustのコードはClaudeCodeに書いてもらいました

モデル

  • PyTorch(2層のNN)
  • LightGBM
  • PyTorch ONNX(2層のNN)
  • LightGBM ONNX

タスク

Titanicの生存予測(2値分類)

学習コードは notebooks/ にある。

特徴量

  • Pclass(Int)
  • Sex(String)
  • Age(Int)
  • SibSp(Int)
  • Parch(Int)
  • Fare(Float)

前処理

  • Sex → Label Encoding
  • Age → Fill Null(median)
  • Age → Standard Scaler
  • Fare → Standard Scaler

結果

フレームワークで実装したエンドポイントに1000回のリクエストを送信して、計測した結果を集計した。
すべて、同じマシン上で実行・リクエストを行っている。

Framework 平均推論時間 (ms)
FastAPI 1.75 ms
Flask 2.0 ms
ort (Rust) 0.47 ms
tract (Rust) 0.46 ms

フレームワークごとの結果

Framework モデル Avg Min Max Total Variance
FastAPI PyTorch 1.761 1.591 7.440 1760.629 0.043
LightGBM 1.758 1.548 3.142 1758.414 0.017
PyTorch (onnx) 1.542 1.400 2.534 1541.626 0.009
LightGBM (onnx) 1.757 1.562 2.716 1757.161 0.010
Flask PyTorch 2.013 1.836 6.441 2013.075 0.037
LightGBM 2.170 2.005 3.072 2169.753 0.009
PyTorch (onnx) 1.774 1.612 2.348 1773.535 0.008
LightGBM (onnx) 1.990 1.798 3.943 1989.903 0.023
ort (Rust) PyTorch (onnx) 0.426 0.402 0.804 425.580 0.001
LightGBM (onnx) 0.599 0.533 0.917 598.631 0.000
tract (Rust) PyTorch (onnx) 0.428 0.403 0.693 427.609 0.000
LightGBM (onnx) 0.510 0.488 0.764 510.188 0.000

モデルごとの結果

モデル Framework Avg Min Max Total Variance
PyTorch FastAPI 1.761 1.591 7.440 1760.629 0.043
Flask 2.013 1.836 6.441 2013.075 0.037
LightGBM FastAPI 1.758 1.548 3.142 1758.414 0.017
Flask 2.170 2.005 3.072 2169.753 0.009
PyTorch (onnx) FastAPI 1.542 1.400 2.534 1541.626 0.009
Flask 1.774 1.612 2.348 1773.535 0.008
ort (Rust) 0.426 0.402 0.804 425.580 0.001
tract (Rust) 0.428 0.403 0.693 427.609 0.000
LightGBM (onnx) FastAPI 1.757 1.562 2.716 1757.161 0.010
Flask 1.990 1.798 3.943 1989.903 0.023
ort (Rust) 0.599 0.533 0.917 598.631 0.000
tract (Rust) 0.510 0.488 0.764 510.188 0.000

まとめ

Pythonでエンドポイントを立てるならFastAPIを選べば間違いない、特に学習したモデルをonnxに変換できるとベスト。

モデルをonnxに変換できる かつ Rustを利用できる環境 なら、Rustでエンドポイントを立てるのを検討しても良いと思う。

GitHub Actions 条件式の罠

GitHub Actionsの条件式でハマった話

こんにちは。今回は、GitHub ActionsでCI/CDを実装する際に、特定のブランチにマージしたときにだけ実行したいコマンドがあり、その条件式の設定で少しハマったので共有します。

発生した問題

GitHub Actionsの条件式にブランチ名を設定しようとしたところ、意図しない動作となってしまいました。 具体的には、以下のようにpush先のブランチがmainブランチの時に真になるようにと条件式を記述したのですが、この書き方ではどのブランチ名に対しても真になってしまいました。

if: ${{ github.ref_name }} == "main"

github.ref_name などのGitHubコンテキストについてはこちらを参照してください。

docs.github.com

解決方法

すべてのブランチ名が真になってしまう理由は、次の公式ドキュメントに記載の通りでした。

式構文 ${{ }} を使用すると、内容が文字列に変わり、文字列は真値です。 たとえば、if: true && ${{ false }} は true に評価されます。 GitHub Actions のワークフロー構文 - GitHub Docs

正しくは、以下のように ${{ }} を外して記述する必要があるようです。

if: github.ref_name == "main"

この記述方法であれば、意図通りmainブランチにマージされたときだけ、条件式が真となり、コマンドが実行されます。

実験

この仕様を踏まえて少し実験をしてみました。 次の、3種類の条件式でPush先のブランチ名がmainブランチかどうかの条件式を記述しました。

  1. if ${{ github.ref_name }} == 'main'
    すべてのブランチに対して真になる
  2. if github.ref_name == 'main'
    mainブランチにpushした時にのみ真になる
  3. contains(github.ref_name, 'main')
    mainという文字列を含むブランチにpushした時に真になる

実験で使用したコード

name: Test CI

on:
  push:
    branches:
      - main
      - release

jobs:
  build-pipeline:
    runs-on: ubuntu-latest
    environment: ${{ github.ref_name }}
    steps:
      - uses: actions/checkout@v4
      - name: always run
        run:
          echo ${{ github.ref_name }} 
      - name: condition with $ and {}
        if: ${{ github.ref_name }} == 'main' # Pushされたブランチがmainの時は実行する
        run: |
          echo "Done"
      - name: condition without $ and {}
        if: github.ref_name == 'main' # Pushされたブランチがmainの時は実行する
        run: |
          echo "Done"  
      - name: condition with contains
        if: contains(github.ref_name, 'main') # Pushされたブランチがmainの時は実行する
        run: |
          echo "Done"

実験コード github.com

実験結果

上記のコードを利用して、mainブランチからreleaseブランチにマージを行いました。実験で使用したGitHub Actions( Merge pull request #4 from takeru1205/main · takeru1205/ci-test@e6fc87f · GitHub)

その結果、つぎの画像のように1つ目の ${{}}を利用した条件式だけは条件式が真となってしまいコマンドが実行されてしまいました。

まとめ

GitHub Actionsの条件式でブランチ名を指定する際は、${{ }} を外して記述しましょう。 もしデプロイするコマンドが入っていたら想定外の本番デプロイが発生するかもしれません。

俺の考えた最強のリモートPC開発環境

超簡単まとめ

  • Tailscale + mosh
  • カフェなど出先から簡単に自宅PCにアクセスできる

やりたいこと

  • 家の外から家のリモートPCにssh接続
  • IP固定しない
  • 無料

実現する際に抱えていた問題

  • MacBookPro(Late2016)じゃまともに開発(実行)できない
  • 普段の開発環境をそのまま使いたい
  • マンションのネットワークで簡単に外部ネットワークからsshできない
  • 外部からのsshが遅い
  • ネットワーク何もわからん

Tailscaleで簡単グローバルssh

Tailscaleは無料で使えるVPNサービスです。 会員登録もGoogleアカウントで済むため、お手軽に使えます。 しかも様々なOSに対応しており、MacOSLinuxはもちろんWindowsiOSAndroidにも対応しています。

ローカルマシン、リモートマシンの双方にTailscaleをインストールすると、Tailscale上でネットワークに繋がっているマシンを確認することができます。

Tailscaleブラウザ画面

Tailscaleを使ってリモートマシンにssh接続する場合は、マシンの名前とIPを指定することでリモートPCに接続することが可能です。 $ ssh remotePC@XXX.XXX.XXX.XXX

moshで快適リモート開発

Tailscaleを使って外からリモートPCにssh接続してみると、ネットワーク環境にもよりますが基本的にはもっさりしているなと感じます。 と言うのも、sshではリモートPCからのレスポンスを待って入力が反映されるため、待ち時間が発生してしまい、それがもっさりしているなと感じる要因です。 数分の作業であればもっさりしていても良いのですが、求めているのは快適な環境です。

そこで、moshを使います。 moshは、キータイプした際にもっさり感がないように振る舞ってくれるので、キータイプ時のフラストレーションがかなり軽減されます。 また、カフェやモバイル回線のような不安定ネットワークで使われることを考えて開発されているため、接続が切れても自動で再接続を行ってくれるなどの便利な機能がいくつかあります。 詳細な解説はコチラを参照してください。 なお、moshはもちろんtmuxと併用することができます。

moshもTailscale同様に様々なOSに対応しているため、適宜自分の環境に合わせてインストールしてみてください。

moshとTailscaleを使ってssh接続する場合は次のようになります。

$ mosh remotePC@XXX.XXX.XXX.XXX

また、自分はPythonをJupyter Notebookで使うことがあるため、リモートPC上でJupyter Notebookに使用しているポートをローカルPCに転送したい時がります。 このような場合でもmoshは、sshを代替する事を目標に開発されているため、sshで使えるオプションを使用することができます。

具体的には、

$ mosh --ssh="ssh -L 8888:localhost:8888" remotePC@XXX.XXX.XXX.XXX

とすることで、リモートPC上の8888ポートで建っているJupyter Notebookをブラウザ上でXXX.XXX.XXX.XXX:8888にアクセスすることで、ローカルPCから操作することが可能になります。

羽田圭介作 スクラップ・アンド・ビルド を読んで

著者である羽田圭介さんが、自分の好きなYouTuberの動画に何度か出演した事で興味を持っていた、「スクラップ・アンド・ビルド」という作品を読んで、思ったことを書きたくなったから書いてみようと思う。無知が故の阿呆な事も書いていると思うが容赦願いたい。

「スクラップ・アンド・ビルド」と言えば芥川賞受賞作品であるが、自分にとっては純文学作品を読むのは初めての経験だった(国語の授業とかで知らない間に読んでるかもしれない)。自分が普段読む物は技術書が主で、物語は以前森見登美彦作品を読み漁っていた程度である。そのような自分の文学に対する姿勢の低さから、純文学は難しくて自分には何が書いてあるのか理解できない、といった先入観があった。 しかし実際に読んでみて、想像していた難しさとは異なる難しさがあるなと思った。 一番自分が読んでいて難しいと感じた事は、場面の転換である。今まで自分が読んできた作品は、場面転換があれば「その晩」や「次の日」など、冒頭部分で明示的に場面が切り替わった事を書いている事がほとんどだった。しかし「スクラップ・アンド・ビルド」では、ある程度読み進んでから場面が切り替わっていた事に気づく事が何度もあった。そのため、何度も同じ部分を読み返す事になり、結果的に脳内で情景がイメージしやすくなったと感じた。ただ、このような書き方が純文学特有の物なのか、羽田圭介さんの書き方なのかは分からない。この点から羽田圭介さんの他の作品を読む必要性がでてきた。また、他の純文学作品、特に芥川賞受賞作品も読む必要性を感じた。どツボにハマったのかもしれない。 また、これも他の作品と比べてみたい点ではあるが、自分が気になったものに、結果をほとんど書いていないという点がある。例えば恋愛を書いた物語では、おそらく恋愛が成就するが、最後のシーンは書かないといった事がよくあるように思う。これは読む人がそれぞれ自由に解釈できるという点でとても良いと思うが、それにしてもこの作品では書かなさすぎである。結局どうなったのか気になる事が多すぎて眠むに眠れないほどである。だが、これは純文学としてよくある事だろうなというイメージがある。なぜかはわからないが、そんなイメージがあるのだから仕方ない。他の純文学を読むのがさらに楽しみになってきた。

ここで、タイトルである「スクラップ・アンド・ビルド」という言葉について思った事を書く。まず「スクラップ」は破壊といった意味を持つ言葉だというイメージがある。次に「ビルド」は作るというようなイメージがある。これらから、作品全体を通して「何かを壊し、作る」というテーマを持った話なのだろうと思った。結果的に、このような先入観を持って作品を読み進めたのは正解だったと思う。というのも主題はおそらく、主人公の健斗が祖父を尊厳死させるべく、今までの生活を変化(破壊)するが、一方で祖父が生活を楽しみ生きようとする(作る)、といった物だと思う。しかし、この「スクラップ・アンド・ビルド」というテーマは、これだけで終わらないのである。作品の中で健斗を中心に色々な所で「スクラップ・アンド・ビルド」が繰り広げられていた。作品内にいくつかの「スクラップ・アンド・ビルド」がある事に気づけたのは、上で書いた先入観を持っていたためであり、より楽しむ事ができたと思っている。

主人公の健斗についても思った事を書きたいと思う。健斗は、フリーターであり就職活動をしている。自分はこの健斗にとても共感できた。というのも自分自身も就職活動中であり、就職活動がうまくいかない主人公に自分の姿を重ねることができてしまったためである。また、性格の面においても少し自分と重なってしまう点があった。作中で、健斗は祖父を尊厳死さすべく、生きたいという気持ちを無くすために、祖父の「自分はもう死んだらいい」と言った言葉に対して慰めを言わない。しかし、誰にもそのような説明はしていないため、主人公の姉が「そんなことはないよ」と慰める。すると健斗は、なぜそんな生きたい気持ちが湧くような言葉を言うのかと、苛立ちを感じてしまう。健斗自身の考えを共有していないのにも関わらず、自分と同様の行動を期待するのは間違っている。他にも、健斗の彼女に対する言動などからも健斗の自己中心的な性格が読み取れ、自分が今までの人生を振り返ると自覚する自己中心的な行動や発言に重なってしまった。このように、主人公に自分を重ねてしまう場面があった事から、物語に没入して読めたのかもしれない。

初めて純文学を読んだ感想は、思っていた程とっつきにくい物ではなかったなというものである。むしろ一冊読んでしまったがために、羽田圭介さんの他の作品や、他の芥川賞受賞作品を読んでしまいたくなった。 「スクラップ・アンド・ビルド」という作品自体に対する感想は、結末がはっきりしないなというものである。これは決してネガティブな感想ではなく、結末がはっきりしないからこそ、自分で色々な結末を想像する事ができてしまうというポジティブな感想である。一方で、作者の思い描く一つの結末が気になるのも確かである。

最後に、感想をこうして書いたことについて書きたい。小中学校と、夏休みの宿題で読書感想文が一番できなかった自分がなぜこんな感想を書こうと思ったのか不思議でならない。昔は1行すら書けなかった自分が2000字以上も感想を書いている現実が受け入れ難い。これが成長なのかもしれない。もしくは、「スクラップ・アンド・ビルド」を読む事で、過去の自分を壊し新たな自分を作るスクラップアンドビルドをしようとしているのかもしれない。

ちなみに「スクラップアンドビルド」という和製英語があるらしい。

PythonでDict(ハッシュテーブル)を実装する

データ構造やアルゴリズムを勉強していて,ハッシュテーブルの仕組みを軽く学んで実装してみたので備忘録に残しておきます.

Dict(ハッシュテーブル)とは

ハッシュテーブルについてはWikipediaを引用しておきます.

ハッシュテーブル (英: hash table) は、キーと値の組(エントリと呼ぶ)を複数個格納し、キーに対応する値をすばやく参照するためのデータ構造。ハッシュ表ともいう。ハッシュテーブルは連想配列や集合の効率的な実装のうち1つである。(Wikipedia/ハッシュテーブル)

ハッシュテーブルは,キーに対応する値を素早く参照するために,キーをハッシュ化します. しかし,キーをハッシュ値にする際,異なるキーでも同じハッシュ値を生成してしまう,衝突(collision)が発生することがあります. この時衝突を回避する方法として以下のの2種類があります.

  • チェイン法
  • オープンアドレス法

今回はチェイン法を使ってこの衝突を回避しています. 具体的には,同じハッシュ値のデータが挿入された場合には,連結リスト(Linked List)を末尾に連結します.

簡単な実装

チェイン法を使って衝突を回避するために,シンプルな連結リストを用意しておきます.

class LinkedList:
    def __init__(self, value):
        self.value = value
        self.next = None

シンプルなハッシュ関数と,データの追加,削除,探索メソッドを実装します.

class MyDict:
    def __init__(self):
        self.table = [None] * 1000

    def hash(self, key):
        # 1000で割った余りをhash値として扱う
        return key % 1000

    def add(self, key, value):
        hashed_key = self.hash(key)
        if self.table[hashed_key]:  # すでにキーにデータが存在したら
            ll = self.table[hashed_key]  # すでに存在しているデータ(連結リストの先頭)
            while ll:  # 連結リストの最後尾までループして,最後にデータを追加する
                if not ll.next:  # 連結リストの最後の場合
                    ll.next = LinkedList(value)  # 新しい値を連結リストに連結
                    break
                else:
                    ll = ll.next
        else:
            self.table[hashed_key] = LinkedList(value)  # データが既に存在していない場合,連結リストとしてデータを挿入

    def get(self, key):
        values = []
        hashed_key = self.hash(key)
        ll = self.table[hashed_key]
        if not ll:  # 指定したキーにデータが存在しない場合は-1を返す
            return -1
        while ll:  # 連結リストが存在する場合
            values.append(ll.value)  # 連結リストの値をリストに追加
            if not ll.next:  # もし連結リストの最後尾だったら
                return values
            else:
                ll = ll.next

    def remove(self, key, value):
        hashed_key = self.hash(key)
        ll = self.table[hashed_key]
        if not ll:  # 指定したキーにデータが存在しなかったら
            print('No Data')
            return
        if ll.value == value:  # 連結リストの先頭を削除する場合
            if ll.next:  # 先頭を削除して,連結リストを一つ前にずらす
                self.table[hashed_key] = ll.next
            else:  # データが一つだけの場合
                self.table[hashed_key] = None
            print(f'Key:{key}, Value:{value} Removed')
            return
        ll_prev = ll
        ll = ll_prev.next
        while ll:  # 指定したキーに複数連結リストが存在する場合
            if ll.value == value:
                ll_prev.next = ll.next
                print(f'Key:{key}, Value:{value} Removed')
                return
            else:
                ll_prev = ll
                ll = ll.next
        print('Data not Found')

実行スクリプトと実行結果を示しておきます

データの追加

dictionary = MyDict()
dictionary.add(1, 'orange')
dictionary.add(2, 'apple')
dictionary.add(3, 'grape')
dictionary.add(1001, 'mikan')
dictionary.add(1003, 'muscat')
dictionary.add(2002, 'green apple')
dictionary.add(3002, 'pineapple')
dictionary.add(2004, 'melon')

# Dict内のデータの表示
for i in range(1, 5):
    print(f'Key: {i}, Value: {dictionary.get(i)}')

出力結果

>>> Key: 1, Value: ['orange', 'mikan']
>>> Key: 2, Value: ['apple', 'green apple', 'pineapple']
>>> Key: 3, Value: ['grape', 'muscat']
>>> Key: 4, Value: ['melon']

データの削除

dictionary.remove(1, 'orange')
dictionary.remove(2002, 'green apple')
dictionary.remove(3, 'muscat')

# データの表示
for i in range(1, 5):
    print(f'Key: {i}, Value: {dictionary.get(i)}')

出力結果

>>> Key:1, Value:orange Removed
>>> Key:2002, Value:green apple Removed
>>> Key:1003, Value:muscat Removed

>>> Key: 1, Value: ['mikan']
>>> Key: 2, Value: ['apple', 'pineapple']
>>> Key: 3, Value: ['grape']
>>> Key: 4, Value: ['melon']

Sim2Real転移のためのDomain Randomization(翻訳)

この記事は,Lilian Weng氏によるDomain Randomization for Sim2Real Transferの記事を日本語訳したものです.

元記事 : Domain Randomization for Sim2Real Transfer: 2019年5月5日 by Lilian Weng

訳 : たける

シミュレーター上で学習したモデルやポリシーを実ロボット上で動作させる場合,確実にsim2realのギャップに直面してしまいます.Domain Randomizationは,学習する環境をランダム化することで,このギャップを埋めるというシンプルながらも協力なアイデアです.

ロボット分野における最も難しい課題の一つに,どのようにして実世界にモデルを転移させるかという問題があります.深層強化学習アルゴリズムのサンプル非効率性と,実ロボットでのデータ収集が高コストな事から,理論的には無限にデータを収集できるシミュレーターでのモデル学習が必要となることがよくあります.ですが,シミュレーターと物理世界のリアリティギャップは,物理ロボットとの作業では失敗につながることが多いです.このギャップは,物理パラメータ(摩擦,kp , 減衰,質量,密度など)の違い,さらには不正確な物理モデリング(柔らかい表面感の衝突など)によって引き起こされます.

sim2realのギャップを近づけるためには,シミュレーターを改善し現実に近づける必要があります.いくつかアプローチがあります:

  • System Identification(システム同定)

    • システム同定とは,物理システムの数学的モデルを構築することです.強化学習のコンテクストでは,数学的モデルとはシミュレーターのことです.シミュレーターをより現実的な物にするためには,注意深いキャリブレーションが必要です.
    • 残念ながら,キャリブレーションには多くの費用がかかります.さらに,同じマシンでも温度や湿度,位置,時間の経過による摩耗で,多くの物理パラメータが大きく異なる可能性があります.
  • Domain Adaption(ドメイン適応)

    • Domain Adaption(DA)とは,タスクモデルによって強制されたマッピング正則化によって,シミュレーションのデータ分布を実際のものと一致するように更新するために開発された転移学習手法の一つです.
    • 多くのドメイン適応モデルは,特に画像分類やEnd-to-Endの画像ベースの強化学習タスク用に,敵対的損失(Adversarial Loss)やGANで構築されています.
  • Domain Randomization(ドメインランダム化)
    • Domain Randomization(DR)を使用すると,ランダム化されたプロパティを使用して様々なシミュレーション環境を作成することができ,それら全てで機能するようにモデルを学習します.
    • このモデルは実世界の環境に適用できる可能性が高いですが,実際のシステムは訓練バリエーションの豊富な分布の中での1つのサンプルになると思われます.

Domain AdaptionとDomain Randomizationはどちらも教師なしです.Domain Adaptionは,実際の分布を捉えるために大量の実データサンプルを必要としますが,Domain Randomizationは実データをほとんど必要としない,もしくは全く必要としません.本記事では,このDomain Randomizationに焦点を当てています.

f:id:tkr1205:20201013130646p:plain
図1. sim2real転移における,3つのアプローチを表したコンセプト図

Domain Randomizationとは何か

定義をより一般的にするために,フルにアクセスすることが可能な環境(つまりシミュレーター)をソースドメイン,モデルを転移させる環境(つまり物理的な世界)をターゲットドメインと呼びます.学習はソースドメインで行います.ランダム化空間(\xi \in \Xi \subset \mathbb{R}^N)からサンプリングされた構成(\xi)を使用して,ソースドメイン(e_\xi)N個のランダム化パラメータのセットを制御することが可能です.

方策の学習の過程で,エピソードはランダム化が適応されたソースドメインから収集されます.したがって,方策は様々な環境にさらされ,一般化することを学びます.方策のパラメータ(\theta)は,分布全体で期待報酬(R)の平均を最大化するように学習されます.

f:id:tkr1205:20201010165829p:plain

ここで,(\tau_\xi)は,(\xi)でランダム化されたソースドメインから収集された軌道です.ある意味で,「ソースドメインとターゲットドメイン間の不一致は,ソースドメインの変動性としてモデル化されます」(Peng et al. 2018)

一様Domain Randomization

Domain Randomizationのオリジナルの形式(Tobin et al. 2017;Sadeghi et al. 2016)では,各ランダム化パラメータ(\xi_i)は,\xi_i \in [\xi_i^\text{low}, \xi_i^\text{high}, i=1,...,N]によって制限されます.そして各パラメータは,範囲内で均一にサンプリングされます.

ランダム化パラメータは,例えば以下のようなシーンの外観を制御することが可能です(Fig. 2参照).シミュレーション及びランダム化された画像から学習したモデルは,実際のランダム化されていない画像に転移させることが可能です.

  • 座標や形状,オブジェクトの色
  • 素材の質感
  • 照明の条件
  • 画像にランダムなノイズの付与
  • シミュレーター内のカメラの位置や向き,視野
f:id:tkr1205:20201010172247p:plain
図2. ランダム化された環境でキャプチャされた画像.(引用元: Tobin et al, 2017(https://arxiv.org/abs/1703.06907))

シミュレーターにおける物理ダイナミクスもまたランダム化(Peng et al. 2018)することが可能です.研究によると,再帰な方策は部分観測可能な現実を含む様々な物理ダイナミクスに適応可能であることが示されている.例として,以下のような物が挙げられます

  • オブジェクトの質量と寸法
  • ロボットの質量と寸法
  • 減衰,kp,関節の摩擦
  • PID制御のゲイン
  • 関節の制限
  • 行動における遅延
  • 観測ノイズ

OpenAI Roboticsでは,ビジュアル及びダイナミクスのDomain Randomizationを使用して,実ロボットのDexterouis Robot Handで方策を学習することができています(OpenAI, 2018).この研究では,ロボットハンドに50回連続でランダムなターゲットの向きにオブジェクトを回転させることを目標としています.このタスクにおけるsim2realのギャップは,(a)ロボットとオブジェクト間の多くの同時接触と(b)オブジェクトの衝突やその他の動きの不完全なシミュレーションにより,非常に大きくなっている.最初,方策はオブジェクトを5秒以上落とさずにいられませんでした.しかし,Domain Randomizationのおかげで,方策は最終的に驚くほどうまく機能するように進化しました.


Learning Dexterity: Uncut

なぜDomain Randomizationはうまくいくのか?

次に,なぜDomain Randomizationはそんなにうまくいくのか尋ねるでしょう.アイデア自体は非常に単純に思えます.以下の2つは私が最も説得力があると思った説明です.

最適化としてのDomain Randomization

あるアイデア(Vuong, et al. 2019)では,Domain Randomizationにおけるランダム化パラメータの学習が,双レベルの最適化であるとみなしています.実際の環境[tex:e{real}]にアクセスでき,ランダム化構成が[tex:\phi, \xi \sim P\phi(\xi)]でパラメータ化された分布からランダム化構成がサンプルされると仮定し,方策[tex:\pi\theta]が学習されている[tex:e{real}]で最大のパフォーマンスを達成するように以下の様に学習します.

f:id:tkr1205:20201013131852p:plain

ここで,\mathcal{L}(\pi; e)は環境eで評価される方策\piの損失関数です.

一様なDomain Randomizationでは,人の手でランダム化の範囲を選択しますが,多くの場合ドメインの知識と転移性能に基づいた何度かの試行錯誤を伴う調整が必要とされます.基本的に,これは最適な[tex:\mathcal{L}(\pi{\theta^*(\phi)}; e\text{real})]のために\phiをチューニングする手動の最適化プロセスです.

次セクションでのガイド付きDomaion Randomizationは,主にこの視点に触発されており,双レベルの最適化を行い,最適なパラメータ分布を自動的に学習することを目的としています.

メタ学習としてのDomain Randomization

OpenAIのプロジェクト(OpenAI, 2018)では,様々なダイナミクスで一般化するためにLSTM方策を学習しました.ロボットが最初の回転をできるようになると,次の回転ができるようになる必要な時間が遥かに短くなることがわかりました.またオンポリシーな方策は,物理ロボットに転移できないことがわかりました.どちらも方策が動的に学習し,新しい環境に適応しているという証拠です.

ある意味で.Domain Randomizationは様々な異なるタスクのコレクションを構成します.リカレントネットワークのメモリは,タスク間のメタ学習を実現し,実世界の設定でさらに作業ができるようにします.

ガイド付きDomain Randomization

普通のDomain Randomziationは,現実のデータへのアクセスを想定していないため,ランダム化の設定はシミュレーションの中で可能な限り広く一様にサンプリングされ.実際の環境がこの広い分布でカバーされることを期待しています.一様なサンプリングを,タスクパフォーマンスや実際のデータ,シミュレーターからのガイダンスに置き換えるように,より洗練された戦略を考えるのが妥当です.

ガイド付きDomain Randomization(Guided Domain Randomization)のモチベーションの一つに.非現実的な環境でのモデルの学習を避けることで計算リソースを節約するというものがあります.もう一つは,過度に広いランダム化分布から生じる可能性があり,方策の学習の成功を妨げる可能性がある実行不可能な方法を回避することです.

タスクパフォーマンスのための最適化

様々なランダム化パラメータ[tex:\xi \sim P \phi (\xi)] を使用して,一連の方策を学習するとします,ここで[tex:P\xi]は\phiによってパラメータ化された\xiの分布です.後で,フィードバックを収集するために,ターゲットドメインのダウンストリームタスクでそれら全てを試す事にしました(つまり,実際にロボットを制御するか,バリデーションセットで評価します).このフィードバックは,\xiの設定がどれくらい良いものかを示し,\phiを最適化するための信号を提供します.

NASに触発されたAutoAugument(Cubuk, et al. 2018)は,画像分類のための最良のデータオーグメンテーション(せん断,回転,反転など)の選択を強化学習の問題として学習する仕組みです.AutoAugmentは,sim2real転移には提案されていませんが,タスクのパフォーマンスによって導かれるDomain Randomizationのバケットに含まれることに注意してください.個々のオーグメンテーション設定は,バリデーションセットで評価され,パフォーマンスの向上はPPOを学習するための報酬として使用されます.この方策は,データセットごとに異なるオーグメンテーション戦略を出力します.例えばCIFAR-10の場合,AutoAugumentは主にカラーベースの変換を選択しますが,ImageNetはジオメトリベースを優先して選択します.

Ruiz(2019)は,タスクフィードバックを強化学習の報酬と見なして,\xiを調整するための「learning to simulate(シミュレーションの学習)」という名前の強化学習ベースの手法を提案しました.多変量ガウス分布としてモデル化された方策を,メインタスクのバリデーションデータのパフォーマンス距離を報酬として,\xiを推定するように学習されています.

f:id:tkr1205:20201013131934p:plain
図3. "learning to simulate"のアプローチを示した概要図. (引用元: Ruiz(2019), https://arxiv.org/abs/1810.02513)

進化アルゴリズムは別の方法であり.フィードバックを進化を促すための適応度として扱います(Yu et al, 2019).この研究では,CMA-ES(共分散行列適応進化戦略)を用いましたが,ターゲット環境での\xi条件付き方策の評価を適応度としています.付録で,研究者らはCMA-ESと\xiダイナミクスをモデル化するようなベイズ最適化やニューラルネットワークなどの他の方法と比較しています.これらのメソッドは,CMA-ESほど安定しておらず,サンプル効率が低いという主張がされています.興味深いことに,P(\xi)ニューラルネットワークでモデル化すると,LSTMがフィードフォワードネットワークよりも著しく優れています.

sim2realギャップは,外観のギャップとコンテンツのギャップの組み合わせであると考える人もいます.つまり,GANに影響を受けたほとんどのDomain Adaptionモデルは,外観のギャップに焦点を合わせています.Meta-Sim(Kar, et al. 2019)は,タスク固有の合成データセットを生成することにより,コンテンツのギャップを埋めることを目的としています.Meta-Simは,自動運転車の学習を例としているため非常に複雑な環境となっています.この場合,合成シーンはプロパティ(場所や色)を持つオブジェクトの階層,及びオブジェクト間の関係によってパラメータ化されます.階層とは,Structure Domain Randomization(SDR; Prakash, et al. 2018)に近い確率的シーン文法によって指定され,事前にわかっている前提です.モデルGはシーンプロパティsの分布を拡張するように次の方法で学習します,

  1. まず,事前学習を行います.恒等関数G(s)=sを学習します.
  2. 実際のデータ分布とシミュレーションデータ分布間のMMD損失を最小化します.これには微分不可能なレンダラーを通じたバックプロパゲーションが含まれます.この論文は,G(s)の属性を摂動させることで,数値的に計算しています.
  3. 合成データで学習しましたが,実際のデータで評価した場合,REINFORCEタスクの損失を最小化します.繰り返しになりますが,これはAutoAugumentと非常によく似ています.

不幸にも,このようなメソッドはsim2realには適していません.強化学習や進化アルゴリズムには,多数の実サンプルが必要です.また,物理ロボットのデータ収集をリアルタイムで学習ループに含めるのは非常に高いコストがかかります.計算リソースが少なくなるかどうかは,タスクによって異なります.

実データ分布との一致

実際のデータをガイド付きDomain Randomizationに使用することは,システム同定やDomain Adaptionを行うのとよく似ています.Domain Adaptionの中心となるアイデアは,実際のデータ分布に一致するようにデータ分布を修正することです.実データガイド付きDomain Randomizationの場合,シミュレータの状態分布を実世界の状態分布に近づけるランダム化パラメータ\xiを学習したいというものです.

SimOptモデル(Chebotar et al. 2019)は,まず初期ランダム化分布[tex:P\phi(\xi)]の下で学習を行い,方策[tex:\pi{\theta, P\theta}]を得ます.そして,この方策はシミュレーターと物理ロボットの両方に適用され,それぞれ軌道[tex:\tau\xi]と\tau_\text{real}を収集します.最適化目的関数は,以下のシミュレーションと実際の軌道間の誤差を最小化することです.

f:id:tkr1205:20201013132241p:plain

ここで,D(.)は,軌道ベースの不一致を測るものです.「Learning to simulate」の論文同様に,SimOptも微分不可能シミュレーターを通じて,勾配伝播の問題を解決する必要があります.これには,相対エントロピーポリシー検索(relative entropy policy search)と呼ばれる手法を用いました,詳細は論文を参照してください.

f:id:tkr1205:20201013132352p:plain
図4. SimOptフレームワークの概要図. (引用元: Chebotar et al, 2019(https://arxiv.org/abs/1810.05687))

"Randomized-to-Canonical Adaption Networks"を略であるRCAN(James et al. 2019)は,エンドツーエンドな強化学習タスクに対して有効なDomain AdaptionとDomain Randomizationを組み合わせた物です.Image-conditional GAN(cGAN)は,ドメインランダム化された画像をランダム化されていない画像に変換するようにシミュレーターで学習されます.その後,同じモデルを使用して実際の画像を,対応するシミュレートされた画像に変換し,エージェントが学習したものと同じようにします.基本的な仮説は,ドメインランダム化されたシミュレーション画像の分布が実際のサンプルをカバーするのに十分広いということです.

f:id:tkr1205:20201013132904p:plain
図5. RCANは,ドメインランダム化された画像や実画像を対応するシミュレーション画像に変換できる,条件付き画像生成器です(引用元: James et al., 2019(https://arxiv.org/abs/1812.07252))

モデルは,ビジョンベースのロボットアームで把持を行うために,シミュレーターでエンドツーエンドに学習します,ランダム化は,トレイの仕切りやオブジェクトの掴む位置,ランダムなテクスチャ,照明の位置,方向,色などが各タイムステップで適用されます.生成される正規バージョンの見た目は,シミュレーターのデフォルトの見た目です.RCANは生成器を学習しようとするということです.

G ランダム化された画像 → {正規バージョンの画像,セグメンテーション,深度}

ここで,セグメンテーションマスクと背インド画像は補助タスクとして使用されます.RCANは,一様なDomain Randomzationと比較して,ゼロショット転移に優れていましたが.どちらも実画像のみで学習されたモデルよりも性能が低いことが示されています.コンセプト的には,RCANはGraspGANと逆で,Domain Adaptionによって合成画像を実画像に変換します.

シミュレーターでのガイド付きデータ

DeceptionNetとして知られる,ネットワークドリブンなDomain Randomization(Zakharov et al. 2019)は,画像分類タスクでのドメイン間のギャップを埋めるのに役立つランダム化を学習することで動機づけされています.

ランダム化は,エンコーダ-デコーダアーキテクチャを持つDeceptionモジュールを通じて適用されます.Deceptionモジュールは,画像を変換(背景の変更,歪みの追加,照明の変更など)するように特別な設計がされています.他の認識ネットワーク(Recognition Net)は,変換された画像に対して分類を実行することでメインのタスクを処理します.

学習の工程には2段階あります,

  1. 認識ネットワークを固定した状態で,誤差逆伝播中に逆勾配を適用して,予測とラベルの差を最大化します.これにより,Deceptionモジュールが最も混乱するトリックを学習できます.
  2. Deceptionモジュールを固定した状態で,入力画像を変更して認識ネットワークを学習します.

f:id:tkr1205:20201013135025p:plain
図6. どのようにしてDeceptionNetが働くか.(引用元: Zakharov et al., 2019(https://arxiv.org/abs/1904.02750))

Deceptionモジュールを学習するためのフィードバックは,ダウンストリーム分類器から提供されます.しかし,上記のセクションのようにタスクのパフォーマンスを最大化しようとするのではなく,ランダム化モジュールがより難しいケースを作り出すことを目的としています.大きな欠点の一つに,データセットやタスクごとに異なるDeceptionモジュールを手動で設計する必要があるため,簡単にスケーラブルにできない点です.ゼロショットであるという事を考えると,結果はSOTAのDomain AdaptionメソッドによるMNIST, LineMODよりも悪いです.

同様に,Active Domain Randomization(ADR;Mehta et al., 2019)も.より難しいトレーニングサンプルを作成するためにシミュレーションデータに依存しています.ADRは,指定されたランダム化範囲内で最も情報量の多い環境のバリエーションを探索します.ここで,情報量はランダム化された環境インスタンスと参照(元の,非ランダム化な)環境インスタンスにおけるロールアウトの不一致で測ります.少しSimOptに似てる気がしますか?まぁ,SimOptはシミュレーションと実際のロールアウトの不一致を測るのに対し,ADRはランダム化と非ランダム化シミュレーションの不一致を測り.高コストな実データ収集を避けいることに注意してください.

f:id:tkr1205:20201013135221p:plain
図7. どのようにしてActive Domain Randomization(ADR)が働くか(引用元: Mehta et al., 2019(https://arxiv.org/abs/1904.04762))

学習は,以下の流れで行われます

  1. 与えられた方策を参照環境とランダム化された環境の両方で実行し,それぞれ2セットの軌道を収集します.
  2. ロールアウトした軌道が,参照環境でのものか,ランダム化環境のものかどうかを識別するモデル(discriminator)を学習します.予測された\log p(ランダム化される確率)が報酬として使用されます.ランダム化環境のロールアウトと参照環境のロールアウトが異なるほど.予測が容易になり,報酬が高くなります.
    • 直感的には,環境が簡単なら,同じエージェントが参照環境と同様の軌道を生成できます.そして,モデルは様々な行動を奨めることで,困難な環境でも報酬を与え探索します.
  3. 分類器による報酬は,Stein Variational Policy Gradient(SVPG)に伝えられ,多様なランダム化の設定を出力します.

ADRのアイデアは,2つの小さな懸念点があります.確率的な方策を実行する際に,環境変数の難易度を測定するのに軌道間の類似度を使用するのは,あまり良い方法ではないかもしれません.sim2realの結果は,残念ながらそれほどエキサイティングではないように見えますが,論文ではADRがランダム化パラメータの範囲を狭めていることが勝因であると述べられています.

参考

[1] Josh Tobin, et al. “Domain randomization for transferring deep neural networks from simulation to the real world.” IROS, 2017.

[2] Fereshteh Sadeghi and Sergey Levine. “CAD2RL: Real single-image flight without a single real image.” arXiv:1611.04201 (2016).

[3] Xue Bin Peng, et al. “Sim-to-real transfer of robotic control with dynamics randomization.” ICRA, 2018.

[4] Nataniel Ruiz, et al. “Learning to Simulate.” ICLR 2019

[5] OpenAI. “Learning Dexterous In-Hand Manipulation.” arXiv:1808.00177 (2018).

[6] OpenAI Blog. “Learning dexterity” July 30, 2018.

[7] Quan Vuong, et al. “How to pick the domain randomization parameters for sim-to-real transfer of reinforcement learning policies?.” arXiv:1903.11774 (2019).

[8] Ekin D. Cubuk, et al. “AutoAugment: Learning augmentation policies from data.” arXiv:1805.09501 (2018).

[9] Wenhao Yu et al. “Policy Transfer with Strategy Optimization.” ICLR 2019

[10] Yevgen Chebotar et al. “Closing the Sim-to-Real Loop: Adapting Simulation Randomization with Real World Experience.” Arxiv: 1810.05687 (2019).

[11] Stephen James et al. “Sim-to-real via sim-to-sim: Data-efficient robotic grasping via randomized-to-canonical adaptation networks” CVPR 2019.

[12] Bhairav Mehta et al. “Active Domain Randomization” arXiv:1904.04762

[13] Sergey Zakharov,et al. “DeceptionNet: Network-Driven Domain Randomization.” arXiv:1904.02750 (2019).

[14] Amlan Kar, et al. “Meta-Sim: Learning to Generate Synthetic Datasets.” arXiv:1904.11621 (2019).

[15] Aayush Prakash, et al. “Structured Domain Randomization: Bridging the Reality Gap by Context-Aware Synthetic Data.” arXiv:1810.10093 (2018).

スレートプレートを買った自慢がしたい

スレートプレートを買った

f:id:tkr1205:20200405004033j:plain

最近は外出せずにYouTubeでお料理の動画ばかり見ている. 色々なお料理の動画を見ている中で,私はスレートプレートに出会ってしまった.

なぜ私がそれに惹かれたのかは分からない. ただ,スレートプレートを使った料理はどれもオシャレに見えた.

すぐにスレートプレートを調べた.ダイソーやセリアなどの百円ショップに売っているらしい. 今は不要不急の外出の自粛が求められているが,これは不要不急ではない. 早急になんとしてでも手に入れなければならない.ASAPである. 手に入れた暁には,世間への不満にあふれるTwitterのTLにオシャレな料理で一石を投じようではないか. 食欲が刺激されることで少しは世間に対する不満が和らぐに違いない. マズローの5段階欲求説によるなら生理的欲求を満たそうとする段階に陥るはずであり,世間への不満は和らぎ殺伐としたTLには美味しいご飯で溢れるはずである.

善は急げである,外出自粛を理由に一日中来ている部屋着を脱ぎ捨て私はすぐに家を出た. 目指すはお財布の味方ダイソーである. スレートプレートを買ったらどんな料理を作ろうか期待を胸に食器コーナーへと向かった.

だが現実はそうもいかなかった. 考えることは皆同じなのだろうか,私と同じ様に皆お料理の動画を見ているのだろうか,すでに国民の多くは生理的欲求の段階に陥ってしまっているのだろうか.

だが私は諦めない. 地球温暖化はなんのその,化石燃料を燃やしCO2を排出しながらセリアに向かった.

現実は想像を絶するものだった. セリアにもスレートプレートは置いていなかったのだ.

Google大先生は百円均一にも様々な種類のスレートプレートが売っていると教えてくれた. だが,実際はそうではなかった. 仮にも情報系大学で4年間学んだ者が,インターネットの情報を一切疑わず,信じてしまったのだ. 私は一体何を学んでいたのだろうか,メディア・リテラシーとはなんなのか. その謎を解明すべく南米の奥地へと向かいたいところではあるが,私にはスレートプレートを早急に手に入れるという使命がある.

私は諦めなかった. インターネットをもう一度信頼した. その結果,なんとニトリにスレートプレートがあるとGoogle大先生が言うではないか.

すぐにニトリへとハンドルを切った. 一度インターネットに裏切られた身である. 半信半疑の面持ちのままエスカレーターを上がり食器コーナーへと向かった.

嗚呼,なんということだろうか,そこには黒く平らな板が輝いているではないか. これこそ私の求めていたスレートプレートである.

価格は100円とは行かないが500円程度である.しかもかなりのサイズである. 私は迷うこと無く手に取り颯爽とレジヘと向かった.

この時私の脳内ではオシャレな料理の写真をツイートする妄想を繰り広げていた. 私は同時に肉が食べたいと強く思った.同時にこれだけプレートが大きいならば肉も大きくないと格好がつかないとも思った.

私はニトリを後にし,いつも利用しているスーパーへと向かった. 通いなれた私を遮るものなど何もなく,まっすぐ精肉コーナーにたどり着いた. その時,私はある物を見てしまった. 1ポンドステーキに貼ってある20%オフのステッカーを.

私はすぐにステーキ肉を手に取り,料理の付け合せを考え始めた. すぐに脳内を総動員させ,人参とコーンというベストな答えを導き出した.

ありきたりかもしれないが,彩りを考えたときに人参は映えると考えた. 料理の盛り付けには彩りも重要である事ももちろんYouTubeで履修済みである.

すぐに帰宅し,調理へと取り掛かった. 調理はこちらを参考にしていただきたい.

そして料理は完成した.

YouTubeで見ていた,あのオシャレな料理が目の前にある.

是非これを読んだ人は作ってみてほしい. 味の満足感よりもオシャレな料理を作れたことに満足する.不思議な体験が得られることだろう.

f:id:tkr1205:20200405004023j:plain