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 )
}
}
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.
Las 10 clases son: Label Description
0 T-shirt/top (Camiseta o top). 1 Trouser/pants (Pantalones). 2 Pullover shirt (Pullover). 3 Dress (Vestido). 4 Coat (Abrigo). 5 Sandal (Sandalias). 6 Shirt (Camisa). 7 Sneaker (Zapatos deportivos). 8 Bag (Bolso o maleta) 9 Ankle boot (Botines).
El objetivo del ejercicio es realizar una Clasificación de Imágenes en la que podamos decir a que tipo de artículo pertenece la imagen, realizando las siguientes tareas:
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
Con el mejor modelo que seleccionemos, usar las imágenes de test, obteniendo sus predicciones y mostrando la matriz de confusión.
Visualizar 25 imágenes aleatorias de todo el test, mostrando su predicción junto con la imagen.
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")
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]))
}
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
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.
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:
model %>% compile(
optimizer = optimizer_rmsprop(),
loss = 'sparse_categorical_crossentropy',
metrics = c('accuracy')
)
Para entrenar el modelo de red neuronal hacemos lo siguiente:
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:
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:
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.
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
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
## ________________________________________________________________________________
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:
model2 %>% compile(
optimizer = "adam",
loss = 'sparse_categorical_crossentropy',
metrics = c('accuracy')
)
Para entrenar el modelo de red neuronal hacemos lo siguiente:
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.
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.
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.
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
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.