Conceptos Fundamentales
El descenso de gradiente es un algoritmo de optimización que ajusta los parámetros del modelo siguiendo la dirección opuesta al gradiente para minimizar la función de pérdida. El objetivo es encontrar los parámetros que minimicen la función de pérdida calculando las derivadas parciales respecto a los parámetros del modelo.
En la implementación práctica, utilizamos el descenso de gradiente estocástico por minibatches (mini-batch SGD), que es el algoritmo estándar en deep learning.
Dos hiperparámetros cruciales que debemos definir: la tasa de aprendizaje (learning rate) y el tamaño del lote (batch size).
Imlpementación desde Cero
import random
import torch
import numpy as np
from matplotlib import pyplot as plt
# Función para generar dataset sintético
def crear_dataset(pesos, sesgo, n_muestras):
# Generar y = X·pesos + sesgo + ruido
X = torch.randn(n_muestras, len(pesos))
y = torch.mm(X, pesos.unsqueeze(1)) + sesgo
y += torch.normal(0, 0.01, y.shape)
return X, y
# Parámetros verdaderos
pesos_reales = torch.tensor([3.5, -2.1])
sesgo_real = 1.8
caracteristicas, objetivo = crear_dataset(pesos_reales, sesgo_real, 1000)
print(f'Primer muestra: {caracteristicas[0]}, objetivo: {objetivo[0].item():.2f}')
# Visualización
plt.figure(figsize=(8, 5))
plt.scatter(caracteristicas[:, 1].numpy(), objetivo.numpy(), alpha=0.5)
plt.xlabel('Característica 2')
plt.ylabel('Objetivo')
plt.show()
# Generador de minibatches
def generador_batches(tamano_batch, X, y):
n_muestras = len(X)
indices = list(range(n_muestras))
random.shuffle(indices)
for inicio in range(0, n_muestras, tamano_batch):
fin = min(inicio + tamano_batch, n_muestras)
batch_idx = torch.tensor(indices[inicio:fin])
yield X[batch_idx], y[batch_idx]
tamano_batch = 20
iterador_datos = generador_batches(tamano_batch, caracteristicas, objetivo)
# Inicialización de parámetros
W = torch.randn(2, 1, requires_grad=True) * 0.01
b = torch.zeros(1, requires_grad=True)
# Definición del modelo
def modelo_lineal(X, W, b):
return torch.mm(X, W) + b
# Función de pérdida (error cuadrático medio)
def perdida_ecm(y_pred, y_real):
return (y_pred - y_real.view(y_pred.shape)) ** 2 / 2
# Algoritmo de optimización SGD
def optimizador_sgd(parametros, lr, batch_size):
with torch.no_grad():
for param in parametros:
param.data -= lr * param.grad / batch_size
param.grad.zero_()
# Bucle de entrenamiento
tasa_aprendizaje = 0.02
epocas = 5
for epoca in range(epocas):
for batch_X, batch_y in generador_batches(tamano_batch, caracteristicas, objetivo):
prediccion = modelo_lineal(batch_X, W, b)
perdida = perdida_ecm(prediccion, batch_y)
perdida.sum().backward()
optimizador_sgd([W, b], tasa_aprendizaje, tamano_batch)
perdida_total = perdida_ecm(modelo_lineal(caracteristicas, W, b), objetivo)
print(f'Época {epoca + 1}, pérdida: {perdida_total.mean().item():.6f}')
Implementación con PyTorch
import torch
from torch.utils.data import DataLoader, TensorDataset
from torch import nn
# Creación del dataset
pesos_true = torch.tensor([3.5, -2.1])
sesgo_true = 1.8
X_train, y_train = crear_dataset(pesos_true, sesgo_true, 1000)
# Preparación de datos para PyTorch
dataset = TensorDataset(X_train, y_train)
cargador_datos = DataLoader(dataset, batch_size=20, shuffle=True)
# Definición del modelo con nn.Module
class RegresionLineal(nn.Module):
def __init__(self):
super().__init__()
self.capa_lineal = nn.Linear(2, 1)
def forward(self, x):
return self.capa_lineal(x)
modelo = RegresionLineal()
# Inicialización de pesos
nn.init.normal_(modelo.capa_lineal.weight, 0, 0.01)
nn.init.zeros_(modelo.capa_lineal.bias)
# Función de pérdida
criterio = nn.MSELoss()
# Optimizador
optimizador = torch.optim.SGD(modelo.parameters(), lr=0.02)
# Entrenamiento
for epoca in range(5):
for batch_X, batch_y in cargador_datos:
optimizador.zero_grad()
predicciones = modelo(batch_X)
perdida = criterio(predicciones, batch_y)
perdida.backward()
optimizador.step()
perdida_epoca = criterio(modelo(X_train), y_train)
print(f'Época {epoca + 1}, pérdida MSE: {perdida_epoca.item():.6f}')
# Parámetros aprendidos
print(f'Pesos aprendidos: {modelo.capa_lineal.weight.data.view(-1).tolist()}')
print(f'Sesgo aprendido: {modelo.capa_lineal.bias.item():.4f}')