Sun. Dec 4th, 2022

Cómo usar Pipelines y agregar transformadores personalizados al flujo de procesamiento

Foto de seo yeon Lee en Unsplash¿Está buscando una manera de mantener su flujo de ML organizado, mientras mantiene la flexibilidad en su flujo de procesamiento? ¿Quiere trabajar con canalizaciones mientras incorpora etapas únicas a su procesamiento de datos? Este artículo es una guía simple paso a paso sobre cómo usar las canalizaciones de Scikit-Learn y cómo agregar transformadores personalizados a su canalización. Si ha trabajado como científico de datos durante el tiempo suficiente, probablemente haya oído hablar de Skicit. -Aprender tuberías. Es posible que los haya encontrado después de trabajar en un proyecto de investigación desordenado y terminar con un cuaderno gigante lleno de pasos de procesamiento y varias transformaciones, sin estar seguro de qué pasos y parámetros se aplicaron en ese último intento exitoso que dio buenos resultados. De lo contrario, debe haberlos encontrado si alguna vez tuvo la oportunidad de implementar un modelo en Producción. En resumen, una canalización es un objeto creado para científicos de datos que desean que su flujo de procesamiento y modelado de datos esté bien organizado y se aplique fácilmente a nuevos datos. Incluso los científicos de datos más profesionales son humanos, con memoria limitada y habilidades de organización imperfectas. Afortunadamente, tenemos canalizaciones para ayudarnos a mantener el orden, la replicabilidad y… nuestra cordura. La primera parte de esta publicación es una breve introducción sobre qué son las canalizaciones y cómo usarlas. Si ya está familiarizado con las canalizaciones, profundice en la segunda parte, donde analizo la personalización de las canalizaciones. Una canalización es una lista de transformaciones secuenciales, seguida de un objeto estimador Scikit-Learn (es decir, un modelo ML). La canalización nos brinda un marco estructurado para aplicar transformaciones a los datos y, en última instancia, ejecutar nuestro modelo. Describe claramente qué pasos de procesamiento elegimos aplicar, su orden y los parámetros exactos que aplicamos. nos obliga a realizar exactamente el mismo procesamiento en todas las muestras de datos existentes, proporcionando un flujo de trabajo claro y replicable. Es importante destacar que nos permite ejecutar más tarde exactamente los mismos pasos de procesamiento en muestras nuevas. Este último punto es crucial siempre que queramos aplicar nuestro modelo a nuevos datos, ya sea para entrenar nuestro modelo en un conjunto de prueba después de entrenarlo usando el conjunto de entrenamiento, o para procesar nuevos puntos de datos y ejecutar el modelo en Producción.

¿Cómo aplicar una tubería?

Para esta publicación, seguiremos un ejemplo de una tubería de clasificación simple: nos gustaría identificar a las personas con alto riesgo de desarrollar una determinada enfermedad en el próximo año, en función de la información personal y relacionada con la salud. Usaremos un conjunto de datos de juguetes que incluye un Algunas características relevantes (este es un conjunto de datos artificial que creé solo con fines de demostración).
test_df = pd.read_csv(‘toy_data_test.csv’, index_col = 0)train_df.head()Nuestro preprocesamiento incluirá la imputación de valores perdidos y la escala estándar. |A continuación, ejecutaremos un estimador RandomForestClassifier. El siguiente código muestra el uso básico de una canalización. Primero, importamos los paquetes necesarios. A continuación, definimos los pasos de la canalización: lo hacemos proporcionando una lista de tuplas al objeto de canalización, donde cada tupla consiste en el nombre del paso y el objeto transformador/estimador que se aplicará. # importar paquetes relevantes
de sklearn.pipeline canal de importación
de sklearn.preprocessing importar StandardScaler
de sklearn.imputar importar SimpleImputer
from sklearn.ensemble import RandomForestClassifier# define nuestra canalización
tubería = tubería ([(‘imputer’, SimpleImputer()),(‘scaler’, StandardScaler()), (‘RF’, RandomForestClassifier())]) Luego ajustamos el Pipeline a los datos del tren y predecimos el resultado de nuestros datos de prueba. Durante la etapa de ajuste, se guardan los parámetros necesarios de cada paso, creando una lista de transformadores que “recuerdan” exactamente qué transformaciones aplicar y qué valores usar, seguido de un modelo entrenado. Finalmente, aplicamos la canalización completa a nuevos datos usando el método predict(). Esto ejecuta las transformaciones en los datos y predice el resultado usando el estimador.X_train = train_df.drop(columns = [‘High_risk’])
y_tren = tren_df[‘High_risk’]# ajustar y predecir
pipe.fit (tren_X, tren_y)
pipe.predict (X_test) Si queremos ajustar el modelo y obtener los valores predichos para el conjunto de trenes en un solo paso, también podemos usar el método combinado: pipe.fit_predict (X_train, y_train) Como ya vimos, una tubería es simplemente una secuencia de transformadores seguidos de un estimador, lo que significa que podemos mezclar y combinar varias etapas de procesamiento utilizando transformadores Scikit-Learn incorporados (por ejemplo, SimpleImputer, StandardScaler, etc.). Pero, ¿qué pasa si queremos agregar un paso de procesamiento específico que ¿No es uno de los sospechosos habituales para el procesamiento de datos? En este ejemplo, estamos tratando de identificar pacientes con alto riesgo de desarrollar una determinada enfermedad en el próximo año, en función de características personales y relacionadas con la salud. En la sección anterior, creamos una canalización que imputaba los valores faltantes, escalaba los datos y finalmente aplicaba un clasificador Random Forest. Sin embargo, después de observar el conjunto de datos completo, nos damos cuenta de que una de las características, la edad, tiene algunos valores negativos o sospechosamente altos. :Después de investigar un poco, descubrimos que el campo de edad se agrega manualmente y, a veces, contiene errores. Desafortunadamente, la edad es una característica importante en nuestro modelo, por lo que no queremos dejarla fuera. Decidimos (solo para este ejemplo…) reemplazar los valores improbables por el valor de la edad media. Afortunadamente, podemos hacer esto escribiendo un transformador y configurándolo en su lugar apropiado dentro de la canalización. Aquí escribiremos y agregaremos un transformador personalizado: AgeImputer. Nuestra nueva tubería ahora incluirá un nuevo paso antes de la imputación y el escalador: tubería = tubería ([(‘age_imputer’, AgeImputer()),(‘imputer’, SimpleImputer()),(‘scaler’, StandardScaler()), (‘RF’, RandomForestClassifier())])

¿Cómo escribir un transformador?

Comencemos analizando la estructura de un transformador y sus métodos. Un transformador es una clase de Python. Para que cualquier transformador sea compatible con Scikit-Learn, se espera que conste de ciertos métodos: fit(), transform(), fit_transform(), get_params() y set_params(). El método adaptar() se adapta a la tubería; transformar() aplica la transformación; y el combinado ajuste_transformar() El método se ajusta y luego aplica la transformación al mismo conjunto de datos. Las clases de Python pueden heredar convenientemente la funcionalidad de otras clases. Más específicamente, nuestro transformador puede heredar algunos de estos métodos de otras clases, lo que significa que no tenemos que escribirlos nosotros mismos. Los métodos get_params() y set_params() se heredan de la clase BaseEstimator. El método fit_transform() se hereda de la clase TransformerMixin. Esto nos hace la vida más fácil porque significa que solo tenemos que implementar los métodos fit() y transform() en nuestro código, mientras que el resto de la magia sucederá por sí solo. El siguiente código ilustra la implementación de fit() y transform() métodos del nuevo transformador ImputeAge descrito anteriormente. Recuerde, queremos que nuestro transformador “recuerde” la edad media y luego reemplace los valores imposibles con este valor. El método __init__() (también llamado constructor) iniciará una instancia del transformador, con la edad máxima permitida como entrada. El método fit() calculará y guardará el valor de la edad media (redondeado para que coincida con el formato entero de la edad en los datos), mientras que el método transform() utilizará el valor de la edad media guardado para aplicar la transformación a los datos. # import paquetes
desde sklearn.base import BaseEstimator, TransformerMixin# define el transformador
class AgeImputer(BaseEstimator, TransformerMixin):def __init__(self, max_age):
print(‘Inicializando transformador…’)
self.max_age = max_agedef fit(self, X, y = Ninguno):
self.edad_media = round(X[‘Age’].significar())
volver selfdef transform(self, X):
imprimir (‘reemplazar valores de edad imposibles’)
X.loc[(X[‘age’] > self.max_age)
| (X[‘age’] < 0), “edad”]= self.edad_media
return XSi deseamos ver el resultado de nuestra transformación, podemos aplicar este paso específico de la tubería y ver los datos transformados: age_scaled = pipe[0].fit_transform(tren_X)
age_imputedComo se esperaba, los valores imposibles fueron reemplazados por la edad promedio basada en el conjunto de trenes.Una vez que hayamos escrito nuestro transformador y lo hayamos agregado a la canalización, podemos proceder a aplicar la canalización completa a nuestros datos normalmente.pipe.fit(X_train, y_train)
canalización.predecir(X_prueba)

Dale sabor con transformadores más complejos

El ejemplo anterior muestra una versión simplificada de la realidad, donde solo queríamos agregar un pequeño cambio a una canalización existente. En la vida real, es posible que deseemos agregar varias etapas a nuestra tubería o, a veces, incluso reemplazar todo el flujo de preprocesamiento de una tubería por un transformador de preprocesamiento personalizado. En tales casos, nuestra nueva clase de transformador podría tener métodos adicionales para varias etapas de procesamiento que se aplicarán a nuestros datos, además de los métodos fit() y transform(). Estos métodos se usarán dentro de los métodos fit() y transform() para varios cálculos y procesamiento de datos. Pero, ¿cómo decidimos qué funcionalidades pertenecen al método fit() y cuáles pertenecen al método transform()? Como pauta general , el método de ajuste calcula y guarda cualquier información que podamos necesitar para cálculos posteriores, mientras que el método de transformación usa el resultado de estos cálculos para cambiar los datos. Me gusta repasar las etapas de transformación una por una e imaginar que las estoy aplicando a una nueva muestra. Agrego cada etapa de procesamiento al método de transformación y luego me hago las siguientes preguntas: ¿Esta etapa requiere alguna información de los datos originales?
Los ejemplos de dicha información incluyen valores medios, desviaciones estándar, nombres de columnas, entre otros. Si la respuesta es afirmativa, el cálculo subyacente necesario pertenece al método fit() y la etapa de procesamiento en sí pertenece al método transform(). Este fue el caso del transformador simple ImputeAge(), donde calculamos el valor medio en el método fit() y lo usamos para cambiar los datos en el método transform(). ¿Se requiere esta etapa de procesamiento en sí misma para extraer información que será necesario en una etapa de procesamiento posterior? Por ejemplo, teóricamente podría tener una etapa adicional aguas abajo que requiere la desviación estándar de cada variable. Suponiendo que quiero que la desviación estándar se calcule en los datos imputados, tendré que calcular y guardar los valores estándar del marco de datos transformado. En ese caso, incluiré la etapa de procesamiento en el método transform() así como en el método fit(), pero a diferencia del método transform(), el método fit() no devolverá los datos transformados. En otras palabras, el método fit() puede aplicar transformaciones a los datos si es necesario para fines internos, siempre y cuando no devuelva el conjunto de datos alterado. En última instancia, el método fit() realizará secuencialmente todos los cálculos necesarios y guardará su resultados, y el método transform() aplicará secuencialmente todas las etapas de procesamiento a los datos y devolverá los datos transformados. ¡Eso es todo!

Para concluir…

Comenzamos aplicando una tubería usando transformadores listos para usar. Luego cubrimos la estructura de los transformadores y aprendimos cómo escribir un transformador personalizado y agregarlo a nuestra tubería. Finalmente, repasamos las reglas básicas que determinan la lógica detrás de los métodos de “ajuste” y “transformación” del transformador. En caso de que aún no haya comenzado a usar canalizaciones, espero haberlo convencido de que las canalizaciones son sus amigos, que ayude a mantener sus proyectos de ML organizados, menos propensos a errores, replicables y fáciles de aplicar a nuevos datos. Si encontró útil este artículo, o si tiene algún comentario, ¡me encantaría leerlo en los comentarios!