Qué son y cómo crear una red neuronal convolucional con Keras

Share on linkedin
Share on twitter
Share on email

Tratando imágenes mediante redes neuronales convolucionales con Keras

Las redes neuronales convolucionales consiste en aplicar redes neuronales sobre imágenes. ¿Sobre imágenes? Pues sí. Clasificar imágenes, detectar qué contienen, generar nuevas imágenes… todo esto es posible mediante redes neuronales convolucionales. En este post te voy a explicar qué son y cómo puedes crear una red neuronal convolucional en Keras con Python. ¿Suena interesante verdad? ¡Pues vamos a ello!

De imágenes a números

Una de las primeras preguntas que solemos hacernos cuando nos enfrentamos a este problema es, ¿cómo podemos trabajar sobre una imagen? Pues convirtiéndolo en números. Te explico cómo.

Pensemos en una imagen en monocromática (que tiene colores blanco o negro, como esta). Si lo piensas una imagen no son más que muchos píxeles (cuadraditos) juntos. Cada uno de esos píxeles guarda información. En el caso de las imágenes monocromáticas, por ejemplo, el pixel guarda dos valores: 1 si es blanco y 0 si es negro (o al revés).

Por tanto, si lo piensas, una imagen monocromática no es más que un dataset grande de unos y ceros. Pero, ¿qué pasa con las imágenes a color?

Pues la lógica es la misma, solo que en este caso, no hay una única capa, sino tres: una roja (R), una verde (G) y una azul (B). RGB. ¿Te suena de algo? Supongo que sí, ya que es la composición cromática de la mayoría de imágenes para pantallas.

En esta imágen podéis ver claramente como una imagen a color consta de 3 capas o layers las cuales, cuando las juntas, generan la imagen final.

Dicho esto, veámos cómo podemos leer las imágenes, en este caso en Python.

Leyendo Imágenes en Python

La teoría está muy bien, pero sin práctica… no sirve de nada. Por eso, vamos a ver cómo crear un clasificador de imágenes que clasifique entre perros y gatos. Para ello, el dataset que voy a usar es el dataset de Cat vs Dog de Kaggle. Como la información está en un zip, primero hago un unzip del zip global y después otro del fichero train.zip.

In [1]:
import os
from zipfile import ZipFile

with ZipFile('dogs-vs-cats.zip', 'r') as zipObj:
   # Extract all the contents of zip file in current directory
   zipObj.extractall()

with ZipFile('train.zip', 'r') as zipObj:
   # Extract all the contents of zip file in current directory
   zipObj.extractall()

Ahora vamos a crear un dataframe con cada uno de los objetos dentro de la carpeta que nos hemos bajado. Como el propio nombre del archivo indica si el animal es un perro o es un gato, extraeremos esa info como la etiqueta de la imagen.

In [2]:
import pandas as pd 

filenames = os.listdir("train")

categories = []
for filename in filenames:
    category = filename.split('.')[0]
    if category == 'dog':
        categories.append(1)
    else:
        categories.append(0)

df = pd.DataFrame({
    'filename': filenames,
    'category': categories
})

df.head(5)
Out[2]:
filenamecategory
0cat.0.jpg0
1cat.1.jpg0
2cat.10.jpg0
3cat.100.jpg0
4cat.1000.jpg0

Vemos como ya tenemos un dataframe con cada uno de los archivos y sus etiquetas. Ahora podemos abrir una imagen para que veáis como lo que en realidad lee Python es un array que tiene tres valores, cada uno de ellos entre 0 y 250, que son los límites de los valores RGB.

In [3]:
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

img=mpimg.imread('train/cat.0.jpg')
img
Out[3]:
array([[[203, 164,  87],
        [203, 164,  87],
        [204, 165,  88],
        ...,
        [240, 201, 122],
        [239, 200, 121],
        [238, 199, 120]],

       [[203, 164,  87],
        [203, 164,  87],
        [204, 165,  88],
        ...,
        [241, 202, 123],
        [240, 201, 122],
        [238, 199, 120]],

       [[203, 164,  87],
        [203, 164,  87],
        [204, 165,  88],
        ...,
        [241, 202, 123],
        [240, 201, 122],
        [239, 200, 121]],

       ...,

       [[153, 122,  55],
        [153, 122,  55],
        [153, 122,  55],
        ...,
        [  2,   2,   0],
        [  2,   2,   0],
        [  2,   2,   0]],

       [[152, 121,  54],
        [152, 121,  54],
        [152, 121,  54],
        ...,
        [  2,   2,   0],
        [  2,   2,   0],
        [  2,   2,   0]],

       [[151, 120,  53],
        [151, 120,  53],
        [151, 120,  53],
        ...,
        [  1,   1,   0],
        [  1,   1,   0],
        [  1,   1,   0]]], dtype=uint8)

Y aunque Python lea el array, lo que en realidad nosotros vemos es la imagen de un gatito.

In [4]:
plt.imshow(img) 
Out[4]:
<matplotlib.image.AxesImage at 0x1bc258c1b38>

Entendiendo qué es una red neuronal convolucional

Ya hemos aprendido a leer una imagen. Ahora podríamos convertir las tres capas en una única columna para crear el clasificador con una red neuronal normal, como la que hice en este post.

Sin embargo, las redes neuronales fully connected no son tan eficientes a la hora de trabajar con imágenes. Las redes neuronales convolucionales funcionan muchísimo mejor. Este tipo de red neuronal consiste en coger una imagen con sus tres capas e ir aplicando una serie de procesos que nos permitan simplifcar su información.

Esos «procesos» que os comento, son los siguientes:

Capa Convolucional

Consiste en convolver la imagen actual por una matriz, llamada Kernel o filtro, es decir:

  1. Superpones el kernel a la imagen.
  2. Multiplicas el valor del kernel por el de la imagen.
  3. Calculas el producto de los resultados del paso previo.
  4. Mueves el kernel un pixel y repites el proceso.

Os dejo un ejemplo del proceso para que se entienda mejor.

Aplicando Capa Convolucionar

Una vez tenemos el resultado previo, hacemos lo mismo que en una red neuronal normal: le sumamos un parámetro (bias) y aplicamos la función de activación, como puede ser ReLu o sigmoide (en este post te explico qué son, cómo funcionan y cómo programarlas 😉).

Así es como funciona, a grandes rasgos una capa convolucional. ¿Qué hemos conseguido con esto? Pues gracias a las capas convolucionales, la red neuronal es capaz de detectar líneas, formas, texturas, etc.. ¿No te lo crees? Prueba a probar con esta imagen de ejemplo de Deeplearning.ai, a ver qué resultado te da 😉

Y aunque visto en la imagen parece muy simple, seguro te han surgido alguna de las siguientes dudas:

  • ¿Qué dimensiones tiene que tener el Kernel?
  • ¿Qué valores?
  • ¿Cómo se crea una capa convolucional?
  • Si con cada capa, el resultado es más pequeño… ¿es que hay un límite de capas convolucionales que podamos meter?

Pues bien, vayamos por partes.

Qué dimensiones tiene que tener el Kernel de una red neuronal convolucional

Pues bien, para la primera pregunta no hay una respuesta exacta. Sí que parece que una red con muchas capas y kernels más pequeños es más eficiente que una red neuronal convolucional que tenga menos capas y Kernels más grandes (link). De hecho, lo más normal es que el Kernel tenga una dimensión de 3×3 (enlace).

Conclusión: usamos un Kernel de 3×3.

Qué valores tiene que tener el Kernel de una red neuronal convolucional

Aunque haya ciertas estructuras de filtros que permiten detectar ciertos tipos de formas, estos se suelen dejar como parámetros que la red neuronal deberá optimizar. Es decir, al igual que los pesos en las redes neuronales fully connected, el Kernel suele inicializarse con valores aleatorios para después optimziarse.

¿Cómo se crea una capa convolucional?

Crear una capa convolucional es muy sencillo. Habría dos formas de hacer, con Tensorflow o Keras:

  • Tensorflow: tf.nn.conv2d()
  • Keras: model.add(layers.Conv2D())
¿Hay un límite de capas convolucionales que puedes meter?

Efectivamente, con cada convolución, se reduce el tamaño del resultado, por lo que si no se hace nada, sí que habría un número máximo de capas convolucionales que se pueden aplicar. Pero, esto mismo lo podemos evitar incluyendo lo que se conoce como padding o relleno a la imagen.

El padding básicamente consiste en añadir un borde de una cierta cantidad de pixeles a la imagen antes de aplicar la convolución, de tal manera que el resultado de la convolución tenga el mismo tamaño que la matriz de entrada sin el relleno.

Con todo esto ya tendríamos explicado qué son las capas convolucionales, cómo funcionan y cómo se programan en Tensorflow y Keras. Ahora, vayamos con las capas de pooling.

Capa de Pooling

Como habrás visto, las capas convolucionales permiten detectar patrones en la imagen pudiendo mantener el tamaño del resultado. ¿Qué es lo que pasa con eso? Pues básicamente que al mantener el tamaño de la imagen, el proceso es muy difícil. Al fin y al cabo una imagen tiene muchos píxeles. Para solucionar esto están las capas de pooling.

Las capas de pooling permiten reducir el peso de la representación para así agilizar el proceso de aprendizaje de la red neuronal. Además, suele hacer que el resultado de las capas convolucionales sea más robusto. Suena bien, pero, ¿en qué consisten?

El funcionamiento de una capa de pooling es bastante sencillo. Supongamos que tenemos una matriz de 4×4. Si aplicamos una capa de Max Pooling, por ejemlo, solo tendríamos que dividir la matriz en 4 cuadrantes y crear una nueva matriz con el valor más alto que haya dentro de cada uno de los resultados. Te pongo un ejemplo:

Ejemplo de Max Pooling

¿Qué conseguimos con esto? Pues básicamente, si el valor es alto significa que la capa convolucional habrá encontrado un patrón, de tal forma que las capa de max pooling nos aseguran que los patrones detectados en la capa convolucional se mantengan en la siguiente capa de la red.

Además de la capa de max pooling existe también la capa de average pooling, la cual calcula el promedio en vez de el valor más alto. Sin embargo esta capa no se suele usar tanto como la capa de max pooling, ya que no suele dar tan buenos resultados.

Además, las capas de max pooling no tienen ningún parámetro que la red tendrá que apreneder, lo cual facilita el proceso. Lo que sí existen es una serie de hiperparámetros que tendremos que elegir y que serán fijos.

Una vez más, la funciónd e maxpooling la podemos aplicar tanto en Tensorflow como en Keras:

  • Tensorflow: tf.nn.max_pool2d()
  • Keras: tf.keras.layers.MaxPool2D()

Sabiendo esto, ya podríamos comenzar con nuestra red neuronal convolucional.

Creando una red neuronal convolucional

Teniendo en cuenta todo lo anterior, vamos a crear una red neuronal convolucional. La estructura que seguiremos serán:

  1. Una capa convolucional 3×3 (sin paddings) seguida de una capa de MaxPooling de 2×2
  2. Una capa convolucional 3×3 (sin paddings) seguida de una capa de MaxPooling de 2×2
  3. Aplanar el resultado para poder aplicar una
In [5]:
import tensorflow as tf

model = tf.keras.models.Sequential()

# Añadimos la primera capa
model.add(tf.keras.layers.Conv2D(64,(3,3), activation = 'relu', input_shape = (128,128,3)))
model.add(tf.keras.layers.MaxPooling2D(pool_size = (2,2)))

# Añadimos la segunda capa
model.add(tf.keras.layers.Conv2D(64,(3,3), activation = 'relu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size = (2,2)))

# Hacemos un flatten para poder usar una red fully connected
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(64, activation='relu'))

# Añadimos una capa softmax para que podamos clasificar las imágenes
model.add(tf.keras.layers.Dense(2, activation='softmax'))


model.compile(optimizer="rmsprop",
              loss='categorical_crossentropy',
              metrics=['accuracy'])

Ahora que ya tenemos el modelo creado vamos a:

  • Reclasificar las etiquetas del df en perro y gato.
  • Crear df de train y test.
  • Generador de batches y aplicación de data agumentation (cortesía del notebook de Usimity)

Como veréis, si bien los dos primeros pasos son similares a lo que haríamos en una red neuronal normal, el tercero paso es algo nuevo. Y es que, tratar con imágenes es un proceso que requiere de mucha memoria, por lo que no podemos hacerlo todo de golpe, sino que tenemos que hacerlo por lotes o o batches.

Además, podemos aplicar ciertas distorsiones a la imagen (zoom, darle la vuelta, etc.) para crear una imagen nueva y así aumentar nuestro dataset de entrenamiento. Todo esto lo hacemos con la función ImageDataGenerator de Keras.

In [7]:
from sklearn.model_selection import train_test_split

# Convertimos la variable category en Gato o Perro
df["category"] = df["category"].replace({0: 'gato', 1: 'perro'})

# Creamos los df de train y test con un split del 75-25
df_train, df_test = train_test_split(df, test_size=0.25, random_state=42)

# Creamos unas modificaciones sobre las imágenes para tener más cantidad de imágenes con las que entrenar.
datos_train = tf.keras.preprocessing.image.ImageDataGenerator(
    rotation_range=15,
    rescale=1./255,        # Normalizar la imagen
    shear_range=0.1,
    zoom_range=0.2,
    horizontal_flip=True,
    width_shift_range=0.1,
    height_shift_range=0.1
)

# Generador de imágenes 
tamaño_batch = 15
generador_train = datos_train.flow_from_dataframe(
    df_train, 
    "train/", 
    x_col='filename',
    y_col='category',
    target_size= (128,128),
    class_mode= 'categorical',
    batch_size= tamaño_batch
)


# Repetimos el proceso para test
datos_test =  tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255)
generador_test = datos_test.flow_from_dataframe(
    df_test, 
    "train/", 
    x_col='filename',
    y_col='category',
    target_size=(128,128),
    class_mode='categorical',
    batch_size=tamaño_batch
)
Found 18750 validated image filenames belonging to 2 classes.
Found 6250 validated image filenames belonging to 2 classes.

Ya con esto podemos entrenar el modelo.

In [8]:
epochs=3 
historia = model.fit_generator(
    generador_train, 
    epochs=epochs,
    validation_data=generador_test,
    validation_steps=df_test.shape[0]//tamaño_batch,
    steps_per_epoch=df_train.shape[0]//tamaño_batch
)
WARNING:tensorflow:From <ipython-input-8-34fa295a25fe>:7: Model.fit_generator (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
Please use Model.fit, which supports generators.
WARNING:tensorflow:sample_weight modes were coerced from
  ...
    to  
  ['...']
WARNING:tensorflow:sample_weight modes were coerced from
  ...
    to  
  ['...']
Train for 1250 steps, validate for 416 steps
Epoch 1/3
1250/1250 [==============================] - 516s 413ms/step - loss: 0.6497 - accuracy: 0.6429 - val_loss: 0.5669 - val_accuracy: 0.7091
Epoch 2/3
1250/1250 [==============================] - 403s 322ms/step - loss: 0.5747 - accuracy: 0.7042 - val_loss: 0.5024 - val_accuracy: 0.7591
Epoch 3/3
1250/1250 [==============================] - 396s 317ms/step - loss: 0.5385 - accuracy: 0.7395 - val_loss: 0.5020 - val_accuracy: 0.7505

Con esto ya tendríamos creada nuestro clasficador de imágenes. ¿Fácil verdad? Ya solo queda ver cómo ha ido aprendiendo nuestra red neuronal convolucional hecha en Keras. ¡Veamos!

In [17]:
import matplotlib.pyplot as plt

acc = historia.history['accuracy']
epochs = range(epochs)
plt.plot(epochs, acc)
plt.title('Training and validation loss')
Out[17]:
Text(0.5, 1.0, 'Training and validation loss')

Como ves, en solo 3 epochs, nuestra red neuronal convolucional ha conseguido aprender y tener una tasa de acierto de casi el 75%, lo cual para tan pocas vueltas, está muy bien.

Espeor que este tutorial te haya servido para entender un poco mejor cómo funciona el procesamiento de imágenes, las técnicas que se usan y cómo se programa en Keras.