xfeatとAutoGluonでベンチマークモデル作成
この記事ではxfeatとAutoGluonを使って、機械学習モデルの評価で役に立つベンチマークモデルを作ってみようと思います。
はじめに
みなさんは機械学習モデルのベンチマーク作成に悩んだことはないですか?
個人的な経験では、新規プロジェクトのデータサイエンティストが自分一人の時に悩む時がありました(モデルの精度はデータに依存する部分が大きいですが、新しいデータ、タスクで比較対象がないなか、いかに自身のモデルの有用性を伝えるかなどの面で)。
過去には...
- 素のデータセットでモデルを学習した結果で比較する
- 実験過程の記録を使用する
- 複数アルゴリズムを試した時の結果を利用する(後付けでA, B, C...のアルゴリズムを試してAが一番良かった)
- HPOした時の結果を利用する(後付けでA, B, C...のハイパーパラメータを探索してAが一番良かった)
こういったアプローチをとったことがあります。
しかしこれらのアプローチは自作自演というか出来レース的で、必ず自身のモデルが勝つ(勝てる)状況のなかで、どこまで精度を追求するかというモチベーションの面で悪い影響を与えてしまう部分もあるのではないかなと感じていました。
こんな時に、”機械学習コンペのようにいいライバルがいたらいいなー”というのがこの記事でやりたいことです。
この記事では、テーブルデータに対してxfeatとAutoGluonを使って"いいライバル(ベンチマークモデル)"を作ってみたいと思います。
xfeatとは
xfeatは、PFNが開発したOSSで特徴量エンジニアリングと探索のためのライブラリです。
主な特徴は以下の通りです。
- 数値、カテゴリ変数に使える処理が一通り揃っている
- PandasのDataFrameを入力にしており、使いやすい
- Optunaを使った特徴量選択も可能
- cuDF(RAPIDS)とCuPyをサポートしており、GPU上で高速に動作
個人的にはAPIが直感的なのでとても使いやすいライブラリです。
AutoGluonとは
Paper: AutoGluon-Tabular: Robust and Accurate AutoML for Structured Data
AutoGluonは、AWSが開発した画像、テキスト、テーブルデータに対してAutoMLを実行するOSSです(この記事ではテーブルデータに対してAutoMLを実行するAutoGluon Tabularを紹介します)。
主な特徴は以下の通りです。
- テーブルデータの教師あり学習の「回帰」と「分類(二値、多値)」に対応
- 簡単なAPIで実行可能
- 複数モデルのアンサンブル(重み付け平均、スタッキング)に対応
- テキスト変数から特徴生成が可能
xfeatと組み合わせることで数値、カテゴリ、テキストのデータセットに対応できるので幅が広がります。
ベンチマークモデル作成
それではベンチマークモデルの作成を進めていきたいと思います。
ライブラリのインストール
# AutoGluon
pip install -U pip
pip install -U setuptools wheel
pip install -U "mxnet<2.0.0"
pip install autogluon==0.1.0
pip install pygraphviz
# xfeat
pip install git+https://github.com/pfnet-research/xfeat.git
pygraphviz
はモデルの可視化で使いますが、なくてもモデルの学習・推論はできます。
必要な場合はGraphviz
と併せてインストールしてください。
データの準備
この記事ではUCIのin-vehicle coupon recommendation Data Setを使いたいと思います。
!wget https://archive.ics.uci.edu/ml/machine-learning-databases/00603/in-vehicle-coupon-recommendation.csv
import pandas as pd
df = pd.read_csv('in-vehicle-coupon-recommendation.csv')
df
このデータセットはドライバーが、目的地や時間、天気などの様々な状況でクーポンを受け入れるかどうか(Y={0, 1})を調査したデータセットです。
target = 'Y'
categorical_columns = [x for x in df.columns if df[x].dtypes == 'object']
numerical_columns = [x for x in df.columns if (df[x].dtypes != 'object') & (x != target)]
入力変数には数値、カテゴリがありますが、ここではカテゴリ変数に対してxfeatで特徴量を作成していきたいと思います(自身のデータで実行する際はnumerical_columns
、categorical_columns
が意図したように分けられているか確認して必要であれば修正してください)。
print(categorical_columns)
# ['destination', 'passanger', 'weather', 'time', 'coupon', 'expiration', 'gender', 'age', 'maritalStatus', 'education', 'occupation', 'income', 'car', 'Bar', 'CoffeeHouse', 'CarryAway', 'RestaurantLessThan20', 'Restaurant20To50']
print(numerical_columns)
# ['temperature', 'has_children', 'toCoupon_GEQ5min', 'toCoupon_GEQ15min', 'toCoupon_GEQ25min', 'direction_same', 'direction_opp']
特徴量を作る前に学習用と検証用にデータを分割します。
from sklearn.model_selection import train_test_split
train_df, val_df = train_test_split(df, test_size=0.2, random_state=42)
print(train_df.shape)
print(val_df.shape)
# (10147, 26)
# (2537, 26)
xfeatを使った特徴量エンジニアリング
最初にカテゴリ変数に対してカウントエンコーディングを行います。
学習データを基にエンコーダを作成し、検証データに適用します。
from xfeat import CountEncoder
encoder = CountEncoder(input_cols=categorical_columns)
train_df = encoder.fit_transform(train_df)
val_df = encoder.transform(val_df)
次にカテゴリ変数同士でペアを作り、結合します。
from xfeat import ConcatCombination
encoder = ConcatCombination(input_cols=categorical_columns,
drop_origin=False,
r=2)
train_df = encoder.fit_transform(train_df)
val_df = encoder.transform(val_df)
カテゴリ変数に対してターゲットエンコーディングを行います。
from sklearn.model_selection import KFold
from xfeat import TargetEncoder
fold = KFold(n_splits=5, shuffle=True, random_state=42)
encoder = TargetEncoder(input_cols=categorical_columns,
target_col=target,
fold=fold)
train_df = encoder.fit_transform(train_df)
val_df = encoder.transform(val_df)
ここまで何も考えずにカテゴリ変数に対して特徴量エンジニアリングしてきたので、重複している列、同じ値しか入っていない列、相関が高過ぎる変数のペアなどがあれば削除します。
from xfeat.pipeline import Pipeline
from xfeat.selector import DuplicatedFeatureEliminator
from xfeat.selector import ConstantFeatureEliminator
from xfeat.selector import SpearmanCorrelationEliminator
def get_feature_selector():
return Pipeline(
[
DuplicatedFeatureEliminator(),
ConstantFeatureEliminator(),
SpearmanCorrelationEliminator(threshold=0.9),
]
)
input_columns = [x for x in train_df.columns if x != target]
selector = get_feature_selector()
df_reduced = selector.fit_transform(train_df[input_columns])
print("Selected columns: {}".format(df_reduced.columns.tolist()))
train_df = train_df[df_reduced.columns.tolist()+list(target)]
val_df = val_df[df_reduced.columns.tolist()+list(target)]
6つの変数が削除されました。
AutoGluonを使った機械学習モデリング
続いてAutoGluonで機械学習モデルの学習を行います。
AutoGluon==0.1.0
ではデフォルトで以下のモデルを実行します。
- RandomForest(sklearn)
- ExtraTrees(sklearn)
- KNearestNeighbors(sklearn)
- LightGBM
- CatBoost
- XGBoost
- NeuralNet(MXNet, FastAI)
いくつかのモデルはハイパーパラメータを少しいじって複数パターン実行してくれます。
実行時はまずPredictor
を定義します。
今回は評価指標にroc_auc
を指定しています。
import autogluon as ag
from autogluon.tabular import TabularDataset, TabularPredictor
output_path = 'agModels-predictClass-xfeat'
predictor = TabularPredictor(
label=target,
problem_type='binary', # 二値分類
eval_metric='roc_auc', # 評価指標
path=output_path, # モデルの格納場所
verbosity=4, # ログの出力レベル
)
入力となるtrain_df
, val_df
にはカテゴリがカテゴリのまま入っている状態ですが、AutoGluonは内部ではアルゴリズムに合わせて変換してくれます(ラベルエンコーディングしてから、sklearn系はone-hot, GBDT系はcategorical, NN系はEmbeddingなど)。欠損処理も同様です。
今回のデータセットでは、1つテキスト認識されてしまうカラムがあったため、テキスト処理をFalse
にしています(カテゴリかテキストかの判断は文字列長やカーディナリティを基に判断)。
その他はデフォルトの設定で学習を実行します。
predictor.fit(
train_data=train_df,
tuning_data=val_df, # これを渡さない場合はランダムスプリット
time_limit=None, # おおよその時間制限を設けられる
presets='medium_quality_faster_train', #デフォルトプリセット
_feature_generator_kwargs={
'enable_text_ngram_features': False,
'enable_text_special_features': False,
'enable_raw_text_features': False
},
#excluded_model_types=['KNN'], # 一部モデルを除外する場合
)
結果が以下となります(xfeatを使わないバージョンは別途実行しています)。
モデル | AutoGluonのみ | AutoGluon + xfeat |
---|---|---|
WeightedEnsemble_L2 | 0.849777 | 0.857070 |
LightGBMXT | 0.844241 | 0.848947 |
CatBoost | 0.839667 | 0.846775 |
LightGBMLarge | 0.837590 | 0.846599 |
LightGBM | 0.835666 | 0.841780 |
XGBoost | 0.834714 | 0.840627 |
NeuralNetMXNet | 0.811182 | 0.832815 |
RandomForestEntr | 0.812572 | 0.822937 |
RandomForestGini | 0.815846 | 0.819350 |
ExtraTreesEntr | 0.797244 | 0.817992 |
ExtraTreesGini | 0.798302 | 0.815785 |
NeuralNetFastAI | 0.818770 | 0.811000 |
KNeighborsDist | 0.539007 | 0.715612 |
KNeighborsUnif | 0.539007 | 0.678436 |
仮にLightGBMで素のデータセットを学習した場合のroc_auc
が0.835666
と捉えると、xfeatで特徴量を作成することで0.841780
になり、さらに複数モデルのアンサンブル(WeightedEnsemble_L2
:デフォルトは重み付け平均)で0.857070
となります。
ライバルは少し強くなりました
学習が終わると.leaderboard()
で一覧が取得できます。
predictor.leaderboard()
最終的に使用されたモデル
predictor.plot_ensemble_model()
# 'agModels-predictClass-xfeat/ensemble_model.png'
ライバルは精度を示すだけでなく、ヒント(feature_importance)もくれます。
predictor.feature_importance(val_df,
subsample_size=val_df.shape[0], # 全レコード使う場合
num_shuffle_sets=1 # シャッフルする回数、ドキュメントは最低10を推奨。今回は無視
)
マイナスの特徴量はシャッフルして精度が改善した特徴量です(実際に使用する際は複数回シャッフルしてください)。
最後に
xfeat + AutoGluonの組み合わせはコード量が少なく、且つ良いベンチマークモデルが作れそうだなと思い、この記事を書いてみました。xfeatとAutoGluonはまだまだできることがあるのでさらに強いベンチマークモデルを作ることも可能だと思います。
何かの参考になると嬉しいです。
Discussion