import numpy as np
import matplotlib.pyplot as plt

6.6 Ejemplo con Colores RGB

  • Codificación R(ed)G(reen)B(lue) para los colores en informática

  • En este enlace puedes seleccionar un color a ojo y obtener su valor RGB

  • Colores cercanos tendrán valores RGB cercanos

Funciones del SOM

Usaremos un SOM bidimensional con m x n celdas.

Usaremos un array bidimensional más un eje donde colocaremos los pesos correspondientes a los canales R, G y B, es decir, un vector de tres componentes de números enteros en el intervalo \([0,255]\).

La función find_BMU devuelve las coordenadas (en el grid) de la neurona BMU dado el mapa SOM y el vector de datos de entrenamiento x.

Los inputs son el mapa SOM y el vector de entrenamiento.

Devuelve las coordenadas \((g,h)\) de la BMU en el grid.

def find_BMU(SOM,x):
    distSq = (np.square(SOM - x)).sum(axis=2)
    return np.unravel_index(np.argmin(distSq, axis=None), distSq.shape)

La función update_weights() tiene como inputs

  • mapa SOM

  • x: vector de entrenamiento (train_ex(ample))

  • los parámetros

    • tasa de aprendizaje

    • radio del kernel

    • coordenadas de la BMU en el grid

    • parámetro step relacionado con el máximo número de celdas a actualizar en cualquier dirección

Esta función actualiza los valores de los vectores prototipos de las neuronas del mapa SOM.

Si el radio es cercano a cero, solamente actualiza la neurona BMU. En caso contrario, actualiza solamente las celdas en un vecindario de la BMU dado por step.

def update_weights(SOM, train_ex, learn_rate, radius_sq, 
                   BMU_coord, step=3):
    g, h = BMU_coord

    if radius_sq < 1e-3:
        SOM[g,h,:] += learn_rate * (train_ex - SOM[g,h,:])
        return SOM

    for i in range(max(0, g-step), min(SOM.shape[0], g+step)):
        for j in range(max(0, h-step), min(SOM.shape[1], h+step)):
            dist_sq = np.square(i - g) + np.square(j - h)
            dist_func = np.exp(-dist_sq / 2 / radius_sq)
            SOM[i,j,:] += learn_rate * dist_func * (train_ex - SOM[i,j,:])   
    return SOM    

La función train_SOM realiza el entrenamiento de la red neuronal. Requiere

  • SOM inicializado o parcialmente entrenado

    • Es posible tener un mapa SOM parcialmente entrenado y guardar el resultado

  • datos de entrenamiento, x

  • los parámetros de aprendizaje:

    • tasa de aprendizaje

    • decay de aprendizaje

    • radio del kernel

    • decay del radio

    • número de epochs de aprendizaje

def train_SOM(SOM, train_data, learn_rate = .1, radius_sq = 1, 
             lr_decay = .1, radius_decay = .1, epochs = 10):    
    learn_rate_0 = learn_rate
    radius_0 = radius_sq
    for epoch in np.arange(0, epochs):
        rand.shuffle(train_data)      
        for train_ex in train_data:
            g, h = find_BMU(SOM, train_ex)
            SOM = update_weights(SOM, train_ex, 
                                 learn_rate, radius_sq, (g,h))
        
        learn_rate = learn_rate_0 * np.exp(-epoch * lr_decay)
        radius_sq = radius_0 * np.exp(-epoch * radius_decay)            
    return SOM

Datos de entrenamiento

  • Fabricamos un ejemplo para usar el SOM

Inicializamos un conjunto de \(n_x=3000\) datos de tipo RGB, es decir, tuplas de tres números enteros en el rango \([0,255]\). Cada uno de ellos lo podemos representar como un pixel coloreado con la función imshow del paquete pyplot.

n_x = 3000
rand = np.random.RandomState(0)
train_data = rand.randint(0, 255, (n_x, 3))
train_data.shape,train_data[0]
((3000, 3), array([172,  47, 117]))

Histogramas de la distribución de cada canal

f,ax=plt.subplots(nrows=1,ncols=3,figsize=(15,5))
bins = 50
ax[0].hist(train_data[:][:,0],bins=bins,range=(0,256))
ax[0].set_title('Canal R')
ax[1].hist(train_data[:][:,1],bins=bins,range=(0,256))
ax[1].set_title('Canal G')
ax[2].hist(train_data[:][:,2],bins=bins,range=(0,256))
ax[2].set_title('Canal B')
f.suptitle('Histogramas por canal')
plt.plot()
[]
_images/06.06_SOM_EjemploColoresRGB_12_1.png
train_data[0:10]
array([[172,  47, 117],
       [192,  67, 251],
       [195, 103,   9],
       [211,  21, 242],
       [ 36,  87,  70],
       [216,  88, 140],
       [ 58, 193, 230],
       [ 39,  87, 174],
       [ 88,  81, 165],
       [ 25,  77,  72]])

Representamos los diez primeros datos usando la función imshow

f,ax = plt.subplots(nrows=1,ncols=1,figsize=(4,4))
ax.imshow(train_data[0:10].reshape(2,5,3))
<matplotlib.image.AxesImage at 0x7f6ee0176d60>
_images/06.06_SOM_EjemploColoresRGB_15_1.png

Representamos todos los datos iniciales

fig, ax = plt.subplots(
    nrows=1, ncols=1, figsize=(12, 12), 
    subplot_kw=dict(xticks=[], yticks=[]))
ax.imshow(train_data.reshape(50, 60, 3))
ax.title.set_text('Training Data')
_images/06.06_SOM_EjemploColoresRGB_17_0.png

Creamos una red SOM y la inicializamos

La red tiene un tamaño de \(10\times 10\) nodos en un mallado bidimensional rectangular.

Inicializamos los 100 vectores con valores aleatorios en los tres atributos (R,G,B).

Mostramos dos inicializaciones diferentes.

# Dimensiones
m = 10
n = 10

f,ax=plt.subplots(nrows=1,ncols=2,figsize=(10,5))
SOM = rand.randint(0, 255, (m, n, 3)).astype(float)
ax[0].imshow(SOM.astype('int'))
ax[0].set_title('Inicialización aleatoria 1')
SOM = rand.randint(0, 255, (m, n, 3)).astype(float)
ax[1].imshow(SOM.astype('int'))
ax[1].set_title('Inicialización aleatoria 2')
f.suptitle('SOM inicial')
plt.plot()
[]
_images/06.06_SOM_EjemploColoresRGB_19_1.png
fig, ax = plt.subplots(
    nrows=1, ncols=2, figsize=(12, 3.5), 
    subplot_kw=dict(xticks=[], yticks=[]))
ax[0].imshow(train_data.reshape(50, 60, 3))
ax[0].title.set_text('Datos de entrenamiento')
ax[1].imshow(SOM.astype(int))
ax[1].title.set_text('Mapa SOM inicializado aleatoriamente')
_images/06.06_SOM_EjemploColoresRGB_20_0.png

Entrenamiento del SOM

Visualizamos cómo evoluciona el mapa SOM en diferentes epochs de aprendizaje

fig, ax = plt.subplots(
    nrows=1, ncols=4, figsize=(15, 3.5), 
    subplot_kw=dict(xticks=[], yticks=[]))
total_epochs = 0
for epochs, i in zip([1, 4, 5, 10], range(0,4)):
    total_epochs += epochs
    SOM = train_SOM(SOM, train_data, epochs=epochs)
    ax[i].imshow(SOM.astype(int))
    ax[i].title.set_text('Epochs = ' + str(total_epochs))
_images/06.06_SOM_EjemploColoresRGB_22_0.png

Efecto del tamaño del SOM

Usamos un grid de \(20\times20\) neuronas

Realizamos dos entrenamientos a partir de condiciones iniciales diferentes

m = 20
n = 20

SOM = rand.randint(0, 255, (m, n, 3)).astype(float)

fig, ax = plt.subplots(
    nrows=1, ncols=4, figsize=(15, 3.5), 
    subplot_kw=dict(xticks=[], yticks=[]))
total_epochs = 0
for epochs, i in zip([1, 4, 5, 10], range(0,4)):
    total_epochs += epochs
    SOM = train_SOM(SOM, train_data, epochs=epochs)
    ax[i].imshow(SOM.astype(int))
    ax[i].title.set_text('Epochs = ' + str(total_epochs))
_images/06.06_SOM_EjemploColoresRGB_24_0.png
m = 20
n = 20

SOM = rand.randint(0, 255, (m, n, 3)).astype(float)

fig, ax = plt.subplots(
    nrows=1, ncols=4, figsize=(15, 3.5), 
    subplot_kw=dict(xticks=[], yticks=[]))
total_epochs = 0
for epochs, i in zip([1, 4, 5, 10], range(0,4)):
    total_epochs += epochs
    SOM = train_SOM(SOM, train_data, epochs=epochs)
    ax[i].imshow(SOM.astype(int))
    ax[i].title.set_text('Epochs = ' + str(total_epochs))
_images/06.06_SOM_EjemploColoresRGB_25_0.png

Important

No estamos convergiendo al mismo mapa, pero en ambos casos convergemos a un mapa que preserva las propiedades topológicas del espacio de datos de entrada.

Comparamos las distribuciones de los canales de color en los datos de entrenamiento y en el SOM entrenado.

f,ax=plt.subplots(nrows=2,ncols=3,figsize=(15,10))
bins = 20
ax[0,0].hist(train_data[:,0],bins=bins,range=(0,256))
ax[0,0].set_title('Datos Canal R')
ax[0,1].hist(train_data[:,1],bins=bins,range=(0,256))
ax[0,1].set_title('Datos Canal G')
ax[0,2].hist(train_data[:,2],bins=bins,range=(0,256))
ax[0,2].set_title('Datos Canal B')

bins = 20
ax[1,0].hist(SOM.reshape(m*n,3)[:,0],bins=bins,range=(0,256))
ax[1,0].set_title('SOM Canal R')
ax[1,1].hist(SOM.reshape(m*n,3)[:,1],bins=bins,range=(0,256))
ax[1,1].set_title('SOM Canal G')
ax[1,2].hist(SOM.reshape(m*n,3)[:,2],bins=bins,range=(0,256))
ax[1,2].set_title('SOM Canal B')
Text(0.5, 1.0, 'SOM Canal B')
_images/06.06_SOM_EjemploColoresRGB_28_1.png

Datos iniciales con sesgo

Realizamos la misma prueba con datos iniciales sesgados hacia el rojo.

n_x = 3000
rand = np.random.RandomState(0)


train_data_R = rand.randint(175, 255, (n_x, 1))
train_data_G = rand.randint(0, 200, (n_x, 1))
train_data_B = rand.randint(0, 150, (n_x, 1))
train_data = np.zeros((n_x,3)).astype('int')
train_data[:,0] = train_data_R.reshape(n_x,)
train_data[:,1] = train_data_G.reshape(n_x,)
train_data[:,2] = train_data_B.reshape(n_x,)

# inicializamos el SOM
m = 20
n = 20

SOM = rand.randint(0, 255, (m, n, 3)).astype(float)
SOMuntrained = SOM.copy()

# entrenamos el SOM
total_epochs = 0
for epochs, i in zip([1, 4, 5, 10], range(0,4)):
    total_epochs += epochs
    SOM = train_SOM(SOM, train_data, epochs=epochs)
    #ax[i].imshow(SOM.astype(int))
    #ax[i].title.set_text('Epochs = ' + str(total_epochs))
train_data[:10]
array([[201, 109, 145],
       [184,  87,  37],
       [247,  33,  11],
       [176, 114,  58],
       [251, 146,  15],
       [215,  95, 110],
       [221,  85,   0],
       [220,  16, 139],
       [210, 112, 122],
       [192, 189,  35]])
fig, ax = plt.subplots(
    nrows=1, ncols=3, figsize=(12, 3.5), 
    subplot_kw=dict(xticks=[], yticks=[]))
ax[0].imshow(train_data.reshape(50, 60, 3))
ax[0].title.set_text('Training Data')
ax[1].imshow(SOMuntrained.astype(int))
ax[1].title.set_text('Untrained SOM Grid')
ax[2].imshow(SOM.astype(int))
ax[2].title.set_text('Trained SOM Grid')
_images/06.06_SOM_EjemploColoresRGB_32_0.png
train_data[0:10,2]
array([145,  37,  11,  58,  15, 110,   0, 139, 122,  35])
# representamos los histogramas

bins = 20

f,ax=plt.subplots(nrows=3,ncols=3,figsize=(15,15))

ax[0,0].hist(train_data[:,0],bins=bins,range=(0,256))
ax[0,0].set_title('Datos Canal R')
ax[0,1].hist(train_data[:,1],bins=bins,range=(0,256))
ax[0,1].set_title('Datos Canal G')
ax[0,2].hist(train_data[:,2],bins=bins,range=(0,256))
ax[0,2].set_title('Datos Canal B')

ax[1,0].hist(SOMuntrained.reshape(m*n,3)[:,0],bins=bins,range=(0,256))
ax[1,0].set_title('SOM Untrained Canal R')
ax[1,1].hist(SOMuntrained.reshape(m*n,3)[:,1],bins=bins,range=(0,256))
ax[1,1].set_title('SOM Untrained Canal G')
ax[1,2].hist(SOMuntrained.reshape(m*n,3)[:,2],bins=bins,range=(0,256))
ax[1,2].set_title('SOM Unrtained Canal B')

ax[2,0].hist(SOM.reshape(m*n,3)[:,0],bins=bins,range=(0,256))
ax[2,0].set_title('SOM Canal R')
ax[2,1].hist(SOM.reshape(m*n,3)[:,1],bins=bins,range=(0,256))
ax[2,1].set_title('SOM Canal G')
ax[2,2].hist(SOM.reshape(m*n,3)[:,2],bins=bins,range=(0,256))
ax[2,2].set_title('SOM Canal B')
Text(0.5, 1.0, 'SOM Canal B')
_images/06.06_SOM_EjemploColoresRGB_34_1.png

Efecto de los parámetros

Investigamos ahora el efecto que tienen los parámetros.

Realizamos gráficas de un SOM entrenado con un mallado de opciones para:

  • la tasa de aprendizaje

  • el radio del kernel de vecindad

Para radios cercanos a cero (primera columna) el algoritmo de aprendizaje solamente cambia las celdas individuales, pero no las vecinas. No se crea un mapa donde exista el fenómeno de emergencia, independientemente del valor de la tasa de aprendizaje.

La tasa de aprendizaje marca la velocidad con la que el SOM puede alcanzar la convergencia. Una alta tasa de aprendizaje realiza cambios más bruscos en los vectores, creando más ruido, mientras que una baja tasa de aprendizaje realiza cambios más sutiles, pero son necesarias muchas iteraciones para llegar a converger.

Importante: La correcta elección de los parámetros depende del caso de estudio.

m = 10
n = 10

SOM = rand.randint(0, 255, (m, n, 3)).astype(float)

n_x = 3000
rand = np.random.RandomState(75)

train_data = rand.randint(0, 255, (n_x, 3))
fig, ax = plt.subplots(
    nrows=3, ncols=3, figsize=(15, 15), 
    subplot_kw=dict(xticks=[], yticks=[]))

for learn_rate, i in zip([0.001, 0.5, 0.99], [0, 1, 2]):
    for radius_sq, j in zip([0.01, 1, 10], [0, 1, 2]):
        rand = np.random.RandomState(0)
        SOM = rand.randint(0, 255, (m, n, 3)).astype(float)        
        SOM = train_SOM(SOM, train_data, epochs = 5,
                        learn_rate = learn_rate, 
                        radius_sq = radius_sq)
        ax[i][j].imshow(SOM.astype(int))
        ax[i][j].title.set_text('$\eta$ = ' + str(learn_rate) + 
                                ', $\sigma^2$ = ' + str(radius_sq))
_images/06.06_SOM_EjemploColoresRGB_37_0.png