Construindo Redes Neurais Simples com PyTorch

Este artigo explora a construção de redes neurais básicas utilizando a biblioteca PyTorch, focando na compreensão detalhada dos componentes e sua montagem.

Definindo uma Camada Linear Manualmente

O módulo torch.nn é fundamental para a criação de redes neurais. Ele oferece a classe nn.Module, que serve como base para todas as camadas e redes neurais. Para criar uma camada totalmente conectada (linear), definimos seus parâmetros de peso e bias.


import torch
from torch import nn

class CamadaLinear(nn.Module):
    def __init__(self, dim_entrada, dim_saida):
        super(CamadaLinear, self).__init__()
        # Inicializa os parâmetros de peso e bias.
        # nn.Parameter garante que esses tensores sejam rastreados para gradientes.
        self.peso = nn.Parameter(torch.randn(dim_entrada, dim_saida))
        self.bias = nn.Parameter(torch.randn(dim_saida))

    def forward(self, x):
        # Calcula a saída: y = x * peso + bias
        # x.matmul(self.peso) realiza a multiplicação de matrizes.
        # self.bias.expand_as(x) garante que o bias tenha a mesma forma que o resultado intermediário.
        resultado_intermediario = x.matmul(self.peso)
        y = resultado_intermediario + self.bias.expand_as(resultado_intermediario)
        print(f"Dimensão da saída intermediária: {y.shape}")
        return y

Testando a Camada Linear

Vamos instanciar e testar a camada linear criada. Um tensor de entrada com forma [2, 4] (2 amostras, 4 características) será usado para gerar uma saída de forma [2, 3].


# Cria uma camada linear com 4 características de entrada e 3 de saída.
camada_teste = CamadaLinear(4, 3)

# Cria um tensor de entrada aleatório.
# A forma [2, 4] indica um batch de 2 amostras, cada uma com 4 características.
entrada = torch.randn(2, 4)
saida = camada_teste(entrada)
print(saida)

A saída mostrará a forma do tensor e os valores calculados, incluindo o gradiante se o cálculo for parte de uma computação maior.


Dimensão da saída intermediária: torch.Size([2, 3])
tensor([[-1.2798, -1.4936,  0.2399],
        [-1.1742,  1.2190, -2.8469]], grad_fn=<AddBackward0>)

É importante notar que para processar uma única amostra, ela pode precisar ser remodelada para ter a dimensão de batch explícita, por exemplo, usando unsqueeze(0).


entrada_single = torch.randn(2, 4)
# Transforma a forma de [2, 4] para [1, 2, 4], simulando um batch de tamanho 1.
entrada_reshaped = entrada_single.unsqueeze(0)
print(f"Forma após unsqueeze: {entrada_reshaped.size()}")


Forma após unsqueeze: torch.Size([1, 2, 4])

Inspecionendo os Parâmetros da Camada

Os parâmetros treináveis (peso e bias) podem ser inspecionados usando named_parameters().


for nome, parametro in camada_teste.named_parameters():
    print(f"Nome: {nome}, Forma: {parametro.size()}")


Nome: peso, Forma: torch.Size([4, 3])
Nome: bias, Forma: torch.Size([3])

Montando um Perceptron com Duas Camadas Lineares

Podemos agora construir uma rede neural mais complexa, um perceptron, combinando duas instâncias da nossa CamadaLinear, com funções de ativação sigmoid entre elas.


class Perceptron(nn.Module):
    def __init__(self, num_entradas, num_ocultas, num_saidas):
        super(Perceptron, self).__init__()
        # Define as duas camadas lineares como submodules.
        self.camada1 = CamadaLinear(num_entradas, num_ocultas)
        self.camada2 = CamadaLinear(num_ocultas, num_saidas)

    def forward(self, x):
        # Passa a entrada pela primeira camada, aplica ativação.
        x = self.camada1(x)
        x = torch.sigmoid(x)
        # Passa o resultado pela segunda camada, aplica ativação final.
        x = self.camada2(x)
        y = torch.sigmoid(x)
        return y

# Instancia o perceptron.
perceptron_simples = Perceptron(3, 4, 1)
print(perceptron_simples)

A estrutura do perceptron pode ser visualizada:


Perceptron(
  (camada1): CamadaLinear()
  (camada2): CamadaLinear()
)

Os parâmetros de ambos os submodules são acessíveis:


for nome, parametro in perceptron_simples.named_parameters():
    print(f"Parâmetro: {nome}, Forma: {parametro.size()}")


Parâmetro: camada1.peso, Forma: torch.Size([3, 4])
Parâmetro: camada1.bias, Forma: torch.Size([4])
Parâmetro: camada2.peso, Forma: torch.Size([4, 1])
Parâmetro: camada2.bias, Forma: torch.Size([1])

Testando a entrada e saída do perceptron:


dados_teste = torch.randn(4, 3)
saida_perceptron = perceptron_simples(dados_teste)
print(saida_perceptron)


Dimensão da saída intermediária: torch.Size([4, 4])
Dimensão da saída intermediária: torch.Size([4, 1])





tensor([[0.5478],
        [0.6146],
        [0.6252],
        [0.8016]], grad_fn=<SigmoidBackward>)

Utilizando nn.Sequential para Montagem Rápida

Para redes neurais feedforward simples, nn.Sequential oferece uma maneira concisa de definir a arquitetura sem a necessidade de escrever um método forward explícito. Os módulos são empilhados em ordem.


class PerceptronSequencial(nn.Module):
    def __init__(self, num_entradas, num_ocultas, num_saidas):
        super(PerceptronSequencial, self).__init__()
        # Define a sequência de camadas e ativações.
        self.sequencia = nn.Sequential(
            nn.Linear(num_entradas, num_ocultas), # Camada linear padrão do PyTorch
            nn.Sigmoid(),
            nn.Linear(num_ocultas, num_saidas),
            nn.Sigmoid()
        )

    def forward(self, x):
        # A saída da sequência é diretamente passada.
        return self.sequencia(x)

# Instancia e testa o perceptron sequencial.
perceptron_seq = PerceptronSequencial(3, 4, 1)
dados_teste_seq = torch.randn(4, 3)
saida_seq = perceptron_seq(dados_teste_seq)
print(saida_seq)

A saída será similar à do perceptron definido manualmente, demonstrando a funcionalidade equivalente:


tensor([[0.5853],
        [0.6061],
        [0.5967],
        [0.6131]], grad_fn=<SigmoidBackward>)

Tags: Pytorch nn.Module nn.Linear nn.Sequential Redes Neurais

Publicado em 7-25 17:27