El aprendizaje profundo (Deep Learning en inglés) con redes neuronales es actualmente una de las ramas de la inteligencia artificial más prometedora. Esta innovadora tecnología se usa comúnmente en aplicaciones como reconocimiento de imágenes, de voz, sistemas de traducción automática, entre otras.

Existen varias opciones en cuanto a tecnologías y librerías se refiere, siendo Tensorflow, desarrollada por Google, la más extendida actualmente.

Sin embargo, nos vamos a centrar en PyTorch, una alternativa emergente que está ganando tracción rápidamente gracias a su facilidad de uso y otras ventajas como su capacidad nativa para ejecutar en la GPU o tarjeta gráfica, lo que permite acelerar procesos tradicionalmente lentos como el entrenamiento de modelos. Es la librería principal de Facebook para aplicaciones de aprendizaje profundo.

Sus elementos fundamentales son los tensores, que se pueden equiparar con vectores de una o varias dimensiones.

Redes Neuronales Artificiales (RNA)

Una Red Neuronal Artificial es un sistema de nodos interconectados de forma ordenada, distribuido en capas, a través del cual una señal de entrada se propaga para producir una salida. Se conocen así porque pretenden emular de forma sencilla el funcionamiento de las redes neuronales biológicas que se encuentran en el cerebro animal.

Constan de una capa de entrada, una o varias capas ocultas y una capa de salida y se las puede entrenar para que “aprendan” a reconocer ciertos patrones. Esta característica es la que las incluye dentro del ecosistema de tecnologías conocidas como inteligencia artificial.

Las RNAs tienen varias décadas de historia, pero han cobrado gran relevancia recientemente debido a la disponibilidad de las grandes cantidades de datos y de la potencia de computación necesarias para su utilización en problemas complejos.

Han supuesto un hito histórico en aplicaciones tradicionalmente esquivas a la programación clásica, basada en reglas como el reconocimiento de imágenes o de voz.

Instalación de PyTorch

Si tenemos instalado el entorno de Anaconda, la instalación se realiza con el comando:

console

conda install pytorch torchvision -c pytorch

En caso contrario podemos usar pip de la siguiente forma:

console

pip3 install torch torchvision

Ejemplo de Red Neuronal Artificial (RNA)

Vamos a ver un ejemplo sencillo de clasificación de imágenes mediante aprendizaje profundo, utilizando el conocido conjunto de datos MNIST, que contiene imágenes de números manuscritos del 0-9.

Carga del dataset


import torch, torchvision

Para poder usar el dataset con PyTorch, se debe transformar a tensor. Para ello, definimos una transformación T que usaremos en el proceso de carga. Definimos también un DataLoader, un objeto generador de Python cuyo cometido es proporcionar las imágenes en grupos de batch_size imágenes a la vez.

Nota: es común, en el entrenamiento de redes neuronales, actualizar los parámetros cada N entradas en vez de cada entrada individual. Sin embargo, incrementar demasiado el tamaño del grupo podría ocupar demasiada memoria RAM en el sistema.


T = torchvision.transforms.Compose([torchvision.transforms.ToTensor()])
images = torchvision.datasets.MNIST('mnist_data', transform=T,download=True)
image_loader = torch.utils.data.DataLoader(images,batch_size=128)

Definición de la topología de la RNA

A continuación hemos de definir cómo va a ser la topología de nuestra red. Una RNA se compone de una capa de entrada, una o varias capas intermedias u ocultas, como se las suele conocer y una capa de salida.

El número de capas ocultas, así como la cantidad de neuronas en ellas, depende de la complejidad y el tipo de problema. En este caso sencillo vamos a implementar dos capas ocultas, con 100 y 50 neuronas respectivamente.

La clase que debemos crear hereda de la clase nn.Module y también necesitaremos inicializar los métodos de la superclase.


import torch.nn as nn
--md-var-hashtag-definimos la red neuronal 
class Classifier(nn.Module):
    def __init__(self):
        super(Classifier,self).__init__()
        self.input_layer = nn.Linear(28*28,100)
        self.hidden_layer = nn.Linear(100,50)
        self.output_layer = nn.Linear(50,10)
        self.activation = nn.ReLU()
    def forward(self, input_image):
        input_image = input_image.view(-1,28*28)                     --md-var-hashtag-convertimos la imagen a vector
        output = self.activation(self.input_layer(input_image)) --md-var-hashtag-pasada por la capa entrada
        output = self.activation(self.hidden_layer(output))   --md-var-hashtag-pasada por la capa oculta
        output = self.output_layer(output)                    --md-var-hashtag-pasada por la capa de salida
        return output

La capa de entrada

Tiene tantas neuronas como datos contienen nuestras muestras. En este caso, las entradas son imágenes de 28x28 píxeles mostrando los números manuscritos. Por tanto, nuestra capa de entrada consta de 28x28 neuronas.

La capa de salida

Tiene tantas posibles salidas como clases hay en nuestros datos, 10 en este caso (dígitos del 0 al 9). A cada entrada, los nodos de salida producirán un valor, el mayor de los cuales se identifica con la clase de salida detectada.

La función de activación

Es la función que define la salida de un nodo en función de una entrada o un conjunto de entradas. En este caso utilizamos la función sencilla ReLU (Rectified Linear Unit):

Función de propagación “Forward”

Define cómo se van realizando los cálculos desde los datos de entrada, que pasan por las distintas capas, hasta la salida. Comienza aplanando la entrada desde un Tensor bidimensional de 28x28 píxeles a uno unidimensional de 784 valores que se le pasan a la capa de entrada, con la función view.

A continuación, estos valores se propagan a las capas ocultas mediante la función de activación y finalmente a la capa de salida, que devuelve el resultado.

Entrenamiento de la RN

Para entrenar nuestra red satisfactoriamente, necesitamos definir algunos parámetros.


from torch import optim
import numpy as np
classifier = Classifier() --md-var-hashtag-instanciamos la RN
loss_function = nn.CrossEntropyLoss() --md-var-hashtag-función de pérdidas
parameters = classifier.parameters()
optimizer = optim.Adam(params=parameters, lr=0.001) --md-var-hashtag-algoritmo usado para optimizar los parámetros
epochs = 3 --md-var-hashtag-número de veces que pasamos cada muestra a la RN durante el entrenamiento
iterations = 0 --md-var-hashtag-número total de iterations para mostrar el error
losses = np.array([]) --md-var-hashtag-array que guarda la pérdida en cada iteración

Lo primero instanciamos un objeto de la clase anteriormente definida, que llamamos clasificador. Además necesitaremos:

Función de pérdida

Nos valdremos de esta función para la optimización de los parámetros, minimizando su valor durante la fase de entrenamiento de la red. Hay muchas funciones de pérdida disponibles para PyTorch. En este caso, utilizaremos la pérdida de entropía cruzada, que se recomienda en situaciones de clasificación multiclase como la que tratamos en este post.

Optimizador

Este objeto recibe los parámetros del modelo y la tasa de aprendizaje (learning rate) y se encarga de actualizar dichos parámetros en función del gradiente de la función de pérdida de forma iterativa durante el entrenamiento de la red. Se ha utilizado un algoritmo Adam para este caso, aunque hay otras posibilidades.

*Epoch *define el número de veces que se pasará el conjunto de datos por la RNA para entrenamiento. Esta práctica es una convención frecuente en el entrenamiento de sistemas de aprendizaje profundo. El resto de parámetros se usará para almacenar y mostrar resultados posteriormente.

Bucle de entrenamiento


from torch.autograd import Variable --md-var-hashtag-necesario para calcular gradientes
for e in range(epochs):
    for i, (images, tags) in enumerate(image_loader):
        images, tags = Variable(images), Variable(tags) --md-var-hashtag-Convertir a variable para derivación
        output = classifier(images) --md-var-hashtag-calcular la salida para una imagen
        classifier.zero_grad() --md-var-hashtag-poner los gradientes a cero en cada iteración
        error = loss_function(output, tags) --md-var-hashtag-calcular el error
        error.backward() --md-var-hashtag-obtener los gradientes y propagar
        optimizer.step() --md-var-hashtag-actualizar los pesos con los gradientes
        iterations += 1
        losses = np.append(losses,error.item())

El entrenamiento se realiza el número de veces que se ha definido en la variable epochs, que se refleja en el bucle exterior. Seguidamente se van realizando los siguientes pasos:

  1. Extracción de las imágenes y sus etiquetas en del objeto image_loader* *previamente definido.
  2. Transformación de ambos al tipo Variable, ya que es este tipo de datos el que nos permite almacenar los gradientes para así poder optimizar los parámetros o pesos del modelo.
  3. Pasamos la entrada (imágenes) al modelo clasificador.
  4. Ponemos los gradientes a cero. De no llevar a cabo esta operación, los gradientes se acumularían dando lugar a clasificaciones erróneas.
  5. Calculamos la pérdida, que es una medida de la diferencia entre la predicción y las etiquetas presentes.
  6. Con la función backward() obtenemos y propagamos los gradientes.
  7. Se actualizan los pesos con el objeto optimizador. Esto se conoce como método de backpropagation.
  8. Guardamos el número de iteraciones y las pérdidas en cada una de ellas, para poder mostrarlas de forma visual.

Resultados

¡Ya estamos listos para ver los resultados de nuestro entrenamiento! Usaremos la librería matplotlib*. *Como hemos guardado las iteraciones y la pérdida, simplemente tenemos que mostrarlos en una gráfica que nos dará una idea de cómo ha ido progresando nuestra RNA.


import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
--md-var-hashtag-vemos las pérdidas en cada iteración de forma gráfica
plt.plot(np.arange(iterations),losses)

Se puede apreciar cómo va disminuyendo el error de clasificación a medida que se ha ido entrenando la RNA.

Conclusión

Existen diversas opciones disponibles para la programación de RNAs, tanto libres como propietarias. Aunque TensorFlow, de Google, sigue siendo líder indiscutible del mercado, poco a poco van apareciendo alternativas interesantes que pueden aportar valor al ecosistema por medio de compatibilidades nativas, facilidad de uso, etc.

PyTorch está ganando popularidad rápidamente y puede ser una opción muy interesante, ya seas un experto o estés pensando iniciarte en el mundo del aprendizaje profundo.

Cuéntanos qué te parece.

Enviar.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.