noviembre 19, 2020

~ 9 MIN

Torchvision

< Blog RSS

Open In Colab

Torchvision

En posts anteriores hemos visto diferentes aspectos de la librería de redes neuronales Pytorch. Sin embargo, existen otras herramientas dentro del mismo ecosistema que utilizan las características fundamentales de Pytorch para construir por encima soluciones enfocadas a campos de aplicación concretos. Entre estas librerías podemos encontrar torchvision, para aplicaciones de visión artificial, torchtext, para aplicaciones de procesamiento de lenguaje, torchaudio, para aplicaciones en las que procesemos sonido, y muchas otras. Estas librerías contienen modelos, datasets y otras operaciones comunes para cada aplicación. De hecho, ya hemos utilizado algunas de estas librerías en posts anteriores. En este post veremos en detalle los aspectos más interesantes de la librería torchvision.

import torch
import torchvision

Datasets

El primer recurso que podemos aprovechar de la librería torchvision es su amplio abanico de conjuntos de datos, listos para ser utilizados. Entre estos datasets encontramos algunos tan comunes como MNIST, CIFAR10 o Imagenet para clasificación de imágenes, pero también tenemos datasets para otras tareas, como la detección de objetos o la segmentación semántica.

⚡ Encuentra la lista completa de datasets aquí

Veamos un ejemplo de cómo descargar el dataset CIFAR10.

trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True)

Como puedes ver, las imágenes se descargarán automáticamente con la opción download=True. También puedes descargar conjuntos de entrenamiento o de test con la opción train.

testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True)
import random
import matplotlib.pyplot as plt

r, c = 3, 5
fig = plt.figure(figsize=(2*c, 2*r))
for _r in range(r):
    for _c in range(c):
        ax = plt.subplot(r, c, _r*c + _c + 1)
        ix = random.randint(0, len(trainset))
        img, label = trainset[ix]
        plt.axis("off")
        plt.imshow(img)
plt.tight_layout()
plt.show()

png

De esta forma tan sencilla puedes descargar cualquier otro de los datasets en torchvision, los cuales puedes utilizar como cualquier otro objeto de tipo Dataset de Pytorch como los que utilizamos en otros posts.

Transformaciones

Una de las técnicas de regularización más utilizadas a la hora de entrenar redes neuronales para tareas de visión artificial es el data augmentation. Torchvision nos ofrece un gran conjunto de transformaciones que podemos utilizar de la siguiente manera.

trainset = torchvision.datasets.CIFAR10(
    root='./data',
    train=True,
    download=True,
    transform = torchvision.transforms.Compose([
        torchvision.transforms.ToTensor(),
        torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    ])
)

Puedes concatenar transformaciones pasando una lista de transformaciones a la clase Compose, la cual aplicará una transformación tras otra. En este caso, convertimos la imagen a un tensor de tipo float con valores entre 0 y 1 y moviendo la dimensión de los canales al principio (requisito para entrenar redes convolucionales). Después, la normalizamos con una valor medio de 0.5 y una desviación típica de 0.5, obteniendo tensores entre -1 y 1. De esta manera, nuestro dataset ya está listo para alimentar una red convolucional.

img, label = trainset[0]
img.dtype, img.shape, img.max(), img.min()
(torch.float32, torch.Size([3, 32, 32]), tensor(1.), tensor(-1.))

De la misma manera, podemos asignar transformaciones aleatorias que nos darán una versión diferente de una misma imagen cada vez que se la pidamos al dataset.

trainset = torchvision.datasets.CIFAR10(
    root='./data',
    train=True,
    download=True,
    transform = torchvision.transforms.Compose([
        torchvision.transforms.RandomCrop((28,28)),
        torchvision.transforms.Resize((32,32)),
        torchvision.transforms.RandomHorizontalFlip(),
        # ...
        torchvision.transforms.ToTensor(),
        torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    ])
)
Files already downloaded and verified
r, c = 3, 5
fig = plt.figure(figsize=(2*c, 2*r))
for _r in range(r):
    for _c in range(c):
        ax = plt.subplot(r, c, _r*c + _c + 1)
        ix = 10
        img, label = trainset[ix]
        plt.axis("off")
        # desnormalizar
        img = img*0.5 + 0.5
        img = img.permute(1, 2, 0)
        plt.imshow(img)
plt.tight_layout()
plt.show()

png

Modelos

Una de las características más interesantes de la librería torchvision es el gran listado de modelos que nos ofrece. Estos modelos contienen implementaciones de redes convolucionales populares tales como Resnet, Mobilenet, VGG... Todas listas para ser utilizadas y con la posibilidad de descargar sus pesos pre-entrenados en el dataset Imagenet para hacer transfer learning.

⚡ Puedes encontrar un listado de los modelos disponibles aquí

Vamos a ver un ejemplo de cómo descargar una red neuronal utilizando torchvision.

resnet = torchvision.models.resnet18()

Una vez descargada la red, podemos inspeccionar todas sus capas.

resnet
ResNet(
  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
  (layer1): Sequential(
    (0): BasicBlock(
      (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)
    )
    (1): BasicBlock(
      (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)
    )
  )
  (layer2): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer3): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer4): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
  (fc): Linear(in_features=512, out_features=1000, bias=True)
)

Para descargar la red pre-entrenada, es tan sencillo como

resnet = torchvision.models.resnet18(pretrained=True)

Una práctica muy común a la hora de entrenar redes convolucionales es descargar un modelo ya entrenado y modificarlo para ajustarlo a nuestra tarea en particular. Por ejemplo, si queremos utilizar la red resnet18 para clasificar imágenes del dataset CIFAR10 obtendremos resultados no deseados ya que la red original fue entrenada para 1000 clases, mientras que CIFAR10 solo tiene 10.

Para adaptar la red, podemos simplemente sustituir la última capa de la siguiente manera.

num_classes = 10
resnet.fc = torch.nn.Linear(resnet.fc.in_features, num_classes)
resnet
ResNet(
  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
  (layer1): Sequential(
    (0): BasicBlock(
      (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)
    )
    (1): BasicBlock(
      (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)
    )
  )
  (layer2): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer3): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer4): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
  (fc): Linear(in_features=512, out_features=10, bias=True)
)
img, label = trainset[ix]
# simulamos un batch de 3 imágenes
batch = torch.stack([img, img, img])
y = resnet(batch)
y.shape
torch.Size([3, 10])

De la misma manera, podemos descargar cualquier modelo disponible y adaptarlo para nuestro caso o utilizarlo como componente en un sistema más complejo.

Operaciones

Por último, torchvision nos ofrece algunas operaciones comunes en aplicaciones de visión artificial. Algunos ejemplos, que ya hemos usado en posts anteriores, son: calcular la métrica IoU, el algoritmo de NMS, componentes que encontramos en algunas arquitecturas como RoIAlign o FeaturePyramidNetwork, entre otros.

En el siguiente ejemplo definimos dos conjuntos de cajas y calculamos el IoU de todas las combinaciones.

# x_min, y_min, x_max, y_max
box1 = torch.tensor([[0, 0, 1, 1], [0, 0, 0.5, 0.5]])
box2 = torch.tensor([[0, 0, 1, 1], [0.5, 0.5, 1, 1]])
torchvision.ops.box_iou(box1, box2)
tensor([[1.0000, 0.2500],
        [0.2500, 0.0000]])

Resumen

En este post hemos visto las diferentes posibilidades que nos ofrece la librería torchvision a la hora de llevar a cabo tareas de visión artificial. Construyendo sobre la funcionalidad básica de Pytorch, esta librería nos ofrece datasets comunes, modelos pre-entrenados, transformaciones y operaciones útiles a la hora de entrenar redes convolucionales para tareas de visión por computador.

< Blog RSS