Página web del curso

Clasificación de imágenes con el método ELM

Contenidos

Clasificación

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:

  1. Realizamos una regresión utilizando los datos de training. Es decir, construimos un modelo ajustando los datos de training. En esta regresión, el criterio no será unicamente minimizar los errores cuadradáticos para los datos de training, sino que se buscará también que los resultados tengan la mayor capacidad de predicción posible para clasificar imágenes nuevas.
  2. Con el modelo obtenido por regresión predecimos los valores correspondientes a los datos de testing.
  3. Interpretamos estas prediciones (números reales) sustituyéndolos por las etiquetas correspondientes, que el modelo asigna a cada imagen.

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.

Clasificación con un kernel lineal

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

In [2]:
import numpy as np
import matplotlib.pyplot as plt

Leemos los datos

In [3]:
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.

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

In [5]:
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]$.

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

In [7]:
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} $$
In [8]:
Omega = np.dot(H, H.T)
W = np.linalg.solve(I/C + Omega, Y)

Y por lo tanto

In [9]:
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.

In [10]:
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.

In [11]:
Porcentaje_de_aciertos = np.sum(clase_predicha == etiquetas_test)/float(m)*100.
print ('Porcentaje de aciertos = %.1f%%' % Porcentaje_de_aciertos)
Porcentaje de aciertos = 76.0%

Para ver con más detalle cómo se clasificaron las imágenes, calculemos la matriz de confusión

In [12]:
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()
Matriz de confusión
[[ 9  0  0  0  0  0  0  0  0  1]
 [ 0 10  0  0  0  0  0  0  0  0]
 [ 0  1  5  1  0  0  2  1  0  0]
 [ 1  0  1  7  0  0  0  0  0  1]
 [ 0  0  0  0  9  0  0  0  1  0]
 [ 0  0  0  0  0  8  0  0  2  0]
 [ 1  0  1  0  0  0  7  0  0  1]
 [ 1  0  0  0  1  0  0  8  0  0]
 [ 0  0  0  2  0  2  0  0  6  0]
 [ 0  0  0  0  2  0  0  1  0  7]]

Veamos los resultados, dibujando primero las etiquetas y luego las imágenes asignadas

In [13]:
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()
Etiquetas predichas para las muestras de testing
2 7 1 0 9 9 5 7 1 9
9 5 9 4 3 4 5 7 0 3
1 8 4 3 1 3 5 2 7 7
0 3 5 5 5 8 3 0 4 4
9 7 9 2 4 2 2 9 8 7
6 0 5 3 1 3 7 9 4 6
4 4 6 8 6 1 0 0 8 9
0 6 7 1 0 4 6 0 1 2
6 3 6 1 3 5 7 4 2 8
5 1 1 8 6 8 0 4 8 0


Imágenes que corresponden a las etiquetas anteriores

Clasificación con kernels no lineales

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.

In [14]:
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}$.

In [15]:
# 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()
Porcentaje de aciertos = 93.0%

Matriz de Confusión

[[10  0  0  0  0  0  0  0  0  0]
 [ 0 10  0  0  0  0  0  0  0  0]
 [ 0  0  8  1  0  0  0  0  1  0]
 [ 0  0  0  9  0  1  0  0  0  0]
 [ 0  0  0  0  9  0  1  0  0  0]
 [ 0  0  0  0  0 10  0  0  0  0]
 [ 0  0  0  0  1  0  9  0  0  0]
 [ 0  0  0  0  0  0  0 10  0  0]
 [ 0  0  0  0  0  0  0  0  9  1]
 [ 0  0  0  0  0  0  0  1  0  9]]
Etiquetas predichas para las muestras de testing
2 7 1 0 9 9 5 7 1 9
3 8 0 7 5 7 5 7 0 3
1 5 4 3 1 3 8 2 7 7
0 3 5 5 5 5 3 0 4 4
9 9 9 6 4 3 2 4 8 7
6 0 5 8 1 3 8 9 4 2
4 4 6 9 6 1 3 0 8 9
0 6 7 2 0 6 6 0 1 2
6 8 6 1 3 5 7 9 2 8
5 1 1 4 2 8 6 4 8 7


Imágenes que corresponden a las etiquetas anteriores

Ejercicios


Ejercicio 1

Utilizando el kernel RBF

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

In [16]:
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.

In [17]:
%run Ejercicio1
C =  1.0  sigma =  1.0
Porcentaje de aciertos = 89.0%
C =  1.0  sigma =  10.0
Porcentaje de aciertos = 94.0%
C =  1.0  sigma =  100.0
Porcentaje de aciertos = 95.0%
C =  1.0  sigma =  1000.0
Porcentaje de aciertos = 85.0%
C =  10.0  sigma =  1.0
Porcentaje de aciertos = 89.0%
C =  10.0  sigma =  10.0
Porcentaje de aciertos = 94.0%
C =  10.0  sigma =  100.0
Porcentaje de aciertos = 96.0%
C =  10.0  sigma =  1000.0
Porcentaje de aciertos = 93.0%
C =  100.0  sigma =  1.0
Porcentaje de aciertos = 89.0%
C =  100.0  sigma =  10.0
Porcentaje de aciertos = 94.0%
C =  100.0  sigma =  100.0
Porcentaje de aciertos = 97.0%
C =  100.0  sigma =  1000.0
Porcentaje de aciertos = 92.0%
C =  1000.0  sigma =  1.0
Porcentaje de aciertos = 89.0%
C =  1000.0  sigma =  10.0
Porcentaje de aciertos = 94.0%
C =  1000.0  sigma =  100.0
Porcentaje de aciertos = 96.0%
C =  1000.0  sigma =  1000.0
Porcentaje de aciertos = 92.0%

Aciertos =  97.0 %, C =  100.0 , sigma =  100.0
Etiquetas predichas para las muestras de testing

2 7 1 0 9 9 5 7 1 9 
3 8 0 7 3 7 5 7 0 3 
1 5 4 3 1 3 8 2 7 7 
0 3 5 5 5 5 3 0 4 4 
9 9 9 6 4 3 2 6 8 7 
6 0 5 8 1 2 8 9 4 2 
4 4 6 8 6 1 3 0 8 9 
0 6 7 2 0 6 6 0 1 2 
6 8 6 1 3 5 7 9 2 8 
5 1 1 4 2 8 6 4 8 7


Ejercicio 2

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.

In [18]:
%run Ejercicio2
Porcentaje de aciertos = 80.0%
Etiquetas predichas para las muestras de testing

P Q Y W F Y B L M Q 
J W V T V U L I L K 
F M X K E N K U I X 
U Q R A D A C B X Y 
P U E K E W G E E V 
V W E J X B Q A Z U 
O U J O F Y Z E S Z 
A N N L P H W X H Y 
T P V F T K J V A M 
G M G N W Z M E D X 

Imágenes que corresponden a las etiquetas anteriores

Referencias