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")