In [ ]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
En esta sección, estudiamos cómo encadenar los estimadores.
Para algunos tipos de datos, por ejemplo, para datos de tipo texto, debemos aplicar un paso de extracción de características para convertirlos en características numéricas. Para demostrarlo, vamos a cargar el dataset de spam que utilizamos previamente.
In [ ]:
import os
with open(os.path.join("datasets", "smsspam", "SMSSpamCollection")) as f:
lines = [line.strip().split("\t") for line in f.readlines()]
text = [x[1] for x in lines]
y = [x[0] == "ham" for x in lines]
In [ ]:
from sklearn.model_selection import train_test_split
text_train, text_test, y_train, y_test = train_test_split(text, y)
Hemos aplicado extracción de características manualmente del siguiente modo:
In [ ]:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
vectorizer = TfidfVectorizer()
vectorizer.fit(text_train)
X_train = vectorizer.transform(text_train)
X_test = vectorizer.transform(text_test)
clf = LogisticRegression()
clf.fit(X_train, y_train)
clf.score(X_test, y_test)
El aprender una transformación y luego aplicarla a los datos de test es muy común en aprendizaje automático. Por tanto, scikit-learn tiene una forma cómoda de hacer esto, llamada tuberías (pipelines):
In [ ]:
from sklearn.pipeline import make_pipeline
pipeline = make_pipeline(TfidfVectorizer(), LogisticRegression())
pipeline.fit(text_train, y_train)
pipeline.score(text_test, y_test)
Como puedes comprobar, esto hace el código mucho más corto y fácil de manejar. Realmente, está ejecutándose exactamente lo mismo. Cuando llamamos a fit
en la tubería, se llamará a cada método de forma sucesiva.
Después del primer ajuste, se utilizará el método transform
para crear una nueva representación. Ésta se utilizará como entrada en el método fit
del siguiente paso, y así sucesivamente. En el último paso, no se llamará a transform
.
Si llamamos a score
, solo se llamará a transform
en cada paso, generando lo que serían los datos de test. Al final aplicaremos score
sobre la representación final obtenida. Lo mismo pasa para predict
.
Construir tuberías no solo nos permite simplificar el código, también es muy importante para el ajuste de parámetros. Imaginemos que queremos ajustar el parámetro $C$ de la regresión logística anterior.
Lo podríamos hacer de la siguiente forma (incorrecta):
In [ ]:
# Este código ilustra un error común, ¡no lo utilices!
from sklearn.model_selection import GridSearchCV
vectorizer = TfidfVectorizer()
vectorizer.fit(text_train)
X_train = vectorizer.transform(text_train)
X_test = vectorizer.transform(text_test)
clf = LogisticRegression()
grid = GridSearchCV(clf, param_grid={'C': [.01, .1, 1, 10, 100]}, cv=5)
grid.fit(X_train, y_train)
grid.score(X_test, y_test)
Hemos aplicado búsqueda grid con validación cruzada utilizando X_train
. Sin embargo, cuando aplicamos TfidfVectorizer
estamos considerando el conjunto X_train
completo, no solo los folds de entrenamiento. Por tanto, podríamos estar usando conocimiento acerca de la frecuencia de las palabras en los folds de test. Esto se llama "contaminación" del conjunto de test y lleva a estimaciones demasiado optimistas del rendimiento de generalización o a parámetros seleccionados de forma incorrecta. Sin embargo, podemos arreglar esto con una tubería:
In [ ]:
from sklearn.model_selection import GridSearchCV
pipeline = make_pipeline(TfidfVectorizer(),
LogisticRegression())
grid = GridSearchCV(pipeline,
param_grid={'logisticregression__C': [.01, .1, 1, 10, 100]}, cv=5)
grid.fit(text_train, y_train)
grid.score(text_test, y_test)
Observa que tenemos que indicar en que parte de la tubería aparece el parámetro $C$. Esto lo hacemos con la sintaxis especial __
. El nombre que hay antes del __
es simplemente el nombre de la clase en minúscula, la parte que hay después de __
es el parámetro que queremos ajustar por búsqueda grid.
Otro beneficio de considerar tuberías es que podemos buscar sobre los propios parámetros de los algoritmos de extracción de características mediante el uso de GridSearchCV
:
In [ ]:
#Este código puede tardar bastante tiempo
from sklearn.model_selection import GridSearchCV
pipeline = make_pipeline(TfidfVectorizer(), LogisticRegression())
params = {'logisticregression__C': [.1, 1, 10, 100],
"tfidfvectorizer__ngram_range": [(1, 1), (1, 2), (2, 2)]}
grid = GridSearchCV(pipeline, param_grid=params, cv=5)
grid.fit(text_train, y_train)
print(grid.best_params_)
grid.score(text_test, y_test)
In [ ]: