Implementando uma Rede Neural Simples na Prática
Da teoria à prática: vamos construir uma rede neural com PyTorch!
Nos últimos artigos, exploramos os conceitos fundamentais das redes neurais e do deep learning, entendendo como essas tecnologias funcionam e por que são tão poderosas.
Agora, é hora de colocar esse conhecimento em prática e construir nossa própria rede neural do zero! 🚀
Vamos utilizar a biblioteca PyTorch
para construir e treinar uma rede neural simples do tipo MLP (Multilayer Perceptron) para classificação de dígitos escritos à mão no dataset MNIST.
O código no Colab você encontra em: https://elisaterumi.substack.com/p/notebooks
Configurando o Ambiente
Para seguir este tutorial, você precisará do Google Colab ou de um ambiente com Python e PyTorch instalados.
O Google Colab é uma ótima opção, pois já vem com suporte para PyTorch e permite o uso de GPUs gratuitamente.
O Dataset MNIST
MNIST é um conjunto de dados clássico composto por imagens de dígitos escritos à mão (0 a 9) em escala de cinza, com tamanho 28x28 pixels, onde cada dígito tem a sua classe correspondente (0 a 9).
Esse dataset é amplamente utilizado para experimentos em reconhecimento de padrões e aprendizado de máquina.
Vamos codificar!
Vamos criar um código de treinamento e teste de uma rede neural com o conjunto de dados MNIST, que contém imagens de dígitos manuscritos. Vamos ver passo a passo o que o código faz.
A arquitetura que utilizaremos é uma rede neural simples, com uma única camada totalmente conectada (fully connected) que recebe as imagens como entrada, “achatando-as” em um vetor de 784 (28x28) valores e gerando 10 saídas correspondentes às classes (dígitos de 0 a 9).
O modelo será treinado com a função de perda CrossEntropyLoss
e o otimizador SGD
.
Acesse o Colab (Notebook) com o código: https://elisaterumi.substack.com/p/notebooks
Importação das bibliotecas necessárias:
torch e torch.nn: São usadas para criar e treinar redes neurais no PyTorch.
torch.optim: Fornece algoritmos de otimização como o Gradiente Descendente Estocástico (SGD) usado aqui.
torchvision.datasets: Importa o conjunto de dados MNIST e outras bibliotecas para manipulação de imagens.
torchvision.transforms: Usado para realizar transformações nas imagens, como converter para tensor.
matplotlib.pyplot: Usado para visualizar as imagens.
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
Carregar o dataset MNIST:
transform = transforms.ToTensor()
A transformação ToTensor()
converte as imagens para tensores, que são objetos essenciais para trabalhar com PyTorch. As imagens em MNIST são originalmente no formato de 28x28 pixels, em uma escala de cinza (valores entre 0 e 255), e a transformação as converte para tensores com valores entre 0 e 1.
trainset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
trainloader = DataLoader(trainset, batch_size=64, shuffle=True)
testset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
testloader = DataLoader(testset, batch_size=64, shuffle=False)
Trainset e testset: São os conjuntos de dados de treinamento e teste do MNIST, respectivamente.
Trainloader e testloader: São "DataLoader" que facilitam a iteração sobre os conjuntos de dados em batches (lotes) de tamanho 64.
Construindo a Rede Neural
Para criar nossa rede neural, vamos criar a função:
class MLP(nn.Module):
def __init__(self):
super(MLP, self).__init__()
self.fc1 = nn.Linear(28*28, 10) # Camada única
MLP (Perceptron Multicamadas) é uma classe que define nossa rede neural. Ela herda de
nn.Module
, que é a classe base para todas as redes neurais no PyTorch.fc1: Uma camada totalmente conectada (fully connected) que mapeia a entrada de 28x28 (totalizando 784 valores) para 10 valores (que representam as 10 classes de dígitos 0-9).
class MLP(nn.Module):
def __init__(self):
super(MLP, self).__init__()
self.fc1 = nn.Linear(28*28, 10) # Camada única
def forward(self, x):
x = x.view(-1, 28*28) # Flatten
x = self.fc1(x)
return x
O método forward
define como os dados passam pela rede. O x.view(-1, 28*28)
"flatten" (achata) a imagem de 28x28 para um vetor de 784 elementos, que é alimentado na camada fc1
.
Vamos agora inicializar o modelo, a função de perda e o otimizador:
model = MLP()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.1)
model: Cria uma instância da classe
MLP
que definimos.criterion: Define a função de perda (ou custo), que é
CrossEntropyLoss
para classificação multi-classe. Esta função calcula a diferença entre as predições e as verdadeiras classes.optimizer: Usamos o Gradiente Descendente Estocástico (SGD) com uma taxa de aprendizado de 0.1 para otimizar os parâmetros do modelo.
Treinamento e Avaliação
Durante o treinamento, a rede ajusta seus pesos iterativamente para minimizar o erro na classificação das imagens. Para realizar o treinamento:
for epoch in range(3):
for images, labels in trainloader:
optimizer.zero_grad() # Zera os gradientes
outputs = model(images) # Passa as imagens pela rede
loss = criterion(outputs, labels) # Calcula a perda
loss.backward() # Calcula os gradientes
optimizer.step() # Atualiza os parâmetros da rede
print(f'Época {epoch+1}, Loss: {loss.item():.4f}')
Vamos analisar o código:
for epoch in range(3): Treina o modelo por 3 épocas.
optimizer.zero_grad(): Zera os gradientes acumulados de iterações anteriores.
outputs = model(images): Realiza a inferência, passando as imagens pela rede.
loss = criterion(outputs, labels): Calcula a perda com base nas predições (
outputs
) e nos rótulos verdadeiros (labels
).loss.backward(): Propaga os gradientes de volta através da rede para ajustar os pesos.
optimizer.step(): Atualiza os pesos da rede de acordo com os gradientes calculados.
Após algumas épocas de treinamento, avaliamos a precisão do modelo com o conjunto de teste para verificar sua capacidade de generalização. Para avaliar o modelo:
correct, total = 0, 0
with torch.no_grad():
for images, labels in testloader:
outputs = model(images)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'Acurácia: {100 * correct / total:.2f}%')
Aqui, o modelo é testado no conjunto de dados de teste.
torch.no_grad(): Desliga o cálculo de gradientes, já que não estamos treinando.
torch.max(outputs, 1): Retorna o índice da maior previsão, ou seja, a classe com maior probabilidade.
(predicted == labels).sum().item(): Conta o número de previsões corretas.
Inferência em Novas Imagens
Após o treinamento, podemos alimentar a rede com uma nova imagem e visualizar a predição do modelo. No código, também incluímos uma visualização da imagem antes da inferência, tornando o processo mais intuitivo.
def inferencia(model, image):
model.eval() # Coloca o modelo em modo de avaliação
with torch.no_grad():
image = image.view(-1, 28*28) # Flatten
output = model(image)
_, predicted = torch.max(output, 1)
return predicted.item()
inferencia: Função que recebe uma imagem e retorna a predição da classe.
Coloca o modelo em modo de avaliação (
model.eval()
), o que desliga alguns mecanismos como o "dropout" que são usados apenas no treinamento.
Para visualizar a imagem que queremos classificar:
sample_image, _ = testset[0]
plt.imshow(sample_image.squeeze(), cmap='gray')
plt.title("Imagem de teste")
plt.axis("off")
plt.show()
Aqui, uma imagem de teste é extraída do testset
e exibida.
squeeze()
remove dimensões extras, como o canal de cor.A imagem é exibida usando
matplotlib
.
Agora vamos fazer a inferência na imagem, para ver o que o nosso classificador vai retornar:
predicao = inferencia(model, sample_image)
print(f'Predição da imagem: {predicao}')
A inferência é realizada sobre a imagem exibida, e a predição é impressa no terminal. Podemos ver que nosso classificador acertou o dígito informado!
Próximos Passos
Após o treinamento, nossa rede neural atingiu uma precisão razoável (91.82 %), mas ainda há espaço para melhorias. Para otimizar o desempenho, podemos:
Adicionar mais camadas à rede (deep learning);
Testar diferentes funções de ativação;
Ajustar hiperparâmetros, como taxa de aprendizado e número de épocas;
Utilizar técnicas como normalização e dropout.
Experimentar com redes convolucionais (CNNs).
Para problemas de classificação de imagens, redes convolucionais geralmente apresentam um desempenho superior. As CNNs são especialmente boas em capturar padrões espaciais nas imagens.
Aproveite para explorar outras abordagens que podem melhorar ainda mais o desempenho do modelo!
Conclusões
Nosso código treinou uma rede neural simples do tipo MLP para classificar imagens do dataset MNIST, que contém imagens de dígitos manuscritos de 28x28 pixels.
O treinamento foi feito com um otimizador de gradiente descendente estocástico (SGD) e a função de perda utilizada foi a CrossEntropyLoss
.
Após o treinamento, o modelo foi testado e avaliado em termos de acurácia. Como vimos, o modelo teve um desempenho razoável, mas ainda pode ser melhorado de diversas formas.
Se você estiver começando com PyTorch, esse é um ótimo ponto de partida. 😉
Espero que tenham gostado do artigo!