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 inlineimport matplotlib.pyplot as pltimport seaborn as sns; sns.set() # para el estilo de los plotimport numpy as npnp.set_printoptions(precision=4, suppress=True)import pandas as pdfrom sklearn.cluster import KMeansfrom 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
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.
Ejecutar un algoritmo de K-means al conjunto de datos mediante el módulo KMeans de sklearn.cluster.
Visualizar los resultados para k=2.
Re ejecutar el agrupamiento de K-means varias veces; se obtuvo los resultados de cada prueba (Test 1, 2, 3 y 4).
Comparar los resultados de cada prueba (ver gráfico).
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_dfmodel = KMeans(n_clusters =2) # los clusterskm_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 predichosdf['KMeans'] = km_res_2.labels_# pasamos los valores como 0 y 1 de KMeansdf['KMeans'] = df['KMeans'].map({0: 1, 1: 2})df['KMeans'] = km_res_2.labels_
Show code
# pasamos los valores como 0 y 1 de Classmapping = {"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
#plotfig, (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.metricscm = 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.