Una breve introducción a los pipelines de SciKit

Breve introducción a los pipelines de SciKit

Y por qué deberías empezar a usarlos.

Foto de Sigmund en Unsplash

¿Alguna vez has entrenado un modelo de aprendizaje automático y tus predicciones parecían demasiado buenas para ser verdad? ¿Pero luego te diste cuenta de que tenías una fuga de datos entre tus datos de entrenamiento y prueba?

¿O has tenido muchos pasos de preprocesamiento para preparar tus datos de manera que sea difícil transferir los pasos de preprocesamiento desde el entrenamiento de tu modelo hacia la producción para hacer predicciones reales?

¿O tu preprocesamiento se vuelve confuso y es difícil compartir tu código de una manera legible y fácil de entender?

Entonces es posible que desees probar el Pipeline de scikit-learn. El Pipeline es una solución elegante para configurar tu flujo de trabajo para el entrenamiento, prueba y producción de ML, lo que facilita tu vida y hace que tus resultados sean más reproducibles.

Pero ¿qué es un pipeline, cuáles son los beneficios y cómo se configura un pipeline? Voy a responder a estas preguntas y darte algunos ejemplos de código de los bloques de construcción. Al combinar estos bloques de construcción, puedes construir pipelines más sofisticados, adaptados a tus necesidades.

¿Qué es un Pipeline?

Un pipeline te permite ensamblar varios pasos en tu flujo de trabajo de ML que transforman secuencialmente tus datos antes de pasarlos a un estimador. Por lo tanto, un pipeline puede consistir en pasos de preprocesamiento, ingeniería de características y selección de características antes de pasar los datos a un estimador final para tareas de clasificación o regresión.

¿Por qué debería usar un Pipeline?

En general, usar un pipeline facilita tu vida y acelera el desarrollo de tus modelos de ML. Esto se debe a que un pipeline:

  • conduce a un código más limpio y comprensible
  • es fácil de replicar y entender los flujos de datos
  • es más fácil de leer y ajustar
  • hace que la preparación de datos sea más rápida, ya que el pipeline automatiza la preparación de datos
  • ayuda a evitar la fuga de datos
  • permite la optimización de hiperparámetros para ejecutarse en todos los estimadores y parámetros del pipeline a la vez
  • es conveniente, ya que solo tienes que llamar a fit() y predict() una vez para ejecutar todo tu pipeline de datos

Después de haber entrenado y optimizado tu modelo y estás satisfecho con los resultados, puedes guardar fácilmente el pipeline entrenado. Luego, cada vez que quieras ejecutar tu modelo, simplemente carga el pipeline pre-entrenado y estás listo para hacer algunas inferencias. Con esto, puedes compartir fácilmente tu modelo de una manera muy limpia, que es fácil de replicar y entender.

¿Cómo configuro un Pipeline?

Configurar un pipeline con scikit-learn es muy simple y directo.

El Pipeline de scikit-learn utiliza una lista de pares clave-valor que contiene los transformadores que deseas aplicar en tus datos como valores. Las claves las puedes elegir arbitrariamente. Las claves se pueden utilizar para acceder a los parámetros de los transformadores, por ejemplo, al ejecutar una búsqueda en cuadrícula durante una optimización de hiperparámetros. Como los transformadores se almacenan en una lista, también puedes acceder a los transformadores mediante indexación.

Para ajustar los datos en tu pipeline y hacer predicciones, puedes ejecutar fit() y predict() como lo harías con cualquier transformador o regresor en scikit-learn.

Un pipeline muy simple podría verse así:

from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MinMaxScaler
from sklearn.linear_model import LinearRegression

pipeline = Pipeline(steps=[
    ("imputer", SimpleImputer()),
    ("scaler", MinMaxScaler()),
    ("regression", LinearRegression())
])

pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)

Sin embargo, scikit-learn te facilita aún más las cosas si no deseas ingresar valores clave para tus transformadores. En su lugar, puedes utilizar la función make_pipeline() y scikit-learn establecerá los nombres basados en el nombre de la clase del transformador.

from sklearn.impute import SimpleImputer
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import MinMaxScaler
from sklearn.linear_model import LinearRegression

pipeline = make_pipeline(steps=[
    SimpleImputer(),
    MinMaxScaler(),
    LinearRegression()
])

¡Eso es todo! Con esto, has configurado rápidamente un pipeline simple que puedes empezar a utilizar para entrenar un modelo y realizar predicciones. Si quieres ver cómo se ve tu pipeline, simplemente puedes imprimirlo y scikit-learn te muestra una vista interactiva del pipeline.

Pero, ¿qué pasa si quieres construir algo más complejo y personalizable? Por ejemplo, manejar valores categóricos y numéricos de manera diferente, agregar características o transformar el valor objetivo.

No te preocupes, scikit-learn proporciona funcionalidades adicionales con las que puedes crear pipelines más personalizados y llevar tus pipelines al siguiente nivel. Estas funciones son:

  • ColumnTransformer
  • FeatureUnion
  • TransformedTargetRegressor

Exploraré cada una de ellas y te mostraré ejemplos de cómo utilizarlas.

Transformando características seleccionadas

Si tienes diferentes tipos de características, por ejemplo, continuas y categóricas, probablemente quieras transformar estas características de manera diferente. Por ejemplo, escalar las características continuas mientras codificas las características categóricas en one-hot.

Puedes realizar estos pasos de preprocesamiento antes de pasar tus características al pipeline. Pero al hacerlo, no podrás incluir estos pasos de preprocesamiento y parámetros en tu búsqueda de hiperparámetros más adelante. Además, incluirlos en el pipeline hace que sea mucho más fácil manejar tu modelo de aprendizaje automático.

Para aplicar una transformación, o incluso una secuencia de transformaciones, solo a columnas seleccionadas, puedes usar ColumnTransformer. Su uso es muy similar a Pipeline, en lugar de pasar un par clave-valor a steps, simplemente pasamos los mismos pares a transformers. Luego, podemos incluir el transformer creado como un paso en nuestro pipeline.

from sklearn.compose import ColumnTransformerfrom sklearn.pipeline import Pipelinefrom sklearn.preprocessing import OneHotEncodercategorical_transformer = ColumnTransformer(    transformers=[("encode", OneHotEncoder())])pipeline = Pipeline(steps=[    ("categorical", categorical_transformer, ["col_name"])    ])

Dado que solo queremos aplicar la transformación a ciertas columnas, necesitamos pasar estas columnas al pipeline. Además, podemos indicarle al ColumnTransformer qué hacer con las columnas restantes. Por ejemplo, si quieres mantener las columnas que no son modificadas por el transformer, debes establecer remainder en passthrough. De lo contrario, las columnas se eliminarán. En lugar de no hacer nada o eliminar las columnas, también podrías transformar las columnas restantes pasando un transformer.

from sklearn.compose import ColumnTransformerfrom sklearn.preprocessing import MinMaxScaler, OneHotEncodercategorical_transformer = ColumnTransformer( transformers=[("encode", OneHotEncoder(), ["col_name"])], remainder="passthrough")categorical_transformer = ColumnTransformer( transformers=[("encode", OneHotEncoder(), ["col_name"])], remainder=MinMaxScaler())```

Dado que scikit-learn permite el apilamiento de Pipelines, incluso podríamos pasar un Pipeline al ColumnTransformer en lugar de declarar cada transformación que queremos realizar en el ColumnTransformer en sí mismo.

from sklearn.compose import ColumnTransformerfrom sklearn.impute import SimpleImputerfrom sklearn.pipeline import Pipelinefrom sklearn.preprocessing import MinMaxScaler, OneHotEncodercategorical_transformer = Pipeline(steps=[("encode", OneHotEncoder())])numerical_transformer = Pipeline(   steps=[("imputation", SimpleImputer()), ("scaling", MinMaxScaler())])preprocessor = ColumnTransformer(   transfomers=[     ("numeric", numerical_transformer),     ("categoric", categorical_transformer, ["col_name"]),   ])pipeline = Pipeline(steps=["preprocesssing", preprocessor])

Combinando características

Ahora, puedes ejecutar diferentes pasos de preprocesamiento en diferentes columnas, pero ¿qué pasa si quieres derivar nuevas características a partir de los datos y agregarlas a tu conjunto de características?

Para esto, puedes usar FeatureUnion, que combina objetos transformadores en un nuevo transformador con los objetos combinados. Al ejecutar un pipeline con un FeatureUnion, se ajusta cada transformador de forma independiente y luego se unen sus salidas.

Por ejemplo, supongamos que queremos agregar la Media Móvil como una característica, podríamos hacer esto:

from sklearn.compose import FeatureUnionfrom sklearn.pipeline import Pipelinepreprocessor = (   FeatureUnion(     [       ("moving_Average", MovingAverage(window=30)),       ("numerical", numerical_pipeline),     ]   ),)pipeline = Pipeline(steps=["preprocesssing", preprocessor])

Transformando el valor objetivo

Si tienes un problema de regresión, a veces puede ayudar transformar el objetivo antes de ajustar una regresión.

Puedes incluir dicha transformación utilizando la clase TransformedTargetRegressor. Con esta clase, puedes usar transformadores proporcionados por scikit-learn como un escalador MinMax o escribir tus propias funciones de transformación.

Una gran ventaja del TransformedTargetRegressor es que automáticamente mapea las predicciones de vuelta al espacio original mediante una transformación inversa. Por lo tanto, no necesitas preocuparte por esto más adelante cuando pases del entrenamiento del modelo a hacer predicciones en producción.

from sklearn.compose import TransformedTargetRegressorfrom sklearn.impute import SimpleImputerfrom sklearn.pipeline import Pipelinefrom sklearn.preprocessing import MinMaxScalerregressor = TransformedTargetRegressor(    regressor=model,     func=np.log1p,     inverse_func=np.expm1)pipeline = Pipeline(   steps=[      ("imputer", SimpleImputer()),       ("scaler", MinMaxScaler()),       ("regressor", regressor)    ])pipeline.fit(X_train, y_train)y_pred = pipeline.predict(X_test)

Construyendo tus propias funciones personalizadas

A veces no es suficiente utilizar los métodos de preprocesamiento que proporciona scikit-learn. Sin embargo, esto no debería detenerte al usar Pipelines. Puedes crear fácilmente tus propias funciones que luego puedes incluir en el pipeline.

Para esto, necesitas construir una clase que contenga los métodos fit() y transform() ya que se llaman al ejecutar el pipeline. Sin embargo, estos métodos no necesariamente necesitan hacer algo. Además, podemos hacer que la clase herede de BaseEstimator y TransformerMixin de scikit-learn para proporcionarnos alguna funcionalidad básica que nuestro pipeline necesita.

Por ejemplo, supongamos que queremos hacer predicciones en una serie de tiempo y queremos suavizar todas las características mediante un promedio móvil. Para esto, simplemente configuramos una clase con un método transform que contiene la parte de suavizado.

from sklearn.base import BaseEstimator, TransformerMixinfrom sklearn.impute import SimpleImputerfrom sklearn.pipeline import Pipelinefrom sklearn.preprocessing import MinMaxScalerclass MovingAverage(BaseEstimator, TransformerMixin):    def __init__(self, window=30):          self.window = window        def fit(self, X, y=None):          return self        def transform(self, X, y=None):        return X.rolling(window=self.window, min_periods=1, center=False).mean()pipeline = Pipeline(   steps=[       ("ma", MovingAverage(window=30)),       ("imputer", SimpleImputer()),       ("scaler", MinMaxScaler()),       ("regressor", model),   ])pipeline.fit(X_train, y_train)y_pred = pipeline.predict(X_test)

¿Qué más hay que saber?

El valor de retorno predeterminado de los transformadores en scikit-learn es un arreglo de numpy. Esto puede causar problemas en tu pipeline si solo deseas aplicar una transformación en ciertas características en el segundo paso del pipeline, por ejemplo, solo características categóricas.

Sin embargo, para evitar que tu pipeline se rompa, puedes cambiar el valor de retorno predeterminado de todos los transformadores a un dataframe indicando:

from sklearn import set_configset_config(transform_output = "pandas")

Cuando ejecutas una optimización de hiperparámetros o cuando verificas parámetros individuales de tu pipeline, puede ser útil acceder a los parámetros directamente. Para acceder a los parámetros, puedes usar la sintaxis <estimador>__<parámetro>. Por ejemplo, en el ejemplo anterior del promedio móvil, podríamos acceder al ancho de ventana del transformador MovingAverage llamando a pipeline.set_params(pipeline__ma_window=7).

Conclusión

Usar el Pipeline de scikit-learn puede facilitar mucho tu vida al desarrollar nuevos modelos de ML y configurar los pasos de preprocesamiento. Además de tener muchos beneficios, configurar un Pipeline también es simple y directo. Sin embargo, puedes construir Pipelines de preprocesamiento sofisticados y personalizables en los que solo tu creatividad establece los límites.

Si te gustó este artículo o tienes alguna pregunta, no dudes en dejar un comentario o contactarme. También estoy interesado en tus experiencias con el Pipeline de scikit-learn.

¿Quieres leer más sobre Pipelines? Echa un vistazo al siguiente enlace:

  • https://scikit-learn.org/stable/modules/compose.html#pipeline