AutoGluon超入門

# はじめに
AutoGluonというAutoMLのライブラリを使おうとしたときに、使い方を結構調べたので自分がやりたかったことをまとめておきます。

# AutoGluonとは
AWSが提供しているAutoMLのライブラリです。テーブルデータはもちろん、画像、物体検出、自然言語など様々なデータ型に対して使用できるようです。
AutoMLのライブラリと聞くと、lazypredictのようなデータを準備して、標準化などの前処理をした上でいろんなモデルを評価してくれるようなものを思い浮かべますが、AutoGluonはデータを準備するだけでよく、カテゴリ変数のエンコーディングなども勝手にやってくれる優れものです。

[AutoGluon-Tabular: Robust and Accurate AutoML for Structured Data](https://arxiv.org/abs/2003.06505)

# AutoGluonの使い方
本記事ではsklearn.datasetのfetch_california_housingを使って、テーブルデータx回帰を扱います。基本的な使い方は抑えられると思います。

## インストール
pipでインストールできます。kaggleノートブックではautogluon.tabularとしないと嫌な表示がされるようです。

```python
!pip install autogluon
!pip install autogluon.tabular # Kaggle notebook
```

## 一番簡単な例
シンプルな例を示します。コード中ではあえて明示している部分があります。もっと削れば3行でいけます。

### データの準備
sklearn.datasetのfetch_california_housingを使います。元のデータセットだとすべて数値ですが、前処理が必要ないことを示すためにあえてカテゴリ変数を作ります。
AutoGluonはpandasのデータフレーム型で渡さないといけません。polars等で読み込んだ場合、.to_pandasをする必要があります。
公式ドキュメントではTabularDataset(.\データ.csv)で読み込んでいますが、pandasでも大丈夫なのでなじみがあるpandasで書いていきます。

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

california = fetch_california_housing()
df = pd.DataFrame(california.data, columns=california.feature_names)
df['PRICE'] = np.array(california.target)

def house_age(age):
    if age <= 10:
        age = 'new'
    elif age > 10 and age <= 30:
        age = 'mid'
    else:
        age = 'old'
    return age
df['HouseAge'] = df['HouseAge'].map(house_age)

train, test = train_test_split(df, test_size=0.2, random_state=42)
print(f"TRAIN:{train.shape}, TEST:{test.shape}")

# 出力
TRAIN:(16512, 9), TEST:(4128, 9)
```

### 学習
学習の流れとしてはlightGBMなどと同じでmodelを定義して、fitで学習させます。
どういう風に学習しているのかはここでは言及しません。[こちらの記事](chrome-extension://efaidnbmnnnibpcajpcglclefindmkaj/https://pages.awscloud.com/rs/112-TZM-766/images/1.AWS_AutoML_AutoGluon.pdf)や[論文](https://arxiv.org/abs/2003.06505)に説明があります。

TabularPredictとfitのパラメータは下記のとおりです。
**TabularPredict**
* lable : 目的変数のカラム名を指定します。Autogluonでは説明変数, 目的変数に分けなくてもここで指定するだけで良きにしてくれます。
* problem_type : 解きたい問題の種類です。今回は回帰なのでregressionを指定します。2値分類ならbinary、マルチクラスならmulticlassを指定します。
* eval_metric : 評価関数です。思いつくものは大体使えると思います。
**fit**
* train_data : 訓練データです。
* test_data : lightGBMのvalid_setみたいな感じだと思います。必須ではありません。
* presets : 自動で組んでくれるモデルの数とか重さを選べます。下表中で上に行くほど精度がよく重くなります。必須ではありません。
* time_limit : １つのモデルで学習に使う時間です。単位は秒です。presetsと連動させる必要があり、例えばpresetsでbest_qualityを選んでいるのにtime_limitが小さすぎるとエラーが出ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/811003/70170233-aacb-e9cd-3b00-7fb37d2806e4.png)
[公式ドキュメント](https://auto.gluon.ai/stable/tutorials/tabular/tabular-essentials.html#presets)

コードを書いていきます。
学習から結果確認まで簡単にできます。
結果の確認はleaderboardとfit_summary()のどちらでも見れます。
* leaderboard : 簡単にどのモデルが一番良かったか確認できる。見やすい。
* fit_summary : 使った説明変数とかモデルのハイパーパラメータも表示される。ちょっとみにくい。

leaderboardやfit_summaryでの評価関数の表示がマイナスになっていますが、すべてx-1で表示される仕様のようです。

**学習まで**
```python
import autogluon
from autogluon.tabular import TabularDataset, TabularPredictor
model = TabularPredictor(
    label='PRICE',
    problem_type='regression',
    eval_metric='mape'
    )

predictor = model.fit(
    train_data=train,
    test_data=test,
    presets='optimize_for_deployment', 
    time_limit=5, 
    verbosity=0 # ログの表示
)
```
**leaderboard**
```python
predictions = predictor.predict(test)
predictor.leaderboard(test)

# 出力
model	score_test	score_val	eval_metric	pred_time_test	pred_time_val	fit_time	pred_time_test_marginal	pred_time_val_marginal	fit_time_marginal	stack_level	can_infer	fit_order
0	KNeighborsDist	-0.442327	-0.437835	mean_absolute_percentage_error	0.032931	0.029457	0.034693	0.032931	0.029457	0.034693	1	True	1
1	WeightedEnsemble_L2	-0.442327	-0.437835	mean_absolute_percentage_error	0.034929	0.029457	0.077695	0.001998	0.000000	0.043002	2	True	2
```

**fit_summary()**
```python
predictor.fit_summary()

# 出力
*** Summary of fit() ***
Estimated performance of each model:
                 model  score_val                     eval_metric  pred_time_val  fit_time  pred_time_val_marginal  fit_time_marginal  stack_level  can_infer  fit_order
0  WeightedEnsemble_L2  -0.437835  mean_absolute_percentage_error       0.029457  0.077695                0.000000           0.043002            2       True          2
1       KNeighborsDist  -0.437835  mean_absolute_percentage_error       0.029457  0.034693                0.029457           0.034693            1       True          1
Number of models trained: 2
Types of models trained:
{'KNNModel', 'WeightedEnsembleModel'}
Bagging used: False 
Multi-layer stack-ensembling used: False 
Feature Metadata (Processed):
(raw dtype, special dtypes):
('category', []) : 1 | ['HouseAge']
('float', [])    : 7 | ['MedInc', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', ...]
{'model_types': {'KNeighborsDist': 'KNNModel',
  'WeightedEnsemble_L2': 'WeightedEnsembleModel'},
 'model_performance': {'KNeighborsDist': -0.4378350753111248,
  'WeightedEnsemble_L2': -0.4378350753111248},
 'model_best': 'WeightedEnsemble_L2',
 'model_paths': {'KNeighborsDist': ['KNeighborsDist'],
  'WeightedEnsemble_L2': ['WeightedEnsemble_L2']},
 'model_fit_times': {'KNeighborsDist': 0.034693241119384766,
  'WeightedEnsemble_L2': 0.04300189018249512},
 'model_pred_times': {'KNeighborsDist': 0.02945685386657715,
  'WeightedEnsemble_L2': 0.0},
 'num_bag_folds': 0,
 'max_stack_level': 2,
 'model_hyperparams': {'KNeighborsDist': {'weights': 'distance'},
  'WeightedEnsemble_L2': {'use_orig_features': False,
   'valid_stacker': True,
   'max_base_models': 0,
   'max_base_models_per_type': 'auto',
   'save_bag_folds': True}},
 'leaderboard':                  model  score_val                     eval_metric  \
 0  WeightedEnsemble_L2  -0.437835  mean_absolute_percentage_error   
 1       KNeighborsDist  -0.437835  mean_absolute_percentage_error   
```
### いろいろ確認
学習に使われた説明変数を確認します。
説明変数が多いとfit_summary()ではすべて表示されないようなので、ちゃんと確認したいときはfeature_metadataで確認できます。

```python
feature_metadata = predictor.feature_metadata
features_used = feature_metadata.get_features()
print(len(features_used))
print(features_used)

# 出力
8
['MedInc', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', 'Latitude', 'Longitude', 'HouseAge']
```

学習後に一番良い（評価関数の値が良かった）モデルを使うこともできます。
また、学習が終わると学習したすべてのモデルがフォルダに.pklの形で保存されるのでそれを使うこともできます。

```python
best_model = predictor.leaderboard(test).iloc[0]['model']
print(f"Best model: {best_model}")
model = predictor._trainer.load_model(best_model)
model.predict(test)

# 出力
Best model: KNeighborsDist
array([0.7952446, 1.1951613, 2.374857 , ..., 3.0178776, 1.2840751,
       1.758698 ], dtype=float32)
```

説明変数の重要度を確認します。表示される重要度はpermutation importanceです。
下記のコードのようにmodelにモデル名を渡すこともできますが、modelに何も書かない場合は、best_modelの時の重要度が表示されているようです。

```python
predictor.feature_importance(test,
                             model='WeightedEnsemble_L2',
                             subsample_size=test.shape[0], # 5000以上推奨
                             num_shuffle_sets=10 # 10以上推奨
                            )
# 出力

importance	stddev	p_value	n	p99_high	p99_low
Latitude	0.544581	0.007826	2.103220e-18	10	0.552624	0.536539
Longitude	0.431816	0.006095	1.789579e-18	10	0.438080	0.425552
MedInc	0.175098	0.006007	5.277614e-15	10	0.181271	0.168925
AveOccup	0.079705	0.001617	4.678775e-17	10	0.081367	0.078044
AveRooms	0.048541	0.003129	1.523938e-12	10	0.051756	0.045325
HouseAge	0.019866	0.000978	1.361002e-13	10	0.020871	0.018860
AveBedrms	0.008722	0.001166	1.031447e-09	10	0.009921	0.007524
Population	0.006633	0.001492	9.894354e-08	10	0.008166	0.005099
```

## カスタムしてみる
上記までで基本的な部分はできました。ここではハイパーパラメータのチューニングや特徴量選択をやってもらう設定、モデルのスタッキングとかの制御の例を書いてみようと思います。

### ハイパーパラメータチューニング
公式ではAutoGluonでハイパーパラメータのチューニングを行うことは推奨していないようです。そんなことするならpresetsでbest_qualityを指定していいモデルを探しましょうよという感じのようです。ですが、一応できるのでコードを示します。
チューニングするハイパーパラメータはモデルごとに指定できます。ここで指定しない場合はAutoGluonのデフォルト値になります。

* num_trials : ハイパーパラメータを範囲の中でいくつ試すか
* search_strategy : autoだとランダムサーチ？

```python
from autogluon.common import space

nn_options = {  
    'num_epochs': 10,
    'learning_rate': space.Real(1e-4, 1e-2, default=5e-4, log=True), 
    'activation': space.Categorical('relu', 'softrelu', 'tanh'),
    'dropout_prob': space.Real(0.0, 0.5, default=0.1),
}

gbm_options = { 
    'num_boost_round': 100, 
    'num_leaves': space.Int(lower=26, upper=66, default=36),
}

hyperparameters = { 
                   'GBM': gbm_options,
                   'NN_TORCH': nn_options, 
                  }

time_limit = 2*60
num_trials = 5 
search_strategy = 'auot'

hyperparameter_tune_kwargs = { 
    'num_trials': num_trials,
    'scheduler' : 'local',
    'searcher': search_strategy,
} 

predictor = TabularPredictor(label='PRICE', eval_metric='mape').fit(
    train_data=train,
    time_limit=time_limit,
    hyperparameters=hyperparameters,
    hyperparameter_tune_kwargs=hyperparameter_tune_kwargs,
)
```

### バギング/アンサンブル/スタッキング
AutoGluonではアンサンブルやスタッキングしたモデルもパラメータの指定だけで作れます。
バギングモデル : 交差検証のfoldごとにモデルを作ってアンサンブルする
スタッキングモデル : バギングモデルの結果を説明変数に追加してさらにバギングモデルを作ったもの
ピンとこないと思いますが、この[ページのBaggingとStacking Ensemblingの図](https://auto.gluon.ai/stable/tutorials/tabular/how-it-works.html)を見るとなんとなくわかります。
実装としては、num_bag_folds、num_bag_sets、num_stack_levelsを指定するだけです。
* num_bag_folds : 何分割交差検証か。
* num_bag_sets : バギングを何回繰り返すか。バギングで作られるモデルの数は
num_bag_flods * num_bag_setsになります。
* num_stack_levels : いくつの層をつくるか。

```python
predictor = model.fit(
    train_data=train,
    test_data=test,
    num_bag_folds=5,
    num_bag_sets=1,
    num_stack_levels=1,
    time_limit=60,
    verbosity=0 # ログの表示
)
predictions = predictor.predict(test)
predictor.leaderboard(test)

# 出力
	model	score_test	score_val	eval_metric	pred_time_test	pred_time_val	fit_time	pred_time_test_marginal	pred_time_val_marginal	fit_time_marginal	stack_level	can_infer	fit_order
0	LightGBM_BAG_L2	-0.159684	-0.166176	mean_absolute_percentage_error	2.222251	9.836669	27.618359	0.666541	3.194228	10.404011	2	True	6
1	WeightedEnsemble_L3	-0.161385	-0.164899	mean_absolute_percentage_error	2.222251	9.837948	27.697854	0.000000	0.001280	0.079495	3	True	7
2	LightGBMXT_BAG_L2	-0.174728	-0.175608	mean_absolute_percentage_error	1.822575	6.817423	21.743330	0.266864	0.174982	4.528982	2	True	5
3	LightGBMXT_BAG_L1	-0.176906	-0.176141	mean_absolute_percentage_error	1.505219	6.452528	17.107950	1.505219	6.452528	17.107950	1	True	3
4	WeightedEnsemble_L2	-0.176906	-0.176141	mean_absolute_percentage_error	1.516138	6.453916	17.154609	0.010919	0.001388	0.046659	2	True	4
5	KNeighborsDist_BAG_L1	-0.431942	-0.423039	mean_absolute_percentage_error	0.026125	0.080958	0.052552	0.026125	0.080958	0.052552	1	True	2
6	KNeighborsUnif_BAG_L1	-0.443944	-0.434590	mean_absolute_percentage_error	0.024366	0.108955	0.053846	0.024366	0.108955	0.053846	1	True	1
```
### 説明変数(特徴量)選択
特長量の選択もやってくれるみたいです。fitのパラメータにfeature_prune_kwargsを渡すと特徴量重要度を考慮して特徴量選択をしてくれます。
feature_prune_kwargsには辞書型を渡す必要があります。空の辞書だと（バギングやスタッキングの）各層でよき特徴量を使ってくれるようです。下のコードのように{'force_prune':True}とするとすべてのモデルで同じ特徴量を使うようです。

```python
predictor = model.fit(
    train_data=train,
    test_data=test,
    feature_prune_kwargs={'force_prune':True},
    time_limit=60,
    verbosity=0 # ログの表示
)
```

## おわりに
強力なAutoMLであるAutoGluonを使ってみました。今回はテーブルデータでしたが、画像やテキスト、マルチモーダルなデータに対しても使えるようなのでいろいろ使ってみたいです。
間違っている箇所等あればご指摘ください。

## 参考記事・文献
https://qiita.com/DS27/items/032e89fe421c74b5027b
https://qiita.com/daikikatsuragawa/items/504be60a24e46c524318
https://qiita.com/matsu3365/items/ceea9160449c88eb8272
https://qiita.com/kirikei/items/f879eb2cfbaf3d37ee0f
https://qiita.com/hima2b4/items/e3c48f558f2a8d9ff8bf
https://auto.gluon.ai/stable/index.html
chrome-extension://efaidnbmnnnibpcajpcglclefindmkaj/https://arxiv.org/pdf/2003.06505

# おまけ
## TabularPredict
TabularPredictのproblem_typeやeval_metricで何が使えるわからないときはわざとエラーを出してみると使えるものを全部表示してくれます。
```python
# problem_typeのエラーの例
ValueError: Invalid problem_type 'regresson'. Valid problem types: ['binary', 'multiclass', 'regression', 'quantile']
# eval_metricのエラーの例
ValueError: Unknown eval_metric 'maei'. Valid metrics for problem_type='regression':
['r2', 'mean_squared_error', 'mse', 'root_mean_squared_error', 'rmse', 'mean_absolute_error', 'mae', 'median_absolute_error', 'mean_absolute_percentage_error', 'mape', 'symmetric_mean_absolute_percentage_error', 'smape', 'spearmanr', 'pearsonr']
```

## モデルの略称
ハイパーパラメータチューニングの際に指定するモデルの略称です。
いくつかわからないのもありますが、わかるのだけ書いておきます。
```python
 ['RF':'Random Forest', 'XT':'Extra Trees model', 'KNN':'KNearestNeighbors model ', 
 'GBM':'LightGBM', 'CAT':'CatBoost', 'XGB':'XGBoost', 'NN_TORCH':'PyToorch NN',
 'LR', 'FASTAI':'Class for fastai v1 neural network models that operate on tabular data',
 'TRANSF', 'AG_TEXT_NN', 'AG_IMAGE_NN', 'AG_AUTOMM', 'FT_TRANSFORMER', 'TABPFN', 
 'TABPFNMIX', 'FASTTEXT', 'ENS_WEIGHTED', 'SIMPLE_ENS_WEIGHTED', 'IM_RULEFIT', 'IM_GREEDYTREE',
 'IM_FIGS', 'IM_HSTREE', 'IM_BOOSTEDRULES', 'VW', 'DUMMY']
```