Evalml: Conjunto apilado que rinde mal

Creado en 6 abr. 2021  ·  11Comentarios  ·  Fuente: alteryx/evalml

Pasos para reproducir:

  1. Cargar el conjunto de datos Happiness en evalml
  2. Corre lo suficiente para incluir conjuntos
  3. El regresor de la línea de base se muestra como clasificado más alto que el regresor apilado.
    Happiness Data Full Set.csv.zip
bug performance

Todos 11 comentarios

@dancuarini Traté de reproducir esto localmente pero no pude; podría deberse a pasos adicionales antes de ejecutar AutoMLSearch (p. ej., tamaño de división de datos, eliminación de columnas). ¡Hablemos de la configuración del problema!

Esto es lo que intenté ejecutar localmente:

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()

Esto da como resultado las siguientes clasificaciones:

image

Progreso actual: discutió con @dancuarini sobre la @Cmancuso sobre la repro-

@ angela97lin espera, ¿estás seguro de que no pudiste reprochar esto? Aquí, el ensamblador apilado aparece en el medio de la clasificación, ¡esperaría que estuviera en la parte superior!

Gracias por compartir el reproductor :)

@dsherry Si bien es un poco sospechoso que el ensamblador apilado no esté en la parte superior, el problema original era que el ensamblador apilado se estaba desempeñando tan mal que estaba clasificado por encima del regresor de la línea de base.

@ angela97lin ah sí entendido! Te envié algunas notas.

Creo que cualquier evidencia de que nuestros conjuntos no siempre estén cerca de la cima es un problema.

Profundice un poco más en esto. Creo que hay algunas razones potenciales por las que el ensamblador se desempeña mal con este conjunto de datos:

  1. El conjunto de datos es realmente pequeño y nuestra estrategia actual de división de datos significa que el ensamblador se proporciona y se valida en un subconjunto muy pequeño de datos. En este momento, si queremos entrenar a un ensamblador apilado, dividimos algunos datos (identificados con ensembling_indices ) para que el ensamblador entrene. Esto es para evitar sobreajustar al ensamblador mediante la capacitación del metaaprendiz con los mismos datos en los que ya se entrenaron las canalizaciones de entrada. Luego hacemos una división de CV, dividiendo aún más los datos de ensembling_indices . Para este conjunto de datos de 128 filas, entrenamos y validamos en 17 y 8 filas, respectivamente. Presenté el número 2144 para discutir si queremos hacer esta división de CV adicional.

  2. Nuestro ensamblador se construye actualmente tomando la mejor canalización de cada familia de modelos encontrada y usándola como canalización de entrada para el ensamblador apilado. Sin embargo, si algunas de las canalizaciones de entrada funcionan bastante mal, es posible que el ensamblador apilado no funcione tan bien como una canalización individual de alto rendimiento.

Por ejemplo, esta es la tabla de clasificación final:
image

Notamos que el conjunto apilado funciona justo en el medio; si simplificamos y decimos que el conjunto apilado promedia las predicciones de sus tuberías de entrada, esto tiene sentido. Para probar mi hipótesis, decidí usar solo las familias de modelos que funcionaron mejor que el ensamblador apilado, en lugar de todas las familias de modelos, y noté que la puntuación resultante funciona mucho mejor que cualquier canalización individual. Esto me lleva a creer que los pipelines individuales de bajo rendimiento llevaron al ensamblador apilado a tener un peor desempeño.

Aquí está el código de reproducción para esto:

Desde arriba:

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())

Con solo usar estas tres familias de modelos, obtenemos una puntuación MAE de ~ 0.22, que es mucho mejor que cualquier canalización individual.

#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},}),

Esto me hace preguntarme si necesitamos repensar qué canalizaciones de entrada debemos alimentar a nuestro ensamblador apilado.

  1. El metaaprendiz que estamos usando (LinearRegressor) no es el mejor. Probé esto a través de la rama stacking_test que creé donde actualicé el metaaprendizaje predeterminado a RidgeCV (scikit-learn default, pero no lo tenemos en EvalML), y el ensamblador funciona mucho mejor:
    image

Próximos pasos después de la discusión con @dsherry :

Pruebe el n. ° 1 y el n. ° 3 (usando Elastic Net) en otros conjuntos de datos, ejecute pruebas de rendimiento, vea si podemos obtener un mejor rendimiento en general.

@ angela97lin Sus puntos sobre la división, para pequeños conjuntos de datos, son acertados. Eventualmente, necesitamos manejar pequeños conjuntos de datos de manera realmente diferente a los más grandes, por ejemplo, usando solo xval de recuento alto en todo el conjunto de datos, incluso LOOCV, y asegurándonos de construir los pliegues de manera diferente para el entrenamiento de metaaprendices en conjunto.

También estoy de acuerdo en que el metaaprendiz debe utilizar una regularización sólida. Usé Elastic Net en H2O-3 StackedEnsemble, y solo recuerdo una vez que el conjunto quedó en segundo lugar en la clasificación. Cada dos veces que probé, fue primero. La regularización nunca debe permitir que los modelos deficientes reduzcan el rendimiento del conjunto.

Y esto estaba alimentando toda la tabla de clasificación de incluso 50 modelos al metaaprendiz. :-)

Solo publicando algunas actualizaciones adicionales sobre esto:

Probado localmente usando todos los conjuntos de datos de regresión. Los resultados se pueden encontrar aquí o solo los gráficos aquí .

De esto:

  • ¡De acuerdo @rpeck! Deberíamos actualizar el metaaprendiz para usar una regularización fuerte con seguridad. ElasticNetCV pareció funcionar mejor que nuestro LinearRegressor en muchos conjuntos de datos. Este problema rastrea esto: https://github.com/alteryx/evalml/issues/1739
  • @dsherry y yo metaaprendiz esté capacitado en estos índices de conjunto. Con la implementación de scikit-learn, cuando entrenamos nuestro StackedEnsembler en esta división de índices de conjunto, terminamos entrenando tanto las canalizaciones de entrada como el metaaprendiz en este pequeño conjunto de datos. Probablemente esta sea la razón por la que no estamos funcionando bien. Si bien los parámetros para nuestras canalizaciones de entrada provienen del ajuste que utilizan los otros datos, estas canalizaciones no se ajustan. A largo plazo, la implementación de nuestra propia implementación podría permitirnos pasar canalizaciones entrenadas al ensamblador, en cuyo caso tendríamos el comportamiento que queremos. Por ahora, ese no es el caso.

Siguiente paso: pruebe esta hipótesis con ensembler manualmente. Intente entrenar manualmente las canalizaciones de entrada en el 80% de los datos, cree predicciones con validación cruzada sobre los datos reservados para el conjunto y entrene al metaaprendiz con predicciones externas.

Los resultados de la experimentación se ven bien: https://alteryx.quip.com/4hEyAaTBZDap/Ensembling-Performance-Using-More-Data

Próximos pasos:

Después de investigar un poco, creemos que el problema no es cómo se desempeña el conjunto, sino cómo informamos sobre la actuación del conjunto. Actualmente, hacemos una división de conjunto separada que es el 20% de los datos, y luego hacemos otra división de validación de tren e informamos la puntuación del conjunto como datos de validación. Esto significa que, en algunos casos, la puntuación del conjunto se calcula utilizando un número muy pequeño de filas (como el conjunto de datos de felicidad anterior).

Al eliminar la división de índices de conjunto y usar nuestro antiguo método de calcular la puntuación de entrenamiento de cv para el conjunto (proporcionar todos los datos, entrenar y validar en un pliegue), vemos que el conjunto se clasifica más alto en casi todos los casos, y aparece como # 1 en muchos más casos. Mientras tanto, la puntuación de validación es la misma o ligeramente mejor.

Tenga en cuenta que, dado que no realizamos ningún ajuste de hiperparámetros, las canalizaciones de entrada no se entrenan y el conjunto solo obtiene las predicciones de las canalizaciones de entrada como entrada, el sobreajuste no es un problema. Podemos revisar la implementación de nuestro propio conjunto y actualizar la estrategia de división en ese momento, pero por ahora, podemos ver mejoras simplemente cambiando la estrategia de división de datos y la implementación de scikit-learn.

Tenga en cuenta que esto provocará un aumento en el tiempo de ajuste cuando el conjunto esté habilitado: todas las canalizaciones ven más datos (sin índices de conjunto reservados) y el conjunto se entrena con más datos. creo que esto esta bien.

Resultados tabulados aquí: https://alteryx.quip.com/jI2mArnWZfTU/Ensembling-vs-Best-Pipeline-Validation-Scores#MKWACADlCDt

¿Fue útil esta página
0 / 5 - 0 calificaciones