学習曲線からの過学習検知

過学習の検知と訓練データとテストデータの分離について

通常、私たちは既に得られている過去や現在のデータを用いた将来のデータの予測を考えることが多いと思います。
そこで、既に得られているデータを例えば9:1や8:2の割合で訓練データとテストデータに分け、訓練データを過去や現在のデータ、テストデータを予測されるべき将来のデータとして見なします。
次に訓練データの学習をした後に、テストデータを用いてモデルがどれだけ上手く将来の値を予測出来るのかを評価します。
時々、訓練データの学習は上手くいくが、テストデータに対する学習は上手くいかないことがあり、これを過学習と呼んでいます。
それは訓練データに現れないようなデータがテストデータに含まれる場合があるということであり、微妙な変化にも影響を受けないような頑健さ(ロバストさ)を持つモデルの構築が目標となります。
今回はこの過学習問題を学習曲線を通じて見ていきたいと思います。

Required Libraries

  • matplotlib 2.0.2
    学習曲線の描画に使用
  • numpy 1.12.1
    行列を効率的に扱うために使用
  • scikit-learn(sklearn) 0.18.2
    データのフェッチや単純な前処理、分類レポートの作成に使用
In [1]:
# This module is needed to calculate division with python2.x
from __future__ import division, print_function
import matplotlib.pyplot as plt
import numpy as np

from sklearn.datasets import fetch_mldata
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelBinarizer
from sklearn.metrics import confusion_matrix, classification_report

import renom as rm
from renom.cuda import cuda
from renom.optimizer import Sgd
cuda.set_cuda_active(False)

Dataset

  • MNIST dataset
MNIST dataset は0から9までの70000枚の数値画像のデータセットになっています。
それぞれの画像ファイルは28ピクセルx28ピクセルの正方形の画像となっており、それぞれのピクセルはグレイスケールの値で表現されています。(0が黒で255が白)
これらの数値画像のピクセル値を基に、それぞれの画像が0から9までの、どの数値の画像なのかを分類できるモデルを作成します。

学習に必要な前準備

  • データの読み込み
  • 前処理

データの読み込み

MNIST dataset が data_home ディレクトリにダウンロードされます。

In [2]:
mnist = fetch_mldata('MNIST original', data_home='.')

X = mnist.data
y = mnist.target

前処理

  • 正規化
    それぞれの説明変数が異なるスケールを持っている時、同じスケールで表現されたデータと比べて学習が難しくなるケースが存在します。
  • データを訓練データとテストデータに分割
    データを学習データと予測データに分割
    実際には学習データに現れないデータが予測用データに含まれることがあります。
  • ラベルデータのワンホットベクトル化
    カテゴリカルなデータはonehotベクトルに変換する必要があります。
In [3]:
X = X.astype(np.float32)
X /= X.max()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1)
labels_train = LabelBinarizer().fit_transform(y_train).astype(np.float32)
labels_test = LabelBinarizer().fit_transform(y_test).astype(np.float32)

Neural Network Model

  • 主にニューラルネットワークの定義にはsequnentialモデルとfunctionalモデルの2種類が存在します。Sequentialモデルは理解しやすく書きやすい、functionalモデルは柔軟に記述が出来るモデルとなっています。 Sequential Model Functional Model
In [4]:
class Model(rm.Model):

    def __init__(self):
        super(Model, self).__init__()
        self._layer1 = rm.Dense(250)
        self._layer2 = rm.Dense(500)
        self._layer3 = rm.Dense(10)

    def forward(self, x):
        t1 = self._layer1(x)
        t2 = self._layer2(t1)
        out = self._layer3(t2)
        return out

学習データのミニバッチ学習

Learning parameters

  • バッチサイズ
    データが非常に大きい時、一度に全てのデータを計算することがメモリ的に難しい場合や、計算に用いる重みの行列データが大きくなり計算スピードが遅くなってしまう場合があります。
    一般的にニューラルネットワークでは、mini-batch にデータを分けて学習が行われます。
    つまり、全データをバッチサイズで規定される小さなグループに分割を行います。
  • Epoch
    Epoch は全てのデータに対する学習の反復回数になります。
    大きすぎる epoch は overfitting を引き起こし、小さすぎる epoch は underfitting を起こし、学習が足りない状態となります。
    したがって、適切な epoch 数を探索する必要があります。
  • Optimizer
    Optimizer は学習の更新方法を決めています。
    更新方法と学習の方法は optimizer によって異なります。
  • 学習率
    学習率は学習のステップサイズとなります。
    学習率が高すぎる時、 loss の値は上手く下がっていかなかったり、学習率が小さすぎる場合は収束するまでに時間がかかり過ぎてしまったりします。
In [5]:
batch = 256
epoch = 100
N = len(X_train)
optimizer = Sgd(lr = 0.05)
model = Model()

learning_curve = []
test_learning_curve = []

for i in range(epoch):
    perm = np.random.permutation(N)
    loss = 0
    for j in range(0, N // batch):
        train_batch = X_train[perm[j * batch:(j + 1) * batch]]
        responce_batch = labels_train[perm[j * batch:(j + 1) * batch]]

        # The computational graph is only generated for this block:
        with model.train():
            l = rm.softmax_cross_entropy(model(train_batch), responce_batch)

        # Back propagation
        grad = l.grad()

        # Update
        grad.update(optimizer)

        # Changing type to ndarray is recommended.
        loss += l.as_ndarray()

    train_loss = loss / (N // batch)

    # Validation
    test_loss = rm.softmax_cross_entropy(model(X_test), labels_test).as_ndarray()
    test_learning_curve.append(test_loss)
    learning_curve.append(train_loss)
    if i % 10 == 0:
        print("epoch %03d train_loss:%f test_loss:%f"%(i, train_loss, test_loss))
epoch 000 train_loss:0.483988 test_loss:0.337702
epoch 010 train_loss:0.264421 test_loss:0.288495
epoch 020 train_loss:0.253830 test_loss:0.284980
epoch 030 train_loss:0.248383 test_loss:0.280919
epoch 040 train_loss:0.245048 test_loss:0.283287
epoch 050 train_loss:0.242510 test_loss:0.291389
epoch 060 train_loss:0.240432 test_loss:0.290518
epoch 070 train_loss:0.238449 test_loss:0.293869
epoch 080 train_loss:0.237188 test_loss:0.289975
epoch 090 train_loss:0.236099 test_loss:0.285034

評価

  • Confusion matrix, 精度、再現率、F1スコア
    ニューラルネットワークの結果の評価には主に2種類あり、一つは分類の評価のための手法、もう一つは回帰の評価のための手法となります。
    分類精度、再現率、F1スコアはよくモデルの分類性能の評価に用いられます。
    精度と再現率、F1スコアは主に分類問題に対するニューラルネットワークの性能評価に使われます。更に分類の場合には、分類が間違っているケースについて false positive と true negative の2種類が存在し、confusion matrixを見ることでどのように分類が間違っているのかを確認することができます.
  • learning curve はニューラルネットワークの出力と正しいラベルとの差になります。
    Learning curve からそれぞれの学習ステップで loss の減少過程を見ることが出来ます。
    我々はこの学習曲線を通してoverfittingやunderfittingといった学習に関する問題についての情報を得ることが出来ます。
In [6]:
predictions = np.argmax(model(X_test).as_ndarray(), axis=1)

# Confusion matrix and classification report.
print(confusion_matrix(y_test, predictions))
print(classification_report(y_test, predictions))

# Learning curve.
plt.plot(learning_curve, linewidth=3, label="train")
plt.plot(test_learning_curve, linewidth=3, label="test")
plt.title("Learning curve")
plt.ylabel("error")
plt.xlabel("epoch")
plt.legend()
plt.grid()
plt.show()
[[650   0   0   0   1   2   2   0   1   1]
 [  0 740   3   3   1   4   0   2   5   0]
 [  6   7 664   6   7   4  10   7  14   5]
 [  9   3  13 658   0  21   3   7  15  10]
 [  2   1   4   0 643   0   4   3   1  22]
 [  7   2   4  31  10 544  17   2  14  11]
 [  8   3   9   1   6  10 683   1   1   0]
 [  3   3   8   2  10   1   0 680   0  25]
 [ 11  18   2  18   9  14   7   3 571  11]
 [  6   1   0  11  27   4   1  17   3 606]]
             precision    recall  f1-score   support

        0.0       0.93      0.99      0.96       657
        1.0       0.95      0.98      0.96       758
        2.0       0.94      0.91      0.92       730
        3.0       0.90      0.89      0.90       739
        4.0       0.90      0.95      0.92       680
        5.0       0.90      0.85      0.87       642
        6.0       0.94      0.95      0.94       722
        7.0       0.94      0.93      0.94       732
        8.0       0.91      0.86      0.89       664
        9.0       0.88      0.90      0.89       676

avg / total       0.92      0.92      0.92      7000

../../../_images/notebooks_basic_algorithm_Detecting_overfitting_problem_from_learning_curve_notebook_14_1.png

まとめ

ニューラルネットワークの学習においてはニューラルネットワークの出力と正解のラベルの差を意味する loss を最小化することが目標になります。
今回の場合は、ニューラルネットワークの入力が28x28=784次元のベクトルとなり、0から9までの10種類の教師ラベルが存在するために、ニューラルネットワークの出力は Softmax 関数で活性化された10次元のベクトルとなります。
つまり、ニューラルネットワークから得られる出力が完全に教師ラベルと一致、つまり予測が100%正解する場合については loss は0となります。
loss の値については train loss と test loss の2種類の loss が存在します。
学習時、ニューラルネットワークは train loss を小さくする方向に学習を行います。
しかし、目的はモデルがテストデータに対して良い予測性能を持つこと、つまり未来のデータの予測が上手くいくモデルとなることです。
この train loss と test loss の推移を学習曲線から見ることができます。
学習曲線を見ると、およそ 20 epoch 付近からわずかに test lossが上昇していることが確認できます。
このように train loss と test loss が学習時に乖離していくことを過学習と呼び、深刻な過学習ほどそれらの学習曲線は乖離していくことになります。
私たちは最も低い test loss になるときのモデルが一番上手く学習できているモデルだと考えています。これらの過学習現象を防ぎ、予測性能を向上させるために、 Dropout や Batch Normalization といった手法を使うこと、更には epoch や learning rate, Batch size などのパラメータを調整することが役立つ可能性があります。
  • Dropout
    Dropoutがニューラルネットワーク中で使われると、ニューラルネットワークは入力データに対して全ての unit ではなく、一部の unit を使って計算を行うようになります。
    定義されたレイヤーのユニットのいくつかが、dropout ratio で規定される割合に応じてランダムに欠損します(計算されません。)
  • バッチ正規化
    Batch normalization は該当レイヤーに入力されるバッチサイズ分のデータだけに対して正規化を行うことになります。
    一般的に正規化はネットワークへの入力前に全てのデータに対してスケールを揃えることを目的としていますが、バッチ正規化は各レイヤーの前で定義され、バッチサイズで与えられるレイヤーへの入力データに対して正規化を行います。

Reference