A regressão linear é um dos conceitos fundamentais no aprendizado de máquina, servindo como base para modelos mais complexos. Implementar este algoritmo manualmente, sem utilizar as abstrações de alto nível das bibliotecas, permite uma compreensão profunda de como o fluxo de tensores, o cálculo de gradientes e a atualização de pesos ocorrem internamente em um framework como o PyTorch.
1. Geração de Dados Sintéticos
Para treinar o modelo, criamos um conjunto de dados controlado onde conhecemos os parâmetros reais. Isso facilita a validação da convergência do algoritmo.
import torch
import random
import matplotlib.pyplot as plt
def gerar_dataset_linear(pesos_reais, vies_real, n_amostras):
"""Gera dados sintéticos baseados em pesos e um viés específico."""
caracteristicas = torch.normal(0, 1, (n_amostras, len(pesos_reais)))
rotulos = torch.matmul(caracteristicas, pesos_reais) + vies_real
# Introdução de ruído gaussiano para simular dados reais
rotulos += torch.normal(0, 0.01, rotulos.shape)
return caracteristicas, rotulos
# Configuração dos parâmetros "ocultos"
coeficientes_alvo = torch.tensor([8.1, 2.0, 2.0, 4.0])
intercepto_alvo = torch.tensor(1.1)
recursos, alvos = gerar_dataset_linear(coeficientes_alvo, intercepto_alvo, 500)
# Visualização rápida de uma dimensão
plt.scatter(recursos[:, 3].numpy(), alvos.numpy(), s=5)
plt.title("Relação entre Característica e Alvo")
plt.show()
2. Criação do Iterador de Dados
O processamento eficiente de dados exige a divisão do conjunto em pequenos lotes (mini-batches). O gerador abaixo embaralha os índices e retorna fatias do dataset a cada iteração.
def iterar_mini_lotes(recursos, alvos, tamanho_lote):
num_exemplos = len(alvos)
indices = list(range(num_exemplos))
random.shuffle(indices)
for i in range(0, num_exemplos, tamanho_lote):
indices_lote = torch.tensor(indices[i: min(i + tamanho_lote, num_examples)])
yield recursos[indices_lote], alvos[indices_lote]
3. Definição do Modelo e Função de Custo
O modelo segue a estrutura linear clássica. Como função de custo, utilizaremos o Erro Médio Absoluto (MAE), que calcula a média da diferença absoluta entre as predições e os valores verdadeiros.
def modelo_linear(X, w, b):
"""Computa a predição linear: y = Xw + b."""
return torch.matmul(X, w) + b
def erro_medio_absoluto(y_pred, y_real):
"""Calcula o Erro Médio Absoluto (MAE)."""
return torch.abs(y_pred - y_real).mean()
4. Otimização via Gradiente Descnedente Estocástico (SGD)
O otimizador ajusta os parâmetros na direção oposta ao gradiente para minimizar a perda. É crucial desativar o rastreamento de gradiente durante a atualização dos parâmetros e zerar os gradientes após cada passo.
def otimizador_sgd(parametros, taxa_aprendizado):
"""Atualização manual dos parâmetros baseada no gradiente."""
with torch.no_grad():
for parametro in parametros:
parametro -= taxa_aprendizado * parametro.grad
parametro.grad.zero_()
5. Ciclo de Treinamento
Inicializamos os parâmetros com valores aleatórios e executamos o loop de treinamento por várias épocas, ajustando os pesos a cada lote processado.
# Inicialização aleatória dos parâmetros com rastreamento de gradiente
w_treino = torch.normal(0, 0.01, size=coeficientes_alvo.shape, requires_grad=True)
b_treino = torch.zeros(1, requires_grad=True)
taxa_aprendizado = 0.03
epocas = 50
tamanho_lote = 16
for epoca in range(epocas):
perda_acumulada = 0
for X_lote, y_lote in iterar_mini_lotes(recursos, alvos, tamanho_lote):
predicao = modelo_linear(X_lote, w_treino, b_treino)
loss = erro_medio_absoluto(predicao, y_lote)
# Backpropagation
loss.backward()
# Atualização dos pesos
otimizador_sgd([w_treino, b_treino], taxa_aprendizado)
perda_acumulada += loss.item()
if (epoca + 1) % 10 == 0:
print(f"Época {epoca+1}: Perda Média = {perda_acumulada/len(alvos):.6f}")
print("\nParâmetros Reais:", coeficientes_alvo.tolist(), intercepto_alvo.item())
print("Parâmetros Treinados:", w_treino.detach().tolist(), b_treino.item())
6. Avaliação de Resultados
Após o treinaemnto, comparamos a linha de regressão obtida com os dados originais para verificar a qualidade do ajuste.
# Selecionando o último recurso para visualização
idx_viz = 3
plt.scatter(recursos[:, idx_viz].numpy(), alvos.numpy(), s=2, alpha=0.5, label='Dados Reais')
# Gerando pontos para a reta de regressão
x_reta = recursos[:, idx_viz].detach()
y_reta = x_reta * w_treino[idx_viz].detach() + b_treino.detach()
plt.plot(x_reta.numpy(), y_reta.numpy(), color='orange', label='Ajuste do Modelo')
plt.xlabel("Característica")
plt.ylabel("Alvo")
plt.legend()
plt.show()