用PyTorch实现线性回归:从理论到代码的保姆级拆解(附d2l库实战)

线性回归是机器学习领域最基础也最重要的算法之一,它不仅是理解更复杂模型的基石,也是许多实际业务场景中的首选解决方案。本文将带你从零开始,用PyTorch完整实现一个线性回归模型,并通过d2l库提供的工具链展示整个开发流程。无论你是刚入门深度学习的新手,还是希望巩固基础的从业者,都能从中获得实用价值。

1. 环境准备与数据生成

在开始建模之前,我们需要确保开发环境配置正确。推荐使用conda创建独立的Python环境:

conda create -n pytorch-regression python=3.9
conda activate pytorch-regression
pip install torch torchvision d2l matplotlib

线性回归的核心假设是特征与目标值之间存在线性关系。我们可以用以下函数生成模拟数据:

import torch

def synthetic_data(w, b, num_samples):
    """生成符合线性关系的数据集"""
    X = torch.normal(0, 1, (num_samples, len(w)))
    y = torch.matmul(X, w) + b
    y += torch.normal(0, 0.01, y.shape)  # 添加噪声
    return X, y.reshape((-1, 1))

true_w = torch.tensor([2.0, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

这段代码生成了1000个样本,每个样本有2个特征。我们可以检查第一个样本:

features[0]: tensor([0.8321, -0.7421])
labels[0]: tensor([7.4107])

提示:在实际项目中,数据探索(EDA)是必不可少的步骤。建议使用matplotlib绘制特征与标签的散点图,直观验证线性关系。

2. 数据加载与批处理

深度学习模型通常采用小批量梯度下降进行训练,这要求我们实现高效的数据加载器。PyTorch提供了DataLoader类,但为了理解原理,我们先手动实现:

import random

def data_iter(batch_size, features, labels):
    """生成随机小批量数据"""
    num_samples = len(features)
    indices = list(range(num_samples))
    random.shuffle(indices)  # 打乱顺序
    
    for i in range(0, num_samples, batch_size):
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_samples)])
        yield features[batch_indices], labels[batch_indices]

使用示例:

batch_size = 10
for X, y in data_iter(batch_size, features, labels):
    print("Batch features shape:", X.shape)
    print("Batch labels shape:", y.shape)
    break

输出将显示:

Batch features shape: torch.Size([10, 2])
Batch labels shape: torch.Size([10, 1])

注意:实际项目中应使用PyTorch的TensorDataset和DataLoader,它们支持多进程加载和自动批处理等高级功能。

3. 模型构建与训练流程

3.1 初始化参数

线性回归模型的数学表达式为:ŷ = XW + b。我们需要初始化权重和偏置:

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

关键点:

  • requires_grad=True 告诉PyTorch需要计算这些参数的梯度
  • 初始值不宜过大,否则可能导致梯度爆炸

3.2 定义模型和损失函数

线性回归模型实现起来非常简单:

def linreg(X, w, b):
    """线性回归模型"""
    return torch.matmul(X, w) + b

我们使用均方误差(MSE)作为损失函数:

def squared_loss(y_hat, y):
    """MSE损失函数"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

3.3 实现优化算法

小批量随机梯度下降(SGD)是最基础的优化器:

def sgd(params, lr, batch_size):
    """小批量梯度下降"""
    with torch.no_grad():  # 禁用梯度计算
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()  # 梯度清零

参数更新公式:θ = θ - η∇θJ(θ)

3.4 训练循环

将上述组件组合成完整训练流程:

lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss

for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y)  # 计算损失
        l.sum().backward()  # 反向传播
        sgd([w, b], lr, batch_size)  # 参数更新
    
    with torch.no_grad():  # 评估整个数据集
        train_l = loss(net(features, w, b), labels)
        print(f'epoch {epoch+1}, loss {float(train_l.mean()):f}')

典型输出:

epoch 1, loss 0.038472
epoch 2, loss 0.000143
epoch 3, loss 0.000048

4. 模型评估与可视化

训练完成后,我们需要评估模型性能:

print(f'w估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b估计误差: {true_b - b}')

输出示例:

w估计误差: tensor([ 0.0003, -0.0002], grad_fn=<SubBackward0>)
b估计误差: tensor([-0.0004], grad_fn=<RsubBackward1>)

可视化是理解模型行为的重要手段。我们可以用matplotlib绘制:

import matplotlib.pyplot as plt

# 绘制特征与预测值的关系
plt.scatter(features[:, 1].detach().numpy(), 
            labels.detach().numpy(), s=5)
plt.plot(features[:, 1].detach().numpy(),
         (w[1]*features[:, 1] + b).detach().numpy(), 'r-')
plt.xlabel('Feature 2')
plt.ylabel('Label')
plt.show()

这张图应该显示数据点沿一条直线分布,而我们的模型线很好地拟合了这个趋势。

5. 使用d2l库简化流程

d2l库提供了许多实用工具可以简化上述流程。例如,数据加载可以简化为:

from d2l import torch as d2l

batch_size = 10
data_iter = d2l.load_array((features, labels), batch_size)

训练循环也可以更加简洁:

trainer = d2l.Trainer(max_epochs=num_epochs)
model = d2l.LinearRegression(lr=lr)
trainer.fit(model, data_iter)

d2l还内置了可视化工具:

d2l.show_images(features[0:10], 2, 5)  # 查看前10个样本
d2l.plot([true_w @ x + true_b for x in features[0:10]], 
         [model(x) for x in features[0:10]], 'Actual', 'Predicted')

6. 进阶技巧与常见问题

6.1 特征缩放

当特征尺度差异大时,应对特征进行标准化:

features = (features - features.mean(0)) / features.std(0)

6.2 学习率选择

学习率对训练效果至关重要。可以尝试学习率调度器:

scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

6.3 过拟合处理

虽然线性回归不易过拟合,但当特征多时可以考虑L2正则化:

loss = nn.MSELoss()
optimizer = torch.optim.SGD([w, b], lr=lr, weight_decay=0.1)  # weight_decay即λ

6.4 GPU加速

如果有GPU可用,可以将数据和模型转移到GPU:

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
features = features.to(device)
labels = labels.to(device)
w = w.to(device)
b = b.to(device)

7. 实际项目中的应用建议

在真实业务场景中使用线性回归时,有几个实用建议:

  1. 数据质量检查:确保没有缺失值和异常值
  2. 特征工程:尝试多项式特征、交互特征等
  3. 模型诊断:检查残差是否符合正态分布
  4. 可解释性:分析权重大小和符号的业务含义

例如,在房价预测项目中,可以这样扩展:

# 添加二次项特征
poly_features = torch.cat([features, features**2], 1)

# 添加交互项
interaction = (features[:, 0] * features[:, 1]).reshape(-1, 1)
all_features = torch.cat([features, features**2, interaction], 1)

最后,记得保存训练好的模型:

torch.save({
    'w': w,
    'b': b
}, 'linear_regression.pth')
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐