Conjunto de diferentes tipos de regresores utilizando scikit-learn (o cualquier otro marco de Python)

27

Estoy tratando de resolver la tarea de regresión. Descubrí que 3 modelos funcionan bien para diferentes subconjuntos de datos: LassoLARS, SVR y Gradient Tree Boosting. Me di cuenta de que cuando hago predicciones usando todos estos 3 modelos y luego hago una tabla de 'salida real' y salidas de mis 3 modelos, veo que cada vez que al menos uno de los modelos está realmente cerca de la salida verdadera, aunque otros 2 podría estar relativamente lejos.

Cuando calculo el mínimo error posible (si tomo la predicción del "mejor" predictor para cada ejemplo de prueba) obtengo un error que es mucho más pequeño que el error de cualquier modelo solo. Así que pensé en tratar de combinar predicciones de estos 3 modelos diferentes en algún tipo de conjunto. La pregunta es, ¿cómo hacer esto correctamente? Todos mis 3 modelos están construidos y ajustados usando scikit-learn, ¿proporciona algún tipo de método que podría usarse para empaquetar modelos en un conjunto? El problema aquí es que no quiero promediar las predicciones de los tres modelos, quiero hacerlo con la ponderación, donde la ponderación debe determinarse en función de las propiedades de un ejemplo específico.

Incluso si scikit-learn no proporciona esa funcionalidad, sería bueno que alguien supiera cómo abordar esta tarea de manera adecuada: calcular la ponderación de cada modelo para cada ejemplo en los datos. Creo que podría hacerlo un regresor separado construido sobre estos 3 modelos, que intentará obtener pesos óptimos para cada uno de los 3 modelos, pero no estoy seguro de si esta es la mejor manera de hacerlo.

Maksim Khaitovich
fuente

Respuestas:

32

En realidad, scikit-learnproporciona dicha funcionalidad, aunque puede ser un poco difícil de implementar. Aquí hay un ejemplo de trabajo completo de un regresor promedio construido sobre tres modelos. En primer lugar, importemos todos los paquetes necesarios:

from sklearn.base import TransformerMixin
from sklearn.datasets import make_regression
from sklearn.pipeline import Pipeline, FeatureUnion
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.linear_model import LinearRegression, Ridge

Luego, necesitamos convertir nuestros tres modelos de regresores en transformadores. Esto nos permitirá fusionar sus predicciones en un solo vector de características usando FeatureUnion:

class RidgeTransformer(Ridge, TransformerMixin):

    def transform(self, X, *_):
        return self.predict(X)


class RandomForestTransformer(RandomForestRegressor, TransformerMixin):

    def transform(self, X, *_):
        return self.predict(X)


class KNeighborsTransformer(KNeighborsRegressor, TransformerMixin):

    def transform(self, X, *_):
        return self.predict(X)

Ahora, definamos una función de construcción para nuestro modelo de frankenstein:

def build_model():
    ridge_transformer = Pipeline(steps=[
        ('scaler', StandardScaler()),
        ('poly_feats', PolynomialFeatures()),
        ('ridge', RidgeTransformer())
    ])

    pred_union = FeatureUnion(
        transformer_list=[
            ('ridge', ridge_transformer),
            ('rand_forest', RandomForestTransformer()),
            ('knn', KNeighborsTransformer())
        ],
        n_jobs=2
    )

    model = Pipeline(steps=[
        ('pred_union', pred_union),
        ('lin_regr', LinearRegression())
    ])

    return model

Finalmente, ajustemos el modelo:

print('Build and fit a model...')

model = build_model()

X, y = make_regression(n_features=10, n_targets=2)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

model.fit(X_train, y_train)
score = model.score(X_test, y_test)

print('Done. Score:', score)

Salida:

Build and fit a model...
Done. Score: 0.9600413867438636

¿Por qué molestarse en complicar las cosas de esa manera? Bueno, este enfoque nos permite optimizar los hiperparámetros del modelo utilizando scikit-learnmódulos estándar como GridSearchCVo RandomizedSearchCV. Además, ahora es posible guardar y cargar fácilmente desde el disco un modelo previamente entrenado.

constt
fuente
Cuando se usa este enfoque, ¿hay una manera simple de extraer qué algo se está usando cuándo / qué fracción de cada algo?
David Hagan el
Quizás observar los coeficientes del modelo lineal resultante ( model.named_steps['lin_regr'].coef_) le dará algunas ideas sobre cuánto contribuye cada modelo en un conjunto a la solución final.
Constt
@constt ¿No necesitaría usar cross_val_predict en sus modelos base? Parece que su modelo de nivel superior recibiría una señal demasiado optimista de sus modelos base, ya que esto está implementado actualmente.
Brian Bien
1
Este es solo un ejemplo de prueba de concepto, no abordé una selección de modelo aquí. Creo que dichos modelos deberían optimizarse en su conjunto, es decir, optimizar los hiperparámetros de todos los modelos incorporados simultáneamente utilizando el enfoque de validación cruzada.
Constt
si ponemos n_targets = 1 X, y = make_regression(n_features=10, n_targets=1)da error de dimensión. ¿Alguien puede explicar qué hacer?
Mohit Yadav
9

Ok, después de pasar un tiempo buscando en Google, descubrí cómo podía hacer la ponderación en python incluso con scikit-learn. Considere lo siguiente:

Entreno un conjunto de mis modelos de regresión (como se mencionó SVR, LassoLars y GradientBoostingRegressor). Luego los ejecuté todos en datos de entrenamiento (los mismos datos que se usaron para el entrenamiento de cada uno de estos 3 regresores). Recibo predicciones para ejemplos con cada uno de mis algoritmos y guardo estos 3 resultados en el marco de datos de pandas con las columnas 'predictedSVR', 'predicttedLASSO' y 'predicttedGBR'. Y agrego la columna final en este archivo de datos que llamo 'predicho', que es un valor de predicción real.

Luego solo entreno una regresión lineal en este nuevo marco de datos:

 #df - dataframe with results of 3 regressors and true output

 from sklearn linear_model
 stacker= linear_model.LinearRegression()
 stacker.fit(df[['predictedSVR', 'predictedLASSO', 'predictedGBR']], df['predicted'])

Entonces, cuando quiero hacer una predicción para un nuevo ejemplo, simplemente ejecuto cada uno de mis 3 regresores por separado y luego hago:

 stacker.predict() 

en salidas de mis 3 regresores. Y obtener un resultado.

El problema aquí es que estoy encontrando pesos promedio óptimos para los regresores en promedio, los pesos serán los mismos para cada ejemplo en el que intentaré hacer predicciones.

Si alguien tiene alguna idea sobre cómo apilar (ponderar) usando las características del ejemplo actual, sería bueno escucharlas.

Maksim Khaitovich
fuente
¡Guau, me gusta mucho este enfoque! ¿Pero por qué usaste en LinearRegression()lugar de LogisticRegression()modelo?
harrison4
1
@ harrison4 porque estaba haciendo regresión, no tarea de clasificación? Así que quería 'ponderar' la salida de cada modelo. De todos modos, este es un mal enfoque, uno bueno se describe aquí: stackoverflow.com/a/35170149/3633250
Maksim Khaitovich
Sí, lo siento tienes razón! ¡gracias por compartir el enlace!
harrison4
5

Si sus datos tienen subconjuntos obvios, puede ejecutar un algoritmo de agrupación como k-means y luego asociar cada clasificador con los grupos en los que funciona bien. Cuando llegue un nuevo punto de datos, determine en qué clúster está y ejecute el clasificador asociado.

También puede usar las distancias inversas desde los centroides para obtener un conjunto de pesos para cada clasificador y predecir usando una combinación lineal de todos los clasificadores.

anthonybell
fuente
Encontré un artículo que probó esta estrategia (junto con una comparación de algunas ideas similares): artículo
anthonybell
Idea interesante, aunque requiere mucho trabajo para aplicarla. Gracias por el papel!
Maksim Khaitovich
1

Realizo un tipo de ponderación haciendo lo siguiente, una vez que todos sus modelos estén completamente entrenados y funcionen bien:

  1. Ejecute todos sus modelos en un gran conjunto de datos de prueba invisibles
  2. Almacene los puntajes de f1 en el conjunto de pruebas para cada clase, para cada modelo
  3. Cuando predice con el conjunto, cada modelo le dará la clase más probable, por lo tanto, ponderar la confianza o probabilidad por el puntaje f1 para ese modelo en esa clase. Si se trata de distancia (como en SVM, por ejemplo), simplemente normalice las distancias para obtener una confianza general y luego proceda con la ponderación por clase f1.

Puede ajustar aún más su conjunto tomando la medida del porcentaje correcto durante un tiempo. Una vez que haya calificado un nuevo conjunto de datos significativamente grande, puede trazar el umbral en pasos de 0.1, por ejemplo, contra el porcentaje correcto si usa ese umbral para calificar, para tener una idea de qué umbral le dará, digamos, 95% correcto para la clase 1, y así sucesivamente. Puede seguir actualizando el conjunto de pruebas y los puntajes de f1 a medida que ingresan nuevos datos y realizar un seguimiento de la deriva, reconstruyendo los modelos cuando caen los umbrales o la precisión.

wwwslinger
fuente
1
Eso es interesante, pero funciona solo para tareas de clasificación, por lo que veo, mientras trato de resolver la tarea de regresión. Por lo tanto, no puedo calcular la puntuación F1.
Maksim Khaitovich