<p><a href="http://s0sem0y.hatenablog.com/archive">"機械学習","信号解析","ディープラーニング"の勉強

HELLO CYBERNETICS

深層学習、機械学習、強化学習、信号処理、制御工学などをテーマに扱っていきます

交差検証の話

TOP > >

 

 

f:id:s0sem0y:20171024162957p:plain

 

 

スポンサーリンク

交差検証のやり方

交差検証とは

交差検証ってすごく単純です。学習にこれから使うデータをK分割して、K-1個のデータセットを学習に使い残りの1個を検証に使う。ということをK回繰り返す検証方法です。

以下に簡単に図を載せておきます。

 

f:id:s0sem0y:20171024162957p:plain

 

この図の場合、5回算出した各評価値を平均したものを、最終的な評価値として使います。この評価値はAccuracyかもしれませんし、Recallかもしれませんし、何やろうとしているかによって自分たちで決めてから評価を行うことになります。

 

例えば分類というタスクにおいては以下の評価指標が基本的です。

 

s0sem0y.hatenablog.com

 

 

交差検証の実行それ自体はハッキリ言って単純明快です。難しい式も全く必要ありません。しかし、これをやるかやらないかで、まともに性能評価しているか否かが大きく分かれます。もちろん他にも評価方法は沢山あるんですが、どれを使えばいいかわかんないならば、まずはこれを抑えるべきでしょう。

 

 

 

 

scikit-learnのKFold

交差検証はscikit-learnに入っているKFoldクラスを使うと簡単に実行できます。

 

また、使う機械学習手法がscikit-learnによる実装でなければいけないわけではなく、例えばTensorFlowやChainerなどで実装された学習コードを、scikit-learnの交差検証でラッピングすることが可能です。

 

ですから、是非、scikit-learnをメインで使わないにしても、scikit-learnの交差検証のやり方は抑えておきましょう。

 

例えば以下のコードでは150個のデータを5個に分割して、訓練用と検証用のデータセットを作ってくれているのがわかります。ですから、実際には下記のfor文の中に、何かしらの訓練コードと評価指標を計算するコードが書かれていれば良いというわけです。

 

f:id:s0sem0y:20171027132259p:plain

 

また、今回たまたま、学習データを割り切れる分割数にしていたため良かったものの、実は割り切れない数で分割数を指定してしまった場合には以下のようになります。

 

f:id:s0sem0y:20171027133210p:plain

 

全体が150個のデータに対して、分割数を8にした場合、検証データが19だったり18だったりするわけです。

 

また、KFold.spritというメソッドはあくまでIndexを返してくれるだけであることに注意しましょう。引数であるXとyの要素を返してくれるわけではありません。返してくれるのがindexであるおかげで、どのデータが検証用に割り当てられたのかを確認することもできます。以下では150個のデータを15分割した場合に、どのindexに対応するデータが検証用に割り当てられてのかを表示しています。

 

f:id:s0sem0y:20171027134607p:plain

 

ちゃんと被りなく、15回分の検証用データが作成されていそうですね。今、KFlodのインスタンスを作る際にshuffle=Trueを渡しているため、indexをランダムに15分割して取ってくるようになっています。一方で、shuffle=Falseにしておけば以下のように前から順番に検証用データを取ってくるようになります。

 

f:id:s0sem0y:20171027134838p:plain

 

今回のIrisデータというのは、分類問題で各クラスのデータが順番に格納されているため、検証データをランダムに取ってくる設定にしておかないと、最初の方はクラス1に属するデータばかりが検証データに回されることになり、明らかに変な偏りを持った評価が行われることになってしまいます。

 

何か特別な狙いが無い限りは、検証データには均等にデータが入るようにしたほうが良いでしょう(もちろん訓練データの方も均等が望ましい)。

 

交差検証のサンプル(Keras)

Kerasだと学習がfitだけで済むので、今回はとりあえずKerasを使います。別に他のものでも構いません。

 

from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout
import matplotlib.pyplot as plt
from sklearn.model_selection import KFold
from sklearn.datasets import load_iris
import numpy as np
iris = load_iris()
X = iris.data
T = iris.target
## モデル構築
model = Sequential()
model.add(Dense(128, input_shape=(4,)))
model.add((Dropout(0.2)))
model.add(Dense(3))
model.add(Activation("softmax"))
model.compile(loss="categorical_crossentropy",
optimizer="sgd",
metrics=['accuracy'])
kf = KFold(n_splits=5, shuffle=True)
## 5回分のaccuracyの和を入れる(後で割る)
sum_accuracy = 0
## 5回分のaccuracyを1つ1つ保存する
accuracy = []
## 交差検証
for train_idx, val_idx in kf.split(X=X, y=T):
train_x, val_x = X[train_idx], X[val_idx]
train_t, val_t = T[train_idx], T[val_idx]
##学習
model.fit(train_x, np.eye(3)[train_t], batch_size=16, nb_epoch=100)
##評価
predict = model.predict_on_batch(val_x)
predict_class = np.argmax(predict, axis=1)
acc = np.mean((predict_class==val_t).astype(np.float32))
##評価値をリストに追加
accuracy.append(acc)
##今回の評価値を足す
sum_accuracy += acc
print('accuracy : {}'.format(accuracy))
## 5回分の評価値の平均
sum_accuracy /= 5
print('Kfold accuracy: {}'.format(sum_accuracy))
view raw kfold.py hosted with ❤ by GitHub

 

 

 

作ったモデルに対して交差検証を行い獲得した平均的な評価値を、今回のモデルに対する評価値とします。その後、違うハイパーパラメータを設定してみて(今回ならば中間層のユニットとドロップアウトが調整できる)、同じように交差検証を行い、評価値を比較していくことになります。

 

1回の学習に時間が掛からない、そしてハイパーパラメータの数も少ないならば、是非交差検証をしっかり行ってチューニングをしてやるのが良いでしょう。

 

今回はAccuracy使いましたが、別のものでも良いです(というより、問題に応じて適したものを使うべき)。

 

ディープラーニングでの検証

交差検証は見ての通り、1つのモデルを評価するためにデータを分割した数だけ学習を行わなければなりません。すなわち、1回の学習に1時間掛かるとすれば、そのモデルを評価し終えるのは5分割したならば5時間になるわけです。

 

そして、1回の評価(交差検証)で1セットのハイパーパラメータを評価したことになり、ハイパーパラメータの数を考えると途方に暮れてしまいます。ハイパーパラメータを10セットしか評価しないことにしても、これで50時間もの時間を要することになります。

 

ディープラーニングでの学習時間のことを考えると中々交差検証はできないかもしれません。そこで、検証セットを1つだけ準備しておいて、学習の過程を見張ることにします。訓練データに対する評価指標と、検証データに対する評価指標が剥離する(訓練データばかりに良い評価が得られる)ようであれば、その段階で学習を止めてしまうなどの手法が取られます(early stop)。

 

ハイパーパラメータを変えては学習を繰り返して、検証データに対して評価指標が最も良くなるようにハイパーパラメータの調整を繰り返していくことになります(交差検証法に対して、ホールドアウト法と呼ばれる)。

 

これは交差検証に比べれば随分速く1回の評価を終えることができますが、評価としては極めていい加減と言えます。検証データはパラメータの更新自体には使われなくとも、ニューラルネットワークの学習の決め手には明らかに利用されています。人間自体がその検証データに対して上手く行くように調整しすぎている可能性があるのです。

 

したがって、このようなホールドアウト法では検証データとは別に、更に別のテスト専用のデータを準備しておいて、そのデータで最終的な評価を行うのがベスト(というか必須?)です。

 

(学習に用いるデータを「training set」、ハイパーパラメータの調整に使うデータを「validation set」、評価のためだけに使うデータを「test set」と呼びます)

 

s0sem0y.hatenablog.com

 

 

 

最後に

交差検証以外にもブートストラップ法などもあります。

 

まずは可能であれば最も基本的な交差検証をしっかり使うことが重要だと思われます。

 

交差検証の分割数は、基本的に大きい方が正確な検証になります。以下では数値的な実験が行われています。

d.hatena.ne.jp

 

 

 

交差検証と汎化誤差

上記の交差検証に関する話題として、以下の書籍に載っている議論を思い出しました。

 

ベイズ統計の理論と方法

ベイズ統計の理論と方法

 

 

まずサンプルをK個に分割したとします。ここではk番目のサンプルを学習に参加させないとしましょう。すなわち、この時の事後分布p(w|D)におけるDにはk番目のサンプルは含まれていないということです。

 

このような事後分布による、関数F(w)の期待値はEw(k)[F(w)]  と書くことにします(確率変数がwであり、k番目のサンプルを含んでいないことを明示するためにこう書いている)。

 

ここで、k番目のサンプルを除いて訓練した事後分布を使った予測分布p(k)(x)を考えればそれは、

 

p(k)(x)=Ew(k)[p(x|w)]

 

と書き表されます。この右辺というのは、具体的にはベイズの時によく話している

 

wp(x|w)p(w|D)dw

 

というものだと思ってください。ベイズでの予測というのはいつでも考えうる全てのwを反映させるべく、事後分布で重みづけて使うことになります(事後分布を求めるのが学習・訓練)。

 

ともかく、このようにして作られた予測分布を使って、訓練に使わなかったk番目のサンプルXkに対する予測というのができます。

 

p(k)(Xk)=Ew(k)[p(Xk|w)]

 

を計算するということでありますが、これに対する対数損失L(k)

 

L(k)=logp(k)(Xk)=logEw(k)[p(Xk|w)]

 

となります(負の対数を取っているだけ)。このような対数損失の計算が、全ての分割パターン(今回はK個の分割パターン)について計算することができるはずです。したがって、そのK個の損失全てを平均したものをクロスバリデーション損失関数CKとして

 

CK=1Kk=1KL(k)=1Kk=1KlogEw(k)[p(Xk|w)]

 

と書き表すことができます。クロスバリデーションの分割数Kがデータ数nに等しくなっていれば、単に分割数K=nとして

 

Cn=1nk=1nlogEw(k)[p(Xk|w)]

 

と書けます。一方で、互いに独立に発生する訓練データn1個を使って、未知データXを予測する際の対数損失の期待値は

 

EX[logEw[p(X|w)]]

 

と書け、これは機械学習においてぜひとも最小化したいと考えられる「(データn1個で学習した時の)汎化損失Gn1」です。これらを見比べましょう。


Gn1=EX[logEw[p(X|w)]]

 

Cn=1nk=1nlogEw(k)[p(Xk|w)]

 

汎化損失の方は、n1個のデータを使って推測したp(X|w)によって未知データXnを予測する際の対数損失の期待値です。

クロスバリデーション損失の方はk番目のデータを省いたn1個のデータを使って推測したp(X|w)を使って未知データXkを予測する際の対数損失の平均値です。

 

クロスバリデーション損失における未知データXkはあくまで手元にあるデータから訓練に使わず省いたものであり、汎化損失での未知データXは学習に使われていないあらゆるデータを想定しています。したがって汎化損失Gn1とクロスバリデーション損失Cnはそれぞれ完全に同じものではないです。それにしても、その期待値を考えると両者は一致します。

 

E[Cn]=E[Gn1]

 

応用上重要なことは、汎化損失というのはその数式的に表現できても、それを手元のデータから直接計算する手立てはありません。一方で、クロスバリデーション損失は知っての通り、計算することができます。

 

 

上記の議論について

 

実際にクロスバリデーションを行う際に、サンプル数と分割数を一致させるのは時間的な都合上かなり難しいでしょう。また評価指標としても負の対数尤度を使うかは分かりません。更に言えば、式の通り、パラメータwに関する期待値を取るということは、予測分布をパラメータに関して周辺化して積分するということですが、これはベイズの立場の予測を行う際に現れるものであり、通常の機械学習ではwに対して点推定を行うのでこれはできません。

 

とは言ってもベイズの立場での議論を一旦やめることにすれば、とりあえずwに関する期待値を取ることは諦めても良いですし、評価指標として絶対に負の対数尤度を考えなければならないということでもありません(汎化誤差ってもっと広い範囲を指す)。

 

ってことで、

 

 

Gn1=EX[loss(X,w)]

 

Cn=1nk=1n[loss(Xk,w)]

 

くらいに思って、とりあえず出来そうならクロスバリデーション使いましょうという話でした(投げた)。