El objeto de esta práctica es reconocer digitos y caracteres escritos a mano.
Para ello dividimos los datos en datos de entrenamiento o training y datos de prueba o testing. Con los datos de training elaboraremos un modelo y diremos que la máquina aprende o que estamos usando un método de machine learning. Con el modelo obtenido podemos predecir (clasificar) las imágenes de testing.
Una vez comprobado que el modelo funciona con los datos de testing podremos usarlo para clasificar (asignar etiquetas) a imágenes similares.
Seguiremos los siguientes pasos:
Para clasificar las imágenes vamos a utilizar el método Extreme Learning Machine (ELM). Este busca el menor error para los datos de training en la regresión y, además, que la norma de los coeficientes o pesos obtenidos sea lo menor posible (principio de parsimonia, buscamos el resultado más sencillo), para que la capacidad de generalización de los resultados sea lo mayor posible.
$$ \underset{\mathbf{\beta}}{Minimizar:}\quad L_{ELM}=\frac{1}{2}\Vert\mathbf{\boldsymbol{\beta}}\Vert^{2}+\frac{C}{2}\sum_{i=1}^{N}\Vert\mathbf{\xi}_{i}\Vert^{2} =\frac{1}{2}\Vert\mathbf{\boldsymbol{\beta}}\Vert^{2}+\frac{C}{2}\Vert\mathbf{Y}-\mathbf{H}\boldsymbol{\beta}\Vert^{2} $$donde $\Vert\cdot\Vert$ es la norma de Frobenius, $\beta$ los coeficientes, $\mathbf{\xi}_{i}$ los errores de training para cada muestra siendo (N) el número de muestras de training, $\mathbf{H}$ es la matriz de datos de training, $\mathbf{H}\boldsymbol{\beta}$ es la predicción del modelo, $\mathbf{Y}$ la matriz de etiquetas de los datos de training y $C$ es el parámetro de regularización.
Las $\overset{\star}{\mathbf{\beta}}$ que nos dan la solución de este problema de minimización vienen dadas por
$$ \overset{\star}{\mathbf{\beta}}=\mathbf{H}^{T}\mathbf{W} $$donde $\mathbf{W}$ son los pesos de salida, que vienen dados por
$$ \mathbf{W} =\left(\frac{\mathbf{I}}{C}+\mathbf{\Omega}\right)^{-1}\mathbf{Y} $$y $\mathbf{\Omega}=\mathbf{H}\mathbf{H^{T}}$ es la denominada matriz de kernel. En este caso estamos usando el kernel lineal. De forma expandida, los elementos de $\mathbf{\Omega}$ son
$$ \mathbf{\Omega}_{ij}=\mathbf{x}_i \centerdot \mathbf{x}_j = \sum_{k=1}^{n} x_{ik} x_{jk}, $$donde $\mathbf{x}_i$ es la fila $i$ de $\mathbf{H}$ (datos de training). Más tarde veremos otros tipos de kernels.
El valor correspondiente a cada muestra de testing $\mathbf{x}$ vendría dado por
$$ {\mathbf{Y}_{te}}=\mathbf{H}_{te} \overset{\star}{\mathbf{\beta}} = \mathbf{H}_{te}\mathbf{H}^{T}\mathbf{W}={\mathbf{\Omega}_{te}}\mathbf{W} $$siendo
$$ {\mathbf{\Omega}_{te}}= \mathbf{H}_{te}\mathbf{H}^{T}. $$En el ejemplo que vamos a ver, cada muestra es una imagen de $28\times28$ pixels de un digito escrito a mano. En la matriz de training cada una de estas imágenes aparece como una fila, por lo que tenemos que reconstruirla como una matriz $28\times28$ para poder representarla. Hemos extraído del banco de imágenes MNIST, por sorteo, $900$ imágenes de training y $100$ de testing y las hemos guardado con sus correspondiente etiquetas en el fichero datos_clasificar.zip
import numpy as np
import matplotlib.pyplot as plt
Leemos los datos
datos_train = np.loadtxt('datos_train1.txt')
etiquetas_train = np.loadtxt('etiquetas_train1.txt')
datos_test = np.loadtxt('datos_test1.txt')
etiquetas_test = np.loadtxt('etiquetas_test1.txt')
Veamos las imágenes con las que vamos a trabajar. Recolocamos los píxeles de la imagen y les damos la forma de una matriz $28\times28$. Dibujamos las $100$ primeras imágenes de training.
plt.figure(figsize=(8,8))
for k in range(0, 100):
plt.subplot(10, 10, k+1)
imagen = datos_train[k, ]
imagen = imagen.reshape(28, 28)
plt.imshow(imagen, cmap='gray')
plt.axis('off')
plt.show()
Utilizaremos los parámetros
C = 1 # parámetro de regularización
numero_de_clases = 10 # tenemos diez clases o dígitos (desde 0 hasta 9)
n = datos_train.shape[0] # número de filas (imágenes) de training
m = datos_test.shape[0] # número de filas (imágenes) de testing
I = np.identity(n) # matriz identidad n x n
Normalizamos a $[0,1]$.
H = datos_train/255.
H_test = datos_test/255.
Creamos matrices de etiquetas que contendrán ceros y unos. Estas matrices (una para training y otra para testing) tiene tantas columnas como clases (en este caso $10$) y tantas filas como muestras (es decir $900$ o $100$ para training y testing, respectivamente). Para una muestra $k$ escribiremos, en la fila $k$, un $1$ en la primera fila si pertenece a la primera clase (etiqueta $0$), y dejaremos con ceros las demás filas de esa columna. Si pertenece a la clase $2$ (etiqueta $1$) escribiremos un $1$ en la segunda columna,... y así sucesivamente.
Para las muestras de training
Y = np.zeros((n, numero_de_clases))
for i in range(n):
Y[i, int(etiquetas_train[i])] = 1
Calculamos la predicción dada por el modelo
$$ {\mathbf{Y}_{test}} = {\mathbf{\Omega}_{test}}\mathbf{W}\quad \mathrm{siendo}\quad {\mathbf{\Omega}_{test}}=\mathbf{H}_{test}\mathbf{H}^{T}, $$para lo cual necesitamos calcular $\mathbf{W}$, que viene dada como la solución del sistema lineal
$$ \left(\frac{\mathbf{I}}{C}+\mathbf{\Omega}\right)\mathbf{W}=\mathbf{Y}\quad \mathrm{siendo}\quad {\mathbf{\Omega}}=\mathbf{H}\mathbf{H}^{T} $$Omega = np.dot(H, H.T)
W = np.linalg.solve(I/C + Omega, Y)
Y por lo tanto
Omega_test = np.dot(H_test, H.T)
Y_test = np.dot(Omega_test, W)
La etiqueta predicha para cada imagen (tanto de training como de testing) se obtiene asignándole a dicha imagen la clase correspondiente al número de la columna Y_test
que contiene el valor máximo.
clase_predicha = Y_test.argmax(axis=1)
Calculamos el tanto por ciento de valores acertados comparando las etiquetas predichas y las que teníamos como dato.
Porcentaje_de_aciertos = np.sum(clase_predicha == etiquetas_test)/float(m)*100.
print ('Porcentaje de aciertos = %.1f%%' % Porcentaje_de_aciertos)
Para ver con más detalle cómo se clasificaron las imágenes, calculemos la matriz de confusión
from sklearn.metrics import confusion_matrix
mc = confusion_matrix(etiquetas_test, clase_predicha)
print (u'Matriz de confusión')
print (mc)
plt.figure(figsize=(6,6))
ticks = range(10)
plt.xticks(ticks)
plt.yticks(ticks)
plt.imshow(mc,cmap=plt.cm.Blues,interpolation='nearest')
plt.colorbar(shrink=0.8)
w, h = mc.shape
for i in range(w):
for j in range(h):
plt.annotate(str(mc[i][j]), xy=(j, i),
horizontalalignment='center',
verticalalignment='center')
plt.xlabel('Etiqueta predicha')
plt.ylabel('Etiqueta')
plt.title(u'Matriz de Confusión')
plt.show()
Veamos los resultados, dibujando primero las etiquetas y luego las imágenes asignadas
print('\nEtiquetas predichas para las muestras de testing')
for i in range(0,m):
if i%10 == 9:
print(clase_predicha[i]),
else:
print(clase_predicha[i], end=" "),
print('\n')
print('Imágenes que corresponden a las etiquetas anteriores')
plt.figure(figsize=(8,8))
for k in range(0, 100):
plt.subplot(10, 10, k+1)
imagen = datos_test[k, ]
imagen = imagen.reshape(28, 28)
plt.imshow(imagen, cmap='gray')
plt.axis('off')
plt.show()
Cuando hemos calculado las matrices de kernel $\Omega$ y $\Omega_{test}$, hemos construído, con los datos de training y de testing $H$ y $H_{test}$, una matriz cuyos elementos son los productos escalares de las muestras tomadas de dos en dos.
Hasta ahora, hemos utilizado el producto escalar euclideo. También podemos decir, en el contexto de machine learning, que hemos usado un kernel lineal. $$ K_{Lin}(\mathbf{x}_i,\mathbf{x}_j)=\mathbf{x}_i \centerdot \mathbf{x}_j = \sum_{k=1}^{n} x_{ik} x_{jk} $$ Pero podemos utilizar cualquier otro producto escalar (o kernel) por ejemplo, el kernel gaussiano, también llamado RBF (radial basis function) $$ K_{RBF}(\mathbf{x}_i,\mathbf{x}_j)=\mathrm{exp}\left(-\dfrac{||\mathbf{x}_i-\mathbf{x}_j||^{2}}{\sigma}\right) $$ o el kernel polinomial $$ K_{Poly}(\mathbf{x}_i,\mathbf{x}_j)=(\mathbf{x}_i\centerdot\mathbf{x}_j+a)^{b} $$ Con estos kernel estamos introduciendo nuevos parámetros que es preciso ajustar. En el caso del kernel polinomial, tenemos los parámetros $a$ y $b$ y en el del kernel RBF, $\sigma$.
Veamos si los resultados mejoran con alguno de estos kernel. Por ejemplo, el polinomial.
C = 1 # parámetro de regularización
a = 1; b = 3 # parámetros del kernel polinomial
Ya construimos para el modelo anterior las matrices $\mathbf{H}$, $\mathbf{H}_{test}$ e $\mathbf{Y}$.
# Kernel Polinomial
KernelPoly = lambda X, Y : (np.dot(X,Y.T) + a)**b
# Omega
OmegaP = KernelPoly(H, H)
W = np.linalg.solve(I/C + OmegaP, Y)
OmegaP_test = KernelPoly(H_test, H)
YP_test = np.dot(OmegaP_test, W)
# clase predicha
clase_predicha = YP_test.argmax(axis=1)
# porcentaje de aciertos
Porcentaje_de_aciertos = np.sum(clase_predicha == etiquetas_test)/float(m)*100.
print ('Porcentaje de aciertos = %.1f%%' % Porcentaje_de_aciertos)
# matriz de confusión
mc = confusion_matrix(etiquetas_test, clase_predicha)
print(u'\nMatriz de Confusión\n')
print (mc)
plt.figure(figsize=(6,6))
ticks = range(10)
plt.xticks(ticks)
plt.yticks(ticks)
plt.imshow(mc,cmap=plt.cm.Blues,interpolation='nearest')
plt.colorbar(shrink=0.8)
w, h = mc.shape
for i in range(w):
for j in range(h):
plt.annotate(str(mc[i][j]), xy=(j, i),
horizontalalignment='center',
verticalalignment='center')
plt.xlabel('Etiqueta predicha')
plt.ylabel('Etiqueta')
plt.title(u'Matriz de Confusión')
plt.show()
# Ver resultados
print('\r')
print('Etiquetas predichas para las muestras de testing')
for i in range(0,m):
if i%10 == 9:
print(clase_predicha[i]),
else:
print(clase_predicha[i], end=" "),
print('\n')
print('Imágenes que corresponden a las etiquetas anteriores')
plt.figure(figsize=(8,8))
for k in range(0, 100):
plt.subplot(10, 10, k+1)
imagen = datos_test[k, ]
imagen = imagen.reshape(28, 28)
plt.imshow(imagen, cmap='gray')
plt.axis('off')
plt.show()
def KernelRBF(X,Y,g):
"""
Calcula la matriz de kernel
para el kernel gaussiano
"""
m = X.shape[0]
n = Y.shape[0]
K = np.zeros((m,n))
for i in range(m):
for j in range(n):
dif = np.linalg.norm(X[i,:]-Y[j,:])
K[i,j] = np.exp(-dif**2/g)
return K
buscar el par de parámetros $(C,\sigma)$ que dé un porcentaje de aciertos máximo. Probar con los valores
C_lista = [ 1., 10., 100., 1000.]
sigma_lista = [ 1., 10., 100., 1000.]
y quedarse con el par de parámetros que dé el valor óptimo. Dar las etiquetas predichas para este caso.
%run Ejercicio1
El fichero datos_letras.txt
contiene imágenes de $20\times16$ píxeles de letras manuscritas, cada una de ellas almacenada como una fila de la matriz datos
. Utilizar un $90\%$ de las muestras para training y un $10\%$ para testing y clasificarlas utilizando el kernel RBF. Dibujar las imágenes de testing y dar las etiquetas predichas por el modelo.
%run Ejercicio2