Reconhecimento de Dígitos Manuscritos com LeNet

A rede neural LeNet-5 é uma das primeiras redes neurais convolucionais amplamente utilizadas, desenvolvida por Yann LeCun. Ela consiste em 7 camadas (excluindo a entrada), onde cada camada possui parâmetros treináveis. Cada Feature Map extraído utiliza um filtro específico para identificar características da imagem de entrada.

1.1 Camada de Convolução

A operação de convolução é realizada utilizando filtros aplicados sobre a imagem de entrada. O PyTorch fornece o módulo torch.nn.Conv2d para criar essas camadas. A seguir está explicado o signiifcado dos principais parâmetros:

torch.nn.Conv2d(
    in_channels,  # Número de canais na imagem de entrada
    out_channels, # Número de canais produzidos pela convolução
    kernel_size,   # Tamanho do núcleo convolucional
    stride=1,      # Passo da convolução
    padding=0,     # Preenchimento adicionado à entrada
    dilation=1,    # Espaçamento entre elementos do núcleo
    groups=1,      # Número de conexões bloqueadas
    bias=True      # Se True, adiciona um viés aprendido
)

Por exemplo, se a entrada for uma imagem RGB (3 canais), então in_channels = 3. O número de out_channels determina quantos filtros serão aplicados e, consequentemente, quantos Feature Maps serão gerados.

1.2 Camada de Subamostragem (Pooling)

O pooling reduz a dimensionalidade da saída da camada anterior, preservando as características mais importantes. No PyTorch, isso pode ser implementado usando torch.nn.MaxPool2d.

torch.nn.MaxPool2d(
    kernel_size,   # Tamanho da janela para calcular o máximo
    stride=None,   # Passo da janela
    padding=0,     # Preenchimento implícito
    dilation=1,    # Controle do espaçamento entre os elementos da janela
    return_indices=False,  # Se True, retorna os índices máximos
    ceil_mode=False        # Se True, usa teto ao invés de piso
)

1.3 Estrutura Detalhada da Rede LeNet

Antes das redes neurais convolucionais modernas, métodos tradicionais como HOG e SIFT eram combinados com classificadores como SVM. Esses métodos exigiam muito trabalho manual e não garantiam bons resultados. CNNs como LeNet adotam uma abordagem "end-to-end", automatizando tanto a extração de características quanto a classificação.

Abaixo está apresentada a implementação da rede LeNet em Python com o framework PyTorch:

import torch.nn as nn
import torch.nn.functional as F

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)  # Primeira camada convolucional
        self.pool = nn.MaxPool2d(2, 2)   # Camada de pooling
        self.conv2 = nn.Conv2d(6, 16, 5) # Segunda camada convolucional
        self.fc1 = nn.Linear(16 * 4 * 4, 120)  # Primeira camada totalmente conectada
        self.fc2 = nn.Linear(120, 84)          # Segunda camada totalmente conectada
        self.fc3 = nn.Linear(84, 10)           # Terceira camada totalmente conectada

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))  # Convolução + ReLU + Pooling
        x = self.pool(F.relu(self.conv2(x)))  # Convolução + ReLU + Pooling
        x = x.view(-1, 16 * 4 * 4)            # Redimensionamento para entrada linear
        x = F.relu(self.fc1(x))               # Camada totalmente conectada + ReLU
        x = F.relu(self.fc2(x))               # Camada totalmente conectada + ReLU
        x = self.fc3(x)                       # Camada totalmente conectada final
        return x

Entrada:

A entrada da rede deve ser uma imagem monocromática (1 canal) com dimensões 28x28 pixels. Para aplicações práticas, as imagens devem ser redimensionadas antes de serem fornecidas à rede.

Saída:

A última camada gera um vetor de 10 valores, correspondendo às probabilidades de pertencimento a cada uma das 10 classes possíveis (dígitos de 0 a 9).

2. Implementação do Modelo LeNet

Segue-se a implementação completa do modelo LeNet para reconhecimento de dígitos manuscritos no conjunto MNIST.

import torch
import torchvision
import torch.nn as nn
from torch.utils.data import DataLoader
import torchvision.transforms as transforms

# Definição da transformação para pré-processamento
transform = transforms.Compose([
    transforms.Resize((28, 28)),  # Redimensionamento para 28x28
    transforms.ToTensor(),         # Conversão para tensor
    transforms.Normalize((0.1307,), (0.3081,))  # Normalização
])

# Carregamento dos dados de treino e teste
train_set = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_set, batch_size=64, shuffle=True)

test_set = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform)
test_loader = DataLoader(test_set, batch_size=1000, shuffle=False)

# Definição do dispositivo de execução
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Instanciação do modelo
model = LeNet().to(device)

# Definição da função de perda e otimizador
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

# Treinamento do modelo
for epoch in range(5):  # Número de épocas
    model.train()
    running_loss = 0.0
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()
    print(f'Época {epoch+1}, Loss: {running_loss/len(train_loader)}')

# Avaliação do modelo
model.eval()
correct = 0
total = 0
with torch.no_grad():
    for images, labels in test_loader:
        images, labels = images.to(device), labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f'Acurácia no conjunto de teste: {100 * correct / total}%')

3. Visualização do Modelo

Para visualizar a estrutura do modelo LeNet, pode-se utilizar a biblioteca netron. Abaixo está demonstrado como exportar o modelo para formato ONNX e iniciá-lo no Netron.

import torch.onnx

# Exportação para ONNX
dummy_input = torch.randn(1, 1, 28, 28, device=device)
torch.onnx.export(model, dummy_input, "LeNetModel.onnx", export_params=True, opset_version=10)

# Início do Netron
import netron
netron.start("LeNetModel.onnx")

Tags: Pytorch CNN lenet

Publicado em 9-19 11:31