Kaggle learnで学ぶ機械学習の基礎（中級編）

https://qiita.com/k-ysd/items/7205b0dbc4d6cb36021c

前回の初級編に引き続き、中級講座で機械学習の基礎を学習します。

https://www.kaggle.com/learn/intermediate-machine-learning

今回も７回構成で、各回は解説パート(tutorial)と実践パート(exercise)に分かれています。データ前処理の方法・少ないデータでの評価方法・人気のモデルなど、非常に実践的な内容が多かったです。中級講座を終える頃にはデータサイエンティストの入口に立った気分になれるのではないでしょうか。

前半では住宅価格問題のモデルを毎回作ります。だんだんとスコアが上がっていくのが楽しかったです。

| 回 | スコア |
|:-:|:-:|
| 初級編最終 | 21217 |
| 中級編1 | 20998  | 
| 中級編2 | 16619  |   
| 中級編3 | 16479  |   
| 中級編4 | 16380  |   

以下、要点のメモです。

## 1.はじめに

中級講座全体の紹介のみです。

## 2.欠損値

実際に使うデータにはたいてい欠損値（null項目）が含まれます。欠損値があると予測精度がかなり下がってしまうので、これを処理する方法を学びます。処理の方法は３つあります。

1. 列ごと捨てる
1. 平均値などで補完する
1. 補完した上で、欠損値があることを示す列を追加する

補完する場合、平均値で補完するのがよいか・中央値で補完するのがよいか、などデータの特性によりますよね。一般には、よほど欠損値だらけではない限り、列ごと捨てるよりも補完して生かしたほうが良いと思います。

１）列ごと捨てる
```python
# null値を含む列を捨てる
cols_with_missing = [col for col in X_train.columns
                     if X_train[col].isnull().any()]
reduced_X_train = X_train.drop(cols_with_missing, axis=1)
```

２）平均値などで補完する
```python
from sklearn.impute import SimpleImputer
my_imputer = SimpleImputer(strategy='mean')
imputed_X_train = pd.DataFrame(my_imputer.fit_transform(X_train))

# 列名が消えてしまうので、再セットする
imputed_X_train.columns = X_train.columns
```

３）補完した上で、欠損値があることを示す列を追加する
```python
for col in cols_with_missing:
    X_train_plus[col + '_was_missing'] = X_train_plus[col].isnull()
    
# 以下、２）と同じく補完する
```


## 3.カテゴリー変数

「とてもよい」・「ややよい」や「好きなメーカーを選べ」のようにアンケートの選択肢は連続した数値になりません。このような変数はカテゴリー変数と呼ばれます。カテゴリー変数を前処理なしに機械学習モデルに入れるとうまくいきません。処理の方法は３つあります。

1. 列ごと捨てる
1. 順序エンコーディング
1. ダミー変数

共通）カテゴリー変数列の取得
```python
s = (X_train.dtypes == 'object')
object_cols = list(s[s].index)
```

１）列ごと捨てる
```python
drop_X_train = X_train.select_dtypes(exclude=['object'])
```

２）順序エンコーディング
「とてもよい」・「ややよい」のように値に順序がある場合にはこの方法が適しています。
```python
from sklearn.preprocessing import OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
label_X_train[object_cols] = ordinal_encoder.fit_transform(X_train[object_cols])
```

３）ダミー変数
「赤」「青」などの値に対して、それぞれ「赤列」「青列」という列を追加する方法です。「赤」がセットされたデータであれば、「赤列」に1をセットし、元の列を削除します。ただし、この方法は、値の種類が多すぎる（一般には15種類以上）とうまく機能しません。
```python
from sklearn.preprocessing import OneHotEncoder
OH_encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)
OH_cols_train = pd.DataFrame(OH_encoder.fit_transform(X_train[object_cols]))

# 列名が消えてしまうので、再セットする
OH_cols_train.index = X_train.index

# カテゴリー変数列を削除する
num_X_train = X_train.drop(object_cols, axis=1)

# ダミー変数列を結合する
OH_X_train = pd.concat([num_X_train, OH_cols_train], axis=1)

# すべての列に文字列であることを設定する
OH_X_train.columns = OH_X_train.columns.astype(str)
```

## 4.パイプライン

データ前処理とモデリングなど、複数の処理を１つにまとめます。

最初、なんでこれが必要なのかピンとこなかったんですが、こちらの記事でよく理解できました。訓練データと検証データに同じ処理を行う場合などにすっきりとバグの少ないコードを作ることができます。

https://qiita.com/c60evaporator/items/7829d8d8322a407d54f1

また、５回目で取り上げる交差検証でもパイプラインを使わないと複雑になってしまいます。

```python
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
# 数値列の処理
numerical_transformer = SimpleImputer(strategy='constant')

# カテゴリー変数列の処理。補完とダミー変数処理を直列に合わせる
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
])

# 数値列の処理とカテゴリー変数列を合わせる
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numerical_transformer, numerical_cols),
        ('cat', categorical_transformer, categorical_cols)
    ])

model = RandomForestRegressor(n_estimators=100, random_state=0)
# 前処理とモデルを直列に合わせる
my_pipeline = Pipeline(steps=[('preprocessor', preprocessor),
                              ('model', model)
                             ])
my_pipeline.fit(X_train, y_train)
preds = my_pipeline.predict(X_valid)
```


## 5.交差検証（クロスバリデーション）

モデルがどのくらい妥当であるのか確かめるために検証データが重要ですが、十分な量の検証データを得られない場合もあります。そんな場合に有効な手法が交差検証です。

例えば、データを５分割したとします。最初は１つ目を検証データ・他の４つを訓練データにします。次に２つ目を検証データ・他の４つを訓練データにします。これを繰り返すことでデータのすべての行に基づく測定が可能となります。

データが数分以内で実行できる程度に少なければ、交差検証を試す価値があります。ただし、各検証でスコアが近いのであれば、わざわざ交差検証を行う必要はありません。

```python
from sklearn.model_selection import cross_val_score

scores = -1 * cross_val_score(my_pipeline, X, y,
                              cv=5,
                              scoring='neg_mean_absolute_error')
```

## 6.XGBoost

複数のモデルを組み合わせる手法をアンサンブルと呼びます。初級編で学んだランダムフォレストも決定木によるアンサンブルの一つです。今回学習する勾配ブーストもまたアンサンブルの一つです。

まずはじめに１つのモデルを用意します。これは不正確なもので構いません。このモデルで予測を行い、損失関数を計算します。
次に損失関数が小さくなるような新しいモデルを訓練します。この新しいモデルをアンサンブルに加えます。これを繰り返すのが勾配ブーストです。

今回はXGBoost（エクストリーム勾配ブースト）ライブラリを使います。影響の大きいパラメータは次のとおりです。
* n_estimators: 繰り返し数。一般には100~1000程度ですが、後述するlearning_rateに依存します。
* early_stopping_rounds: n_estimatorsに達しなくても、この回数分改善しなくなったら停止します。
* learning_rate: アンサンブルの各モデルから予測値を加算する前に掛ける値です。
* n_jobs: データを並列処理します。コア数と等しい値を設定するのが一般的です。

```python
from xgboost import XGBRegressor

my_model = XGBRegressor(n_estimators=1000, learning_rate=0.05, n_jobs=4)
my_model.fit(X_train, y_train, 
             early_stopping_rounds=5, 
             eval_set=[(X_valid, y_valid)], 
             verbose=False)
```

## 7.データリーク

この回で学ぶのはテクニックというより考え方です。実践編でもコードを書くのではなく、考え方を問われます。

データリークは、訓練ではとあるデータ項目を使ってうまくいくのですが、実際に予測する場合にはそのデータ項目が使えず、精度の高い予測ができない場合に発生します。２種類のデータリークが存在します。

ターゲットリーク：
時間経過などを考慮すると、実際に予測する時点では使えないデータ項目を使って訓練してしまう場合に発生する現象です。例えば、肺炎になるかどうかを予測する問題を予測するために、「抗生物質を使った」という項目を使用するケースを考えます。抗生物質を使用するのは、肺炎であると判断された後であることが多いです。そのため、抗生物質を使った場合には肺炎である可能性が高く、相関関係が高くなります。しかしながら、予測時点（例えば診察を受ける直前）では、「抗生物質を使った」という情報はまだ得られていないので、このようなデータ項目は除外する必要があります。

訓練と評価の汚染：
評価では、訓練時に使ってないデータを使用する必要があります。訓練データと評価データが混ざってしまった状態を汚染と呼びます。例えば、訓練データと評価データに分割する前の段階で欠損値補完などの前処理をしてしまう、などのケースが考えられます。５回目で学習したパイプラインを使い、パイプラインの中で前処理を行うことで汚染を防ぐことができます。

# 最後に

非常に実践的な内容が多かったので、ここまで学習するとkaggleのコンペに挑戦できそうな気がしますね。次は先人がコンペに投稿したコードから実例を学びたいと思います。