Aprenda a crear una red neuronal simple y una red neuronal convolucional más precisa, con la biblioteca de aprendizaje profundo PyTorch.

PyTorch es una biblioteca de cómputo tensorial basada en Python con soporte de alto nivel para arquitecturas de redes neuronales. También es compatible con el cómputo de descarga de GPU. Un producto del equipo de investigación de IA de Facebook y abierto hace poco más de un año, PyTorch se ha convertido rápidamente en la primera opción de muchos profesionales para el aprendizaje profundo.

En este tutorial, profundizaremos en los principios básicos de la ejecución de PyTorch en Linux, desde la instalación hasta la creación y el entrenamiento de una red neuronal simple que puede reconocer dígitos. Lo limitaremos abordando un ejemplo más complicado que utiliza redes neuronales convolucionales (CNN) para mejorar la precisión.

Esta no será una introducción completa a las redes neuronales, pero se explicarán algunos conceptos. Para más información de redes neuronales haz click aquí.

Si bien una computadora con una GPU no es necesaria para este tutorial, se recomienda.

Instalar PyTorch

La forma más fácil de instalar PyTorch es usar la distribución Anaconda Python. Si tiene Anaconda instalado, puede obtener la última PyTorch ingresando este comando:

$ conda install pytorch torchvision -c pytorch

Si quieres usar Python pip, entonces para Python 2.6, usa estos comandos:

$ pip install http://download.pytorch.org/whl/cu80/torch-0.3.0.post4-cp27-cp27mu-linux_x86_64.whl

$ pip install torchvision

O para Python 3.6, usa estos:

$ pip3 install http://download.pytorch.org/whl/cu80/torch-0.3.0.post4-cp36-cp36m-linux_x86_64.whl

$ pip3 install torchvision   

Un modelo PyTorch

Con PyTorch instalado, vamos a hacer un modelo de aprendizaje profundo, que crea una red neuronal que examinará las imágenes de los dígitos manuscritos del conjunto de datos MNIST e identificará los números. Aquí hay un ejemplo de algunos de los dígitos:

Primero, tendremos que tener el conjunto de datos. Si bien se puede descargar estos directamente desde el sitio web de MNIST y cargarlos en PyTorch, PyTorch nos permite descargar conjuntos de datos de referencia estándar como MNIST, CIFAR-10, COCO y otros sin mucho alboroto.

    transforms = transforms.Compose([
                            transforms.ToTensor(),
                            transforms.Normalize((0.1307,), (0.3081,))])

    train_loader = torch.utils.data.DataLoader(
        datasets.MNIST(‘../data’, train=True, download=True,
               transform=transforms),
        batch_size=64, shuffle=True)
    test_loader = torch.utils.data.DataLoader(
        datasets.MNIST(‘../data’, train=False, transform=transforms),
        batch_size=64, shuffle=True)

Este código creará dos objetos DataLoader que descargarán el conjunto de datos MNIST y servirán lotes aleatorios de 64 imágenes de la colección de 60000 de MNIST.

Observe el argumento de transformaciones aplicado a ambos cargadores. El paquete de torchvision de PyTorch le permite crear un complejo entramado de transformaciones para el aumento de datos que se aplican a las imágenes a medida que se extraen del DataLoader, incluidos el recorte, la rotación, la reflexión y la re-escala.

En nuestro ejemplo, no estamos haciendo nada de eso, pero aprovecharemos la interconexión para transformar los datos de imagen en un tensor. (En el caso de MNIST, este tensor es una matriz de 1x28x28, ya que las imágenes son todas de escala de grises de 28×28 píxeles).

También normalizaremos ese tensor a la desviación estándar y la media del conjunto de datos MNIST. Esto nos lleva de una matriz de píxeles que van de 0 a 255 a un tensor de valores que van de -1 a 1. Hacemos esto porque el entrenamiento de la red neuronal será mucho mejor dentro del rango más estrecho.

Una red neuronal PyTorch

A continuación, creemos nuestra primera red neuronal creando una nueva clase de Python que herede el modulo nn.Module:

     class FirstNet(nn.Module):
            def __init__(self,image_size):
                super(FirstNet, self).__init__()
                self.image_size = image_size
                self.fc0 = nn.Linear(image_size, 1000)
                self.fc1 = nn.Linear(1000, 50)
                self.fc2 = nn.Linear(50, 10)

            def forward(self, x):
                x = x.view(-1, self.image_size)
                x = F.relu(self.fc0(x))
                x = F.relu(self.fc1(x))
                x = F.relu(self.fc2(x))
                return F.log_softmax(x)

La convención general para estas clases de red es que usted cree todas sus capas en el constructor, luego establezca su relación en el método forward ().

Aquí estamos creando una red muy simple donde todas nuestras capas son lineales, la clásica red neuronal “totalmente conectada”, que aplica una traducción lineal a todas las entradas (los valores en la capa se inicializan aleatoriamente). La red comienza con image_size, el tamaño de nuestras imágenes MNIST, y finaliza con 10 salidas, correspondientes a los 10 dígitos (de cero a nueve) que estamos intentando reconocer.

El método forward () nos muestra cómo fluye una imagen a través de la red. Primero convertimos el tensor de la imagen (1x28x28 una vez que llega a través de la tubería de transformación) en una forma que la primera capa pueda entender. Hacemos esto a través del método view (), que en este caso aplana el tensor en una forma de 1×784, la forma de la primera capa lineal.

Las siguientes tres líneas de código aplican cada capa a los datos entrantes sucesivamente, pero también hay una llamada F.relu () que ocurre en cada nivel. ¿Que es esto? Bueno, es un ejemplo de una función de activación. Estas funciones se pueden aplicar a las salidas de cada capa e insertar la no linealidad en el sistema. Sin ellos, tendríamos esencialmente un modelo de regresión lineal. Con ellos, la red neuronal obtiene el poder de aproximaciones universales de funciones.

Hay muchos tipos diferentes de funciones de activación, pero la mayoría de las arquitecturas modernas de aprendizaje profundo utilizarán la ReLU o Unidad lineal rectificada. Aunque esto suena intimidante, es literalmente solo una función f (x) donde f (x) = max (x, 0). La función devuelve cero si la salida es menor que cero, o devuelve la salida original si es mayor que cero.

Finalmente, utilizamos una activación diferente, softmax, en la salida de la capa final. Softmax se aplica a la salida en la capa final para estar en el rango de 0 a 1 para cada una de las 10 clases de salida. Estos se convertirán en estimaciones de probabilidad para cada clase, por lo que para determinar la clase pronosticada de una imagen, encontramos la clase con la probabilidad más cercana a 1.

La creación de una instancia de la red se realiza en la forma tradicional de Python de llamar al constructor:

   model = FirstNet(image_size=28*28)

Si habilitaste el GPU, puedes copiar el modelo al GPU al llamar el métido cuda():

    model.cuda()

Entrenamiento y pruebas del modelo

Habiendo creado nuestro modelo, ahora tenemos que entrenarlo. En algunos marcos, como Keras, la mayor parte del entrenamiento se maneja para usted detrás de escena.

En PyTorch, tenemos que escribir un procedimiento de entrenamiento explícito. Aquí hay un ejemplo, tomado de los ejemplos de PyTorch:

 optimizer = optim.SGD(model.parameters(), lr=lr)

    def train(epoch, model):
        model.train()
        for batch_idx, (data, labels) in enumerate(train_loader):
            if torch.cuda.is_available():
                data, labels = data.cuda(), labels.cuda()
            data, labels = Variable(data), Variable(labels)
            optimizer.zero_grad()
            output = model(data)
            loss = F.nll_loss(output, labels)
            loss.backward()
            optimizer.step()
            if batch_idx % 100 == 0:
                    print(‘Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}’.format(
            epoch, batch_idx * len(data), len(train_loader.dataset),
            100. * batch_idx / len(train_loader), loss.data[0]))

Están sucediendo muchas cosas aquí, pero es bastante sencillo si lo tomamos una línea a la vez. Primero, antes de crear el método train (), instanciamos nuestro optimizador, que actualizará los valores de las capas de la red neuronal en cada paso a través de cada lote desde el DataLoader. Estos valores deberían ser más precisos a medida que el entrenamiento continúa.

Hay varios optimizadores diferentes entre los que puede elegir, como RMSProp, AdaGrad, y el más utilizado hoy en día, ADAM. Pero aquí usaremos el clásico Stellastic Gradient Descent con una tasa de aprendizaje de 0.001. La tasa de aprendizaje le dice al optimizador cuánto cambiar los valores en las capas en cada pase. Establezca la tasa de aprendizaje demasiado alta, y su red puede rebotar entre alta y baja precisión. Establecerlo demasiado bajo, y es posible que veas que el entrenamiento lleva mucho tiempo. Iremos con 0.001, que es un punto de partida decente.

En el método train (), primero colocamos el modelo en modo de entrenamiento y luego recorremos todos los lotes en el conjunto de datos. Para cada lote, copiamos los datos de imagen y las etiquetas (es decir, el dígito que representa la imagen) a la GPU y reiniciamos el optimizador para este lote.

Las imágenes en el lote se pasan a través del modelo para generar el tensor de salida, y nuestras predicciones. Este resultado se compara con las etiquetas (las respuestas correctas) a través de una función de pérdida. Estamos usando la función de pérdida de verosimilitud de registro negativa aquí, que se usa comúnmente en las arquitecturas de clasificación.

Luego invocamos la magia PyTorch. La llamada a loss.backward () calcula la retropropagación, resolviendo el gradiente de la pérdida con respecto a los valores en las capas (o “ponderaciones”). Luego, al llamar a optimizer.step () ajustamos las capas usando este gradiente y la función del optimizador.

Una red neuronal convolucional PyTorch

La mayoría de las arquitecturas de aprendizaje profundo de visión artificial en la actualidad se componen de pilas de redes neuronales convolucionales (CNN) en lugar de las capas completamente conectadas que se muestran arriba.

Una red neuronal convolucional puede considerarse como un grupo de pequeños filtros que pasan sobre la imagen. Cada filtro está entrenado para buscar ciertas cosas, por lo que un filtro podría terminar reconociendo los ojos, otro podría buscar las narices, y así sucesivamente. Aquí hay una red neuronal convolucional muy básica en PyTorch:

 class CNNNet(nn.Module):
        def __init__(self):
            super(CNNNet, self).__init__()
            self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
            self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
            self.conv2_drop = nn.Dropout2d()
            self.fc1 = nn.Linear(320, 50)
            self.fc2 = nn.Linear(50, 10)

        def forward(self, x):
            x = F.relu(F.max_pool2d(self.conv1(x), 2))
            x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
            x = x.view(-1, 320)
            x = F.relu(self.fc1(x))
            x = F.dropout(x, training=self.training)
            x = self.fc2(x)
            return F.log_softmax(x, dim=1)

Si reinicializamos el optimizador, creamos un nuevo modelo con esta red y lo ejecutamos durante 10 veces, repentinamente mejoraremos la precisión a más del 90 por ciento. Además de las capas convolucionales (conv2d), los otros conceptos nuevos que se presentan aquí son MaxPooling, que es una forma de disminución de muestreo, y Dropout, lo que obliga a la red a descontar aleatoriamente una cantidad de activaciones cuando está en modo de entrenamiento. Esto ayuda al modelo a entrenarse de una manera más generalizable, es decir, aprender a discernir la estructura de un 1 en lugar de simplemente aprender a reconocer los valores de píxel de las imágenes de entrenamiento.