Usando pipelines para ahorrar tiempo
Leandro Ruiz
Posted on September 15, 2020
Existe una forma muy sencilla de ahorrarnos tiempo en la etapa de pre-procesamiento y de ajuste de parámetros para nuestros modelos de aprendizaje automático.
Nota: Por lo que entiendo, en español se le llama tubería, pero como no me siento cómodo hablando de plomería, la llamaré con su nombre en ingles; pipeline.
Ahora sin más, comenzemos:
Lo primero es importar el objeto pipeline
de Scikit-Learn:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MinMaxScaler
from sklearn.svm import SVC
pipe = Pipeline([('scaler', MinMaxScaler()), ('svm', SVC())])
Aquí hemos creado dos pasos: el primero, llamado "scaler", es una instancia de MinMaxScaler
, y el segundo, llamado "svm", es una instancia de SVC
. Ahora, podemos entrenar nuestra pipeline, como cualquier otro estimador de Scikit-Learn:
pipe.fit(X_train, y_train)
Usando pipelines con Grid Search
Primero, creamos una grilla de parámetros (parameter grid):
param_grid = {'svm__C': [0.001, 0.01, 0.1, 1, 10, 100],
'svm__gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
Con esta grilla, usamos GridSearchCV
como siempre:
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5) # we call the pipeline as an attribute
grid.fit(X_train, y_train)
print("Best cross-validation accuracy: {:.2f}".format(grid.best_score_))
print("Test set score: {:.2f}".format(grid.score(X_test, y_test)))
print("Best parameters: {}".format(grid.best_params_))
Código completo en este link.
Una mejor froma de hacer pipelines
Usando make_pipeline
de Scikit-Learn, es mucho más sencillo crear una pipeline:
from sklearn.pipeline import make_pipeline
# sintaxis estandar
pipe_long = Pipeline([("scaler", MinMaxScaler()), ("svm", SVC(C=100))])
# sintaxis abreviada
pipe_short = make_pipeline(MinMaxScaler(), SVC(C=100))
Accediendo a los atributos de los pasos
En algún momento querrás inspeccionar los atributos de uno de los pasos de la pipeline. Por ejemplo, los coeficientes de un modelo lineal o los componentes extraidos por PCA (Principal Component Analysis). La forma mas facil de acceder a estos pasos en una pipeline es a través del atributo named_steps
, que es un diccionario con los nombres de los atributos como estimadores:
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
pipe = make_pipeline(StandardScaler(), PCA(n_components=2), StandardScaler())
print("Pipeline steps:\n{}".format(pipe.steps))
pipe.fit(cancer.data)
# extrae los primeros dos componentes principales del paso de "pca"
components = pipe.named_steps['pca'].components_
print("components.shape: {}".format(components.shape))
Accediendo a atributos en una pipeline con Grid Search
Vamos a realizar una grid search en un clasificador de regresión lineal, usando una pipeline y un StandardScaler
para escalar los datos antes de pasarlos al clasificador.
from sklearn.linear_model import LogisticRegression
pipe = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
El parametro de la regularización para ajustar en regresión logistica es el parámetro c
:
param_grid = {'logisticregression__C': [0.01, 0.1, 1, 10, 100]}
Recuerda: La sintaxis para definir una cuadrícula de parámetros para una pipeline es especificar para cada parámetro el nombre del paso, seguido por
__
(doble guion bajo), seguido por el nombre del parámetro. Por ejemplo, para acceder al parámetrogamma
de un modelosvm
, llamamos asvm__gamma
, para el parametroC
de un modeloLogisticRegression
, llamamos alogisticregression__C
.
Divide el dataset y entrena el modelo:
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=4)
grid = GridSearchCV(pipe, param_grid, cv=5)
grid.fit(X_train, y_train)
¿Así que cómo accedemos a los coeficientes del mejor modelo de regresión logistica que fue encontrado por GridSearchCV
?
print('Best estimator:\n{}'.format(grid.best_estimator_))
En este caso, el best_estimator_
es una pipeline con dos pasos, StandardScaler
y LogisticRegression
. Para acceder al paso LogisticRegression
, podemos usar el atributo named_steps
de la pipeline:
print("Logistic regression step:\n".format(grid.best_estimator_.named_steps['logisticregression']))
Ahora que ya tenemos la regresión logística entrenada, podemos acceder a los coeficientes asociados a cada atributo de entrada:
print("Logistic regression coefficients:\n{}".format(grid.best_estimator_.named_steps['logisticregression'].coef_))
Obteniendo pasos de pre-procesamiento con Grid-Search
Usando pipelines, podemos encapsular todos los pasos de pre-procesamiento en nuestro proceso de aprendizaje automático es un solo estimador de Scikit-Learn. Otro beneficio de hacer esto es que podemos ajustar los parámetros del pre-procesamiento usando el resultado de una tarea supervisada como regresión o clasificación.
from sklearn.datasets import load_boston
boston = load_boston()
from sklearn.linear_model import Ridge
X_train, X_test, y_train, y_test = train_test_split(
boston.data, boston.target, random_state=0)
from sklearn.preprocessing import PolynomialFeatures
pipe = make_pipeline(StandardScaler(), PolynomialFeatures(), Ridge())
¿Cómo sabemos que grados de polinomios elegir, o si elegir polinomios o interacciones? Idealmente, queremos seleccionar el parámetro de grado en función del resultado de la clasificación. Usando nuestra pipeline, podemos buscar sobre el parámetro de grado junto con el parámetro alpha o Ridge.
Para hacer esto, definimos una param_grid
que contiene ambos, pre-fijados apropiadamente por el nombre de los pasos:
param_grid = {'polynomialfeatures__degree': [1, 2, 3],
'ridge__alpha': [0.001, 0.01, 0.1, 1, 10, 100]}
Ahora podemos correr de nuevo la grilla de parametros:
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5, n_jobs=-1)
grid.fit(X_train, y_train)
Ahora podemos visualizar los resultados de la validacion cruzada(cross-validation) usando un heat-map:
plt.matshow(grid.cv_results_['mean_test_score'].reshape(3, -1),
vmin=0, cmap='viridis')
plt.xlabel("ridge__alpha")
plt.ylabel("polynomialfeatures__degree")
plt.xticks(range(len(param_grid['ridge__alpha'])), param_grid['ridge__alpha'])
plt.yticks(range(len(param_grid['polynomialfeatures__degree'])),
param_grid['polynomialfeatures__degree'])
plt.colorbar()
Usando Grid-Search para saber que modelo usar
Aquí dejo un ejemplo comparando un RandomForestClassifier
y un SVC
. Sabemos que el SVC
quizás necesite datos escalados, así que tambien buscamos sobre usar StandardScaler
o no pre-procesar los datos. Para el RandomForestClassifier
, ya sabemos que no necesita ningun tipo de pre-procesamiento.
Comenzamos definiendo la pipeline. Aquí, nombramos los pasos explicitamente. Queremos dos pasos, uno de pre-procesamiento y despues el clasificador. Podemos instanciar esto usando SVC
y StandardScaler
:
pipe = Pipeline([('preprocessing', StandardScaler()), ('classifier', SVC())])
Ahora podemos definir la grilla de parámetros para realizar la busqueda. Queremos que el clasificador sea un RandomForestClassifier
o SVC
. Como tienen diferentes parámetros a ajustar, y necesitan diferentes pre-procesamientos, podemos hacer una lista de grillas de búsqueda:
from sklearn.ensemble import RandomForestClassifier
param_grid = [
{'classifier': [SVC()], 'preprocessing': [StandardScaler(), None],
'classifier__gamma': [0.001, 0.01, 0.1, 1, 10, 100],
'classifier__C': [0.001, 0.01, 0.1, 1, 10, 100]},
{'classifier': [RandomForestClassifier (n_estimators=100)],
'preprocessing': [None], 'classifier__max_features': [1, 2, 3]}]
Ahora instanciamos y corremos la grilla como siempre:
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, random_state=0)
grid = GridSearchCV(pipe, param_grid, cv=5)
grid.fit(X_train, y_train)
print("Best params:\n{}\n".format(grid.best_params_))
print("Best cross-validation score: {:.2f}".format(grid.best_score_))
print("Test-set score: {:.2f}".format(grid.score(X_test, y_test)))
Y de esta sencilla manera, obtenemos el mejor modelo con sus mejores parámetros incluidos.
Conclusión
Crear pipelines es una gran manera de ahorrar mucho tiempo en nuestros proyectos. Espero que este articulo les haya servido, y si llegaste hasta aquí, muchas gracias. ¡Hasta la proxima!
Posted on September 15, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 13, 2021