2026/4/6 12:10:55
网站建设
项目流程
用PyTorch复现线性回归从理论到代码的保姆级拆解附D2L数据集实战线性回归是机器学习领域最基础也最重要的算法之一。作为深度学习的入门必备技能它不仅帮助我们理解模型训练的核心机制更是后续复杂神经网络的基础。本文将带你用PyTorch从零实现线性回归结合《动手学深度学习》(D2L)教材中的案例深入解析每个技术细节。1. 环境准备与数据生成在开始建模之前我们需要准备好开发环境。推荐使用Python 3.8和PyTorch 1.12版本。如果你还没有安装PyTorch可以通过以下命令快速安装pip install torch torchvision d2l线性回归的目标是学习一组参数使得预测值与真实值之间的差距最小化。为了演示这个过程我们首先生成一个模拟数据集import torch def synthetic_data(w, b, num_examples): 生成yXwb噪声 X torch.normal(0, 1, (num_examples, 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, -3.4]) true_b 4.2 features, labels synthetic_data(true_w, true_b, 1000)这段代码生成了1000个样本每个样本有两个特征。我们设置了真实权重true_w[2, -3.4]和偏置true_b4.2并在输出中添加了少量噪声模拟真实场景。提示在机器学习中我们通常会人为生成这样的数据集来验证算法因为这样可以精确知道模型应该学习到什么参数。2. 数据加载与批处理在实际项目中数据往往无法一次性全部加载到内存中。PyTorch提供了高效的数据加载机制但为了理解底层原理我们先手动实现一个简单的数据迭代器import random def data_iter(batch_size, features, labels): num_examples len(features) indices list(range(num_examples)) random.shuffle(indices) # 随机打乱顺序 for i in range(0, num_examples, batch_size): batch_indices torch.tensor( indices[i: min(i batch_size, num_examples)]) yield features[batch_indices], labels[batch_indices]这个迭代器每次返回一个批次的数据主要做了三件事随机打乱数据顺序按照指定批量大小划分数据返回特征和标签的批量张量我们可以测试一下这个迭代器batch_size 10 for X, y in data_iter(batch_size, features, labels): print(X.shape, y.shape) # 输出: torch.Size([10, 2]) torch.Size([10, 1]) break3. 模型定义与参数初始化线性回归模型的数学表达式非常简单$$ \hat{y} Xw b $$其中$X$是输入特征$w$是权重$b$是偏置项。在PyTorch中我们可以这样实现def linreg(X, w, b): 线性回归模型 return torch.matmul(X, w) b模型参数需要初始化后才能开始训练。通常我们会权重$w$从均值为0标准差较小的正态分布中随机初始化偏置$b$初始化为0w torch.normal(0, 0.01, size(2,1), requires_gradTrue) b torch.zeros(1, requires_gradTrue)这里的关键是设置了requires_gradTrue这告诉PyTorch需要计算这些参数的梯度这是自动微分的基础。4. 损失函数与优化算法线性回归常用的损失函数是平方误差损失MSE$$ L \frac{1}{2n}\sum_{i1}^n (\hat{y}_i - y_i)^2 $$对应的PyTorch实现def squared_loss(y_hat, y): 均方损失 return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2对于优化算法我们使用小批量随机梯度下降SGD。其核心思想是计算当前参数下损失函数关于参数的梯度沿着梯度反方向更新参数重复这个过程直到收敛def sgd(params, lr, batch_size): 小批量随机梯度下降 with torch.no_grad(): # 不计算梯度 for param in params: param - lr * param.grad / batch_size # 参数更新 param.grad.zero_() # 梯度清零注意每次更新参数后必须手动清零梯度否则PyTorch会累积梯度导致错误的结果。5. 训练循环与模型评估现在我们可以将各个组件组合起来构建完整的训练流程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) # 更新参数 # 每个epoch后评估整体损失 with torch.no_grad(): train_l loss(net(features, w, b), labels) print(fepoch {epoch 1}, loss {float(train_l.mean()):f})训练完成后我们可以比较学习到的参数与真实参数print(fw的估计误差: {true_w - w.reshape(true_w.shape)}) print(fb的估计误差: {true_b - b})理想情况下这些误差应该非常小说明模型成功学习到了数据的生成规律。6. 与解析解的比较线性回归有一个独特的性质它存在解析解闭式解。也就是说我们可以通过数学公式直接计算出最优参数而不需要迭代优化$$ w^* (X^TX)^{-1}X^Ty $$X features y labels w_analytic torch.matmul(torch.matmul(torch.inverse(torch.matmul(X.T, X)), X.T), y) print(f解析解得到的w: {w_analytic.reshape(true_w.shape)})虽然解析解看起来更直接但在实际中有几个限制需要计算矩阵的逆当特征维度很高时计算代价大要求数据集能全部放入内存对于更复杂的模型如神经网络通常不存在解析解相比之下梯度下降法可以处理超大规模数据集适用于各种模型结构可以灵活调整学习率等超参数7. 使用PyTorch高级API简化实现前面的实现帮助我们理解了底层原理但在实际项目中我们可以使用PyTorch提供的高级API来简化代码import torch.nn as nn # 定义模型 model nn.Sequential(nn.Linear(2, 1)) # 定义损失函数和优化器 criterion nn.MSELoss() optimizer torch.optim.SGD(model.parameters(), lr0.03) # 训练循环 for epoch in range(num_epochs): for X, y in data_iter(batch_size, features, labels): optimizer.zero_grad() output model(X) loss criterion(output, y) loss.backward() optimizer.step() print(fepoch {epoch1}, loss {loss.item():f})这种实现方式更加简洁而且利用了PyTorch的许多优化如自动参数初始化更高效的优化器实现内置的损失函数更灵活的网络结构定义8. 实际应用中的注意事项在将线性回归应用到真实项目时有几个关键点需要考虑特征缩放当特征量纲差异大时应该进行标准化处理from sklearn.preprocessing import StandardScaler scaler StandardScaler() features_scaled scaler.fit_transform(features)正则化为防止过拟合可以添加L1/L2正则化# L2正则化(岭回归) optimizer torch.optim.SGD([ {params: model[0].weight, weight_decay: 0.1}, {params: model[0].bias}], lr0.03)学习率调整合适的学习率对收敛至关重要scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size30, gamma0.1)评估指标除了损失函数还应该关注R²分数等指标from sklearn.metrics import r2_score r2 r2_score(labels.numpy(), model(features).detach().numpy())GPU加速对于大规模数据可以使用GPU加速计算device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) features, labels features.to(device), labels.to(device)9. 线性回归的局限性及扩展虽然线性回归简单有效但它有一些固有局限线性假设只能建模线性关系无法处理非线性模式解决方案可以通过特征工程引入多项式特征# 添加二次项特征 X_poly torch.cat([features, features**2], dim1)对异常值敏感平方损失会放大异常点的影响解决方案使用Huber损失等鲁棒损失函数criterion nn.HuberLoss()多重共线性问题当特征高度相关时参数估计不稳定解决方案使用主成分分析(PCA)降维或增加正则化尽管有这些局限线性回归仍然是理解更复杂模型的基础。许多先进的深度学习架构如残差网络(ResNet)中的跳跃连接本质上可以看作是对线性关系的扩展。