Este tutorial documenta cómo usar TensorFlow en gráficas Nvidia con la librería keras desde RStudio y en Windows 10.

En mi caso uso R version 4.0.2 (2020-06-22) y RStudio versión 1.3.959

La motivación de este tutorial es que no he encontrado tutoriales actualizados sobre cómo entrenar redes neuronales en gráficas Nvidia desde Windows, y me costó tiempo conseguirlo. Así que difruta la fruta.

1 Instalar Keras y TensorFlow

Para comenzar, necesitas instalar la librería keras:

install.packages("keras")
keras::install_keras()

Luego instala la librería tensorflow:

install.packages("tensorflow")
tensorflow::install_tensorflow()

A fecha de la redacción de este tutorial, te instalará TensorFlow V2.2. Puedes comprobar tu versión de tensorflow instalada con:

tensorflow::tf_version()
## [1] '2.2'

La versión de TensorFlow es importante, ya que ella determina qué versión de CUDA debes instalar. TensorFlow versión 2.1 (y versiones posteriores) requiere de CUDA 10.1. Digo esto porque existe CUDA versión 11.1, pero si la instalas, no te va a funcionar.

1.1 CUDA

Para instalar CUDA, vete a esta página y descárgate el CUDA Toolkit 10.1 Update2, ya que es la versión más actualizada de CUDA 10.1 . Lo instalas y au. Fíjate en la carpeta donde se instalará, pues ahí tienes que poner los archivos .dll de cuDNN.


1.2 cuDNN

Si CUDA te sirve para hacer computación en tarjetas gráficas de Nvidia, la librería cuDNN te sirve para entrenar redes neuronales en dicho hardware.

Con que te descargues una versión de CuDNN mayor o igual a 7.4.1 es suficiente. En mi caso, uso cuDNN v8.0.4

Para descargarte cuDNN, vete a esta página y dale a Download cuDNN. Te pedirá que te registres en el programa de desarrolladores de Nvidia. Lo haces y te descargas cuDNN >= v7.4.1

Al descargarte cuDNN, te vendrán 3 carpetas: bin, lib e include. Cópialas y pégalas sobre las carpetas homónimas de CUDA.

Finalmente, añade esas 3 carpetas a la variable PATH. Puedes hacerlo fácilmente desde configuración avanzada del sistema-> Variables de entorno.


2 Al lío

2.1 Preprocesado

Vamos a entrenar una red para que nos clasifique imágenes a color. El dataset a usar es cifar10 (el 10 hace referencia a que hay 10 tipos de imágenes: camión, coche, moto… etc).

Los tipos de imagenes son:

  • “airplane” (avión)

  • “automobile” (coche)

  • “bird” (pájaro)

  • “cat” (gato)

  • “deer” (ciervo)

  • “dog” (perro)

  • “frog” (rana)

  • “horse” (caballo)

  • “ship” (barco)

  • “truck” (camión)

Primero cargamos keras:

library(keras)

Ahora cargamos el dataset CIFAR10:

dataset <- dataset_cifar10()

Pasamos las clases a codificación one-hot con to_categorical():

dataset$train$y <- to_categorical(dataset$train$y, num_classes = 10)
dataset$test$y <- to_categorical(dataset$test$y, num_classes = 10)

Le ponemos bien los nombres de las clases a las fotos:

class_names <- c("airplane", "automobile", "bird", "cat", "deer","dog","frog","horse","ship", "truck")

colnames(dataset$train$y) <- class_names
colnames(dataset$test$y) <- class_names

Como hemos puesto los nombres a las clases durante la codificación one-hot, ahora podemos saber qué tipo de foto estamos seleccionando:

which(dataset$train$y[1,] == 1)
## frog 
##    7

Se ve que la foto nº1 del set de entrenamiento es la de una rana


Ahora normalizamos la profundidad de color de los píxeles al dividirlos por su valor máximo (el color en estas fotos está codificado en 8 bits=256 valores, incluyendo el 0, luego el valor máximo será 255). Con esto normalizamos los píxeles del rango [0,255] al rango [0,1]

dataset$train$x <- dataset$train$x/255
dataset$test$x <- dataset$test$x/255

2.2 Definimos la arquitectura de la red neuronal a usar

Definimos el modelo con keras_model_sequential() y los comandos de la familia layer_(). Usaremos activación “softmax” para la capa de salida; y activación “relu” para el resto:

modelo = keras_model_sequential()

modelo %>%
  # La capa de entrada y la 1ª oculta son de tipo convolucional 2D 
  layer_conv_2d(filters = 32, kernel_size = c(3,3), padding = "same", input_shape = c(32, 32, 3), activation = "relu" ) %>%
  # 2ª capa oculta
  layer_conv_2d(filters  = 32, kernel_size = c(3,3), activation = "relu")%>%
  # Usamos max pooling para comprimir la información que maneja la red neuronal
  layer_max_pooling_2d(pool_size = c(2,2)) %>%
  layer_dropout(0.25) %>%
  # 3ª capa oculta
  layer_conv_2d(filters = 32, kernel_size = c(3,3), padding = "same", activation = "relu") %>%
  # 4ª capa oculta
  layer_conv_2d(filters = 32, kernel_size = c(3,3), activation = "relu") %>%
  # Max pooling otra vez y dropout
  layer_max_pooling_2d(pool_size = c(2,2)) %>%
  layer_dropout(0.25) %>%
  # Pasamos la información de 2D a 1D para pasársela a una capa fully connected
  layer_flatten() %>%
  # 5ª capa oculta, esta vez de tipo fully connected y con dropout
  layer_dense(units = 512, activation = "relu")%>%
  layer_dropout(0.5) %>%
  # Capa de salida
  layer_dense(units = 10, activation = "softmax")

2.3 Compilación de la red neuronal

Compilamos el modelo con compile(). En este paso definimos la loss function, el optimizer y métricas a calcular:

Recuerda que la loss function es cómo se calcula el error cometido por la red neuronal al clasificar las imágenes.

El optimizador es el algoritmo que busca el mínimo error global en el espacio de error (es decir, el tipo de backpropagation)

modelo %>% compile(
  loss = "categorical_crossentropy",
  optimizer = optimizer_rmsprop(lr = 0.0001, decay = 1e-6),
  metrics = "accuracy" )

summary(modelo)
## Model: "sequential"
## ________________________________________________________________________________
## Layer (type)                        Output Shape                    Param #     
## ================================================================================
## conv2d (Conv2D)                     (None, 32, 32, 32)              896         
## ________________________________________________________________________________
## conv2d_1 (Conv2D)                   (None, 30, 30, 32)              9248        
## ________________________________________________________________________________
## max_pooling2d (MaxPooling2D)        (None, 15, 15, 32)              0           
## ________________________________________________________________________________
## dropout (Dropout)                   (None, 15, 15, 32)              0           
## ________________________________________________________________________________
## conv2d_2 (Conv2D)                   (None, 15, 15, 32)              9248        
## ________________________________________________________________________________
## conv2d_3 (Conv2D)                   (None, 13, 13, 32)              9248        
## ________________________________________________________________________________
## max_pooling2d_1 (MaxPooling2D)      (None, 6, 6, 32)                0           
## ________________________________________________________________________________
## dropout_1 (Dropout)                 (None, 6, 6, 32)                0           
## ________________________________________________________________________________
## flatten (Flatten)                   (None, 1152)                    0           
## ________________________________________________________________________________
## dense (Dense)                       (None, 512)                     590336      
## ________________________________________________________________________________
## dropout_2 (Dropout)                 (None, 512)                     0           
## ________________________________________________________________________________
## dense_1 (Dense)                     (None, 10)                      5130        
## ================================================================================
## Total params: 624,106
## Trainable params: 624,106
## Non-trainable params: 0
## ________________________________________________________________________________


2.4 Entrenmiento del modelo en la GPU

Si todo ha salido bien, deberíamos poder entrenar la red neuronal en nuestra gráfica.

En mi caso, mi estación de trabajo tardó 1 hora en entrenar esta red, ya que para ello usaba su procesador (Ryzen 9 3900X, 12 núcleos/24 hilos). Por otro lado, mi portátil es capaz de entrenar la red neuronal en unos 13 minutos en su GTX 1050 Ti (una tarjeta de gama baja).

set.seed(1)
start.time = Sys.time()

historial_entrenamiento_red_neuronal <- modelo %>% 
  fit(x = dataset$train$x, 
      y = dataset$train$y,
      batch_size = 32,
      epochs = 60,
      validation_data = list(dataset$test$x, dataset$test$y),
      shuffle = TRUE )

end.time = Sys.time() # Medimos tiempo que tarda en entrenarse la red


¿Cuánto has dicho que tardaba la red neuronal en entrenarse?

duracion_entrenamiento <- end.time-start.time
cat("El entrenamiento de la red neuronal ha tardado", duracion_entrenamiento, "minutos")
## El entrenamiento de la red neuronal ha tardado 12.67227 minutos

Ah, gracias.


2.5 Rendimiento del modelo

Podemos ver el rendimiento del modelo en el set de entrenamiento y en el set de test:

plot(historial_entrenamiento_red_neuronal)


Aunque el gráfico haga referencia al set de validación, en este caso el set de validación es el set de test (fíjate en el parámetro validation_data del comando fit())


precision <- historial_entrenamiento_red_neuronal$metrics$val_accuracy[60]*100

precision <- format(round(precision,2), nsmall = 2)

print(paste0("la red puede clasificar imágenes a color con una precisión en el set de test del ", precision, "%"))
## [1] "la red puede clasificar imágenes a color con una precisión en el set de test del 75.93%"


3 Probar la red

Con el comando predict() podemos usar la red para clasificar una imagen cualquiera:

set.seed(1)
prediccion_imagenes_test <- predict(modelo, dataset$test$x)
colnames(prediccion_imagenes_test) <- class_names

Vamos a ver la imagen de test nº22

barplot(prediccion_imagenes_test[22,], ylim = c(0,1)) 

La red neuronal predice la imagen de test nº22 como un avión. Comprobamos si es verdad visualizando la imagen con la librería EBImage, Nótese que transponemos la imagen con transpose() para que se vea bien (de lo contrario, nos sale girada 90º a la izquierda y volteada):

library(EBImage)

imagen_test__22 <- Image(data = transpose(dataset$test$x[22,,,]), 
                     dim = c(32,32,3), 
                     colormode = "Color")
plot(imagen_test__22)

A pesar del tamaño tan pequeño de la imagen, diría que es un F-117 Nighthawk


Vamos a probar con la imagen nº432:

barplot(prediccion_imagenes_test[432,], ylim = c(0,1)) 

Podría ser un perro o un gato.

imagen_test__432 <- Image(data = transpose(dataset$test$x[432,,,]), 
                     dim = c(32,32,3), 
                     colormode = "Color")
plot(imagen_test__432)

¡¡Es un perrete!!