scikit-learn の機械学習パイプライン
はじめに
機械学習で予測モデルを作るときは
- データの分割
- データの前処理
- 予測モデルの学習
- クロスバリデーションによるハイパーパラメータチューニング
といった手順を踏む必要がある。慣れるまではこれらの手順に対応する scikit-learn のクラスをひとつひとつ呼び出して自分で一連の処理をやってみるのが勉強になるが、慣れてしまうと似たような手続きを毎回書くのは非常に面倒くさい。
scikit-learn には、この一連の処理を簡潔に記述するためのパイプラインの仕組みがあるので、その使用方法について説明する。
一連のコードは Google Colab 上にアップロードしてある。
データの分割
これは人間が管理すべき問題なので、自動化もやろうと思えばできるだろうが、人間がいちいちやったほうがよい。機械学習をやるとき、データは基本的に
- 訓練データ
- 教師データともいう。予測モデルを学習させるためのデータ
- 検証データ
- 予測モデルが未知のデータにどれくらい当てはまるかを検証するためのデータ
- テストデータ
- 作成した予測モデルが実際の運用時にどれくらいの性能となるか評価するためのデータ
の 3 つに分割することになる。検証データへの当てはまりを見てモデルをチューニングしてもよいが、テストデータへの当てはまりを見てモデルをチューニングするのはズルである[1]。
データの分割は sklearn.model_selection.train_test_split
で行う。検証データはクロスバリデーションのときに訓練データから一部を取り出して作るので、最初は訓練データとテストデータに分割しておけばよい。
データをシャッフルした上で分割するので、分割後のデータが元のデータの何行目のデータなのかを管理するのが面倒になる。pandas.DataFrame
や pandas.Series
の状態で保持しておけばインデックスが維持されるので、特別な理由がなければ pandas.DataFrame
に変換しておくほうがよい[2]。
# 疑似データの生成(ここは適宜データの読み込みに書き換える)
import numpy as np
import pandas as pd
from sklearn.datasets import make_regression
# numpy.ndarray 形式
X_raw, y_raw = \
make_regression(n_samples=300, n_features=50, n_informative=30, random_state=42)
# pandas.DataFrame 形式に変換
X = pd.DataFrame(X_raw)
y = pd.DataFrame(y_raw)
# データの分割
from sklearn.model_selection import train_test_split
# 訓練データ、テストデータの分割
X_train, X_test, y_train, y_test = \
train_test_split(X, y, test_size=0.25, random_state=42)
前処理と予測のパイプライン
読み込んだデータの加工 → モデルのフィッティング までの一連の処理をひとまとめにする仕組みが sklearn.pipeline.Pipeline
である。たとえば StandardScaler
で前処理をしたあとで、Ridge
による回帰を行う場合には以下のようなコードを書く。
# パイプライン
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge
pipe = Pipeline([
('scaler', StandardScaler()),
('regressor', Ridge()),
])
Pipeline
の引数は steps
であり、内部的には named_steps
として保持されている。ここで各ステップにつけた名前は、ハイパーパラメータチューニングの際に使う。
print(pipe.named_steps)
{'scaler': StandardScaler(copy=True, with_mean=True, with_std=True), 'regressor': Ridge(alpha=1.0, copy_X=True, fit_intercept=True, max_iter=None,
normalize=False, random_state=None, solver='auto', tol=0.001)}
クロスバリデーションとハイパーパラメータの探索
GridSearchCV
を使うことで、クロスバリデーションによって検証データへの当てはまり(R2CVスコア)を改善するようにハイパーパラメータの探索を行うことができる。
グリッドサーチとは、空間を格子状に分割し、その格子点をすべて調べることでその中からもっともよい格子点を選ぶ方法である。探索したい空間が2次元(つまりハイパーパラメータが2個)までならワークするが、各次元における格子点の数を
グリッドサーチは実装が簡単なため第一の選択肢に上がるが、格子点上しか探せないため精度にも欠ける。より精密にチューニングしたい場合や、ハイパーパラメータの個数が多い場合には工夫が必要になる[3]。
GridSearchCV
は fit
でハイパーパラメータのチューニングまで行い、predict
でもっともよかったハイパーパラメータの予測器で予測を行ってくれる。
# クロスバリデーションとグリッドサーチによるハイパーパラメータ探索
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import r2_score
from sklearn.metrics.scorer import make_scorer
# ハイパーパラメータの探索範囲の指定
# 「ステップ名__ハイパーパラメータ名」の形式で指定する
params = {
'regressor__alpha': np.logspace(-8, -1, 100),
}
# 評価関数を指定する場合は make_scorer で定義する
scorer = make_scorer(r2_score, greater_is_better=True)
# グリッドサーチの設定
## scoring ... 評価関数を指定する場合はここに書く
## cv ... k-fold CV の k の数。訓練データを何分割するか
## refit ... 最後に一番よかったハイパーパラメータの組み合わせで、すべての訓練データを用いて学習し直す
## n_jobs ... 並列実行の job 数。 -1 を指定すると使用可能なプロセッサをすべて使う
## verbose ... 進捗状況の表示。1,2,3 が指定可能で、数が増えるほど細かい情報が表示される
model = GridSearchCV(pipe, params, scoring=scorer, cv=5, refit=True, n_jobs=-1, verbose=1)
# 学習とハイパーパラメータの探索
model.fit(X_train, y_train.values.flatten())
# 全組み合わせに対するスコアの情報
result = pd.DataFrame.from_dict(model.cv_results_)
# もっともよかったときのR2CVスコア
model.best_score_
# もっともよかったハイパーパラメータ
model.best_params_
# もっともよかったハイパーパラメータで学習した予測器
model.best_estimator_
# テストデータへの当てはまりを確認
from sklearn.metrics import r2_score
r2_score(y_test, model.predict(X_test))
Discussion