[Python] PyCaretでKaggleの回帰問題に挑戦！

# はじめに

[Kaggle 問題にチームで取り組み奮闘記](https://datascienceteam.connpass.com/event/237584/)での発表内容です。

[このKaggleの問題](https://www.kaggle.com/c/tabular-playground-series-jul-2021)に取り組みました。

https://datascienceteam.connpass.com/event/237584/

https://www.kaggle.com/c/tabular-playground-series-jul-2021

## PyCaret でかんたん AutoML

### PyCaret ?

**PyCaret**は、機械学習のワークフローを自動化する Python 製のオープンソース、ローコード機械学習ライブラリです。PyCaret は基本的に、いくつかの**機械学習ライブラリやフレームワークの Python ラッパー**です。

https://techninjahere.medium.com/best-opensource-automl-frameworks-4e40820e2d79

Googleトレンドでも注目度が高いのが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/b4baa10a-d35e-d6ac-fc51-a2f1ea5cb93a.png)

## 使われている主なライブラリ・フレームワーク

### 機械学習ライブラリ

- [scikit-learn](https://scikit-learn.org/stable/index.html)

### 勾配ブースティング × 決定木

- [XGBoost](https://xgboost.readthedocs.io/en/stable/)
- [LightGBM](https://lightgbm.readthedocs.io/en/latest/)
- [CatBoost](http://learningsys.org/nips17/assets/papers/paper_11.pdf)

### 自然言語処理

- [spaCy](https://spacy.io/)

### ハイパーパラメーター 最適化フレームワーク

- [Optuna](https://optuna.org/)
- [Hyperopt](http://hyperopt.github.io/hyperopt/)

### 分散実行フレームワーク

- [Ray](https://www.ray.io/)

## **PyCaretクイックスタート**

Kaggleの問題に取り組む前に、PyCaretをかんたんに動かしてみたいと思います。
回帰問題における PyCaret の最小の流れは、

1. setup()
2. create_model()
3. predict_model()

の流れです。インスタンスを作成したあと、モデルも決め打ちで、ビジュアライズもとくになしで OK であれば、この 3 つの関数に最低限の引数およびデータを入れてあげればすぐに機械学習を実行することが可能です。

このコードを実行するだけで、予測値が出力されます。

```python
# PyCaretの最初単位
# !pip install PyCaret

from PyCaret.datasets import get_data
from PyCaret.regression import *

data = get_data('insurance')        # テストデータの取得

s = setup(data, target = 'charges') # インスタンスの作成

lr = create_model('lr')             # モデルの作成

predictions = predict_model(lr, data=data) # 実行
```

Label列に予測値が入っていることを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/2fa296aa-d825-b3cf-a0db-7f7b499db2d9.png)

## scikit-learn での実装例

これをscikit-learnで実装しようとすると、

- データセットの分割
- 特徴量の取捨
- グラフの作成

などで多くのコードが必要。

![team_de_kaggle_2022-02-28-16-27-32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/dc81bec2-dc75-e5f6-0749-2a5edc2a7976.png)

## 分析開始

ということで、Kaggleの問題に取り組んでいきます。取り組む問題はこちら。

https://www.kaggle.com/c/tabular-playground-series-jul-2021

## 前処理部分は省略します:relaxed:

<details><summary>前処理たくさんしてます。</summary>

### **Library Import**

```python
!pip install openpyxl

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
```

### **DATA LOAD**

```python
train = pd.read_csv('../input/tabular-playground-series-jul-2021/train.csv')
test = pd.read_csv('../input/tabular-playground-series-jul-2021/test.csv')
```

## 前処理

```python
l_data = pd.read_excel('../input/air-quality-time-series-data-uci/AirQualityUCI.xlsx')
l_data.drop(columns = ['NMHC(GT)', 'NO2(GT)'], inplace = True)

# Preprocessing Time Column
l_data['hour'] = 0
for i in range(l_data.shape[0]):
  l_data['hour'][i] = l_data['Time'][i].hour

time_se = l_data['Date'].dt.date - l_data['Date'].dt.date.min()
``

```python
# Making DataFrame to concat with train data!
leak = pd.DataFrame({
    'deg_C' : l_data['T'],
    'relative_humidity' : l_data['RH'],
    'absolute_humidity' : l_data['AH'],
    'sensor_1' : l_data['PT08.S1(CO)'],
    'sensor_2' : l_data['PT08.S2(NMHC)'],
    'sensor_3' : l_data['PT08.S3(NOx)'],
    'sensor_4' : l_data['PT08.S4(NO2)'],
    'sensor_5' : l_data['PT08.S5(O3)'],
    'target_carbon_monoxide' : l_data['CO(GT)'],
    'target_benzene' : l_data['C6H6(GT)'],
    'target_nitrogen_oxides' : l_data['NOx(GT)'],
    'year' : l_data['Date'].dt.year,
    'month' : l_data['Date'].dt.month,
    'week' : l_data['Date'].dt.week,
    'day' : l_data['Date'].dt.day,
    'dayofweek' : l_data['Date'].dt.dayofweek,
    'time' : time_se,
    'hour' : l_data['hour'],
    'working_hours' : l_data['hour'].isin(np.arange(8, 21, 1)).astype("int"),
    'is_weekend' : (l_data["Date"].dt.dayofweek >= 5).astype("int")
})
leak['time'] = leak['time'].apply(lambda x : x.days)
leak
```

### 一つのデータセットにまとめる

```python
all_data = pd.concat([train, test])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/b507f0d0-79a9-5d76-90d3-538fa752ec21.png)

### 日付の前処理

```python
all_data['date_time'] = pd.to_datetime(all_data['date_time'])
all_data['year'] = all_data['date_time'].dt.year
all_data['month'] = all_data['date_time'].dt.month
all_data['week'] = all_data['date_time'].dt.week
all_data['day'] = all_data['date_time'].dt.day
all_data['dayofweek'] = all_data['date_time'].dt.dayofweek
all_data['time'] = all_data['date_time'].dt.date - all_data['date_time'].dt.date.min()
all_data['hour'] = all_data['date_time'].dt.hour
all_data['time'] = all_data['time'].apply(lambda x : x.days)
# all_data["is_winter"] = all_data["month"].isin([1, 2, 12])
# all_data["is_sprint"] = all_data["month"].isin([3, 4, 5])
# all_data["is_summer"] = all_data["month"].isin([6, 7, 8])
# all_data["is_autumn"] = all_data["month"].isin([9, 10, 11])
all_data["working_hours"] =  all_data["hour"].isin(np.arange(8, 21, 1)).astype("int")
all_data["is_weekend"] = (all_data["date_time"].dt.dayofweek >= 5).astype("int")
all_data.drop(columns = 'date_time', inplace = True)
all_data
```

### 外れ値前処理

```python
leak = leak.reset_index(drop = True)
all_data = all_data.reset_index(drop = True)

out_index = []

for col in leak.columns[:8]:
    out_index.append(leak[leak[col] == -200].index)

for i in range(8):
    leak.loc[out_index[i], leak.columns[i]] = all_data.loc[out_index[i], leak.columns[i]]
```

```python
all_data['dayofweek'] = all_data['dayofweek'].astype(object)
# all_data['month_c'] = all_data['month'].astype(object)
# all_data['hour_c'] = all_data['hour'].astype(object)
leak['dayofweek'] = leak['dayofweek'].astype(object)
all_data = pd.get_dummies(all_data)
leak = pd.get_dummies(leak)
all_data['SMC'] = (all_data['absolute_humidity'] * 100) / all_data['relative_humidity']
all_data['Dew_Point'] = 243.12*(np.log(all_data['relative_humidity'] * 0.01) + (17.62 * all_data['deg_C'])/(243.12+all_data['deg_C']))/(17.62-(np.log(all_data['relative_humidity'] * 0.01)+17.62*all_data['deg_C']/(243.12+all_data['deg_C'])))
```

```python
leak['SMC'] = (leak['absolute_humidity'] * 100) / leak['relative_humidity']
leak['Dew_Point'] = 243.12*(np.log(leak['relative_humidity'] * 0.01) + (17.62 * leak['deg_C'])/(243.12+leak['deg_C']))/(17.62-(np.log(leak['relative_humidity'] * 0.01)+17.62*leak['deg_C']/(243.12+leak['deg_C'])))
```

```python
train2 = all_data[:len(train)]
test2 = all_data[len(train):].reset_index(drop = True)

train2 = pd.concat([train2, leak]).reset_index()
train2.drop(columns = 'index', inplace = True)
```

## **Scaling**

### **Log Scaling**

```python
def log_scaling(col):
  col = np.log1p(col)
  return col
```

```python
cols = ['target_carbon_monoxide', 'target_benzene', 'target_nitrogen_oxides']
for col in cols:
  train2[col] = log_scaling(train2[col])
```

## **ビジュアライゼーションと比較**

ログをとることにより、目的変数の値の幅を少なくし、ばらつきも正規分布に近いかたちになりました。

```python
fig, ax = plt.subplots(len(cols), 2, figsize=(12,12))
n = 0
for i in cols:
  sns.histplot(train[i], ax=ax[n, 0]);
  sns.histplot(train2[i], ax = ax[n, 1]);
  n += 1

fig.tight_layout()
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/e21945c3-ff5c-b469-fa5d-e4eac9927725.png)

## データセットの分割

今回のコンクールでは、目的変数が３つあるため、`Train`と`test`でそれぞれ 3 つの`DataSet`を作成します。

```python
train_3 = train2.drop(columns = ['target_carbon_monoxide', 'target_benzene', 'target_nitrogen_oxides'])
test_3 = test2.drop(columns = ['target_carbon_monoxide', 'target_benzene', 'target_nitrogen_oxides'])

train_co = train2.drop(columns = ['target_benzene', 'target_nitrogen_oxides'])
train_be = train2.drop(columns = ['target_carbon_monoxide', 'target_nitrogen_oxides'])
train_no = train2.drop(columns = ['target_carbon_monoxide', 'target_benzene'])
```

</details>

## **PyCaret**の使用開始

### インストールとライブラリのインポート

Kaggle（及びGoogleColab）のカーネルにはプリインストールされていないので、`pip install`をおこないます。

```python
!pip install pycaret

from pycaret.regression import *
```



## PyCaret でモデルを作成

Kaggle の問題に戻ります。PyCaret の関数は下記を使用します。

1. setup()
2. compare_models()
3. blend_models()
4. tune_model()
5. plot_model()
6. evaluate_model()
7. predict_model()
8. finalize_model()

### **setup()**

この関数は学習環境を初期化し、変換パイプラインを作成します。Setup 関数は他の関数を実行する前に呼び出す必要があります。この関数は、`data`と`target`という 2 つの必須パラメーターを受け取ります。その他のパラメーターは任意です。

設定可能項目はこんなにあります:angel:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/f769c484-38d9-d102-7d31-5aac34a3373e.png)

```python
exp = setup(data=train_co,
        target='target_carbon_monoxide')
```

上記コードを実行するとデータ・タイプの一覧が出力され、Enter を押すと処理が続行、**Quit**をテキスト入力すると処理が中止されます。

![team_de_kaggle_2022-02-26-17-37-34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/447cc30b-779e-ef07-7523-dda28f0d6a1f.png)

処理が終わるとデータセットの情報を出してくれます。

![team_de_kaggle_2022-02-26-17-56-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/4a6ae4e9-d1b4-f43d-0b46-98b4bc173628.png)

### **compare_models()**

回帰用にモデル 20 個以上が使えるようになっています。
それぞれのモデルで交差検証をして評価指数順に並び替えて表示。
デフォルトの評価指数は決定係数になっているので注意。

```python
top3 = compare_models(sort='RMSLE', n_select=3, fold = 3)
```

![team_de_kaggle_2022-02-26-17-54-53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/450b5826-3865-4937-622e-0593da31b938.png)

### **blend_models()**

estimator_list パラメーターで渡されたモデルに対して、Voting Regressor を学習します。

```python
blended = blend_models(estimator_list= top3, fold=3, optimize='RMSLE')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/dc706a49-efaf-59be-0083-af4da07624b2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/35112a94-e558-d6df-e55f-8eb013faecec.png)

![team_de_kaggle_2022-02-27-10-52-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/bbf3c565-7e32-13ba-da25-200e4490f662.png)

### **tune_model()**

```python
tuned_blended = tune_model(blended)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/dd63adee-8119-fad7-42e3-0cfc5bd379bc.png)

### **比較**

左がハイパーパラメーターの実行前、右が実行後です。チューニングを行ったことで、重み（Weights）のパラメーターが変わっていることが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/d70b1165-6d52-11df-02a2-0e0036dba1db.png" width=49%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/da494f11-d580-efa1-357d-a10a542d0826.png" width=49%>

### **plot_model()**

モデルの性能をビジュアライズをして解析できます。様々な種類のグラフを引数を変えるだけでかき分けることができます。

- ‘residuals_interactive’ - Interactive Residual plots
- ‘residuals’ - Residuals Plot
- ‘error’ - Prediction Error Plot
- ‘cooks’ - Cooks Distance Plot
- ‘rfe’ - Recursive Feat. Selection
- ‘learning’ - Learning Curve
- ‘vc’ - Validation Curve
- ‘manifold’ - Manifold Learning
- ‘feature’ - Feature Importance
- ‘feature_all’ - Feature Importance (All)
- ‘parameter’ - Model Hyperparameter
- ‘tree’ - Decision Tree

https://qiita.com/ground0state/items/57e565b23770e5a323e9

### Prediction Error Plot 予測誤差

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/db8b6ac8-08b5-4e25-8782-992fac538f71.png)

### Residuals Plot 残差プロット

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/dbbffe62-623e-06c1-26d0-514d0626203a.png)

### Cook's Distance Plot クックの距離

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/b011fbe9-f8b5-e540-de64-26398c7a005f.png)

### Feature Importance 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/0d6fdd3e-4118-27e0-34d9-98e7fe8397df.png)

## **evaluate_model()**

学習済みモデルの性能を分析するためのユーザインタフェースを表示します。内部で`plot_model`関数を呼び出しています。ボタンを押したときに計算を実行するので、データ量や選ぶグラフによっては結果表示に時間がかかる場合があります。

```python
evaluate_model(tuned_blended)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/d2146abd-f9be-e576-9245-d4ae498d85e0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/6830219f-9527-7f5a-e082-dfb744fc1529.png)

### **finalize_model()**

データセット全体に対して学習させます。

```python
final_model = finalize_model(tuned_blended)
```

### **predict_model()**

学習されたモデルを用いて`Label`列に予測を入れる。

```python
pred_esb = predict_model(tuned_blended)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/b0898509-018b-b15c-9dd4-57f539f9c1ff.png)

### **submit**

サブミット用データの作成をします。
対数を元の数値に変換するため、`np.exp`関数を使用。

```python
sub = pd.read_csv('../input/tabular-playground-series-jul-2021/sample_submission.csv')

sub['target_carbon_monoxide'] = np.exp(pred_esb['Label'])-1
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/f66248de-1b7b-2495-c1a2-7902f00b2871.png)

### AutoMLの関数化

上記まで書いたのがPyCaretを使用したAutoMLの一連の流れですが、KaggleやGoogleColabのNotebook上でそれぞれ実行を繰り返すのが少し手間がかかります。ですので、これらの手順をまとめて関数にし、必要な引数を入力すれば自動で予測まで実行してくれるようにします。

```python
def pycaret_model(train, target, test, n_select, fold, opt, exclude):
  print('Setup Your Data....')
  setup(data=train,
        target=target,
        silent= True)

  print('Comparing Models....')
  top3 = compare_models(sort=opt, n_select=n_select, fold = fold, exclude = exclude)

  print('Blending Models....')
  blended = blend_models(estimator_list= top3, fold=fold)

  print('Tuning Models....')
  tuned_blended = tune_model(blended)

  print('Finallizing Models....')
  final_model = finalize_model(tuned_blended)

  print('Done...!!!')

  pred_esb = predict_model(final_model, test)
  re = pred_esb['Label']

  return re
```

### 'target_benzene' と 'target_nitrogen_oxides' の予測

自作関数を使用して残り2つを予測していきます。関数化したので、コードもシンプルです。

```python
sub['target_benzene'] =
  np.exp(
    pycaret_model(
      train_be, 'target_benzene', test_3, 3, 10, 'RMSLE', ['knn', 'xgboost'])
      )-1


sub['target_nitrogen_oxides'] =
  np.exp(
    pycaret_model(
      train_no,
      'target_nitrogen_oxides', test_3, 3, 10, 'RMSLE', ['xgboost'])
      ) - 1
```

### 後処理

<details><summary>後処理してます。</summary>

### 仕上げ ①

```python
leak_sub =
  pd.read_excel(
    '../input/air-quality-time-series-data-uci/AirQualityUCI.xlsx'
  )
  [7110:].reset_index(
      drop = True
)
```

### 仕上げ ②

```python
co_out = leak_sub[leak_sub['CO(GT)'] == -200].index
be_out = leak_sub[leak_sub['C6H6(GT)'] == -200].index
ni_out = leak_sub[leak_sub['NOx(GT)'] == -200].index

leak_sub.loc[co_out, 'CO(GT)'] = sub.loc[co_out, 'target_carbon_monoxide']
leak_sub.loc[be_out, 'C6H6(GT)'] = sub.loc[be_out, 'target_benzene']
leak_sub.loc[ni_out, 'NOx(GT)'] = sub.loc[ni_out, 'target_nitrogen_oxides']

sub['target_carbon_monoxide'] = leak_sub['CO(GT)']
sub['target_benzene'] = leak_sub['C6H6(GT)']
sub['target_nitrogen_oxides'] = leak_sub['NOx(GT)']
```

</details>

### submit 用の csv を作成

```python
sub.to_csv('sub.csv', index = 0)
```

これでサブミット用のCSVを作成すれば基本は作業終了です。
が、ここから、各関数の引数をいろいろいじくってみようと思います。

## 試行錯誤の開始

### setup() 引数追加

```python
exp = setup(data=train_co,
        target='target_carbon_monoxide',
        normalize = True,                 # 数値列を標準化
        numeric_imputation = 'mean',      # 欠損値の穴埋め
        silent= True)                     # データ型の確認なし
```

### blend_models() 引数追加

```python
blended = blend_models(
  estimator_list= best,
  fold=10,              # 交差検証の回数
  optimize='RMSLE',     # 評価指数
  choose_better = True  # 結果がよくなるときのみブレンドされたモデルを返す
  )
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/feef9d6e-5a75-1e79-5ede-660859d61df0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/433b967e-2eba-efff-7029-c4c1e642ba79.png)

### サーチライブラリの変更

ハイパーパラメーターのチューニングに`optuna`を使用。
デフォルトは`scikit-learn`

```python
# !pip install optuna
tuned_blended = tune_model(
  blended,
  optimize='RMSLE',
  search_library="optuna",    # optuna の使用
  choose_better = True,
  n_iter=30
  )
```

### tune_model()の使用順を変更

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/f9906507-5dc3-66ba-9ffd-a73f56765134.png)

## **SCORE**

試行錯誤してみたものの、やればやるほどスコアがあがっていくわけではありませんでした。いいスコアがでたり、でなかったり。一筋縄ではいかないなぁ・・という感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/439bc3eb-a342-bc1c-d123-477ab2cac2b1.png)

## 作成したNoteBook

実際に作成したNoteBookのリンクを貼ります。自分の環境にコピーして実行してみてください。

https://www.kaggle.com/akihirosuto/team-de-kaggle-suto

## まとめ

PyCaretは導入から実行までかんたん便利:hugging:

ドキュメントを読み、

- 各関数の使用順
- 引数でできること

頭の整理に役立つなぁと思いました。

## appendix

PyCaret公式のチュートリアルがボリュームも有りわかりやすかったので、日本語に翻訳をして公式リポジトリにプルリクを送ってみました。見事マージされ、コントリビューター一覧にも掲載:couplekiss:

チュートリアルの翻訳はまだ回帰しかやっていないのですが、いずれ他も取り組みたいと思います！協力してもらえる人も大募集:metal:

https://github.com/pycaret/pycaret

![team_de_kaggle_2022-03-01-16-43-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/58e97599-5ed3-6d12-d6b3-b69bf2bd18b2.png)

![team_de_kaggle_2022-03-01-16-46-56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80467/f0ea0386-4342-bf04-7023-44a6a848d423.png)