沢山のパイプライン(pipeline)を1つにまとめて、一気に片付ける方法 

#1.はじめに
　pipelineは、色々な前処理を連結して行う時にコードが簡潔に書けて便利なのですが、今回は、**沢山のパイプライン(pipeline)を1つにまとめて、一気に片付ける方法**がとても便利だったので、備忘録として残します。

#2.下準備
　デモ用のデータセットを、kaggleの **[HR Analytics](https://www.kaggle.com/giripujar/hr-analytics)** からダウンロードします。

　カレントディレクトリーに、**inputフォルダー、outputフォルダー、modelフォルダー**を用意し、ダウンロードしたデータセット **HR_comma_sep.csv** は、**inputフォルダー**に保存します。

![スクリーンショット 2020-01-31 09.54.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/1bd80764-c5dc-70b7-2966-5d9f69c4303a.png)



　HR_comma_sep.csv は、9項目の特徴量を元にその人が退社するかどうかを予測(left列)するデータセットで、全部で14,999行あります。

　kaggleのコンペの様に、この内10,000行をtrain、残り4,999行をtestとし、trainで学習モデルを作って、testの結果を予測する場面を想定します。

```python
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split

# -------------  データセットの作成 ------------------
# データセットを読み込む
df = pd.read_csv('./input/HR_comma_sep.csv')

# 行をシャッフルし、インデックスをリセットし、IDを付加
df = df.sample(frac=1, random_state=1)  
df = df.reset_index(drop=True) 
df = df.reset_index()  
df = df.rename(columns={'index':'ID'})

# 行数でtrain, testに分割
train = df[0:10000]
valid = df[10000:]

# カテゴリー変数をワンホットエンコーディング
df_train = pd.get_dummies(train)
df_valid = pd.get_dummies(valid)

# 正解ラベルと特徴量に分割
y = df_train['left']
X = df_train.drop(['ID','left'], axis=1)
y_valid = df_valid['left']
X_valid = df_valid.drop(['ID','left'], axis=1)

# train とtestに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1)    

print('X_train.shape = ', X_train.shape)
print('y_train.shape =  ', y_train.shape)
print('X_test.shape = ', X_test.shape)
print('y_test.shape = ', y_test.shape)
print('X_valid.shape = ', X_valid.shape)
print('y_valid.shape = ', y_valid.shape)
print()
```
![スクリーンショット 2020-01-30 21.02.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/23e09bce-4b5d-cd48-30a8-5994c765353a.png)
　データセットの行をシャッフル後、trainとtestに分割し、それぞれカテゴリー変数はワンホットエンコーディングして、正解ラベル(y, y_valid)と特徴量(X, X_valid)に分離します。

　さらに、学習モデルを作るための、X, yは、**`train_test_split`**で、学習用(X_train, y_train)と評価用(X_test, y_test)に分割します。これで、下準備は完了です。

#3.パイプラインの設定
　今回は、**前処理付き学習モデルのパイプラインを8本**用意して、**１つの大きなパイプラインにまとめます**。こうすることで、8本のパイプラインを順次動かすことが出来ます。

```python
# -------- パイプラインの設定 -------- 
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.pipeline import Pipeline

pipelines = {    
    'KNN':
        Pipeline([('scl',StandardScaler()),
                  ('est',KNeighborsClassifier())]), 
    'Logistic':
        Pipeline([('scl',StandardScaler()),
                  ('est',LogisticRegression(solver='lbfgs', random_state=1))]), 
    'SVM':
        Pipeline([('scl',StandardScaler()),
                  ('est',SVC(C=1.0, kernel='linear', class_weight='balanced', random_state=1, probability=True))]),
    'K-SVM':
        Pipeline([('scl',StandardScaler()),
                  ('est',SVC(C=1.0, kernel='rbf', class_weight='balanced', random_state=1, probability=True))]),
    'Tree':
        Pipeline([('scl',StandardScaler()),
                  ('est',DecisionTreeClassifier(random_state=1))]),
    'RandomF':
        Pipeline([('scl',StandardScaler()),
                  ('est',RandomForestClassifier(n_estimators=100, random_state=1))]), 
    'GBoost':
        Pipeline([('scl',StandardScaler()),
                  ('est',GradientBoostingClassifier(random_state=1))]),    
    'MLP':
        Pipeline([('scl',StandardScaler()),
                  ('est',MLPClassifier(hidden_layer_sizes=(3,3),
                                       max_iter=1000,
                                       random_state=1))]), 
    }
```

#5.パイプラインの処理
　後は、**`for pipe_name, pipeline in pipelines.items():`**とすると、各パイプラインの先頭にある文字列（例えば、'KNN')が **`pipe_name`** に、各パイプラインのインスタンスが**`pipeline`**に順次入ります。つまり、

**`pipeline.fit(X_train, y_train)`** で学習モデル作成
**`pipeline.predict(X_test)`**  で学習モデルでの予測
**`pickle.dump(pipeline, open(file_name, 'wb'))`** で学習モデルの保存

こんな感じで使えて、非常に便利です。

```python
# ------- パイプラインの処理 ------
from sklearn.metrics import accuracy_score
from sklearn.metrics import log_loss
import pickle

scores = {}
for pipe_name, pipeline in pipelines.items():
    
    # 学習
    pipeline.fit(X_train, y_train)
    
    # 指標計算
    scores[(pipe_name,'test_log')] = log_loss(y_test, pipeline.predict_proba(X_test))
    scores[(pipe_name,'valid_log')] = log_loss(y_valid, pipeline.predict_proba(X_valid))
    scores[(pipe_name,'test_acc')] = accuracy_score(y_test, pipeline.predict(X_test))
    scores[(pipe_name,'valid_acc')] = accuracy_score(y_valid, pipeline.predict(X_valid))
    
    # Submit保存(outputフォルダー)　
    ID=df_valid['ID']
    preds = pipeline.predict_proba(X_valid)  # 予測確率
    submission = pd.DataFrame({'ID': ID, 'left':preds[:, 1]})  
    submission.to_csv('./output/'+pipe_name+'.csv', index=False) 
    
    # モデル保存(modelフォルダー)
    file_name = './model/'+pipe_name+'.pkl'
    pickle.dump(pipeline, open(file_name, 'wb'))

# 指標の表示
df = pd.Series(scores).unstack()
df = df.sort_values('test_acc', ascending=False)
print(df)
```
![スクリーンショット 2020-01-30 21.09.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/145bd129-d39d-7b42-1b9a-3dec5dfba384.png)
　ここでは、8本のパイプラインそれぞれについて、**学習、指標計算(accuracy, logloss)、Submit保存(予測確率)、モデル保存**を行っています。同じ様な処理をまとめて一気にやりたい時にも、**pipeline**は超便利ですね。

　ちなみに、kaggleの場合、`y_valid`は秘密（というか、それを当てるのがkaggle)なので、`valid_acc`、`valid_loss`は計算出来ませんが、今回は分かっているので、付け加えています。^^