本文へスキップ
ひもとくAI

早期終了とドロップアウト

レッスン 3/7

早期終了:検証データの損失が上がり始めたら、学習を止める

ニューラルネットワークは、勾配降下法で少しずつ重みを動かして学習します。訓練データ全体を1回使うことを 1周(エポック)と呼びます。周を重ねるほど訓練データの損失は下がりますが、長く学習させすぎると、訓練データを丸暗記し始めます。

検証データ:学習の途中で、新しいデータでの様子を見るためのデータ

途中の様子を見るために、データを3つに分けます。

  • 訓練データ: 重みを動かす(学習する)ために使う
  • 検証データ: 学習の途中で、新しいデータでの損失を測るために使う(学習には使わない)
  • テストデータ: 最後に一度だけ、本当の実力を測るために使う

テストデータを途中の判断に使うと、テストデータに合わせて選んだことになり、実力を正しく測れなくなります。そのため、途中の判断には検証データを使います。

実験:学習を長く続けると、どうなるか

ノイズ(ばらつき)の多い2クラスの分類のデータを、訓練40点・検証100点・テスト400点に分け、大きなネットワーク(隠れ層100個を2層)を2000周学習させました。

周損失050010001500200000.51
  • 訓練データの損失
  • 検証データの損失
周ごとの損失。訓練データの損失は、20周目の約0.22から下がり続け、2000周目には約0.03になる。検証データの損失は、20周目の約0.32から上がり続け、2000周目には約1.17になる。2つの線の差が、周を重ねるほど広がっていく

訓練データの損失は下がり続けますが、検証データの損失は、9周目で最も小さくなった後、上がり続けました。9周目より後は、訓練データの丸暗記が進み、新しいデータでは悪くなっているのです。

そこで、検証データの損失が最も小さかった周の重みを残して、学習を止める 方法を 早期終了(アーリーストッピング)と呼びます。実際には、「検証データの損失が、10周続けて最小を更新しなかったら止める」のように、少し待ってから止めます。

学習の止め方訓練データの正解率テストデータの正解率検証データの損失
2000周まで続ける1.0000.843約1.17
早期終了(9周目の重み)―0.845最も小さい

正解率はほとんど同じなのに、損失は大きく違う

この実験では、テストデータの正解率は0.845と0.843で、ほとんど変わりませんでした。それでも、損失は約3倍に悪化しています。第5章で学んだとおり、交差エントロピーは「自信を持って外したとき」に大きな罰を与えます。2000周学習させたモデルは、間違えるときに、自信満々で間違える ようになっていたのです。正解率だけを見ていると、この種類の過学習に気づけません。

ドロップアウト:学習中に、ニューロンをランダムに休ませる

ドロップアウト は、学習の1回ごとに、隠れ層のニューロンの一部(例えば半分)を ランダムに選んで、出力を0にする(休ませる)方法です。休ませるニューロンは、毎回選び直します。

なぜ、過学習を防げるのか

チームで仕事をするとき、特定の1人に頼りきっていると、その人がいない日に仕事が回りません。毎日ランダムに半分の人が休むチームでは、誰か1人に頼らず、どの人がいても仕事が回るように工夫するはずです。

ニューラルネットワークも同じです。ドロップアウトがないと、「このニューロンとこのニューロンの組み合わせで、訓練データのこの点を当てる」という、特定のニューロンに頼った丸暗記が起きやすくなります。毎回ランダムにニューロンが休むと、特定の組み合わせに頼れなくなるので、丸暗記しにくくなります。毎回違うニューロンの組み合わせ(少しずつ違う小さなネットワーク)を学習させて、その平均をとっている、と考えることもできます。ニューロンではなく、重み(線)を1本ずつランダムに0にする方法もあり、ドロップコネクト と呼ばれます。

学習するときと、使うとき(推論)で動きが違う

  • 学習するとき: ニューロンを確率 pp で休ませる。休ませなかったニューロンの出力は 11−p\frac{1}{1 - p} 倍する(p=0.5p = 0.5 なら2倍)。休んだ分だけ次の層への合計が減るのを、補うためです
  • 使うとき(推論): 全部のニューロンを使う。休ませることも、倍にすることもしない

実験:ドロップアウトの効果

前のスライドと同じデータと大きなネットワークで、2つの隠れ層に確率0.5のドロップアウトを入れて、2000周学習させました。

周検証の損失050010001500200000.51
  • ドロップアウトなし
  • ドロップアウトあり(0.5)
周ごとの検証データの損失。ドロップアウトなしは、200周目に約0.61、400周目に約0.83と上がる。ドロップアウトありは、200周目に約0.51、400周目に約0.69と、上がり方がゆるやか。ただし、2000周目にはどちらも約1.17〜1.18になり、差はなくなる
200周目の検証の損失400周目の検証の損失2000周目のテストの正解率
ドロップアウトなし0.6140.8340.843
ドロップアウトあり0.5120.6860.840

ドロップアウトを入れると、検証データの損失が悪くなる速さが、ゆるやかになりました。しかし、2000周も学習させると、最後はどちらも同じくらいまで悪化し、テストの正解率もほとんど変わりませんでした。

この実験のように、訓練データが40点しかない小さな問題では、ドロップアウトの効果は小さい ことがあります。ドロップアウトが大きな効果を発揮するのは、重みの数がとても多い大きなネットワークを、たくさんのデータで学習させる場面です(画像認識の有名なCNN、AlexNet(2012年)で広く知られるようになりました)。また、ドロップアウトだけに頼らず、早期終了と組み合わせて使うのが普通です。

NumPyでドロップアウトを書く、よくある誤解と振り返り

ドロップアウトは、「休ませるかどうか」を表す0と1の配列(マスク)を作り、隠れ層の出力に掛けるだけで書けます。

import numpy as np

rng = np.random.default_rng(0)
h = np.array([[0.5, 1.2, 0.0, 2.0],
              [1.0, 0.3, 0.8, 0.4]])      # 隠れ層の出力(2件 × ニューロン4個)
p = 0.5                                   # 休ませる確率

r = rng.random(h.shape)                   # 0〜1のでたらめな数(h と同じ形)
mask = (r >= p) / (1 - p)                 # 休ませるなら0、残すなら 1/(1-p) = 2
h_dropped = h * mask                      # 学習するときは、これを次の層に渡す
  • rng.random(形) は、0以上1未満のでたらめな数を、指定した形で作ります
  • r >= p は、r が p 以上なら True(1)、そうでなければ False(0)です。r は0〜1に均等にばらつくので、p = 0.5 なら、およそ半分が0(休む)になります
  • 残したニューロンは 1 / (1 - p) 倍(ここでは2倍)して、休んだ分を補います
  • 使うとき(推論)は、マスクを掛けずに h をそのまま次の層に渡します

誤差逆伝播では、同じマスクを、変化の割合にも掛けます。休んだニューロンは出力に影響していないので、変化の割合も0になります(第7章のプーリングで、一番大きいマスにだけ変化の割合を戻したのと同じ考え方です)。

よくある誤解

  • 「正解率が同じなら、過学習していない」: 早期終了の実験では、正解率はほとんど同じでも、損失は約3倍に悪化していました。自信満々の間違いが増えていたのです。損失も合わせて見ましょう
  • 「早期終了は、テストデータの損失を見て止める」: 止める周を決めるのは、検証データです。テストデータで決めると、テストデータに合わせて選んだことになり、本当の実力を測れなくなります
  • 「ドロップアウトは、使うとき(推論)にもニューロンを休ませる」: 休ませるのは学習するときだけです。使うときに休ませると、毎回結果が変わってしまいます
  • 「ドロップアウトを入れれば、必ず正解率が上がる」: 小さな問題では、効果が小さいことがあります(スライドの実験)。早期終了や正則化と組み合わせ、検証データで効果を確かめて使います

振り返り

  • データは訓練・検証・テストの3つに分ける。途中の判断には検証データを使う
  • 早期終了: 検証データの損失が最も小さかった周の重みを残して、学習を止める
  • ドロップアウト: 学習するときだけ、ニューロンをランダムに休ませ、残りを 11−p\frac{1}{1-p} 倍する。特定のニューロンに頼った丸暗記を防ぐ

演習

演習を読み込んでいます…