Deep Learning

DNN: Tutorial básico de Keras en R

Autor/a
Afiliación

Francisco Plaza Vega

Ingeniería en Estadística

Bienvenidos a la guía rápida para empezar con keras en R para construir los primeros modelos de Deep Learning, particularmente los modelos de Perceptrón Multicapa (Multi Layer Perceptron, MLP). Keras es una API (Application Program Interface) basada en tensorflow, diseñada para permitir una implementación rápida, fácil y modular con diferentes arquitecturas de Deep Learning.

Características clave de Keras incluyen:

  • Facilidad de uso: Keras tiene una API simple y coherente optimizada para casos de uso comunes, lo que facilita el entendimiento y la utilización, incluso para aquellos que son nuevos en el campo.

  • Modularidad: Keras está estructurada en módulos, lo que permite una configuración y expansión flexibles. Los componentes de Keras como las capas, funciones de activación, optimizadores, entre otros, son independientes y combinables.

  • Extensibilidad: Se puede extender fácilmente con nuevas funcionalidades como nuevos tipos de capas, funciones de pérdida, etc., lo que la hace adaptable a necesidades avanzadas.

  • Compatibilidad: Keras se ejecuta sobre varios frameworks de backend de computación, siendo TensorFlow el más común y recomendado. Esto significa que se puede aprovechar la potencia y flexibilidad de estos frameworks, incluyendo la capacidad de ejecución en GPU para procesamiento de alto rendimiento.

Keras se utiliza para construir y entrenar modelos de Deep Learning de manera eficiente, desde redes simples hasta complejas, con soporte para una amplia gama de arquitecturas de red. Su diseño y facilidad de uso la posiciona como una de las bibliotecas más populares en el ámbito del Deep Learning.

Esta guía está basada en la guía oficial de Keras en R (https://tensorflow.rstudio.com/guides/keras/basics).

1 Importar Keras

library(keras)
Warning: package 'keras' was built under R version 4.3.3

2 Nuestro primer modelo

2.1 Modelo secuencial

El modelo secuencial (keras_model_sequential()) en Keras utiliza una manera intuitiva para construir un modelo de red neuronal. Esencialmente, es un conjunto de capas apilado de manera lineal, es decir, que el modelo se va creando capa por capa de manera secuencial. Este tipo de modelo es muy útil cuando se tiene una cadena simple de capas, donde cada capa tiene exactamente un tensor de entrada y un tensor de salida.

  • Inicialización: Comenzamos inicializando el modelo Sequential. Esto es como preparar un contenedor vacío donde se irán añadiendo capas para nuestro modelo.

  • Añadir Capas: Luego, añadimos las capas al modelo. Keras proporciona una variedad de capas que se pueden añadir, como capas dense (fully connected, i.e. MLP), capas de convolución, capas recurrentes, etc. Cada capa se va apilando sobre la anterior. Usaremos un modelo con dos capas ocultas, la primera y segunda capa oculta del modelo utilizarán una función de activación relu y luego la capa de salida tendrá una activación de tipo softmax.

model <- keras_model_sequential()

model %>%
  # Agrega la primer capa al modelo y establece la forma que tendrá la capa de entrada
  layer_dense(units = 64, activation = 'relu', input_shape = c(32)) %>%

  # Agrega otra capa:
  layer_dense(units = 64, activation = 'relu') %>%

  # Agrega la capa de salida (10 nodos) con la función 'softmax':
  layer_dense(units = 10, activation = 'softmax')

2.2 Configurando las capas

Acá vemos algunas de las opciones que presenta la librería, las opciones consideran la función de activación, el inicializador para el kernel y algunas opciones de regularización:

  • activation: Establece la función de activación para cada una de las capas.

  • kernel_initializer y bias_initializer: Los esquemas de inicialización que darán el punto de partida a los pesos de cada capa (kernel y bias). Por defecto, se utiliza el inicializador uniforme de Glorot.

  • kernel_regularizer y bias_regularizer: Los esquemas de regularización que se aplicarán a los pesos de cada capa (kernel and bias), como regularización en L1 o L2, o ambas. Por defecto no se aplica ningún método de regularización.

Ejemplos

# Creamos una capa con activación sigmoide:
layer_dense(units = 64, activation ='sigmoid')

# Una capa con regularización en L1 para el kernel y un factor de 0.01 
layer_dense(units = 64, kernel_regularizer = regularizer_l1(0.01))

# Una capa con regularización en L2 para el bias y un factor de 0.01 
layer_dense(units = 64, bias_regularizer = regularizer_l2(0.01))

# Una capa con inicialización de pesos en el kernel utilizando una matriz aleatoria ortogonal
layer_dense(units = 64, kernel_initializer = 'orthogonal')

# Una capa inicialización del vector bias en 2.0
layer_dense(units = 64, bias_initializer = initializer_constant(2.0))

3 Entrenamiento y evaluación

3.1 Configurar el entrenamiento

Una vez se han añadido todas las capas necesarias, el siguiente paso es compilar el modelo. Aquí es donde se define la función de pérdida que el modelo tratará de minimizar, el optimizador que determina cómo el modelo actualizará los parámetros durante el entrenamiento, y las métricas de evaluación que queremos observar. Por ejemplo, para un problema de clasificación, se podría usar la función de pérdida de entropía cruzada categórica categorical_crossentropy y el optimizador adam.

model %>% compile(
  optimizer = 'adam',
  loss = 'categorical_crossentropy',
  metrics = list('accuracy')
)

La función compile toma tres argumentos importantes que se deben configurar adecuadamente, dependiendo de si el modelo se utiliza para tareas de clasificación o de regresión:

  • Optimizador: Define cómo se actualiza el modelo durante el entrenamiento. Algunas opciones populares incluyen adam para una amplia gama de aplicaciones, rmsprop recomendado para redes recurrentes, y sgd (descenso de gradiente estocástico) que es útil en contextos específicos y cuando se necesita un control más detallado sobre el entrenamiento.
    • Clasificación y regresión: adam, rmsprop, sgd.
  • Función de pérdida: Especifica el criterio que el modelo intentará minimizar.
    • Clasificación: categorical_crossentropy para clasificación multiclase, binary_crossentropy para clasificación binaria.
    • Regresión: mean_squared_error (mse) para medir el promedio de los errores al cuadrado entre los valores predichos y reales, mean_absolute_error (mae) para la diferencia media absoluta.
  • Métricas: Utilizadas para monitorear y evaluar el rendimiento del modelo durante el entrenamiento.
    • Clasificación: accuracy es comúnmente usada para evaluar la proporción de aciertos.
    • Regresión: mean_squared_error o mse para el error cuadrático medio, mean_absolute_error o mae para el error absoluto medio. pueden ser usadas para evaluar cuán cerca están las predicciones del modelo de los valores reales.

Ejemplos de configuración del modelo:

# Configura un modelo para regresión
model %>% compile(
  optimizer = 'adam',
  loss = 'mse',           # mean squared error
  metrics = list('mae')   # mean absolute error
)

# Configura un modelo para clasificación
model %>% compile(
  optimizer = optimizer_rmsprop(learning_rate = 0.01),
  loss = "categorical_crossentropy",
  metrics = list("categorical_accuracy")
)

3.2 Datos de entrada (Entrenamiento y prueba)

Se pueden entrenar modelos de Keras directamente en matrices y arreglos de R (que posiblemente se hayan creado a partir de data.frames de R). Un modelo se ajusta a los datos de entrenamiento utilizando el método fit. Las variables data y labels son matrices con elementos que siguen una distribución de probabilidad normal estándar.

#Datos de entrenamiento simulados
data <- matrix(rnorm(1000 * 32), nrow = 1000, ncol = 32)
labels <- matrix(rnorm(1000 * 10), nrow = 1000, ncol = 10)

#Datos de prueba simulados
test_data <- matrix(rnorm(300 * 32), nrow = 300, ncol = 32)
test_labels <- matrix(rnorm(300 * 10), nrow = 300, ncol = 10)

model %>% fit(
  data,
  labels,
  epochs = 10,
  batch_size = 32
)
Epoch 1/10
32/32 - 1s - loss: -2.0688e-01 - accuracy: 0.0960 - 675ms/epoch - 21ms/step
Epoch 2/10
32/32 - 0s - loss: -4.4227e-01 - accuracy: 0.1120 - 41ms/epoch - 1ms/step
Epoch 3/10
32/32 - 0s - loss: -6.3738e-01 - accuracy: 0.1200 - 29ms/epoch - 921us/step
Epoch 4/10
32/32 - 0s - loss: -8.5355e-01 - accuracy: 0.1360 - 34ms/epoch - 1ms/step
Epoch 5/10
32/32 - 0s - loss: -1.0190e+00 - accuracy: 0.1400 - 25ms/epoch - 780us/step
Epoch 6/10
32/32 - 0s - loss: -1.2954e+00 - accuracy: 0.1500 - 17ms/epoch - 537us/step
Epoch 7/10
32/32 - 0s - loss: -1.5062e+00 - accuracy: 0.1510 - 30ms/epoch - 932us/step
Epoch 8/10
32/32 - 0s - loss: -1.6185e+00 - accuracy: 0.1530 - 30ms/epoch - 925us/step
Epoch 9/10
32/32 - 0s - loss: -1.8616e+00 - accuracy: 0.1500 - 23ms/epoch - 727us/step
Epoch 10/10
32/32 - 0s - loss: -2.2438e+00 - accuracy: 0.1540 - 20ms/epoch - 624us/step

3.3 Entrenando el modelo

El método fit toma tres argumentos importantes:

  • epochs: El entrenamiento se estructura en épocas. Una época es una iteración sobre todo el conjunto de datos de entrada.

  • batch_size: Cuando se pasan datos en forma de matriz o arreglo, el modelo divide los datos en conjuntos más pequeños e itera sobre estos estos conjuntos durante el entrenamiento. Este entero especifica el tamaño de cada lote. Es importante tener en cuenta que el último lote puede ser más pequeño si el número total de muestras no es divisible por el tamaño del lote.

  • validation_data: Al prototipar un modelo, es útil monitorear fácilmente su rendimiento en algunos datos de validación. Pasar este argumento —una lista de entradas y etiquetas— permite al modelo mostrar la pérdida y métricas en modo de inferencia para los datos pasados, al final de cada época.

Aquí se presenta un ejemplo utilizando validation_data, donde creamos un set de datos adicional. Otra opción es utilizar validation_split(), donde extraerá un subconjunto de datos del entrenamiento (tanto en los inputs X como en las etiquetas Y) para efectuar la validación:

data <- matrix(rnorm(1000 * 32), nrow = 1000, ncol = 32)
labels <- matrix(rnorm(1000 * 10), nrow = 1000, ncol = 10)

val_data <- matrix(rnorm(100 * 32), nrow = 100, ncol = 32)
val_labels <- matrix(rnorm(100 * 10), nrow = 100, ncol = 10)

model %>% fit(
  data,
  labels,
  epochs = 10,
  batch_size = 32,
  validation_data = list(val_data, val_labels)
)
Epoch 1/10
32/32 - 0s - loss: 0.2927 - accuracy: 0.0930 - val_loss: -1.9791e+00 - val_accuracy: 0.1100 - 256ms/epoch - 8ms/step
Epoch 2/10
32/32 - 0s - loss: 0.0823 - accuracy: 0.1010 - val_loss: -1.4662e+00 - val_accuracy: 0.1000 - 54ms/epoch - 2ms/step
Epoch 3/10
32/32 - 0s - loss: -6.3662e-01 - accuracy: 0.0870 - val_loss: -1.1032e+00 - val_accuracy: 0.1300 - 46ms/epoch - 1ms/step
Epoch 4/10
32/32 - 0s - loss: -3.4476e-01 - accuracy: 0.1080 - val_loss: -1.4138e+00 - val_accuracy: 0.0900 - 61ms/epoch - 2ms/step
Epoch 5/10
32/32 - 0s - loss: -7.1458e-01 - accuracy: 0.1000 - val_loss: -2.6395e+00 - val_accuracy: 0.1300 - 42ms/epoch - 1ms/step
Epoch 6/10
32/32 - 0s - loss: -8.3069e-01 - accuracy: 0.1020 - val_loss: -1.3383e+00 - val_accuracy: 0.1500 - 50ms/epoch - 2ms/step
Epoch 7/10
32/32 - 0s - loss: -1.7367e+00 - accuracy: 0.1090 - val_loss: -7.6512e-01 - val_accuracy: 0.1000 - 40ms/epoch - 1ms/step
Epoch 8/10
32/32 - 0s - loss: -1.4613e+00 - accuracy: 0.1020 - val_loss: -1.9789e+00 - val_accuracy: 0.1400 - 50ms/epoch - 2ms/step
Epoch 9/10
32/32 - 0s - loss: -1.1939e+00 - accuracy: 0.1180 - val_loss: -1.7426e+00 - val_accuracy: 0.0900 - 44ms/epoch - 1ms/step
Epoch 10/10
32/32 - 0s - loss: -1.5717e+00 - accuracy: 0.1080 - val_loss: -1.4803e+00 - val_accuracy: 0.1000 - 47ms/epoch - 1ms/step

3.4 Evaluación del modelo

Para evaluar la función de pérdida y las métricas establecidas usamos evaluate. Se puede utilizar tanto para el conjunto de entrenamiento como para el de prueba:

model %>% evaluate(test_data, test_labels, batch_size = 32)
10/10 - 0s - loss: -4.5548e-01 - accuracy: 0.1300 - 15ms/epoch - 2ms/step
      loss   accuracy 
-0.4554779  0.1300000 

3.5 Predicción de observaciones

Y para predecir la salida de la última capa en el conjunto de prueba (pero también se puede hacer para el entrenamiento):

model %>% predict(test_data, batch_size = 32) %>% head()
10/10 - 0s - 65ms/epoch - 7ms/step
            [,1]        [,2]       [,3]        [,4]       [,5]        [,6]
[1,] 0.195472911 0.020692170 0.11873411 0.090526842 0.04747483 0.063484013
[2,] 0.246287227 0.066558935 0.11277572 0.012254063 0.12813528 0.072297566
[3,] 0.057932884 0.003986907 0.09824687 0.002683881 0.77617496 0.002414052
[4,] 0.007556915 0.027510762 0.49224505 0.003122625 0.23085147 0.077297240
[5,] 0.037863251 0.012916965 0.20119390 0.041215729 0.20616631 0.122328557
[6,] 0.028843455 0.013159634 0.01308275 0.001210736 0.26774430 0.101673916
           [,7]        [,8]         [,9]       [,10]
[1,] 0.12658481 0.046494108 4.522344e-02 0.245312676
[2,] 0.09047966 0.005971425 4.420675e-02 0.221033350
[3,] 0.04951634 0.000399508 1.568512e-04 0.008487809
[4,] 0.03028112 0.009660723 9.086001e-05 0.121383168
[5,] 0.31157717 0.056666274 1.159394e-03 0.008912438
[6,] 0.03464352 0.018494736 1.734982e-02 0.503797114

4 Extras

4.1 Callbacks

Un callback es un objeto que se pasa a un modelo para personalizar y extender su comportamiento durante el entrenamiento. Se puede escribir un callback personalizado o utilizar los callbacks integrados en Keras (más detalles en este enlace de R-CRAN, que incluyen:

  • callback_model_checkpoint: Guarda puntos de control del modelo a intervalos regulares.
  • callback_learning_rate_scheduler: Cambia dinámicamente la tasa de aprendizaje.
  • callback_early_stopping: Interrumpe el entrenamiento cuando el rendimiento en la validación ha dejado de mejorar.

Para usar un callback, se añade en la función fit:

callbacks <- list(
  callback_early_stopping(patience = 2, monitor = 'val_loss'),
  callback_tensorboard(log_dir = './logs')
)

model %>% fit(
  data,
  labels,
  batch_size = 32,
  epochs = 5,
  callbacks = callbacks,
  validation_data = list(val_data, val_labels)
)

4.2 Guardar y restaurar modelos

Se pueden guardar y cargar los pesos asociados a algún modelo

# Guardar los pesos del modelo
model %>% save_model_weights_tf('ruta_al_archivo/')

# Cargar los pesos del modelo
model %>% load_model_weights_tf('ruta_al_archivo/')

También se puede guardar y cargar un modelo completo, sin necesidad de ejecutar el código nuevamente.

# Guardar modelo completo, en formato SavedModel
model %>% save_model_tf('ruta_al_archivo/')

# Cargar modelo completo, incluidos pesos y optimizador en formato SavedModel
model <- load_model_tf('ruta_al_archivo/')

Espero esta guía sea de utilidad para poder comenzar a implementar los primeros modelos de Deep Learning.