夜は短し歩けよ未来大生

未来大生が人工知能を中心に勉強していく上で、学習メモや日記として書いていきます。

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

Solving Rubik's Cube with a Robot Hand を読んだ

はじめに

この記事は強化学習Advent Calendar20198日目の記事です.

たくさん空いているのでどなたでも気軽に参加して貰えると嬉しいです.

自己紹介

北海道の雄大な自然の中にある大学に通う学部4年のたけるです. 卒業研究では,深層強化学習を使ったロボットの制御をしています. 来年からは同大学の大学院修士課程に進学します.

Advent Calendar を企画した理由は,深層強化学習を使った研究をしている人が周りに少ないため,議論できる相手がおらず論文を正しく読めているのか不安に感じたのでoutputすることでツッコミを頂こうと考えたからです.(質問などもいただけると頑張って答えます)

また,英弱につき自信のない部分がいくつかあるので,読み間違い等ありましたらツッコんでいただけると嬉しいです.加えて,こういった説明記事を書くのが初めてなので優しく指摘していただけるとありがたいです.

概要

論文のURL : [1910.07113] Solving Rubik's Cube with a Robot Hand

OpenAIのブログ記事 : Solving Rubik’s Cube with a Robot Hand

この論文はOpenAIによる,ロボットハンドを用いてルービックキューブを解く(回転させる)ことを学習した論文です.分野としてはsim2realと呼ばれる,シミュレーション上で学習し,実世界に学習したものを転移させるものです.この論文ではADRというアルゴリズムをシミュレーション上での学習時に適用します.これにより,ゼロショット(実世界での学習無し)での実世界への転移を先行研究よりも高速に,高い精度で達成した.また,あくまでルービックキューブを回転させる事を学習し,解き方はソルバー(アルゴリズム)を用いている.

学習した結果の動画もYouTubeに上がっているので,合わせて確認するとイメージがしやすいです.

www.youtube.com

あると幸せになる知識

基本的に以前の研究の進化系なので,この論文に軽く目を透しておくと幸せになります.

扱う分野として,sim2realと呼ばれる,シミュレーション上で学習したモデルを実環境に転移させるものに取り組みます.ここで,大きな問題となるのがシミュレーションは実世界のダイナミクスを完全に再現できているわけでない点です.実世界の物理現象に何が関与しているのかはとても難しい問題なため,シミュレーション上で得られるものと実世界で得られるものでは誤差があります.そこで,シミュレーション上でサンプリングを行う際にノイズを与えることで実世界に転移させた際にも上手く動作するだけの汎化性能を得るDomain Randomizationという手法があります.このとき,与えるノイズが大きければ大きいほど高い汎化性能が得られるが,状態空間が膨大になり学習が困難になるという問題が起きます.一方でノイズを小さくすると学習は簡単でにはなるが,十分な汎化性能が得られないという問題があります.

アブストラク

全訳を載せておきます.

私達はシミュレーションのみで学習を行ったモデルで,実ロボットにおいて今までにない程複雑なマニピュレーションタスクを解いたことを示す.この手法は2つのキーとなる要素からなります.1つ目:Automatic Domain Randomization(ADR),2つ目:機械学習のためのロボットプラットフォームです.ADRはだんだん難しくなるランダムな環境の分布を自動的に生成する.ADRを用いて学習を行った制御ポリシーやカメラによる状態推定がsim2real転移を大きく改善したことを示す.制御ポリシーにおいて,ADRによって生成された環境の分布で学習を行ったmemory-augmetedなモデルはテストで明確に創発的メタ学習の兆候を示した.カスタムしたロボットプラットフォームとADRの組み合わせによってルービックキューブをハンドマニピュレータを使って解くことができるようになった.結果をまとめたビデオが見れます: http://openai.com/blog/solving-rubiks-cube/

背景

ロボットなどの学習には大量のデータが必要であり,実世界で収集するのは難しい.そこで,シミュレーション上でデータを集める事ができれば魅力的である.しかし,シミュレーションはロボットや環境の全てを詳細には再現できていないため,sim2realの転移に取り組む必要がある.

以前の研究でDomain Randomizationにはシミュレーション環境のみで学習をしたモデルを実ロボットに転移させられるポテンシャルがあることがわかっている.また,以前OpenAIではマニピュレータを用いてブロックを回転させる研究を行っている.

シミュレーションのデータだけで,ルービックキューブを解くのに必要なマニピュレータの制御と状態推定を行う.ルービックキューブをマニピュレータで解くのは器用さと正確さを必要とするため,とても難しい問題である.また,ルービックキューブの状態推定も正確さを必要とする.

これらを達成するために,ポリシーと状態推定の学習にランダム化された環境の分布を自動的に生成するADR(Automatic Domain Randomization)を提案する.

扱うタスク

f:id:tkr1205:20191208220513p:plain

Block Reorientation

以前の研究で扱った,手の上でブロックを回転させるタスクである,詳細は,こちらを参照してほしい.

Rubik's Cube

言うまでもなくルービックキューブを解くというタスクである.

このタスクは,ルービックキューブ自体の解法についてはCociembaライブラリーを使って手順を作成するため,タスクの焦点はマニピュレータの制御と状態推定である.

つまり,このタスクに取り組むにはルービックキューブの姿勢と6つの面の角度を推定し,そこからマニピュレータを制御する必要がある,

また,ルービックキューブの面を90°回転させる事をrotation, キューブ自体を90°回転(別の面を上に持ってくる)させる事をflipと呼ぶ.

実際の学習では,上面を回転させる事が他の面を回転させるより遥かに簡単であることがわかったため,任意の面を回転させるのではなく,flipとrotationを組み合わせてルービックキューブを解く.(回転させたい面が下にある場合,2回flipする.また,真ん中の列を回転させたい場合,上の面をrotationし,2回flipした後rotationすることで真ん中の列を回転させたことになる)

実環境の設定

ロボットの設定

ハンドマニピュレータにはShadow Dexterous Handを使用し,ケージには3つのRGBカメラとPhase Spaceというモーションキャプチャー用カメラがついている.また,ハンドマニピュレータの先端にはPhase Spaceのモーションキャプチャー用LEDがついている.

ルービックキューブの設定

カメラの入力からルービックキューブの状態を予測させるのは難易度が高い.そこで,踏み台として「スマート」(センサとBluetoothモジュールを搭載した)なルービックキューブを使います.このスマートなルービックキューブをGiiker Cubeと呼ぶ.

制御ポリシーを学習する際,状態予測をするモデルが学習できていないため,このGiiker Cubeを使用した.また,一部の実験ではこのGiiker Cubeを使って,状態予測モデルによるエラーを悪化させずに制御ポリシーをテストした.

シミュレーションの設定

ダイナミクスキャリブレーション

シミュレーションと実世界でのマニピュレータの関節の動きを比較した.これらの値から,関節のパラメータをMSEで誤差を最小化したシミュレーションを使用した.

f:id:tkr1205:20191208222201p:plain

(左:キャリブレーション前の関節の値,右:キャリブレーション後の関節の値)

ルービックキューブの設定

完全な立方体だと指が引っかかったりするため,角を1.425mm削って角を丸くした.これは,実際のルービックキューブも同様に角が丸くなっている.

Automatic Domain Randomization

今までのsim2realでは,制御ポリシーと状態推定モデルをシミュレーション上でDomain Randomzationを使って学習し,どちらも実ロボットに転移させることが可能であった.だが,問題点として大量のパラメータのチューニングとイテレーションループが必要でだった.

ADRはこれらの課題を環境の分布をランダムに拡張することで解決した.

ADRの概要

Domain Randomizationは分布の範囲を手動で設定し,固定する必要があるが,ADRは自動で範囲が設定され,その範囲を変更することができる.

f:id:tkr1205:20191209001708p:plain

上の図は,ADRの概要図である.

ADRは環境全体の分布を制御し,この分布から環境をサンプリングし,それを使用しトレーニングデータを生成し,モデルのトレーニングを行う.そして,モデルの評価を行い,その評価から分布の更新を行う.

基本的には,モデルが上手く動作すると環境の分布は徐々に広がる.環境の分布が広がると,環境のとりうる値が広がる,具体的には,摩擦係数の範囲や,オブジェクトの大きさの範囲が広がる.

学習の初期の環境は,ランダムに変更されない.具体的にはポリシーの場合,初期の環境は実ロボットから測定された値に基づいて設定されます.

ADRの良い点

Domain Randomizationと比較して,ADRには良い点が2つある.

  • 最初にひとつの環境で学習を行い,最低限のパフォーマンスを達成した時にのみ新たな環境が追加されるため,トレーニングが進むにつれて難易度が徐々に高くなる.
  • 手動のパラメータチューニングがなくなる.ランダムにするパラメータが増えるとチューニングするパラメータが増えるため自動でチューニングされるのは重要である.

アルゴリズム

各環境は e_\lambdaで表され,\lambda \in \mathbb{R}^dである.dはランダム化するパラメータの数であり,例を上げると重力や摩擦係数,キューブの大きさなどである.

Domain Randomization では,\lambda \sim P_\phiである.\phi \in \mathbb{R}^{d'}でパラメータ化する.

ADRでは,この\phiを学習の中で動的に直接変化する.

ADRエントロピーH(P_\phi) = \frac{1}{d} \int P_\phi (\lambda) \log P_\phi (\lambda) d \lambda とする.このエントロピーが多いとは,環境の分布が大きくより多様な環境がサンプリングされるということである.また,正規化することで異なる環境のパラメータを比較することが可能になる.

ADRにおけるi番目のパラメータは\lambda_i, i = 1, 2, 3, 4, 5, ... , dで表される.また,({\phi_i}^L, {\phi_i}^H)\lambda \sim U({\phi_i}^L, {\phi_i}^H)となるように一様にサンプリングされる.

全体の分布は以下の式で表され,

P_\phi(\lambda) = \prod_{i=1}^d U({\phi_i}^L, {\phi_i}^H)

ADRエントロピーは以下の式となる.

 H(P_\phi) = \frac{1}{d} \sum_{i=1}^d \log ({\phi_i}^L - {\phi_i}^H)

アルゴリズムの流れ

  1. 色々な環境のパラメータから1 つの次元をランダムに選択する(\lambda_i).ここでは,摩擦係数が選択されたと仮定する.
  2. 摩擦係数バッファーにモデルの評価を追加していく.
  3. ある程度の数の評価が溜まったら,平均値を求める.
  4. 求めた平均値と閾値High  t_H閾値Lowt_Lと比較を行う.

平均値  > t_H なら分布を大きくし,

平均値  く t_L なら分布を小さくする.

つまり,モデルが上手く動作しているならば分布を大きくすることでよりタスクを難しくし,上手く動作していなければ分布を小さくすることでタスクを簡単にする.

ただ単純に分布を大きくするのではなく,ランダムに選択された次元Aでは上手く動作したため分布を大きくしたが,次に選択された次元Bでは上手く動作しなかったため分布を小さくすると言った形で自動で分布を変化させる.このあたりのパラメータの調節を人の手ではなくautomaticに行っている.

f:id:tkr1205:20191209105741p:plain

また,Algorithm1を見てみると,バッファーに追加する際にサンプリングしている環境は現在の分布の最小値もしくは最大値である.これは50%でどちらか選択される.

例えば,摩擦係数が選択されている時, {\phi_i}^Lにはより小さい摩擦係数が代入されている.一方で {\phi_i}^Hには大きい摩擦係数が代入されている.この時,50%の確率でどちらかからサンプリングされた環境でモデルを実行し評価を得る.この時,評価の平均値が閾値Highよりも高ければ,より大きい範囲の摩擦係数が分布に設定される.もし平均値が閾値Lowよりも小さければ,より範囲の狭い摩擦係数が分布に設定される.

また,評価(Algorithm 1)は確率 P_bで実行され,生成(Algorithm 2)は確率 1 - P_bで実行される.

あくまでADRは分布を動的に変化させ,分布から環境を生成するアルゴリズムであり,ADR自体が何かを学習するわけではない

ADR自体が学習を行うのではなく,学習アルゴリズム自体に生成したデータを渡してあげる必要がある.状態予測モデルモデルには,ADRのパラメータからレンダリングしたデータを渡し,ADRのデータからポリシーを学習する.

ADRのPolicyにおける分布の初期値 {\phi_i}^{0,L} = {\phi_i}^{0,H}は実ロボット上で実際に計測された値である.また,状態予測モデルの初期値は {\phi}^0 = 0であり,見かけ上の変化が無い.

シミュレーション上でのPolicyの学習

Policyの学習にはPPOを用いた.行動空間や報酬の設計は以前に行った研究から変えていない.

1エピソードを終了する条件として, - 50回連続で成功した時 - キューブを手から落とした時 - 次の行動に移ろうとしてタイムアウトした時

Policyのアーキテクチャ

Policyのアーキテクチャは主に以前の研究と同じである.だが,2点拡張した重要なポイントがある.

f:id:tkr1205:20191209113309p:plain

1点目は,ネットワークではReLUの後にLSTMが続くネットワークを使用しているが,以前の研究と比較してユニットの数を2倍にすることでネットワークの容量を2倍にしている.

ValueネットワークにもPolicyネットワークにも同じアーキテクチャを用いている.2つのネットワークの異なる点は,ValueネットワークはLSTMの出力をスカラーに射影しており, 10^{-6}のL2正則化をかけることで長期間の学習でも重みが発散するのを抑えている.

2点目は,以前の研究ではPolicy, Valueネットワークにおいてノイズのあるバージョンとノイズの無いにバージョンの異なる観測値を入力としており,ネットワークごとに全ての観測値をまとめて一つのベクトルにして入力していた.

Policyはノイズのある観測値のみを入力とし,Valueは観測値といくつかのノイズのある観測値を入力とした.(下の表を参照)

f:id:tkr1205:20191209114355p:plain

Asymmetric Actor-Criticアーキテクチャを用いているが,入力の連結部分を「embed-and -add」と呼ぶものに変更した.具体的には,最初に各観測を重み共有無しで512次元の潜在空間に埋め込む,そしてそれぞれの潜在表現を足し合わせてReLUを適用する.これを行う目的として,既存のポリシーに新しい観測を簡単に追加し,PolicyとValueネットワーク間で入力の埋め込みを共有することができることです.

Rapidを使った分散学習

多様な環境の分布で学習を行うために分散学習を行った.分散学習を行う際,OpenAI Fiveでも学習に使用したRapidという分散学習フレームワークを使用した.

Block Reorientationタスクには4x8=32個の NVIDIA V100を使用し,4x100=400ワーカーを32CPU Coreで使用した.また,Rubik's Cubeタスクには8x8=64個の NVIDIA V100を使用し,8x115=920ワーカーを32CPUコアで使用した.このタスクは数ヶ月継続して学習を行った.学習に使った累積経験は約1万3千年であり,OpenAI Fiveと同じオーダーである,

Policyのクローン

ADRを使用すると常に困難な分布から学習行うため,同じポリシーを長期間学習することが効果的である.したがって,ゼロから学習をすることはほとんどなく,ADRとPolicyの両方の以前のパラメータを使って学習をしなおした.Policyのアーキテクチャで説明した「embed-and-add」により,観測部分は簡単に変更することができる.だが,Policyはこのように簡単には変更できない.また,初期化されていないモデルで学習をリスタートすると数週間から数ヶ月の進捗が失われます.そこで,DAGGERアルゴリズム(ポリシー抽出)の精神で,行動クローニングを実装し,教師ポリシーに非常に近いパフォーマンスレベルで新しいポリシーを効率的に初期化した.

クローンを作成する際の設定は強化学習に似ているが,教師だけでなく生徒ポリシーもメモリ上に乗っている,ロールアウト中,生徒のアクションを使って環境とやり取りをし,生徒と教師のアクション分布の差を最小化(KLの発散を最小化),価値予測(L2Lossを最小化)する.これがとても上手く機能しており,クローンの作成は行動空間を変更しない限り,任意のPolicyアーキテクチャの変更で上手く機能する.

Visionによる状態推定

制御ポリシーは3台のカメラとニューラルネットワークからなる状態推定を受け取る.また,制御ポリシーにはキューブの位置と方向に加えて6面全ての角度の推定値が必要である.

ポリシーには[-π, π]の絶対回転角度が必要である.中央のステッカーは回転対称なので,1つのカメラから絶対角度を推定することができない.そこで,状態を追跡するorキューブを変更する必要がある.

今回は状態推定に2つのオプションを使うことで対応した.

  • 非対称な中央のステッカーのみからのVision : これをする目的はキューブの位置と回転,6面の角度を求めるためである.キューブの中央のステッカーの一角を切り取ることで回転対称性を無くす.これにより絶対回転角度がわかるようにした.
  • Giiker Cube : これをする目的は,位置と回転を求めるためである.面の角度はGiikerキューブに組み込まれたセンサを使用する.ポリシーからのエラーのみでvisionによる角度推定のエラーをよりひどくしないために,ほとんどの実験でこのGiiker Cubeを使用した.

f:id:tkr1205:20191209122909p:plain f:id:tkr1205:20191209122915p:plain

(上 : 中央のステッカーを切り取ることで非対称にしたブロック,下:様々なセンサーの埋め込まれたGiiker Cube)

長期的な目標は,現実世界で任意のオブジェクトと相互作用できるロボットを構築することである.そのため,理想では普通のルービックキューブを使用して,visionからのみでこのタスクを解きたいと考えている.これは可能だと考えているが,リカレントモデルでより広範囲に学習する(回転の記憶を状態推定モデルが保持する)か,エンドツーエンドな(状態推定モデルとPolicyが共に学習される)学習の仕組みが必要だと考える.

状態推定モデル

状態推定モデルは以前に行った研究と似ている.3つのRGBカメラを上面と左右に設置している.

位置と方向は上手く直接推定することができるが,センターのステッカーを非対称にしたキューブを使用した場合でも,6面全ての角度を直接予測するのは難しい.そのため,面の角度予測をいくつかの異なる予測に分割した.

  1. Active axis : キューブの3つある軸のうち一つだけが「アクティブ」であるという仮説のもとに,現在どの軸がアクティブであるかを予測する.

  2. Active face angle : アクティブな軸に関する面の \frac{\pi}{2}ラジアンの剰余を予測する.範囲は [-\frac{\pi}{4}, \frac{\pi}{4} ].面が底面にあり,手に隠れている場合, [-\pi, \pi]ラジアンで絶対角度を直接予測するのはとても難しい.このようにして \frac{\pi}{2}ラジアンの剰余を予測するには,形状とキューブの相対位置を認識するだけで良いため,簡単にできる.

  3. Top face angle : 上面の絶対角度を [-\pi, \pi]ラジアンで予測する.これは,真上にカメラがあるため,非対称なステッカーのキューブを使用している場合,単一のタイムスタンプから予測できる.上面のカメラはめったに遮られることがないため,上面のみを予測する.

これらから,各面の絶対回転角度を求めることができる(これらからどのように絶対角度を求めるかは,付録CのAlgorithm5を参照). また,角度予測において,角度をπラジアンごとに90°に離散化すると,回帰で直接角度を予測するよも優れたパフォーマンスが得られる.

Rapidを使った分散学習

制御ポリシーと同様に状態推定モデルは完全に生成データから学習し,実世界の画像は使用していない.そして,同様に複雑な学習な設定が必要なため,ポリシーと同様に分散学習のRapidフレームワークを使用した.

f:id:tkr1205:20191209124427p:plain

画像のネットワーク図における点線はネットワークの重みを共有していることを示している.つまり,ネットワークの重みは3つのカメラは共有している.

f:id:tkr1205:20191209124625p:plain

上の表はシミュレーション上と実世界での実験結果の比較である. Full モデルが提案手法である.また,Orientationがブロックの方向,Positionは位置,Top Faceは面の角度である.表を見ると,Domain Randomizationを使用していない場合,シミュレーション上ではOrientationとTop Faceの2つでエラーが最も少ないが,実世界ではエラーがとても多いことがわかる.一方で,提案手法ではシミュレーション上ではエラーが一番低いわけではないが,実世界ではエラーが一番少ないことがわかる.(ここで使用しているGround TruthはPhase Spaceから,全ての面の角度はGiiker Cubeから取得している)

状態推定モデルの損失関数について

状態推定モデルの損失関数についてだが,位置と方向にはMSE, 各面の角度はクロスエントロピーを求めた.だが,これらはスケールが異なるのでFocal Lossを用いて自動でLossnに重みを割り当てている.

また,重回帰タスクに上手く適応させるため変更した点がある.各予測に対して低いエラー値を設定し,その値以下のエラーをサンプリングする確率をfocal lossの確率pとして使用するようにした.つまり, FL(p; \gamma) = -(1-p) \gamma \log(p)となり,全ての実験で \gamma = 1とした.これにより,学習中にlossの重みを動的に変更できるため,lossの重みを手動でチューニングする必要がなくなり最適化が向上する.

結果

sim2simにおけるポリシーの転移

sim2simは,ポリシーを学習し,学習には使用していない手で調整した環境の分布にポリシーを転移し,パフォーマンスを評価する.

f:id:tkr1205:20191209130035p:plain

図より,ADRエントロピーを大きくする(分布を大きく)とsim2simのパフォーマンスが向上していることがわかる.

Sim2Realにおけるポリシーの転移

実ロボットの実行には高いコストがかかるため,7つの異なるポリシーに限定して評価を行った.それぞれ実ロボットで10回試行した結果である.また,シミュレーションは各500回試行した結果である.

f:id:tkr1205:20191209130438p:plain

上の表は実験の結果である.

Baselineは以前に行った研究である.Manual DRはADRと同じモデルアーキテクチャを使用して通常のDRで学習した結果である.ADRの(Small, Medium, Large)は学習した期間が異なり,XL, XXLは継続的に学習を行った結果である.

Manual DRとADR(Large)を比較した時,同じ学習期間であり,シミュレーション上での成功率はManual DRの方が高いにも関わらず実世界での結果がとても低い.この2つの違いは環境の分布のみであり,環境の分布を変化させることが重要なポイントであることがわかる.

Baselineはハードウェアの調整などを行った結果であるが,長期間学習を行ったADR(XL, XXL)はハードウェアの調整無しで平均パフォーマンスが2倍,中央値は3倍以上に向上している.

これらから,ADRは数ヶ月に渡る手動のパラメータチューニングの結果を少ない手動のパラメータチューニングではるかに上回った.

徐々に難しい環境でポリシーを学習する影響

ADRは学習する分布の複雑さを徐々に拡大するように設計されている.単一の環境から学習を初めて,学習が進むと非常に複雑な(多様な)環境を学習することが可能である.

上の表のsmall, medium, large, XLから分布だけを取り出して,固定したものとADRを使用して0から学習したものを比較する.

f:id:tkr1205:20191209131841p:plain

上の図を見ると,分布が固定されているDRよりもADRの方がはるかに学習が早いのがわかる.

この結果から,環境の分布のエントロピーを適応的に大きくすることが重要であるとわかった.また,エントロピーを固定っする場合,エントロピーが大きいほど学習するのに時間がかかっている(図左のsmallとXL).具体的には,あまりに複雑な環境では十分な信号が得られないためポリシーが学習しにくいと考えている.

徐々に難しい環境で状態推定モデルを学習する影響

状態推定モデルをADRで学習する際,ADRは光源の距離,マテリアルのメタリックと光沢,そしてTensorFlowの歪操作(ガウスノイズとチャネルノイズ)を制御している.

f:id:tkr1205:20191209132743p:plain

上の表は以前の研究で得られたBaselineとADRを比較した結果である.実世界におけるエラーを比較した時,ADRの方がBaselineよりも優れた結果を示しているのがわかる.一方で,シミュレーション環境における結果を比較した時,ADRの方がエラーが大きい.しかし,これはADRは常により難しいタスクを生成し続けるためである.

Rubik's Cubeタスクにおける量的評価

公正に評価を行うためにTNoodleからランダムにサンプリングしたもので評価を行う.つまり,チェリーピッキング(自分に都合良いデータを選ぶこと)はない.

今回しようしたものはL2 U2 R2 B D2 B2 D2 L2 F’ D’ R B F L U’ F D’ L2という組み合わせである.ルービックキューブを解かれた状態から,この状態までグチャグチャにすることで評価を行った.これはルービックキューブを解くのと実質同じことである.また,この状態にするのに合計43回(orientation 26回,flip17回)の操作が必要である.

f:id:tkr1205:20191209133153p:plain

Manual DRとADR(XL)は2週間学習を行い,ADR(XXL)は数ヶ月学習を行った.

Rubik's Cubeタスクにおける質的評価

f:id:tkr1205:20191209133855p:plain

上の図は実世界における実行中に様々な邪魔をしている様子である(最初の動画を見てみると結構強めに押したりしている). (最初の)動画を見てみると,ロボットが誤って違う面を回転させていることがわかる.その場合,最善の方法は面を回転して元に戻し,サブゴールを変更せずに継続することである.また,ズレによるインターロックを回避するために,rotationを実行する前にflipした後で面の位置を調整して合わせていることが確認できる.それでもrotationが上手くいかないこともある.その時は,ポリシーが握りを調整して,rotationを別の方法で試行し最終的に成功することがよくある.また,rotationをしようとしてflipした場合も,もう一度把持し直して最終的に成功する.

難しい回転の際に引っかかって,しばらくするとキューブを落とす可能性が高い.これは,数秒間静止したキューブしか観測しないため,それまでしていた行動を「忘れた」からだと考える.

まとめ

シミュレーション上でハンドマニピュレータの制御ポリシーとルービックキューブの状態推定モデルを学習する際にADRを適用し,実世界に転ゼロショットで転移させることができた.制御ポリシーと状態推定モデルは別々に学習を行い,ポリシーの学習には数ヶ月かけている.

ルービックキューブをハンドマニピュレータを使って解くという論文であり,解き方自体はアルゴリズムを使っているという点において,物議を醸していた(例えばコレ)のを観測したが,確かに名前が悪いと思う.

また,Giiker Cubeを学習の一部に使用しており,Policyに対して状態を渡す際にGiiker Cubeの値をそのまま渡すと勘違いしている人も中にはいたが,Giiker Cubeは主に状態推定モデルの学習に使用しており,状態推定モデルでも動作するっぽい.(映像はおそらくGiiker Cubeを使用している.)

コメント

強化学習 Advent Calendar2019はたくさん空きがあるので,是非参加してください!!

iPadを買っておけ間違いないから!!!

この記事は,FUN Advent Calendar2019の12月5日の記事です.

昨日はnao(ki)さんの1/32の車のお話。でした.

学部4年の知能コースのたける(Twitter)です. 強化学習とか深層学習とかロボットの制御的な感じの研究してます. 特に最近は自分の無能さに病みがちです. 来年からは未来大の修士課程です.

毎年12月5日にAdvent Calendarに参加しているんですが,実は今日12月5日が誕生日です. 誕生日プレゼントください,誕プレ乞食するために毎年12月5日に記事書いてます.貰えたことほぼ無いけど

Amazon欲しい物リスト(Geforce TITAN RTXが欲しいです)

本題に入る前に

未来大生のような悲しい人達には基本彼氏や彼女がいないと思うので,クリスマスプレゼントにお金が消えるなんて事は無いと思います.そこで,自分にクリスマスプレゼントを買いましょう.今すぐ買いましょう.今スグです!!!

今スグiPadを買いましょう!!!

まず,コチラをクリック!

そして,いいなと思った物をカートに入れる.そしたら,コチラもおすすめですみたいなところにあるApple Pencilもカートに入れましょう.

あとはお金の払える魔法のカードとか使えばクリスマスくらいには届きます.

iPadを使う利点

iPadを使う利点が無限にあります.今回は中でもいくつか紹介したいと思います.

  • 紙のノートを持ち歩く必要がない
  • PDFなどの資料に直接書き込める
  • 論文を印刷せずに書き込み放題
  • 電子書籍にとても向いている
  • Macのサブディスプレイになる
  • YouTubeが見やすい

紙のノートを持ち歩く必要がない

皆さんは講義の日にノート持ってくるの忘れたー!とか,持ってくるノート間違えたー!!という経験はありませんか?ありますよね?僕は何度もあります.

他にもノートを何冊も持ち歩くの面倒だなと感じた事はありませんか?

iPadApple Pencilがあればそんな日々とはおさらばです.

iPadには色々なノートアプリがあります.

有名所では,

  • Notability
  • GoodNotes 5
  • Noteshelf 2

などが挙げられます.全部同じ様な事ができるので好きな物を選ぶといいと思います.

自分はNotabilityを使用しているので以下Notabilityでの説明になります.

下の2つのノートは実際に自分がiPadApple Pencil, Notabilityを使って取ったノートです.(字が汚いのはデフォです)

f:id:tkr1205:20191204201717j:plainf:id:tkr1205:20191204201739j:plain

キレイにノートが取れていますね(震え声)

ノートアプリがあればグラフをキレイに描けたり,撮った板書の写真を貼り付けたりなんでもできてしまいます.

個人的にはノート取りまくってると手が黒くなってしまう事案が発生しないのが良いポイントです.

PDFなどの資料に直接書き込める

講義によっては,パワーポイントなんかの講義資料を配布してくださる先生もいらっしゃいます.

その場合には講義資料をiPadに取り込んでしまいましょう.

多くのノートアプリはPDFファイルなどの形式に対応しているので,講義資料をダウンロードして直接書き込むことができてしまいます.

また,プリント資料を配布してくださる先生もいらっしゃいます.そんな場合にはプリントの写真を取ってノートアプリに取り込みましょう.そうすれば書き込み放題です.

f:id:tkr1205:20191204233319j:plainf:id:tkr1205:20191204233330j:plain

論文を印刷せずに書き込み放題

ノートアプリにPDFを直接読み込めるということは,arxivなどに上がっている論文を読み込む事もできます.

表紙の次に空ページを作って,そこにアブストの全訳を書いてみたり,少しメモを取ったり.

また,自分の様な英弱には,ノートアプリの中で単語を指定してその和訳を調べることもできます.

f:id:tkr1205:20191204204025j:plainf:id:tkr1205:20191204203847j:plain

他にもPDFや書き込んだノートは各種Cloudから共有できるので,Google Driveから論文を読み込んだりすることもできます.

自分はiPhoneTwitterからarxivに飛ぶことが多いので,iPhoneで開いたarxivAirDropiPadに飛ばしてNotabilityで開くということをよくしています.

電子書籍にとても向いている

技術書などを購入する人も多いと思います.

技術書は重いし分厚いし,気になったときにパッと開きたいのに手元にないことがあったり,不便ですよね.

ですが,iPadがあれば分厚い電子書籍も何万冊でも持ち歩き放題.

これは僕のKindleアプリに入っている本です.これらの本をいつも持ち歩くのは大変ですが,iPadがあればいつでも参照することができます.

f:id:tkr1205:20191204224807j:plain

また,色々ゴニョゴニョすればKindleの本をPDFにしてノートアプリに取り込むこともできます.

そうすれば下の写真のようにKindleアプリではできなかった,本に直接手書きでメモすることが可能になります.

また,理工系の書籍がKindleストアでは頻繁にセールしているので,半額とかで買うこともできます.

f:id:tkr1205:20191204225322j:plain

Macのサブディスプレイになる

対象機種は限られていますが,iPadMacのサブディスプレイとして使うことができます.

iPadがサブディスプレイになるのでスタバに行ってドヤる際にも,iPadがあればデュアルディスプレイを使うことができてしまいます.

また,デュアルディスプレイ中にもApple Pencilが使えるので,Adobe IllustratorApple Pencilで使うことができます.

他にもAdobeのいくつかのソフトがiPadアプリとして提供されているのでApple Pencilを使って簡単に使用することができます.

YouTubeが見やすい

YouTubeを見るときに何を使って見ていますか?

スマートフォンは画面が小さいし,PCはいちいち取り出してしまうのが面倒ではないですか?

そこでiPadを使いましょう.スマートフォンより大きく,PCよりも出し入れが楽に行なえます.

また,こういったケースを使えば,自立してくれるのでより見やすくなります.

最後に

iPadApple Pencilを買ってノートを持ち歩く生活とはおさらばしましょう.

iPad買わなかった人は僕に誕生日プレゼントください.iPad買った人も僕に誕生日プレゼントください( Amazon欲しい物リスト).

明日はパンダちゃんさんです

最後にもう一度

今スグiPadを買いましょう!!!

おまけ

兵庫の実家の母親が誕生日を祝ってくれました.(後ろの人形は誕生日会参加者らしい)

お母さん,僕はそこにいません.僕もケーキ食べたかった.

f:id:tkr1205:20191204232213j:plain

GitHub Actionsを使って,TeXをビルドする仕組み作ってみた

概要

GitHub ActionsというCIを用いて,リポジトリにPushすると自動的にTeXファイルからPDFファイルに出力するようなもの.出力はGitHub APIを使って,GitHub ReleaseのAssetsに添付します.

リポジトリ

READMEにも同様の説明があるよ リポジトリ

前提

  • Gitをある程度使える必要があります
  • GitHub Actionsはまだベータのため申請しておく必要があります(申請はコチラ)

使い方

  1. このリポジトリをFork
  2. プライベートリポジトリに設定(参考)
  3. forkしたリポジトリを自分のパソコンにClone
  4. [1回目のみ]Releaseが一つも無い場合 v0.0.0とかにして適当に作っておいてください
  5. ローカルでmain.texを編集
  6. [1回目のみ.]github/workflows/main.yml内repositoryの部分を自分のリポジトリに変更してください
  7. .github/workflows/main.yml内の一番下の行のtagsの値を変更(変更しないとファイルが正常にアップロードされません)
  8. リモートにPush
  9. ActionsでBuildの経過を確認(約9分程度で完了)
  10. GitHub ReleasesのAssetsにPDFファイルがあるので,適宜ダウンロードしてください

cloneした状態のmain.texでは,contents/の中に各sectionが入っているので,気に入らなければ大学から配布されているsample.texを元にして編集してください. 画像などのファイルはfigures/に入っているので,TeX Wikiを参考にしてください.

注意

  • 他のファイル名.texを使用したい場合は,.github/workflows/main.yml内のroot_fileのところの引数を変更してください
  • 一部配布されているsample.texと変更してある部分があるので気をつけてください.
  • bibファイルを使うとどうにもエラーが発生するので使えません.解決方法を教えて下さい. 使えるようになりました (2019/10/23)
  • CI上ではcacheを使えないので,Dockerのビルドに時間がかかります.Localに環境を作れるならLocalの方が爆速です.
  • 分からないことがあれば,最低丸1日は自分で調べてください.それでも分からなければissueなりTwitterなりに質問してください.
  • 問題があればissue, プルリクしてください.

Twitter :@tkr12051

簡単に理解してDQNを実装してみる

この記事は

この記事はFUN Advent Calendar 2018 5日目の記事です。 去年に引き続き12/5に記事を公開してます。

昨日は mecaota さんでした。

注意

時間足りなくて途中で心が折れてます。気をつけてください。 だいぶ適当なこと書いてるので、間違いは訂正コメントしてください。 時間見つけて直します()

この記事を読んでわかること

  • DQNの概要
  • DQNがどんなしくみでできているのか
  • DQNの実装
  • PLEでの学習のさせ方
  • OpenAI Gym以外での学習

この記事を読んでもわからないこと

この記事の対象者

  • MNISTはやったことある人
  • numpyある程度分かる人(行列の計算とかreshapeとか)
  • 深層強化学習に興味あるけど、強化学習がいまいちわからない人
  • OpenAI Gymでしかやってなくて少し飽きてきた人

<注意 >

  • DQNを実装してみた
  • DQNの忠実な再現ではない(Fitted Neural NetworkとDeep Q Networkを組み合わせたような感じ)
  • 正確な表現ではなく、私の感覚的な表現が多いです(コメントで表現の訂正お待ちしてます)
  • こちらの本をメチャクチャ参考にしています。この本とてもおすすめです。

つくりながら学ぶ! 深層強化学習 ~PyTorchによる実践プログラミング~

今回実装したソースコードコチラ (実装はPyTorch, Jupyter Notebook)

今回実装したらこうなるよ↓

DQNとは

まず、DQNを説明する前に強化学習(Reinforcement Learning)について整理しておく必要があると思います。

強化学習(きょうかがくしゅう、: Reinforcement learning)とは、ある環境内におけるエージェントが、現在の状態を観測し、取るべき行動を決定する問題を扱う機械学習の一種。エージェントは行動を選択することで環境から報酬を得る。強化学習は一連の行動を通じて報酬が最も多く得られるような方策(policy)を学習する。代表的な手法としてTD学習Q学習が知られている。 (Wikipedia)

ちょっとイメージしにくいですよね。

宿題をやりたくない小学生のAくんを考えてみる

イメージしやすくするために、宿題をやりたくない小学生のAくんを考えてみます。

Aくんは宿題をやりたくありません。でも、Aくんのお母さんは宿題をやるように言います。

そしてお母さんは機嫌が良いときと悪い時があり、機嫌が良いと宿題に対して寛容になったり、機嫌が悪いと宿題に対して厳しくなります。

そこで、Aくんは機嫌を見て少しでも宿題をやらずにお母さんを誤魔化すために色んな試行錯誤をします。

例えば、

  • 機嫌が良い時に宿題をやってるふりをしながらYouTubeを見る
  • 機嫌が悪い時に宿題を学校に忘れる
  • 機嫌の悪い時に家のお手伝いに精を出す ....etc

などなど、色んな方法を繰り返してみます。

そして基本的には、これらの結果として2つの可能性が考えられます。

  1. お母さんをうまく誤魔化し、褒めてもらえるf:id:tkr1205:20181125175245p:plain
  2. お母さんをうまく誤魔化せず、怒られるf:id:tkr1205:20181125175250p:plain

これを毎日繰り返していくことで、お母さんの機嫌を見て誤魔化せる方法をAくんは知るでしょう。

逆に怒られてしまうような行動も知ることになります。

このようにして、お母さんの様子を見て、宿題をやらずに誤魔化せる方法をAくんは知ることができました。

Aくんのお話を強化学習の言葉で置き換えてみる

強化学習ではAくんはエージェント(Agent)と呼ばれます。ゲームで例えるとゲームをプレイする脳みそを持つプレイヤーのことでもあります(囲碁や将棋なら指し手、テニスや卓球ならプレイヤー)。

お母さんの機嫌の良い悪いは、強化学習における状態(State)と呼ばれます。ゲームで例えるとゲームをプレイするフィールドのことです(囲碁や将棋なら盤面、テニスならボールの位置など)。

そして、エージェントは状態からどう動くのかを決めます。例えば、お母さんの機嫌が良い場合(状態)に「宿題をやらずにゲームをする」といった、行動を選択します。この選択した行動を強化学習では行動(Action)と呼びます。

また、Aくんの結果によって 1.褒められる 2.怒られる の2つの結果が得られました。これらを強化学習では報酬(Reward)と呼びます。つまり、良い結果が得られれば褒められ、悪い結果となれば怒られるのです。強化学習では、この報酬の和を最大化します。つまり、たくさん褒められようとします。そりゃそうですよね、怒られるよりたくさん褒められたいですよね。

表で一度整理しておきます

Aくんのお話 強化学習のお話
Aくん エージェント(Agent)
お母さんの機嫌 状態(State)
Aくんの行動 行動(Action)
褒められる or 怒られる 報酬(Reward)

強化学習の言葉で言い換えると

ここで、Aくんのお話を少し強化学習で言い換えてみると、

Agent(Aくん)は状態(お母さんの機嫌)を観測し、報酬の和を最大化する(たくさん褒められる)行動(誤魔化しかた)を選択する

このようにして強化学習では状態を観測し、その中で最も良いサボり方を学習しようといった手法です。

この手法の中に有名なものでは、

  • Q学習
  • Sarsa
  • Deep Q-Network(今回のメインのお話)

といったものがあります。

Q学習とは

DQNを理解する上で重要なアルゴリズムが一つあります。それはQ学習(Q-Learning)です。

Q学習とは、

Q学習は有限マルコフ決定過程において全ての状態が十分にサンプリングできるようなエピソードを無限回試行した場合、最適な評価値に収束する (Wikipedia)

これもわかりにくいですよね。

まず有限マルコフ決定過程とは、時刻t+1への状態遷移は時刻tにおける状態と行動にのみ依存し、それ以前の状態や行動には関係がないということです。

上のAくんの例で例えるならば、Aくんが褒められるか怒られるかは、その前にAくんがどんな行動を取ったかによってのみ決まり、何日も前のことなど何も関係ないということです。

考えてみれば当然のことです。今日出された宿題を上手く誤魔化して褒められるか、バレて怒られるかは、昨日怒られたかどうかには関係しません。今日上手く誤魔化せるかどうかです。

Q学習ではQテーブルというものを考えます。

具体的には以下のようなものです。

f:id:tkr1205:20181125175229p:plain

Qテーブルは 状態の数(お母さんの機嫌) x 行動の数(Aくんの行動)の表で表します。

Qテーブルにかかれている数字は行動価値(action value)またはQ値と呼ばれるものです。行動価値は言葉の通り、ある状態においてその行動がどれくらい良い行動なのかを表すものです。例えば、お母さんの機嫌の悪いときに「ゲームをする」という行動の価値はとても低いです。ですが、機嫌の良いときに「お手伝いをする」という選択の価値は高いです。

Qテーブルはいわば辞書のようなもので、このような表にすることで状態と行動の辞書のようなものができます。

そこで、以下のようにして今の時刻における状態をQテーブルから参照してあげることで、最も行動価値の高い行動(最も褒められる行動)がわかります。

f:id:tkr1205:20181125175310j:plain

ということは、この行動価値がとても重要な値だと分かると思います。もしも値が間違っていた場合、お母さんの機嫌が悪いときに「ゲームをする」という行動をしてしまうかもしれません。

では、この行動価値はどのようにして求めるのでしょうか? 行動価値を求めるには行動価値関数(action value function)というものを使います。

Q学習では、この行動価値関数をより良いものにするべく学習していきます。

そして、そのためにQ学習では以下のような状態価値の更新式を用います。 (Q(s_t, a_t) は時刻tの状態s_tにおけるa_tの行動価値のことです。)

[tex:Q(s_t, a_t) = Q(s_t, a_t) + \alpha(R(s_t, a_t) + \gamma\max{a'}E[Q(s{t+1}, a')] - Q(s_t, a_t))]

こんなもんいきなり見せられてもわからない人はわからないので、色々あって式の変形をしちゃいます。

[tex:Q(s_t, a_t) = R(s_t, a_t) + \gamma\max{a'}Q(s{t+1}, a')]

すると、なんとも不思議。こんなに短い式になりました。(この式変形の詳細が知りたい人は書籍やGoogleを参考に)

R(s_t, a_t)とは、状態s_tにおいてa_tという行動を取った結果の報酬のことで、褒められたか怒られたかを教えてくれます。

[tex:\gamma\max{a'}Q(s{t+1}, a')] は一度に説明できないので分けて説明します。

まず、先にQ(s_{t+1}, a')を説明します。 これは時刻t+1の状態における行動a'の行動価値のことです。

では、このa'をどう選ぶかですが、これを指定しているのが[tex:\max{a'}]の部分です。 ここでは、[tex:\max{a'}]は「状態価値が最大となるようなa」という意味です。

つまり、[tex:\max{a'}Q(s{t+1}, a')]というのは、時刻t+1の状態における行動価値が最大になる行動の行動価値ということです。 この時刻t+1で最大となく行動価値にかけている\gammaですが、これは時間割引率と呼ばれるものです。

時間割引率とは、人の習性を参考にしたものです。例えば、人は今日得られる1万円と10年後に得られる1万円のどちらの方が欲しいかといった問題の場合、ほとんどの人は今日得られる1万円の方が欲しいと考えます。 このように未来で得るものは少し価値を差し引いて考えてあげなければなりません。そのため、ここでは時間割引率というものを使って、未来の価値を少し差し引いてあげます。

もし、上の説明が難しいと感じたなら以下のように考えてください。

行動価値関数に状態と行動を引数として渡すと行動価値を教えてくれる!!!

f:id:tkr1205:20181125175337j:plain

また、もう少し厳密にわかりやすく知りたい人はコチラの動画(ニコニコ動画)がとてもおすすめです。

ここから本番

DQNとは

DQNとは簡単に言うと行動価値関数やQテーブルの代わりにニューラルネットワークを使おうということです。

ニューラルネットワークは関数のモノマネができるということは有名です。 ポケモンで例えるならば、メタモンです。(メタモンかわいい、でもミミッキュの方がかわいい)

このQ関数をニューラルネットワーク関数近似するという考えはNeural Fitted Q Iterationというもので提案されました。

具体的には、現在の状態を入力として行動を出力するというような関数をニューラルネットワークにさせます。

f:id:tkr1205:20181125175325j:plain

本来提案されたDQNでは、ニューラルネットワークへゲームの画像が入力として渡されます。ですが、今回のDQNではマシンスペックと相談した結果、入力としてエージェントの位置などの座標を入力としてニューラルネットワークに渡しています。

また、他にもNeural Fiited Q Iterationとは異なり、こんなにも有名になった理由があります。それは、学習の安定です。今までのどのような手法と比べても安定して学習させることに成功しています。

学習の安定を実現した理由として2つ挙げられます。1つはExperience Replayと呼ばれるもの。もう一つはFixed Target Q Networkと呼ばれるものです。これらの説明は実装の項で行います。

DQNの実装

今回のDQNの実装については主にPyTorchとPLE(PyGame Learning Environment)で実装しています。

環境については以下です。

使用した環境

環境 Version
OS mac OS X Mojave
Python 3.6.1
Anaconda Anaconda 4.4.0 (x86_64)
numpy 1.15.0
matplotlib 2.0.2
PLE 3.10
pygame 1.9.4
Torch 0.4.0

PLEが公式にサポートしているのはPython2.7.6になります(2018年11月5日現在)。
他のバージョンでは動作は保証できません。

PLEなどのインストールについては以前の記事で解説しているので、コチラを参考にしてみてください。

まずは、必要なライブラリ群をインポートしていきます。

今回はFlappyBirdと呼ばれるタスクに取り組むのでPLEからFlappyBirdをインポートしています。

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import os
os.environ["SDL_VIDEODRIVER"] = "dummy"  # ポップアウトウィンドウを表示しないようにする
from ple import PLE
from ple.games.flappybird import FlappyBird

ここで、namedtupleというものを使って、学習に使うデータの形を宣言しておきます。これに従って以降は学習を行います。

学習経過の可視化

学習のしているのをただひたすら数字で見るのもいいですが、せっかくゲームを学習させているのでどれくらいできるようになっているのか見れるようにしましょう。 表示する頻度は下のパラメータで調整できます。

from JSAnimation.IPython_display import display_animation
from matplotlib import animation
from IPython.display import display

def display_frames_as_gif(frames):
    
    plt.figure(figsize=(frames[0].shape[1]/72.0, frames[0].shape[0]/72.0), dpi=72)
    patch = plt.imshow(frames[0])
    plt.axis('off')
    
    def animate(i):
        patch.set_data(frames[i])
    
    anim = animation.FuncAnimation(plt.gcf(), animate, frames=len(frames), interval=16)
    
    anim.save('movie_flappy_bird_DQN.mp4')
    display(display_animation(anim, default_mode='once'))

namedtupleを使うことで、変数をまとめることができ、変数えのアクセスや管理がしやすくなります。 今回は学習に(現在の状態, 行動, 行動をとった後の状態, 得られた報酬)を使います。これらが必要な理由がもしわからない場合は、Q学習とはの項から読み直してみると分かるかもしれません。

from collections import namedtuple
Transition = namedtuple('Transicion', ('state', 'action', 'next_state', 'reward'))

次に学習に使うハイパーパラメータを宣言しておきます。これは、学習が上手くいかない場合に変更することが多いので、以下の変数はハイパーパラメータとして宣言しておくことをオススメします。

GAMMA = 0.99 # 時間割引率ガンマ
MAX_STEPS = 1200 # 1試行のstep数(フレーム数)
NUM_EPISODES = 800 # 最大試行回数
PRINT_EVERY_EPISODE = 50 #50回に一回logを出力する
SHOW_GIF_EVERY_EPISODE = 100 #100回に一回映像で出力する

Experience Replayについて

Experience Replayとは日本語にすると経験再生と言います。 日本語にするとダサいです。機械学習では、過学習(overfitting)というものが起きます。過学習について説明は特にしませんが、これは良くないことです。強化学習においても例外ではなく、過学習というものが発生します。これの原因は、ゲームのデータをそのまま保存してしまうと時系列的な相関が発生してしまいます。それらの相関をニューラルネットワークは勘違いして学習してしまいます。ですが、これは学習において避けたいことなので、それに対する対処を考えなくてはなりません。そこでReplay Memoryというものを考えます。

学習中のデータはReplay Memoryに一度保存します。そしてニューラルネットワークの学習の時にReplay Memoryからランダムにサンプリング(取り出す)することで時系列での相関を減らします。

class ReplayMemory:
    
    def __init__(self, CAPACITY):
        self.capacity = CAPACITY #メモリの最大値
        self.memory = [] # 経験を保存するリスト
        self.index = 0 # 保存するindexを表す変数
    
    def push(self, state, action, state_next, reward):
        '''trasicion = (state, action, state_next, reward)をReplayMemoryに保存する'''
        
        if len(self.memory) < self.capacity:
            self.memory.append(None) # メモリが満タンじゃないときは足す
            
        # 各引数をnamuedtupleでまとめ、memoryに保存する
        self.memory[self.index] = Transition(state, action, state_next, reward)
            
        self.index = (self.index + 1) % self.capacity # 保存するindexを1つずらす
        
    def sample(self, batch_size):
        '''batch_sizeだけ、ランダムに取り出す'''
        return random.sample(self.memory, batch_size)

    def __len__(self):
        '''関数lenに対して、現在のmemoryの長さを返す'''
        return len(self.memory)

ニューラルネットワークの実装 

ここからニューラルネットワークの実装に入っていきます。ニューラルネットワークの実装にはPyTorchを使用しています。

まずは、ライブラリのインポートです。

import random
from copy import deepcopy
import torch
from torch import nn
from torch import optim
import torch.nn.functional as F

次に学習に関するハイパーパラメータです。こちらも上と同様にハイパーパラメータとしてまとめて外に出しておくのがオススメです。

BATCH_SIZE = 32 # バッチサイズ
CAPACITY = 10000 # Replay Memoryに保存するデータの最大量

PyTorchでのネットワークの宣言は二種類くらい書き方があります。好みで選んでください。

今回使用したニューラルネットワークは、中間層が32個のユニットからなる2層のニューラルネットワークです。(「ディープじゃないやん」とかは無しで。。。)

class Net(nn.Module):
    
    def __init__(self, num_states, num_actions):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(num_states, 32)
        self.fc2 = nn.Linear(32, 32) #中間層
        self.fc3 = nn.Linear(32, num_actions)
    
    def forward(self, x):
        h1 = F.relu(self.fc1(x)) # 活性化関数にはReLu
        h2 = F.relu(self.fc2(h1))
        output = self.fc3(h2)
        return output

次にエージェントの脳みそとなるBrainクラスを宣言します。ここでは、どんな行動を選択するのか決めたり、ネットワークの更新を行います。

Fixed Target Q Networkについて

Experience Replayと同様に重要なメソッドです。 Experience Replayは時系列の過学習を抑制するためのものでしたが、Fixed Target Q Networkは学習を安定させるためのものです。

Fixed Target Q Networkの仕組みを簡単に説明します。強化学習では各エピソード毎に正解とする目標を定め、その目標とのloss(どれくらい間違えているか)を求め、ネットワークを修正していきます。ですが、この目標とする値が結構バラバラになります。そのせいで以下の図のように、目標の値が振動してしまい結果的に上手く学習が進まないことがあります。(緑が目標とする値)

f:id:tkr1205:20181125175345p:plain

このように目標とする値が振動してしまわないように、ある一定間隔の間は目標とする値を固定するようにします。その結果、振動しにくくなり学習が進みやすくなります。また、図にすると以下のようになります。目標とする値は振動せずに、一定時間固定されています。

f:id:tkr1205:20181125175349j:plain

1つ目の図のように目標とする値が振動してしまうのには、原因があります。それは目標とする値を出力するのもまたネットワークが行うからです。これが学習の完了しているネットワークなら問題はありませんが、未だ学習の終わってないネットワークを使って目標とする値を出しているため、図のように振動していまいます。そのため、目標とする値を出力するネットワークの重みを固定してあげます。そうすることで、同じ値を目標として学習させることができます。また、ネットワークの重みをずっと固定していても良いことはないので、定期的に学習の少し進んだネットワークの重みをコピーしてあげます。

f:id:tkr1205:20181125175344p:plain

コンストラクタでは、主にオブジェクトの生成を行っています。 Experience Replayのために経験を保存しておくためのReplay Memoryや、学習するネットワークのモデル,Fixed Target Q Networkのためにモデルのコピーも行っています。

replay関数では、Experience Replayを使って学習を行っています。 まず、Replay Memoryからバッチサイズの数だけデータを取りだします。ただ取り出しただけだと、1x4がBATCH_SIZE数ならんでいて、ネットワークの学習に使うには少し不都合です。なので、形を変えてあげます。

そこで取り出したデータを上で宣言した、namedtupleを使って(state, action, next_state, reward)の1つの形にまとめてあげます。以下のような書き方をすることで(state x バッチサイズ, action x バッチサイズ, next_state x バッチサイズ, reward x バッチサイズ)という形にすることができます。

batch = Transition(*zip(*transitions))
class Brain:
    def __init__(self, num_states, num_actions):
        self.num_actions = num_actions # 行動の数を取得
        
        # 経験を保存するメモリオブジェクトを生成
        self.memory = ReplayMemory(CAPACITY)
        
        # NNを構築
        self.model = Net(num_states, num_actions)
        
        #print(self.model) # ネットワークの形を出力
        
        # target_net
        self.target_net = copy.deepcopy(self.model)
        self.target_net.load_state_dict(self.model.state_dict())
        
        # 最適化手法の設定
        self.optimizer = optim.Adam(self.model.parameters(), lr=0.001)
        
    def replay(self):
        '''Experience Replayでネットワークの結合パラメータを出力'''
        
        # メモリサイズがミニバッチより小さい間は何もしない
        if len(self.memory) < BATCH_SIZE:
            return
        
        
        # メモリからミニバッチ分のデータを取り出す
        transitions = self.memory.sample(BATCH_SIZE)
        
        # 各変数をミニバッチに対応する形に変形
        # trainsicionsは1stepごとの(state, action. state_next, reward)が、BATCH_SIZE分格納されている
        # つまり、(state, action, state_next, reward)xBATCH_SIZE
        # これをミニバッチにしたい
        # (state x BATCH_SIZE, action x BATCH_SIZE, state_next x BATCH_SIZE, reward x BATCH_SIZE)にする
        batch = Transition(*zip(*transitions))
        
        # 各変数の要素をミニバッチに対応する形に変形する
        # 例えばstateの場合、[torch.FloatTensor of size 1x4]がBATCH_SIZE分並んでいるが、
        # それを torch.FloatTensor of BATCH_SIZE x 4に変換する
        
        state_batch = torch.cat(batch.state)
        action_batch = torch.cat(batch.action)
        reward_batch = torch.cat(batch.reward)
        non_final_next_states = torch.cat([s for s in batch.next_state if s is not None])
        
        
        # 教師信号となるQ(s_t, a_t)値を求める
        self.model.eval()
        
        # ネットワークが出力したQ(s_t, a_t)を求める
        # self.model(state_batch)は、右左の両方のQ値を出力しており
        # [torch.FloatTensor of size BATCH_SIZE x 2]になっている
        # ここから実行したアクションa_tに対応するQ値を求めるため、action_batchで行った行動a_tが
        # 右か左かのindexを求め、それに対応するQ値をgatherで引っ張り出す
        state_action_values = self.model(state_batch).gather(1, action_batch)
        
        # max{Q(s_t+1, a)}値を求める。ただし、次の状態があるかに注意。
        
        # flappybirdがdoneになっておらず、next_stateがあるかをチェックするインデックスマスクを作成
        non_final_mask = torch.ByteTensor(
            tuple(map(lambda s: s is not None, batch.next_state)))
        
        # まずは全部0にしておく
        next_state_values = torch.zeros(BATCH_SIZE)
        
        # 次の状態があるindexの最大Q値を求める
        # 出力にアクセスし、max(1)で列方向の最大値の[値、index]を求める
        # そしてそのQ値を取り出します
        self.target_net.eval()
        next_state_values[non_final_mask] = self.target_net(
            non_final_next_states).max(1)[0].detach()
        
        # 3.4 教師となるQ(s_t, a_t)を求める
        expected_state_action_values = (next_state_values * GAMMA) + reward_batch
        
        # ネットワークを訓練モードに切り替える
        self.model.train()
        
        # 損失関数を計算する (smooth_l1_lossはHuberloss)
        # expected_state_action_valuesは
        # sizeが[minbatch]になっているから、unsqueezeで[minbatch x 1]へ
        loss = F.smooth_l1_loss(state_action_values, expected_state_action_values.unsqueeze(1))
        
        # 結合パラメータを更新する
        self.optimizer.zero_grad() # 勾配をリセット
        loss.backward() # バックプロパゲーションを計算
        self.optimizer.step() # 結合パラメータを更新
    
    def update_target_model(self):
        # モデルの重みをtarget_networkにコピー
        self.target_net.load_state_dict(self.model.state_dict())
    
    def decide_action(self, state, episode):
        '''現在の状態に応じて、行動を決定する'''
        epsilon = 0.41 * (1 / (episode + 1))
        
        if epsilon <= np.random.uniform(0, 1):
            self.model.eval()
            with torch.no_grad():
                action = self.model(state).max(1)[1].view(1, 1)
            # ネットワークの出力の最大値のindexを取り出す = max(1)[1]
            # .view(1, 1)は[torch.LongTensor of size 1] を size 1x1 に変換する
        
        else:
            # 0, 1の行動をランダムに返す
            action = torch.LongTensor(
                    [[random.randrange(self.num_actions)]])
            # actionは[torch.LongTensor of size 1x1]の形になる
        
        return action
    
    def brain_predict(self, state):
        self.model.eval() # ネットワークを推論モードに切り替える
        with torch.no_grad():
            action = self.model(state).max(1)[1].view(1, 1)
        return action

力尽きたので、コード載せます。基本はコードにコメント書いてるので、理解の助けになると思います。

時間があるときに追記していきます。

Agent Class

ゲームをプレイするエージェントくんのクラス(例えるならばAくん)

class Agent:
    def __init__(self, num_states, num_actions):
        '''課題の状態と行動の数を設定する'''
        self.brain = Brain(num_states, num_actions)
        # エージェントが行動を決定するための頭脳を生成
        
    def update_q_network(self):
        '''Q関数を更新する'''
        self.brain.replay()
        
    def update_target_model(self):
        self.brain.update_target_model()
        
    def get_action(self, state, episode):
        '''行動を決定する'''
        action = self.brain.decide_action(state, episode)
        return action
    
    def memorize(self, state, action, state_next, reward):
        '''memoryオブジェクトに、state, action, state_next, rewardの内容を保存する'''
        self.brain.memory.push(state, action, state_next, reward)
    
    def predict_action(self, state):
        action = self.brain.brain_predict(state)
        return action

Environment Class

実際のトレーニングループを記述しています

class Environment:
    
    def __init__(self):
        self.game = FlappyBird()
        self.env = PLE(self.game, fps=30, display_screen=False)
        self.num_states = len(self.game.getGameState())  # 8
        self.num_actions = len(self.env.getActionSet()) # 1
        self.agent = Agent(self.num_states, self.num_actions)
    
    def run(self):
        '''実行'''
        episode_10_list = np.zeros(10) # 10試行分の成功したstep数を格納し、平均ステップ数を出力に利用
        episode_final = False # 最後の試行フラグ
        reward_per_epoch = []
        lifetime_per_epoch = []
        
        for episode in range(NUM_EPISODES): # 試行回数分繰り返す
            
            self.env.reset_game() # 環境の初期化
            observation = self.game.getGameState() # 観測をそのまま状態sとして使用
            state = observation
            state = np.array(list(self.get_relative_state(state)))
            state = torch.from_numpy(state).type(torch.FloatTensor) # numpy変数をPyTorchのテンソルに変換
            # FloatTensor size 4 を size 1x4に変換
            state = torch.unsqueeze(state, 0)
            
            # record frame
            frames = [self.env.getScreenRGB()]
            
            cum_reward = 0  # このエピソードにおける累積報酬の和
            t = 0 #  time-step数
            step = 0 # episode数
            
            if episode % 15 == 0:
                self.agent.update_target_model()
            
            
            while not self.env.game_over():
                step += 1
                
                action = self.agent.get_action(state, episode) # 行動を求める
                # 出力されたactionをゲームに反映し、返り値に報酬を得る
                rew = self.env.act(self.env.getActionSet()[action])
                t += 1
                observation_next = self.game.getGameState() 
                done = self.game.game_over()
                
                frames.append(self.env.getScreenRGB())
                
                # 報酬を与える。さらにepisodeの終了評価と、state_nextを設定する
                if done:  # ステップ数が200経過するか、一定角度以上傾くとdoneはtrueになる
                    state_next = None  # 次の状態はないので、Noneを格納

                    # 直近10episodeの立てたstep数リストに追加
                    episode_10_list = np.hstack(
                        (episode_10_list[1:], step + 1))
                    
                    # 罰則を与える
                    reward = torch.FloatTensor([-1.0])
                    
                else:
                    if rew > 0:
                        reward = torch.FloatTensor([1.0])
                    else:
                        reward = torch.FloatTensor([0.0])  # 普段は報酬0
                    
                    state_next = observation_next  # 観測をそのまま状態とする
                    state_next = np.array(list(self.get_relative_state(state_next)))
                    state_next = torch.from_numpy(state_next).type(
                        torch.FloatTensor)  # numpy変数をPyTorchのテンソルに変換
                    state_next = torch.unsqueeze(state_next, 0)  # size 4をsize 1x4に変換
                    
                # 1 time-stepにおける報酬和
                cum_reward += rew
                
                # メモリに経験を追加
                self.agent.memorize(state, action, state_next, reward)

                # Q-networkを更新する
                self.agent.update_q_network()

                # 観測の更新
                state = state_next
                
                # 終了時の処理
                if done:
                    print('%d Episode: Finished after %d steps:10試行の平均step数 = %.1lf' % (
                        episode, step + 1, episode_10_list.mean()))
                    reward_per_epoch.append(cum_reward)
                    lifetime_per_epoch.append(step+1)
                    break
                    
            if episode_final is True:
                # 動画の保存と描画
                display_frames_as_gif(frames)
                break
                    
            # 50エピソード毎にlogを出力
            if episode % PRINT_EVERY_EPISODE == 0:
                print("Episode %d finished after %f time steps" % (episode, t))
                print("cumulated reward: %f" % cum_reward)
                

            # 100エピソード毎にアニメーションを作成
            if episode % SHOW_GIF_EVERY_EPISODE == 0:
                print("len frames:", len(frames))
                display_frames_as_gif(frames)
                continue
            
            # 2000タイムステップ以上続いたアニメーションを作成
            if step > 2000:
                print("len frames:", len(frames))
                display_frames_as_gif(frames)
                
        # グラフの作成
        #make_graph(reward_per_epoch, lifetime_per_epoch)
    
    bucket_range_per_feature = {
        'next_next_pipe_bottom_y': 40,
        'next_next_pipe_dist_to_player': 512,
        'next_next_pipe_top_y': 40,
        'next_pipe_bottom_y': 20,
        'next_pipe_dist_to_player': 20,
        'next_pipe_top_y': 20,
        'player_vel': 4,
        'player_y': 16
    }
    
    def get_relative_state(self, state):
        # パイプの絶対位置の代わりに相対位置を使用する
        state = copy.deepcopy(state)
        state['next_next_pipe_bottom_y'] -= state['player_y']
        state['next_next_pipe_top_y'] -= state['player_y']
        state['next_pipe_bottom_y'] -= state['player_y']
        state['next_pipe_top_y'] -= state['player_y']

        # アルファベット順に並び替える
        state_key = [k for k, v in sorted(state.items())]

        # 相対位置を返す
        state_idx = []
        for key in state_key:
            state_idx.append(int(state[key] / self.bucket_range_per_feature[key]))
        return tuple(state_idx)

    
    # モデルの保存
    def save_model():
        torch.save(agent.brain.model.state_dict(), 'weight.pth')
        

main Class

# mainクラス
flappy_env = Environment()
flappy_env.run()
torch.save(flappy_env.agent.brain.model.state_dict(), 'weight.pth') #モデルの保存

おまけ

学習グラフの出力

Environmentクラスのグラフを出力する部分のコメントを外してね

def make_graph(reward_per_epoch, lifetime_per_epoch):
    fig, (axL, axR) = plt.subplots(ncols=2, figsize=(10,4))
    axL.set_title('lifetime')
    axL.grid(True)
    axL.plot(lifetime_per_epoch, color='magenta')
    axR.set_title('reward')
    axR.grid(True)
    axR.plot(reward_per_epoch , color='magenta')
    fig.show()

総評

疲れた、時間足りない。 文章とソースコードだけの説明には限界がある。 技術書わかりにくいって文句言ってたけど、そりゃそうだ、説明のしようがないね。

あと、今日誕生日なので誕生日プレゼント恵んでください。 欲しいものリスト

明日は mizukmbさんです。

勉強する意味。

注意

このブログは個人の考え・意見です。 発言に一切の責任は負いません。
異論、あなたの考え、大歓迎です。
思うことがあれば是非教えてほしいです。

時間がない人向け

勉強する意味がわからない人は、勉強しなくてもいいから色んなことに興味を持って欲しい。 興味を増やすにはテレビのニュースを見るのが一番だと思っています。作業をしながらでも話半分でいいからテレビのニュースを見てみましょう。「ん?どうゆうこと?」と思ったことをスマホやパソコンを使って深く深く掘り下げてみてください。勉強なんてしなくてもそれで十分です。そこから知識や学びは広がっていきます。

勉強する意味がわからない学生

先日電車に揺られていると、電車の中で中学生くらいの風貌の少年たちから勉強する意味がわからないといった言葉が聞こえてきました。少し気になり聞き耳を立てていると、数学や英語が将来何の役に立つのかといった内容でした。

身に覚えがある

多くの人は同じように考えたことがあるんではないでしょうか?少なくとも私は同じように考えた記憶があります。というか、自分は学校の教員に対して言い放った記憶がありますが。そして大人になればわかるよと言われました。

大人になったよ

さて、今現在大学3年生となり学生ではありますが、世間的には大人として扱っていただくことが多い社会的立場になりましたが自分には未だに勉強する意味はわかりません。どうやら未だにあの時の先生のおっしゃった大人にはなれていないようです。勉強する意味は未だにわかりません。でも、多少は成長し大学に入学し大人に近い思考をもった一人の人間として勉強する意味を考えてみようと思う。

勉強する意味を考えてみる

そこで今までの経験の中で簡単に思いつく、勉強をしていて実生活において役立ったことを考えてみる。

  • 友達と行ったご飯での割り勘の計算(割り算の計算)
  • 消費税の計算(掛け算)
  • 水素水なんて物を買わずに済む(理科)

パッと思いついたのはこんな物だ。ここから考えるに国語や社会、英語は役に立たないから勉強しなくてもいいのではないかという結論に至ってしまいそうだ。

理数系だけ勉強すればいい?

決してそんなことはない。別に割り勘や消費税なんて自分で計算しなくても店員さんやレジが計算してくれるし、水素水なんて高級なお水を買うことで日本経済を回す役に立っているのだ。別に勉強する必要なんてないような気がしてくる。

勉強しなくてもいいんじゃね

ここまで長くなったが私の考えとしては、生きていくだけなら勉強なんてしなくてもいいと思う(もちろん賛否両論あると思う。ぜひ思ったことをコメントしてくれると嬉しい。)。これを見て「じゃあ勉強しない」なんて言わずに、どうせここまで読んだのだから最後まで読んで欲しい。

なぜか

自分は特に必要だとは思わなかったからだ。もちろん社会で生きていく以上は、一般常識やある程度のマナーは必要である。だが、たかだか掛け算ができないとか、英語が話せないとか、古文が読めないだとか、別に困らないのである。決して良い考えではないが、困っていれば誰かが助けてくれる。別に生きていくだけならば問題ないだろう。昔から人は一人では生きていけないと言う。だから助けてもらえばいいのである。

でも現実は甘くない

本当に生きるだけであればこれでもなんとか生きていけるかも知れない。だが、これだけでは満足な生活はできないだろうとだけ言っておこう。

どうしても勉強したくない

気持ちはとってもわかる。でも、勉強したくない人たちにどうしてもして欲しいことがある。それはなにかに興味を持つことだ。別に勉強なんてしなくていい、ゲーム、本、スポーツ、エッチなこと、何にだっていい、ただ何かに興味を持って欲しい。

今現在何にも興味がない

今現在何にも興味がない人もいるだろう。そんな人には是非テレビでニュースを見て欲しい。スマホのニュースアプリとかではなくテレビでニュースを見るのだ。なぜか、それは自分で見ようとしなくても勝手に情報を伝えてくれるからだ。スマホアプリでニュースを見ようと思うと自発的な行動が必要になってしまう。勉強の必要性がわからない人にはきっとスマホアプリでは続かない。別にスマホをいじりながらでもいいのだ、ぼーっとしながらでも惰性でも話半分でいいからニュースを見るのだ。

気になってしまう

話半分でもニュースを見ていると、ふとした時に「ん?どうゆうこと?」と思うことが少なからずある。これがとても重要なことなのだ。是非その時には今あなたが手にしているスマホやパソコンを使って疑問に思った言葉をそのままGoogleの検索窓に打ち込むのだ。ニュースになっていることは、大概記事にしてまとめられている。そして出てきて、わからない言葉なんかをまた調べて広げていくのだ。そしてどうしてもわからないことは友人や学校の先生親なんかに聞いて欲しい。そういった事を続けているといつの間にかあなたは知識人になっている。きっと色んな物に興味を持っているだろう。

興味のあるものだけでも十分だ

興味をもてたなら、今度はそれについて詳しくなっていくのだ。もうすでに詳しい気でいるかもしれないが案外そんなことはない。ゲームが好きだと言うならそのゲームの動く仕組みは知っているのだろうか。自分の好きなゲームの仕組みは知っていてもいいんじゃないだろうか。自分の興味のあるもの、好きなものだけでもいい、勉強するんじゃなく調べるのだ。

長々と書いたけど

ここまで長々と読みにくい文章を書いてきたが、一番言いたいことを軽くまとめる。

勉強する意味なんて僕にはわからないし、正解もないと思う。もしかしたら意味なんてないかもしれない。でも何かに興味を持って欲しい。あなたの好きなもの、興味のあるものを全力で楽しんで欲しい。好きなものや興味のあるものを否定する権利は誰にもないのだから。好きなものを好きなだけ楽しむのが一番。ぜひそこから広く知識や学びを広げて欲しい。