<meta name="monetization" content="$ilp.uphold.com/EXa8i9DQ32qy">
Antes de crear una red neuronal necesitamos importar nuestras imágenes en un formato apto. Este texto busca introducir los básicos de la importación, visualización y manejo de datos en el framework para deep learning PyTorch. Nos enfocamos en imágenes pero los conceptos son aplicables para todo tipo de datos; al final, las imágenes no son más que números, comunmente enteros, asignados a cada pixel, o sea datos.
Importaremos imágenes  en formato numpy, crearemos una subclase de 
torch.utils.data.Datasettorchvision.transformsmatplotlib.pyplottorch.utils.data.DataLoaderUna nota antes de empezar. Si bien es más sencillo escribir 
Datasettorch.utils.data.Datasettorchvisiontorch¿Dónde correr el código?
En mi opinión, la mejor forma de aprender deep learning es escribir y escribir código. Esto es irrealizable, en terminos de tiempo, si se utiliza el poder de una CPU. Estaremos trabajando en Google Colab donde se nos asigna de manera gratuita por máximo 12 horas un GPU modelo Tesla K80. Solo necesitamos una cuenta de Google.
Ingresar a 
https://colab.research.google.comNUEVO BLOCK DE NOTAS DE PYTHON 3Entorno de ejecuciónCambiar tipo de entorno de ejecuciónCon 
torch.cuda.is_available()¿Dónde está lo que necesitamos?
Para aprender a navegar por la documentación de PyTorch puedes leer: Entendiendo PyTorch: las bases de las bases para hacer inteligencia artificial.
En la sección Python API de la barra vertical izquierda de la documentación de PyTorch encontramos el paquete 
torch.utils.dataEn él encontramos herramientas útiles para importar nuestros datos y tenerlos listos para operar sobre ellos; 
torch.utils.data.Datasettorch.utils.data.DataLoaderEl segundo paquete que utilizaremos es 
torchvisiontorchaudiotorchtextEn este texto usaremos únicamente el paquete 
torchvision.transformsInstanciando Dataset
DatasetCon nuestros datos originales debemos crear un objeto de la clase 
Datasettorch.utils.data.Datasettorch.utils.data.Dataset__getitem__()__len__()permitirá que__len__()nos retorne el número de datos en nuestrolen(dataset); yDatasetnos permite hacer indexing de tal forma que, por ejemplo,__getitem()__te devuelva el primer elemento en elMiDataset[0].Dataset
Importemos librerías necesarias para este texto.
import torch
import torchvision
import tensorflow as tfPor la facilidad de descarga, usaremos Tensorflow para descargar  la famosa base de datos MNIST, que cuenta con imágenes de números, en formato numpy (no te preocupes si no sabes qué es). Con 
torchvision.datasets.MNISTtorch.utils.data.DatasetQueremos construir el dataset para una red neuronal que mapee cada imagen con su respectivo número. Por ejemplo, si la imagen tiene un 7 entonces queremos que nuestra red neuronal identifique que dicha imagen tiene un 7, así de sencillo. Así luce la base de datos:
Primero, aprendamos cómo crear nuestro set de datos. Importamos:
(x_train, y_train), (x_val, y_val) = tf.keras.datasets.mnist.load_data()Por el momento solo hagamos caso a 
x_trainy_traintype(x_train)numpy.ndarrayx_train_tensors = torch.from_numpy(x_train).float().cuda()El tipo de 
x_train_tensorstorch.Tensortorchvision.transformstorch.utils.data.DatasetMNISTDatasetclass MNISTDataset(torch.utils.data.Dataset):
  def __init__(self, numpy_data):
    self.data = numpy_data
  
  def __len__(self):
    return len(self.data)
  def __getitem__(self, idx):
    idx_numpy = self.data[idx]
    return idx_numpyAntes de explicar lo que hicimos, introduzcamos brevemente los métodos mágicos (magic methods en inglés). Estos métodos siempre van rodeados de dos guiones bajos y fueron creados para proveer de ciertas cualidades "especiales" a los objetos instanciados de las clases en las que son definidos.
El método mágico más común es 
__init____len____getitem__Ahora vayamos al código paso por paso. Primero, con el método constructor, 
__init__selfnumpy_dataselfMNISTDatasetnumpy_dataDentro del constructor, 
self.dataMNISTDatasetdatanumpy_datadatanumpy_dataselfself.datadatasetMNISTDatasetdataset = MNISTDataset(x_train)El segundo método mágico, y es obligatorio para crear un objeto 
Dataset__len__MNISTDatasetdatasetdatalen(dataset)El tercer método mágico, también obligatorio, es 
__getitem__idxself.data[idx]dataset[0]dataset[1]dataset[n]Tenemos nuestro primer objeto instanciado de una subclase de tipo 
torch.utils.data.Dataset.Visualizando nuestro dataset
datasetCon el paquete 
matplotlib.pyplotmatplotlibPrimero usemos el siguiente código sobre nuestro 
datasetMNISTDatasetimport matplotlib.pyplot as plt
plt.figure()
for i in range(len(dataset)):
    imagen = dataset[i]
    plt.subplot(1, 4, i + 1)
    plt.tight_layout()
    plt.title("Imagen {i}".format(i=i))
    plt.imshow(imagen, cmap="gray")
    if i == 3:
        breakque nos retorna:
El código importa el paquete 
matplotlib.pyplotpltplt.figure()fordataset[i]iif i == 3plt.show()breakplt.subplot(1, 4, i + 1)iii = 0i + 1 = 1plt.tight_layout()plt.imshow(imagen, cmap='gray')cmap="gray"Transformando los datos
PyTorch nos permite agregar transformaciones a nuestras imágenes, esto es útil, por ejemplo, en los casos en que nuestras imágenes tengan diferentes tamaños (para poderlas pasar por el grafo de nuestro modelo tenemos que tenerlas con el mismo tamaño), necesitemos aumentar el tamaño de nuestro dataset, o simplemente queramos modificarlas. 
Para esto recurrimos al paquete 
torchvision.transformsEn este paquete encontramos herramientas para crear transformaciones en forma de clases con métodos mágicos 
__call__torchvision.transforms.CenterCroptorchvision.transforms.ToTensorMás aún, encontramos una herramienta para encadenar transformaciones y aplicarlas de golpe a una imagen. 
torchvision.transforms.Composetransformaciones = torchvision.transforms.Compose([
        torchvision.transforms.CenterCrop(10),
        torchvision.transforms.ToTensor()
    ])En ese sentido, estamos primero cortando por el centro nuestras imágenes y después convirtiéndolas en tensores. Las transformaciones en torchvision.transforms se aplican solo en tensores o PIL Images, no numpy, no pandas. En la columna derecha de la documentación de 
torchvision.transformstransforms.ToPILImage()torchvision.transforms.Composetransformaciones = torchvision.transforms.Compose([
        torchvision.transforms.ToPILImage(),
        torchvision.transforms.CenterCrop(10),
        torchvision.transforms.ToTensor()
    ])Ahora algo de Python. Si dentro de la documentación ingresamos al código fuente de 
torchvision.transforms.Compose__call__(self, img)imgPodemos correr las transformaciones sobre una de nuestras imágenes en nuestro objeto 
datasetMNISTDatasetimagen_trans = transformaciones(dataset[0])Para visualizar nuestras nuevas imágenes utilicemos el mismo código previo pero adecuado a la visualización de un tensor.
plt.figure()
for i in range(len(dataset)):
    imagen = dataset[i]
    imagen_trans = transformaciones(imagen)
    imagen_trans = torch.squeeze(imagen_trans)
    plt.subplot(1, 4, i + 1)
    plt.tight_layout()
    plt.title("Imagen {i}".format(i=i))
    plt.imshow(imagen_trans.cpu(), cmap="gray")
    if i == 3:
        break¿Qué es nuevo? Con 
imagen_trans = transformaciones(imagen)datasetimagen_trans.shapetorch.Size([1, 14, 14])plt.imshowimagen_trans = torch.squeeze(imagen_trans)torch.Size([14, 14])plt.imshow(imagen_trans.cpu(), cmap="gray")imagen_transplt.imshowRecortar a la mitad el tamaño de nuestras imágenes con 
torchvision.transforms.CenterCrop(14)transformaciones = torchvision.transforms.Compose([
        torchvision.transforms.ToPILImage(),
        torchvision.transforms.RandomVerticalFlip(p=0.9),
        torchvision.transforms.ToTensor()
    ])Con 
torchvision.transforms.RandomVerticalFlip(p=0.9)datasetEs conveniente que  las transformaciones se hagan imagen por imagen pero dentro de nuestra subclase de 
torch.utils.data.DatasetMNISTDatasetMNISTDataset__getitem__datasety_trainMNISTDatasetclass MNISTDataset(torch.utils.data.Dataset):
  def __init__(self, numpy_data, etiquetas, transformaciones = None):
    self.data = numpy_data
    self.transformaciones = transformaciones
    self.etiquetas = etiquetas
  
  def __len__(self):
    return len(self.data)
  def __getitem__(self, idx):
    
    idx_numpy = self.data[idx]
    idx_etiqueta = self.etiquetas[idx]
    if self.transformaciones:
      idx_numpy = self.transformaciones(idx_numpy)
    muestra = {"imagen" : idx_numpy,
               "etiqueta" : idx_etiqueta}
  
    return muestra¿Qué es nuevo? Primero, en el método constructor estamos incluyendo dos argumentos formales: las etiquetas y las transformaciones. Segundo, el método 
__getitem__(self, idx)idxidx_etiquetatorchvision.transforms.Composeidxidx_numpyidxidxListo. Tenenemos un 
datasetDataLoader
El último paso para tener listos nuestros datos para el entrenamiento es convertir nuestro dataset, instancia de la subclase MNISTDataset, en un objeto de tipo 
torch.utils.data.DataLoaderA él debemos ingresar un objeto instanciado de una subclase de la clase 
torch.utils.data.DatasetdatasetMNISTDatasetdataloader = torch.utils.data.DataLoader(dataset, batch_size=8,
                        shuffle=True)Estamos indicando que queremos que cada batch tenga cuatro imágenes; y que cada vez que iniciemos una nueva ronda de entrenamiento, el 
DatasetDataLoaderSi bien un objeto 
torch.utils.data.DataLoadernext(iter(dataloader))Datasetnext(iter(dataloader))batch = next(iter(dataloader))
Aquí está la magia de 
torch.utils.data.DataLoaderbatchdatasetEn siguientes textos veremos por qué es tan útil tener nuestros datos en este formato. Comenzaremos con la creación de nuestros modelos.
