Clustering de imágenes con Python

René Silva Valdés
Python Data Science
5 min readSep 16, 2019

Clustering de imágenes (MNIST) usando las librerías de Python para data science.

Cuando comencé a escribir en Medium, lo hice con la intención de no tener que re-hacer un mismo trabajo (o re-escribir un mismo código) para resolver problemas similares. Hace un par de días, tuve que incurrir en este “doble gasto” con un problema de data science, así que opté por documentarlo para el futuro. Se trata de un problema de clustering que formó parte del CTF de Trend Micro 2019, cuyo enunciado (traducido del inglés) se muestra abajo (asimismo, el código para obtener la flag se encuentra en mi Github).

Categoría: Comodín
Puntos: 100
Se le ha dado un conjunto de datos que contiene imágenes de los dígitos 3 y 6.
El conjunto de datos contiene un total de 34022 imágenes (16768 archivos son imágenes del dígito 3 y el resto son imágenes del dígito 6).
El conjunto de datos de alguna manera se mezcló al azar y también contiene algunas imágenes duplicadas de cada dígito.
Su tarea es encontrar el número de imágenes únicas de cada dígito en el conjunto de datos.
Los pasos del desafío son:
1. Dado que las imágenes se mezclan al azar, necesitamos una forma de separar las imágenes de los dígitos 3 y 6 entre sí. Puede usar un algoritmo de clustering para este propósito.
2. Averigua qué dígito pertenece a qué grupo.
3. Cuente el número de imágenes únicas en cada grupo y forme la flag.

(Tenga en cuenta que si x es el número de imágenes únicas del dígito 3 ey es el número de imágenes únicas del dígito 6, entonces la bandera será TMCTF {x_y})

Descargar el archivo

Extraiga el archivo descargado utilizando la siguiente contraseña.
Opcional: Verifique la integridad del archivo 7z comparando la suma de verificación del archivo SHA256.
Comando para verificar la suma de verificación SHA256:
Linux: nombre de archivo sha256sum.7z
Windows: certUtil -hashfile filename.7z SHA256
> Contraseña: bfF0zdtn4CpcQq1VFqUL
> SHA256 checksum: 772e15c6bbe830bb9a4c6b1beb877da021fbea889e9a201fc9075ee5767f8849

El problema no tiene mucha complejidad, sin embargo requiere ciertas nociones sobre clustering. Primero que todo, debemos cargar los datos y formatearlos como vectores de características (cada pixel es una característica), para esto utilizamos las librerías os, numpy y opencv.

folder = "./data"
onlyfiles = [f for f in os.listdir(folder) if os.path.isfile(os.path.join(folder, f))]
print("Working with {0} images".format(len(onlyfiles)))
print("Image examples: ")
for i in range(40, 42):
print(onlyfiles[i])
sample = cv2.imread("data/"+onlyfiles[i],0)
plt.imshow(sample)

# settings for LBP
radius = 3
n_points = 8 * radius
data_r = []
data_s = []
lbp_r = []
lbp_s = []
for file in onlyfiles:
im = cv2.imread("data/"+file,0)
data_r.append(im)
lbp_r.append(local_binary_pattern(im, n_points, radius))
data_r = np.array(data_r)
lbp_r = np.array(lbp_r)
n_samples_r = len(data_r)
data_r = data_r.reshape((n_samples_r, -1))
lbp_r = lbp_r.reshape((n_samples_r, -1))
X_r = data_r
Y_r = lbp_r
Imagen de muestra.

Luego de cargados los datos, procedemos a realizar una inspección preliminar. Para esto realizamos una inspección visual mediante Análisis de Componentes Principales (o PCA, por sus siglas en inglés) usando la librería sklearn.

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_r_pca = pca.fit_transform(X_r)
Xax_r=X_r_pca[:,0]
Yax_r=X_r_pca[:,1]
Y_r_pca = pca.fit_transform(Y_r)
Xax_lr=Y_r_pca[:,0]
Yax_lr=Y_r_pca[:,1]
fig,ax=plt.subplots(figsize=(7,5))
fig.patch.set_facecolor('white')
ax.scatter(Xax_r,Yax_r,s=40)plt.xlabel("First Principal Component",fontsize=14)
plt.ylabel("Second Principal Component",fontsize=14)
plt.legend()
plt.show()

Cabe destacar que para el análisis de imágenes suele ser útil la extracción de características usando herramientas como LBP (Local Binary Patterns), sin embargo, para este problema en particular no es necesario (pues no se trata de imágenes tan complejas). A partir de PCA se puede corroborar la existencia de las dos clases especificadas, aunque la distribución de elementos dentro de cada cluster sea algo distinta. Procedemos a ejecutar nuestro algoritmo de clustering para ver que muestran los resultados. En este caso utilizaremos k-means por ser un buen parámetro de comparación para otros modelos, además de uno de los algoritmos de clustering más utilizados.

from sklearn import cluster
X = data_r
k_means = cluster.KMeans(n_clusters=2)
k_means.fit(X)
labels = k_means.labels_fig,ax=plt.subplots(figsize=(7,5))
fig.patch.set_facecolor('white')
for l in np.unique(labels):
ix=np.where(labels==l)
ax.scatter(Xax_r[ix],Yax_r[ix],s=40)
# for loop ends
plt.xlabel("First Principal Component",fontsize=14)
plt.ylabel("Second Principal Component",fontsize=14)
plt.legend()
plt.show()
Visualización usando PCA de los resultados de k-means.

Un detalle importante del problema que deseamos resolver es que se nos dice cuantos elementos pertenecen a cada clase, por lo que podemos usar estos valores para validar nuestro modelo. También podemos observar que las clases están “balanceadas” al comparar ambos números, por lo que k-means debería tener un buen desempeño. Luego de correr nuestro modelo, chequeamos cuantos elementos pertenecen a cada clase:

np.shape(np.where(labels==0)) #17.254
np.shape(np.where(labels==1)) #16.768

Como podemos observar, los tamaños de los clusters son exactamente los que nos dice el enunciado (16.768 imágenes del dígito 3 y 17.254 del dígito 6), lo que valida nuestro modelo. Luego debemos identificar una copia de cada imagen y clasificarlas. Para eliminar las imágenes repetidas utilizamos la librería numpy (la ejecución de esta parte del código toma algunos minutos):

# settings for LBP
radius = 3
n_points = 8 * radius
data_s = []
lbp_s = []
for file in onlyfiles:
im = cv2.imread("data/"+file,0)
if np.any([np.array_equal(data_s[n],im) == True for n in range(len(data_s))]) == False:
data_s.append(im)
lbp_s.append(local_binary_pattern(im, n_points, radius))
data_s = np.array(data_s)
lbp_s = np.array(lbp_s)
n_samples_s = len(data_s)
data_s = data_s.reshape((n_samples_s, -1))
lbp_s = lbp_s.reshape((n_samples_s, -1))
X_s = data_s
Y_s = lbp_s

Luego de separar las imágenes repetidas, clasificamos los datos con nuestro modelo:

r = k_means.predict(X_s)

De esta forma obtenemos que las imágenes únicas son 14962 y 347 para las clases 3 y 6 respectivamente, con lo cual podemos armar la flag :)

Un factor importante al momento de resolver este challenge es la metodología, la cual va dada por la experiencia, pero también por el conocimiento teórico que tengamos. A modo de ejemplo, si hubiéramos intentado hacer clustering usando k-means luego de quitar las imágenes repetidas, el algoritmo no hubiese funcionado correctamente, pues como pudimos notar a posteriori, las clases estaban desbalanceadas, lo que perjudicaría el desempeño de k-means.

A partir de este entretenido challenge hemos aprendido a realizar clustering de imágenes usando k-means y a realizar inspección visual de datos usando PCA, ambas cosas utilizando el lenguaje Python. También hemos repasado algunos conceptos de aprendizaje no supervisado y análisis de imágenes. Espero que a partir de este writeup puedan resolverse con mayor rapidez problemas similares a este.

Gracias por leer mi post, espero seguir documentando aplicaciones de data science en Python.

またね!

--

--