Para iniciar cualquier notebook, comenzaremos por invocar los módulos necesarios para su desarrollo. Para esto utilizaremos el comando import seguido del nombre del módulo. Cuando queramos utilizar una función interna del módulo, debemos escribir su nombre antes de la función. Por ejemplo:
import modulo
modulo.funcion()
en el caso en que no queramos escribir el nombre completo de un modulo, podemos colocar con alias con el comando as
import modulo as md
md.funcion()
finalmente, si solo queremos acceder de forma directa a todas las funciones internas del módulo sin necesidad de escribir su nombre (ni el alias) cada vez, podemos escribir
from modulo import *
funcion()
lo cual cargará todas las funciones del módulo sobrecargando aquellas funciones ya definidas en el código que tengan el mismo nombre.
Este último método, aunque cómodo, es el menos aconsejable de todos</span>.
In [ ]:
import pandas as pd # Cargamos pandas con el alias pd
En este notebook resolveremos un problema simple de clasificación que deja en evidencia conceptos básicos de Machine Learning (ML). El problema planteado consiste en identificar la especie del animal (perro o gato) basados en tres características: ¿el animal busca la pelota cuando se la lanzamos?, ¿el animal suele ser apático? y ¿el animal disfruta más de la comida de perro, de la de gato o del bacon?
Para la carga de datos usaremos la función read_csv
de pandas. Pandas cuenta con un amplio listado de funciones para la carga de datos. Mas informacion en la documentación de la API.
In [ ]:
dfl = pd.read_csv('data/perros_o_gatos.csv', index_col='observacion')
print('Estos datos han sido tomados del libro Mastering machine learning with scikit-learn de Gavin Hackeling, \
PACKT publishing open source, pp. 99')
dfl # En jupyter al escribir una variable sin mas, la celda nos devuelve su contenido.
Los datos se componen de observaciones numeradas del 1 al 14 y 3 features o características representadas en las columnas (también se les conocen como inputs). La columna especie es la respuesta a nuestro problema, por lo que no representa un feature. Esto quiere decir que solo la usaremos para saber si el algoritmo de machine learning está haciendo una buena clasificación o no. A esta columna (especie) se la suele llamar target, label, output o y.
Se dice que un problema de machine learning es supervisado si dentro de los datos tenemos el target, por lo que podemos evaluar a nuestro algoritmo durante su entrenamiento.
Si no contamos con las etiquetas o labels estaremos ante un problema no supervisado. El algoritmo deberá encontrar por sí mismo los patrones que puedan diferenciar los datos. Un ejemplo de este tipo de problemas es cuando queremos reconocer objetos en una imagen. Nuestro algoritmo intentará segmentar los diferentes objetos, usando por ejemplo sus contornos, pero sin conocer la forma exacta o el objeto que debe identificar.
In [ ]:
dfl.describe()
In [ ]:
dfl['juega al busca'].sum()
In [ ]:
dfl.loc[dfl['especie']=='perro','juega al busca'].sum()
In [ ]:
labels = dfl['especie']
df = dfl[['juega al busca', 'apatico', 'comida favorita']]
df
In [ ]:
labels
In [ ]:
df['comida favorita'].value_counts()
Las variables categóricas deben ser convertidas a numéricas para poder ser interpretadas por el algorítmo de machine learning. Una posible codificación sería:
| comida favorita | valor |
|-----------------|---------|
|comida de gato | 0 |
|comida de perro | 1 |
|bacon | 2 |
-----------------------------
Sin embargo, esta codificación asigna un orden artificial a las variables. Nuestro ordenador sabe que 0 < 1 < 2, por lo que asociará que
comida de gato < comida de perro < bacon.
Este tipo de codificación representa la columna comida favorita en tres columnas de 0 o 1 de la siguiente manera
| comida favorita | comida favorita=comida de gato | comida favorita=comida de perro | comida favorita=bacon |
|-----------------|--------------------------------|---------------------------------|-----------------------|
|comida de gato | 1 | 0 | 0 |
|comida de perro | 0 | 1 | 0 |
|bacon | 0 | 0 | 1 |
--------------------------------------------------------------------------------------------------------------
Atención, se debe tener cuidado cuando se desee utilizar este tipo de codificación en datasets (muy) grandes en el que el némero de categorías son cientos o miles, pues cada categoría generará una nueva columna en nuestro dataset.
El módulo scikit-learn contiene una gran mayoría de las herramientas que necesitamos para resolver un problema típico de machine learning. Aquí podemos encontrar algorítmos de clasificación, regresión, clustering; además de métodos para el preprocesado de datos. Mas información en el sitio oficial de scikit-learn.
Para cargar este módulo usaremos el comando
import sklearn
en lugar de usar su nombre completo.
Otra forma de cargar funciones de un módulo es hacer referencia a la función directamente:
from sklearn.preprocessing import OneHotEncoder
A pesar de que la función OneHotEncoder
existe en sklearn, nosotros utilizaremos otra función llamada DictVectorizer
. Esta función recibe como entrada variables numéricas y/o categóricas y devuelve las variables numéricas como flotantes y a las categóricas le aplica la codificación one-hot.
Nota: la función DictVectorizer
no respeta el orden de entrada de las columnas pudiendo devolver un dataframe con otro orden de columnas. Para que respete el orden de las filas, debemos especificar el parámetro orient='records'
</span>.
In [ ]:
from sklearn.feature_extraction import DictVectorizer
vectorizer = DictVectorizer(sparse=False)
ab = vectorizer.fit_transform(df.to_dict(orient='records'))
dft = pd.DataFrame(ab, columns=vectorizer.get_feature_names())
dft.head()
De esta forma, nuestro dataframe ya está preparado para ser utilizado por cualquiera de los algoritmos de clasificación de scikit-learn.
Un árbol de clasificación divide los datos en subconjuntos cada vez mas pequeños para ir determinando la clase a la cual pertenecen. Básicamente, lo que hace este algoritmo es buscar la pregunta necesaria para poder separar la mayor cantidad de datos en dos grupos. Luego, vuelve a buscar la siguiente pregunta que romperá mejor al subgrupo y así, sucesivamente, hasta reducir los grupos lo suficiente hasta que quedemos satisfechos.
Para este ejemplo tenemos tantas preguntas posibles como features. Por ejemplo, podemos preguntar:
Para responder esta clase de preguntas los algoritmos de machine learning se sirven de una función de transferencia o una función de costo. Un árbol de decisión utiliza una función de tranferencia llamada Entropía para cuantificar el nivel de incertidumbre que tenemos en la clasificación de los datos. Es decir, la pregunta que sea capaz de reducir mas la incertidumbre (entropía) será la primera pregunta a hacer.
La entropía viene definida de la siguiente manera:
$H(x) = - \sum_{i=1}^n p_i \log_2 p_i$
donde $H$ es la entropía, $p_i$ es la probabilidad que sea perro o gato. Veamos cual es la entropía de nuestro problema:
Como tenemos 6 perros y 8 gatos, la probabilidad de escoger un perro al azar es $\frac{6}{14}$, y la de que sea gato es $\frac{8}{14}$. Entonces la entropía inicial de nuestro problema es:
$H(x) = -(\frac{6}{14}\log_2\frac{6}{14} + \frac{8}{14}\log_2\frac{8}{14}) = 0.9852... $
In [ ]:
from numpy import log2
def entropia_perro_gato(count_perro, count_gato):
prob_perro = count_perro / float(count_perro + count_gato)
prob_gato = count_gato / float(count_perro + count_gato)
return 0.0 if not count_perro or not count_gato else -(prob_perro*log2(prob_perro) + prob_gato*log2(prob_gato))
In [ ]:
perro = dfl['especie']=='perro'
gato = dfl['especie']=='gato'
no_busca = dfl['juega al busca']==False
si_busca = dfl['juega al busca']==True
print('A %d perros y %d gatos sí les gusta jugar al busca. H=%0.4f' % (
dfl[perro]['juega al busca'].sum(),#podemos contar sumando el numero de True
len(dfl[gato & si_busca]),#o filtrando y contando cueantos valores quedan
entropia_perro_gato(4,1),
))
print('A %d perros y %d gatos no les gusta jugar al busca. H=%0.4f' % (
len(df[perro&no_busca]),
len(df[gato&no_busca]),
entropia_perro_gato(len(dfl[perro & no_busca]),
len(dfl[gato & no_busca])),
))
In [ ]:
print(entropia_perro_gato(0,6))
print(entropia_perro_gato(6,2))
In [ ]:
from sklearn.tree import DecisionTreeClassifier
classifier = DecisionTreeClassifier(criterion='entropy')
classifier.fit(dft, labels)
In [ ]:
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['xtick.labelsize'] = 12
plt.rcParams['ytick.labelsize'] = 12
In [ ]:
feat = pd.DataFrame(index=dft.keys(), data=classifier.feature_importances_, columns=['score'])
feat = feat.sort_values(by='score', ascending=False)
feat.plot(kind='bar',rot=85)
In [ ]:
from sklearn.tree import export_graphviz
dotfile = open('perro_gato_tree.dot', 'w')
export_graphviz(
classifier,
out_file = dotfile,
filled=True,
feature_names = dft.columns,
class_names=list(labels),
rotate=True,
max_depth=None,
rounded=True,
)
dotfile.close()
La celda anterior exportó el árbol de decisión creado con sklearn y entrenado con nuestros datos a un archivo .dot
Este archivo lo procesaremos con el comando dot de la terminal. Desde jupyter, podemos ejecutar comandos de terminal sin salir del notebook:
In [ ]:
!dot -Tpng perro_gato_tree.dot -o perro_gato_tree.png
finalmente para cargar la imagen usamos:
In [ ]:
from IPython.display import Image
Image('perro_gato_tree.png', width=1000)
In [ ]:
import numpy as np
In [ ]:
np.array(classifier.predict(dft))
In [ ]:
np.array(labels)
In [ ]:
print('Error rate %0.4f'%((np.array(classifier.predict(dft))==np.array(labels)).sum() / float(len(labels))))
In [ ]:
test = pd.read_csv('data/perros_o_gatos_TEST.csv', index_col='observacion')
test
In [ ]:
label_test = test['especie']
del test['especie']
In [ ]:
ab = vectorizer.transform(test.to_dict(orient='records'))
dftest = pd.DataFrame(ab, columns=vectorizer.get_feature_names())
dftest.head()
In [ ]:
list(classifier.predict(dftest))
In [ ]:
list(label_test)
In [ ]:
print('Error rate %0.4f'%((np.array(classifier.predict(dftest))==np.array(label_test)).sum() / float(len(label_test))))