Preparé unas clases introductorias a la librería de aprendizaje automático scikit-learn. Las notas de las clases, que aquí comparto, si bien no llegan a conformar un tutorial, perfilan como una guía para recorrer la documentación de la librería en gran parte de su extensión.

En esencia reorganicé las guías de usuario de la librería en torno al flujo de trabajo del desarrollo de modelos. Adicionalmente, como en la vida real los datos de los que uno suele disponer están lejos de ser perfectos, añadí algunas técnicas para trabajar con conjuntos de datos desbalanceados.

  • Recorrido por las posibilidades de la librería (guías de usuario)
  • Familizarización con la documentación (API)
  • Tarea: implementación de un flujo de trabajo sencillo para regresión (parecido al tutorial básico)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sn

Requisitos

Algo de familiaridad con NumPy. Ver este tutorial.

Utilidad

  • Aprendizaje supervisado
    • Clasificación
    • Regresión
  • Aprendizaje no supervisado
  • Aprendizaje por refuerzos

Redes neuronales: solo perceptrón multicapa y restricted Boltzmann machine.

Datos

scikit-learn consume datos con forma de matriz o arreglo bidimensional, de dimensión (n_muestras, n_atributos) — es como imaginamos normalmente a los datos, dispuestos en una tabla donde las columnas son los atributos y hay tantas muestras como filas.

Convencionalmente en la documentación la varible X se utiliza para los atributos propiamente dichos, y la variable y para los objetivos. Cuando el objetivo es uno solo, y suele tomar la forma de arreglo unidimensional de dimensión (n_muestras,).

Objetos

En scikit-learn hay dos tipos fundamentales de objetos:

  • Los transformadores, que implementan los métodos

    • fit(X, y) y
    • transform(X),
  • y los estimadores, que implementan

    • fit(X, y),
    • predict(X).

Flujo de trabajo

Datos

Vamos a usar el conjunto de datos de plantas de iris para los ejemplos. Este dataset es una especie de hola mundo del aprendizaje de máquinas.

Cantidad de instancias: 150

Atributos (4)

1. Largo del sépalo [cm]
2. Ancho del sépalo [cm]
3. Largo del pétalo [cm]
4. Ancho del pétalo [cm]

Objetivos (1)

5. Clase
    - Setosa
    - Versicolour
    - Virginica

Valores ausentes: No

from sklearn.datasets import load_iris

iris = load_iris()
X, y = iris.data, iris.target

print('X: datos   ', X.shape)
print('y: objetivo', y.shape)
X: datos    (150, 4)
y: objetivo (150,)

Limpieza de datos

https://en.wikipedia.org/wiki/Data_cleansing

  • Cardinalidad
  • Rango
  • Desviación
  • Tipo
    • Booleano
    • Numéro (separadores)
    • Texto
      • espacios (trimming)
      • tildes
      • casos (mayúsculas, minúsculas)
  • Codificación (UTF-8, etcétera)

Partición del conjunto de datos

  • Entrenamiento (50%)
  • Validación (25%) — salvo cross-validation o ausencia de hiperparámetros.
  • Prueba (25%)

Ejemplo: train_test_split

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)

Transformadores: preprocesamiento de atributos

Transformación

http://scikit-learn.org/stable/modules/preprocessing.html#standardization-or-mean-removal-and-variance-scaling

  • Estandarización (StandardScaler) — A cada atributo le remueve su valor medio y lo escala dividiéndolo por su desviación estándar.
    • Centrar los datos es prácticamente obligatorio (hay excepciones).
    • Normalizar los datos solo si los atributos difieren en unidades y/u órdenes de magnitud.
  • Reajuste
    • Rango (MinMaxScaler)
    • Valor absoluto (MaxAbsScaler)

Ejemplo: StandardScaler

from sklearn.preprocessing import StandardScaler

std = StandardScaler(with_mean=True, with_std=True)
std.fit(X_train)
X_train_std = std.transform(X_train)     

http://scikit-learn.org/stable/modules/preprocessing.html#normalization

  • Normalización (Normalizer) — Divide vectores por su norma (afecta filas en vez de columnas).

Imputación de valores ausentes

http://scikit-learn.org/stable/modules/preprocessing.html#imputation-of-missing-values

  • Descarte (tirar la muestra)
  • Valor más común
  • Valor medio
  • Valor mediano
  • Estimación (clasificación/regresión)
  • Hot-deck (el valor de la muestra más parecida)
  • Valor ausente (NA) como otro valor

Creación

http://scikit-learn.org/stable/modules/preprocessing.html#generating-polynomial-features

De $(X_1, X_2)$ a $(1, X_1, X_2, X_1^2, X_1X_2, X_2^2)$.

Reducción de dimensionalidad

http://scikit-learn.org/stable/modules/unsupervised_reduction.html

  • PCA — análisis de componentes principales. Estandarizar los datos antes de usar PCA.
  • Casi todos los estimadores no supervisados implementan el método transform(X).
  • Algunos estimadores supervisados también.

Ejemplo: PCA

from sklearn.decomposition import PCA

pca = PCA(n_components=2, whiten=False)
pca.fit(X_train_std)
X_train_pca = pca.transform(X_train_std)

Selección — solo aprendizaje supervisado

http://scikit-learn.org/stable/modules/feature_selection.html

  • Umbral de varianza
  • Análisis univariado
  • Usando un estimador
  • Eliminación recursiva (también existe la agregación recursiva)

Ejemplo: SelectKBest

from sklearn.feature_selection import SelectKBest

kbest = SelectKBest(k=1)
kbest.fit(X_train_std, y_train)
X_train_kbest = kbest.transform(X_train_std)

Estimadores: selección de modelos

¿Qué estimador usar para el conjunto de datos de plantas de iris?

  1. ¿Más de 50 muestras? Sí, el conjunto de datos tiene 150 (en realidad un poco menos porque hemos separado un conjunto de prueba).
  2. ¿Hay que predecir una categoría? Sí, queremos predecir a qué especie pertenece cada planta.
  3. ¿Los datos estás anotados? Sí, están anotados en tres categorías.
  4. ¿Más de 100,000 muestras? No...

Nos recomiendan usar una máquina de vectores de soporte (support vector machine) con un kernel lineal.

Ejemplo: SVC

from sklearn.svm import SVC

estimador = SVC(kernel='linear', C=1, probability=True)
estimador.fit(X_train_std, y_train)

X_test_std = std.transform(X_test)

y_pred = estimador.predict(X_test_std)

predict_proba(X)

http://scikit-learn.org/stable/modules/calibration.html

Al realizar la clasificación, a menudo se desea no solo predecir la etiqueta de la clase, sino también obtener una probabilidad de la etiqueta. Esta probabilidad da algún tipo de confianza en la predicción. Algunos modelos pueden darle estimaciones pobres de las probabilidades de la clase y algunos incluso no admiten la predicción de probabilidad. El módulo de calibración le permite calibrar mejor las probabilidades de un modelo determinado o agregar soporte para la predicción de probabilidad.

estimador.predict_proba(X_test[:3]).round()
array([[ 0.,  0.,  1.],
       [ 0.,  0.,  1.],
       [ 0.,  1.,  0.]])

Cantidad de objetivos — solo aprendizaje supervisado

http://scikit-learn.org/stable/modules/multiclass.html

  • Clasificador
    • Binario
    • Multi
      • Clase
      • Etiqueta
      • Clase-etiqueta
  • Regresor
    • Univariado
    • Multivariado

En scikit-learn todos los clasificadores aceptan varias clases. Algunos estimadores trabajan inherentemente con múltiples objetivos y le sacan provecho a la correlación entre los mismos. Cuando no es el caso del estimador, existen diferentes estrategias para que admita múltiples objetivos:

  • OVO (uno-contra-uno),
  • OVA (uno-contra-todos).

Ensambles — solo aprendizaje supervisado

http://scikit-learn.org/stable/modules/ensemble.html

Diferentes tipos de ensambles:

  • Promediadores: estimadores en paralelo, reducen la varianza.
  • Propulsores (boosting): estimadores en serie, reducen el sesgo.

Evaluación del modelo

http://scikit-learn.org/stable/modules/model_evaluation.html

Cada estimador implementa un método llamado score(X, y) que devuelve un puntaje del desempeño del estimador. El puntaje es calculado usando una métrica acorde a la naturaleza del estimador, por ejemplo los regresores suelen reportar mientas que los clasificadores, efectividad.

estimador.score(X_test_std, y_test)
0.97368421052631582
from sklearn.metrics import accuracy_score

accuracy_score(y_test, y_pred)
0.97368421052631582
from sklearn.metrics import confusion_matrix

confusion_matrix(y_test, y_pred)
array([[13,  0,  0],
       [ 0, 15,  1],
       [ 0,  0,  9]])
from sklearn.metrics import classification_report

clases = ['Setosa', 'Versicolour', 'Virginica']
print(classification_report(y_test, y_pred, target_names=clases))
             precision    recall  f1-score   support

     Setosa       1.00      1.00      1.00        13
Versicolour       1.00      0.94      0.97        16
  Virginica       0.90      1.00      0.95         9

avg / total       0.98      0.97      0.97        38

F1

Útil para conjuntos de datos desbalanceados.

$F_1 = 2 \cdot \frac{\mathrm{precision} \cdot \mathrm{recall}}{\mathrm{precision} + \mathrm{recall}}$

from sklearn.metrics import f1_score

f1_score(y_test, y_pred, average='weighted')  
0.97395228308462156

Kappa de Cohen

https://en.wikipedia.org/wiki/Cohen's_kappa

Útil para conjuntos de datos desbalanceados.

$\kappa = \frac{p_o - p_e}{1 - p_e}$

from sklearn.metrics import cohen_kappa_score

cohen_kappa_score(y_test, y_pred)
0.95978835978835975

Flujo de datos

Pipeline

http://scikit-learn.org/stable/modules/pipeline.html

Todos los objetos del flujo, excepto el último, deben ser muestreadores/transformadores (deben implementar el método sample/transform). El último objeto puede ser de cualquier tipo, suele ser un estimador.

Prestar atención a ColumnTransformer que permite aplicar transformadores a DataFrames de Pandas.

from sklearn.pipeline import make_pipeline

flujo = make_pipeline(StandardScaler(), SVC())
flujo.fit(X_train, y_train)
flujo.score(X_test, y_test)
0.97368421052631582

Optimización: validación cruzada

http://scikit-learn.org/stable/modules/cross_validation.html

El conjunto de datos de validación sirve para ajustar a los hiperparámetros de los objetos que componen el flujo de trabajo, tanto como para la composición del flujo en sí mismo. La validación cruzada es útil cuando el conjunto de validación es necesario y las muestras son escasas.

Se necesitan dos cosas:

  1. Una estrategia de particionamiento de los datos.
  2. Una métrica de evaluación.

Estrategias

  • K-fold, stratified k-fold — estrategias por defecto para regresores y clasificadores respectivamente.
  • Leave one out (LOO)
  • Leave P out (LPO)
  • Shuffle & split, stratified shuffle & split

Métricas

  • De no especificarse ninguna, se usa el método score(X, y) del estimador.
  • Las métricas más comunes se pueden pasar como argumento (string), ver tabla.
  • Se pueden armar puntuadores a partir de cualquier métrica, tanto de la API como definidas por el usuario, y pasar como argumento (función).

http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html

from sklearn.model_selection import cross_val_score

resultados = cross_val_score(flujo, X_train, y_train, cv=5, scoring='f1_weighted')

print('F1 promedio: %0.2f (+/- %0.2f)' % (resultados.mean(), resultados.std() * 2))
F1 promedio: 0.96 (+/- 0.08)
from sklearn.model_selection import GridSearchCV

hiperparámetros = {
    'svc__kernel':('linear', 'rbf'),
         'svc__C':[1, 10]
}

grilla = GridSearchCV(flujo, hiperparámetros)
grilla.fit(X_train, y_train)
    
estimador = grilla.best_estimator_

Evaluación final

Una vez elegido el modelo y ajustados sus hiperparámetros, si se desea los conjuntos de datos de entrenamiento y de validación pueden fusionarse en un nuevo conjunto de entrenamiento para reentrenar el modelo final usando más datos — de hecho es lo que hace GridSearchCV para el mejor estimador.

En cambio por más seguridad que se tenga del desempeño del modelo, no es recomendable usar los datos del conjunto de prueba, es mejor usarlos para medir su desempeño y asegurarnos de que esté libre de errores.

y_pred = estimador.predict(X_test)
f1_score(y_test, y_pred, average='weighted')
import pickle

# persistencia
with open('modelo.pickle', 'wb') as archivo:
    pickle.dump(estimador, archivo)

# carga
with open('modelo.pickle', 'rb') as archivo:
    estimador = pickle.load(archivo)

Extra: Muestreo — conjunto desbalanceado

El entrenamiento y la validación de estimadores suele requerir conjuntos de datos balanceados; no así la prueba de los mismos ya que deben enfrentar datos reales del problema (desbalanceados).

scikit-learn apenas provee algoritmos de muestro, podemos usar la extensión imbalanced-learn que implementa varios.

pip install imbalanced-learn

imbalanced-learn aporta objetos del tipo muestreador (sampler) que implementan los métodos

  • fit(X, y) y
  • sample(X, y).

Algunos algoritmos:

  • Under-sampling
    • ClusterCentroids
    • RandomUnderSampler
  • Over-sampling
    • SMOTE
    • RandomOverSampler

Nota: imbalanced-learn re-implementa la clase Pipeline para que admita muestreadores.

Ejemplo: RandomUnderSampler

from sklearn.datasets import make_classification
from imblearn.under_sampling import RandomUnderSampler

# generación de un conjunto de datos
X, y = make_classification(n_classes=2, class_sep=2, weights=[0.1, 0.9],
                           n_informative=3, n_redundant=1, flip_y=0,
                           n_features=20, n_clusters_per_class=1,
                           n_samples=200, random_state=10)

# aplicación de random under-sampling
rus = RandomUnderSampler()
X_resampled, y_resampled = rus.fit_sample(X, y)

Ejercicio: Boston price data set

Cantidad de instancias: 506

Atributos (13)

1.  CRIM per capita crime rate by town
2.  ZN proportion of residential land zoned for lots over 25,000 sq.ft.
3.  INDUS proportion of non-retail business acres per town
4.  CHAS Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
5.  NOX nitric oxides concentration (parts per 10 million)
6.  RM average number of rooms per dwelling
7.  AGE proportion of owner-occupied units built prior to 1940
8.  DIS weighted distances to five Boston employment centres
9.  RAD index of accessibility to radial highways
10. TAX full-value property-tax rate per 10,000 USD
11. PTRATIO pupil-teacher ratio by town
12. B 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
13. LSTAT % lower status of the population

Objetivos (1)

14. MEDV Median value of owner-occupied homes in 1000’s USD

Valores ausentes: No

from sklearn.datasets import load_boston

boston = load_boston()
X, y   = boston.data, boston.target

print('X: datos   ', X.shape)
print('y: objetivo', y.shape)
X: datos    (506, 13)
y: objetivo (506,)
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)

Sugerencias:

  • 1.1 Modelo lineal
    • 1.1.1. Ordinary least squares
    • 1.1.2. Ridge regression
    • 1.1.3. Lasso
 
from sklearn.metrics import r2_score

r2_score(y_test, y_pred) # mientras más cerca de 1.0, mejor
0.89914051165600339

Automatización

TPOT es una herramienta de aprendizaje automático automatizado que optimiza el flujo de trabajo.

pip install tpot
from tpot import TPOTRegressor

tpot = TPOTRegressor(generations=3, population_size=20, verbosity=2)
tpot.fit(X_train, y_train)

y_pred = tpot.predict(X_test)
Warning: xgboost.XGBRegressor is not available and will not be used by TPOT.
Optimization Progress:  50%|█████     | 40/80 [00:11<00:09,  4.27pipeline/s]
Generation 1 - Current best internal CV score: 14.469198135864161
Optimization Progress:  75%|███████▌  | 60/80 [00:28<00:10,  1.84pipeline/s]
Generation 2 - Current best internal CV score: 14.43020757845812
                                                                            
Generation 3 - Current best internal CV score: 14.358659442160791

Best pipeline: RandomForestRegressor(input_matrix, RandomForestRegressor__bootstrap=True, RandomForestRegressor__max_features=DEFAULT, RandomForestRegressor__min_samples_leaf=DEFAULT, RandomForestRegressor__min_samples_split=8, RandomForestRegressor__n_estimators=DEFAULT)

from sklearn.metrics import r2_score

r2_score(y_test, y_pred) # mientras más cerca de 1.0, mejor
0.87143869370430582
tpot.export('tpot_boston_pipeline.py')