PyTorch Regresión Lineal

La regresión lineal es uno de los algoritmos de aprendizaje automático más básicos, utilizado para predecir un valor continuo. Es un método de análisis de regresión simple y común, cuyo objetivo es predecir la salida ajustando una función lineal.

Para un problema simple de regresión lineal, el modelo se puede expresar como:

  • y es el valor predicho (valor objetivo).
  • \(x_1\), \(x_2\), \(x_n\) son las características de entrada.
  • \(w_1\), \(w_2\), \(w_n\) son los pesos a aprender (parámetros del modelo).
  • b es el término de sesgo.

En PyTorch, el modelo de regresión lineal se puede implementar heredandonn.Modulela clase. Usaremos un ejemplo sencillo para explicar en detalle cómo usar PyTorch para implementar un modelo de regresión lineal.


Preparación de datos

Primero preparamos algunos datos sintéticos para entrenar nuestro modelo de regresión lineal. Aquí podemos generar un conjunto de datos con una relación lineal simple, donde cada muestra tiene dos características \(x_1\) y \(x_2\).

Ejemplo

import torch
import numpy as np
import matplotlib.pyplot as plt

# Semilla aleatoria, para asegurar que cada ejecución obtenga los mismos resultados
torch.manual_seed(42)

# Generar datos de entrenamiento
X = torch.randn(100, 2)  # 100 muestras, cada muestra con 2 características
true_w = torch.tensor([2.0, 3.0])  # Suponer pesos reales
true_b = 4.0  # Término de sesgo
Y = X @ true_w + true_b + torch.randn(100) * 0.1  # Añadir algo de ruido

# Imprimir parte de los datos
print(X[:5])
print(Y[:5])

El resultado de salida es el siguiente:

tensor([[ 1.9269,  1.4873],
        [ 0.9007, -2.1055],
        [ 0.6784, -1.2345],
        [-0.0431, -1.6047],
        [-0.7521,  1.6487]])
tensor([12.4460, -0.4663,  1.7666, -0.9357,  7.4781])

Este código crea un conjunto de datos lineal con ruido.

  • La entrada X es una matriz de 100x2; cada muestra tiene dos características.
  • La salida Y se genera a partir de los pesos y el sesgo reales, y se le añade algo de ruido aleatorio.
  • El uso detorch.manual_seed(42)asegura que cada ejecución obtenga los mismos resultados, lo que facilita la depuración y la reproducibilidad.

Definición del modelo de regresión lineal

Podemos, mediante la herencia,nn.Moduledefinir un modelo de regresión lineal simple. En PyTorch, el núcleo de la regresión lineal esnn.Linear()la capa, que se encarga automáticamente de la inicialización de los pesos y el sesgo.

Ejemplo

import torch.nn as nn

# Definir el modelo de regresión lineal
class LinearRegressionModel(nn.Module):
    def __init__(self):
        super(LinearRegressionModel, self).__init__()
        # Definir una capa lineal, con 2 características de entrada y 1 valor de predicción de salida
        self.linear = nn.Linear(2, 1)  # Dimensión de entrada 2, dimensión de salida 1

    def forward(self, x):
        return self.linear(x)  # Propagación hacia adelante, devolver el resultado de la predicción

# Crear una instancia del modelo
model = LinearRegressionModel()

Aquí,nn.Linear(2, 1)representa una capa lineal que tiene 2 características de entrada y 1 salida.forwardEl método define cómo realizar la propagación hacia adelante a través de esta capa.

Nota: nn.Linearcrea automáticamente la matriz de pesos y el vector de sesgo, no es necesario definirlos manualmente.


Definición de la función de pérdida y el optimizador

La función de pérdida común para la regresión lineal esla pérdida de error cuadrático medio (MSELoss), que se utiliza para medir la diferencia entre los valores predichos y los valores reales. PyTorch proporciona una función MSELoss lista para usar.

Usaremos elSGD (descenso de gradiente estocástico)oAdamcomo optimizador para minimizar la función de pérdida.

Ejemplo

# Función de pérdida (error cuadrático medio)
criterion = nn.MSELoss()

# Optimizador (usar SGD o Adam)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)  # Tasa de aprendizaje establecida en 0.01

# También se puede usar el optimizador Adam
# optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
Componente Descripción
MSELoss Calcula el error cuadrático medio entre los valores predichos y los reales, con la fórmula \(\frac{1}{n}\sum(y_{pred} - y_{true})^2\)
SGD Actualiza los parámetros mediante el descenso de gradiente estocástico; la tasa de aprendizaje controla la magnitud de cada actualización
Adam Optimizador de tasa de aprendizaje adaptativa, normalmente converge más rápido

Entrenamiento del modelo

Durante el entrenamiento, realizaremos los siguientes pasos:

  1. Usar los datos de entrada X para la propagación hacia adelante y obtener los valores predichos.
  2. Calcular la pérdida (la diferencia entre los valores predichos y los reales).
  3. Usar la retropropagación para calcular los gradientes.
  4. Actualizar los parámetros del modelo (pesos y sesgo).

Entrenaremos el modelo durante 1000 épocas e imprimiremos la pérdida cada 100 épocas.

Ejemplo

# Entrenar el modelo
num_epochs = 1000  # Entrenar durante 1000 épocas
for epoch in range(num_epochs):
    model.train()  # Poner el modelo en modo de entrenamiento

    # Propagación hacia adelante
    predictions = model(X)  # El modelo genera las predicciones
    loss = criterion(predictions.squeeze(), Y)  # Calcular la pérdida (nota: las predicciones deben comprimirse a 1D)

    # Retropropagación
    optimizer.zero_grad()  # Vaciar los gradientes anteriores
    loss.backward()  # Calcular gradientes
    optimizer.step()  # Actualizar los parámetros del modelo

    # Imprimir la pérdida
    if (epoch + 1) % 100 == 0:
        print(f'Epoch [{epoch + 1}/1000], Loss: {loss.item():.4f}')
  • predictions.squeeze(): comprime la salida del modelo de un tensor 2D a 1D, porque el valor objetivoYes un arreglo unidimensional.
  • optimizer.zero_grad(): antes de cada retropropagación es necesario vaciar los gradientes anteriores; de lo contrario, los gradientes se acumulan.
  • loss.backward(): calcula automáticamente los gradientes de todos los parámetros entrenables.
  • optimizer.step(): actualiza los pesos y el sesgo según los gradientes calculados.

Modo de entrenamiento vs modo de evaluación:Es necesario llamar amodel.train()en el bucle de entrenamiento. Aunque este ejemplo no usa Dropout ni BatchNorm, adquirir este hábito es importante para modelos complejos.


Evaluación del modelo

Una vez finalizado el entrenamiento, podemos evaluar el modelo observando sus pesos y sesgo. También podemos hacer predicciones sobre nuevos datos y compararlas con los valores reales.

Ejemplo

# Ver los pesos y el sesgo después del entrenamiento
print(f'Predicted weight: {model.linear.weight.data.numpy()}')
print(f'Predicted bias: {model.linear.bias.data.numpy()}')

# Hacer predicciones sobre nuevos datos
with torch.no_grad():  # En la evaluación no es necesario calcular gradientes
    predictions = model(X)

# Visualizar las predicciones y los valores reales
plt.scatter(X[:, 0], Y, color='blue', label='True values')
plt.scatter(X[:, 0], predictions, color='red', label='Predictions')
plt.legend()
plt.show()
  • model.linear.weight.dataymodel.linear.bias.data: estos atributos almacenan los pesos y el sesgo del modelo.
  • torch.no_grad(): en el modo de evaluación no es necesario calcular gradientes, lo que ahorra memoria y mejora la velocidad de inferencia.

Código de ejemplo completo

A continuación se muestra el código completo de todos los pasos anteriores, integrado para poder ejecutarse directamente:

Ejemplo

import torch
import torch.nn as nn
import matplotlib.pyplot as plt

# 1. Preparar los datos
torch.manual_seed(42)
X = torch.randn(100, 2)  # 100 muestras, 2 características
true_w = torch.tensor([2.0, 3.0])
true_b = 4.0
Y = X @ true_w + true_b + torch.randn(100) * 0.1

# 2. Definir el modelo
class LinearRegressionModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(2, 1)

    def forward(self, x):
        return self.linear(x)

model = LinearRegressionModel()

# 3. Definir la función de pérdida y el optimizador
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 4. Entrenar el modelo
num_epochs = 1000
for epoch in range(num_epochs):
    model.train()

    predictions = model(X)
    loss = criterion(predictions.squeeze(), Y)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if (epoch + 1) % 100 == 0:
        print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')

# 5. Evaluar el modelo
print(f'\nPeso después del entrenamiento: {model.linear.weight.data.numpy()}')
print(f'Sesgo después del entrenamiento: {model.linear.bias.data.numpy()}')
print(f'Peso real: {true_w.numpy()}')
print(f'Sesgo real: {true_b}')

Análisis de los resultados de ejecución

Después de ejecutar el código anterior, verás una salida similar a la siguiente:

Epoch [100/1000], Loss: 0.0654
Epoch [200/1000], Loss: 0.0398
Epoch [300/1000], Loss: 0.0243
Epoch [400/1000], Loss: 0.0148
Epoch [500/1000], Loss: 0.0090
Epoch [600/1000], Loss: 0.0055
Epoch [700/1000], Loss: 0.0033
Epoch [800/1000], Loss: 0.0020
Epoch [900/1000], Loss: 0.0012
Epoch [1000/1000], Loss: 0.0008

训练后的权重: [[2.0016 2.9973]]
训练后的偏置: [4.0015]
真实权重: [2. 3.]
真实偏置: 4.0

Se puede observar que, a medida que aumenta el número de épocas de entrenamiento, el valor de la pérdida disminuye continuamente; finalmente, los pesos y el sesgo del modelo son muy cercanos a los valores reales, lo que indica que el modelo aprendió correctamente la relación lineal de los datos.


Ampliación: uso de optimizadores más avanzados

Además de SGD, PyTorch ofrece muchos otros optimizadores. A continuación se muestra un ejemplo con el optimizador Adam:

Ejemplo

# Usar el optimizador Adam (normalmente converge más rápido)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

# Entrenar el modelo
for epoch in range(num_epochs):
    model.train()

    predictions = model(X)
    loss = criterion(predictions.squeeze(), Y)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if (epoch + 1) % 100 == 0:
        print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
Optimizador Características Escenarios de uso
SGD Simple y directo, requiere ajustar manualmente la tasa de aprendizaje Grandes conjuntos de datos, escenarios clásicos
Adam Tasa de aprendizaje adaptativa, convergencia rápida La mayoría de los escenarios, opción predeterminada recomendada
RMSprop Adecuado para redes neuronales recurrentes Modelos secuenciales como RNN, LSTM, etc.

Preguntas frecuentes

Problema 1: el valor de pérdida no disminuye

Si el valor de pérdida no disminuye durante el entrenamiento, las posibles causas incluyen:

  • La tasa de aprendizaje es demasiado pequeña, lo que hace que la actualización de parámetros sea demasiado pequeña.
  • La tasa de aprendizaje es demasiado grande, lo que provoca que se salte la solución óptima.
  • Los datos tienen problemas, como diferencias demasiado grandes en el rango de los valores de las características.

Solución:

  • Intente ajustar la tasa de aprendizaje (empiece probando con 0.1, 0.01, 0.001).
  • Estandarice los datos de entrada.
  • Verifique si hay valores atípicos o faltantes en los datos.

Problema 2: forma de las predicciones no coincide

Si se produce un error de discrepancia de formas al calcular la pérdida, es posible que necesite usarsqueeze()ounsqueeze()ajustar la forma del tensor.

Ejemplo

# Verificar la forma de salida
predictions = model(X)
print(f'Forma de salida del modelo: {predictions.shape}')  # Debería ser [100, 1]

# Comprimir a 1D
predictions = predictions.squeeze()
print(f'Forma después de comprimir: {predictions.shape}')  # Debería ser

# Calcular la pérdida
loss = criterion(predictions, Y)

Problema 3: cómo mejorar la precisión del modelo

  • Aumentar el número de épocas de entrenamiento.
  • Ajustar la tasa de aprendizaje.
  • Usar una estructura de modelo más compleja (como una red neuronal multicapa).
  • Aumentar la cantidad de datos de entrenamiento.
Otras extensiones