Bridge over troubled Techs.

オープンストリーム CTO 寺田英雄の個人的ブログ

ディープラーニング ResNet のヒミツ

 先日、当社と共同研究をしている庄野研のゼミに参加させてもらった。その日は論文の輪講の日だった。そこでM2のSさんがレクチャーしてくれた Deep Residual Learning の話が面白かったので、以下メモとして記してみる。

#なお、このメモはDLについての基本的な仕組みは知っている人を前提に書いている。

 ResNetとは?

 もの凄い勢いで活発に研究されている Deep Learning機械学習であるが、昨年 ILSVRC'2015 という学会のコンペで、一般物体認識で最高性能を叩き出した ResNet (Deep Residual Net)という学習・識別器がある。当時 Microsoft Research にいた Kaiming He 氏が開発した、152層!のニューラルネットである。その論文はこちら

 多層ネットワークの勾配消失/発散問題

 昨今のDL研究で分かってきたDLの性質の一つとして、"Wide より Deep"というものがある(追記:さらに最近では、Wideも重要という論文も出てきている)。同じニューロン数で学習するなら、ネットワークの各層の幅(ニューロン数)をワイドにするよりも、階層の深さをよりディープにしたほうが性能があがるという性質である。

#なお、これは経験則に近いものらしく、理論的になぜそうなのかというのは、あまり良く分かってはいないらしい。 

 ならば、『どんどん階層を増やせば良いじゃない』か、と誰でも考えるが、問題はそれほど単純ではない。従来の方式で単純に階層を増やしすぎると勾配消失(vanishing)勾配発散(exploding)という問題が発生してしまって、うまく学習できなくなるのである。なお、この勾配の消失や発散をまとめて degradation と呼ぶらしい。de-gradationというニュアンスであろうか。 

勾配消失が起きる理由

 DLで学習する際には各層ごとに活性化関数を微分して勾配を求める。まず入力層に近い『浅い』層ではその層への入力と出力の差(accuracy)が大きいため、微分操作は有効に働く。しかし、学習が層の『深い』段階へと進むにつれ、(当たり前だが)学習が収束に近づくと入力から出力への変換精度がどんどん上がっていくため、入力と出力の差は極めて小さくなり、勾配を取りにくくなる。層から層への伝播は掛け算の性質をもっているため、この差の減少傾向は指数関数的になる。つまり、層を増やしていくと、あっという間に勾配が消えてなくなってしまうのである。

勾配発散が起きる理由

 DLでは、ある層のニューロンは次の層のニューロンと重みWで積和をとって結合している。この重みが1以上ならば、多層になると『重み✕重み✕重み・・・』が発散する可能性があることがわかる。

Residual Learning(残差学習)

 普通のDLの各階層では、入力信号 x をそのままネットワークに入力して出力 H(x) を計算し学習する。

 ResNetでは、出力から入力を引いた残差(Residual) F(x)=H(x)-x を学習する形にする。なぜそんなことをするのか?・・・前述の勾配消失の説明を思い出してほしい。DLでは深い層にいくほど、入力と出力の差が小さくなる、つまりH(x)はxに極めて近い値になる。しかし H(x) は有限なNNによる非線形写像であり、綺麗にxに近づけることは難しい場合が多い。むしろ、xに極めて近い出力が欲しいのだから、単純にxをそのまま出力し、それと H(x) の差 F(x) をNNで表現して写像すれば良いのではないか、という理屈が成り立ちそうだからである。(これがこの論文で実証したい中心仮説である。)

 これを実装するには、F(x)を直接計算しようとするよりも、下図のようなショートカット接続をネットワークに導入すれば良い。これにより weight layer(NN階層)はF(x)を学習する形になる。

f:id:terada-h:20161213165701p:plain

図1 残差学習の基本ユニット(これを何層も重ねる)

f:id:terada-h:20161213190641p:plain

図2 ResNetの実装例(34層)。左:従来の方式、右:ResNet方式

 ResNetの結果

 論文にある実験結果によれば、ResNetは素晴らしい効果を産んでおり、従来方式だと階層を増やしていくと、むしろ精度(エラー率)が悪化していたが、ResNet だとそのようなことはなく、層を増やせば増やすほどエラー率が低下する傾向となっている。

 たとえば、18層と34層を比較した結果が図3である。従来方式だと34層のほうが18層よりエラーが増えてしまっている。ResNetでは34層の方が良い結果となっている。

f:id:terada-h:20161213191516p:plain

図3 学習エラーの比較。左:従来方式、右:ResNet

さらに多階層もOK

 論文によれば、筆者らはさらに実験を続け、1202層!のResNetでも同様の傾向、つまり、degradation に邪魔されることなく学習が可能で、さらにエラー率が低下することを確認しているそうである(訓練誤差0.1%、テスト誤差7.93%)。

 この手法の登場により、理屈の上では飛躍的にDLの階層を増やせることになった。計算量は膨大になっていくが、計算機にお金を掛けられる人達であれば、どんどん階層を増やして精度を上げられるのである。すでに画像のパターン分類のようなタスクであれば、人間を超える認識精度に達していると言われるDLであるが、さらなる精度向上が何をもたらすのか興味深い所である。

昔ながらのプログラム設計論(スマホアプリ系)

0.いきさつ

先日、社内で新人に説明するために、基本的なプログラムのモジュール設計法について大急ぎでメモにまとめた。もしかしたら他にも誰かの役に立つかもしれない。せっかくなのでブログのネタにしてしまおう、ということでブログに掲載してみることにした。

ここに書いたことは全然目新しくはなくて、昔からある考え方であるが、現在でも有効なものである。非常に基礎的な考え方なので、オブジェクト指向設計でも、構造化設計でも、(場合によっては関数型でも?、)有効な考え方である。プログラム設計を分割統治法で考えようとするとき、この考え方をマスターしておくと、品質の高いプログラムを実装するのがたやすくなるはずである。

意外に、最近はこうしたことをハッキリ説明してくれている文献があまり見当たらない。特に新人が最初に参考にするような書籍やネット記事などでも触れられていることが少ないのだが、何故だろう? 非常に大事なプログラマの『感覚』なのではないかと思うのだが。

 

1. プログラムのモジュール(クラス・メソッド)分割方針について

プログラムを設計していく場合、クラスなりメソッドなり、何かのカタマリ(=ここではモジュールと呼びます)に分割していくわけですが、そこには経験的に上手くいく指針があります。それらを以下に説明します。

1.1. 常に『このモジュール(クラス・メソッド)の責務(Responsibility)は何か?』を明確にして分割する

  • 『責務』という日本語だと分かりにくいですが、Responsibilityという英語だと、プログラマの直感に近いニュアンスです。
  • Responsibilityは、'Response + ablility' という構成の語であり、『(ある要求に)反応・応答する能力を持っている』という意味です。
  • クラスやメソッドを設計するときのプログラマの意識は、Responsibility=『外部からの要求を受けて、決められた仕事を実施して結果を返すユニット』にあります。
  • このとき、そのモジュールの責務=『どの範囲の要求に応答し、どこまで仕事をさせることにするか』を明確に決めておくことが大事です。
  • 責務を曖昧にしていると、どっちつかずで位置づけが分かりにくく、デバッグがしにくかったり、将来の機能拡張や引継ぎのときに混乱を生じる原因になったりします。

1.2. 一つのモジュールには、明確にシンプルに説明できる一つの責務だけを持たせる

  • 一つのクラスや一つのメソッドにたくさんの機能を盛り込むと、ややこしくなってバグの元になります。
  • 例えば、『なにかを計算して、その結果を表示する、エラーならエラー表示を行う』という機能を一つのメソッドに詰め込むのは、原則として良くない設計です。
    • 以下の4つの単機能の下位メソッドを作り、それらをまとめて制御する上位モジュールを作るのが基本です。
      • 計算する
      • 計算結果を判定する
      • 結果を表示する
      • エラーを表示する

1.3. モジュール間の上下関係・主従関係を明確にする

  • 上位(主位)モジュールは:
    • いろいろな下位モジュールを呼び出します。
    • 全体の状況(状態)を把握しています。
    • 状態を持ち、その更新に責任を持ちます。
    • スレッドやタイマーを管理し、システムのさまざまな処理を起動するタイミングを統括します。
    • メモリやDB,リソースの初期化・確保・削除・解放に責任を持ちます。
    • 別名:Activeモジュールといいます。
  • 下位(従位)モジュールは:
    • 上位モジュールから呼び出されたら、単純な一つの責務を実行します。
    • いつ呼び出されるかは知りません。
    • なぜ呼び出されるかも知りません。
    • 上位モジュールの存在は知りません。
    • 他の下位モジュールの存在も知りません。
    • リソースの確保や解放はしません。
    • 『副作用』や『状態を持つ・状態を更新する』のはナシにするのが理想です。副作用があったとしても最小限かつ明確なものにします。
    • 別名:Passiveモジュールと呼びます

f:id:terada-h:20161026182230p:plain

図:上位モジュールは、下位モジュール呼び出す

 

f:id:terada-h:20161026182328p:plain


図:下位モジュールは、上位モジュールを呼び出さない。下位モジュール同士の直接のやりとりは、原則やらない。

 

f:id:terada-h:20161026182416p:plain

図:下位モジュールは単純・単機能な責務にする。上位モジュールは状態を管理し、全体を統括する。

2. オブザーバ(Delegate,Listener)パターンについて

オブザーバパターンでは、プログラムコード上は、下位が上位を呼び出す形になる場合がある。ただし、呼び出し先のオブザーバ(delegate, listener)はプロトコル(Swift)やインターフェイス(Java)として抽象化されており、具体的な特定のクラスを差していないので、上記ような意味で上位クラスを呼び出すのとは異なっている。

3. iOSアプリでの上下関係の例

  • 最上位モジュール:AppDelegate
  • 上位モジュール:各画面の ViewController
    • VC内のメソッドの上下関係
      • 上位メソッド
        • 状態を把握・状態遷移を行う
        • クラスのプロパティを変更しても良い。
        • 例:
          • viewDidLoad()
          • viewWillAppear()
          • viewDidAppear()
          • viewWillDisappear()
          • UIボタンやセンサーのイベントハンドラ
            • ただし、ハンドラに長々と制御コードを書くのは好ましくない。制御を集約した別クラスを作って、それを呼び出す形にする、など。
          • ・・・
        • ユニットテストしにくい
      • 下位メソッド
        • 状態を持たない(状態値を変更しない、参照しない)
        • クラスのプロパティを変更しない。
        • 入力=引数のみに依存し、結果は全て return 値だけに反映する。
        • 例:
          • 自分で定義する各種処理ルーチン
        • ユニットテストがやりやすい

4. どこで深刻なバグが起きやすいか

  • 上記説明における、下位モジュールでは、あまり深刻なバグは生まれません。
  • ほとんどの深刻なバグは上位モジュールの状態管理や条件判定、あるいはスレッドやタイマーの制御、メモリやリソースの管理に起因します。
  • したがって、デバッグしやすくするには以下の原則を守ることが大事です。
    • スレッドやタイマーの制御方針を明確に、一貫したものに。
    • 状態の管理や状態遷移の方法を明確に、一貫したものに。できるだけ一箇所・ワンパターンに集約する。
    • 安易にフラグ制御を増やさない。
    • メモリ・リソースの確保・解放は同一レイヤー(階層)に揃える。

以上

『ねこもに』のパーティクルフィルターアルゴリズムを書き、特許出願してみた

ねこもに』は、当社オープンストリームが開発中の新商品である。来年春の発売を目指している。現在開催中のCEATECにて発表・出展し、概ね好評をいただいているようだ。

 

ねこもには、スマホBluetoothを使ったシステムだ。猫の首輪にBluetooth(BLE)発信機を取り付けて、専用のスマホアプリを使って猫を探したりできる。

 

このシステムの肝となるのが、猫の場所を見つけるアルゴリズムである。よく誤解されるのだが、 このアルゴリズムは、iBeacon のような ON/OFF表現ーー『近くに居る・居ない』ーーで猫を見つけるのではない。二次元マップ空間上で、連続値的に猫の存在確率分布を計算して推定しているのである。その部分はちょっとした工夫をしていて、ざっと調べたところ類似の前例が見つからなかったので、現在特許を出願している

 

今回、このアルゴリズムの数理的な部分は私が考案させてもらった。たまたま、この方面の知識と経験があったからである。もちろん、突然思いついた訳ではなく、ねこもにという商品のコンセプトが先にあって、その解決策を考えていくうちに発想できたものである。

 

『ねこもに』のコンセプトは、当社のねこもにチームが発案し、毎年開催している社内コンペに勝ち抜いたものである。猫への愛情にあふれた、なかなか良いアイデアである。ただ、最初にその構成案を聞いたときには、これはちょっと無理じゃないのかな?と思った。情報処理のモデルを単純に考えると、典型的な不良設定問題になっていたからだ。

 

ご存知の通り、不良設定問題とは、与えられた入力情報だけでは答えが一つに定まらない、というタイプの数学的問題である。

 

BLEは、個体を識別するIDの他は、電波強度=距離しか検知できないデバイスである。つまり、BLE自身は一次元センサーであって、二次元的な位置を計測する機能はない。

 

これでどうやって猫の位置を精度良く推定しようか?・・・離散的なON/OFF表現のメッシュマップでもできるけど、それだと、メッシュの分解能以上の精度を出すのは難しいし、広域をサポートしようとすると、メッシュのデータを保持するメモリ容量も膨大なものになる・・・考えるうちに気がついたのはモーションステレオとの類似性である。

 

モーションステレオは、単眼カメラでステレオ視を実現する画像処理技術である。単眼カメラは二次元センサーであり、ステレオ視は奥行きを含めた三次元情報の復元が目的である。つまりここでも、一つ次元が少ないセンサーから、一つ次元の高い情報を再構成している。その点でねこもにの課題と似ていることに気づいた。(気づいたときはちょっと嬉しかった)

 

モーションステレオでは、カメラを移動させながら複数の画像を撮影して、奥行き推定を行う。同様にねこもにでも、スマホを移動させながら、BLEとの距離複数回計測して推定に使えば良いだろう、これが基本コンセプトになった。

 

もう一つの問題がある。それはBLEの距離値の誤差がけっこう大きいことである。BLEから得られる距離値は、周囲の環境の影響を受けたりして、平気で数メートル〜数十メートルぐらいの誤差がでるときがある。画像のモーションステレオなら、それなりに良いレンズなどを揃え、適切なキャリブレーションを実施すれば、ほとんど入力誤差を考慮する必要がないが、BLEは誤差だらけであり、誤差を考慮しないとまともな答は出ないのは明白であった。

 

センサーから誤差の影響を減らしたいとなれば、誰もが考えるのがフィルター理論の適用であろう。私もまずは古典的なカルマンフィルターを考えたが、5秒後に却下した。カルマンフィルターでは、対象物体の運動を線型な方程式で表現できないといけない。気まぐれな猫の運動を線型な方程式で記述できるわけがないw。そうなると・・・パーティクルフィルターを使うのが常識であろう。

 

というわけで、ねこもにでは、パーティクルフィルターを使うことで、誤差込みの距離センサー値を複数回重ねあわせて、二次元位置を逐次推定するモデルを使うことに落ち着いた。

 

パーティクルフィルターを位置推定に使う特許や論文は沢山あるのだが、ほとんどがGPSや移動台車のデッドレコニングなどへの適用例であり、ねこもにのようなアプローチは意外に見つからなかった。なので特許を出願したわけである。

 

実際のソフトウェア実装の話も書こうと思ったが、かなり長くなったので、また別の機会にしよう。

 

 

やりなおし

いろいろ思う所があっても、結局、忙しくなると移動中にスマホからSNSに短いコメントを投稿して満足してしまう。

SNSの記事は一過性だ。記事を蓄積して、自分のデータベースのようにするには、やはりブログのほうが適している。

ということで、今後はブログをメインして発信することにする。やりなおしである。