機械学習 オーバーフィッティングは敵ではなく味方である

挑発的なタイトルになりかねないので、前置きをさせていただきます。

>
オーバーフィッティングのエンドモデルを誰も望んでいないのと同様に、アンダーフィッティングのエンドモデルを誰も望んでいないのは事実です。

>
>
というものです。

オーバーフィッティングモデルは、学習データでは素晴らしい性能を発揮しますが、新しいインスタンスに対してうまく汎化することができません。

その結果、特定のデータセットに合わせた完全にハードコードされたモデルに近づいてしまいます。

アンダーフィットモデルは、新しいデータに汎化することはできませんが、元のトレーニングセットをモデル化することもできません。

正しいモデルとは、トレーニングセット、検証セット、テストセット、そして新しいインスタンスの値をうまく予測できるように、データにフィットしたモデルです。

オーバーフィッティング vs. データサイエンティスト

オーバーフィッティングとの戦いにスポットライトが当てられるのは、新人が機械学習を始めるときに、より幻想的で、より魅力的なオーバーフィッティングモデルを作成するためである。

書籍、ブログ記事、コースを通して、一般的なシナリオが示されています。

というシナリオがあります。

このモデルの精度は100%です! このモデルは精度100%です!完璧です!」。

または、そうではありません。

実は、データセットにひどく適合しているだけで、新しいインスタンスでテストすると、ランダムな推測と同じX%のパフォーマンスを発揮するのです。

これらのセクションの後、本や講座の章全体が、オーバーフィッティングとの戦いやそれを回避する方法に費やされています。

この言葉自体が、一般的に悪いこととして汚名を着せられるようになりました。

そして、ここから一般的な観念が生まれるのです。

ということです。

オーバーフィッティングは何としても避けなければならない」。

アンダーフィッティングよりもずっとスポットライトが当てられていますが、これも同じように「悪い」ことです。

注意すべきは、「悪い」というのは恣意的な言葉であり、どの条件も本来は「良い」「悪い」ではないということです。

オーバーフィッティングモデルは、アンダーフィッティングモデルが何もないデータで良い結果を出すのに対し、少なくともいくつかのデータでは良い結果を出すので、技術的にはより有用だと主張する人もいるかもしれませんが、成功の錯覚はこの利点を上回る有力な候補となります。

参考までに、Google TrendsとGoogle Ngram Viewerを参照してみましょう。

Google Trendsは検索データのトレンドを表示し、Google Ngram Viewerは文献中のn-gram(単語などのn個の項目の並び)の出現回数をカウントし、古今東西の膨大な書物を解析している。

オーバーフィッティングの話はよくされますが、それはオーバーフィッティングを避けるという意味合いが強いです。

これはある程度正しいです。

そうです。

最終的なモデルがひどくオーバーフィットすることは避けなければなりませんし、そうでなければ実質的に意味がありません。

しかし、最終的なモデルにはすぐにたどり着けません。

様々なハイパーパラメータを使って、何度も微調整を行います。

この過程で、オーバーフィッティングが起きても気にする必要はありません。

オーバーフィッティングはそれほど悪いものではないということです。

ということです。

オーバーフィットする能力を持つモデルやアーキテクチャは、それを単純化(および/またはデータを微調整)すれば、新しいインスタンスに対してうまく汎化する能力を持つ可能性が高くなります。

  • 後述するように、モデルだけでない場合もあります。

もしモデルがオーバーフィットできるなら、それはデータから(意味のある方法と意味のない方法で)特徴を抽出するのに十分なエントロピー容量を持っています。

そこから、モデルが必要以上のエントロピー容量(複雑さ/パワー)を持っているか、データ自体が十分でないかのどちらかです(非常に一般的なケースです)。

逆のケースもあり得ますが、より稀です。

あるモデルやアーキテクチャが適合しない場合、モデルを微調整して特定の特徴を拾えるかどうか試すことができますが、そのモデルのタイプはタスクに対して明らかに間違っており、何をしてもデータを適合させることができないかもしれません。

モデルによっては、あるクラスを区別したり、値を予測したりするのに十分な特徴を抽出できないため、ある精度のところで止まってしまうのです。

料理で言えば、逆の例えを作ることができます。

シチューは最初から塩を控えめにした方がいい。

味を調えるために後でいつでも塩を加えることができるが、一度入れた塩を取り除くのは難しいからだ。

機械学習では、その逆です。

モデルをオーバーフィットさせてから、それを単純化し、ハイパーパラメータを変更し、データを増やすなどして、うまく一般化する方が良いのですが、その逆をするのは(実用上)難しいのです。

オーバーフィッティングが起こる前にそれを回避することは、正しいモデルやアーキテクチャを見つけることから長い間遠ざけてしまう可能性があります。

実際には、機械学習や深層学習の最も魅力的な使用例では、オーバーフィットに悩まされるデータセットで作業することになります。

このようなデータセットでは、うまく汎化して特徴を抽出できるモデルやアーキテクチャを見つけることができず、日常的にアンダーフィットを行うことになります。

また、私が真のオーバーフィットと呼ぶものと、部分的なオーバーフィットの違いにも注目する必要があります。

あるデータセットにオーバーフィットしたモデルが、トレーニングセットでは60%の精度を達成し、検証セットとテストセットでは40%しか達成できなかった場合、データの一部をオーバーフィットしていることになります。

しかし、データセット全体を飛び越えて、100%に近い(誤った)精度を達成しながら、検証セットとテストセットは例えば40%程度と低いという意味では、真のオーバーフィットとは言えません。

部分的にオーバーフィットするモデルは、本当に(オーバー)フィットするのに十分なエントロピー容量を持っていないため、単純化してもうまく一般化することができないのです。

いったんそうなれば、私の議論が適用されます。

ただし、次のセクションで明らかにするように、それは成功を保証するものではありません。

ケーススタディ – 友好的なオーバーフィッティングの議論

Yann LeCunによってコンパイルされたMNIST handwritten digits datasetは、分類モデルの学習に用いられる古典的なベンチマークデータセットの1つである。

LeCunはDeep Learningの創始者の一人と広く考えられており、ほとんどの人が自分のベルトの下に置くことができない分野への貢献があり、MNIST handwritten digits datasetはConvolutional Neural Networksの初期段階に使用された最初の主要ベンチマークの1つでした。

>
また、最も使い古されたデータセットである可能性もあります。

データセット自体にも、それを作ったLeCunにも問題はないのですが、同じデータセットの例を次々とネット上で見つけるのは退屈です。

一時期は-見ているだけでオーバーフィットしてしまいました。

どの程度ですか?ここで、私の頭の中からMNISTの最初の10桁をリストアップしてみる。

5, 0, 4, 1, 9, 2, 2, 4, 3


どうだったでしょうか?

from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt


# Import and normalize the images, splitting out a validation set
(X_train_full, Y_train_full), (X_test, Y_test) = keras.datasets.mnist.load_data()


X_valid, X_train = X_train_full[:5000]/255.0, X_train_full[5000:]/255.0
Y_valid, Y_train = Y_train_full[:5000], Y_train_full[5000:]


X_test = X_test/255.0


# Print out the first ten digits
fig, ax = plt.subplots(1, 10, figsize=(10,2))
for i in range(10):
    ax[i].imshow(X_train_full[i])
    ax[i].axis('off')
    plt.subplots_adjust(wspace=1)


plt.show()


もう少しです。

>
この機会に、コンテンツ制作者の皆さんに、このデータセットを入門編以上に使いすぎないよう、お願いしておきます。

お願いします。

さらに、このデータセットでは、アンダーフィットのモデルを作るのが難しいです。

直感的な数の層と層ごとのニューロンで構築されたかなり小さな多層パーセプトロン(MLP)分類器でさえ、トレーニング、テスト、検証セットで簡単に98%以上の精度に到達することができます。

以下は、トレーニングセット、検証セット、テストセットの両方において〜98%の精度を達成したシンプルなMLPのJupyterノートブックで、賢明なデフォルトでスピンアップしています。

このMLPの精度は、トレーニングセット、検証セット、テストセットのいずれにおいても約98%です。

初期設定よりも良いパフォーマンスが出るようにチューニングする気にもなりませんでした。

CIFAR10とCIFAR100のデータセット

MNISTの手書き数字よりも複雑で,単純なMLPではアンダーフィットとなりますが,そこそこの大きさのCNNでは本当にオーバーフィットするほど単純なデータセットを使ってみましょう.良い候補はCIFARデータセットです。

その候補はCIFARデータセットです。

CIFAR10では10クラス、CIFAR100では100クラスの画像があります。

さらに、CIFAR100のデータセットには、似たようなクラスが20ファミリーあり、ネットワークはさらに、似ているが異なるクラス間の微細な差異を学習する必要があるのです。

これらは「細かいラベル」(100個)、「粗いラベル」(20個)と呼ばれ、これらを予測することは、特定のクラス、あるいはそのクラスが属するファミリーを予測することに等しい。

例えば、ここにスーパークラス(粗いラベル)とそのサブクラス(細かいラベル)があります。

| — | — |
| スーパークラス | サブクラス
| 食品容器|ボトル、ボウル、缶、カップ、皿|。

カップは円柱で、ソーダ缶に似ており、いくつかのボトルもそうかもしれない。

これらの低レベルの特徴は比較的似ているので、それらをすべて「食品容器」のカテゴリーに入れるのは簡単ですが、何かが「カップ」なのか「缶」なのかを適切に推測するためには、より高度な抽象化が必要です。

この作業をさらに難しくしているのは、CIFAR10では1クラスあたり6000枚、CIFAR100では1クラスあたり600枚の画像があり、ネットワークが微妙な違いを学習するための画像が少なくなっていることです。

取っ手のないコップもありますし、缶の凸凹もあります。

プロファイルを見れば、簡単に見分けがつくかもしれませんね。

ここで、例えばMultilayer Perceptronは学習するための抽象化能力を持たないので、恐ろしくアンダーフィッティングで失敗する運命にあるのです。

畳み込みニューラルネットワークは、神経科学と脳が行う階層的なパターン認識からヒントを得た「ネオコグニトロン」をベースに構築されています。

このネットワークは、このような特徴を抽出することができ、そのタスクに秀でています。

そのため、汎化能力のために精度を犠牲にし、最終的にはそのままでは使えないことがよくあります。

ここで、CIFAR10とCIFAR100のデータセットで2つの異なるネットワークアーキテクチャを学習させてみましょう。

>
> ここでも、ネットワークがオーバーフィットしても、ネットワーク自体が単純化すれば必ずうまく汎化できるという保証はない–傾向はあるが、単純化すれば汎化できないかもしれない–ことがわかると思います。

ネットワークは正しいかもしれないが、データが足りないかもしれない。

CIFAR100の場合 – 1クラスあたり学習用500枚(テスト用100枚)だけでは、単純なCNNでは100クラス全体に対して本当にうまく汎化できないので、データ増強を行う必要があるのでしょう。

データ拡張を行ったとしても、データに対してできることは非常に多いので、高精度のネットワークは得られないかもしれない。

同じアーキテクチャでも、CIFAR10ではうまくいっても、CIFAR100ではうまくいかないということは、例えば「カップ」「缶」「ボトル」と呼ばれる円筒形の物体を区別するような、より細かい部分まで識別できないことを意味します。

>
CIFAR100データセットで高い精度を達成した高度なネットワークアーキテクチャの大部分は、データ増強やその他の方法でトレーニングセットを拡張しています。

そのほとんどは、そうしなければならず、それは悪いエンジニアリングの兆候ではありません。

実際、これらのデータセットを拡張し、ネットワークの汎化を助けることができるという事実は、エンジニアリングの創意工夫の表れです。

さらに、32×32のような小さな画像で画像分類がそれほど難しくないことを確信するのであれば、どんな人間でも、これが何であるかを推測してみることをお勧めします。

画像4は、数個のオレンジ?ピンポン玉?卵の黄身?卵の黄身ではないでしょうが、そのためには「卵」とは何か、テーブルの上に卵の黄身が置いてある可能性が高いかどうかという予備知識が必要ですが、ネットワークはそれを持ち合わせません。

あなたが世界に対して持っている予備知識の量と、それがあなたの見るものにどれだけ影響を与えるかを考えてみてください。

データの取り込み

今回はディープラーニングライブラリとしてKerasを使用しますが、他のライブラリやカスタムモデルでも対応可能です。

まず、データをトレーニング、テスト、検証セットに分け、画像の値を 0..1 に正規化し、ロードしましょう。

from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt


# Starting with CIFAR10
(X_train_full, Y_train_full), (X_test, Y_test) = keras.datasets.cifar10.load_data()


X_valid, X_train = X_train_full[:5000]/255.0, X_train_full[5000:]/255.0
Y_valid, Y_train = Y_train_full[:5000], Y_train_full[5000:]


X_test = X_test/255.0


次に、データセットに含まれるいくつかの画像を視覚化し、我々が直面していることのアイデアを得てみましょう。

fig, ax = plt.subplots(5, 5, figsize=(10, 10))
ax = ax.ravel()


# Labels come as numbers of [0..9], so here are the class names for humans
class_names = ['Airplane', 'Automobile', 'Bird', 'Cat', 'Deer', 'Dog', 'Frog', 'Horse', 'Ship', 'Truck']


for i in range(25):
    ax[i].imshow(X_train_full[i])
    ax[i].set_title(class_names[Y_train_full[i][0]])
    ax[i].axis('off')
    plt.subplots_adjust(wspace=1)


plt.show()


アンダーフィッティングの多層パーセプトロン

何をやってもMLPはそれほどうまくいきません。

しかしこの数値は上限があり、おそらくあまり高くはないでしょう。

ネットワークはある時点からオーバーフィッティングを始め、画像を表す具体的なデータ列を学習します。

しかし、オーバーフィッティングを行ってもトレーニングセットでの精度は低く、単にデータにうまく適合できないため、トレーニングを中止するのに最適なタイミングと言えます。

ネットワークの訓練は、二酸化炭素の排出を伴いますからね。

常識的な範囲を超えてネットワークを実行しないように、EarlyStoppingコールバックを追加し、epochsを(EarlyStoppingが効くように)実行する範囲を超えた数値に設定しましょう。

Sequential API を使って、BatchNormalizationDropout でいくつかのレイヤーを追加することにします。

これらは汎化を助けるもので、少なくともこのモデルに何かを学習させるようにしたいのです。

主なハイパーパラメータは、層の数、サイズ、活性化関数、カーネルの初期化、ドロップアウトの率です。

checkpoint = keras.callbacks.ModelCheckpoint("simple_dense.h5", save_best_only=True)
early_stopping = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)


model = keras.Sequential([
  keras.layers.Flatten(input_shape=[32, 32, 3]),
  keras.layers.BatchNormalization(),
  keras.layers.Dense(75),

  keras.layers.Dense((50), activation='elu'),
  keras.layers.BatchNormalization(),
  keras.layers.Dropout(0.1),

  keras.layers.Dense((50), activation='elu'),
  keras.layers.BatchNormalization(),
  keras.layers.Dropout(0.1),

  keras.layers.Dense(10, activation='softmax')
])


model.compile(loss="sparse_categorical_crossentropy",
              optimizer=keras.optimizers.Nadam(learning_rate=1e-4),
              metrics=["accuracy"])


history = model.fit(X_train, 
                    Y_train, 
                    epochs=150, 
                    validation_data=(X_valid, Y_valid),
                    callbacks=[checkpoint, early_stopping])


学習と汎化はある程度できるが、学習セットとテスト・検証セットの両方で精度が低くなり、全体として精度が低くなる。

CIFAR10では、このネットワークは「まあまあ」なパフォーマンスを見せています。

Epoch 1/150
1407/1407 [==============================] - 5s 3ms/step - loss: 1.9706 - accuracy: 0.3108 - val_loss: 1.6841 - val_accuracy: 0.4100
...
Epoch 50/150
1407/1407 [==============================] - 4s 3ms/step - loss: 1.2927 - accuracy: 0.5403 - val_loss: 1.3893 - val_accuracy: 0.5122


では、その学習履歴を見てみましょう。

pd.DataFrame(history.history).plot()
plt.show()


model.evaluate(X_test, Y_test)


313/313 [==============================] - 0s 926us/step - loss: 1.3836 - accuracy: 0.5058
[1.383605718612671, 0.5058000087738037]


全体の精度は50%まで上がり、ネットワークはかなり早くここに到達し、プラトーになり始めます。

5/10の画像が正しく分類されるのはコイン投げのように聞こえますが、ここには10のクラスがあるので、ランダムに推測するとしたら、平均して10枚中1枚の画像を推測することになることを覚えておいてください。

CIFAR100データセットでは、クラスあたりの学習インスタンスが少なく、クラスの数が非常に多いため、少なくとももう少し強力なネットワークが必要になります。

checkpoint = keras.callbacks.ModelCheckpoint("bigger_dense.h5", save_best_only=True)
early_stopping = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)


# Changing the loaded data
(X_train_full, Y_train_full), (X_test, Y_test) = keras.datasets.cifar100.load_data()


# Modify the model
model1 = keras.Sequential([
  keras.layers.Flatten(input_shape=[32, 32, 3]),
  keras.layers.BatchNormalization(),
  keras.layers.Dense(256, activation='relu', kernel_initializer="he_normal"),

  keras.layers.Dense(128, activation='relu'),
  keras.layers.BatchNormalization(),
  keras.layers.Dropout(0.1),


keras.layers.Dense(100, activation='softmax')
])


model1.compile(loss="sparse_categorical_crossentropy",
              optimizer=keras.optimizers.Nadam(learning_rate=1e-4),
              metrics=["accuracy"])


history = model1.fit(X_train, 
                    Y_train, 
                    epochs=150, 
                    validation_data=(X_valid, Y_valid),
                    callbacks=[checkpoint, early_stopping])


このネットワークの性能はかなり悪い。

Epoch 1/150
1407/1407 [==============================] - 13s 9ms/step - loss: 4.2260 - accuracy: 0.0836 - val_loss: 3.8682 - val_accuracy: 0.1238
...
Epoch 24/150
1407/1407 [==============================] - 12s 8ms/step - loss: 2.3598 - accuracy: 0.4006 - val_loss: 3.3577 - val_accuracy: 0.2434


そして、その進歩の履歴をプロットし、テストセット(検証セットと同じようにうまくいく可能性がある)で評価してみましょう。

pd.DataFrame(history.history).plot()
plt.show()


model.evaluate(X_test, Y_test)


313/313 [==============================] - 0s 2ms/step - loss: 3.2681 - accuracy: 0.2408
[3.2681326866149902, 0.24079999327659607]


予想通り、このネットワークはデータをうまく把握することができませんでした。

結局、オーバーフィットの精度は40%で、実際の精度は24%でした。

精度の上限は40%で、限られたアーキテクチャの中で判別できる部分はオーバーフィットさせたとしても、データセットにオーバーフィットさせることはできませんでした。

このモデルは、私の議論のために言えば、本当にオーバーフィットするために必要なエントロピー容量を持っていません。

このモデルとそのアーキテクチャは、単純にこのタスクに適していません。

技術的にはもっと(オーバー)フィットさせることができますが、長期的にはまだ問題があるでしょう。

例えば、より大きなネットワークにして、理論的にはより複雑なパターンを認識できるようにすることができます。

model2 = keras.Sequential([
  keras.layers.Flatten(input_shape=[32, 32, 3]),
  keras.layers.BatchNormalization(),
  keras.layers.Dense(512, activation='relu', kernel_initializer="he_normal"),

  keras.layers.Dense(256, activation='relu'),
  keras.layers.BatchNormalization(),
  keras.layers.Dropout(0.1),

  keras.layers.Dense(128, activation='relu'),
  keras.layers.BatchNormalization(),
  keras.layers.Dropout(0.1),


keras.layers.Dense(100, activation='softmax')
])


でも、これでは全然ダメですね。

Epoch 24/150
1407/1407 [==============================] - 28s 20ms/step - loss: 2.1202 - accuracy: 0.4507 - val_loss: 3.2796 - val_accuracy: 0.2528


もっと複雑(密度が爆発的に)なのに、それ以上抽出することができないのです。

model1.summary()
model2.summary()


Model: "sequential_17"
...
Total params: 845,284
Trainable params: 838,884
Non-trainable params: 6,400
_________________________________________________________________
Model: "sequential_18"
...
Total params: 1,764,324
Trainable params: 1,757,412
Non-trainable params: 6,912


CIFAR10における畳み込みニューラルネットワークのオーバーフィッティング

さて、何か違うことをやってみましょう。

CNNに切り替えると、データセットから特徴を抽出するのに非常に役立ち、それによってモデルが本当にオーバーフィットするようになり、はるかに高い(幻の)精度に到達することができます。

我々は EarlyStopping コールバックを追い出して、CNN にその処理をさせることにする。

さらに、Dropoutレイヤーを使わず、より多くのレイヤーを通してネットワークに特徴を学習させるようにします。

注:議論を証明しようとする文脈以外では、これはひどいアドバイスになります。

これは最後までやりたいこととは正反対です。

ドロップアウトは、ドロップアウトしていないニューロンに弛みを補わせることで、ネットワークの汎化を助ける。

ネットワークがより多くの層を通して学習することを強いられると、過剰適合モデルにつながる可能性が高くなります。

私が意図的にこのようにしている理由は、ネットワークが実際に特徴を識別する能力の証として、恐ろしくオーバーフィットするようにし、その後単純化して Dropout を追加して本当に汎化できるようにするためです。

もし高い(幻の)精度に到達すれば、MLPモデルよりもはるかに多くのものを抽出することができます。

つまり、単純化し始めることができるのです。

もう一度、逐次APIを使ってCNNを構築してみましょう。

まずはCIFAR10データセットで。

checkpoint = keras.callbacks.ModelCheckpoint("overcomplicated_cnn_cifar10.h5", save_best_only=True)


model = keras.models.Sequential([
    keras.layers.Conv2D(64, 3, activation='relu', 
                        kernel_initializer="he_normal", 
                        kernel_regularizer=keras.regularizers.l2(l=0.01), 
                        padding='same', 
                        input_shape=[32, 32, 3]),
    keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
    keras.layers.MaxPooling2D(2),

    keras.layers.Conv2D(128, 2, activation='relu', padding='same'),
    keras.layers.Conv2D(128, 2, activation='relu', padding='same'),
    keras.layers.MaxPooling2D(2),

    keras.layers.Conv2D(256, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(256, 3, activation='relu', padding='same'),
    keras.layers.MaxPooling2D(2),

    keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
    keras.layers.MaxPooling2D(2),

    keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
    keras.layers.MaxPooling2D(2),

    keras.layers.Flatten(),    
    keras.layers.Dense(32, activation='relu'),
    keras.layers.Dense(10, activation='softmax')
])


model.compile(loss="sparse_categorical_crossentropy",
              optimizer=keras.optimizers.Adam(learning_rate=1e-3),
              metrics=["accuracy"])


model.summary()


history = model.fit(X_train, 
                    Y_train, 
                    epochs=150,
                    batch_size=64,
                    validation_data=(X_valid, Y_valid),
                    callbacks=[checkpoint])


素晴らしい!かなり早くオーバーフィットしました。

数エポック以内に、データをオーバーフィットし始め、エポック31までに98%まで上昇し、検証精度は下がりました。

Epoch 1/150
704/704 [==============================] - 149s 210ms/step - loss: 1.9561 - accuracy: 0.4683 - val_loss: 2.5060 - val_accuracy: 0.3760
...
Epoch 31/150
704/704 [==============================] - 149s 211ms/step - loss: 0.0610 - accuracy: 0.9841 - val_loss: 1.0433 - val_accuracy: 0.6958


出力クラスが10個しかないので、不必要に大きなCNNを作ってたくさんオーバーフィットさせてみても、検証精度はかなり高いのです。

CIFAR10での畳み込みニューラルネットワークの簡略化

では、より合理的なアーキテクチャでどうなるかを見るために、単純化してみましょう。

バッチ正規化とドロップアウトを追加して、汎化するのに役立つようにします。

checkpoint = keras.callbacks.ModelCheckpoint("simplified_cnn_cifar10.h5", save_best_only=True)
early_stopping = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)


model = keras.models.Sequential([
    keras.layers.Conv2D(32, 3, activation='relu', kernel_initializer="he_normal", kernel_regularizer=keras.regularizers.l2(l=0.01), padding='same', input_shape=[32, 32, 3]),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(32, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.4),

    keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.4),

    keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.5),

    keras.layers.Flatten(),    
    keras.layers.Dense(32, activation='relu'),
    keras.layers.BatchNormalization(),
    keras.layers.Dropout(0.3),
    keras.layers.Dense(10, activation='softmax')
])


model.compile(loss="sparse_categorical_crossentropy",
              optimizer=keras.optimizers.Adam(learning_rate=1e-3),
              metrics=["accuracy"])


model.summary()


history = model.fit(X_train, 
                    Y_train, 
                    epochs=150,
                    batch_size=64,
                    validation_data=(X_valid, Y_valid),
                    callbacks=[checkpoint, early_stopping])


このモデルの学習可能なパラメータ数は323,146個で、前のCNNの1,579,178個と比べると控えめな数です。

Epoch 1/150
704/704 [==============================] - 91s 127ms/step - loss: 2.1327 - accuracy: 0.3910 - val_loss: 1.5495 - val_accuracy: 0.5406
...
Epoch 52/150
704/704 [==============================] - 89s 127ms/step - loss: 0.4091 - accuracy: 0.8648 - val_loss: 0.4694 - val_accuracy: 0.8500


実はかなりまともな85%の精度を達成しているのです オッカムの剃刀がまた当たった。

では、その結果をいくつか見てみよう。

y_preds = model.predict(X_test)
print(y_preds[1])
print(np.argmax(y_preds[1]))


fig, ax = plt.subplots(6, 6, figsize=(10, 10))
ax = ax.ravel()


for i in range(0, 36):
    ax[i].imshow(X_test[i])
    ax[i].set_title("Actual: %s
Pred: %s" % (class_names[Y_test[i][0]], class_names[np.argmax(y_preds[i])]))
    ax[i].axis('off')
    plt.subplots_adjust(wspace=1)

plt.show()


主な誤判定は、この小さなセットの中の2つの画像です。

犬は鹿と誤判定されましたが(十分立派)、エミューの鳥のクローズアップは猫と分類されました(十分面白いのでそのままにしておきましょう)。

CIFAR100の畳み込みニューラルネットワークのオーバーフィッティング

CIFAR100データセットにするとどうなるか?

checkpoint = keras.callbacks.ModelCheckpoint("overcomplicated_cnn_model_cifar100.h5", save_best_only=True)
early_stopping = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)


model = keras.models.Sequential([
    keras.layers.Conv2D(32, 3, activation='relu', kernel_initializer="he_normal", kernel_regularizer=keras.regularizers.l2(l=0.01), padding='same', input_shape=[32, 32, 3]),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(32, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),

    keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),

    keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),

    keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),

    keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),

    keras.layers.Flatten(),    
    keras.layers.Dense(256, activation='relu'),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(128, activation='relu'),
    keras.layers.BatchNormalization(),

    keras.layers.Dense(100, activation='softmax')
])


model.compile(loss="sparse_categorical_crossentropy",
              optimizer=keras.optimizers.Adam(learning_rate=1e-3),
              metrics=["accuracy"])


model.summary()


history = model.fit(X_train, 
                    Y_train, 
                    epochs=150,
                    batch_size=64,
                    validation_data=(X_valid, Y_valid),
                    callbacks=[checkpoint])


Epoch 1/150
704/704 [==============================] - 97s 137ms/step - loss: 4.1752 - accuracy: 0.1336 - val_loss: 3.9696 - val_accuracy: 0.1392
...
Epoch 42/150
704/704 [==============================] - 95s 135ms/step - loss: 0.1543 - accuracy: 0.9572 - val_loss: 4.1394 - val_accuracy: 0.4458


素晴らしい! 学習セットで ~96% の精度! 検証精度が44%なのは気にしないでください。

モデルを単純化し、より一般化できるようにしましょう。

簡略化した後の一般化の失敗

ここで明らかになったのは、オーバーフィットの能力が、モデルを単純化したときに汎化できることを保証するものではない、ということです。

CIFAR100の場合、1クラスあたりの学習インスタンスが少ないため、以前のモデルを単純化したものではうまく学習できない可能性が高いのです。

試してみましょう。

checkpoint = keras.callbacks.ModelCheckpoint("simplified_cnn_model_cifar100.h5", save_best_only=True)
early_stopping = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)


model = keras.models.Sequential([
    keras.layers.Conv2D(32, 3, activation='relu', kernel_initializer="he_normal", kernel_regularizer=keras.regularizers.l2(l=0.01), padding='same', input_shape=[32, 32, 3]),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(32, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.4),

    keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.4),

    keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.5),

    keras.layers.Flatten(),    
    keras.layers.Dense(256, activation='relu'),
    keras.layers.BatchNormalization(),
    keras.layers.Dropout(0.3),
    keras.layers.Dense(100, activation='softmax')
])


model.compile(loss="sparse_categorical_crossentropy",
              optimizer=keras.optimizers.Adam(learning_rate=1e-3),
              metrics=["accuracy"])


history = model.fit(X_train, 
                    Y_train, 
                    epochs=150,
                    batch_size=64,
                    validation_data=(X_valid, Y_valid),
                    callbacks=[checkpoint, early_stopping])


Epoch 1/150
704/704 [==============================] - 96s 135ms/step - loss: 4.4432 - accuracy: 0.1112 - val_loss: 3.7893 - val_accuracy: 0.1702
...
Epoch 48/150
704/704 [==============================] - 92s 131ms/step - loss: 1.2550 - accuracy: 0.6370 - val_loss: 1.7147 - val_accuracy: 0.5466


プラトー状態になってしまい、なかなかデータを一般化することができません。

この場合、モデルのせいではないかもしれません。

特に、同じ入力形状でデータセット内の画像も似ているCIFAR10データセットで高い精度が得られたことから、このタスクにちょうどいいのかもしれません。

このモデルは、一般的な形状についてはそれなりの精度を出せるようですが、細かい形状の区別はできないようです。

検証精度の点では、より単純なモデルの方がより複雑なモデルよりも実際に性能が良い。

つまり、より複雑なCNNはこれらの細かい詳細を全く理解していないのである。

この問題は、1クラスあたり500枚の学習画像しかなく、十分でないことにあると思われます。

より複雑なネットワークでは、十分な多様性がないため、オーバーフィットにつながります。

オーバーフィットを避けるために単純化すると、これまた多様性がないため、アンダーフィットにつながります。

ということです。

このため、先にリンクした論文の大部分や、ネットワークの大部分は、CIFAR100データセットのデータを補強しているのです。

MNISTの手書き数字データセットとは異なり、純粋に高精度を得るのが容易なデータセットではなく、我々が構築しているような単純なCNNでは、おそらく高精度を得ることはできないでしょう。

ただ、非常に特殊なクラスの数、情報の少ない画像、そして人間がこれらを識別するためにどれだけの予備知識を持っているかということを忘れないでください。

いくつかの画像を補強し、学習データを人為的に拡大することで、少なくとも高い精度を得られるよう最善を尽くしましょう。

CIFAR100は、やはり単純なモデルで高い精度を得るには本当に難しいデータセットであることを念頭に置いてください。

最先端のモデルは、誤差を減らすために様々な新しい技術を使っており、これらのモデルの多くはCNNですらなく、トランスフォーマーなのです。

>
これらのモデルの状況を見たい方は、PapersWithCodeが論文、ソースコード、結果を美しくまとめてくれています。

KerasのImageDataGeneratorクラスによるデータ補強###。

データ補強は役に立つのか?通常はそうですが、私たちが直面しているような深刻なトレーニングデータ不足では、ランダムな回転、反転、切り取りなどでできることがたくさんあります。

もしあるアーキテクチャがデータセットでうまく汎化できない場合、データ増強によってそれを高めることができるでしょうが、それはおそらくそれほど多くはないでしょう。

ということで、Kerasの ImageDataGenerator クラスを使って、モデルの精度を向上させることを期待して、ランダムな変更を加えた新しい学習データを生成してみましょう。

もし改善されたとしても、それは大きな量ではなく、うまく一般化したり、完全にデータをオーバーフィットさせたりする能力なしに、データセットを部分的にオーバーフィットする状態に戻る可能性が高いでしょう。

データに一定のランダムな変動がある場合、変動が「新しい」データに適応し続けるため、モデルは同じエポック数でオーバーフィットする可能性が低くなります。

例えば300エポック実行してみましょう。

これは私たちが学習した他のネットワークよりもかなり多い数です。

これは、画像が流れてくる間にランダムに修正されるためで、大きなオーバーフィッティングをすることなく可能です。

checkpoint = keras.callbacks.ModelCheckpoint("augmented_cnn.h5", save_best_only=True)


model = keras.models.Sequential([
    keras.layers.Conv2D(64, 3, activation='relu', kernel_initializer="he_normal", kernel_regularizer=keras.regularizers.l2(l=0.01), padding='same', input_shape=[32, 32, 3]),
    keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.4),

    keras.layers.Conv2D(128, 2, activation='relu', padding='same'),
    keras.layers.Conv2D(128, 2, activation='relu', padding='same'),
    keras.layers.Conv2D(128, 2, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.4),

    keras.layers.Conv2D(256, 3, activation='relu', padding='same'),
    keras.layers.Conv2D(256, 3, activation='relu', padding='same'),
    keras.layers.Conv2D(256, 3, activation='relu', padding='same'),
    keras.layers.BatchNormalization(),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.4),

    keras.layers.Flatten(),    
    keras.layers.Dense(512, activation='relu'),
    keras.layers.BatchNormalization(),
    keras.layers.Dropout(0.3),
    keras.layers.Dense(100, activation='softmax')
])


train_datagen = ImageDataGenerator(rotation_range=30,
        height_shift_range=0.2,
        width_shift_range=0.2,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True,
        vertical_flip=True,
        fill_mode='nearest')


valid_datagen = ImageDataGenerator()


train_datagen.fit(X_train)
valid_datagen.fit(X_valid)


train_generator = train_datagen.flow(X_train, Y_train, batch_size=128)
valid_generator = valid_datagen.flow(X_valid, Y_valid, batch_size=128)


model.compile(loss="sparse_categorical_crossentropy",
              optimizer=keras.optimizers.Adam(learning_rate=1e-3, decay=1e-6),
              metrics=["accuracy"])


history = model.fit(train_generator, 
                    epochs=300,
                    batch_size=128,
                    steps_per_epoch=len(X_train)//128,
                    validation_data=valid_generator,
                    callbacks=[checkpoint])


Epoch 1/300
351/351 [==============================] - 16s 44ms/step - loss: 5.3788 - accuracy: 0.0487 - val_loss: 5.3474 - val_accuracy: 0.0440
...
Epoch 300/300
351/351 [==============================] - 15s 43ms/step - loss: 1.0571 - accuracy: 0.6895 - val_loss: 2.0005 - val_accuracy: 0.5532


このモデルは検証セットで~55%の性能を発揮していますが、まだ部分的にデータをオーバーフィットしています。

このモデルは検証セットで ~55% のパフォーマンスで、まだ部分的にデータにオーバーフィットしています。

val_loss は下がらなくなり、batch_size を大きくしてもかなり不安定です。

このネットワークは単純に高い精度でデータを学習し適合させることができません。

このネットワークのバリエーションはデータをオーバーフィットさせるエントロピーの能力を持っているにもかかわらずです。

結論は?

オーバーフィッティングは本質的に悪いことではありません。

オーバーフィットのエンドモデルは嫌ですが、疫病神として扱うべきものではなく、より多くのデータと単純化ステップがあれば、モデルがより良いパフォーマンスを示す可能性があることを示す良いサインである可能性もあります。

これは決して保証されたものではなく、CIFAR100データセットは一般化が容易でないデータセットの例として使用されています。

この雑談のポイントは、繰り返しになりますが、逆張りすることではなく、あまり行われていないように見えるこのテーマについての議論を煽ることです。

>
このような主張をする私は誰なのでしょうか?
>
>
>

私は、明日への深い興味を持ちながら、家で練習している人に過ぎません。

>私は誰ですか?
> 私には間違える能力があるのでしょうか?
> >
>
>

とてもあります。

>
> この作品をどのように受け止めるべきでしょうか?
>
>
>

どう受け止めるか – 意味のあるなしは自分で考えてください。

もし、私がこのことを指摘するのは場違いだと思うのであれば、教えてください。

また、この件に関して私が間違っていると思うのであれば、ぜひとも私に教えてください。

タイトルとURLをコピーしました