Saltar al contenido
Home » Desarrollo » Redes Neuronales Convolucionales para el reconocimiento de imágenes

Redes Neuronales Convolucionales para el reconocimiento de imágenes

Hoy en día, el aprendizaje profundo (deep learning) ha permitido el desarrollo de tecnologías avanzadas para el procesamiento de datos, dígase: señales, imágenes, textos y otros. Muchos de estos datos poseen una alta dimensionalidad, o sea demasiados atributos que hacen que el modelo a entrenar sea muy complejo. Esto tiene como consecuencia que modelos de aprendizaje automático tradicionales no den buenos resultados. Para abordar este problema, se ha hecho uso de técnicas de extracción de características y reducción de la dimensionalidad. El uso de Redes Neuronales Convolucionales (CNN, por sus siglas en inglés) en el procesamiento de imágenes y visión artificial ha sido vital para este propósito.

Una CNN es una red neuronal multicapa, cuyo diseño fue inspirado en el córtex visual de los animales. Fueron creadas en el año 1989 por Yann LeCun para resolver el problema de identificación de dígitos escritos a mano. Para procesar una imagen con la CNN, se aplica un filtro de convolución, digamos una matriz para extraer lo fundamental de esta. La operación que se realiza no es más que una suma ponderada entre los elementos de la imagen (matriz A) y el filtro (matriz B). Esta matriz B es más pequeña que A y se desplaza tanto por las filas y columnas de A en su vecindad. La figura ilustra mejor una operación de convolución sobre una matriz. Se debe tener en cuenta que una imagen a color se representa como una matriz de enteros (píxeles) y consta de tres canales (rojo, verde y azul).

Puedes profundizar más sobre el tema en este post, además recomendamos el libro Deep Learning de Ian Goodfellow et. al (2016) en su capítulo 9. En esta lectura, conocerás cómo las imágenes tienen muchas propiedades estadísticas que son invariables a la traslación, propiedades que se aprovechan en una CNN para obtener una segmentación de la imagen.

Procesando dígitos escritos a mano con una CNN

Vamos a ver algunas de las aplicaciones más notorias de las CNN. En este caso proponemos una arquitectura neuronal para abordar el problema de reconocimiento de dígitos escritos a mano. Este es un problema muy estudiado en la literatura desde que fue propuesto por Yan LeCun en: Handwritten Digit Recognition with a Back-Propagation Network (1989). Desde el año 2012-2021 hemos encontrado más de 75 enfoques para resolver el problema. En esta revisión, pueden acceder a trabajos completos y algunos de estos tienen sus códigos de fuente disponibles en varias tecnologías, que van desde C++ hasta Python.

Como ven en la figura anterior, el problema a resolver consiste en: Dado una imagen en escala de grises, que representa un dígito escrito a mano, desarrollar un estimador que permita clasificar la imagen a su correspondiente dígito. Como problema de aprendizaje supervisado, específicamente de clasificación, las etiquetas o clases predictoras serían los dígitos del 0-9 que corresponden a cada imagen de entrada.

Sin más, ¡manos a la obra!

Vamos a reconocer dígitos con nuestra CNN

Haremos uso del popular dataset MNIST, que consiste en una base de datos de imágenes de 28×28. Tiene 60 000 ejemplos para el entrenamiento y 10 000 para pruebas. La siguiente propuesta está desarrollada en Python 3.7. Por otro lado, hacemos uso de las bibliotecas: Pytorch 1.2 para CUDA (framework de aprendizaje automático), Scikit-learn 0.23.2 y Torchvision 0.4.0.

1. Preparando el archivo principal trainer_model.py

import torch
import torchvision
import matplotlib.pyplot as plt
from models.model_cnn_builder import ModelCNN
import torch.optim as optim
import torch.nn as nn
import torchvision.transforms as transforms
from sklearn.metrics import classification_report,accuracy_score
import os
EPOCHS = 20
if __name__ == "__main__":
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    transform = transforms.Compose(
        [transforms.ToTensor(),
         transforms.Normalize([0.5], [0.5])])
    wdir = os.getcwd() + "/"
    data_img_train = torchvision.datasets.MNIST('/tmp/mnist/data', transform=transform, train=True, download=True)
    data_img_test = torchvision.datasets.MNIST('/tmp/mnist/data', transform=transform, train=False, download=True)
    train_loader = torch.utils.data.DataLoader(data_img_train, batch_size=64, shuffle=True)
    test_loader = torch.utils.data.DataLoader(data_img_test, batch_size=64, shuffle=True)
    # Printing and plotting some information of input
    print("Shape of an example {}".format(data_img_train.data.shape))
    
    plt.imshow(data_img_train.data[0], cmap='gray')
    plt.show()
    plt.imshow(data_img_test.data[0], cmap='gray')
    plt.show()

Salida:

Shape of an example torch.Size([60000, 28, 28])

Se puede observar que hacemos uso de CUDA (Dispositivo de Arquitectura Computacional Unificada, siglas en inglés). Pytorch soporta varios dispositivos de procesamiento para garantizar el paralelismo durante el entrenamiento. El problema de entrenamiento de una CNN es costoso computacionalmente debido al número de parámetros o pesos de la red que deben ser aprendidos. Por ello, algunos enfoques buscan paralelizar usando los núcleos de CPU, o los de la GPU mediante CUDA.

Con torchvision.datasets.MNIST podemos cargar nuestra base de datos de imágenes y descargarla a nuestro disco localmente. Además, hacemos uso de un Dataloader para acceder de manera perezosa a las imágenes y no sobrecargar la memoria.

2. Implementamos la CNN en model_cnn_builder.py

En este punto, hacemos uso de las facilidades que brinda el framework Pytorch para implementar nuestra arquitectura neuronal.

from builtins import super
from collections import OrderedDict
import torch.nn as nn
class ModelCNN(nn.Module):
    def __init__(self,labels):
        super(ModelCNN, self).__init__()
        self.convnet=nn.Sequential(OrderedDict([
            ('c1',nn.Conv2d(1,128,kernel_size=(3,3))),#128
            ('relu1', nn.ReLU()),
            ('c2', nn.Conv2d(128, 128, 5)),
            ('relu2', nn.ReLU()),
            ('maxpool2', nn.MaxPool2d(kernel_size=(2,2))),
            ('c3', nn.Conv2d(128, 256, 5)),
            ('relu3', nn.ReLU()),
            ('maxpool3', nn.MaxPool2d(kernel_size=(2,2)))
        ]))
        self.dropout=nn.Dropout(p=0.25) # For Dropout regularization
        self.fc = nn.Sequential(OrderedDict([
            ('f4',nn.Linear(2304,128)),
            ('relu4', nn.ReLU()),
            ('f5', nn.Linear(128, labels)),
            ('sig5', nn.LogSoftmax(dim=-1))
        ]))
    def forward(self, x):
        out=self.convnet(x)
        out=out.view(x.size(0),-1)
        # self.fc.float() #Only use in CUDA
        out=self.fc(out)
        # out = self.dropout(self.fc(out))
        return out

El siguiente diseño es la modelación del código anterior:

Observar que la salida es de la capa completamente conectada (Fully-Connected) es una función de activación softmax. De ella, usamos el máximo valor de probabilidad (un valor continuo de 0-1) para las posibles salidas del modelo (10 etiquetas). Se utiliza como regularizador Dropout a un 25 %. Dicho valor significa que el 25 % de los nodos de salida de la última capa CNN serán ignorados aleatoriamente. Un regularizador es una técnica para disminuir la complejidad del modelo de aprendizaje automático y evita el sobre-ajuste (overfitting).

3. Entrenando el modelo

Al fichero del paso 1 (trainer_model.py), le añadimos la siguiente funcionalidad:

def train_cnn_model(model, criterion, optimizer):
    for epoch in range(EPOCHS):  # loop over the dataset multiple times
        running_loss = 0.0
        for batch in train_loader:
            # get the inputs; data is a list of [inputs, labels]
            inputs, labels = batch
            inputs = inputs.to(device)
            labels = labels.to(device)
            # zero the parameter gradients
            optimizer.zero_grad()
            # forward + backward + optimize
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
            # print statistics
            running_loss += loss.item()
        # print some statistic per epoch
        print("Epoch ran: {} loss: {}".format(epoch, running_loss))

En esta función, se hace uso de un criterion y un optimizer. Ambos son elementos fundamentales para el entrenamiento de un modelo de aprendizaje automático con Pytorch. El primero define la función de pérdida a utilizar, en nuestro caso Cross-entropy loss. El segundo, representa el tipo de algoritmo o regla de cambio para el gradiente descendente. En nuestro caso utilizaremos Adam o gradiente adaptativo.

4. Probando el modelo

Al fichero del paso 1 (trainer_model.py), le añadimos la siguiente funcionalidad para evaluar el modelo:

def evaluate_cnn_model(wdir, model):
    predictions = []
    labels_o = []
    # saving model parameters
    torch.save(model.state_dict(), wdir + "mycnn.pt", _use_new_zipfile_serialization=False)
    # evaluate model
    with torch.no_grad():
        for batch in test_loader:
            inputs, labels = batch
            inputs = inputs.to(device)
            labels = labels.to(device)
            # calculate outputs by running images through the network
            outputs = model(inputs)
            # the class with the highest energy is what we choose as prediction
            _, predicted = torch.max(outputs.data, 1)
            predictions.extend(predicted.cpu().numpy())
            labels_o.extend(labels.cpu().numpy())
    accuracy = accuracy_score(predictions, labels_o)
    print("Accuracy for each fold: {}".format(accuracy))
    print("\n" + classification_report(labels_o, predictions))

5. Ejecutando todo

Para completar el fichero trainer_model.py, solo queda crear una instancia de nuestro modelo neuronal y llamar a los métodos para entrenar (train_cnn_model) y probar (evaluate_cnn_model).

Con solo añadir lo siguiente en el bloque principal logramos nuestro objetivo:

    # declare the model with 10 labels
    model = ModelCNN(10).to(device)
    # define criterion and loss function
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    # train the model
    train_cnn_model(model, criterion, optimizer)
    # evaluate the model
    evaluate_cnn_model(wdir, model)

Finalmente, veamos el resultado del entrenamiento para 20 épocas:

Ahora evaluamos nuestro modelo haciendo uso de las métricas Accuracy, Recall y F1-score. Con ello, los resultados se resumen por cada etiqueta del dataset en el conjunto de prueba. La columna support representa la cantidad de ejemplos por etiquetas.

Entrenamos la propuesta haciendo uso de una tarjeta gráfica NVIDIA GeForce GTX 980. De esta manera, recomendamos hacer uso de un hardware similar, ya que en CPU es más lento el proceso. Observen que el Accuracy obtenido para el conjunto de entrenamiento es de 99.23 %. Este resultado es comparable con valores reportados en el estudio del arte, incluso algunos trabajos son superados.

Espero que hayan disfrutado de este post y que les resulte útil para adentrarse en este mundo del deep learning, les dejamos el proyecto completo en GitHub.

1 comentario en «Redes Neuronales Convolucionales para el reconocimiento de imágenes»

  1. In.. presionante, teresante.
    Siempre quise saber cómo era posible el reconocimiento de imágenes. Y aún quiero.
    Gracias por este post, Toledano.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *