機械学習では、元のデータセットに対して前処理や推論フェーズが何段にも重なることがある。 scikit-learn には、そういった何段にも重なった処理を表現しやすくするために Pipeline という機能が備わっている。 今回は、その Pipeline を使ってみることにする。
使った環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.13.5 BuildVersion: 17F77 $ python -V Python 3.6.5
下準備
まずは scikit-learn と必要なパッケージをあらかじめインストールしておく。
$ pip install numpy scipy scikit-learn
続いて Python のインタプリタを起動しておく。
$ python
データセットについては Iris データセットを使う。
>>> from sklearn import datasets >>> iris = datasets.load_iris() >>> X, y = iris.data, iris.target
これで下準備はできた。
Pipeline 機能を使ってみる
Pipeline では、その名のごとく一連の処理をパイプラインで表現する。 具体的には、リストに scikit-learn のオブジェクトを適用する順番で格納しておく。
以下にサンプルコードのパイプラインを示す。 このパイプラインでは、まず前処理として主成分分析 (PCA) した上で、それをランダムフォレストで分類できる。 リストには処理の名前と、その処理を実行するオブジェクトを対にしたタプルを入れる。
>>> from sklearn.pipeline import Pipeline >>> from sklearn.decomposition import PCA >>> from sklearn.ensemble import RandomForestClassifier >>> steps = [ ... ('pca', PCA()), ... ('rf', RandomForestClassifier()) ... ] >>> pipeline = Pipeline(steps=steps)
PCA とランダムフォレストを選んだこと自体には特に意味はなくて、あくまでこんな風にできるよという例。
Pipeline オブジェクトは scikit-learn のオブジェクトが一般的に持っているインターフェースを備えている。
なので、以下のような感じで fit()
メソッドで学習した上で predict()
メソッドで推論、といういつもの流れが使える。
>>> from sklearn.model_selection import train_test_split >>> X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42) >>> pipeline.fit(X_train, y_train) >>> y_pred = pipeline.predict(X_test)
精度自体は今回の目的ではないけど、一応計算しておく。
>>> from sklearn.metrics import accuracy_score >>> accuracy_score(y_test, y_pred) 0.98
GridSearchCV と組み合わせて使う
Pipeline は GridSearchCV と組み合わせることもできる。 GridSearchCV を使うと、ハイパーパラメータの中から性能の良い組み合わせを自動的に探してくれる。
Pipeline と GridSearchCV を組み合わせて使うときは、パラメータ名の指定に工夫が必要になる。
具体的には <処理の名前>__<パラメータ名>
という形式で指定する。
>>> from sklearn.model_selection import GridSearchCV >>> params = { ... 'pca__n_components': range(1, X.shape[1]), ... 'rf__n_estimators': [10, 100, 1000], ... } >>> grid_search_cv = GridSearchCV(pipeline, param_grid=params, cv=4)
ふつうの scikit-learn オブジェクトと GridSearchCV を組み合わせるときは単純にパラメータ名だけで良かった。 それに対して Pipeline には複数のオブジェクトが含まれるため、それを識別するための名前が追加で必要になる。
GridSearchCV#fit()
メソッドでハイパーパラメータの探索処理が走る。
>>> grid_search_cv.fit(X, y)
処理が終わったら best_params_
を参照することで最適なパラメータの値が分かる。
>>> grid_search_cv.best_params_ {'pca__n_components': 2, 'rf__n_estimators': 1000}
Pipeline に組み込むオブジェクトを自作する
ここまでは scikit-learn オブジェクトをそのまま Pipeline に組み込むパターンを試した。 次は Pipeline に自作した前処理や推論フェーズの処理を組み込んでみよう。 とはいえ、これには自作したオブジェクトに scikit-learn のインターフェースをダックタイピングで用意するだけで良い。
まずは前処理をするオブジェクトから。 この処理では受け取った値を 2 で割ったものに変換する。 もちろん、この処理自体には特に意味はない。
>>> class MyPreprocessor(object): ... """前処理 (最低限 fit, transform メソッドが必要)""" ... def fit(self, X, y): ... """学習フェーズ""" ... return self ... def transform(self, X): ... """変換フェーズ""" ... # 2 で割った余りを返す ... return X % 2 ... def predict(self, X): ... """分類フェーズ""" ... # 何もせずに素通しする ... return X ...
続いては Pipeline の最後段で推論をするオブジェクト。 このオブジェクトは受け取った値が 0 か非 0 かで True/False を返す処理をする。 こちらも、もちろん処理自体には特に意味はない。
>>> class MyClassifier(object): ... """回帰・分類処理 (fit メソッドが必要)""" ... def fit(self, X, y): ... """学習フェーズ""" ... return self ... def predict(self, X): ... """分類フェーズ""" ... # X が 0 か非 0 かで True/False を返す ... return X == 0 ...
上記で用意した自作オブジェクトを組み込んだ Pipeline を作る。 このパイプラインでは値を 2 で割った余りに変換した上で、それが 0 なら True をそうでなければ False を返す。 これはようするに、一連の処理で引数が偶数か奇数かを判定することになる。
>>> steps = [ ... ('pre', MyPreprocessor()), ... ('clf', MyClassifier()), ... ] >>> pipeline = Pipeline(steps=steps)
データとしては 0 から 9 までのリストを使う。
>>> import numpy as np >>> X = np.arange(10)
Pipeline にデータを渡してみよう。
今回に関しては実は fit()
関数は使ってないんだけど、とりあえず機械学習なら普通は使うので呼んでおく。
>>> pipeline.fit(X) >>> pipeline.predict(X) array([ True, False, True, False, True, False, True, False, True, False])
うまいこと受け取った引数が偶数か奇数かを判定できている。
めでたしめでたし。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログ (1件) を見る