Evalml: スタックされたアンサンブルのパフォーマンスが低い

作成日 2021年04月06日  ·  11コメント  ·  ソース: alteryx/evalml

再現する手順:

  1. Happinessデータセットをevalmlにロードします
  2. アンサンブルを含めるのに十分な時間実行する
  3. ベースラインリグレッサは、スタックされたリグレッサよりも上位にランク付けされているように表示されます。
    幸福データフルセット.csv.zip
bug performance

全てのコメント11件

@dancuariniこれをローカルで再現しようとしましたが、再現できませんでした。 AutoMLSearchを実行する前の追加の手順が原因である可能性があります(例:データ分割サイズ、列の削除)。 問題の構成について話しましょう!

これが私がローカルで実行しようとしたものです:

from evalml.automl import AutoMLSearch
import pandas as pd
import woodwork as ww
from evalml.automl.callbacks import raise_error_callback

happiness_data_set = pd.read_csv("Happiness Data Full Set.csv")
y = happiness_data_set['Happiness']
X = happiness_data_set.drop(['Happiness'], axis=1)
# display(X.head())

X = ww.DataTable(X)
X_train, X_holdout, y_train, y_holdout = evalml.preprocessing.split_data(X, y, problem_type='regression', test_size=0.2, random_seed=0)
# print(X.types)

automl = AutoMLSearch(X, y, problem_type="regression", objective="MAE", error_callback=raise_error_callback, max_batches=20, ensembling=True)
automl.search()

これにより、次のランキングが得られます。

image

現在の進捗状況:ローカルで再現できないことについて@dancuariniと話し合い、@Cmancusoと連絡を

@ angela97lin待って、これを再現できなかったのですか? ここでは、スタックされたアンサンブルがランキングの中央に表示されます。トップになると思います。

再生機を共有していただきありがとうございます:)

@dsherryスタックされたアンサンブルが一番上にないのは少し疑わしいですが、元の問題は、スタックされたアンサンブルのパフォーマンスが非常に低く、ベースラインのリグレッサーより上にランク付けされていたということでした。

@ angela97linああ、わかりました! 私はあなたにいくつかのメモを送りました。

私たちのアンサンブルが常にトップに近いとは限らないという証拠は問題だと思います。

これをもう少し掘り下げました。 アンサンブルがこのデータセットでパフォーマンスが低下する理由はいくつか考えられます。

  1. データセットは非常に小さく、現在のデータ分割戦略では、アンサンブラーにデータの非常に小さなサブセットが提供され、検証されています。 現在、スタックされたアンサンブルをトレーニングする場合は、アンサンブルがトレーニングするためにいくつかのデータ( ensembling_indices識別される)を分割します。 これは、入力パイプラインがすでにトレーニングされているのと同じデータでメタイヤーナーをトレーニングすることにより、アンサンブラーの過剰適合を防ぐためです。 次に、1つのCV分割を実行し、さらにensembling_indicesからデータを分割します。 この128行のデータセットでは、それぞれ17行と8行でトレーニングと検証を行います。 この追加のCV分割を行うかどうかを議論するために#2144を提出しました。

  2. 私たちのアンサンブルは現在、見つかった各モデルファミリの最良のパイプラインを取得し、それをスタックされたアンサンブルの入力パイプラインとして使用することによって構築されています。 ただし、一部の入力パイプラインのパフォーマンスが非常に低い場合、スタックされたアンサンブラーは、パフォーマンスの高い個々のパイプラインほどパフォーマンスが高くない可能性があります。

たとえば、これは最終的なランキングテーブルです。
image

スタックされたアンサンブルが真ん中で正しいスマックを実行することに気付きます。単純化して、スタックされたアンサンブルが入力パイプラインの予測を平均化すると言う場合、これは理にかなっています。 仮説をテストするために、すべてのモデルファミリではなく、スタックされたアンサンブルよりもパフォーマンスが高いモデルファミリのみを使用することにしました。結果のスコアは、個々のパイプラインよりもはるかに優れていることに気付きました。 これにより、個々のパイプラインのパフォーマンスが低下したために、スタックされたアンサンブルのパフォーマンスが低下したと私は信じています。

これの再現コードは次のとおりです。

上から:

import pandas as pd
import woodwork as ww
happiness_data_set = pd.read_csv("Happiness Data Full Set.csv")
y = happiness_data_set['Happiness']
X = happiness_data_set.drop(['Happiness'], axis=1)

X = ww.DataTable(X)
X_train, X_holdout, y_train, y_holdout = evalml.preprocessing.split_data(X, y, problem_type='regression', test_size=0.25, random_seed=0)

automl = AutoMLSearch(X, y, problem_type="regression", objective="MAE", error_callback=raise_error_callback, max_batches=10, ensembling=True)
automl.search()

import woodwork as ww
from evalml.automl.engine import train_and_score_pipeline
from evalml.automl.engine.engine_base import JobLogger

# Get the pipelines fed into the ensemble but only use the ones better than the stacked ensemble
input_pipelines = []
input_info = automl._automl_algorithm._best_pipeline_info
from evalml.model_family import ModelFamily

trimmed = dict()
trimmed.update({ModelFamily.RANDOM_FOREST: input_info[ModelFamily.RANDOM_FOREST]})
trimmed.update({ModelFamily.XGBOOST: input_info[ModelFamily.XGBOOST]})
trimmed.update({ModelFamily.DECISION_TREE: input_info[ModelFamily.EXTRA_TREES]})

for pipeline_dict in trimmed.values():
    pipeline_class = pipeline_dict['pipeline_class']
    pipeline_params = pipeline_dict['parameters']
    input_pipelines.append(pipeline_class(parameters=automl._automl_algorithm._transform_parameters(pipeline_class, pipeline_params),
                                                      random_seed=automl._automl_algorithm.random_seed))
ensemble_pipeline = _make_stacked_ensemble_pipeline(input_pipelines, "regression")
X_train = X.iloc[automl.ensembling_indices]
y_train = ww.DataColumn(y.iloc[automl.ensembling_indices])
train_and_score_pipeline(ensemble_pipeline, automl.automl_config, X_train, y_train, JobLogger())

これらの3つのモデルファミリを使用するだけで、MAEスコアは約0.22になります。これは、個々のパイプラインよりもはるかに優れています。

#output of train_and_score_pipeline(ensemble_pipeline, automl.automl_config, X_train, y_train, JobLogger())
{'scores': {'cv_data': [{'all_objective_scores': OrderedDict([('MAE',
                  0.22281276417465426),
                 ('ExpVariance', 0.9578811127332543),
                 ('MaxError', 0.3858477236606914),
                 ('MedianAE', 0.2790362808260225),
                 ('MSE', 0.0642654425375983),
                 ('R2', 0.9152119239698017),
                 ('Root Mean Squared Error', 0.2535062968401343),
                 ('# Training', 17),
                 ('# Validation', 9)]),
    'mean_cv_score': 0.22281276417465426,
    'binary_classification_threshold': None}],
  'training_time': 9.944366216659546,
  'cv_scores': 0    0.222813
  dtype: float64,
  'cv_score_mean': 0.22281276417465426},
 'pipeline': TemplatedPipeline(parameters={'Stacked Ensemble Regressor':{'input_pipelines': [GeneratedPipeline(parameters={'Imputer':{'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'most_frequent', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'One Hot Encoder':{'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Random Forest Regressor':{'n_estimators': 184, 'max_depth': 25, 'n_jobs': -1},}), GeneratedPipeline(parameters={'Imputer':{'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'One Hot Encoder':{'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'XGBoost Regressor':{'eta': 0.1, 'max_depth': 6, 'min_child_weight': 1, 'n_estimators': 100},}), GeneratedPipeline(parameters={'Imputer':{'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'One Hot Encoder':{'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Extra Trees Regressor':{'n_estimators': 100, 'max_features': 'auto', 'max_depth': 6, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_jobs': -1},})], 'final_estimator': None, 'cv': None, 'n_jobs': -1},}),

これにより、スタックされたアンサンブルにどの入力パイプラインをフィードする必要があるかを再考する必要があるかどうか疑問に思います。

  1. 私たちが使用しているmetalearner(LinearRegressor)は最高ではありません。 デフォルトのmetalearnerをRidgeCV(scikit-learn defaultですが、EvalMLにはありません)に更新した作成したstacking_testブランチを介してこれをテストしましたが、アンサンブラーのパフォーマンスははるかに優れています。
    image

@dsherry

他のデータセットで(Elastic Netを使用して)#1と#3を試し、パフォーマンステストを実行して、全体的なパフォーマンスを向上できるかどうかを確認します。

@ angela97lin小さなデータセットの場合、分割に関するあなたのポイントは的を射ています。 最終的には、小さなデータセットを大きなデータセットとはまったく異なる方法で処理する必要があります。たとえば、データセット全体、さらにはLOOCVで高フォールドカウントのxvalのみを使用し、アンサンブルメタラーナートレーニング用にフォールドを異なる方法で構築するようにします。

また、metalearnerは強力な正則化を使用する必要があることにも同意します。 H2O-3StackedEnsembleでElasticNetを使用しましたが、アンサンブルがリーダーボードで2番目に来たことを一度だけ覚えています。 私がテストするたびに、それは最初でした。 正則化により、貧弱なモデルがアンサンブルのパフォーマンスを低下させることは決してありません。

そしてこれは、50モデルのリーダーボード全体をメタイヤーナーに供給していました。 :-)

これに関するいくつかの追加の更新を投稿するだけです:

すべての回帰データセットを使用してローカルでテストされました。 結果はここまたはここのチャートだけで見つけることができ

これから:

  • @rpeckに同意しました! 確実に強力な正則化を使用するようにmetalearnerを更新する必要があります。 ElasticNetCVは、多くのデータセットでLinearRegressorよりもパフォーマンスが優れているように見えました。 この問題はこれを追跡します: https
  • @dsherryと私は、データ分割戦略について再検討しました。現在、アンサンブルのデータを分割しています。 ただし、これは、メタイヤーナーがこのアンサンブルインデックスでトレーニングされることを想定しています。 scikit-learnの実装では、このアンサンブルインデックス分割でStackedEnsemblerをトレーニングすると、この小さなデータセットで入力パイプラインとメタラーナーの両方をトレーニングすることになります。 これが、私たちがうまく機能していない理由である可能性があります。 入力パイプラインのパラメーターは他のデータを使用したチューニングからのものですが、これらのパイプラインは適合していません。 長期的には、独自の実装をローリングすることで、トレーニング済みのパイプラインをアンサンブラーに渡すことができます。その場合、必要な動作が得られます。 今のところ、そうではありません。

次のステップ:アンサンブルを使用してこの仮説を手動でテストします。 データの80%で入力パイプラインを手動でトレーニングし、アンサンブル用に確保されたデータで相互検証された予測を作成し、予測外のメタイヤーをトレーニングしてみてください。

実験の結果は良さそうです: https

次のステップ:

少し掘り下げてみると、問題はアンサンブルのパフォーマンスではなく、アンサンブルのパフォーマンスをどのように報告するかにあると考えています。 現在、データの20%である別のアンサンブル分割を実行してから、別のトレイン検証分割を実行し、アンサンブルのスコアを検証データとして報告します。 これは、場合によっては、アンサンブルスコアが非常に少数の行を使用して計算されることを意味します(上記の幸福データセットのように)。

アンサンブルインデックスの分割を削除し、アンサンブルのcvトレーニングスコアを計算する古い方法を使用することで(すべてのデータを提供し、トレーニングと検証を1回行う)、ほとんどすべての場合でアンサンブルが上位にランク付けされ、表示されます。多くの場合、#1として。 一方、検証スコアは同じかわずかに優れています。

ハイパーパラメータの調整を行わないため、入力パイプラインはトレーニングされず、アンサンブルは入力パイプラインの予測を入力として取得するだけなので、過剰適合は問題になりません。 独自のアンサンブルの実装を再検討し、分割戦略を更新することはできますが、今のところ、データ分割戦略とscikit-learnの実装を変更するだけで改善が見られます。

これにより、アンサンブルが有効になっている場合に適合時間が長くなることに注意してください。すべてのパイプラインはより多くのデータを参照し(予約されたアンサンブルインデックスはありません)、アンサンブルはより多くのデータでトレーニングされます。 これでいいと思います。

ここに表にされた結果: https

このページは役に立ちましたか?
0 / 5 - 0 評価