05.7 Redes Neuronales Convoluciones¶
Las redes convolucionales ([LeCun et al., 1989]), también conocidas como redes neuronales convolucionales, (o convolutional neural networks CNN o ConvNet) , son un tipo especializado de red neuronal para procesar datos que tienen una topología similar a una cuadrícula. Los ejemplos incluyen datos de series temporales, que se pueden considerar como una cuadrícula 1-D que toma muestras a intervalos de tiempo regulares, y datos de imágenes, que se pueden considerar como una cuadrícula 2-D de píxeles.
El uso de redes densas para procesar imagenes ignora una propiedad clave de éstas, que es que los píxeles cercanos están más fuertemente correlacionados que los píxeles más distantes. Muchos de los enfoques modernos de la visión por computadora explotan esta propiedad al extraer características locales que dependen solo de pequeñas subregiones de la imagen. La información de dichas características se puede fusionar en etapas posteriores de procesamiento para detectar características de orden superior y, en última instancia, generar información sobre la imagen en su conjunto. Además, es probable que las características locales, que son útiles en una región de la imagen, lo sean en otras regiones de la imagen, por ejemplo, si se traslada el objeto de interés.
Las ConvNet se diseñaron para procesar imágenes y tratan de simular el funcionamiento de la visión humana. Restringen la arquitectura para usar un conjunto de pesos más reducido. En particular, a diferencia de una red neuronal normal, las capas ConvNet tienen neuronas dispuestas en 3 dimensiones: ancho, alto, profundidad (se adoptan las iniciales en inglés \(w,h,d\)). En las redes neuronales densas se tiene una matriz de pesos \((m,n)\) para conectar una capa anterior de \(n\) neuronas con una de \(m\). Aquí se conectarán las \(n\) neuronas con \(d\) capas convolucionales usando un bloque de \((w,h,d)\) pesos. La reducción de complejidad viene dado porque la dimensión de \((w,h)\) es muy inferior a \((m,n)\)
Una operación de convolución básica que se aplica a una imagen bidimensional I como entrada, usando un kernel o filtro K bidimensional y que nos da como resultado una nueva imagen S sería por ejemplo:
Las redes neuronales convolucionales modelan de forma consecutiva pequeñas piezas de información, tratando de extraer información sobre diferentes patrones de cada imagen. La primera capa intentará detectar los bordes y establecer patrones de detección de bordes, por ejemplo. Todo ello en una secuencia de mapas que constituirán el primer bloque de convolución. Luego, en secuencia de capas posteriores trataran de combinarlos en formas más simples y, finalmente, en patrones de las diferentes posiciones de los objetos, iluminación, escalas, etc. Las capas finales intentarán hacer coincidir una imagen de entrada con todos los patrones y llegar a una predicción final como una suma ponderada de todos ellos, usando ya una red densa.
Izquierda: Una red neuronal normal de 3 capas. Derecha: Un ConvNet organiza sus neuronas en tres dimensiones (ancho, alto, profundidad), como se visualiza en una de las capas. Cada capa de un ConvNet transforma el volumen de entrada 3D en un volumen de salida 3D de activaciones neuronales. En este ejemplo, la capa de entrada roja contiene la imagen, por lo que su ancho y alto serían las dimensiones de la imagen, y la profundidad sería de 3 (canales rojo, verde, azul).
Una Arquitectura Convolucional tipo estará compuesta por las capas: [INPUT - CONV - RELU - POOL - FC]. Cuyo detalle es:
INPUT [bxhx3] contendrá los valores de píxeles raw de la imagen, en este caso una imagen de ancho b, altura h, y con tres canales de color R, G, B.
La capa CONV calculará la salida de las neuronas que están conectadas a las regiones locales en la entrada, cada una calculando un producto de punto entre sus pesos y una pequeña región a la que están conectadas en el volumen de entrada. Esto puede resultar en un volumen como [BxHxd] si decidimos usar d filtros. Los pesos que conectan los pixels localmente son compartidos y son aprendibles.
La capa RELU aplicará una función de activación por elementos, como el umbral \((max(0,x))\) en cero. Esto deja el tamaño del volumen sin cambios ([BxHxd]).
La capa POOL realizará una operación de downsampling a lo largo de las dimensiones espaciales (ancho, alto), lo que dará como resultado un volumen como [bxhxd], siendo (b,h) inferiores a (B,H). Esta capa no utiliza pesos aprendibles.
La capa FC o densa (es decir, totalmente conectada) calculará los puntajes de clase, lo que resultará en un volumen de tamaño [1x1xK], donde cada uno de los K números corresponde a un puntaje de clase, entre las K categorías del conjunto. Al igual que con las redes neuronales ordinarias y como su nombre indica, cada neurona en esta capa estará conectada a todos los números en el volumen anterior.
Capa convolucional¶
Los parámetros de la capa CONV consisten en un conjunto de filtros aprendibles. Cada filtro es pequeño espacialmente (a lo largo de ancho y alto), pero se extiende a través de toda la profundidad del volumen de entrada. Por ejemplo, un filtro típico en una primera capa de un ConvNet puede tener un tamaño de 7x7x3 (es decir, 7 y 7 píxeles de ancho y alto, y 3 de profundidad, por los canales de color). Durante el paso hacia adelante, se desliza cada filtro a través del ancho y alto del volumen de entrada y se calculan los productos entre las entradas del filtro y la entrada en cualquier posición.
Por ejemplo la convolución Conv2d(3, 12, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False) nos indica:
La profundidad de la entrada es 3, por los canales R,G,B. Puede ser un canal único para el caso de imagenes en gris o más de 3 si se recogen datos en el espectro electromagnético con sensores especiales.
La profundidad de salida es 12, se entrenan 12 características distintas para las imágenes.
Las dimensiones del filtro es 7 pixels de ancho por 7 de alto.
El paso o stride es 2 por 2. Si el paso o stride es 1 a 1 se mueve el filtro pixel a pixel y la salida es de igual dimensión, Si es 2 a 2 la dimensión de los mapas de salida se reduce.
El padding o relleno de ceros es 3 por 3. De no usar el padding, un filtro (7, 7) tiene una pérdida de 3 filas superiores e inferiores y 3 columnas izquierda y derecha. Añadiendo un marco de ceros (3, 3) se evita tener que reducir la dimensión de los mapas de salida respecto a las imágenes de entrada
El parámetro Bias está anulado. Aquí se indica si además de la matriz de pesos del kernel se utiliza una matriz de bias.
A medida que se desliza el filtro sobre el ancho y la altura del volumen de entrada, se produce un mapa de activación en 2 dimensiones que da las respuestas de ese filtro en cada posición espacial. Se habla de mapa porque cada una de las 12 matrices de salida no es exactamente una imagen, sino una matriz donde se tratan de destacar características. Intuitivamente, la red aprenderá filtros que se activan cuando ven algún tipo de característica visual, como un borde de alguna orientación o una mancha de algún color en la primera capa, o eventualmente patrones completos en forma de panal o rueda en capas superiores de la red. Cada uno de ellos producirá un mapa de activación 2-dimensional separado. Se apilan estos mapas a lo largo de la dimensión de profundidad resultando el volumen de salida.
Se puede interpretar como una salida de una neurona que mira solo una pequeña región en la entrada y comparte parámetros con todas las neuronas a la izquierda y a la derecha espacialmente (ya que todos estos números resultan de aplicar el mismo filtro).
[LeCun et al., 1989] presentaron uno de los primeros trabajos sobre imagenes en escala de grises que riquieren un único canal de entrada usando una red convolucional. La capa de entrada son imagenes 16x16, se forman 12 mapas 8x8 en una primera capa de convolución (H1) y en una segunda de convolución (H2) se forman 12 mapas 4x4. La segunda capa de convolución está totalmente conectada con una capa oculta de 30 unidades (H3) que a su vez está también ‘fully connected’ (FC) con la capa de salida de 10 neuronas.
En resumen la capa Conv, parte de un volumen de tamaño \((W_1 \times H_1 \times D_1)\):
Requiere cuatro hiperparámetros:
Número de filtros \((K)\).
Tamaño del filtro \((F)\).
Paso o salto \((S)\).
Cantidad de relleno a cero \((P)\).
Produce un volumen de tamaño \((W_2 \times H_2 \times D_2)\) donde:
\(W_2=(W_1 − F + 2P)/(S + 1)\)
\(H_2 = (H_1 - F + 2P)/(S + 1)\). Es decir, el ancho y el alto se calculan por igual por simetría.
\(D_2=K\)
La cantidad de parámetros compartidos son \(F \cdot F \cdot D_1\) pesos por filtro, que da un total de \(((F \cdot F \cdot D_1) \cdot K)\) pesos y \((K)\) sesgos.
En el volumen de salida, el segmento de profundidad \(d\)-ésimo (de tamaño \((W_2 \times H_2)\) es el resultado de realizar una convolución del filtro \(d\)-ésimo sobre el volumen de entrada con una zancada de \((S)\), y luego desviarse por \(d\)-ésimo sesgo.
Los hiperparámetros de disposición espacial tienen restricciones mutuas. Si la entrada tiene tamaño (W = 10), no se usa relleno cero (P = 0), y el tamaño del filtro es (F = 3), entonces sería imposible usar paso (S = 2). Ya que la fórmula de la dimensión del mapa de salida da un valor \(4.5\) que no es entero.
Un ejemplo gráfico del proceso de convolución para una entrada \((W_1=5, H_1=5, D_1=3)\) y unos parámetros de la capa de convolución \((K=2, F=3, S=2, P=1)\) es
Como se puede comprobar el elemento \(-3\) del primer mapa de salida se obtiene multiplicando uno a uno los elementos de cada filtro por los canales de entrada y sumando todos los productos más el valor del bias. Idem para el siguiente elemento \(-1\) una vez ejecutado el salto hacia la derecha en columnas. Una vez llegado hasta la última columna se ejecuta un salto en filas.
Retropropagación. El paso hacia atrás para una operación de convolución (tanto para los datos como para los pesos) también es una convolución (pero con filtros invertidos espacialmente).
La retropropagación entre la capa convolucional \(l\) y la primera capa densa o FC \(l+1\), si previamente se han calculado los \(\delta\) de la capa \(l+1\), requiere calcular la convolución inversa de los valores \(\delta^{l+1}\):
Donde \(M_l\) está determinado por la longitud del vector resultante de reacomodar el mapa de salida de la capa \(l\). Se obtiene un mapa con la retropropagación de errores \(\Phi^l\) obtenido de reacomodar \(\phi^l_j\) en la dimensión del volumen (B,H,D) de la capa \(l\). Finalmente si termina en una activación la retropropagación vendrá dada por:
Capa de Agrupación (Pooling)¶
Es común insertar periódicamente una capa de agrupación entre capas sucesivas de Conv en una arquitectura de ConvNet. Su función es reducir progresivamente el tamaño espacial de la representación para reducir la cantidad de parámetros y cálculos en la red y, por lo tanto, controlar también el sobreajuste. La capa de agrupación funciona de forma independiente en cada segmento de profundidad de la entrada y cambia su tamaño espacialmente, utilizando la operación MAX. El detalle:
Se parte de un volumen de tamaño \((W_1 \times H_1 \times D_1)\).
Requiere dos hiperparámetros:
Su extensión espacial \((F)\).
Salto \((S)\).
Produce un volumen de tamaño \((W_2 \times H_2 \times D_2)\) donde:
\(W_2=(W_1−F)/(S+1)\).
\(H_2=(H_1−F)/(S+1)\).
\(D_2=D_1\).
No supone introducir nuevos parámetros en el aprendizaje, ya que es una función fija de la entrada. Para las capas de agrupación, no es común usar en la entrada el padding o relleno ceros.
Un ejemplo gráfico es
En general se pueden definir otras funciones de Pooling, como la promedio (en desuso porque funciona peor que Max) o L2-norm.
Retropropagación. El paso hacia atrás para una operación max(x, y) tiene una interpretación simple como que solo enruta el error retropropogado en la capa \(l+1\) dado por los \(delta^{l+1}_p\) a la entrada que tenía el valor más alto en el paso hacia adelante:
Deshacerse de la agrupación. Springenberg et al. (2014) proponen descartar la capa de agrupación en favor de una arquitectura que solo consiste en capas CONV repetidas. Para reducir el tamaño de la representación, sugieren usar un salto más grande en la capa CONV de vez en cuando. También se ha encontrado que descartar capas de agrupación es importante para entrenar buenos modelos generativos, como los autocodificadores variacionales (VAE) o las redes generativas adversarias (GAN). Parece probable que las arquitecturas futuras presenten muy pocas o ninguna capa de agrupación.
Reglas generales para modelar una red convolucional¶
La capa de entrada (que contiene la imagen) debe ser divisible por \(2^n\). Los números comunes incluyen 32 (por ejemplo, CIFAR-10), 64, 96 (por ejemplo, STL-10) o 224 (por ejemplo, ImageNet), 384 y 512.
Las capas conv deben usar filtros pequeños (por ejemplo, 3x3 o como máximo 5x5), usar un salto de \(S = 1\) y, lo que es más importante, rellenar el volumen de entrada con ceros (padding) de tal manera que la capa conv no altere las dimensiones espaciales de la entrada (si F = 3, P = 1; si F = 5, P=2; en general \(P = (F - 1)/2\)).
Las capas de pool se encargan de reducir el muestreo de las dimensiones espaciales de la entrada. La configuración más común es usar max-pooling con campos receptivos 2x2 (es decir, F = 2), y un salto de 2 (S = 2); esto es, se descarta exactamente el 75%. Más infrecuente, por la dificultad de encajarlo en la dimensión de la entrada es el uso de F = 3 y S = 2. Dimensiones de maxpooling superiores son muy infrecuentes ya que es muy agresiva y deficitaria y conduce a peores rendimentos.
Algunas arquitecturas convolucionales destacadas¶
Hay varias arquitecturas en el campo de las Redes Convolucionales que tienen un nombre. Los más comunes son:
LeNet. Las primeras aplicaciones exitosas de redes convolucionales fueron desarrolladas por [LeCun et al., 1998] en la década de 1990. De estos, el más conocido es la arquitectura LeNet que se utilizaba para leer códigos postales, dígitos, etc.
AlexNet. El primer trabajo que popularizó las redes convolucionales en visión artificial fue el AlexNet, desarrollado por [Krizhevsky et al., 2012] (2012). El AlexNet se presentó al desafío ImageNet ILSVRC en 2012 y superó significativamente al segundo finalista (error top 5 del 16% en comparación con el subcampeón con un error del 26%). La red tenía una arquitectura muy similar a LeNet, pero era más profunda, más grande y presentaba capas convolucionales apiladas una encima de la otra (anteriormente era común tener una sola capa CONV siempre seguida inmediatamente por una capa POOL).
ZF Neto. El ganador de ILSVRC 2013 fue una Red Convolucional de Matthew Zeiler y Rob Fergus. Se hizo conocido como ZFNet (abreviatura de Zeiler & Fergus Net). Fue una mejora en AlexNet al ajustar los hiperparámetros de la arquitectura, en particular al expandir el tamaño de las capas convolucionales medias y hacer que el paso y el tamaño del filtro en la primera capa sean más pequeños.
GoogLeNet. El ganador de ILSVRC 2014 fue una red convolucional de [Szegedy et al., 2014] de Google. Su principal contribución fue el desarrollo de un Módulo de Inicio que redujo drásticamente el número de parámetros en la red (4M, en comparación con AlexNet con 60M). Además, este documento utiliza Average Pooling en lugar de capas totalmente conectadas en la parte superior de ConvNet, eliminando una gran cantidad de parámetros que no parecen importar mucho. También hay varias versiones de seguimiento de GoogLeNet, la más reciente Inception-v4.
VGGNet. El subcampeón en ILSVRC 2014 fue la red de Karen Simonyan y Andrew Zisserman que se conoció como VGGNet. Su principal contribución fue mostrar que la profundidad de la red es un componente crítico para un buen rendimiento. Su mejor red final contiene 16 capas CONV / FC y, atractivamente, presenta una arquitectura extremadamente homogénea que solo realiza circunvoluciones 3x3 y agrupación 2x2 desde el principio hasta el final. Su modelo preentrenado está disponible para uso plug and play en Caffe. Una desventaja de VGGNet es que es más caro de evaluar y utiliza mucha más memoria y parámetros (140M). La mayoría de estos parámetros se encuentran en la primera capa totalmente conectada, y desde entonces se descubrió que estas capas FC se pueden eliminar sin degradar el rendimiento, lo que reduce significativamente el número de parámetros necesarios.
ResNet. Residual Network desarrollado por [Kaiming et al., 2015] fue el ganador de ILSVRC 2015. Cuenta con conexiones de salto especiales y un uso intensivo de la normalización por lotes. A la arquitectura también le faltan capas totalmente conectadas al final de la red. ResNets son actualmente modelos de red neuronal convolucional de última generación y son la opción predeterminada para usar ConvNets en la práctica (a partir del 10 de mayo de 2016).
Esquema de la arquitectura VGGNet¶
El modelo logra una precisión de prueba del 92,7 % entre los cinco primeros en ImageNet, que es un conjunto de datos de más de 14 millones de imágenes pertenecientes a 1000 clases. Dentro del modelo VGGNet se implementa la arquitectura VGG16:
Redes Residuales Profundas¶
Las Redes Residuales Profundas (ResNets) consisten en muchas “Unidades Residuales” apiladas. Cada unidad (ver la siguiente figura) puede expresarse de forma general:
Dónde \(x_l\) y \(x_{l+1}\) son entradas y salidas de la unidad \(l\)-ésima, \(F\) es una función residual, \(h(x_l)=x_l\) es un mapeo de identidad y \(f\) es la función ReLU.
Para un mayor detalle y por ejemplo profundizar en la retropropagación de las ResNets consultar el capitulo de Kaiming:
https://link.springer.com/chapter/10.1007/978-3-319-46493-0_38
Ver también: https://pytorch.org/hub/pytorch_vision_resnet/
Ejercicio práctico de entrenamiento de un conjunto de imagenes usando convoluciones¶
El manejo de mapas implica pasar de usar matrices como modelo de datos a tensores que son matrices de varias dimensiones. Los costes computacionales del entrenamiento de una red convolucional profunda con un conjunto de entrenamiento con valores \(N\) alto es muy elevado. Todo ello lleva a ser más práctico usar una arquitectura de desarrollo como Pytorch en lugar de sk-learn y usar una arquitectura de ejecución GPU en lugar de CPU. Este cuaderno está preparado para usarse en Google Colaboratory, donde se puede realizar un ejecución gratuita en GPU.
PyTorch es una biblioteca de aprendizaje automático de código abierto basada en la biblioteca de Torch, utilizado para implementar aplicaciones de visión artificial o procesamiento de lenguajes naturales. Desarrollado por el Laboratorio de Investigación de Inteligencia Artificial4 de Facebook (FAIR).Es un software libre y de código abierto liberado bajo la Licencia Modificada de BSD. A pesar de que la interfaz de Python está más pulida y es el foco principal del desarrollo, PyTorch también tiene una interfaz en C++.
Varios software de Aprendizaje Profundo están construidas utilizando PyTorch, como Tesla Autopilot, Uber’s Pyro, HuggingFace’s Transformers, PyTorch Lighting, y Catalyst.
PyTorch proporciona dos características de alto nivel:
Computación de tensores (como NumPy ) con una aceleración fuerte a través de unidades de procesamientos gráficos (GPU).
Redes neuronales profundas construidas en un sistema de diferenciación automática de bases de datos.
https://pytorch.org/
Comprendido el funcionamiento de sk-learn, el diseño de una red neuronal en Pytorch requiere los siguientes pasos básicos:
Los datos se estructuran en tensores de 4 dimensiones (\(N, D, B, H\)) para manejar imagenes bidimensionales (\(B, H\)). Siendo \(N\) el número de ejemplos del lote o de un conjunto completo de entrenamiento, \(D\) es el número de canales de entrada (3 para RGB) o la profundidad de mapas de cada capa.
Hay que definir una clase con la estructura de las capas de la red. La clase tendrá un constructor (Init) y un método forward. Crearemos un objeto llamando al constructor. Por ejemplo: model = CNN_long().
Hay que especificar la función de pérdida y el método de optimización.
El entrenamiento requerirá hacer un bucle por época donde se extraigan 1 a 1 los lotes del conjunto de entrenamiento y se vayan calculando las pérdidas y entrenando la red. Es un proceso estándar aunque algo más abierto que en sk-learn.
El modelo entrenado se puede guardar en fichero. Y una vez guardado es posible su recuperación para no tener que repetir el entrenamiento.
Modos de ejecución del cuaderno¶
El cuaderno tiene una serie de variables booleanas que le permite ejecutarlo de diferentes maneras:
googleColaboratory : estando a True activará las opciones de acceso a Drive de Google y a la ejecución CUDA de Google Colaboratory.
entrenamiento : estando a True ejecuta el entrenamiento y guarda los datos a fichero en la carpeta data ya sea en Drive o local. Si está a False recupera la configuración de fichero existente en data.
resNet : estando a True se ejecuta sobre el modelo definido en class ResNet(nn.Module) que implementa una red residual. Si está a False carga un modelo en función del valor de la variable convolucionProfunda.
convolucionProfunda : Si resNet está a False y aquí aparece True implementa class CNN_long(nn.Module) con 3 bloques y hasta 6 convoluciones. En ella se presenta un estilo muy simple de programación de Pytorch basado en el uso de nn.Sequential. Si está a False se implementa class CNN_short(nn.Module) con sólo 2 convoluciones.
El entrenamiento de un modelo CNN_short es viable en una máquina CPU como Windows. Para el resto de modelos lo recomendable es una ejecución en una máquina con capacidad GPU. Google Colaboratory tiene un entorno libre que permite hacerlo manteniendo la sesión activa de forma manual. Con una cuenta de pago sería posible hacer esta ejecución en un segundo plano.
import glob, os
import numpy as np
from PIL import Image
from IPython import display
import matplotlib.pyplot as plt
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
import torch
import torch.nn as nn
from torch.autograd import Variable
Se definen los parámetros de ejecución
googleColaboratory = False
entrenamiento = False
convolucionProfunda = False
resNet = True
if googleColaboratory:
import google as goo
Carga de Datos¶
PyTorch tiene dos primitivas para trabajar con bases de datos conocidas para hacer pruebas: torch.utils.data.DataLoader y torch.utils.data.Dataset.
Dataset almacena los ejemplos y sus correspondientes etiquetas, y DataLoader genera un iterable sobre el conjunto de datos.
Se utiliza la base de datos en línea CIFAR10 con 50.000 imagenes de entrenamiento de 10 clases distintas y 10.000 imagenes para prueba:
https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html
Las imágenes en CIFAR-10 tienen un tamaño de 3x32x32, es decir, imágenes en color de 3 canales de 32x32 píxeles de tamaño.
La base de datos es accesible desde torchvision.datasets
if googleColaboratory:
goo.colab.drive.mount('/content/drive/')
import torch
from torchvision import datasets, transforms
lote_size = 128
lote_size = 4
# Download and load the training data
if googleColaboratory:
trainset = datasets.CIFAR10('/content/drive/My Drive/Colab Notebooks/data/', download=True, train=True, transform=transforms.ToTensor())
else:
trainset = datasets.CIFAR10('data/', download=True, train=True, transform=transforms.ToTensor())
trainloader = torch.utils.data.DataLoader(trainset, batch_size=lote_size, shuffle=True)
# Download and load the test data
if googleColaboratory:
testset = datasets.CIFAR10('/content/drive/My Drive/Colab Notebooks/data/', download=True, train=False, transform=transforms.ToTensor())
else:
testset = datasets.CIFAR10('data/', download=True, train=False, transform=transforms.ToTensor())
testloader = torch.utils.data.DataLoader(testset, batch_size=lote_size, shuffle=True)
print("Tensor Entrenamiento=", trainset.data.shape, "Tensor Validación=", testset.data.shape)
clases=('avion', 'coche','pajaro', 'gato', 'ciervo','perro','rana','caballo','barco', 'camión')
Files already downloaded and verified
Files already downloaded and verified
Tensor Entrenamiento= (50000, 32, 32, 3) Tensor Validación= (10000, 32, 32, 3)
Se visualizan algunas de las imagenes cargadas
from torchvision.utils import make_grid
for ix in range(3):
for images, _ in trainloader:
plt.figure(figsize=(6,3))
plt.axis('off')
plt.imshow(make_grid(images, nrow=16).permute((1, 2, 0)))
break
Clases con la definición de la Red¶
class CNN_short(nn.Module):
def __init__(self):
super(CNN_short, self).__init__()
# capa convolucional (ve tensores Nx3x32x32 y devuelve tensores Nx6x28x28)
self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5, stride=1, padding=0)
self.pool = nn.MaxPool2d(kernel_size=2) # Devuelve tensores Nx6x14x14
# capa convolucional (ve tensores Nx6x14x14 y devuelve tensores Nx16x10x10)
self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1, padding=0)
# Capa oculta totalmente conectada. Salida a 10 clases
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x))) # Entran tensores Nx3x32x32 y salen Nx6x14x14
x = self.pool(F.relu(self.conv2(x))) # Entran tensores Nx6x14x14 y salen Nx16x5x5
# Se aplana la salida de conv2 a (batch_size, 16 * 5 * 5)
#x = x.view(x.size(0), -1)
x = x.view(-1, 16*5*5) # Se aplana a un vector 16x5x5 = 400
x = F.relu(self.fc1(x)) # Capa densa de 400 -> 120
#x = F.dropout(x, 0.5) #dropout se incluye para evitar overfitting
x = F.relu(self.fc2(x)) # Capa densa de 120 -> 84
#x = F.dropout(x, 0.5) #dropout se incluye para evitar overfitting
x = self.fc3(x) # Capa densa de 84 -> 10 con la puntuación por clase
return x
class CNN_long(nn.Module):
def __init__(self):
super().__init__()
self.network = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2), # salida: N x 16 x 16 x 64
nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2), # salida: N x 8 x 8 x 128
nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2), # salida: N x 4 x 4 x 256
nn.Flatten(), # Se aplana a un vector 4 x 4 x 256 = 4096
nn.Linear(256*4*4, 1024), # Red densa FC : De 4096 -> 1024
nn.ReLU(),
nn.Linear(1024, 512), # Red densa FC : De 1024 -> 512
nn.ReLU(),
nn.Linear(512, 10)) # Red densa FC : De 512 -> 10 con la puntuación por clase
def forward(self, xb):
return self.network(xb)
Definición de la red residual (ResNet)¶
La red está formada por la repetición de un bloque similar (ResidualBlock). El bloque está formado por una secuencia:
Las convoluciones empleadas son todas de tamaño de filtro 3, salto 1 (en el primer bloque o layer y 2 en el resto) y padding o relleno a ceros 1. Los bloques residuales se integran en 3 capas o layers. La estructura de la red de convolución completa usando los bloques es:
La estructura aparece explicitamente en el cuaderno trás hacer print(model).
El paso \([BN]\) es una Normalización (restando la media \(\mu\) y dividiendo por la desviación típica \(\sigma\))
Se utiliza una única capa conectada entre el vector aplanado el número de etiquetas del modelo.
La acumulación del valor residual se hace en cada bloque en la instrucción out += residual, copiandose el valor x existente al principio.
Pytorch incluye en línea unas definiciones de ResNet:
https://pytorch.org/hub/pytorch_vision_resnet/
Se puede descargar el modelo entrenado o no. El no entrenado puede utilizarse para un entrenamiento propio. Lo único tener en cuenta que el modelo devuelve hasta 1000 clases en salida. Si se usan 10 como en CIFAR10, el puntuaje máximo estará en los 10 primeros valores de esos 1000
# 3x3 convolution
def conv3x3(in_channels, out_channels, stride=1):
return nn.Conv2d(in_channels, out_channels, kernel_size=3,
stride=stride, padding=1, bias=False)
# Residual block
class ResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1, downsample=None):
super(ResidualBlock, self).__init__()
self.conv1 = conv3x3(in_channels, out_channels, stride)
self.bn1 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(out_channels, out_channels)
self.bn2 = nn.BatchNorm2d(out_channels)
self.downsample = downsample
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
# ResNet
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=10):
super(ResNet, self).__init__()
self.in_channels = 16
self.conv = conv3x3(3, 16)
self.bn = nn.BatchNorm2d(16)
self.relu = nn.ReLU(inplace=True)
self.layer1 = self.make_layer(block, 16, layers[0])
self.layer2 = self.make_layer(block, 32, layers[1], 2)
self.layer3 = self.make_layer(block, 64, layers[2], 2)
self.avg_pool = nn.AvgPool2d(8)
self.fc = nn.Linear(64, num_classes)
def make_layer(self, block, out_channels, blocks, stride=1):
downsample = None
if (stride != 1) or (self.in_channels != out_channels):
downsample = nn.Sequential(
conv3x3(self.in_channels, out_channels, stride=stride),
nn.BatchNorm2d(out_channels))
layers = []
layers.append(block(self.in_channels, out_channels, stride, downsample))
self.in_channels = out_channels
for i in range(1, blocks):
layers.append(block(out_channels, out_channels))
return nn.Sequential(*layers)
def forward(self, x):
out = self.conv(x)
out = self.bn(out)
out = self.relu(out)
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.avg_pool(out)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out
# Se crea un modelo CNN
#if googleColaboratory:
if googleColaboratory:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') #training with either cpu or cuda
if resNet:
if googleColaboratory:
model = ResNet(ResidualBlock, [2, 2, 2]).to(device)
else:
model = ResNet(ResidualBlock, [2, 2, 2])
elif convolucionProfunda:
model = CNN_long()
else:
model = CNN_short()
if googleColaboratory:
model = model.to(device=device) #to send the model for training on either cuda or cpu
print(model)
ResNet(
(conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(layer1): Sequential(
(0): ResidualBlock(
(conv1): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(1): ResidualBlock(
(conv1): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer2): Sequential(
(0): ResidualBlock(
(conv1): Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): ResidualBlock(
(conv1): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer3): Sequential(
(0): ResidualBlock(
(conv1): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): ResidualBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(avg_pool): AvgPool2d(kernel_size=8, stride=8, padding=0)
(fc): Linear(in_features=64, out_features=10, bias=True)
)
Especificar la Función de Pérdida y el Optimizador¶
http://pytorch.org/docs/stable/nn.html#loss-functions
http://pytorch.org/docs/stable/optim.html
import torch.optim as optim
# Se especifica la función pérdida
loss_func = nn.CrossEntropyLoss()
# Se especifica el optimizador
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
#optimizer = optim.Adam(model.parameters(), lr=0.001)
Entrenamiento de la red¶
Hay que observar como la pérdida en entrenamiento y validación disminuye con el tiempo; si la pérdida de validación aumenta alguna vez, indica un posible sobreajuste.
from torch.autograd import Variable
num_epochs = 10
model.train()
lineasTraza = int(len(trainloader)/20)+1
numlineas = 0
# Train the model
for epoch in range(num_epochs):
if not entrenamiento:
print("No habilitada la opción de entrenamiento")
break
correct = 0
total = 0
for images, labels in trainloader:
images = images.to(device=device)
labels = labels.to(device=device)
# gives batch data, normalize x when iterate train_loader
b_x = Variable(images) # batch x
b_y = Variable(labels) # batch y
output = model(b_x)
loss = loss_func(output, b_y)
pred_y = torch.max(output, 1)[1].data.squeeze()
correct += (pred_y == labels).sum().item()
total +=float(labels.size(0))
# clear gradients for this training step
optimizer.zero_grad()
# backpropagation, compute gradients
loss.backward()
# apply gradients
optimizer.step()
numlineas+=1
if numlineas == lineasTraza:
print('*', end='')
numlineas=0
print ('Época [{}], Accuracy-Entrenamiento {:.4f}'.format(epoch + 1, 100*correct/total))
No habilitada la opción de entrenamiento
Se hace un back-up del modelo generado¶
filename = "cifar10_profunda.pt" if convolucionProfunda else "cifar10_short.pt"
if resNet: filename = "cifar10_resnet.pt"
filename = "/content/drive/My Drive/Colab Notebooks/data/" + filename if googleColaboratory else "data/"
if entrenamiento:
print("Backup al fichero=", filename)
torch.save(model.state_dict(), filename)
if googleColaboratory:
goo.colab.files.download(filename)
else:
print("No habilitado Backup del fichero")
No habilitado Backup del fichero
Recuperación del modelo desde el archivo de back-up¶
device = torch.device('cpu')
if googleColaboratory:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') #training with either cpu or cuda
model = CNN_long() if convolucionProfunda else CNN_short()
if resNet: model = ResNet(ResidualBlock, [2, 2, 2]).to(device)
filename = "cifar10_profunda.pt" if convolucionProfunda else "cifar10_short.pt"
if resNet: filename = "cifar10_resnet.pt"
filename = "/content/drive/My Drive/Colab Notebooks/data/" + filename if googleColaboratory else "data/" + filename
print("Fichero cargado=", filename)
if not entrenamiento:
if googleColaboratory:
model.load_state_dict(torch.load(filename, map_location=torch.device('cpu'))) #recovery trained model
else:
model.load_state_dict(torch.load(filename, map_location=torch.device('cpu'))) #recovery trained model
model.eval()
print(model)
Fichero cargado= data/cifar10_resnet.pt
ResNet(
(conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(layer1): Sequential(
(0): ResidualBlock(
(conv1): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(1): ResidualBlock(
(conv1): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer2): Sequential(
(0): ResidualBlock(
(conv1): Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): ResidualBlock(
(conv1): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer3): Sequential(
(0): ResidualBlock(
(conv1): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): ResidualBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(avg_pool): AvgPool2d(kernel_size=8, stride=8, padding=0)
(fc): Linear(in_features=64, out_features=10, bias=True)
)
Se evalua el modelo contra el conjunto de validación¶
model.eval()
with torch.no_grad():
correct = 0
total = 0
for images, labels in testloader:
images = images.to(device=device)
labels = labels.to(device=device)
test_output = model(Variable(images))
pred_y = torch.max(test_output, 1)[1].data.squeeze()
#accuracy = (pred_y == labels).sum().item() / float(labels.size(0))
correct += (pred_y == labels).sum().item()
total +=float(labels.size(0))
accuracy=100*correct/total
print('El Acierto en los 10.000 ejemplos de prueba es : %.2f %%' % accuracy)
El Acierto en los 10.000 ejemplos de prueba es : 80.29 %
Vamos a ver el aspecto de los mapas de las convoluciones¶
El dibujo sólo está preparado para modo CPU. Una vez entrenado en GPU y volcado a fichero, recuperar en CPU y dibujar los mapas.
Sólo tiene un sentido el color de las imagenes. El color de los mapas no es significativo, pues no son dibujos RGB en si mismo, sino extracciones de características
import matplotlib.pyplot as plt
lote_image, lote_label = next(iter(testloader))
if resNet:
lote_mapa1 = model.conv(lote_image)
lote_mapa2 = model.bn(lote_mapa1)
lote_mapa3 = model.relu(lote_mapa2)
lote_mapa4 = model.layer1[0].conv1(lote_mapa3)
lote_mapa5 = model.layer1[0].bn1(lote_mapa4)
lote_mapa6 = model.layer1[0].relu(lote_mapa5)
lote_mapa7 = model.layer1[0].conv2(lote_mapa6)
lote_mapa8 = model.layer1[0].bn2(lote_mapa7)
lote_mapa9 = model.layer1[1].conv1(lote_mapa8)
lote_mapa10 = model.layer1[1].bn1(lote_mapa9)
lote_mapa11 = model.layer1[1].relu(lote_mapa10)
lote_mapa12 = model.layer1[1].conv2(lote_mapa11)
lote_mapa13 = model.layer1[1].bn2(lote_mapa12)
lote_mapa14 = model.layer2[0].conv1(lote_mapa13)
lote_mapa15 = model.layer2[0].bn1(lote_mapa14)
elif convolucionProfunda:
conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
lote_mapa1 = conv1(lote_image)
lote_mapa2 = F.relu(lote_mapa1)
conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
lote_mapa3 = conv2(lote_mapa2)
lote_mapa4 = F.relu(lote_mapa3)
pool = nn.MaxPool2d(2, 2)
lote_mapa5 = pool(lote_mapa4)
conv3 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1)
lote_mapa6 = conv3(lote_mapa5)
lote_mapa7 = F.relu(lote_mapa6)
conv4 = nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1)
lote_mapa8 = conv4(lote_mapa7)
lote_mapa9 = F.relu(lote_mapa8)
lote_mapa10 = pool(lote_mapa9)
conv5 = nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1)
lote_mapa11 = conv5(lote_mapa10)
lote_mapa12 = F.relu(lote_mapa11)
conv6 = nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1)
lote_mapa13 = conv6(lote_mapa12)
lote_mapa14 = F.relu(lote_mapa13)
lote_mapa15 = pool(lote_mapa14)
else:
lote_mapa1 = model.conv1(lote_image)
lote_mapa2 = F.relu(lote_mapa1)
lote_mapa3 = model.pool(lote_mapa2)
lote_mapa4 = model.conv2(lote_mapa3)
lote_mapa5 = F.relu(lote_mapa4)
lote_mapa6 = model.pool(lote_mapa5)
image = lote_image[0] ## Se coge la primera imagen del lote
mapa1 = lote_mapa1[0]
mapa2 = lote_mapa2[0]
mapa3 = lote_mapa3[0]
mapa4 = lote_mapa4[0]
mapa5 = lote_mapa5[0]
mapa6 = lote_mapa6[0]
if convolucionProfunda or resNet:
mapa7 = lote_mapa7[0]
mapa8 = lote_mapa8[0]
mapa9 = lote_mapa9[0]
mapa10 = lote_mapa10[0]
mapa11 = lote_mapa11[0]
mapa12 = lote_mapa12[0]
mapa13 = lote_mapa13[0]
mapa14 = lote_mapa14[0]
mapa15 = lote_mapa15[0]
label = int(lote_label[0])
numCols = 16 if convolucionProfunda or resNet else 7
fig, ax = plt.subplots(nrows=10, ncols=numCols, figsize=(18,14))
#ax = ax.flatten()
if resNet:
literales=['Imagen', 'Conv', 'Normal', 'ReLU', 'Conv', 'Normal', 'ReLU', 'Conv', 'Normal', 'Conv', 'Normal', 'ReLU', 'Conv', 'Normal', 'Conv', 'Normal', 'ReLU']
elif convolucionProfunda:
literales=['Imagen', 'Conv1', 'ReLU', 'Conv2', 'ReLU', 'Pool', 'Conv3', 'ReLU', 'Conv4', 'ReLU', 'Pool', 'Conv5', 'ReLU', 'Conv6', 'ReLU', 'Pool']
else:
literales=['Imagen', 'Conv1', 'ReLU', 'Pool', 'Conv2', 'ReLU', 'Pool']
for i in range(10):
ax[i][0].imshow(image.numpy().transpose((1, 2, 0)))
if i < mapa1.shape[0]:
ax[i][1].imshow(mapa1[i].detach().numpy())
ax[i][2].imshow(mapa2[i].detach().numpy())
ax[i][3].imshow(mapa3[i].detach().numpy())
ax[i][4].imshow(mapa4[i].detach().numpy())
ax[i][5].imshow(mapa5[i].detach().numpy())
ax[i][6].imshow(mapa6[i].detach().numpy())
if convolucionProfunda or resNet:
ax[i][7].imshow(mapa7[i].detach().numpy())
ax[i][8].imshow(mapa8[i].detach().numpy())
ax[i][9].imshow(mapa9[i].detach().numpy())
ax[i][10].imshow(mapa10[i].detach().numpy())
ax[i][11].imshow(mapa11[i].detach().numpy())
ax[i][12].imshow(mapa12[i].detach().numpy())
ax[i][13].imshow(mapa13[i].detach().numpy())
ax[i][14].imshow(mapa14[i].detach().numpy())
ax[i][15].imshow(mapa15[i].detach().numpy())
for j in range(numCols):
ax[i][j].set_yticks([])
ax[i][j].set_xticks([])
ax[0][j].set_title(literales[j])
#fig.suptitle('Imagen tipo ' + str(clases[label]), fontsize=16)
plt.tight_layout()
plt.show()
Se visualiza la predicción¶
b_x = Variable(lote_image) # batch x
output = model(b_x)
fig, (ax1, ax2) = plt.subplots( ncols=2)
y=output[0].detach().numpy()
y = (y - min(y))/(max(y)-min(y))
ax2.axis('off')
ax2.imshow(image.numpy().transpose((1, 2, 0)))
ax1.set_yticks(np.arange(10))
ax1.barh(np.arange(10), y)
ax1.set_yticklabels(clases)
ax1.set_title('Probabilidad')
plt.tight_layout()
Se prueba con fotos externas¶
def importImagesFromPath(my_directory, imageSize):
transform = transforms.Compose([
transforms.Resize(imageSize),
transforms.CenterCrop(imageSize),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
_first=True
filesName = []
for file in glob.glob(my_directory+"/*.jpg"):
PILimag = Image.open(file)
tensorImag = transform(PILimag).unsqueeze(0) ## Se transforma en tensor y se añade 1 dimensión
filesName.append(file)
if _first:
loteImag = tensorImag
_first = False
else:
loteImag = torch.cat((loteImag, tensorImag), 0)
return loteImag, filesName
imagAnimal, filesName = importImagesFromPath("./data/animalsTest", 32)
b_x = Variable(imagAnimal) # batch x
predict = model(b_x)
iTest=8
fig, (ax1, ax2) = plt.subplots( ncols=2)
y=predict[iTest].detach().numpy()
y = (y - min(y))/(max(y)-min(y))
ax2.axis('off')
ax2.imshow(imagAnimal[iTest].numpy().transpose((1, 2, 0)))
ax2.set_title(filesName[iTest])
ax1.set_title('Probabilidad')
ax1.set_yticks(np.arange(10))
ax1.barh(np.arange(10), y)
ax1.set_yticklabels(clases)
plt.tight_layout()
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).