\index{red!neuronal!convolucional}
Noelia Vállez Enano
$^{a}$Universidad de Castilla-La Mancha
$^{b}$Ubotica Technologies
Las redes neuronales convolucionales (en inglés convolutional neural network, CNN) son una extensión de las redes neuronales artificiales (ANN)\index{red!neuronal!artificial} en las que se incluyen capas convolucionales, explicadas en detalle en las siguientes secciones, para aprender a extraer, de forma automática, las características de los datos de entrenamiento al inicio de la arquitectura (Fig. @ref(fig:cnn)).
Las primeras capas convolucionales de la red aprenden a extraer características generales de los datos de entrada, mientras que las últimas capas convolucionales extraen características mucho más específicas. Cuanto más larga (o más **profunda**) es la red, mayor cantidad de detalles podrá aprender a distinguir. Esto es lo que ha propiciado la aparición del término ``aprendizaje profundo" [@goodfellow2016deep].Tras las capas convolucionales suelen encontrarse las capas densas o totalmente conectadas de la misma tipología de las vistas en el Cap. @ref(capNN). Esta parte de la red será la encargada de realizar la clasificación\index{clasificación} de las observaciones en función de los valores de las características extraídas en la parte convolucional. Por tanto, se dice que este tipo de redes tiene dos partes: una de extracción de características (realizada por la red convolucional) y otra de clasificación o regresión (como las vistas en el Cap. @ref(capNN)).
Aunque las ANN \index{red!neuronal!artificial} pueden utilizarse con los valores de color de una imagen como variables para reconocer qué hay en ella (véase Cap. @ref(capNN)), no permiten extraer información de carácter espacial (de indudable importancia en el estudio de fenómenos con datos anclados al espacio y con dependencia espacial, entre otros ámbitos). Para lidiar con este problema, las CNN incorporan capas convolucionales para extraer características de las observaciones con las que se alimenta la red, incluyendo información sobre la estructura espacial [@LeCun1995-LECCNF].
Las convoluciones realizan una tarea similar al sistema visual humano; de hecho, se inspiran en cómo el ser humano percibe y procesa las características de los objetos. Aunque se diseñaron principalmente para ayudar a resolver tareas de visión por computador, donde la entrada de la red es una imagen, es posible utilizarlas también con entradas vectoriales o series temporales.
Una convolución aplica un filtro sobre la entrada siguiendo un proceso de ventana deslizante. El filtro (o kernel) no es otra cosa que una matriz con unos pesos que se centra en cada uno de los valores de la entrada para calcular una media ponderada de los valores de la entrada, siendo las ponderaciones los valores del filtro [@Bueno2015]. El tamaño de los filtros suele ser, por tanto, impar.
La Fig. @ref(fig:convolution) muestra el resultado de aplicar la operación de convolución con un filtro de tamaño

(\#fig:convolution)Ejemplo de convolución. De izquierda a derecha: entrada (negro = 0, blanco = 1), filtro y salida.
En general, una convolución en dos dimensiones se define como:
\begin{equation} {\bf M}[x,y]=\sum_{s=-a}^{a}\sum_{t=-b}^{b} {\bf F}[s,t] {\bf E}[x-s,y-t], \end{equation}
donde
Por tanto, cada valor del ejemplo de la Fig. @ref(fig:convolution) se obtiene como:
\begin{gather} \begin{aligned} M_{1,1} = 1\cdot0+ 0\cdot 0+1 \cdot 0+0 \cdot 0 + & 1 \cdot 1 + 0 \cdot 1+ 1\cdot 0 +0\cdot 1 +1 \cdot 1=2 \ M_{1,2} = 1\cdot0+ 0\cdot 0+1 \cdot 0+0 \cdot 1 + & 1 \cdot 1 + 0 \cdot 1+ 1\cdot 1 +0\cdot 1 +1 \cdot 1=3 \ & \cdots \ M_{2,2} = 1\cdot1+ 0\cdot 1+1 \cdot 1+0 \cdot 1 + & 1 \cdot 1 + 0 \cdot 1+ 1\cdot 1 +0\cdot 1 +1 \cdot 1=5 \ & \cdots \ M_{3,3} = 1\cdot1+ 0\cdot 1+1 \cdot 0+0 \cdot 1 + & 1 \cdot 1 + 0 \cdot 0+ 1\cdot 0 +0\cdot 0 +1 \cdot 0=2 \end{aligned} \end{gather}
La elección de unos u otros valores del filtro dará lugar a matrices de salida que realcen o suavicen ciertas partes de la entrada. Por ejemplo, si la entrada es una imagen, es posible definir filtros que realcen los bordes, que los suavicen o incluso que detecten dichos bordes y cómo de marcados están. La Fig. @ref(fig:filtros) muestra el resultado de aplicar distintos filtros a una imagen de entrada.
Los valores (o pesos) de los filtros se ajustaban tradicionalmente de forma manual según el problema a resolver. En los frameworks actuales se ajustan durante el proceso de entrenamiento de la CNN junto con el resto de pesos de la red, lo cual permite encontrar los valores del filtro que maximicen la precisión de la red.
Las capas convolucionales de la CNN no están compuestas por perceptrones, sino por neuronas convolucionales que aplican los filtros de convolución sobre la salida de la capa anterior. Por tanto, los pesos de estas neuronas convolucionales se organizan en forma de matriz, siendo cada matriz un filtro. Cada neurona\index{neurona} da lugar a una matriz cuyos valores pasarán por la función de activación para obtener finalmente lo que se conoce como mapa de activaciones. Por tanto, la salida de una capa convolucional compuesta por varias de estas neuronas está formada por un conjunto de estos mapas y tiene forma de matriz 3D (Fig. @ref(fig:mapas)).

(\#fig:mapas)Conjunto de mapas de activaciones de una determinada capa (cada filtro de la capa da lugar a un mapa diferente).
Al igual que los perceptrones, las neuronas convolucionales incluyen términos independientes que se suman al resultado de realizar la convolución con el filtro. Por ejemplo, una capa con 64 filtros de tamaño 7
\begin{equation} {\bf Y} = g ({ \bf F} \otimes {\bf E} + {\bf B}) , \end{equation}
donde
La mayoría de las CNN utilizan la ReLU como función de activación, o alguna variante de esta. Esta función de activación funciona muy bien con el método del descenso del gradiente\index{descenso del gradiente} que se utiliza para actualizar los pesos.
En el caso de que la entrada no sea una matriz 2D sino una matriz 3D como, por ejemplo, una imagen con varios canales de color o un conjunto de mapas de activación, los filtros contarán con una tercera dimensión. La Fig. @ref(fig:relu) muestra el resultado de aplicar un filtro de tamaño

(\#fig:relu)Resultado de aplicar un filtro 3D a una imagen antes y después de pasar por el filtro de activación.
Si se aplica el filtro convolucional a una entrada, la matriz resultante será algo más pequeña ya que no se puede centrar el filtro en los bordes de la matriz. Para poder hacerlo, se suele incrementar la entrada con un relleno (en inglés padding\index{padding@\textit{padding}}). El relleno se puede realizar con ceros, con algún valor, con el valor más cercano del borde, etc. La Fig. @ref(fig:padding) muestra algunos de los rellenos más empleados.
\index{stride@\textit{stride}}
El desplazamiento (en inglés stride) básico con el que se aplica un filtro convolucional es de 1. Sin embargo, la aplicación de muchos filtros repartidos en capas a lo largo de la red hace que sea especialmente difícil mantener todos los datos generados en un momento determinado del entrenamiento. Para reducir este volumen de datos, se suelen aplicar las convoluciones con un desplazamiento mayor que 1. Esto reduce el tamaño del mapa de activaciones obtenido por una determinada capa (véase Fig. @ref(fig:stride)).

(\#fig:stride)Desplazamiento 2 x 2 del filtro. El punto es el centro de la zona en la que se aplica el filtro en cada momento.
La ejecución en secuencia de varias capas convolucionales es muy efectiva a la hora de decidir si ciertas características están o no presentes en la entrada. Sin embargo, una de sus ventajas, y a la vez una de sus limitaciones, es que mantiene la localización espacial de las características. Aunque es necesaria cierta información espacial como, por ejemplo, el que hubiera unos bigotes cerca de una boca sería característico de una imagen que contuviese un gato, pequeños movimientos del contenido de la imagen producirían mapas de características diferentes.
Una forma de mitigar este problema es usar capas de agrupación (en inglés pooling\index{pooling@\textit{pooling}}). Estas capas agrupan un número de valores adyacentes de los mapas de características obteniendo un nuevo conjunto de mapas más pequeños. Es posible emplear distintos tipos de operaciones con las que realizar la agrupación. Los más empleados suelen ser el max pooling y el average pooling [@goodfellow2016deep], que seleccionan el máximo de los valores o su media, respectivamente (Fig. @ref(fig:pooling)). El tamaño más típico es 2

(\#fig:pooling)Resultado de emplear dos métodos de agrupación diferentes para reducir la dimensión de los datos.
La primera red convolucional fue propuesta en 1982 [@fukushima1982neocognitron]. Esta arquitectura recibió el nombre de Neocognitron y ya constaba de capas convolucionales y capas de pooling\index{pooling@\textit{pooling}}. Siguiendo la misma idea, en 1998 se diseñó otra CNN para resolver el problema de reconocimiento de dígitos manuscritos, MNIST [@lecun1998gradient]. A esta arquitectura de CNN se la conoce con el nombre de LeNet y es una de las arquitecturas más pequeñas que se puede definir para resolver un problema de clasificación\index{clasificación} (Fig. @ref(fig:lenet)). El extractor de características consta de dos capas convolucionales alternadas con 2 capas de pooling que obtienen un total de 400 variables. La parte final con el clasificador está compuesta por 3 capas densas de 120, 84 y 10 neuronas.
A pesar de los buenos resultados obtenidos por la arquitectura, el uso de estos métodos para resolver problemas reales estaba aún lejos debido a la carga computacional requerida para su entrenamiento. No fue hasta el año 2012, cuando los ganadores del concurso ImageNet Challenge presentaron una nueva arquitectura llamada AlexNet, que las CNN volvieron a estar en el punto de mira de los investigadores [@deng2012imagenet]. A partir de ese momento, y teniendo en cuenta los grandes avances computacionales de las tarjetas gráficas (GPU), que permitían ejecutar operaciones matriciales de forma eficiente, se empezaron a desarrollar cada vez más arquitecturas diferentes.
Durante los primeros años, las arquitecturas desarrolladas tenían cada vez más capas y más filtros en cada una de ellas para extraer la mayor cantidad de información posible de la entrada. Sin embargo, las arquitecturas más profundas se encontraron con un problema: el desvanecimiento del gradiente.
Ciertas funciones de activación como, por ejemplo, la sigmoide, comprimen el espacio de entrada entre 0 y 1. Esto hace que grandes cambios en la entrada produzcan cambios muy pequeños en la salida, haciendo que la derivada sea pequeña. Como los gradientes de la red se calculan durante la propagación hacia atrás, capa a capa, siguiendo la regla de la cadena, si los valores son muy cercanos a 0, la multiplicación de muchos de estos valores hará que el gradiente de la red caiga rápidamente. Un gradiente muy pequeño hará que los pesos de las capas iniciales apenas se modifiquen con cada iteración y no lleguen a obtener valores adecuados durante el entrenamiento.
Algunas soluciones a este problema son:
-
El uso de activaciones tipo ReLU, cuya derivada no genera valores muy pequeños.
-
Capas de normalización. Si se normalizan los datos de entrada ya no habrá grandes cambios entre ellos y los valores estarán lejos de los extremos de la sigmoide.
-
Uso de bloques con conexiones residuales que añaden (o concatenan) el valor de la entrada del bloque a su salida. Esta estrategia se utiliza en las arquitecturas tipo ResNet. Para más detalles, véase @alarcon2022deteccion.
Cuanto mayor es el número de parámetros de la red, mayor probabilidad hay de que ``memorice" los datos de entrenamiento. Esto se debe a la cantidad de características que la red es capaz de extraer y medir. Si la red es muy profunda, aprenderá cosas muy concretas del conjunto de entrenamiento, lo que dará lugar a modelos que no generalizan bien con nuevos datos [@tetko1995neural].2
Además de esto, la no linealidad que añaden las funciones de activación puede hacer que se encuentren fronteras de decisión que modelen datos que no son linealmente separables, pero también facilita que se produzca el sobreajuste (Fig. @ref(fig:overfitting)).
Para evitar que se produzca el sobreajuste se suelen emplear técnicas de regularización. Se trata de técnicas que impiden que los modelos sean demasiado complejos mejorando su capacidad de generalización. Algunas de estas técnicas son: \index{dropout@\textit{dropout}}
-
Dropout. Durante el entrenamiento, algunas activaciones se ponen a 0 de forma aleatoria (entre el 10% y el 50%). Esto hace que una capa de la red no dependa siempre de los mismos nodos anteriores.
-
Early Stopping. \index{early stopping@\textit{early stopping}} Se trata de parar el entrenamiento antes de que se produzca el sobreajuste y seleccionar ese modelo como final. Para ello se utilizan dos conjuntos: uno de entrenamiento y otro de validación. Cuando las curvas de pérdida de ambos conjuntos comienzan a divergir, se para el entrenamiento y se selecciona el modelo resultante del momento anterior al comienzo de la divergencia (Fig. @ref(fig:early)).
-
Regularización L1. Penaliza los pesos grandes, por lo que fuerza a los pesos a tener valores cercanos a 0 (sin ser 0). Añade un término de penalización a la función de coste sumando todos los pesos de la matriz y multiplicado por el valor de
$\alpha$ , otro hiperparámetro:3
\begin{equation} \alpha||\boldsymbol W||1 = \alpha\sum_i\sum_j|w{ij}| . \end{equation}
- Regularización L2 o weight decay\index{weight decay@\textit{weight decay}}. Es parecida a la regularización L1, pero con una expresión algo diferente:
\begin{equation} \frac{\alpha}{2} ||\boldsymbol W||2^2 = \frac{\alpha}{2}\sum_i\sum_j w^2{ij} . \end{equation}
Como se ha comentado anteriormente, las técnicas de deep learning\index{deep learning@\textit{deep learning}} suelen requerir de gran cantidad de datos para su correcto funcionamiento. En muchas situaciones, se dispone de un conjunto limitado para poder entrenar los modelos de forma correcta, por lo que para tratar de suplir la falta de datos se recurre a la generación de datos artificiales, mediante técnicas de data augmentation (en la literatura en español también se denominan con su nombre en inglés) [@shorten2019survey]. \index{data augmentation@\textit{data augmentation}}
Esta técnica realiza pequeñas variaciones en los datos del conjunto de entrenamiento del que se dispone para obtener datos adicionales, manteniendo el significado semántico de los mismos. También permite mejorar la generalización de los modelos. Por ejemplo, si se tienen imágenes donde una de ellas contiene un elemento de la clase "gato", las modificaciones que se realicen deben permitir poder reconocer esa misma clase a partir de las imágenes modificadas.
Algunos ejemplos de técnicas de data augmentation \index{data augmentation@\textit{data augmentation}} en imagen pueden ser: la realización de rotaciones, modificación del contraste o cambios en la iluminación, reescalados, adición/eliminación de ruido o cambio en las proyecciones de las mismas (Fig. @ref(fig:dataAugmentation)).
Para utilizar las técnicas de data augmentation en R, se pueden incluir capas específicas de preprocesado durante la definición del modelo, que serán ejecutadas durante el entrenamiento de forma aleatoria. Es decir, cada vez que el proceso de entrenamiento necesite una imagen, decidirá de forma aleatoria si aplicar cada una de las capas o no. En el siguiente ejemplo se realizan rotaciones aleatorias, volteados horizontales y acercamientos a la imagen:
data_augmentation <-
keras_model_sequential() |>
layer_random_rotation(0.1) |>
layer_random_flip("horizontal") |>
layer_random_zoom(0.1)
A continuación se muestran los diferentes tipos de preprocesado disponibles para imagen y redes neuronales convolucionales:
layer_random_crop()
layer_random_flip()
layer_random_flip()
layer_random_translation()
layer_random_rotation()
layer_random_zoom()
layer_random_height()
layer_random_width()
layer_random_contrast()
::: {.infobox data-latex=""} NOta
Otros tipos de data augmentation\index{data augmentation@\textit{data augmentation}} disponibles en keras
y R para otro tipo de datos pueden consultarse en:
https://tensorflow.rstudio.com/guides/keras/preprocessing_layers :::
En esta sección se entrena una red neuronal convolucional para clasificar las observaciones (imágenes) contenidas en el conjunto de datos CIFAR10 en las correspondientes clases. Cada una de las imágenes contenidas en el conjunto de datos contiene un único elemento que puede ser clasificado como avión, coche, pájaro, gato, ciervo, perro, rana, caballo, barco o camión.
La descarga de los datos debe hacerse a través del enlace https://drive.google.com/file/d/1-pFGg-bkooss1fNp5UNYR0-hLUmDP_XO/view?usp=sharing y tiene que guardarse en una carpeta data
dentro del proyecto de trabajo para asegurar la reproducibilidad del ejemplo.
::: {.infobox data-latex=""} Nota
Existe otra versión del conjunto de datos, denominada como CIFAR100, en la cual se definen un total de 100 posibles categorías en las que pueden ser clasificadas las imágenes que contiene: https://www.rdocumentation.org/packages/keras/versions/2.9.0/topics/dataset_cifar100 :::
Cada una de las imágenes contenidas en el conjunto tiene un tamaño de 32
A continuación se exponen los pasos que llevan desde la carga y visualización de los datos hasta determinar el rendimiento de la red con las imágenes del conjunto de test, pasos similares a los ya descritos en el Cap. @ref(capNN), pero adaptando la red al tipo de dato utilizado.
El primer paso es cargar la librería keras
, para crear las redes neuronales necesarias y cargar el conjunto de imágenes CIFAR10, que se encuentra disponible públicamente:
library(keras)
load("data/cifar10.RData")
A continuación, se ve (o comprueba) el contenido de las variables generadas, pudiéndose observar que el conjunto de datos CIFAR10
ya viene separado en dos subconjuntos: el de entrenamiento y el de test. El conjunto de entrenamiento está compuesto por 50.000 imágenes y el de test por 10.000. En ambos casos, estas imágenes se almacenan en la variable x.
names(cifar)
#> [1] "train" "test"
dim(cifar$train$x)
#> [1] 50000 32 32 3
dim(cifar$train$y)
#> [1] 50000 1
dim(cifar$test$x)
#> [1] 10000 32 32 3
dim(cifar$test$y)
#> [1] 10000 1
Además, las imágenes de cada subconjunto tienen definida la clase a la que pertenecen; en este caso, cualquiera de las 10 clases indicadas anteriormente. En ambos subconjuntos, esta etiqueta se almacena en la variable y. A continuación, se muestra un pequeño ejemplo que permite mostrar alguna de las imágenes contenidas en el conjunto de datos de entrenamiento junto con su etiqueta (Fig. @ref(fig:cifar-eti)):
class_names <- c('avion', 'coche', 'pajaro', 'gato', 'ciervo',
'perro', 'rana', 'caballo', 'barco', 'camion')
index <- 1:30
par(mfcol = c(5,6), mar = rep(1, 4), oma = rep(0.2, 4))
cifar$train$x[index,,,] |>
purrr::array_tree(1) |>
purrr::set_names(class_names[cifar$train$y[index] + 1]) |>
purrr::map(as.raster, max = 255) |>
purrr::iwalk(~{plot(.x); title(.y)})
\index{preprocesamiento}
Una vez cargados los datos y comprobado su contenido, igual que en el Cap. @ref(capNN), se puede realizar algún tipo de preprocesado. Al estar trabajando con imágenes, es muy típico estandarizar sus valores de color para mitigar las diferencias producidas por las diferentes condiciones de iluminación.
En este caso, también igual que en el Cap. @ref(capNN), se transforman los valores originales de la imagen (en rango de 0 a 255) a valores entre 0 y 1 dividiendo cada valor por el máximo, 255:
cifar$train$x <- cifar$train$x/255
cifar$test$x <- cifar$test$x/255
La CNN se crea en dos pasos, para, además, mostrar cómo se pueden utilizar las funciones proporcionadas por la librería keras
para definir una CNN en varias partes, combinándolas poco a poco.
En el primero, se define la base convolucional de la red combinando varias capas Conv2d
con MaxPooling2D
. Para ello se utiliza la interfaz sequential proporcionada por Tensorflow/Keras a través de la función keras_model_sequential()
. Esta es la parte de la red que se encarga de aprender las características que permitirán representar el contenido de la imagen. Otra de las diferencias principales que se puede observar en esta red es que, al aceptar imágenes de 3 canales, RGB, en vez de imágenes en escala de grises, el tamaño de la entrada de la primera capa tiene que reflejar esta circunstancia: input_shape = c(32, 32, 3)
.
model <- keras_model_sequential() |>
layer_conv_2d(filters = 32, kernel_size = c(3,3), activation = "relu",
input_shape = c(32,32,3)) |>
layer_max_pooling_2d(pool_size = c(2,2)) |>
layer_conv_2d(filters = 64, kernel_size = c(3,3), activation = "relu") |>
layer_max_pooling_2d(pool_size = c(2,2)) |>
layer_conv_2d(filters = 64, kernel_size = c(3,3), activation = "relu")
Como se puede observar, en esta parte de la red se reduce la dimensión de la información de manera paulatina en cada capa, obteniendo las características representativas del objeto contenido en cada imagen hasta llegar a un tamaño de
summary(model, line_length=74)
#> Model: "sequential_2"
#> __________________________________________________________________________
#> Layer (type) Output Shape Param #
#> ==========================================================================
#> conv2d_2 (Conv2D) (None, 30, 30, 32) 896
#> max_pooling2d_1 (MaxPooling2D) (None, 15, 15, 32) 0
#> conv2d_1 (Conv2D) (None, 13, 13, 64) 18496
#> max_pooling2d (MaxPooling2D) (None, 6, 6, 64) 0
#> conv2d (Conv2D) (None, 4, 4, 64) 36928
#> ==========================================================================
#> Total params: 56,320
#> Trainable params: 56,320
#> Non-trainable params: 0
#> __________________________________________________________________________
Ahora, se añaden capas que transformen los resultados de la parte convolucional de la red implementada en la probabilidad de que la imagen represente cada una de las posibles clases.
Para ello, primero se inserta una capa de tipo flatten
que se encarga de transformar la salida de la última capa convolucional dense
de 64 neuronas con activación relu
se encarga de realizar las primeras operaciones con esos datos y de reducir la dimensionalidad. Finalmente, una última capa dense
con activación softmax
se encarga de obtener la probabilidad de que la imagen represente cada una de las 10 posibles clases:
model |>
layer_flatten() |>
layer_dense(units = 64, activation = "relu") |>
layer_dense(units = 10, activation = "softmax")
A continuación se muestra la estructura final del modelo implementado:
summary(model, line_length=74)
#> Model: "sequential_2"
#> __________________________________________________________________________
#> Layer (type) Output Shape Param #
#> ==========================================================================
#> conv2d_2 (Conv2D) (None, 30, 30, 32) 896
#> max_pooling2d_1 (MaxPooling2D) (None, 15, 15, 32) 0
#> conv2d_1 (Conv2D) (None, 13, 13, 64) 18496
#> max_pooling2d (MaxPooling2D) (None, 6, 6, 64) 0
#> conv2d (Conv2D) (None, 4, 4, 64) 36928
#> flatten_1 (Flatten) (None, 1024) 0
#> dense_7 (Dense) (None, 64) 65600
#> dense_6 (Dense) (None, 10) 650
#> ==========================================================================
#> Total params: 122,570
#> Trainable params: 122,570
#> Non-trainable params: 0
#> __________________________________________________________________________
::: {.infobox data-latex=""} Nota
Un detalle a tener en cuenta con respecto al ejemplo del Cap. @ref(capNN) es la variable Total params
. Este valor indica el número de parámetros que contiene la red neuronal y, en cierta manera, el tamaño de la misma. Se puede observar que en este caso la CNN tiene 122.570 parámetros, mientras que la red del ejemplo del Cap. @ref(capNN) tiene 11.935.
:::
Finalmente, es necesario compilar el modelo, indicando algunos de los parámetros de configuración necesarios para el proceso de entrenamiento, como el optimizador a utilizar, la función de coste y las métricas a calcular para poder evaluar la red entrenada:
model |> compile(
optimizer = "sgd", # stochastic gradient descent
loss = "sparse_categorical_crossentropy", # función utilizada para problemas de clasificación con varias clases
metrics = "accuracy" # Precisión
)
\index{clasificación}
Una vez generada la estructura de la red neuronal convolucional, ya se puede entrenar, mediante la función fit()
, para resolver el problema de clasificación. Para ello, se le debe indicar el conjunto de imágenes de entrenamiento, x, que debe utilizar y sus etiquetas correspondientes, y. Además de otros parámetros, se podrá configurar el número de épocas (epochs
, pasadas sobre el conjunto completo de entrenamiento), el tamaño del batch que se utilizará en cada iteración (con batch_size
, número de imágenes por iteración), qué porcentaje de elementos del conjunto de datos se utilizarán para validar el modelo (con validation_split
, imágenes utilizadas durante el entrenamiento pero solo para obtener una estimación real del error cometido) o la tasa de aprendizaje (learning_rate
), entre otros.
training_evolution <- model |>
fit(
x = cifar$train$x, y = cifar$train$y,
epochs = 10, batch_size = 32,
validation_split = 0.2,
learning_rate = 0.1,
verbose = 2
)
::: {.infobox data-latex=""} Nota
Como se puede observar, el batch_size
configurado es menor que el del Cap. @ref(capNN) (32
#> Epoch 1/10
#> 1250/1250 - 12s - loss: 2.1097 - accuracy: 0.2316 - val_loss: 1.9339 - val_accuracy: 0.2958 - 12s/epoch - 9ms/step
#> Epoch 2/10
#> 1250/1250 - 8s - loss: 1.7478 - accuracy: 0.3667 - val_loss: 1.6987 - val_accuracy: 0.3965 - 8s/epoch - 6ms/step
#> Epoch 3/10
#> 1250/1250 - 8s - loss: 1.5464 - accuracy: 0.4399 - val_loss: 1.4731 - val_accuracy: 0.4707 - 8s/epoch - 7ms/step
#> Epoch 4/10
#> 1250/1250 - 9s - loss: 1.4304 - accuracy: 0.4866 - val_loss: 1.3653 - val_accuracy: 0.5149 - 9s/epoch - 7ms/step
#> Epoch 5/10
#> 1250/1250 - 8s - loss: 1.3477 - accuracy: 0.5199 - val_loss: 1.3407 - val_accuracy: 0.5257 - 8s/epoch - 6ms/step
#> Epoch 6/10
#> 1250/1250 - 7s - loss: 1.2784 - accuracy: 0.5437 - val_loss: 1.2563 - val_accuracy: 0.5564 - 7s/epoch - 6ms/step
#> Epoch 7/10
#> 1250/1250 - 7s - loss: 1.2118 - accuracy: 0.5705 - val_loss: 1.2331 - val_accuracy: 0.5720 - 7s/epoch - 6ms/step
#> Epoch 8/10
#> 1250/1250 - 8s - loss: 1.1539 - accuracy: 0.5954 - val_loss: 1.1807 - val_accuracy: 0.5882 - 8s/epoch - 6ms/step
#> Epoch 9/10
#> 1250/1250 - 7s - loss: 1.1015 - accuracy: 0.6135 - val_loss: 1.1516 - val_accuracy: 0.5935 - 7s/epoch - 6ms/step
#> Epoch 10/10
#> 1250/1250 - 7s - loss: 1.0526 - accuracy: 0.6286 - val_loss: 1.1014 - val_accuracy: 0.6128 - 7s/epoch - 6ms/step
Tras el entrenamiento, se puede observar la evolución del mismo mediante las gráficas de coste/pérdida y precisión (Fig. @ref(fig:plot-curve3)).
plot(training_evolution)

(\#fig:plot-curve3)Evolución durante el entrenamiento de la precisión y la pérdida: conjuntos de entrenamiento y validación.
Como se puede apreciar, la red entrenada es capaz de alcanzar un 60% de precisión tanto en el conjunto de entrenamiento como en el de validación.
Una vez entrenado el modelo ya se puede aplicarse al conjunto de test. La clasificación de cualquiera de las imágenes en una de las posibles clases se lleva a cabo mediante la función predict()
. En realidad, la salida de la función es la probabilidad de que dicha imagen pertenezca a cada una de las posibles clases, clasificándose en aquella para la cual la probabilidad sea mayor:
predictions <- predict(model, cifar$test$x)
head(round(predictions, digits=2), 5)
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
#> [1,] 0.03 0.00 0.18 0.47 0.01 0.14 0.16 0 0.00 0.00
#> [2,] 0.04 0.07 0.00 0.00 0.00 0.00 0.00 0 0.89 0.00
#> [3,] 0.08 0.28 0.00 0.00 0.00 0.00 0.00 0 0.55 0.07
#> [4,] 0.82 0.01 0.04 0.00 0.00 0.00 0.00 0 0.11 0.00
#> [5,] 0.00 0.00 0.05 0.11 0.11 0.03 0.69 0 0.00 0.00
Con la función evaluate()
se calculan tanto el coste o pérdida como la precisión de la red neuronal sobre el conjunto de test. Como se puede observar, se obtienen valores muy similares a los que se obtuvieron para el conjunto de entrenamiento:
evaluate(model, cifar$test$x, cifar$test$y, verbose = 0)
#> loss accuracy
#> 1.094381 0.611100
Con la función predict también se puede generar la matriz de confusión de la red:
prediction_matrix <- model |> predict(cifar$test$x) |> k_argmax()
confusion_matrix <- table(as.array(prediction_matrix), cifar$test$y)
confusion_matrix
#>
#> 0 1 2 3 4 5 6 7 8 9
#> 0 650 25 57 12 39 12 3 21 82 35
#> 1 43 790 15 18 13 11 16 12 43 179
#> 2 119 20 676 180 325 170 112 94 33 26
#> 3 23 15 57 427 76 184 54 48 18 18
#> 4 1 0 13 20 236 12 5 21 3 2
#> 5 6 7 52 145 41 488 24 82 5 10
#> 6 14 5 71 110 119 42 755 13 4 14
#> 7 7 6 30 46 126 60 16 676 9 18
#> 8 114 58 15 19 20 13 6 5 777 62
#> 9 23 74 14 23 5 8 9 28 26 636
Otros ejemplos para trabajar en R pueden encontrarse en los siguiente enlaces: (i) Transfer learning and fine tuning, explicación de estas técnicas para clasificar imágenes que contienen perros y gatos https://tensorflow.rstudio.com/guides/keras/transfer_learning (ii) https://tensorflow.rstudio.com/guides/ y (iii) https://tensorflow.rstudio.com/examples/
\index{perceptrón!multicapa} \index{red!neuronal!convolucional}
::: {.infobox_resume data-latex=""}
-
Este capítulo presenta las principales características de las redes neuronales convolucionales y sus diferencias con el perceptrón multicapa.
-
Además, se exponen los principales problemas a la hora de diseñar este tipo de redes profundas y se plantean sus posibles soluciones.
-
Finalmente, se exponen los pasos necesarios para entrenar y poner en práctica una red neuronal convolucional en R, con la librería
Tensorflow/Keras
, que clasifique \index{clasificación} las imágenes del conjunto de datosCIFAR10
en 10 posibles clases. :::
Footnotes
-
Nótese que el filtro no puede aplicarse a los bordes de la matriz de entrada. En la Sec. @ref(relleno) se tratan algunas soluciones a este problema. ↩
-
El problema del sobreajuste también surge en las redes neuronales artificiales, si bien es más evidente en las convolucionales por ser más profundas. ↩
-
Aunque hay algunos métodos que ayudan a encontrar la mejor combinación de valores de los hiperparámetros, en la práctica los establece el investigador. ↩