Agrupación de datos mediante K Means: aprendizaje no supervisado para la detección de billetes falsos con Machine Learning

Autor

Jess Garriga

Fecha de Publicación

12 de enero de 2023

En nuestro proyecto de Ciencia de Datos abordamos el siguiente problema: se han falsificado billetes y los bancos no pueden distinguirlos debido a que los falsificadores emplean tecnologías que recrean con fidelidad las características de los billetes reales. Por lo tanto, el objetivo del proyecto es distinguir los billetes falsificados de los auténticos utilizando ciencia de datos. Este es el proyecto final para el curso Foundations of Data Science: K-Means Clustering in Python.

NOTA: Con la pestaña de arriba a la derecha (“Código”) pueden optar por mostrar/ocultar el código empleado en general. Lo mismo con cada celda en particular (“Show code”).

Descripción de los datos

El conjunto de datos de autenticación se encuentra en OpenML, del cual solo tomamos los siguientes atributos: Class, V1 varianza; y V2 asimetría de la imagen transformada mediante Wavelet. Como indica la fuente, los datos se extrajeron de imágenes tomadas de ejemplares de billetes auténticos y falsos. Los autores utilizaron el software Wavelet para extraer características de estas imágenes. Nuestro conjunto de datos tiene 1372 filas y 5 columnas. Los tipos de datos son ‘float64’ y Class ‘object’. Según las medidas estadísticas, la media de V1 es 0.433 y su desviación estándar es 2.842; la media de V2 es 1.922 y su desviación estándar es 5.869.

Show code
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()  # para el estilo de los plot
import numpy as np
np.set_printoptions(precision=4, suppress=True)
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
Show code
df = pd.read_csv('bank.csv')
df.drop('Unnamed: 0', inplace=True, axis=1)
df.shape # forma del dataset
(1372, 5)
Show code
df.head()
V1 V2 V3 V4 Class
0 3.62160 8.6661 -2.8073 -0.44699 b'1'
1 4.54590 8.1674 -2.4586 -1.46210 b'1'
2 3.86600 -2.6383 1.9242 0.10645 b'1'
3 3.45660 9.5228 -4.0112 -3.59440 b'1'
4 0.32924 -4.4552 4.5718 -0.98880 b'1'
Show code
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1372 entries, 0 to 1371
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   V1      1372 non-null   float64
 1   V2      1372 non-null   float64
 2   V3      1372 non-null   float64
 3   V4      1372 non-null   float64
 4   Class   1372 non-null   object 
dtypes: float64(4), object(1)
memory usage: 53.7+ KB

Información de los atributos

  • V1. varianza de la imagen transformada Wavelet (continua)
  • V2. Asimetría de la imagen transformada Wavelet (continua)
  • V3. curtosis de la imagen transformada Wavelet (continua)
  • V4. entropía de la imagen (continua)
  • Class (objeto). Presumiblemente 1 para auténtico y 2 para falsificado
Show code
df.describe().transpose() # estadísticas descriptivas
count mean std min 25% 50% 75% max
V1 1372.0 0.433735 2.842763 -7.0421 -1.773000 0.49618 2.821475 6.8248
V2 1372.0 1.922353 5.869047 -13.7731 -1.708200 2.31965 6.814625 12.9516
V3 1372.0 1.397627 4.310030 -5.2861 -1.574975 0.61663 3.179250 17.9274
V4 1372.0 -1.191657 2.101013 -8.5482 -2.413450 -0.58665 0.394810 2.4495
Show code
v1 = df['V1']
v2 = df['V2']

plt.figure(figsize=(6,6))
plt.scatter(v1, v2)
plt.xlabel('V1: varianza')
plt.ylabel('V2: asimetría')
plt.show()

Métodos

Según las características del conjunto de datos, se evaluó que el mismo es idóneo para aplicar la agrupación K-Means. De acuerdo a las suposiciones expuestas a lo largo del curso de agrupamiento por K-Means, el tamaño de filas es adecuado (posee muchas observaciones = 1372) y el de columnas también (posee pocas características = 2).

A continuación, se interpretan los resultados de un modelo de clasificación binario, por medio de agrupamiento de k-means, que predice si un billete es auténtico o no con base en el conjunto de datos de autenticación de billetes. Los datos se normalizaron para mejorar su comparabilidad.

Show code
# normalización
data_df = np.column_stack((df['V1'], df['V2']))
min_df = np.min(data_df, 0)
max_df = np.max(data_df, 0)
normed_df = (data_df - min_df) / (max_df - min_df)
Show code
w_class = pd.DataFrame(normed_df, columns = ['V1', 'V2'])
w_class = w_class.join(df['Class'])

Los datos se analizaron de la siguiente manera:

  1. Ejecutar un algoritmo de K-means al conjunto de datos mediante el módulo KMeans de sklearn.cluster.
  2. Visualizar los resultados para k=2.
  3. Re ejecutar el agrupamiento de K-means varias veces; se obtuvo los resultados de cada prueba (Test 1, 2, 3 y 4).
  4. Comparar los resultados de cada prueba (ver gráfico).
  5. Finalmente, se concluye que el algoritmo de agrupamiento de k-means es estable.

Resumen de los resultados

Según los resultados, vemos que en cada prueba el algoritmo clasifica dos grupos representados por colores amarillo para la clase 1 (billetes genuinos), y morado para la clase 2 (billetes falsificados), y cada prueba arrojó resultados estables. Los valores de los centroides en cada prueba es:

TEST 1 (CLUSTER INICIAL) TEST 2
[0.6736 0.698] [0.3696 0.4478] [0.3696 0.4478] [0.6736 0.698]
TEST 3 TEST 4
[0.3699 0.4479] [0.6738 0.6982] [0.3699 0.4479] [0.6738 0.6982]
Show code
# comparación de Class de los datos originales con los resultados pronosticados de nuestro modelo
# modelo para normed_df

model = KMeans(n_clusters = 2) # los clusters
km_res_2 = model.fit(normed_df)
clusters_2 = km_res_2.cluster_centers_

dv1 = clusters_2[:,0] 
dv2 = clusters_2[:,1]

y_model = model.predict(normed_df)
y_kmeans_2 = km_res_2.predict(normed_df)
Show code
# añadimos una columna al dataset con los valores predichos
df['KMeans'] = km_res_2.labels_

#  pasamos los valores como 0 y 1 de KMeans
df['KMeans'] = df['KMeans'].map({0: 1, 1: 2})

df['KMeans'] = km_res_2.labels_
Show code
#  pasamos los valores como 0 y 1 de Class
mapping = {"b'1'":0 , "b'2'":1}
df = df.replace({'Class': mapping})

La predicción nos da 607 billetes verdaderos y 765 falsos:

Show code
df.groupby('KMeans').describe()
V1 V2 ... V4 Class
count mean std min 25% 50% 75% max count mean ... 75% max count mean std min 25% 50% 75% max
KMeans
0 765.0 2.299326 1.954225 -2.7419 0.88992 2.5227 3.884000 6.8248 765.0 4.880242 ... 0.394810 2.4495 765.0 0.11634 0.320842 0.0 0.0 0.0 0.0 1.0
1 607.0 -1.917463 1.880789 -7.0421 -3.08325 -1.8219 -0.701125 2.7365 607.0 -1.805465 ... 0.396105 2.1353 607.0 0.85832 0.349010 0.0 1.0 1.0 1.0 1.0

2 rows × 40 columns

Show code
fig, axs = plt.subplots(2, 2, figsize=(12,10))
fig.suptitle('Modelo de clasificación binaria (k=2) para la autenticación de billetes, V1 y V2 normalizados')

Test_1 = axs[0, 0].scatter(normed_df[:,0], normed_df[:,1],
            c=y_kmeans_2, cmap='viridis',
            s = 25, alpha = .75)
axs[0, 0].scatter(dv1, dv2,
           s = 200, c = 'black', alpha = .5)
axs[0, 0].set_title('Test 1')

Test_2 = axs[0, 1].scatter(normed_df[:,0], normed_df[:,1],
            c=y_kmeans_2, cmap='viridis',
            s = 25, alpha = .75)
axs[0, 1].scatter(dv1, dv2,
           s = 200, c = 'black', alpha = .5)
axs[0, 1].set_title('Test 2')

Test_3 = axs[1, 0].scatter(normed_df[:,0], normed_df[:,1],
            c=y_kmeans_2, cmap='viridis',
            s = 25, alpha = .75)
axs[1, 0].scatter(dv1, dv2,
           s = 200, c = 'black', alpha = .5)
axs[1, 0].set_title('Test 3')

Test_4 = axs[1, 1].scatter(normed_df[:,0], normed_df[:,1],
            c=y_kmeans_2, cmap='viridis',
            s = 25, alpha = .75)
axs[1, 1].scatter(dv1, dv2,
           s = 200, c = 'black', alpha = .5)
axs[1, 1].set_title('Test 4')

for ax in axs.flat:
    ax.set(xlabel='V1: Varianza',
           ylabel='V2: Asimetría')

for ax in axs.flat:
    ax.label_outer()

plt.show()

Ahora, nos queda comparar las predicciones del conjunto de datos simplificado con los del conjunto original, en particular revisando los valores objetivo (target) del atributo ‘Class’, que indica presumiblemente 1 para genuino (morado en la figura de la izquierda) y 2 para falsificado (amarillo en la figura de la izquierda).

Show code
#plot
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10,5))
fig.suptitle('Comparando resultados')

ax1.scatter(normed_df[:,0], normed_df[:,1],
            c=y_kmeans_2, cmap='viridis',
            s = 25, alpha = .75)
ax1.scatter(dv1, dv2,
           s = 200, c = 'black', alpha = .5)
ax1.set_title('Modelo de Clasificación Binario')

for i,w_class in w_class.groupby('Class'):
    ax2.scatter(w_class['V1'], w_class['V2'], label=i)

ax2.set_title('Clases 1 y 2')

plt.legend(loc=4)

plt.show()

Dado que se solicitó un modelo de clasificación binaria que supone dos clusters, vemos que en el cuadrante compuesto por x (0.45 a 0.65) e y (0.5 a 0.7) se produce la mayor pérdida, es decir, donde los resultados de nuestro modelo permiten pasar billetes falsos (ver la figura de abajo).

Por lo tanto, podemos decir que el modelo cumple su función en diferenciar billetes genuinos de falsificados con las pérdidas asumidas en el cuadrante mencionado. Según la matriz de confusión:

Show code
# usamos confusion_matrix y ConfusionMatrixDisplay de sklearn.metrics

cm = confusion_matrix(df['Class'], df['KMeans'])

disp = ConfusionMatrixDisplay(confusion_matrix=cm)

disp.plot()

plt.title('Matriz de Confusión')
plt.xlabel('Etiqueta Predicha')
plt.ylabel('Etiqueta Verdadera')

plt.show()

  • Verdaderos negativos = 87 (billetes falsos etiquetados como falsos)
  • Falsos negativos = 521 (billetes verdaderos etiquetados como falsos)
  • Verdaderos positivos = 89 (billetes verdaderos etiquetados como verdaderos)
  • Falsos positivos = 675 (billetes falsos etiquetados como verdaderos)

Recomendaciones

Las limitaciones de nuestro enfoque se vinculan a qué tan ampliamente se pueden aplicar los resultados obtenidos como a las condiciones en que los mismos son válidos. Este modelo se aplica a un caso particular –el de la predicción de billetes falsos por medio de la clasificación binaria. Por lo tanto, la reaplicación de este algoritmo puede dar resultados distintos en otro conjunto de datos, a pesar de que se tenga el mismo objetivo, así como también su uso frente a otro problema tendría que ser evaluado. Recomendamos correr otros experimentos en el cuadrante donde identificamos los límites de nuestro algoritmo para refinar la identificación de billetes falsos.