En un post pasado de teoría de la información en deep learning vimos como Naftali Tishby y su equipo han aplicado con éxito el método del cuello de botella de la información al deep learning.
En sus simulaciones analizaron cuánta información retiene cada capa de una red neuronal de la entrada y de la salida (objetivo) y cómo varía durante el proceso de entrenamiento.
A continuación vamos a hacer un experimento con una red convolucional (CNN) observando cómo evolucionan las representaciones en las capas intermedias en el proceso de entrenamiento. De esta forma, podremos hacernos una idea intuitiva de qué información se retiene de la entrada en el proceso de entrenamiento.
Para una descripción de cómo funcionan las redes convolucionales se pueden ver buenos tutoriales como este.
La red convolucional que vamos a utilizar consta de las siguientes capas:
- Una capa con 20 filtros de convolución con su correspondiente función de activación con rectificador.
- Una capa de pooling 2:1.
- Una capa con 50 filtros de convolución con su correspondiente función de activación con rectificador.
- Una capa de pooling 2:1.
- Se convierten los filtros resultantes de dos dimensiones en 2450 unidades de una dimensión.
- Una capa densa de 500 unidades con su correspondiente función de activación con rectificador.
- Una última capa de 10 unidades con softmax para clasificación.
Vemos la arquitectura del modelo y un resumen en Keras.
Los datos de entrada son los dígitos MNIST, que se han puesto en formato 60K x [1 x 28 x 28], convertiendo a float y normalizado a 1.
Para visualizar el contenido de las capas, una vez creado y entrenado el modelo, lo pasamos como entrada a una clase tipo Modelo de Keras que devuelve los valores de las salidas de las capas. Al final del post incluimos el código fuente utilizado.
En el experimento, introducimos un dígito “7” en el modelo y vemos cómo cambian las salidas de los filtros de la primera capa (observamos ampliado el filtro 11) de pooling en función del número de epochs (un epoch es una iteración sobre todas las muestras de entrenamiento) realizados en el entrenamiento.
Entrenamos el modelo con un solo epoch y esto es lo que observamos.
Vemos como al principio del entrenamiento las capas todavía retienen mucha información de la entrada poco relevante para la salida (predicción del dígito observado).
Ahora hacemos lo mismo con 20 epochs.
Vemos que ya ha empezado la fase de compresión y la red se desprende de parte de la información de la entrada, reteniendo sólo las características más relevantes para la salida (objetivo). Parece como si se eliminara el ruido comprimiendo la información. En esta fase es donde mejora la generalización de la red clasificando mejor las entradas no vistas en el entrenamiento.
Por lo tanto, parece que nuestros resultados corroboran de manera intuitiva y pedagógica la interpretación del proceso de entrenamiento del deep learning desde el punto de vista de teoría de la información.
Código en Pyhton (puede haber problemas con la sangría por el copiado del código).
# se importan los paquetes from keras import backend as K from keras import models from keras.models import Sequential from keras.layers.convolutional import Conv2D from keras.layers.convolutional import MaxPooling2D from keras.layers.core import Dense from keras.layers.core import Activation from keras.layers.core import Flatten from keras.datasets import mnist from keras.utils import np_utils from keras.optimizers import SGD, RMSprop, Adam import numpy as np import matplotlib.pyplot as plt np.random.seed(1671) # semilla para reproducibilidad #Se define la clase con la red convolucional class LeNet: @staticmethod def build(input_shape, classes): model = Sequential() # CONV => RELU => POOL model.add(Conv2D(20, kernel_size=5, padding="same", input_shape=input_shape)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) # CONV => RELU => POOL model.add(Conv2D(50, kernel_size=5, padding="same")) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) # Flatten => RELU layers model.add(Flatten()) model.add(Dense(500)) model.add(Activation("relu")) # softmax para clasificación model.add(Dense(classes)) model.add(Activation("softmax")) return model # parámetros NB_EPOCH = 20 BATCH_SIZE = 128 VERBOSE = 1 OPTIMIZER = Adam() VALIDATION_SPLIT=0.2 IMG_ROWS, IMG_COLS = 28, 28 # Las dimensiones de cada imagen son 28x28 NB_CLASSES = 10 # La salida tendrá 10 clases o digitos diferentes INPUT_SHAPE = (1, IMG_ROWS, IMG_COLS) # carga de los datos de entrenamiento y prueba (X_train, y_train), (X_test, y_test) = mnist.load_data() K.set_image_dim_ordering("th") # conversión de los datos en float y normalización X_train = X_train.astype('float32') X_test = X_test.astype('float32') X_train /= 255 X_test /= 255 # Se añade un eje para formato de los datos de entrada 60K x [1 x 28 x 28] X_train = X_train[:, np.newaxis, :, :] X_test = X_test[:, np.newaxis, :, :] print(X_train.shape[0], 'train samples') print(X_test.shape[0], 'test samples') # convertir las clases o dígitos en matrices binarias y_train = np_utils.to_categorical(y_train, NB_CLASSES) y_test = np_utils.to_categorical(y_test, NB_CLASSES) # inicializar el modelo y el optimizador model = LeNet.build(input_shape=INPUT_SHAPE, classes=NB_CLASSES) model.compile(loss="categorical_crossentropy", optimizer=OPTIMIZER, metrics=["accuracy"]) # entrenar el modelo history = model.fit(X_train, y_train, batch_size=BATCH_SIZE, epochs=NB_EPOCH, verbose=VERBOSE, validation_split=VALIDATION_SPLIT) # evaluar el modelo con los datos de test score = model.evaluate(X_test, y_test, verbose=VERBOSE) print("\nTest score:", score[0]) print('Test accuracy:', score[1]) # resumen del modelo model.summary() # Tensor para extraer las salidas de las capas layer_outputs = [layer.output for layer in model.layers[:6]] # Modelo al que se le pasa el modelo creado y devuelve las salidas activation_model = models.Model(inputs=model.input, outputs=layer_outputs) #Convertimos la imagen de entrada en un tesor 4D X1_test=X_test[0] X1_test = X1_test[:, np.newaxis, :, :] X1_test.shape plt.imshow(X_test[0,0], cmap=plt.get_cmap('gray')) # Mostramos la imagen de entrada plt.show() # Devuleve una lista de arrays para cada capa # del resultado de predecir la imagen de entrada activations = activation_model.predict(X1_test) # Mostramos los valores de todos los filtros en una capa seleccionada first_layer_activation = activations[2] print(first_layer_activation.shape) for i in range(0, 20): plt.subplot(4,5,i+1) plt.imshow(first_layer_activation[0, i, :, :], cmap=plt.get_cmap('gray')) plt.show() plt.imshow(first_layer_activation[0, 10, :, :], cmap=plt.get_cmap('gray')) plt.show()