Autor: Luis Fernando Apáez Álvarez
-Curso PyM-
Clase 1: Algoritmos de agrupación
Fecha: 24 de mayo del 2023
El aprendizaje no supervisado es una rama del aprendizaje automático (machine learning) que se enfoca en extraer patrones y estructuras ocultas en conjuntos de datos sin la guía de etiquetas o respuestas predefinidas. A diferencia del aprendizaje supervisado, donde se proporcionan ejemplos de entrada y salida deseada para que el modelo aprenda, en el aprendizaje no supervisado, el modelo debe descubrir por sí mismo la estructura subyacente en los datos sin ninguna información externa.
El objetivo principal del aprendizaje no supervisado es encontrar patrones, regularidades o agrupaciones inherentemente presentes en los datos, lo que puede ayudar a comprender mejor los datos y generar conocimiento nuevo. Hay diferentes enfoques dentro del aprendizaje no supervisado, pero los dos más comunes son el clustering (agrupamiento) y la reducción de dimensionalidad.
El clustering consiste en agrupar los datos en conjuntos o clústeres basados en similitudes o características comunes. El algoritmo de clustering busca encontrar la estructura oculta de los datos, donde los puntos dentro de un mismo clúster son más similares entre sí que con los puntos de otros clústeres. Esto puede ser útil para descubrir segmentos de mercado, patrones de comportamiento o clasificar datos no etiquetados.
La reducción de dimensionalidad es otro enfoque común en el aprendizaje no supervisado. En muchos casos, los conjuntos de datos contienen muchas características o dimensiones, lo que puede dificultar el análisis y la visualización. La reducción de dimensionalidad busca comprimir la información de los datos en un espacio de menor dimensión mientras se conserva la mayor cantidad posible de información relevante. Esto puede ayudar a simplificar y visualizar los datos, así como a eliminar el ruido y las redundancias.
Otros enfoques dentro del aprendizaje no supervisado incluyen la detección de anomalías, la generación de datos y la asociación de reglas, entre otros. En general, el aprendizaje no supervisado es una herramienta poderosa para explorar datos sin etiquetar y descubrir patrones y estructuras útiles sin la necesidad de una retroalimentación explícita.
Comenzaremos el estudio de los algoritmos de agrupación con el algoritmo KNN (k-nearest neighbor, k-vecinos más cercanos). Este algoritmo de aprendizaje no supervisado nos permite realizar la clasificación de elementos de acuerdo a distancias, partiendo del principio de que se pueden encontrar puntos similares cerca uno del otro.
Supongamos que tenemos los dos gráficos siguientes:
En el gráfico de la izquierda tenemos los datos sin clasificar, pero claramente podemos notar tres agrupaciones posibles las cuales coloreamos en el gráfico de la derecha. Luego, considerando los nuevos puntos grises en el gráfico de la derecha, supongamos que los queremos clasificar. Debido a su cercanía con las correspondiente agrupaciones podemos intuir qué color le será asociado a cada uno.
Para clasificar, el algoritmo KNN calcula las distancias de todos los puntos respecto al punto gris y, una vez que nosotros fijamos el número de vecinos, supongamos que lo fijamos en cuatro, entonces el algoritmo se queda con los cuatro puntos más cercanos al punto gris. Luego, esos cuatro puntos más cercanos se somenten a votación y cada uno vota por su color, de modo que el color que le asociamos al punto gris será aquél que tenga más votaciones
En este caso vemos que hay un empate pues hay dos votos para el azul por dos del verde, de tal manera se recomienda que el número de vecinos sea impar.
Veamos un ejemplo, práctico:
# importaciones necesarias
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier
# Cargamos el conjunto de datos a utilizar
iris = sns.load_dataset('iris')
iris.head()
sepal_length | sepal_width | petal_length | petal_width | 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 |
# Graficamos
plt.style.use('ggplot')
sns.scatterplot(data=iris, x='petal_length', y='petal_width', hue='species')
plt.title("Largo de pétalo vs ancho de pétalo por especie")
plt.show()
Notamos claramente tres clústers (agrupaciones). Ahora, implementaremos un modelo de KNN que indentifique, justamente, dichos clústers
# Instanciamos el modelo configurando un numero de vecinos de 5
knn = KNeighborsClassifier(n_neighbors=5)
# Definimos las variables
X = iris[['petal_length', 'petal_width']].values
# Etiquetas asociadas
y = iris['species'].ravel()
# Ajustamos
knn.fit(X,y)
KNeighborsClassifier()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
KNeighborsClassifier()
Ahora sí, supongamos que tenemos los siguientes puntos nuevos
# Graficamos
plt.style.use('ggplot')
# Puntos nuevos
plt.plot(2.2, 0.6, marker="*", color='gray', markersize=12)
plt.plot(4.9, 1.6, marker="*", color='gray', markersize=12)
plt.plot(6, 2, marker="*", color='gray', markersize=12)
sns.scatterplot(data=iris, x='petal_length', y='petal_width', hue='species')
plt.title("Largo de pétalo vs ancho de pétalo por especie")
plt.show()
Y utilicemos el clasificador para determinar a qué clúster pertenecen:
import numpy as np
# Definimos un array con los datos nuevos
X_new = np.array([[2.2, 0.6],
[4.9, 1.6],
[6, 2]])
# Realizamos las predicciones
knn.predict(X_new)
array(['setosa', 'versicolor', 'virginica'], dtype=object)
Esto es:
etiquetas = list(knn.predict(X_new))
dict_etiquetas = {}
for i in range(3):
dict_etiquetas[etiquetas[i]] = list(X_new[i])
dict_etiquetas
{'setosa': [2.2, 0.6], 'versicolor': [4.9, 1.6], 'virginica': [6.0, 2.0]}
# Puntos nuevos
for key, value in dict_etiquetas.items():
plt.plot(value[0], value[1], marker="*", color='gray', markersize=12)
plt.text(value[0], value[1], key)
sns.scatterplot(data=iris, x='petal_length', y='petal_width', hue='species')
plt.title("Largo de pétalo vs ancho de pétalo por especie")
plt.show()
Finalmente, veamos cómo evaluar nuestro modelo y ver qué tan bueno es:
# Importaciones necesarias
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay, classification_report
# Dividimos los datos
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# Ajustamos sobre el conjunto de entrenamiento
knn_e = KNeighborsClassifier(n_neighbors=5)
knn_e.fit(X_train, y_train)
# Calculamos y_pred
y_pred = knn_e.predict(X_test)
# Veamos la matriz de confusion
ConfusionMatrixDisplay(confusion_matrix(y_test, y_pred)).plot()
plt.show()
# Reporte de metricas
print(classification_report(y_test, y_pred))
precision recall f1-score support setosa 1.00 1.00 1.00 10 versicolor 1.00 0.88 0.93 8 virginica 0.92 1.00 0.96 12 accuracy 0.97 30 macro avg 0.97 0.96 0.96 30 weighted avg 0.97 0.97 0.97 30
# La siguiente metrica nos permite conocer el porcentaje de exactitud del modelo
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred)
0.9666666666666667
Vemos que en general obtenemos un muy buen modelo.
La pregunta final es: ¿cuál es el mejor valor posible para k
? Para respoder a dicha pregunta probaremos con distintos valores de la k
y utilizaremos como criterio de comparación el valor asociado a la exactitud. Para ello
# Posibles valores de k
k_values = [i for i in range(2, 13)]
k_acc = []
# Ajustamos un modelo para distintos valores de k
for k in k_values:
# Instanciamos con k vecinos
knn = KNeighborsClassifier(n_neighbors=k)
# Ajustamos
knn.fit(X_train, y_train)
# Calculamos y_pred
y_pred = knn.predict(X_test)
# Calculamos la exactitud
acc = accuracy_score(y_test, y_pred)
k_acc.append(acc)
# Una vez calculados los distintos valores del accuracy para los modelos
# graficamos
plt.plot(k_values, k_acc, marker='o', color='darkblue')
plt.title(f"Valores del accuracy para k en {k_values}")
plt.xlabel('k')
plt.ylabel("accuracy")
plt.show()
Vemos que el mejor valor de k
es de k=2
. Así, implementamos el modelo final:
# Ajustamos sobre el conjunto de entrenamiento
knn_final = KNeighborsClassifier(n_neighbors=2)
knn_final.fit(X_train, y_train)
# Calculamos y_pred
y_pred = knn_final.predict(X_test)
# Veamos la matriz de confusion
ConfusionMatrixDisplay(confusion_matrix(y_test, y_pred)).plot()
plt.show()
# Reporte de metricas
print(classification_report(y_test, y_pred))
precision recall f1-score support setosa 1.00 1.00 1.00 10 versicolor 1.00 1.00 1.00 8 virginica 1.00 1.00 1.00 12 accuracy 1.00 30 macro avg 1.00 1.00 1.00 30 weighted avg 1.00 1.00 1.00 30
Donde terminado con un modelo perfecto.