Evalml: Stacked ensemble 表现不佳

创建于 2021-04-06  ·  11评论  ·  资料来源: alteryx/evalml

重现步骤:

  1. 将幸福数据集加载到 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等等,你确定你不能复制这个吗? 在这里,stacked ensemble 出现在排名的中间——我希望它排在首位!

感谢分享复制器:)

@dsherry虽然堆叠集成器不在顶部有点可疑,但最初的问题是堆叠集成器表现不佳,以至于它的排名高于基线回归器!

@angela97lin啊,明白了! 我给你发了一些笔记。

我认为任何证明我们的合奏并不总是接近顶峰的证据都是一个问题。

再深入一点。 我认为集成器在这个数据集上表现不佳有一些潜在的原因:

  1. 数据集非常小,我们当前的数据拆分策略意味着集成器提供并在非常小的数据子集上进行验证。 现在,如果我们想训练一个堆叠的集成器,我们会拆分一些数据(用ensembling_indices标识)供集成器训练。 这是为了通过在输入管道已经训练过的相同数据上训练 metalearner 来防止过度拟合集成器。 然后我们进行一次 CV 拆分,进一步从ensembling_indices拆分数据。 对于这个 128 行的数据集,我们分别在 17 行和 8 行上进行训练和验证。 我提交了 #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())

通过仅使用这三个模型系列,我们得到了 ~0.22 的 MAE 分数,这比任何单个管道都要好得多。

#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) 并不是最好的。 我通过创建的stacking_test分支对此进行了测试,在该分支中我将默认的 metalearner 更新为 RidgeCV(scikit-learn 默认,但我们在 EvalML 中没有),并且集成器的性能要好得多:
    image

@dsherry讨论后的下一步:

在其他数据集上尝试 #1 和 #3(使用 Elastic Net),运行性能测试,看看我们是否可以获得更好的整体性能。

@angela97lin您关于小数据集拆分的观点是正确的。 最终,我们需要以真正不同于大数据集的方式处理小数据集,例如,仅在整个数据集上使用高折叠计数 xval,甚至是 LOOCV,并确保我们为 ensemble metalearner 训练构建不同的折叠。

我也同意 metalearner 需要使用强正则化。 我在 H2O-3 StackedEnsemble 中使用了 Elastic Net,只记得有一次该集成在排行榜中排名第二。 每次我测试时,它都是第一次。 正则化绝不应该让糟糕的模型降低集成的性能。

这甚至将 50 个模型的整个排行榜输入到了 metalearner 中。 :-)

只是发布一些关于此的额外更新:

使用所有回归数据集在本地进行测试。 结果可以在这里找到或只是图表在这里

由此:

  • 同意@rpeck! 我们应该更新 metalearner 以确保使用强正则化。 ElasticNetCV 在许多数据集上的表现似乎比我们的 LinearRegressor 更好。 这个问题跟踪这个: https :
  • @dsherry和我重新讨论了我们的数据拆分策略:现在,我们为整体拆分数据。 然而,这是在我们希望金属学习者在这个集合指数上进行训练的假设下。 使用 scikit-learn 实现,当我们在这个集合索引拆分上训练StackedEnsembler时,我们最终会在这一小组数据上训练输入管道和 metalearner。 这可能是我们表现不佳的原因。 虽然我们输入管道的参数来自其他数据使用的调整,但这些管道并未拟合。 从长远来看,滚动我们自己的实现可以让我们将经过训练的管道传递给集成器,在这种情况下,我们将拥有我们想要的行为。 目前,情况并非如此。

下一步:用集成器手动测试这个假设。 尝试在 80% 的数据上手动训练输入管道,在为集成而留出的数据上创建交叉验证的预测,并训练具有超出预测的 metalearner。

实验结果看起来不错: https :

下一步:

经过一番挖掘,我们认为问题不在于集成如何执行,而在于我们如何报告集成的性能。 目前,我们做一个单独的 ensemble split,占数据的 20%,然后再做一次 train-validation split,并将 ensemble 的分数报告为验证数据。 这意味着在某些情况下,使用非常少的行(如上面的幸福数据集)计算整体得分。

通过删除集成索引拆分并使用我们计算集成的 cv 训练分数的旧方法(提供所有数据,一次训练和验证),我们看到集成在几乎所有情况下都排名更高,并且出现在更多情况下作为#1。 同时,验证分数相同或略好。

请注意,由于我们不进行任何超参数调整,因此不会对输入管道进行训练,并且集成仅将输入管道的预测结果作为输入,因此过拟合不是问题。 我们可以重新实现我们自己的集成,然后更新拆分策略,但现在,我们可以通过更改数据拆分策略和 scikit-learn 的实现来看到改进。

请注意,当启用集成时,这将导致拟合时间增加:所有管道看到更多数据(没有保留的集成索引),并且集成在更多数据上进行训练。 我认为这很好。

结果在此处列出: https :

此页面是否有帮助?
0 / 5 - 0 等级