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.


Algoritmo KNN¶

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:

knn001.PNG

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

knn003-2.PNG

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:

In [1]:
# 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()
Out[1]:
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
In [17]:
# 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

In [3]:
# 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)
Out[3]:
KNeighborsClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
KNeighborsClassifier()

Ahora sí, supongamos que tenemos los siguientes puntos nuevos

In [4]:
# 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:

In [5]:
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)
Out[5]:
array(['setosa', 'versicolor', 'virginica'], dtype=object)

Esto es:

In [6]:
etiquetas = list(knn.predict(X_new))

dict_etiquetas = {}

for i in range(3):
    dict_etiquetas[etiquetas[i]] = list(X_new[i])
    
dict_etiquetas
Out[6]:
{'setosa': [2.2, 0.6], 'versicolor': [4.9, 1.6], 'virginica': [6.0, 2.0]}
In [7]:
# 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:

In [8]:
# 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()
In [9]:
# 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

In [10]:
# La siguiente metrica nos permite conocer el porcentaje de exactitud del modelo
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred)
Out[10]:
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

In [11]:
# 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:

In [12]:
# 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()
In [13]:
# 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.