2018年3月10日土曜日

Deep Learningの初歩の初歩

思考の整理のために書き溜めていたものが溢れてきたので、Deep Learningの *適当な* メモを置いておきます。 触り始めたばかりの初心者の方には多少は参考になるかも。

なぜDeep Learning?

Deep Learningは機械学習の一分野であり、Deep Learningの前身とも言えるのは(多層)ニューラルネットだ。 ニューラルネットは闇の時代が非常に長かったが、 中間層の活性化関数をsigmoidからReLUに変えた事、ミニバッチを採用した事、最適化関数が劇的に進化した事、Dropoutなどの過学習を抑える仕組みが確立された事、など様々な改善の結果として深い層での学習 (Deep Learning) が可能になった。

Deep Learningの価値は、他の手法との比較をするとわかりやすい。 ニューラルネットの闇の時代により良い様々なアルゴリズムが模索されてきたが、 Random ForestやKernel SVMは今でもベースラインとして利用される典型例だ。 このようなアルゴリズムと比較すると、モデルに自由度がある事が一番大きな差であると思う。 自由度が大きい=パラメータチューニングが大変となる面もあるのだけど、簡単なモデルならコツを掴めば割と短時間でできるように思う。 勘所は以降で説明する「パラメータチューニングのいろは」にまとめておいた。

モデルの自由度が生み出した最大の利益はCNNやRNNなどの派生手法の躍進だけど、より根本的な点ではKernel SVMとの比較で利点が際立つ。 Deep Learningはモデルの自由度に応じて良好な表現を有限空間で探索するカーネルと捉える事ができる。 一方、Kernel SVMで用いられる一般的な正定値カーネルを用いてこれを表現しようとすると無限次元になってしまう。 モデルが自由度を得た事で、大きな改善余地を得ている事がわかる。

ではRandom Forestと比較するとどうだろう。Random Forestには大まかに3つの問題がある。 (1)スパースなデータに弱く、(2)矩形分割が基本となるため、矩形領域以外の予測や対角境界の予測に多くの決定木を必要としてしまう。 また多層ニューラルネットと同様の問題として、(3)層数を深くすればするほど過学習しやすくなる問題が知られている。 少なくとも2に関してはReLUのほうが柔軟性があると思うし、3に関してはDropoutなどの大きな効果を挙げた汎化手法をそのまま適用するのが難しい点は、大きな差だろう。 もっともDeep Forestのような複合手法も徐々に出てきてはいる。

またライブラリとして使っていると軽視しがちだけど、Deep Learningはいくつかの仮定の下で大域解となる事が証明されている (簡単説明原文)。 仮定にReLUを用いているのが地味に重要と思うけど、 この仮定に基づいた最適化であれば、モデルの自由度も保証される事になる (たぶん)。 簡単説明をされている方のサイトの Deep Learningの理論的論文リスト も参考になる。 理論的解析が進む事で、より良い学習方法がわかってくるかも知れない。 ちなみに私が今のところ一番わかりやすいと思っている資料は以下です。

基本的なとこ

勉強したばかりでやらかしがちな失敗は、以下でたいてい解決すると思う。 情報収集の基本は英語となるけど、とにかく早い分野のため、非母国語のハンデを背負って追随していくのはやはり辛いところがある。 arXivTimesを読むくらいは日課にして良いのではないかと思っている。


パラメータチューニングのいろは

データセット/特徴量

具体的な方法はタスクによるけど、test/productionでデータセットの取り扱いがズレてしまうのは、一番やりがちなミスだと思う。 ここで間違うと、いざ運用しようとした時のテストで「あれ、何かおかしいぞ」みたいな事になる。 実世界のデータは動的なので、なぜそのやり方だと長期運用した時に安定するかのほうが大事だと思う。 そこに重点を置くと取り扱い方で間違う事はなくなる。

データセットは規模も重要だけど、それ以上に歪んだ分布は大きな性能減の要因となる。 狭過ぎる空間を抜き取った学習は、情報が失われた事による性能減少にも注意。 データセットが巨大になり過ぎてきた場合は、転移学習が手軽な割には性能改善しやすい。 特徴量は過学習しにくい形状にしておくのも重要だし、微分可能な状態にしておかないと大きく精度が落ちるので、結局は特徴量の職人芸も非常に重要になる。

スケーリング

スケーリングの有無で精度は大きく変化する (参考: スケーリングと精度の関係) ので事前のスケーリングはしたほうが良い。 様々な手法があるので比較してみたほうが良いけど、平凡な標準化が良い性能になる。Batch Normalizationで使われているだけの事はある。

モデル

基本的には深くするほど複雑な特性を学習できるようになるので良い。 ただし層数を深くする場合、以下のような問題と対策を考慮に入れておく。
  1. 微分を繰り返す事で勾配消失や勾配爆発が発生し過学習しやすくなる→ReLU、Batch Normalization、Dropout、Gradient Clipping、etc.
  2. 浅層の学習の影響を受け深層の学習が不安定になり収束も遅くなる→短縮経路や残差の利用を選択肢に入れる (ex: ResNet)
対策を見ていると関数のサチりにくさ、汎化効果や安定化効果のある処理が重要である事が何となくわかる。 特に2の影響があるため、全結合では深層になるほど素子数を減らし、収束しやすくすると良い結果になりやすいように感じている。
ただ一般的に全結合は現状、2-3層の構造が最も良い精度になる事がほとんどです。 これは層を積み重ねてもtop-kで特徴抽出しようとするため、ほとんど同じ特性しか抽出できないためと思います。
最近は困ったらResNetベースのMobileNetを使っとけば良いんじゃないかと思ってます。 学習効率が良くて性能が良いので、GPUを使わなくても割とイケる。

カーネル

画像タスクでは CNN > 全結合 がほぼ確定している。 全結合でもCNNの構造を理屈上は再現可能なのにです。 これは近傍を畳み込むと良いという事前知識に基づくカーネルを利用し、特徴らしい特徴が存在しない部分を畳み込む部分が大きいと思う。 あとは全結合のようにパラメータを増やさず過剰適合を回避しつつ、全体領域ではなく隣接局所領域を見て特徴化できているのが大きいのだろうなあ。 ただこの条件が適用できないタスクも多く、この辺をうまく取り扱うカーネルの余地は大きいのでは。

隣接局所領域という事前知識の重要性は割と簡単に確認できます。 特徴量のピクセル位置を以下のようにランダム化した上で学習すると、CNN は全結合と同じくらいの精度しか出ません。 学習分布のハックこそが重要とよくわかります。
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
indices = numpy.random.randint(0,784,size=784)
for pos_old in range(0, 784):
    pos_new = indices[pos_old]
    x_old = pos_old // 28
    y_old = pos_old % 28
    x_new = pos_new // 28
    y_new = pos_new % 28
    tmp = x_train[:, x_new, y_new]
    x_train[:, x_new, y_new] = x_train[:, x_old, y_old]
    x_train[:, x_old, y_old] = tmp
    tmp = x_test[:, x_new, y_new]
    x_test[:, x_new, y_new] = x_test[:, x_old, y_old]
    x_test[:, x_old, y_old] = tmp


最適化関数

とりあえず Adam を使っておけば良い風潮だけど、SGD+Momentumは根強い人気がある。 RMSPropなどもたまに使われているように感じる。 SGD+Momentum の人気は ResNetや GAN で SGD+Momentum > Adam となる事が多く、Adamの汎化性能に疑問が付いたためである。 根底は更新が大きく/小さくなり過ぎる問題によるもので、weight decay の補正で改善される事がわかり、最近は AMSGrad や AdaBound など Adam を上回る様々な手法が再び出てきている。 よく起きる問題をすべて考慮した AMSBound も出て、ひとまずの安寧を得たようには感じている。 とはいえ問題が起きるかどうかはタスク次第で、結局は Adam がよく使われている感じ。
どの手法でも Gradient Clipping は一度は付けてみると良い気がしている。

サンプリング・重み付け

不均衡データのクラス分類は、よほど酷いズレがある場合なら採用を検討する。 正例と負例が8:2だったらクラスの重みを変化させたり、サンプリングして1:1にしたほうが良い。
その程度で済む話なら良いが、実際には正例と負例の分布は1:1だけど局所的な分布の歪みがスコアに大きく左右する事もある。 また本質的に片方だけが予測しやすいようなデータもある。 分類タスクは結局のところ分布の偏りを判断基準にするので、均衡にし過ぎる事にもそもそも問題が出てくる。 そのような問題が大きくなってきたら回帰を利用してみたり、クラスの重みを調整するなどしたほうが良いかも知れない。
最近は損失関数で不均衡を対処する手法として Focal loss というのが出てきた。 特徴量そのものの不均衡にも対応できるし、シンプルで良い。 Focal loss をより高度化した手法がさらに良い性能を出しているようで、今後の期待が高まる。

データの水増し

特にCNNにおいては、CNNがデータの移動や回転に弱い事もあり、その対策としてそれらの変換を加えたデータを自動生成して、データの水増しをする事が多い。 データの水増しにも様々な手法があり、例えばこことかここで典型的な手法が説明されている。 ただしこのような手作業でのデータ水増しは汎用性がない。 実用上は使わざるを得ない事が多いものの、私はあまり良い方法とは思っていない。 逆にモデルベースで解決しようとするCapsNetのような手法は今後の期待が高いと思う。
また上記のような手動のData Augumentation技術ではなく、分布に仮定をおかず与えられたデータから水増しするGenerative Adversarial Networks (GAN) に関連する手法を用いて正則化を行う手法も期待は高いと思う。 GANというよりはAdversarial Trainingと書いたほうが良いのかな。 このような手法はどのような構造にも適用しやすいため、こちらは今後も進化していくのではないか。 もっとも学習が遅く不安定という問題もある。

ミニバッチ

バッチサイズを一定量大きくするとデータへの依存性を減らしたり、ノイズへの強度を高めたり、学習速度の向上が望める。 そのため大規模なデータではバッチサイズを大きくしたいところだが、一定量より大きくすると性能が下がる。 これは試行回数によるとも思うけど、flat minimaな結果ではなくsharp minima (不安定) な結果になるため学習率にも依存していたり敵対的攻撃にも弱くなる問題もあるらしい
バッチサイズと学習率の関係を Google が詳しく調べていて、このページが日本語でよくまとまっている。 平たく言えばバッチサイズを増やすと学習率を変化させるのと同等の効果がある。 ただバッチサイズあり/なしの比較論文って見たことない。 バッチサイズが 1 だとデータに過剰適合してしまうことがよくあるので、ミニバッチが小さいほうが良いとは言えども、数個のミニバッチは付けたほうが良いとは思っている。

損失関数

クラス分類は交差エントロピーほぼ一択。回帰ではMSE,MAE,MAPEなど様々な選択肢がある。 ただ回帰はあまり性能が出ないので (参考: 回帰の弱さ確認)、私は回帰をほとんど使ってない。 回帰の損失関数の選び方については非常によくまとまったページがあったので、ここも参考にして欲しい
交差エントロピーが利用される最大の理由は収束速度が早いためだと思う。 ただラベルのノイズが大きい場合はヒンジ損失やMAEが交差エントロピーより良いという報告もある。MAEは理論的な説明もなされている。

正則化

とりあえずDropout、Batch Normalization (以下、BN) を付けて過学習を防ぐと良い。 ただし両方付けると性能が落ちる事もある。 BN由来のわずかな性能減は様々なケース、特に内部共変量シフトが起きにくい層数が少なくても十分なタスクでよく見かける。 BNは更新の分布を安定化する事で学習を高速化する効果があるが、Dropoutはノイズを増やすので低速化する。 層が深いと死ぬし、割合調整が難しいので、層が多い場合は出力層の手前にだけ付ければ良いという記述をたまに読む。
BNには最適化する対象の違いでInstance Normalization, Layer Normalizationといった亜種がある。 最近はこれらを自動で最適化するSwitchable Normalizationのような手法も出てきた。 でも標準なBNが今でも一番使われている。
Dropoutは中間層のDropout率は0.5にすると最も正則化効果が大きい。入力層は0.2が良いとも言われる。 Variational Dropout (簡単説明) というDropout率を制御する手法もあり、Dropoutより分散を小さく、スパース性を大きくできるだけでなく、ランダムラベルに弱い問題に対応できる。さらに性能を改善する手法も色々と研究されているようだ。
L1/L2正則化は微弱なパラメータを0にし次元削減をする事で予測の安定性を高めようとするが、この丸めは特徴量に正規分布を仮定している事を忘れてはいけない。 パラメータも手動で決めないといけないのでDeep Learningだと深層のパラメータ調整は難しいようにも感じる。 Thresholded ReLUにも同様の正則化効果がありそうだけど、どうなんだろう。

初期化

ReLUはHe、sigmoidではGlorotを使うのが定跡 (参考: HeとGlorotの収束速度の簡単説明)。
最近はDeep Learningがうまく動く事が当たり前のようになっているけど、初期化の特性は割と重要な事を教えてくれます。 例えば標準偏差を0にしたり、1くらいまで上げると容易に学習できなくなる事が確認できるのですが、この裏では勾配消失などの問題が起きている事が確認できます。 またこのような特性がある事から、スケーリング、初期化、最適化関数の最適値はかなり固定されており、モデルやカーネルを変更しない事には精度が伸びない事がここからわかるように思います。
HeやGlorotではバイアス項が0になる事を仮定していますが、この仮定を取り払う事で収束速度の向上が望めるという論文もあるみたい

出力層の活性化関数

クラス分類はsoftmaxもしくはsigmoid/tanh/softsignが定跡。 softmaxは分類の確信度が低くても総和が1になるよう補正するので分類確率を示すが、sigmoid/tanh/softsignは補正しないので分類の確信度を示す。
回帰ではlinearや出力形状に合わせた関数を使うのが定跡。

中間層の活性化関数

多くのモデルではとりあえずサチらない非線形関数、ReLU を使っておけば良い感じ。 ReLU にも色々な派生手法があるけど、データセットやモデルのほうが遥かに重要 (参考: 様々な最適化項目の寄与度比較)だし、一番最初に書いた理論的証明の支持もある。
ただし bias や weight が負側に更新されやすいデータでは、ReLU の出力が 0 になる dying ReLU は大きな問題になる。 そのようなタスクでは Leaky ReLU や PReLU などの派生手法を使うべきなのかも。
最近は ReLU よりも softplus や Swish、Mish で良い性能が出るという報告を見かけるようになった。滑らかな特性を持ちつつサチらない活性化関数に可能性があるのかも知れない。 現状ではほんの少しの改善にしかならないが、滑らかである利点として、層数が少なくて済むのは明らかな利点と思う。 他にも tanhExp とか出てきた。
しかし NALU という活性化関数というか構造はなかなか凄い。 ReLU を用いても数値情報の操作に関する外挿を表現する事は難しかった問題を解決できるようになった。 CNN なども隣接ノードに関して外挿的な効果があると思うし、これは大きな進化と思う。 数値情報の操作においては非線形である事が重要という訳ではない良い例でもあり、このようなアプローチは増えていくかも知れない。

コロコロ見解も変わる分野なので、何か思い付いたら追記していこうと思います。