En los últimos tiempos habrás oído hablar de machine learning, deep learning, reinforcement learning, muchas más cosas que contienen la palabra learning y, por supuesto, Big Data. Con los avances en capacidad de cálculo de los últimos años y la popularización de lenguajes de alto nivel, hemos entrado de lleno en la fiebre de hacer que las máquinas aprendan. En esta clase veremos cómo utilizar el paquete scikit-learn
de Python para poder crear modelos predictivos a partir de nuestros datos de una manera rápida y sencilla.
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
En primer lugar vamos a probar con un ejemplo muy sencillo: ajustar una recta a unos datos. Esto difícilmente se puede llamar machine learning, pero nos servirá para ver cómo es la forma de trabajar con scikit-learn
, cómo se entrenan los modelos y cómo se calculan las predicciones.
En primer lugar fabricamos unos datos distribuidos a lo largo de una recta con un poco de ruido:
x = np.random.randn(50)
y = 2.0 * x + 0.8 * np.random.randn(50)
plt.scatter(x, y)
<matplotlib.collections.PathCollection at 0x7fbbc58782e8>
El proceso para usar scikit-learn
es el siguiente:
features
y variable a predecir y
model.fit
)model.predict
)from sklearn.linear_model import LinearRegression
model = LinearRegression(fit_intercept=True)
features = x.reshape(-1, 1)
model.fit(features, y)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
y_hat = model.predict(features)
Para calcular el error, en el módulo sklearn.metrics
tenemos varias funciones útiles:
from sklearn import metrics
abs_error = metrics.mean_absolute_error(y, y_hat)
abs_error
0.52948707531800132
Y ahora predecimos con datos nuevos:
x_new = np.linspace(x.min(), x.max(), 10)
y_pred = model.predict(x_new.reshape(-1, 1))
plt.scatter(x, y)
plt.plot(x_new, y_pred, 'k--')
plt.scatter(x_new, y_pred, marker='x', lw=3, zorder=10)
plt.fill_between(x_new, y_pred + abs_error, y_pred - abs_error, color="C0", alpha=0.3)
<matplotlib.collections.PolyCollection at 0x7fbbba931400>
¡Y ya está! Lo básico de scikit-learn
está aquí. Lo próximo será usar diferentes tipos de modelos y examinar con rigor su rendimiento para poder seleccionar el que mejor funcione para nuestros datos.
En aprendizaje automático tenemos dos tipos de problemas:
En función de la naturaleza de nuestro problema, scikit-learn
proporciona una gran variedad de algoritmos que podemos elegir.
En scikit-learn
tenemos disponibles muchos datasets clásicos de ejemplo que podemos utilizar para practicar. Uno de ellos es el dataset MNIST, que consiste en imágenes escaneadas de números escritos a mano por funcionarios de los EEUU. Para cargarlo, importamos la función correspondiente de sklearn.datasets
:
from sklearn.datasets import load_digits
digits = load_digits()
print(digits["DESCR"])
Optical Recognition of Handwritten Digits Data Set =================================================== Notes ----- Data Set Characteristics: :Number of Instances: 5620 :Number of Attributes: 64 :Attribute Information: 8x8 image of integer pixels in the range 0..16. :Missing Attribute Values: None :Creator: E. Alpaydin (alpaydin '@' boun.edu.tr) :Date: July; 1998 This is a copy of the test set of the UCI ML hand-written digits datasets http://archive.ics.uci.edu/ml/datasets/Optical+Recognition+of+Handwritten+Digits The data set contains images of hand-written digits: 10 classes where each class refers to a digit. Preprocessing programs made available by NIST were used to extract normalized bitmaps of handwritten digits from a preprinted form. From a total of 43 people, 30 contributed to the training set and different 13 to the test set. 32x32 bitmaps are divided into nonoverlapping blocks of 4x4 and the number of on pixels are counted in each block. This generates an input matrix of 8x8 where each element is an integer in the range 0..16. This reduces dimensionality and gives invariance to small distortions. For info on NIST preprocessing routines, see M. D. Garris, J. L. Blue, G. T. Candela, D. L. Dimmick, J. Geist, P. J. Grother, S. A. Janet, and C. L. Wilson, NIST Form-Based Handprint Recognition System, NISTIR 5469, 1994. References ---------- - C. Kaynak (1995) Methods of Combining Multiple Classifiers and Their Applications to Handwritten Digit Recognition, MSc Thesis, Institute of Graduate Studies in Science and Engineering, Bogazici University. - E. Alpaydin, C. Kaynak (1998) Cascading Classifiers, Kybernetika. - Ken Tang and Ponnuthurai N. Suganthan and Xi Yao and A. Kai Qin. Linear dimensionalityreduction using relevance weighted LDA. School of Electrical and Electronic Engineering Nanyang Technological University. 2005. - Claudio Gentile. A New Approximate Maximal Margin Classification Algorithm. NIPS. 2000.
Ya tenemos los datos separados en matriz de características y vector de predicción. En este caso, tendré 64 = 8x8 características (un valor numérico por cada pixel de la imagen) y mi variable a predecir será el número en sí.
features, labels = digits.data, digits.target
features.shape
(1797, 64)
labels
array([0, 1, 2, ..., 8, 9, 8])
Para visualizar estas imágenes tendremos que hacer un .reshape
:
num_ = features[42]
label_ = labels[42]
num_
array([ 0., 0., 0., 0., 12., 5., 0., 0., 0., 0., 0., 2., 16., 12., 0., 0., 0., 0., 1., 12., 16., 11., 0., 0., 0., 2., 12., 16., 16., 10., 0., 0., 0., 6., 11., 5., 15., 6., 0., 0., 0., 0., 0., 1., 16., 9., 0., 0., 0., 0., 0., 2., 16., 11., 0., 0., 0., 0., 0., 3., 16., 8., 0., 0.])
num_.reshape(8, 8).astype(int)
array([[ 0, 0, 0, 0, 12, 5, 0, 0], [ 0, 0, 0, 2, 16, 12, 0, 0], [ 0, 0, 1, 12, 16, 11, 0, 0], [ 0, 2, 12, 16, 16, 10, 0, 0], [ 0, 6, 11, 5, 15, 6, 0, 0], [ 0, 0, 0, 1, 16, 9, 0, 0], [ 0, 0, 0, 2, 16, 11, 0, 0], [ 0, 0, 0, 3, 16, 8, 0, 0]])
plt.figure(figsize=(2, 2))
plt.imshow(num_.reshape(8, 8), cmap=plt.cm.gray_r)
<matplotlib.image.AxesImage at 0x7fbbb9d4e4e0>
label_
1
Ten en cuenta que nosotros sabemos qué número es cada imagen porque somos humanos y podemos leerlas. El ordenador lo sabe porque están etiquetadas, pero ¿qué pasa si viene una imagen nueva? Para eso tendremos que construir un modelo de clasificación.
from sklearn.linear_model import LogisticRegression
model = LogisticRegression()
model.fit(features, labels)
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1, penalty='l2', random_state=None, solver='liblinear', tol=0.0001, verbose=0, warm_start=False)
Y una vez que hemos ajustado el modelo, comprobemos cuáles son sus predicciones usando los mismos datos de entrenamiento:
labels_hat = model.predict(features)
De nuevo usamos sklearn.metrics
para medir la eficacia del algoritmo:
metrics.accuracy_score(labels, labels_hat)
0.99332220367278801
¡Parece que hemos acertado prácticamente todas! Más tarde volveremos sobre este porcentaje de éxito, que bien podría ser engañoso. De momento, representemos otra medida de éxito que es la matriz de confusión:
metrics.confusion_matrix(labels, labels_hat)
array([[178, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 179, 0, 1, 0, 0, 0, 0, 2, 0], [ 0, 0, 177, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 183, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 181, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 182, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 181, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 179, 0, 0], [ 0, 5, 0, 1, 0, 0, 0, 0, 168, 0], [ 0, 0, 0, 1, 0, 0, 0, 0, 2, 177]])
plt.imshow(metrics.confusion_matrix(labels, labels_hat), cmap=plt.cm.Blues)
<matplotlib.image.AxesImage at 0x7fbbb9adc400>
Una vez que hemos visto los dos tipos de problemas supervisados, vamos a ver cómo se trabajan los problemas no supervisados. En primer lugar vamos a fabricar dos nubes de puntos usando la función make_blobs
:
# https://github.com/amueller/scipy-2016-sklearn/blob/master/notebooks/05%20Supervised%20Learning%20-%20Classification.ipynb
from sklearn.datasets import make_blobs
features, labels = make_blobs(centers=[[6, 0], [2, -1]], random_state=0)
features.shape
(100, 2)
plt.scatter(features[:, 0], features[:, 1], c=labels)
<matplotlib.collections.PathCollection at 0x7fbbb452f4a8>
Hemos creado dos grupos y algunos puntos se solapan, pero ¿qué pasaría si no tuviésemos esta información visual? Vamos a emplear un modelo de clustering para agrupar los datos:
from sklearn.cluster import KMeans
model = KMeans()
model
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300, n_clusters=8, n_init=10, n_jobs=1, precompute_distances='auto', random_state=None, tol=0.0001, verbose=0)
Observa que por defecto tenemos 8 clusters. Veamos qué ocurre:
model.fit(features)
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300, n_clusters=8, n_init=10, n_jobs=1, precompute_distances='auto', random_state=None, tol=0.0001, verbose=0)
Ahora no pasamos la información de las etiquetas al algoritmo a la hora de entrenar. En la práctica por supuesto no la tendremos.
centroids = model.cluster_centers_
centroids
array([[ 6.4137938 , 1.01397005], [ 0.99136674, -0.73308347], [ 2.86129879, -1.00785736], [ 1.48245254, 0.57052198], [ 7.09225761, -0.56687762], [ 4.42631252, 0.51489379], [ 5.47914394, -0.67120775], [ 1.75457429, -2.18478731]])
labels_pred = model.predict(features)
Y ahora preparamos el código para representar todas las regiones:
xmin, xmax = features[:, 0].min(), features[:, 0].max()
ymin, ymax = features[:, 1].min(), features[:, 1].max()
xx, yy = np.meshgrid(
np.linspace(xmin, xmax),
np.linspace(ymin, ymax)
)
mesh = np.c_[xx.ravel(), yy.ravel()]
mesh
array([[ 0.50874241, -3.22340315], [ 0.66713041, -3.22340315], [ 0.82551842, -3.22340315], ..., [ 7.95297862, 2.2408932 ], [ 8.11136662, 2.2408932 ], [ 8.26975462, 2.2408932 ]])
Z = model.predict(mesh)
# http://pybonacci.org/2015/01/14/introduccion-a-machine-learning-con-python-parte-1/
plt.pcolormesh(xx, yy, Z.reshape(xx.shape))
plt.scatter(features[:, 0], features[:, 1], marker='x', color='k')#c=labels_pred)
plt.scatter(centroids[:, 0], centroids[:, 1], marker='+', color='r', lw=2)
<matplotlib.collections.PathCollection at 0x7fbbb4b15ef0>
Si lo metemos todo en una función interactiva:
from ipywidgets import interact
@interact(n=(2, 6))
def cluster(n=3):
model = KMeans(n_clusters=n)
model.fit(features)
labels_pred = model.predict(features)
centroids = model.cluster_centers_
Z = model.predict(mesh)
plt.pcolormesh(xx, yy, Z.reshape(xx.shape))
plt.scatter(features[:, 0], features[:, 1], marker='x', color='k')
plt.scatter(centroids[:, 0], centroids[:, 1], marker='+', color='r', lw=2)
plt.show()
Vamos a rescatar nuestro dataset de los dígitos y tratar de visualizarlo en dos dimensiones, lo que se conoce como reducción de dimensionalidad.
from sklearn.manifold import Isomap
model = Isomap(n_components=2)
model.fit(digits.data)
Isomap(eigen_solver='auto', max_iter=None, n_components=2, n_jobs=1, n_neighbors=5, neighbors_algorithm='auto', path_method='auto', tol=0)
Y ahora proyectamos los datos usando .transform
:
digits_proj = model.transform(digits.data)
plt.scatter(digits_proj[:, 0], digits_proj[:, 1],
c=digits.target, cmap=plt.cm.Vega10, alpha=0.5)
plt.colorbar()
<matplotlib.colorbar.Colorbar at 0x7fbbb414e4a8>
load_iris
) utilizando las funciones que tienes más abajo. ¿Hay alguna forma clara de separar las tres especies de flores?features
y vector de etiquetas labels
. Conviértelos a arrays de NumPy usando .as_matrix()
.sklearn.manifold.Isomap
o sklearn.decomposition.PCA
y usa un algoritmo de clustering con 3 clusters. ¿Se parecen los clusters que aparecen a los grupos originales?# sp: preserve
import pandas as pd
def load_iris_df():
from sklearn.datasets import load_iris
iris = load_iris()
features, labels = iris.data, iris.target
df = pd.DataFrame(features, columns=iris.feature_names)
df["species"] = pd.Categorical.from_codes(iris.target, categories=iris.target_names)
#df = df.replace({'species': {0: iris.target_names[0], 1: iris.target_names[1], 2: iris.target_names[2]}})
return df
iris_df = load_iris_df()
# sp: preserve
iris_df.head()
sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | species | |
---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | setosa |
1 | 4.9 | 3.0 | 1.4 | 0.2 | setosa |
2 | 4.7 | 3.2 | 1.3 | 0.2 | setosa |
3 | 4.6 | 3.1 | 1.5 | 0.2 | setosa |
4 | 5.0 | 3.6 | 1.4 | 0.2 | setosa |
# sp: preserve
_ = pd.scatter_matrix(iris_df, c=iris_df["species"].cat.codes, figsize=(10, 10))
Las siguientes celdas contienen configuración del Notebook
Para visualizar y utlizar los enlaces a Twitter el notebook debe ejecutarse como seguro
File > Trusted Notebook
# Esta celda da el estilo al notebook
from IPython.core.display import HTML
css_file = '../styles/aeropython.css'
HTML(open(css_file, "r").read())