空飛ぶロボットのつくりかた

ロボットをつくるために必要な技術をまとめます。ロボットの未来についても考えたりします。

アルゴリズムのお勉強(3)- 動的計画法(DP) : ボトムアップ形式

f:id:robonchu:20190610003125p:plain
http://theoryofprogramming.com/2015/03/02/dynamic-programming-introduction-and-fibonacci-numbers/

やりたいこと

動的計画法とは

以下動的計画法 - Wikipediaから抜粋。

  • 細かくアルゴリズムが定義されているわけではなく、下記2条件を満たすアルゴリズムの総称である。

    1. 帰納的な関係の利用:より小さな問題例の解や計算結果を帰納的な関係を利用してより大きな問題例を解くのに使用する。

    2. 計算結果の記録:小さな問題例、計算結果から記録し、同じ計算を何度も行うことを避ける。

今回着目するのは1番の方。漸化式で解ける。

ボトムアップ形式のDPを使ってとく例題

atcoder.jp

  • AtCoder Beginner Contest 129のC問題より抜粋

問題: C - Typical Stairs

f:id:robonchu:20190610005349p:plain

入力

f:id:robonchu:20190610005526p:plain

出力

f:id:robonchu:20190610005700p:plain

制約

f:id:robonchu:20190610005639p:plain

入力/出力例

入力
6 1
3
出力
4

移動方法は以下の 4通りです。

  • 0→1→2→4→5→6
  • 0→1→2→4→6
  • 0→2→4→5→6
  • 0→2→4→6

解き方

以下の漸化式を用いて壊れた床の場合はスキップすることで解ける。

f:id:robonchu:20190610010827p:plain

  • x段目にたどり着く方法はx-1から一歩で上がるか、x-2から二歩であがるかの二通りだから。

ボトムアップ形式のDPを使った実装例(C++)

f:id:robonchu:20190610010157p:plain

#include <iostream>
#include <vector>

using namespace std;
const long long mod=1e9+7;
 
int main(){
  int N, M;
  cin >> N >> M;
 
  //  0段目があるので、N+1の配列を用意し、
  //  壊れていない階段をtrue=1、壊れている階段をfalse=0で初期化。
  //  これを使ってスキップの処理を行う。
  vector<int> oks(N + 1, true);
  for(int i = 0; i < M; ++i) {
    int a;
    cin >> a;
    oks[a] = false;
  }

  //  0~N段目までの"通り数"を入れる配列
  vector<long long int> dp(N + 1);

  //  0段目を1で初期化
  dp[0] = 1;
 
  for(int now = 0; now < N; ++now){
    //  ここがポイント。
    //  階段が壊れていなければ、ここのfor loopはi = N - 1 を除いて二回、i = N - 1のときは一回実行される。
    //  この二回の実行の繰り返しによって上記の漸化式が実装でされることになる。
    //  以下に簡単なイメージ図を添付。  
    for(int next = now + 1; next <= min(N, now + 2); ++next){
      //  最初に作ったoks配列を用いることで壊れている床をスキップする。
      if (oks[next]) {
        //  漸化式。
        dp[next] += dp[now];

        //  期待される出力のためにmodで割る。
        dp[next] %= mod;
      }
     }
   }

   // 求めたいN番目の通り数を出力。
   cout << dp[N] << endl;
   return 0;
}

f:id:robonchu:20190610015946p:plain

内容まとめ

  • 漸化式を使って解く。

  • 漸化式の実装にコツがいるが、基本的には問題に対して正しく漸化式が作れれば解ける。

  • 実装する前に上のように紙に書くなどして頭を整理すると良い。

感想

DPが使いこなせるようになれば、解ける問題が一気に増えるイメージ。

次はトップダウンも扱いたい。簡潔ないい問題ないかな。

アルゴリズムのお勉強(2)- 深さ優先探索(DFS)

f:id:robonchu:20190520021125p:plain
Wikipedia - 深さ優先探索

やりたいこと

深さ優先探索とは

上図のように繋がっているノードを上から下へ順々に探索。再帰関数かスタックで解ける。

詳細は以下参照。

深さ優先探索を用いて解く例題

問題: D - Even Relation

f:id:robonchu:20190520021705p:plain

入力

f:id:robonchu:20190520021747p:plain

出力

f:id:robonchu:20190520022010p:plain

制約

f:id:robonchu:20190520021905p:plain

入力/出力例

入力
3
1 2 2
2 3 1
出力
0
0
1
深さ優先探索を用いた実装例(C++)
再帰関数を用いた実装
#include <iostream>
#include <vector>
 
using namespace std;
const int MAX_N = 100001;

// 対象のノード番号の要素にそれに隣接するノード名とその距離のペアが入る
vector<std::pair<int, int>> vm[MAX_N];
int ans[MAX_N];

// 第1引数に対象ノード番号を入れる
// 第2引数はすでにチェックした番号を示す
void DFS(int target, int checked){
  for(int i=0; i < vm[target].size(); ++i){
    int nn = vm[target][i].first;  // 隣接ノード番号を格納
    if(nn == checked) continue;  //チェックしたものの場合は飛ばす
    
    if(vm[target][i].second % 2) {
      // 2で割り切れない場合、白黒を入れ替える
      if(ans[target]) ans[nn] = 0;
      else ans[nn] = 1;
    }else{
      ans[nn] = ans[target];
    }
    DFS(nn, target);
  }
}

 
int main(){
  int n;
  cin >> n;

  int u, v, w;
  for(int i=0; i<n-1; ++i){
    cin >> u >> v >> w;
    // u,vの同じノード番号の要素に値を追加。
    vm[u].push_back(std::make_pair(v,w));
    vm[v].push_back(std::make_pair(u,w));
  }
 
  // 1から順に探索を始める。1は最初に探索するので、第2引数にもセット。
  DFS(1, 1);

  for(int i=1; i<=n; ++i){
    cout << ans[i] << endl;
  }
  return 0;
}

内容まとめ

  • 深さ優先探索は上記例のように繋がっているノードを上から下へ順に調べていく手法。

  • 実装時には再帰関数かスタックを用いる。

感想

アルゴリズムは実装ポイントを知っていると、実装時間がだいぶ短縮できる。

それぞれのアルゴリズムの内容理解に加えて実装ポイントを抑えていこう。

実機を使ってマニピュレーション技術を学ぼう(1)- 環境セットアップ編

f:id:robonchu:20190512104005p:plain

やりたいこと

  • ロボットのコア技術であるマニピュレーション技術(主に認識とプランニング)を安価な実機を動かしながら学んでいく。

  • タスクとしては、様々な対象物を認識して、物を把持したり、置いたりする。

仕様実機

想定OS/ミドルウェア

  • Ubuntu16.04

  • ROS Kinetic

    • ROS2も触り始めたい...

今回のテーマ

  • ロボットアームBraccioの環境設定

今回必要なもの

Arduinoのセッティング

以下を参照。とてもわかり易いです。ありがとうございます!

Braccioのセッティング

ソフト

  1. GitHub - arduino-org/arduino-library-braccio: Arduino Braccio LibraryからGit clone

  2. cloneした内容を~/arduino-1.8.9/libraries/にコピー

  3. arduinoを起動

  4. 左上のFile->Examples->Braccio->testBraccio90を選択し書き込み

ハード

  1. 説明書を見ながら頑張って組み立てる笑。以下動画がおすすめ。とても簡単。

アライメント

  1. BraccioのシールドをUnoに挿して電源接続。

  2. Unoのリセットボタンを押すと先程のtestBraccio90が走る。理想状態は以下の写真。

    f:id:robonchu:20190512104641p:plain

  3. ズレていたら以下のように最終行を書き換えて修正し、初期値のズレを計測(メモ)しておく。

 /*
  testBraccio90.ino

 testBraccio90 is a setup sketch to check the alignment of all the servo motors
 This is the first sketch you need to run on Braccio
 When you start this sketch Braccio will be positioned perpendicular to the base
 If you can't see the Braccio in this exact position you need to reallign the servo motors position

 Created on 18 Nov 2015
 by Andrea Martino

 This example is in the public domain.
 */

#include <Braccio.h>
#include <Servo.h>


Servo base;
Servo shoulder;
Servo elbow;
Servo wrist_rot;
Servo wrist_ver;
Servo gripper;

void setup() {  
  //Initialization functions and set up the initial position for Braccio
  //All the servo motors will be positioned in the "safety" position:
  //Base (M1):90 degrees
  //Shoulder (M2): 45 degrees
  //Elbow (M3): 180 degrees
  //Wrist vertical (M4): 180 degrees
  //Wrist rotation (M5): 90 degrees
  //gripper (M6): 10 degrees
  Braccio.begin();
}

void loop() {
  /*
   Step Delay: a milliseconds delay between the movement of each servo.  Allowed values from 10 to 30 msec.
   M1=base degrees. Allowed values from 0 to 180 degrees
   M2=shoulder degrees. Allowed values from 15 to 165 degrees
   M3=elbow degrees. Allowed values from 0 to 180 degrees
   M4=wrist vertical degrees. Allowed values from 0 to 180 degrees
   M5=wrist rotation degrees. Allowed values from 0 to 180 degrees
   M6=gripper degrees. Allowed values from 10 to 73 degrees. 10: the toungue is open, 73: the gripper is closed.
  */
  
  // the arm is aligned upwards  and the gripper is closed
                     //(step delay, M1, M2, M3, M4, M5, M6);
  // Braccio.ServoMovement(20,         95, 90, 90, 90, 90,  73);  // ここを以下のように書き換え
  Braccio.ServoMovement(20,         95, 95, 98, 95, 86,  73);  
  
     
}

以上で簡単なBraccioのセットアップは完了。

テスト動作

  1. exampleプログラムのtakethespongeを書き込み

    • arduino editorを開いて左上のFile->Examples->Braccio->takethespongeを選択
  2. 実行すると、こんな感じ↓

  • 色はスプレーで塗装してます

次回

  • ROS x MoveIt! でモーションプランをする予定。

f:id:robonchu:20190512110151p:plain

所感

$200+αで家でいろんな実験ができるのはいい感じ。きれいに色塗るの難かしい...

最終的に机の上を自律で片付けるくらいまでまとめれたらいいな。

アルゴリズムのお勉強(1)- 幅優先探索(BFS)

f:id:robonchu:20190505092638p:plain
幅優先探索-Wikipedia

やりたいこと

幅優先探索とは

幅優先探索実装時に用いるキューの使い方(C++)

幅優先探索を用いて解く例題

問題: A - Darker and Darker

f:id:robonchu:20190505095511p:plain

入力

f:id:robonchu:20190505095607p:plain

制約

1≦H,W≦1000

入力/出力例

入力
3 3
...
.#.
...
出力
2
幅優先探索を用いた実装例(C++)
#include <iostream>
#include <queue>

#define P pair<int,int>
using namespace std;

// マスの縦横
int H,W;
// #,.を格納するマス
char A[1000][1000];

// 幅優先探索の深さを格納するマス
int am[1000][1000];
// 隣り合う辺への移動量
int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0, -1, 0};
// 答え
int ans = 0;
 
void BFS(){
  // 入力を受取り、キューに位置x,yを格納
  cin >> H >> W;
  queue<P> que;
  for (int i=0;i<H;i++){
    for (int j=0;j<W;j++){
      cin >> A[i][j];
      if (A[i][j]=='#'){
        am[i][j] = 0;
        que.push(P(i,j));
      }
    }
  }
  // キューを前から取り出し、隣り合う辺が.か#か調べ.なら#に更新し、深さを1足す
  while (!que.empty()){
    P p = que.front();
    que.pop();
    for (int i=0;i<4;i++){
      int nx = p.first + dx[i];
      int ny = p.second + dy[i];
      if (nx>=0 && nx<H && ny>=0 && ny<W && A[nx][ny]!='#'){
        A[nx][ny] = '#';
        am[nx][ny] = am[p.first][p.second]+1;
        que.push(P(nx,ny));
      }
    }
  }
}
 
int main(){
  BFS();
  // 答えとなる最も深いものを取り出す
  for (int i=0;i<H;i++){
    for (int j=0;j<W;j++){
      ans = max(ans, am[i][j]);
    }
  }
  cout << ans << endl;
  return 0;
}

内容まとめ

  • 幅優先探索は上記例のように始点から近い順に網羅的に調べていく手法。

  • 実装時にはqueueを使う。

感想

アルゴリズムの勉強楽しい。問題も解き方が多数あるから頭の体操になって良い。

AtcoderのC,D問題が解けるくらいのアルゴリズムを少しずつまとめていきたいな : )

Sim2Real論文まとめ(2) - PixelDomainAdaptation

f:id:robonchu:20190303135127p:plain

やりたいこと

実データを取得するのが大変なので、限られたデータからDomain Adaptationがしたい。

そのために有用そうなUnsupervised Pixel-Level Domain Adaptation with GANを理解する。

論文について

arxiv.org

  • presented at CVPR 2017

  • Google Brainの論文

評価

☆☆☆☆(4/5)

ロス関数の作り方やGANを安定させるための工夫が参考になる。

内容まとめ

Abstract

  • 品質のいいアノテーション付きのデータセットを作るのはとても大変

  • 解決策として、人工的にデータによる自動アノテーションすることが考えられるが、生成モデルが現実世界に適合できないことも多い。

  • 本論文では、画像空間においてあるドメインから対象とするドメインに適応させる教師なしのGANのアプローチを提案する。

Introduction

  • ImageNetなどのアノテーション付き大規模データセットは認識技術の向上に大きく貢献した。

  • 一方でデータの作成がとても大変だという課題がある。

  • ここで、人口的に作成した画像で代替できればよいが、それだけで訓練したモデルでは実画像に対して適用できないことが多い。

  • ひとつの解決策として、教師なしのDomain Adaptationがある。

  • つまり、ラベル付けされたあるDomainからラベル付けされていない対象Domainへ学んだ知識を転移させたい。

  • 本論文では元の特徴量を保持しつつ、あるDomainの画像をまるで対象のDomainであるように見映を変化させるようなモデルを提案する。

  • Pixelレベルでの教師なしのDomain Adaptationであり、PixelDAと呼ぶ。既存技術に対し様々な利点がある。

f:id:robonchu:20190302232505p:plain

上図からわかるように対象Domainに近い見映の画像が生成さえれている。

以下に簡単に提案手法の特徴を述べる。

Decoupling from the Task-Specific Architecture
  • 一般的なDomain AdaptationではDomain Adaptationのプロセスとタスク(Classification or Detection etc ..)特有の構造が絡み合っている。

  • これに対してPixelDAではDomain Adaptationの構成を再学習することなしに、タスク特有の構造を変えることができる。

Generalization Across Label Spaces
  • PixelDAはタスク固有部分とdomain adaptation部を非干渉化できているので、訓練時とテスト時で異なるlabel spaceを扱うことができる
Training Stability
  • GANを利用したDomain Adaptationは初期状態に影響を受けやすい。

  • この対策として、ソース画像と生成画像を用いた損失関数による訓練とピクセル類似度による正則化によって安定性を向上させる。(詳細後述)

Data Augumentation
  • これまでの一般的なDomain Adaptationdeは有限のソースとターゲットデータからの学習に限られる。

  • 提案モデルはソースデータと確率ノイズベクターによる条件付けによって、制限なく仮想的にターゲットDomainから似た画像を生成することができる。

Model

  • PixelDAは汎用的に使用可能だが、説明のため画像の classificationを例に説明を行う。

  • この論文のゴールは、ソースDomainからターゲットDomainに対して汎化的なclassifierを訓練することである。

  • 提案モデルはタスク固有のclassificationからdomain adaptationのプロセスが分離されているので、1度adaptするとdomain adaptationすることなしに他のclassificationタスクへ適用(訓練)することができる。

  • 私たちはDomain間の差異はhigh-level(対象物のタイプや幾何的な変化etc...)というよりlow-level(ノイズや色、彩度etc...)な情報による影響が大きいと考えていることを強調したい。

  • 定式化すると

    f:id:robonchu:20190303135234p:plain

    はソースのデータセットを表現し、 xsはソース画像、ysはソースラベル。

    f:id:robonchu:20190303135259p:plain

    はGenerator functionを表し、θがパラメータでソース画像とnoise vectorであるzからfake imageであるxfを出力する。

    Generator function Gが求まると、以下のようにadaptされた新しいデータセットをつくることができる。

    f:id:robonchu:20190303135334p:plain

Learning
  • GをターゲットDomainに近い画像を生成できるようGANを用いる。

  • ソース画像xsとnoise vector zからadaptされた画像xfを生成するGを訓練する。

  • モデルは、ターゲットDomainからサンプルされた画像かどうかの尤度dを出力するdiscriminator func Dによって補強される。

  • Discriminatorはgeneratorから作られたfake画像かターゲットDomainからとってきたreal画像かを見分けようとする関数である。

  • ここで大事なことが、一般的なGANはnoise vectorのみに制限付けられているのに対して、提案モデルはnoise vectorとソースDomainからの画像の両方に制限付けられている、ということである。

  • なので、ゴールは以下のmin-max gameを最適化することである。

    f:id:robonchu:20190303135357p:plain

    αとβは重みで、LdはターゲットDomainに近づけるためにloss funcでLtはタスクをこなすためのloss funcである。

  • Ldは以下のようにかける。DとGのmin-max game。

    f:id:robonchu:20190303135413p:plain

  • Ltは以下のようにかける。classificationの場合の典型的なsoftmax cross-entropy lossを用いている。

    f:id:robonchu:20190303135437p:plain

    ここで、Gで生成した画像とソース画像の両方を用いてTを訓練しているのは訓練を安定させるため。これによって、初期値依存性が減り安定性が大きく向上した

    f:id:robonchu:20190303123809p:plain

    実装したモデルは上図参照。min-max gameでの最適化時は2つのステップに分かれている。

    1. generatorのθGを固定した状態で、discriminator とタスク特徴のパラメータθDとθTを更新する。

    2. パラメータθDとθTを固定した状態で、θGを更新する。

Content-similarity loss
  • low-levelの画像のadaptationについて、前景と後景に対して処理をするのが良いという知見がある。

  • そこでラベル付けされた情報の類似性を保つために前景と後景を切り分けるz-buffer maskを用いて工夫を行う。

  • その工夫とは前景のpixelのみソース画像とgenerate画像の間の大きな差異にペナルティを与えるlossを新たに追加するというものである。

  • これによって、min-max gameの最適化がより安定する。

  • まとめると以下のようにloss関数を表現できる。

    f:id:robonchu:20190303135508p:plain

    αとβとγは重みで、Lcは類似度を一定に保つようなcontent-similarity lossである。

  • このloss func Lcinputとoutputの全体の差ではなく、pixelごとの差に対してpenaltyを与えるmasked pairwise mean squared error(masked-PMSE)を用いる。以下のように表現でき、generateされた前景画像とソース前景画像に対して、計算される。

    f:id:robonchu:20190303135532p:plain

    mはbinary maskでkはkはinput xのpixle数でL2ノルㇺをとっている。

  • これによって対象物の全体の形状など再現するための情報を保つことができる。

Evaluation

ここからは画像ベースでPixelDAを使った例を貼り付けます。

MNISTの例

f:id:robonchu:20190303135554p:plain

Linemodeの例

f:id:robonchu:20190303135619p:plain

ラベリング情報は保ちつつlow-levelな情報が様々変化している画像が生成されていることがわかる。

所感

loss関数をうまく設計することで変化させたい情報とさせたくない情報を切り分けれるのは面白い。

Domain Adaptationにおいてlow-levelの情報に着目しているのは納得。

GANを安定化させるための共有要素/普遍的な方法を検討していきたい。

C++のお勉強(3) - ポインタ/スマートポインタ

f:id:robonchu:20190303150234p:plain:w100

やりたいこと

ポインタの考え方(特にC++11)を理解し、使いこなせるようにする

ブラウザでのコード実行方法

C++ Shell がおススメ

ポインタ

型* 変数名 = オブジェクトのアドレス

  • &は変数のアドレスの取得

  • *は指定したアドレスのデータにアクセス

例1 : アドレスを利用した値の書き換え

#include <iostream>
#include <string>
using namespace std;

int main (){

  string name = "doraemon";
  string *name_address;
  name_address = &name;
  *name_address  = "dramichan";
  cout << name << endl;  // dramichan
  cout << *name_address << endl;  // dramichan
  return 0;
}

例2 : ポインタのポインタ

#include <iostream>
using namespace std;

int main(){
  string name = "atom";
  string* name_address = &name;
  string** name_address_adress = &name_address;
  *name_address = "uran";  // uran
  cout << name <<endl;  // uran
  cout << **name_address_adress <<endl;
}

スマートポインタ

スマートポインタで管理されるオブジェクトは明示的にdeleteしなくても、使われなくなったら自動的に破棄される

unique_ptr

あるメモリの所有権を持つ unique_ptrはただ一つのみ

使い方
  • #include で使える。

  • ポインタの保持するメモリにアクセスするには、operator*()や operator->()が使用可能。

  • unique_ptrは、コピーは禁止だが、ムーブは可能。

  • deleterの指定可能。

ポイント

shared_ptr

同一のメモリの所有権を複数で共有できるようにしたスマートポインタが、shared_ptr

使い方
  • #include で使える。

  • ポインタの保持するメモリにアクセスするには、operator*()や operator->()が使用可能。

  • コピーもムーブも可能。

  • deleterの指定可能。

ポイント
  • shared_ptrの初期化にmake_sharedを使うこと

  • unique_pointに比べて遅い

  • 循環参照に気をつけて!以下のような状況を指す。

#include <iostream>
#include <memory>

class Robot
{
public:
    Robot() {}
    ~Robot() {}
    
    std::shared_ptr<Robot> hand_ptr;
};

int main()
{

    std::shared_ptr<Robot> r1 = std::make_shared<Robot>();
    std::shared_ptr<Robot> r2 = std::make_shared<Robot>();

    r1->hand_ptr = r2;
    r2->hand_ptr = r1;

    return 0;
} // pointaが解放されない!
  • 循環参照はstd::weak_ptrで解決できる

weak_ptr

循環参照によって生じる問題を防ぐために導入されたスマートポインタで所有権をもたない

使い方例
#include <iostream>
#include <memory>

class Robot
{
public:
    Robot() {}
    ~Robot() {}
    
    std::weak_ptr<Robot> hand_ptr;
};

int main()
{
    std::shared_ptr<Robot> r1 = std::make_shared<Robot>();
    {
        std::shared_ptr<Robot> r2 = std::make_shared<Robot>();

        r1->hand_ptr = r2;
        r2->hand_ptr = r1;
    }  // ここでr2が解放
    return 0;
} // ここでr1が解放
ポイント
  • ダグリングポインタ(無効なメモリ領域を指すポインタ)に気をつけること!以下に例を示す。
std::shared_ptr<Robot> robot = std::make_shared<Robot>();
std::weak_ptr<Robot> weak_robot = robot;

// この間に、robotの寿命が切れるとweak_robotはダグリングポインタになってしまう
  • ここで監視しているshared_ptrオブジェクトを取得するlock関数を用いることでダグリングポインタの問題に対処できる。例えば以下のようにして不正アクセスを防ぐことができる。
if (auto r = weak_robot.lock()) {
  // こうすることで不正アクセスを防いだうえで*r に対して処理をかける
}

参考 : weak_ptr::lock - cpprefjp C++日本語リファレンス

使い分け

使用頻度としてはunique_ptr > shared_ptr > weak_ptrが理想的

  1. まず unique_ptrの利用を検討する。

  2. 複数で共有する必要のある場合、shared_ptrを利用する。

  3. 循環参照になっていないか確認し、なっている場合weak_ptrを利用する。

所感

次は右辺値・左辺値・ムーブについて理解したい。

全体参考

Sim2Real論文まとめ(1) - SimGAN

f:id:robonchu:20190225163721p:plain

やりたいこと

センサノイズをシミュレータで再現したい。

そのために有用そうなSimGANを理解する。

論文について

arxiv.org

  • CVPR2017でBest Award

  • 珍しいAppleの論文

評価 

☆☆☆☆☆(5/5)

めちゃくちゃコスパ良いアプローチ。素晴らしい。

内容まとめ

Abstract

  • 教師あり学習の難点として、たくさんのラベル付きデータが必要になる。

  • シミュレータでデータを作ることが期待されているが、シミュレータと実世界の乖離が問題で上手く行かないことが多い。

  • 本論文はその溝を埋める手法を提案する。

  • 方法としてはラベル付きのシミュレーションのデータとラベルなしの実データを用いて、Genertive Adversarial Networks(GANs)によりシミュレーションデータを実データに近づける(S+U Learning with SimGAN)

  • 以下の三点がキーポイント。

    1. a "self-regularization" term

    2. a local adversarial loss

    3. updating the discriminator using history of refined images

    これらによってリアリスティックな画像を生成することができる。

Introduction

  • シミュレータをリアルに近づけるアプローチも盛んだが、良いレンダリングアルゴリズムのものを使ったとして、実世界を完全に再現できているわけでなく、これを用いて学習した場合、過学習することもある。

  • この論文の目標はラベリングなしの実データとシミュレータを用いてリアルな画像を生成すること。

  • そのために、refinerというネットワークを考案した。下図参照。

f:id:robonchu:20190225163721p:plain

  • これはシミュレータが作った生成画像をrefineする。

  • 実データに近づけるため、refinerはadversarial lossを用いて訓練する。

  • これはGANsに似たアプローチをとっていて、実データとの区別がつかないように更新するdiscriminative networkを用いている。

  • ここで、生成データのアノテーション情報を保つため、生成データとrefineされたデータの差にペナルティを与えるようなself-regularization lossをadversarial lossに追加している

  • そして、ピクセルレベルでのfully CNNを用いることで、大域的な構造を保つ

  • GANsのアプローチは対立する2つのネットワークを訓練するため、不安定になりがち。これに対して、discriminatorの受容野を画像全体ではなく複数の局所的な領域に制限する

  • さらに安定に訓練を行うため、過去のrefineされた画像を用いてdiscriminatorの更新を行う

Contributions

  1. ラベルなしの実データを用いて合成データをrefineする(S+U learning)

  2. adversarial lossとself-regularization lossを用いてrefiner networkを訓練する

  3. refiner networkを安定して訓練するためのGANsに対するいくつかの大事な修正を行う

  4. 定性的な実験結果とSOTAな結果を示す

S+U Learning with SimGAN

  • ゴールはアノテーション情報を保持しつつ生成画像をリアルに近づけるrefinerを訓練すること。

  • 以下の2つの損失関数によって、関数パラメータθを学習によって最適化する。

f:id:robonchu:20190225170151p:plain

  • 第一項は実データに近づける作用、第二項はアノテーション情報を保つ作用がある。

  • 詳細はこれから説明。

Adversarial Loss with Self-Regularization
  • GANsに似ている提案手法は同様に2つのネットワークのminmax gameであり、refiner networkとdiscriminator nerworkを交互に更新する。
Discriminator
  • discriminator networkは以下の損失関数を最小化することによってΦを更新する。

f:id:robonchu:20190225170433p:plain

  • この式は交差エントロピー誤差で~xiが生成データ、yiが実データである。

  • Dはrefineされた画像である確率を出力する層を最終層に持つConvNetとして実装。つまり、refineであれば1、実データであれば0が理想。

  • 確率的勾配降下法によって更新する。

Refiner

f:id:robonchu:20190225170151p:plain

  • (1)式のlrealは訓練済みのdiscriminatorを用いて以下のようにかける。

f:id:robonchu:20190225171437p:plain

  • このロス関数が小さくできれば、discriminatorをより騙すようなrefinerに近づく。

  • さらに、生成されたリアルな画像はシミュレータで付与されたアノテーション情報も保ってほしい

  • これを実現するため、生成データとrefineされたデータのピクセルレベルでの違いを小さくするようself-regularization lossを用いる。

  • そうすると、(1)の式は以下にような表現になる。こおkでψは恒等関数。||はL1ノルム。

f:id:robonchu:20190225172350p:plain

  • ここでRはピクセルレベルでrefineするためプーリングなどがないfully convolutinal neural netで実装。
更新則
  • 以下の通りでR(θ)を更新する間はΦを固定、D(Φ)を更新する間はθを固定。

  • 交互に繰り返してLr(θ)とLd(Φ)を最小化する。

f:id:robonchu:20190225172824p:plain

Local Adversarial Loss
  • refinerがとても強いdiscriminatorを訓練した時、refinerが過度に特徴を強調する方向へ働くことがある。

  • ここで、局所的な状態もrefinerはリアルに近づけるべきで、同様にdiscriminatorも局所的な情報で判断できるべき。

  • 上記を踏まえ下図のように discriminatorのネットワーク出力をw x hの確率マップにし、refinerのadversarial loss関数を局所パッチのcross entropy lossesで表す

f:id:robonchu:20190225174432p:plain

Updating Discriminator using a History of Refined Images
  • discriminatorが最新のrefined imagesにのみ正しく区別できる傾向に陥ることがある。

  • これは本来すべてのrefined imagesを正しく区別したいため良くない。

  • なので、discriminatorの更新時に過去のrefined imagesも使用することで、安定性を向上させる

  • 下図のようにmini-batchの半分を最新の画像、もう半分をバッファにためていた画像を用いる。

f:id:robonchu:20190225180256p:plain

以上がSimGANのロジックの部分。

Experiments

  • ここからは画像ベースでSimGANを使った向上例をペタペタ貼り付けます。かなり汎用性がありあそう。

  • 視線方向はたもちつつ、リアルな画像へrefine↓

f:id:robonchu:20190225180926p:plain

  • より詳細↓

f:id:robonchu:20190225181104p:plain

  • 手の姿勢推定のためのDepth画像への適応例↓

f:id:robonchu:20190225181213p:plain

  • 過去の画像を使ってdiscriminatorを更新したことの効果↓

f:id:robonchu:20190225181434p:plain

  • Local adversarial loss の効果↓

f:id:robonchu:20190225181617p:plain

所感

実機でデータセットを作るのはホント大変なので、生成データとアノテーションなしの実データでSimlationとRealのGapを埋めれるのは素晴らしい。

特にセンサのノイズは個体によって異なるし、表現しにくいので、このアプローチはほんとに興味深い。