まずは蝋の翼から。

学んだことを書きながら確認・整理するためのメモブログ。こういうことなのかな?といったことをふわっと書いたりしていますが、理解が浅いゆえに的はずれなことも多々あると思うのでツッコミ歓迎

交差検証法あれこれ

交差検証

何故モデルの評価に交差検証が必要か書く。

学習元は「Pythonではじめる機械学習」。

簡易的な汎化性能の評価

汎化性能を評価する簡単な方法は、

  1. train_test_split関数でデータを訓練セットとテストセットに分割
  2. 訓練セットに対してfit関数を用いてモデルを作成
  3. モデルに対し、テストセットへscore関数を用いて、正解率を出す。
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

iris = load_iris()
X = iris.data # 特徴量
y = iris.target # 結果


# 訓練セットとテストセットに分割
X_train, X_test, y_train, y_test = train_test_split(X,y,random_state=0)

# モデルのインスタンスを生成し、訓練データで学習
logreg = LogisticRegression().fit(X_train,y_train)

# テストセットでモデルを評価
print(logreg.score(X_test,y_test)) #=>0.868421052632

train_test_split関数は、データをランダムに分割するのでscoreは偶然に左右されやすい。 そのため一般的には、交差検証(cross varidation)が用いられる。これはモデルの汎化性能を評価する統計的な手法となる。

交差検証では、全てのデータを訓練に1回用いて複数の検証結果をまとめるため偶然に左右されづらく、また分割数が多いほど訓練に用いるデータ数が多くなり効率的に学習ができる。

交差検証

k分割交差検証

最も一般的な交差検証として、k分割交差検証(k-fold cross-validation)がある。

これは、データをk分割し、それらを用いる検証法。kは5 ~ 10が一般的。

例えばk = 5としてそれぞれの分割に数字を振ると、検証①ではテストデータとしてセット1、訓練データとしてセット1~5を使う。次に検証②ではテストデータとしてセット2、訓練データとしてセット1,3,4,5を使う。これをk=5回繰り返すことで検証scoreが5つ求まる。

このとき、一般的にはこの5つの検証scoreの平均値を評価の指標にする。

from sklearn.model_selection import cross_val_score

score = cross_val_score(logreg, iris.data, iris.target, cv = 5) #分類器として5分割を指定
score #=> array([ 1. ,  0.96666667,  0.93333333,  0.9,  1. ])
score.mean() #=> 0.96000000000000019

なお、標準的なk分割交差検証ではデータを前からk分割する。しかし、クラス分類器では、こデータの入り方に偏りがある場合適切なscoreを返さないため一般的には層化k分割交差検証(stratifiedk k-fold cross-validation)を用いる。これは、各分割データセットに含まれるクラスの比率が同じになるように分割がされる。なお、回帰問題ではあまり関係がないので、sklearnのcross_val_score関数は標準的なk分割交差検証が実装されている。

デメリットとして、k分割交差検証は分割数が多くなるほど時間がかかる。

1つ抜き交差検証(leave-one-out)

1つ抜き交差検証は、テストデータとして1データを用い、訓練データとして他の全てのデータを用いる手法。

大規模データにおいては時間がかかるが小規模データに関して短時間で検証ができる。

from sklearn.model_selection import LeaveOneOut
loo=LeaveOneOut()

score = cross_val_score(logreg, iris.data, iris.target, cv = loo) #分類器としてLeaveOneOut()を指定
score.mean() #=> 0.95333333333333337

シャッフル分割交差検証(shuffle-splitcross-valid)

シャッフル分割交差検証は、テストデータ数、訓練データ数、繰り返し数を指定して検証する手法(浮動小数点で指定した場合は割合)。

from sklearn.model_selection import ShuffleSplit
shuffle_split=ShuffleSplit(test_size=0.5,train_size=0.5,n_splits=10) #テスト、訓練に50%ずつ選び、10回繰り返す


score = cross_val_score(logreg, iris.data, iris.target, cv = shuffle_split) #shuffle_splitを指定
score.mean() #=> 0.95733333333333326

また、test_size + train_size < 1.0を指定することでデータの一部だけを用いた検証をおこなうことができる(サブサンプリング)。これは大規模データで効率的に検証をおこないたいときに有用。

グリッドサーチ

Grid Searchはパラメータに対して指定した値の全組み合わせを試してパラメータ最適化を行う方法。

そのとき、テストデータを用いて最適化をおこなうと、「テストデータを用いて最適化したパラメータにテストデータで検証する」こととなり、汎化性能が下がる。そのため、データをテストデータ、モデルを構築する訓練データ、パラメータを最適化する検証データの3つに分ける。そして、パラメータチューニングをしたあとに、訓練データと検証データを結合させ訓練データ2を作成して、最適なパラメータを用いて再度訓練データ2(訓練データ+検証データ)でモデルを構築しなおして、テストデータで性能の評価をおこなう。
訓練データ2を作成した理由は、訓練用データの量を増やすことでデータを有効活用するのが理由。

このフローは言い方を変えるならば、「データをテストデータと訓練データに分け、その後訓練データを訓練データ(小)と検証データに分ける」と考えても良い(以下のプログラムではその流れ)。

交差検証を用いたグリッドサーチ

当たり前だがグリッドサーチにおいても、訓練データと検証データを分割する際にデータの分割内容によって性能が大きく異なる。そのため、グリッドサーチを利用するときも交差検証を用いる。

scikit-learnでは、探索したいパラメータの辞書型オブジェクトを作成する。 そして、GridSearchCVクラスの引数に、モデル、サーチすべきパラメータのグリッド、使用したい交差検証戦略を指定してインスタンスを生成する。

from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC

# サーチするパラメータのグリッド
param_grid={'C':[0.001,0.01,0.1,1,10,100],
              'gamma':[0.001,0.01,0.1,1,10,100]}

# モデルSVC、作成したグリッド、交差検証戦略は5分割層化交差検証
grid_search = GridSearchCV(SVC(), param_grid, cv=5)

#テストデータと訓練データに分ける
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, random_state=0)

# 訓練データを用いてモデル構築。この際、cv=5にしているので訓練データは内部で訓練データ(小)と検証データに分かれて5分割層化交差検証されている
grid_search.fit(X_train,y_train) 

grid_search.score(X_test,y_test)
# =>0.97368421052631582

見つけたパラメータはbest_params属性に、交差検証精度(そのパラメータ設定のさまざまな分割に対する平均精度)はbestscore属性に格納されている。

grid_search.best_params_
# => {'C': 100, 'gamma': 0.01}

grid_search.best_score_
# => 0.9732142857142857 (訓練データ(小)と検証データでのスコア)

グリッドサーチの計算は高価なので、まずは広めのレンジで粒度を粗目にパラメータを指定し、実際の値を確認し、狙いを定めた後に細かい指定をする流れが効率的。

scores=np.array(results.mean_test_score).reshape(6,6)

# 平均交差検証スコアのプロット
mglearn.tools.heatmap(scores, xlabel='gamma', xticklabels=param_grid['gamma'],
                     ylabel='C', yticklabels=param_grid['C'], cmap="viridis")

f:id:chito_ng:20190214093753p:plain

ネストした交差検証

先程はテストデータと訓練データ(訓練データ(小)+検証データ)は1度だけ分割しているので、テストデータの分割のされかたに評価が依存する。そのため、テストデータと訓練データへの分割に対しても交差検証をおこない何度も分割した方がより適切な評価となる。

scikiit-learnではcrossvalscoreをGridSearchCVのインスタンスをモデルとして呼び出すことでネストした交差検証をおこなうことができる。

なお、ネストした交差検証でおこなえることはあくまで「あるモデルのあるデータに対する評価」であり、「良い予測モデルの構築」そのものではないことに注意。

# cross_val_score(GridSearchを用いたSVCクラス分類モデル, データ)

scores = cross_val_score(GridSearchCV(SVC(), param_grid, cv=5), # 下行で分けた訓練データが訓練データ(小)と検証データに5分割層化交差検証
                         iris.data,iris.target,cv=5) # irisデータがテストデータと訓練データデータに5分割層化交差検証
print(scores) # =>[ 0.96666667  1.          0.96666667  0.96666667  1.        ]
print(scores.mean()) # => 0.98