Edited at

グリッドサーチでPCAをするか決める


環境


  • Windows 10

  • Python 3.7.1

  • scikit-learn 0.20.0


この記事でやろうとしていること

機械学習においてメジャーな次元削減法に、PCAがあります。

例えば、PCAで次元を減らしてからSVR(Support Vector Machine Regression)にかける、なんて使い方をするかと思います。

この場合、PCAのパラメーター(成分数: n_componentsとか)とSVRのパラメーター(CとかEpsilonとか)を決める必要があります。

scikit-learnのPipelineGridSearchCVを使えば、これらのパラメーターをいっぺんにグリッドサーチしてくれます。

しかし、GridSearchCVが検証してくれるのは、全パラメーターを組み合わせたパターンのみです。

PCAをかけないパターンも含めていっぺんに検証してほしいのです。

例えば、こんな例を見てください。


データ準備

例えば、ボストンの住宅価格のデータを使って回帰予測をします。

from sklearn.datasets import load_boston

housing = load_boston()

データの内容については、このあたりの記事をご覧ください。

train_test_splitでトレーニングデータとテストデータに分けます。

from sklearn.model_selction import train_test_split

X_train, X_test, y_train, y_test = train_test_split(housing.data, housing.target,
train_size=0.75, test_size=0.25)


例1) PCA -> SVRのPipelineで解析する

まず、Pipelineに含めるEstimatorを設定します。

from sklearn.svm import SVR

from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
pl1 = Pipeline([['pca', PCA(random_state=2018)],
['svr', SVR(kernel='linear')]])

続いて、パラメーターの候補を与えます。

prms1 = {'pca__n_components': [0.1, 0.5, 0.9],

'svr__C': [0.1, 0.5, 1.],
'svr__epsilon': [0.05, 0.10, 0.20]}

PipelineとパラメーターをGrid Searchにかけます。

from sklearn.model_selection import GridSearchCV

gs1 = GridSearchCV(pl1, prms1, n_jobs=-1, return_train_score=True, cv=5, verbose=10)
gs1.fit(X_train, y_train)

テストデータで精度を検証します。

スクリーンショット (30).png

Pearsonの相関係数で.527。

ふむ、こんなものか。


例2) PCAなしでSVRをする

さっきはPCAをSVRに併用して.527の精度を出してましたが、PCAなしだとどうなるでしょう。

prms2 = {'C': [0.1, 0.5, 1.],

'epsilon': [0.05, 0.10, 0.20]}
gs2 = GridSearchCV(SVR(kernel='linear'), prms2, n_jobs=-1, return_train_score=True, cv=5, verbose=10)
gs2.fit(X_train, y_train)

テストデータで精度検証

スクリーンショット (29).png

PCA使わない方が精度がいい!

こんなことがあるので、PCAをかけないパターンも一緒に検証してほしいのです。


都合のいいPCAクラス

GridSearchCVは、パラメーターの全組み合わせしか検証してくれません。

逆に、PCA側がパラメーター次第で「なにもせず」データを返してくれればよいのです。

そんな都合のいいPCAクラスを作成します。


クラスを作る

scikit-learnは、PipelineやGridSearchCVに投げるクラスを自作する方法を用意してくれています。

このあたりの記事を参照しつつ、都合のいいクラスを作ってみました ↓

from sklearn.base import BaseEstimator, TransformerMixin

from sklearn.decomposition import PCA
class myPCA(BaseEstimator, TransformerMixin):
def __init__(self, n_components=0, random_state=None):
self.n_components = n_components
self.random_state = random_state

def fit(self, X, y=None):
if self.n_components==0:
return self
self.pca = PCA(n_components=self.n_components, random_state=self.random_state)
self.pca.fit(X)
return self

def transform(self, X, y=None):
if self.n_components==0:
return X
return self.pca.transform(X)

n_componentsが0の場合は、PCAを行わず、データXをそのまま返すようにしています。

0以外の時は、普通にscikit-learnのPCAをかけて返します。


クラスをインポートする

この都合のいいクラスmyPCAを、別の.pyファイルに書いて保存し、importで呼び出します。

こうしないと、並列演算をしたときにエラーが出ます。

from mypca import myPCA


クラスを試す

先ほどの、例1と同じ設定でmyPCAを試してみましょう。

prms3 = prms1

pl3 = Pipeline([['pca', myPCA(random_state=2018)],
['svr', SVR(kernel='linear')]])
gs3 = GridSearchCV(pl3, prms3, n_jobs=-1, return_train_score=True, cv=5, verbose=10)
gs3.fit(X_train, y_train)

スクリーンショット (32).png

ちゃんとさっきと同じ結果が出ました。

では、n_componentsの候補に0を加えてやってみます。

prms4 = {'pca__n_components': [0, 0.1, 0.5, 0.9],

'svr__C': [0.1, 0.5, 1.],
'svr__epsilon': [0.05, 0.10, 0.20]}
pl4 = pl3
gs4 = GridSearchCV(pl4, prms4, n_jobs=-1, return_train_score=True, cv=5, verbose=10)
gs4.fit(X_train, y_train)

スクリーンショット (33).png

例2(PCAなしでSVRのみ)の時と同じ結果が出ました。

PCAを使わないパターンも試して、最良の組み合わせを返してくれたわけです。


まとめ

自分でクラス関数を作ることによって、「そのEstimatorを使わないパターン」も含めてGridSearchCVが検証してくれました。

今回はPCAでやりましたが、ほかの前処理についても同じように適用できるのではないかと思います。

注)

この記事には色々と間違いがあるかもしれないので、何かに使う場合はご注意ください。

もっと楽にやる方法があったら教えてください。

 

今回使ったコードのipynbファイル