Tutorial de CNN do PyTorch: Construa e Treine Redes Neurais Convolucionais em Python

Redes Neurais Convolucionais (CNNs) são um pilar da visão computacional moderna, possibilitando aplicações como reconhecimento de imagens, detecção facial e carros autônomos. Essas redes são projetadas para extrair automaticamente padrões e características de imagens, tornando-as mais poderosas do que técnicas tradicionais de aprendizado de máquina para tarefas visuais.

Neste tutorial, iremos implementar uma CNN usando PyTorch, uma estrutura de aprendizado profundo que é amigável ao usuário e altamente eficiente para aplicações de pesquisa e produção.

Pré-requisitos: Aprendizado Profundo e PyTorch

Antes de entrar nos detalhes das CNNs, você deve estar familiarizado com o campo do aprendizado profundo e as bibliotecas Python que usaremos durante a configuração do nosso ambiente.

A aprendizagem profunda é um subconjunto do aprendizado de máquina, onde a estrutura fundamental do modelo é uma rede de entradas, camadas ocultas e saídas. Tal rede pode ter uma ou muitas camadas ocultas. A intuição original por trás do aprendizado profundo era criar modelos inspirados na forma como o cérebro humano aprende: através de células interconectadas chamadas neurônios. É por isso que continuamos a chamar os modelos de aprendizado profundo de “redes” neurais. Essas estruturas de modelo em camadas exigem muito mais dados para aprender do que outros modelos de aprendizado supervisionado para derivar padrões dos dados não estruturados. Normalmente, falamos de pelo menos centenas de milhares de pontos de dados.

Embora existam várias estruturas e pacotes para implementar algoritmos de aprendizado profundo, vamos nos concentrar no PyTorch, uma das estruturas mais populares e bem mantidas. Além de ser utilizado por engenheiros de aprendizado profundo na indústria, o PyTorch é uma ferramenta favorita entre os pesquisadores. Muitos artigos sobre aprendizado profundo são publicados usando o PyTorch. Ele é projetado para ser intuitivo e amigável ao usuário, compartilhando muitos pontos em comum com a biblioteca Python NumPy.

Se você precisa de uma introdução a esses conceitos, considere se inscrever no curso Aprendizado Profundo com PyTorch hoje.

O que é uma Rede Neural Convolucional (CNN)?

Redes neurais convolucionais, comumente chamadas de CNN ou ConvNet, são um tipo específico de rede neural profunda bem adequada para tarefas de visão computacional. A invenção das CNNs remonta à década de 1980. No entanto, elas só se tornaram populares na década de 2010, após as inovações computacionais resultantes da implementação de unidades de processamento gráfico (GPUs). De fato, a rápida popularização das CNNs ajudou o campo das redes neurais a recuperar destaque, levando à chamada “terceira onda de redes neurais” que ainda estamos vivenciando hoje.

As CNNs são especificamente inspiradas no córtex visual biológico. O córtex possui pequenas regiões de células que são sensíveis a áreas específicas do campo visual. Essa ideia foi expandida por um experimento fascinante de Hubel e Wiesel em 1962.

As CNNs tentam replicar esse recurso criando redes neurais complexas que são compostas por diferentes camadas específicas para tarefas. CNNs são chamadas de “feed-forward” porque a informação flui diretamente pelo modelo. Não há conexões de feedback nas quais as saídas do modelo são alimentadas de volta para si mesmo, em comparação com outros modelos que usam técnicas como retropropagação.

Em particular, uma CNN geralmente consiste nas seguintes camadas:

Camada convolucional

Este é o primeiro bloco de construção de uma CNN. Como o nome sugere, a principal tarefa matemática realizada é chamada de convolução, que é a aplicação de uma função de janela deslizante a uma matriz de pixels representando uma imagem. A função deslizante aplicada à matriz é chamada de kernel ou filtro. Na camada de convolução, vários filtros de tamanho igual são aplicados, e cada filtro é usado para reconhecer um padrão específico da imagem, como a curvatura dos dígitos, as bordas, a forma inteira dos dígitos, e mais. 

Função de ativação

Normalmente, uma função de ativação ReLU é aplicada após cada operação de convolução. Essa função ajuda a rede a aprender relações não-lineares entre os recursos na imagem, tornando a rede mais robusta para identificar diferentes padrões. Também ajuda a mitigar problemas de gradientes que desaparecem. 

Camada de pooling

O objetivo da camada de pooling é extrair as características mais significativas da matriz convoluta. Isso é feito aplicando algumas operações de agregação, que reduzem a dimensão do mapa de características (matriz convoluta), diminuindo assim a memória utilizada durante o treinamento da rede. O pooling também é relevante para mitigar o overfitting.

Camadas totalmente conectadas

Estas camadas estão na última camada da rede neural convolucional, e suas entradas correspondem à matriz unidimensional achatada gerada pela última camada de pooling. Funções de ativação ReLU são aplicadas a elas para não linearidade.

Arquitetura de Rede Neural Convolucional. Fonte: DataCamp

Você pode ler uma explicação mais detalhada da matemática por trás das CNNs em nosso tutorial, Redes Neurais Convolucionais em Python.

Por que usar CNNs para classificação de imagens?

Redes neurais convolucionais têm sido uma das inovações mais influentes no campo da visão computacional. Elas tiveram um desempenho muito melhor do que modelos tradicionais de aprendizado de máquina, como SVMs e árvores de decisão, e produziram resultados de ponta. 

Além disso, as camadas convolucionais concedem às CNNs suas características invariantes à tradução, capacitando-as a identificar e extrair padrões e características dos dados independentemente de variações em posição, orientação, escala ou tradução.


As CNNs provaram ser bem-sucedidas em muitos estudos de caso e aplicações da vida real, como:

  • Classificação de imagens, detecção de objetos, segmentação, reconhecimento facial;
  • Carros autônomos que aproveitam sistemas de visão baseados em CNNs;
  • Classificação da estrutura cristalina usando uma rede neural convolucional;
  • Sistemas de câmeras de segurança.

Além das tarefas de classificação de imagens, as CNNs são versáteis e podem ser aplicadas a uma variedade de outros domínios, como processamento de linguagem natural, análise de séries temporais e reconhecimento de fala.

Implementando uma CNN com PyTorch

Agora que você está familiarizado com a teoria das CNNs, estamos prontos para colocar a mão na massa. Nesta seção, iremos construir e treinar uma CNN simples com o PyTorch. Nosso objetivo é construir um modelo para classificar dígitos em imagens. Para treinar e testar nosso modelo, utilizaremos o famoso conjunto de dados MNIST, uma coleção de 70.000 imagens em escala de cinza, 28×28, com dígitos escritos à mão.

1. Importando bibliotecas necessárias

Abaixo você pode encontrar as bibliotecas que iremos usar neste tutorial. Em essência, vamos aproveitar o PyTorch para construir nossa CNN, e o módulo de visão computacional do PyTorch torchvision, para baixar e carregar o conjunto de dados MNIST. Por fim, também iremos utilizar torchmetrics para avaliar o desempenho do nosso modelo.

import numpy as np import pandas as pd import matplotlib.pyplot as plt import torch from torch import optim from torch import nn from torch.utils.data import DataLoader from tqdm import tqdm # !pip install torchvision import torchvision import torch.nn.functional as F import torchvision.datasets as datasets import torchvision.transforms as transforms # !pip install torchmetrics import torchmetrics

2. Carregando e pré-processando o conjunto de dados

O PyTorch também possui um ecossistema rico em ferramentas e extensões, incluindo torchvision, um módulo para visão computacional. Torchvision inclui vários conjuntos de dados de imagens que podem ser usados para treinar e testar redes neurais. Em nosso tutorial, iremos usar o conjunto de dados MNIST. 

Primeiramente, iremos baixar e converter o conjunto de dados MNIST em um tensor, a estrutura de dados fundamental no PyTorch, semelhante a arrays NumPy, mas com capacidades de aceleração de GPU.

Em seguida, também usaremos o DataLoader para lidar com o agrupamento e embaralhamento dos conjuntos de dados de treino e teste. Um DataLoader do PyTorch pode ser criado a partir de um Dataset para carregar dados, dividi-los em lotes e realizar transformações nos dados, se desejado. Em seguida, ele fornece uma amostra de dados pronta para treinamento. No código abaixo, carregamos os dados e os salvamos em DataLoaders com um tamanho de lote de 60 imagens:

batch_size = 60 train_dataset = datasets.MNIST(root="dataset/", download=True, train=True, transform=transforms.ToTensor()) train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True) test_dataset = datasets.MNIST(root="dataset/", download=True, train=False, transform=transforms.ToTensor()) test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True)

Opcionalmente, o conjunto de dados de treino pode ser dividido em duas partes de treino e validação. A validação é uma técnica usada em aprendizado profundo para avaliar o desempenho do modelo durante o treinamento. Ela ajuda a detectar potenciais sobreajuste e subajuste de nossos modelos, e é particularmente útil para otimizar hiperparâmetros. No entanto, para simplificar, não usaremos validação neste tutorial. Se você quiser aprender mais sobre validação, pode conferir uma explicação completa em nosso Introdução ao Aprendizado Profundo com o Curso PyTorch.

Agora que temos nossos dados, vamos ver como um lote aleatório de dígitos se parece:

def imshow(img): npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) plt.show() # obter algumas imagens de treino aleatórias dataiter = iter(dataloader_train) images, labels = next(dataiter) labels # mostrar imagens imshow(torchvision.utils.make_grid(images))

3. Definindo a arquitetura da CNN

Para resolver o problema de classificação, vamos aproveitar a classe nn.Module, o bloco de construção do PyTorch para criar intuitivamente arquiteturas de redes neurais sofisticadas.

No código abaixo, criamos uma classe chamada CNN, que herda as propriedades da classe nn.Module. A classe CNN será o esquema de uma CNN com duas camadas convolucionais, seguidas por uma camada totalmente conectada.

No PyTorch, usamos nn.Conv2d para definir uma camada convolucional. Passamos a ela o número de mapas de características de entrada e saída. Também configuramos alguns dos parâmetros para a camada convolucional funcionar, incluindo o tamanho do kernel ou filtro e o padding.

Em seguida, adicionamos uma camada de max pooling com nn.MaxPool2d. Nela, deslizamos uma janela sem sobreposição sobre a saída da camada convolucional anterior. Em cada posição, selecionamos o valor máximo da janela para passar adiante. Essa operação reduz as dimensões espaciais dos mapas de características, diminuindo o número de parâmetros e a complexidade computacional na rede. Por fim, adicionamos uma camada linear totalmente conectada.

A função forward() define como as diferentes camadas estão conectadas, adicionando várias funções de ativação ReLU após cada camada convolucional.

class CNN(nn.Module): def __init__(self, in_channels, num_classes): """ Building blocks of convolutional neural network. Parameters: * in_channels: Number of channels in the input image (for grayscale images, 1) * num_classes: Number of classes to predict. In our problem, 10 (i.e digits from 0 to 9). """ super(CNN, self).__init__() # 1ª camada convolucional self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=8, kernel_size=3, padding=1) # Camada de max pooling self.pool = nn.MaxPool2d(kernel_size=2, stride=2) # 2ª camada convolucional self.conv2 = nn.Conv2d(in_channels=8, out_channels=16, kernel_size=3, padding=1) # Camada totalmente conectada self.fc1 = nn.Linear(16 * 7 * 7, num_classes) def forward(self, x): """ Define the forward pass of the neural network. Parameters: x: Input tensor. Returns: torch.Tensor The output tensor after passing through the network. """ x = F.relu(self.conv1(x)) # Aplicar primeira convolução e ativação ReLU x = self.pool(x) # Aplicar max pooling x = F.relu(self.conv2(x)) # Aplicar segunda convolução e ativação ReLU x = self.pool(x) # Aplicar max pooling x = x.reshape(x.shape[0], -1) # Achatar o tensor x = self.fc1(x) # Aplicar camada totalmente conectada return x x = x.reshape(x.shape[0], -1) # Achatar o tensor x = self.fc1(x) # Aplicar camada totalmente conectada return x

Uma vez que tenhamos definido a classe CNN, podemos criar nosso modelo e movê-lo para o dispositivo onde será treinado e executado. 

Redes neurais, incluindo CNNs, mostram melhor desempenho ao serem executadas em GPUs, mas isso pode não ser o caso em seu computador. Portanto, executaremos o modelo em uma GPU apenas quando disponível; caso contrário, usaremos uma CPU regular.

device = "cuda" if torch.cuda.is_available() else "cpu" model = CNN(in_channels=1, num_classes=10).to(device) print(model) >>> CNN( (conv1): Conv2d(1, 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv2): Conv2d(8, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (fc1): Linear(in_features=784, out_features=10, bias=True) )

4. Treinando o modelo CNN

Agora que temos nosso modelo, é hora de treiná-lo. Para isso, primeiro precisaremos determinar como mediremos o desempenho do modelo. Como estamos lidando com um problema de classificação multi-classe, usaremos a função de perda de entropia cruzada, disponível no PyTorch como nn.CrossEntropyLoss. Também iremos utilizar o otimizador Adam, um dos algoritmos de otimização mais populares.

# Definir a função de perda criterion = nn.CrossEntropyLoss() # Definir o otimizador optimizer = optim.Adam(model.parameters(), lr=0.001)

Iremos iterar ao longo de dez épocas e lotes de treinamento para treinar o modelo e realizar a sequência usual de passos para cada lote, conforme mostrado abaixo.

num_epochs=10 for epoch in range(num_epochs): # Iterar sobre os lotes de treinamento print(f"Epoch [{epoch + 1}/{num_epochs}]") for batch_index, (data, targets) in enumerate(tqdm(dataloader_train)): data = data.to(device) targets = targets.to(device) scores = model(data) loss = criterion(scores, targets) optimizer.zero_grad() loss.backward() optimizer.step()
Epoch [1/10] 100%|██████████| 1000/1000 [00:13<00:00, 72.94it/s] Epoch [2/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.27it/s] Epoch [3/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.16it/s] Epoch [4/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.00it/s] Epoch [5/10] 100%|██████████| 1000/1000 [00:13<00:00, 75.69it/s] Epoch [6/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.24it/s] Epoch [7/10] 100%|██████████| 1000/1000 [00:12<00:00, 78.23it/s] Epoch [8/10] 100%|██████████| 1000/1000 [00:12<00:00, 78.16it/s] Epoch [9/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.96it/s] Epoch [10/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.93it/s]

5. Avaliando o modelo

Depois que o modelo estiver treinado, podemos avaliar seu desempenho no conjunto de dados de teste. Vamos usar a acurácia, uma métrica popular para problemas de classificação. A acurácia mede a proporção de casos classificados corretamente em relação ao número total de objetos no conjunto de dados. É calculada dividindo o número de previsões corretas pelo número total de previsões feitas pelo modelo.

Primeiro, configuramos a métrica de precisão do torchmetrics. Em seguida, usamos o método .eval do modelo para colocar o modelo no modo de avaliação, pois algumas camadas nos modelos PyTorch se comportam de forma diferente nos estágios de treinamento versus teste. Também adicionamos um contexto Python com torch.no_grad, indicando que não faremos cálculo de gradiente.

Então, iteramos sobre exemplos de teste sem cálculo de gradiente. Para cada lote de teste, obtemos as saídas do modelo, pegamos a classe mais provável e passamos para a função de precisão juntamente com as etiquetas. Por fim, calculamos as métricas e imprimimos os resultados. Obtivemos uma pontuação de precisão de 0.98, o que significa que nosso modelo classificou corretamente 98% dos dígitos. Nada mal!

# Configuração da métrica de precisão multiclasse acc = Accuracy(task="multiclass",num_classes=10) # Iterar sobre os lotes do conjunto de dados model.eval() with torch.no_grad(): for images, labels in dataloader_test: # Obter probabilidades previstas para o lote de dados de teste outputs = model(images) _, preds = torch.max(outputs, 1) acc(preds, labels) precision(preds, labels) recall(preds, labels) # Calcular a precisão total do teste test_accuracy = acc.compute() print(f"Test accuracy: {test_accuracy}") >>> Test accuracy: 0.9857000112533569

Você também pode usar outras métricas populares de classificação, incluindo recall e precisão. Contamos tudo sobre essas métricas com exemplos práticos em nosso Curso Intermediário de Deep Learning com PyTorch.

Melhorando o Desempenho do Modelo

Embora nosso modelo CNN alcance um desempenho forte, existem várias estratégias que podemos usar para aprimorar ainda mais sua precisão, robustez e generalização para novos dados.

Nesta seção, exploraremos técnicas-chave, como aumento de dados, ajuste de hiperparâmetros e aprendizado por transferência para otimizar o desempenho do nosso modelo.

Técnicas de aumento de dados

Aumento de dados é uma técnica utilizada para melhorar a precisão do nosso modelo, criando aleatoriamente novos dados de treinamento. Por exemplo, durante o carregamento, pode-se aplicar transformações nas imagens de treinamento, como redimensionamento, espelhamento horizontal ou vertical, rotação aleatória e assim por diante. Dessa forma, podemos criar imagens aumentadas e atribuí-las com o mesmo rótulo da imagem original, aumentando assim o tamanho do conjunto de treinamento.

Adicionar transformações aleatórias às imagens originais nos permite gerar mais dados enquanto aumentamos o tamanho e a diversidade do conjunto de treinamento. Isso torna o modelo mais robusto a variações e distorções comumente encontradas em imagens do mundo real e reduz o overfitting, pois o modelo aprende a ignorar as transformações aleatórias.

No entanto, é importante ter cautela com o aumento de dados, pois às vezes pode prejudicar o processo de treinamento. Por exemplo, em nosso problema, se aplicarmos o espelhamento vertical ao número “6”, ele parecerá com o número “9”. Passá-lo para o modelo rotulado como “6” confundirá o modelo e dificultará o treinamento. Esses exemplos mostram que, às vezes, aumentos específicos podem impactar o rótulo.

Ajuste de hiperparâmetros

Outra estratégia para melhorar o desempenho do nosso modelo é alterar os valores dos hiperparâmetros envolvidos nas diferentes camadas do modelo. Esse ajuste de hiperparâmetros requer uma compreensão profunda da matemática por trás das redes neurais e do significado dos diferentes hiperparâmetros.

Por exemplo, você pode ajustar suas camadas de CNN alterando o tamanho dos filtros ou aumentando o preenchimento. Você também pode definir um valor diferente para os pesos iniciais dos neurônios.

Como não saberemos os valores ótimos dos hiperparâmetros antecipadamente, algum grau de tentativa e erro será necessário. Isso é normalmente feito por meio de uma técnica conhecida como busca em grade, que permite avaliar sistematicamente um modelo em uma grade de valores de parâmetros.

No entanto, tenha cuidado ao usar essa técnica, pois normalmente é computacionalmente cara, especialmente ao lidar com redes neurais complexas e grandes conjuntos de dados de treinamento.

Da mesma forma, você pode aumentar a complexidade do seu modelo adicionando mais camadas convolucionais e lineares. No entanto, tenha cuidado ao adicionar novas camadas, pois o número de neurônios pode aumentar drasticamente, resultando em tempos de treinamento mais longos e possíveis sobreajustes.

Você pode aprender mais sobre ajuste de hiperparâmetros em nosso Curso de Introdução ao Aprendizado Profundo com PyTorch.

Usando modelos pré-treinados

Treinar modelos de aprendizado profundo do zero é um processo longo e tedioso, e geralmente requer muitos dados de treinamento. Em vez disso, muitas vezes podemos usar modelos pré-treinados, ou seja, modelos que já foram treinados em alguma tarefa.

Às vezes, podemos reutilizar diretamente um modelo pré-treinado se ele já puder resolver a tarefa que nos interessa. Em outras ocasiões, talvez precisemos ajustar o modelo pré-treinado para se adequar à nova tarefa. Isso é conhecido como transfer learning.

Usar modelos pré-treinados no PyTorch é bastante fácil. O Torchvision fornece uma coleção de modelos pré-treinados para várias tarefas relacionadas a imagens. Esses modelos são pré-treinados em conjuntos de dados de imagens em grande escala e estão facilmente disponíveis. Confira nosso Curso de Aprendizado Profundo para Imagens com PyTorch para aprender tudo o que você precisa saber sobre eles.

Implantando o Modelo CNN

Após treinar seu modelo de classificação altamente preciso no PyTorch, você pode agora salvar o modelo e seus pesos pré-treinados para uso futuro e compartilhá-lo com sua equipe, garantindo que eles possam carregá-lo sem problemas.

Para salvar um modelo, podemos usar torch.save. Uma extensão de arquivo comum para modelos torch é pt ou pth. Para salvar os pesos do modelo, passamos model.state_dict para torch.save fornecendo o nome do arquivo de saída, por exemplo, MulticlassCNN.pth.

Para carregar um modelo salvo, inicializamos um novo modelo com a mesma arquitetura. Em seguida, utilizamos o método de carregamento de estado dict juntamente com torch.load para carregar os parâmetros para o novo modelo.

# Salvar o modelo torch.save(model.state_dict(), 'MulticlassCNN.pth') # Criar um novo modelo loaded_model = CNN(in_channels=1, num_classes=10) # Carregar o modelo salvo loaded_model.load_state_dict(torch.load('MulticlassCNN.pth')) print(loaded_model) CNN( (conv1): Conv2d(1, 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv2): Conv2d(8, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (fc1): Linear(in_features=784, out_features=10, bias=True) )

Conclusão

Nós cobrimos uma visão geral completa das CNNs, fornecendo detalhes sobre cada camada da arquitetura da CNN. Além disso, fornecemos um guia sobre como implementar uma CNN no PyTorch, abrangendo os principais passos, desde o carregamento de dados e design do modelo até o treinamento e avaliação do modelo. Finalmente, também analisamos várias estratégias para melhorar o desempenho do nosso modelo. Aplicamos todos esses conjuntos de habilidades a um cenário do mundo real relacionado a uma tarefa de classificação multiclasse. 

Há muito a aprender sobre aprendizado profundo, sem dúvida um dos campos mais empolgantes e exigentes em IA. Felizmente, a DataCamp está aqui para ajudar. Confira nossos materiais e cursos dedicados e torne-se um especialista em redes neurais:

Source:
https://www.datacamp.com/tutorial/pytorch-cnn-tutorial