Cuando llevamos a la práctica el aprendizaje de máquina, generalmente seguimos una metodología bien establecidad que nos dice qué pasos seguir para llevar a cabo la aplicación de un algoritmo de aprendizaje a un problema específico.
Si bien cada algorimto y cada problema presenta particularidades que deben ser exploradas, existe una metodología general que buscaremos abordar aquí. Esta metodología consiste, básicamente, en los siguientes pasos:
Los algoritmos de aprendizaje de máquina trabajan con datos representados por vectores en $\mathbb{R}^d$. Estos datos se obtienen a partir de un generador que determina la distribución original de éstos.
Aquí tomaremos un ejemplo muy sencillo de clasificación: buscaremos una máquina de aprendizaje que pueda decidir si un objeto (representado por un vector de rasgos) es o no un gato.
Utilizaremos las siguientes paqueterías:
import pandas as pd
import numpy as np
Cargaremos un archivo de datos. Este archivo es originalmente un csv en que cada columna representa un rasgo de nuestros datos.
data = pd.read_csv('cat_data.csv')
data
| ¿es animal? | ¿es mamífero? | ¿es felino? | ¿es doméstico? | ¿tiene dos orejas? | ¿es negro? | ¿tiene cuatro patas? | ¿es gato? | |
|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
| 2 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
| 3 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 0 |
| 4 | 1 | 1 | 1 | 0 | 1 | 0 | 1 | 0 |
| 5 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 |
| 6 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
| 7 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 |
| 8 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 10 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 |
| 11 | 1 | 1 | 1 | 0 | 1 | 0 | 1 | 0 |
| 12 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 |
| 13 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 |
Contamos con 14 datos (renglones). Cada uno de estos datos cuenta con 8 rasgos que lo describen (columnas). Sin embargo, el último rasgo (última columna) es la clasificación a la que pertenece cada objetos; es decir, la variable "¿es gato?" es precisamente el problema que queremos resolver.
Denotaremos cada ejemplo como un vector:
$$x = (X_1,...,X_n)$$Donde $X_i$, $i=1,...,n$ es una variable (columna) que representa un rasgo de ese dato especifico.
Como hemos observados, la última variable responde a la clase del objeto (si es gato o no lo es), por lo que tomaremos esta columna como el objetivo de la clasificación. Para esto, separaremos esta matriz en los vectores que representan los objetos ($X$) y sus clases ($Y$).
#Convertir los datos a numpy
npData = data.to_numpy()
#Ejemplos
X = npData[:,:-1]
#Clases de los ejemplos
Y = npData[:,-1]
#Tamaño de los datos
#Unidades de entrada
m,n = X.shape
Los datos $X$ es una matriz del número de datos por la dimensión de estos. Cada renglón representa un dato de entrada, estos son vectores $x= (X_1,...,X_7) \in \mathbb{R}^7$.
La supervisación $Y$ es un vector que asigna a cada dato una clase: 1 si es gato, 0 si no es gato.
Finalmente, podemos decir que tenemos un conjunto supervisado dado por:
$$\mathcal{S} = \{(x,y) : x \in \mathbb{R}^7, y \in \{0,1\}\}$$Cada par $(x,y)$ representa un vector/dato de entrada y su clase $y$ (si es o no gato). Por lo que se puede ver que el problema es una clasificación binaria.
print(X)
print(Y)
[[1 1 1 1 1 1 1] [0 0 0 1 0 1 0] [1 0 1 1 0 1 1] [1 1 0 1 1 0 1] [1 1 1 0 1 0 1] [1 1 1 1 0 0 0] [1 0 0 1 1 1 0] [1 1 1 1 0 0 1] [1 0 0 1 0 0 0] [0 0 0 0 0 0 0] [1 1 1 0 1 1 1] [1 1 1 0 1 0 1] [1 1 1 1 1 0 1] [1 1 1 1 1 0 0]] [1 0 0 0 0 1 0 1 0 0 0 0 1 1]
Finalmente, podemos identificar aquí los elementos de un problema de aprendizaje para este caso particular:
Ahora que tenemos los datos (o experiencia) necesitamos separar estos datos en entrenamiento y evaluación (no usaremos validación aquí).
Para hacer esto tomaremos un 70\% para entrenamiento y un 30\% de la evaluación: la selección de ambos conjuntos se hará aleatoriamente. Asimismo, nos deberemos asegurar que ningún dato de entrenamiento se encuentre en la evaluación, es decir, que no se repitan datos entre ambos conjuntos.
Para hacer esta selección utilizaremos la paquetería SKLearn que nos permite exportar la función train_test_split que separa los datos aleatoria y disjuntamente asignándole un tamaño a la evaluación.
#Importamos la paquetería necesaria
from sklearn.model_selection import train_test_split
#Separamos los datos con la función train_test_split
#El conjutno de evaluación será del 30% (0.3)
X_train, X_test, Y_train, Y_test = train_test_split(X,Y, test_size=0.3)
print('Elementos en el entrenamiento: {}\nElementos en la evaluación: {}'.format(len(X_train), len(X_test)))
Elementos en el entrenamiento: 9 Elementos en la evaluación: 5
Ya que contamos sólo con 14 datos, 9 de estos (70\%) forman parte del entrenamiento y los 5 (30\%) restantes de la evaluación.
Una vez que hemos preparado los datos y los hemos separado en entrenamiento y evaluación, necesitamos elegir un algoritmo.
Dado que tenemos un problema supervisado, debemos seleccionar un algoritmo de aprendizaje supervisado. Seleccionaremos el método de Bayes ingenuo que es un algoritmo basado en inferencia bayesiana. No profundizaremos en este método; usaremos, de nuevo, la paquetería de SKLearn.
from sklearn.naive_bayes import CategoricalNB as Bayes
El algoritmo de Bayes ingenuo tiene pocos hiperparámetros, en este caso se cuenta con una hiperparámetro (alpha) que se encarga de ajustar las probabilidades (smoothing), pero en el que no porfundizaremos.
LM = Bayes(alpha=1)
Una vez que hemos determinado nuestra máquina de aprendizaje, procederemos a entrenarlo. El entrenamiento consiste en aprender los parámetros que se ajusten mejor a los datos de entrenamiento. Cada método de aprendizaje tiene parámetros específicos que debe aprender. No profindizaremos en esto.
Es importante señalar que el entrenamiento se hace a partir de los datos de entrenamiento ($X$ y $Y$) y únicamente de estos. En este paso, la máquina de aprendizaje no debe tener ningún contacto con el conjunto de evaluación.
LM.fit(X_train, Y_train)
CategoricalNB(alpha=1)
El resultado del entrenamiento es un modelo de aprendizaje: este modelo consiste en la máquina de aprendizaje con los parámetros que mejor se ajustan a los datos. Es decir, bajo estos parámetros deseamos que la máquina de aprendizaje sea capaz de generalizar el conocimiento que adquirió de los datos de entrenamiento.
Para saber qué tan bien la máquina de aprendizaje "aprendio", evaluaremos su desempeño: es decir, la capacidad de generalizar que tiene la máquina.
Para esto, aplicaremos el modelo de aprendizaje a datos que no hayan sido vistos antes. Estos datos son precisamente los datos de evaluación. Por esta razón es importante que durante el entrenamiento la máquina de aprendizaje no haya tenido contacto con ellos.
Podemos entonces aplicar el modelo de aprendizaje a estos datos.
#Aplicamos el modelo a los datos de evaluación
Y_LM = LM.predict(X_test)
print(Y_LM)
[1 1 1 1 0]
En este caso, la máquina propone una clasificación posible que, espereamos, sea lo más parecido a la clasificación lineal. Para evaluar esto, utilizaremos un concepto central de los métodos de evaluación, la matriz de confusión. Esta matriz nos muestra el número de elementos positivos (que fueron clasificados como "gato" o 1) y los elementos negativos ("que fueron clasificados como "no gato" o 0) tanto de la clasificación de la máquina, como la clasificación del supervisor.
| Negativos (S) | Positivos (S) | |
|---|---|---|
| Negativos (LM) | Verdaderos Negativos (TN) | Falsos Negativos (FN) |
| Positivos (LM) | Falsos Positivos (FP) | Verdaderos Positivos (TP) |
La matriz de confusión nos da los siguientes elementos:
#Paqutería necesaria
from sklearn.metrics import confusion_matrix
#Visualización de la matriz de confusión
pd.DataFrame(data=confusion_matrix(Y_test,Y_LM),
index=['Negativos (S)','Positivos (S)'], columns=['Negativos (LM)','Positivos (LM)'])
| Negativos (LM) | Positivos (LM) | |
|---|---|---|
| Negativos (S) | 1 | 2 |
| Positivos (S) | 0 | 2 |
Aquí S es el supervisor, la categoría real, y LM la clasificación que hace la máquina. El cruce de estos dos elementos nos indica cuántos elementos positivos reales fueron clasificados comompositivos por la máquina. De igual forma para los negativos. Cada entrada de la matriz nos dice cuántos elementos positivos o negativos del supervisor fueron clasificados de esta forma por la máquina.
Para obtener una evaluación numérica podemos utilizar los resultados de la matriz de confusión. Existen diferentes métricas de evaluación. Una forma muy común de evaluar es la Accuracy o Exactitud, que está determinada como:
$$Acc = \frac{TP + TN}{TN+FN+FP+TP}$$Otras medidas de evaluación son la precisión y el recall:
$$Prec = \frac{TP}{TP+FP}$$$$Rec = \frac{TP}{TP+FN}$$Finalmente la métrica $F_1$ nos da un promedio geométrico de ambas métricas:
$$F_1 = 2\cdot \frac{Prec \cdot Rec}{Prec + Rec}$$Estas métricas pueden obtenerse de la paquetería de skleanr en el módulo de metrics.
#Paqutería necesaria
from sklearn.metrics import classification_report
#Reporte de clasificación
print(classification_report(Y_test,Y_LM))
precision recall f1-score support
0 1.00 0.33 0.50 3
1 0.50 1.00 0.67 2
accuracy 0.60 5
macro avg 0.75 0.67 0.58 5
weighted avg 0.80 0.60 0.57 5
Además de estás métricas, la función de classification report nos da un promedio y un promedio ponderado. Ya que las métricas de precisión, recall y $F_1$ trabajan por cada una de las clases, estos promedios nos dan una noción general de estos. Son los promedios de las métricas a través de las clases.
El macro average es simplemente el promedio de cada métrica a través de las diferentes clases:
$$macro\_avg = \sum_{i=1}^n metric(i)$$donde $metric(i)$ es cualquiera de las métricas (precision, recall o $f_1$ aplicada en la clase $i$.
Por su parte el weighted average es el promedio ponderado por la probabilidad (o el valor esperado) de esa métrica en una clase:
$$weighted\_avg = \sum_{i=1}^n p(i) \cdot metric(i)$$donde $p(i)$ es el número de instancias (support) entre el total.