rm(list = ls())
options(warn = -1)
# Función que carga e instala en caso de no estarlo la librería de keras
carga_librerias <- function()
{
  if (!suppressWarnings(suppressMessages(require(keras))))
  {
    install.packages("keras")
    library(keras)
  }
  if (!suppressWarnings(suppressMessages(require(imager))))
  {
    install.packages("imager")
    library(imager)
  }
  if (!suppressWarnings(suppressMessages(require(tidyverse))))
  {
    install.packages("tidyverse")
    library(tidyverse)
  }
  if (!suppressWarnings(suppressMessages(require(caret))))
  {
    install.packages("caret")
    library(caret)
  }
  if (!suppressWarnings(suppressMessages(require(yardstick ))))
  {
    install.packages("yardstick ")
    library(yardstick )
  }
  if (!suppressWarnings(suppressMessages(require(kableExtra ))))
  {
    install.packages("kableExtra ")
    library(kableExtra )
  }
  if (!suppressWarnings(suppressMessages(require(formattable ))))
  {
    install.packages("formattable ")
    library(formattable )
  }
}

EJERCICIO 2: Clasificación imágenes Fashion-Mnist

La base de datos que vamos a utilizar es Fashion-Mnist, que es un conjunto de imágenes de 28x28 pixeles, en escala de grises, que representan a 10 artículos de la tienda Zalando. https://github.com/zalandoresearch/fashion-mnist

Para acceder a la base de datos podemos usar la función de Keras en R llamada dataset_fashion_mnist() que se encarga de descargar los datos. Esta función nos devolverá 4 objetos que representarán las imágenes de entrenamiento, las etiquetas de entrenamiento (clase a la que corresponde), imágenes de test y las etiquetas de test.

Las imágenes de entrenamiento y test son objetos que corresponden a un array de números decimales de dimensiones 60.000x28x28 y 10.000x28x28 respectivamente. Y las etiquetas de entrenamiento y test corresponden a un array de números enteros de dimensiones 60.000 y 10.000 respectivamente.

Tendremos 60.000 imágenes en entrenamiento, con 6.000 para cada clase y 10.000 imágenes de test, con 1.000 para cada clase.

  1. Construir y entrenar 2 modelos con las siguientes características:

Modelo 1: Arquitectura de red Densamente Conectada de clasificación formada por: • 2 capas ocultas densamente conectadas, de 256 y 32 neuronas respectivamente • Se usará como Función de activación de capas ocultas relu • Capa de salida con el número de neuronas y función de activación adecuadas • Se usará como Optimizador rmsprop • Elige la función de pérdida y la métrica adecuadas al problema

Modelo 2: Arquitectura de Red Convolucional de clasificación formada por: • 3 capas de convolución, con 32,64 y 128 filtros respectivamente, de tamaño 3x3, el padding que no modifique el tamaño (mantenga el mismo) y activación relu • Cada capa de convolución seguida de capa de pooling de tipo maxpooling de tamaño 2x2. • Capa de aplanado • Capa de dropout con un % del 0.3 • Capa densamente conectada de 512 neuronas y activación relu • Capa de salida con el número de neuronas y función de activación adecuadas • Elige la función de pérdida y la métrica adecuadas al problema

  1. Con el mejor modelo que seleccionemos, usar las imágenes de test, obteniendo sus predicciones y mostrando la matriz de confusión.

  2. Visualizar 25 imágenes aleatorias de todo el test, mostrando su predicción junto con la imagen.

1. Conjunto de datos

library(keras)
datos<-dataset_fashion_mnist()
names(datos)
## [1] "train" "test"

Al cargar el set de datos “fashion_mnist” recibimos cuatro listas, tanto las imágenes (train\(x y test\)x) como las etiquetas (train\(y y test\)y) de entrenamiento y test. Con esta orden separamos los datos referidos al entrenamiento y los datos referidos al test, guardándolos en un nuevo “array” que une los valores almacenados en x e y, bajo el nombre train_images y train_labels para el entrenamiento y test_images y test_labels para el test, haciéndolo así más intuitivo y fácil de localizar.

train_images<-datos$train$x
train_labels<-datos$train$y
test_images<-datos$test$x
test_labels<-datos$test$y

Vemos las dimensiones que tiene cada conjunto de datos:

dim(train_images)
## [1] 60000    28    28

60.000 imágenes de 28x28.

dim(test_images)
## [1] 10000    28    28

10.000 imágenes de 28x28.

train_labels[1:20]
##  [1] 9 0 0 3 0 2 7 2 5 5 0 9 5 5 7 9 1 0 6 4

Creamos un vector donde almacenamos el nombre de cada tipo de ropa.

class_names<- c("Camiseta", "Pantalón", "Jersey", "Vestido", "Abrigo",
                 
                
"Sandalia", "Camisa", "Zapatilla de deporte", "Bolso", "Bota")

2. Preprocesado de los datos

Antes de trabajar con nuestra red, inspeccionamos una imagen cualquiera del conjunto de datos de entrenamiento. Establecemos una semilla para obtener una penda mediante un muestreo entre las 60.000 imágenes de entrenamiento con la orden sample y la representamos con una escala desde el 0 para el negro hasta el 255 para el blanco.

library(tidyr)
library(ggplot2)

image_1 <- as.data.frame(train_images[1, , ])
colnames(image_1) <- seq_len(ncol(image_1))
image_1$y <- seq_len(nrow(image_1))
image_1 <- gather(image_1, "x", "value", -y)
image_1$x <- as.integer(image_1$x)

ggplot(image_1, aes(x = x, y = y, fill = value)) +
  geom_tile() +
  scale_fill_gradient(low = "white", high = "black", na.value = NA) +
  scale_y_reverse() +
  theme_minimal() +
  theme(panel.grid = element_blank())   +
  theme(aspect.ratio = 1) +
  xlab("") +
  ylab("")

Escalamos estos valores a un rango de 0 a 1 antes de alimentarlos al modelo de red neuronal. Para ello, simplemente dividimos por 255.

Es importante que el conjunto de entrenamiento y el conjunto de prueba se preprocesen de la misma manera:

train_images <- train_images / 255
test_images <- test_images / 255

Muestre las primeras 25 imágenes del conjunto de entrenamiento y muestre el nombre de la clase debajo de cada imagen. Verifique que los datos estén en el formato correcto y estemos listos para construir y entrenar la red.

par(mfcol=c(5,5))
par(mar=c(0, 0, 1.5, 0), xaxs='i', yaxs='i')
for (i in 1:25) {
  img <- train_images[i, , ]
  img <- t(apply(img, 2, rev))
  image(1:28, 1:28, img, col = gray((0:255)/255), xaxt = 'n', yaxt = 'n',
        main = paste(class_names[train_labels[i] + 1]))
}

3. MODELO 1

Arquitectura de red Densamente Conectada de clasificación formada por: * 2 capas ocultas densamente conectadas, de 256 y 32 neuronas respectivamente * Se usará como Función de activación de capas ocultas relu * Capa de salida con el número de neuronas y función de activación adecuadas * Se usará como Optimizador rmsprop * Elige la función de pérdida y la métrica adecuadas al problema

3.1. Construir el modelo

El componente básico de una red neuronal es la capa. Las capas extraen representaciones de los datos que se introducen en ellas. La mayor parte del aprendizaje profundo consiste en encadenar capas simples. La mayoría de las capas tienen parámetros que se aprenden durante el entrenamiento. La construcción de la red neuronal requiere configurar las capas del modelo y luego compilar el modelo.

model <- keras_model_sequential()
model %>%
  layer_flatten(input_shape = c(28, 28)) %>%
  layer_dense(units = 256, activation = 'relu') %>%
  layer_dense(units = 32, activation = 'relu') %>%
  layer_dense(units = 10, activation = 'softmax')
# Visualizar el modelo definido
summary(model)
## Model: "sequential"
## ________________________________________________________________________________
##  Layer (type)                       Output Shape                    Param #     
## ================================================================================
##  flatten (Flatten)                  (None, 784)                     0           
##  dense_2 (Dense)                    (None, 256)                     200960      
##  dense_1 (Dense)                    (None, 32)                      8224        
##  dense (Dense)                      (None, 10)                      330         
## ================================================================================
## Total params: 209,514
## Trainable params: 209,514
## Non-trainable params: 0
## ________________________________________________________________________________

La primera capa de esta red, layer_flatten transforma el formato de las imágenes de una matriz 2D (de 28 por 28 píxeles) a una matriz 1D de 28 * 28 = 784 píxeles.

3.2. Compilar modelo

Antes de que el modelo esté listo para el entrenamiento, necesita algunos ajustes más. Estos se agregan durante el paso de compilación del modelo:

  • Función de pérdida: mide la precisión del modelo durante el entrenamiento. Queremos minimizar esta función para “dirigir” el modelo en la dirección correcta.
  • Optimizador: así es como se actualiza el modelo en función de los datos que ve y su función de pérdida.
  • Métricas: se utilizan para monitorear los pasos de entrenamiento y prueba. Usamos para ello la precisión, es decir, la fracción de imágenes que se clasifican correctamente.
model %>% compile(
  optimizer = optimizer_rmsprop(),
  loss = 'sparse_categorical_crossentropy',
  metrics = c('accuracy')
)

3.3. Entrenar modelo

Para entrenar el modelo de red neuronal hacemos lo siguiente:

  • Introducimos el conjunto de datos de entrenamiento, preprocesado previamente, tanto los vectores de imágenes como los vectores de etiquetas.
  • El modelo aprende a asociar las etiquetas a las imágenes.
  • Establecemos primeramente 10 periodos de entrenamiento sobre los 60.000 datos de entrenamiento, mostrando para cada etapa la función de pérdida, la precisión y el tiempo que invierte la muestra en cada etapa.
fit<- model %>% fit(
 train_images, train_labels, 
 epochs = 10, batch_size = 128,
 validation_split = 0.2
)
plot(fit)

Vemos que la precisión del conjunto de entrenamiento es del 88%.

Al entrenar un modelo de machine learning, una de las principales cosas que desea evitar sería el overfitting (sobreajuste). El overfitting aparece cuando el modelo se ajusta bien a los datos de entrenamiento, pero no puede generalizar y hacer predicciones precisas de datos que no ha visto antes.

Las métricas del conjunto de entrenamiento nos permitem ver cómo progresa nuestro modelo en términos del entrenamiento, pero son las métricas del conjunto de validación las que nos permitiran obtener una medida de la calidad del modelo: qué tan bien es capaz de hacer nuevas predicciones basadas en datos que no ha visto antes.

Un gráfico de curvas de aprendizaje muestra sobreajuste si:

  • El evolutivo de la training loss continúa disminuyendo con la experiencia.
  • El evolutivo de la training loss disminuye hasta llegar a un punto de inflexión y comienza a aumentar nuevamente.

La pérdida del modelo casi siempre será menor en el conjunto de datos de entrenamiento que en el conjunto de datos de validación. Esto significa que debemos esperar cierta brecha entre ambas curvas, esta brecha se conoce como la brecha de generalización.

Sabiendo todo esto, podemos decir que nuestro modelo en general no presenta overfitting (o muy poquito) y, por este motivo, nuestras curvas de aprendizaje nos dan a entender que nuestro modelo tiene un buen ajuste. En otras palabas:

  • El evolutivo de la training loss disminuye hasta llegar a un punto de estabilidad.
  • El evolutivo de la training loss disminuye hasta llegar a un punto de estabilidad y presenta una pequeña brecha respecto la training loss.

3.4. Evaluar la precisión del modelo

A continuación, compare el rendimiento del modelo en el conjunto de datos de prueba:

score <- model %>% evaluate(test_images, test_labels, verbose = 0)

cat('Test loss:', score["loss"], "\n")
## Test loss: 0.359294
cat('Test accuracy:', score["accuracy"], "\n")
## Test accuracy: 0.8774

Resulta que la precisión del conjunto de datos de prueba 88% es igual a la precisión del conjunto de datos de entrenamiento. Así que parece que no ha habido sobreajuste.

4. MODELO 2

Arquitectura de Red Convolucional de clasificación formada por: * 3 capas de convolución, con 32,64 y 128 filtros respectivamente, de tamaño 3x3, el padding que no modifique el tamaño (mantenga el mismo) y activación relu * Cada capa de convolución seguida de capa de pooling de tipo maxpooling de tamaño 2x2. * Capa de aplanado * Capa de dropout con un % del 0.3 * Capa densamente conectada de 512 neuronas y activación relu * Capa de salida con el número de neuronas y función de activación adecuadas * Elige la función de pérdida y la métrica adecuadas al problema

4.1. Construir el modelo

model2 <- keras_model_sequential() %>%
layer_conv_2d(filters = 32, kernel_size = c(3,3),padding = "same", activation = "relu",
              input_shape = c(28,28,1)) %>%
layer_max_pooling_2d(pool_size = c(2,2)) %>%
layer_conv_2d(filters = 64, kernel_size = c(3,3),padding = "same", activation = "relu") %>%
layer_max_pooling_2d(pool_size = c(2,2)) %>%
layer_conv_2d(filters = 128, kernel_size = c(3,3),padding = "same", activation = "relu")%>%
layer_max_pooling_2d(pool_size = c(2,2)) %>%
layer_flatten() %>%
layer_dropout(0.3) %>%
layer_dense(units = 512, activation = 'relu') %>% 
layer_dense(units = 10, activation = 'softmax') 

summary(model2)
## Model: "sequential_1"
## ________________________________________________________________________________
##  Layer (type)                       Output Shape                    Param #     
## ================================================================================
##  conv2d_2 (Conv2D)                  (None, 28, 28, 32)              320         
##  max_pooling2d_2 (MaxPooling2D)     (None, 14, 14, 32)              0           
##  conv2d_1 (Conv2D)                  (None, 14, 14, 64)              18496       
##  max_pooling2d_1 (MaxPooling2D)     (None, 7, 7, 64)                0           
##  conv2d (Conv2D)                    (None, 7, 7, 128)               73856       
##  max_pooling2d (MaxPooling2D)       (None, 3, 3, 128)               0           
##  flatten_1 (Flatten)                (None, 1152)                    0           
##  dropout (Dropout)                  (None, 1152)                    0           
##  dense_4 (Dense)                    (None, 512)                     590336      
##  dense_3 (Dense)                    (None, 10)                      5130        
## ================================================================================
## Total params: 688,138
## Trainable params: 688,138
## Non-trainable params: 0
## ________________________________________________________________________________

4.2. Compilar el modelo

Antes de que el modelo esté listo para el entrenamiento, necesita algunos ajustes más. Estos se agregan durante el paso de compilación del modelo:

  • Función de pérdida: mide la precisión del modelo durante el entrenamiento. Queremos minimizar esta función para “dirigir” el modelo en la dirección correcta.
  • Optimizador: así es como se actualiza el modelo en función de los datos que ve y su función de pérdida.
  • Métricas: se utilizan para monitorear los pasos de entrenamiento y prueba. Usamos para ello la precisión, es decir, la fracción de imágenes que se clasifican correctamente.
model2 %>% compile(
  optimizer = "adam",
  loss = 'sparse_categorical_crossentropy',
  metrics = c('accuracy')
)

4.3. Entrenar el modelo

Para entrenar el modelo de red neuronal hacemos lo siguiente:

  • Introducimos el conjunto de datos de entrenamiento, preprocesado previamente, tanto los vectores de imágenes como los vectores de etiquetas.
  • El modelo aprende a asociar las etiquetas a las imágenes.
  • Establecemos primeramente 10 periodos de entrenamiento sobre los 60.000 datos de entrenamiento, mostrando para cada etapa la función de pérdida, la precisión y el tiempo que invierte la muestra en cada etapa.
fit2<- model2 %>% fit(
 train_images, train_labels, 
 epochs = 10, batch_size = 128,
 validation_split = 0.2
)
plot(fit2)

Vemos que la precisión del conjunto de entrenamiento es del 92,7%.

Y al igual que en el modelo1, y con todo lo explicado anteriormente, este se trata también de un buen modelo. De hecho, mejora al anterior.

4.4 Evaluar la precisión del modelo

A continuación, compare el rendimiento del modelo en el conjunto de datos de prueba:

score2 <- model2 %>% evaluate(test_images, test_labels, verbose = 0)

cat('Test loss:', score2["loss"], "\n")
## Test loss: 0.2269521
cat('Test accuracy:', score2["accuracy"], "\n")
## Test accuracy: 0.9192

Vemos que la precisión del conjunto de test es del 91,9% muy parecido al del entrenamiento. Así que, no parece que haya mucho sobreajuste.

VEMOS QUE DE LOS DOS MODELOS QUE HEMOS PLANTEADO, EL QUE ES MÁS PRECISO ES EL SEGUNDO MODELO CON UNA PRECISIÓN EN EL CONJUNTO DE TEST DE UN 91,9% FRENTE AL PRIMER MODELO CON UN 88%. NO OBSTANTE, APLICAREMOS LAS PREDICCIONES AL MODELO2.

5. Predicciones

  • Hacer predicciones Con el modelo entrenado, podemos usarlo para hacer predicciones sobre algunas imágenes.
predictions2 <- model2 %>% predict(test_images)

Aquí, el modelo ha predicho la etiqueta para cada imagen en el conjunto de prueba. Echemos un vistazo a la primera predicción:

predictions2[1, ]
##  [1] 7.172861e-06 3.252408e-08 1.655814e-06 4.117739e-08 2.488207e-06
##  [6] 1.184453e-04 5.349980e-06 4.301413e-03 4.816527e-06 9.955585e-01

Una predicción es un vector de 10 números que miden la fiabilidad del modelo de que la imagen en cuestión pertenezca a uno de los 10 artículos de ropa. Vemos que el valor mayor es para el artículo [10] con un valor de 9.999062e-01, valor mayor incluso que en el modelo1, y perteneciente a la BOTA.

which.max(predictions2[1, ])
## [1] 10

Como las etiquetas están basadas en 0, esto en realidad significa una etiqueta prevista de 9 (que se encuentra en class_names[9]). Entonces la modelo está más segura de que esta imagen es un botín. Y podemos comprobar la etiqueta de prueba para ver que esto es correcto:

test_labels[1]
## [1] 9

Correspondiente a una BOTA, como ya habíamos visto.

Tracemos varias imágenes con sus predicciones. Las etiquetas de predicción correcta son verdes y las etiquetas de predicción incorrecta son rojas.

par(mfcol=c(5,5))
par(mar=c(0, 0, 1.5, 0), xaxs='i', yaxs='i')
for (i in 1:25) {
  img <- test_images[i, , ]
  img <- t(apply(img, 2, rev))
  # subtract 1 as labels go from 0 to 9
  predicted_label <- which.max(predictions2[i, ]) - 1
  true_label <- test_labels[i]
  if (predicted_label == true_label) {
    color <- '#008800'
  } else {
    color <- '#bb0000'
  }
  image(1:28, 1:28, img, col = gray((0:255)/255), xaxt = 'n', yaxt = 'n',
        main = paste0(class_names[predicted_label + 1], " (",
                      class_names[true_label + 1], ")"),
        col.main = color)
}

Vemos que el modelo es muy bueno, de 25 prendas de ropa, solamente ha fallado en 2.

5.1. Matriz de Confusión

Una buena herramienta para visualizar sobre qué objetos hemos predicho mal, es la matriz de confusión (o clasificación). Esta matriz muestra cómo ha clasificado el modelo cada conjunto de prendas.

library(caret)
## Loading required package: lattice
# clases predichas
clases_pred<- apply(predictions2, 1, which.max) - 1

# Crear un objeto de datos confusionMatrix
matriz_confusion <- confusionMatrix(data = factor(clases_pred), reference = factor(test_labels))

# Imprimir la matriz de confusión
print(matriz_confusion )
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1   2   3   4   5   6   7   8   9
##          0 876   0  18  10   1   0  82   0   5   0
##          1   0 990   2   5   0   0   1   0   1   0
##          2  13   0 856   8  33   0  51   0   2   1
##          3  20   5   8 916  12   0  26   0   5   0
##          4   3   3  60  36 905   0  70   0   8   0
##          5   1   0   0   0   0 976   0   6   1   4
##          6  83   1  56  25  49   0 768   0   5   0
##          7   0   0   0   0   0  16   0 983   4  42
##          8   4   1   0   0   0   0   2   0 969   0
##          9   0   0   0   0   0   8   0  11   0 953
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9192          
##                  95% CI : (0.9137, 0.9245)
##     No Information Rate : 0.1             
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.9102          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: 0 Class: 1 Class: 2 Class: 3 Class: 4 Class: 5
## Sensitivity            0.8760   0.9900   0.8560   0.9160   0.9050   0.9760
## Specificity            0.9871   0.9990   0.9880   0.9916   0.9800   0.9987
## Pos Pred Value         0.8831   0.9910   0.8880   0.9234   0.8341   0.9879
## Neg Pred Value         0.9862   0.9989   0.9841   0.9907   0.9893   0.9973
## Prevalence             0.1000   0.1000   0.1000   0.1000   0.1000   0.1000
## Detection Rate         0.0876   0.0990   0.0856   0.0916   0.0905   0.0976
## Detection Prevalence   0.0992   0.0999   0.0964   0.0992   0.1085   0.0988
## Balanced Accuracy      0.9316   0.9945   0.9220   0.9538   0.9425   0.9873
##                      Class: 6 Class: 7 Class: 8 Class: 9
## Sensitivity            0.7680   0.9830   0.9690   0.9530
## Specificity            0.9757   0.9931   0.9992   0.9979
## Pos Pred Value         0.7781   0.9407   0.9928   0.9805
## Neg Pred Value         0.9743   0.9981   0.9966   0.9948
## Prevalence             0.1000   0.1000   0.1000   0.1000
## Detection Rate         0.0768   0.0983   0.0969   0.0953
## Detection Prevalence   0.0987   0.1045   0.0976   0.0972
## Balanced Accuracy      0.8718   0.9881   0.9841   0.9754
  • Observamos que las clases con más precisión en la predicción han sido: 1-CAMISETA,5-SANDALIAS,7-DEPORTIVAS y la 9-BOTAS.
  • Y la que menos precisión ha tenido en la predicción ha sido: 6-CAMISA. Se supone que esta prenda ha sido la de menor precisión por parecerse mucho a la prenda 1-CAMISETA.

6. Conclusiones

Para resolver este problema, hemos aplicado técnicas del Deep Learning para la predicción de artículos de ropa con imágenes. Hemos observado que las predicciones hechas han sido bastante buenas.

Concluimos que parece que no ha existido overfitting ya que hemos aplicado técnicas de reducción de la dimensionalidad (MaxPooling2D), capas de regularización (Dropouts), 10 epocas.

Finalmente, con el modelo entrenado, hemos comprobado que predice bastante bien dentro lo que cabe para la resolución de las imágenes. Para confirmar que nuestro modelo puede generalizar, hemos introducido datos nuevos al modelo y este los ha predicho bien.

Confirmamos que el modelo es bueno obteniendo una precisión de ~ 0.919 para los datos test.