线性回归代码全解析:从数学原理到PyTorch实战与调参

发布时间:2026/10/6 3:20:07
线性回归代码全解析:从数学原理到PyTorch实战与调参 很多新手一提到 linear 代码第一反应就是y wx b觉得太简单不值得认真看。但真到了自己动手写项目跑数据调参的时候才发现问题一个接一个loss 不降、预测值全是同一个数、一加特征就内存爆炸、明明模型能拟合训练集但测试集一塌糊涂。这篇文章就把 linear 代码从数学到代码、从基础到实战全部拆开讲清楚不讲虚的直接上代码、上步骤、上排查经验。内容主要面向正在学机器学习和 PyTorch 的开发者也适合做算法工程、数据分析的朋友查漏补缺。1. 先搞清楚linear 代码到底在写什么1.1 从数学公式到代码的映射关系linear 代码的核心是模仿线性关系一个输出值由多个输入特征的加权求和再加上偏置得到公式就是 (y w_1x_1 w_2x_2 ... w_nx_n b)。这里 (w) 是权重(b) 是偏置。用生活里的话说就是单价乘以数量最后加一个固定费用。买水果就是最典型的线性场景苹果单价 5 元买 (x) 斤运费固定 2 元总价就是 (5x 2)。而这个总价计算规则在代码里就是 linear 层要做的事。那这一套怎么映射到代码在 PyTorch 里nn.Linear(in_features, out_features)这个层内部就是维护了一个权重矩阵 (W) 和一个偏置向量 (b)。输入 (x) 进来代码做的事就是矩阵乘法torch.matmul(x, W.T)加上b。理解了这一层映射关系你就明白 linear 代码听起来很玄本质就是矩阵运算的封装。我见过太多人看到nn.Linear就跳过以为它只是一个全连接层但如果你把它拆开看它干的事情和你用 numpy 写np.dot(X, w) b没有任何本质区别。只是在 PyTorch 中这个层还自己做了参数注册、初始化、梯度计算的前向通道准备工作。1.2 linear 代码的完整组成骨架一套完整的 linear 代码远不是模型定义那么点东西。真实项目里它至少包含六个部分数据准备加载数据、划分训练集/测试集、特征标准化。模型定义确定输入维度、输出维度选择是否加偏置。损失函数回归任务用 MSE分类任务用 CrossEntropy。优化器SGD 或 Adam设置学习率和权重衰减。训练循环前向传播、计算损失、反向传播、更新参数。评估在测试集上计算指标分析误差分布。从工程角度看这六件事缺一件代码就跑不顺。比如没有做特征标准化线性模型的梯度下降可能像走钢丝没有做训练/测试集划分你评估出来的指标全是假的。后面的实操部分我会按这个骨架一步步写代码现在先把骨架记住。2. 核心细节解析一套能用的 linear 代码不能少的部分2.1 特征标准化90% 的人忽略的致命第一步线性模型对特征的尺度极其敏感。假如特征 A 的范围是 0 到 1特征 B 的范围是 0 到 100000那权重更新时特征 B 对应的梯度会大很多模型会优先迁就特征 B导致训练过程特别不稳定。这背后的原因是梯度下降的更新幅度和特征尺度直接相关。你可以把 loss 想象成一个山谷如果两个特征尺度差距太大这个山谷就是个极扁的椭圆梯度下降在里面走之字形收敛极慢。解决办法就是标准化让每个特征的均值接近 0标准差接近 1。代码上用 sklearn 的StandardScaler就行from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)注意一个关键细节fit_transform只能在训练集上调用测试集上只调用transform。因为scaler的均值和方差是从训练集算出来的一旦你在测试集上也做fit_transform测试集的信息就泄漏到了模型里评估结果会虚高这个错误在真实项目里非常常见。2.2 参数初始化、学习率与损失函数的选择linear 层参数初始化的方式直接影响训练结果。在 PyTorch 中nn.Linear默认使用 Kaiming Uniform 初始化这个初始化方式是为了配合 ReLU 这类激活函数设计的。但如果你是在做纯线性回归直接使用默认初始化也没问题。我建议在训练前自己打印一下初始参数确认它们不是全零。全零初始化会导致所有神经元更新同步模型退化成一个神经元这是新手常踩的坑。学习率的选择在 linear 代码里几乎决定了生死。学习率太大loss 会震荡甚至直接变成 NaN学习率太小训练半天 loss 还在原地不动。我常用的经验值SGD 从 0.01 开始试Adam 从 0.001 开始试然后根据 loss 曲线的表现做调整。如果 loss 下降很慢把学习率放大 10 倍再试如果 loss 抖得厉害缩小 10 倍。损失函数的选择则取决于任务类型。回归任务用MSELoss均方误差分类任务用CrossEntropyLoss交叉熵损失。有些人在回归任务里用交叉熵在分类任务里用 MSE前者会得到莫名其妙的梯度行为后者会导致收敛极慢且准确率上不去。一句话任务类型决定损失函数不要混用。2.3 训练循环里最容易出的三个错误训练循环看起来就几行代码但恰恰是出错率最高的地方。第一个错误是忘记在每个 batch 前调用optimizer.zero_grad()。PyTorch 的梯度是累积的如果你不手动清零上一个 batch 的梯度会加到当前 batch 上导致梯度越来越大loss 不降反升。第二个错误是损失计算时没有把预测值压成一维。比如形状是(batch_size, 1)的预测值和形状是(batch_size,)的真实标签做损失计算PyTorch 内部广播机制会把结果变成一个矩阵而不是标量最终 gradient 计算时维度对不上直接报错或者算出一个伪 loss。第三个错误是模型模式没有切换。训练时模型处于train()模式评估时应该切换为eval()模式。虽然nn.Linear层本身没有 dropout 和 batchnorm模式切换影响不大但如果你沿用这个骨架扩展到深度网络忘记切换会导致推理结果不稳定。3. 实操过程与核心环节实现手把手写一遍 linear 代码3.1 数据集准备与可视化解读为了能清楚看到 linear 代码的拟合过程我先生成一份带噪声的线性数据。这样真实分布是已知的模型拟合的效果一眼就能看出来。import numpy as np import matplotlib.pyplot as plt np.random.seed(42) X np.linspace(0, 10, 200).reshape(-1, 1) true_w 2.5 true_b 1.0 y true_w * X.ravel() true_b np.random.normal(0, 1.5, size200)这里的true_w 2.5、true_b 1.0是真实参数噪声标准差 1.5。可视化后你会看到数据点围绕一条直线上下浮动这个围绕直线的形态就是所谓线性关系。把数据做成 PyTorch 需要的格式并划分训练集和测试集import torch from torch.utils.data import TensorDataset, DataLoader X_tensor torch.tensor(X, dtypetorch.float32) y_tensor torch.tensor(y, dtypetorch.float32).view(-1, 1) split_idx int(len(X_tensor) * 0.8) X_train, X_test X_tensor[:split_idx], X_tensor[split_idx:] y_train, y_test y_tensor[:split_idx], y_tensor[split_idx:] train_dataset TensorDataset(X_train, y_train) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue)view(-1, 1)的目的是把形状从(200,)改成(200, 1)确保和模型输出的形状一致这条细节能避免大量报错。3.2 三种实现方式对比手写梯度、PyTorch、sklearn现在给你看三种达到同样效果的实现方式从底层原理到工程便捷感受一下不同层次代码的区别。先看纯 numpy 手写梯度下降版本这个版本能让你真正理解 linear 代码在底层做了什么class LinearRegressionNumpy: def __init__(self, lr0.01, epochs1000): self.lr lr self.epochs epochs self.w None self.b None def fit(self, X, y): n_samples, n_features X.shape self.w np.zeros(n_features) self.b 0 for epoch in range(self.epochs): y_pred np.dot(X, self.w) self.b error y_pred - y dw (1 / n_samples) * np.dot(X.T, error) db (1 / n_samples) * np.sum(error) self.w - self.lr * dw self.b - self.lr * db return self def predict(self, X): return np.dot(X, self.w) self.b这个版本的关键在dw的计算np.dot(X.T, error)就是特征的梯度方向乘上1 / n_samples是取平均梯度最后沿负梯度方向更新参数。每次迭代w都在朝让 loss 最小的方向挪一步这就是训练的本质。再看 PyTorch 版本import torch.nn as nn import torch.optim as optim class LinearModel(nn.Module): def __init__(self, in_features1, out_features1): super().__init__() self.linear nn.Linear(in_features, out_features) def forward(self, x): return self.linear(x) model LinearModel() criterion nn.MSELoss() optimizer optim.SGD(model.parameters(), lr0.01) for epoch in range(500): for batch_x, batch_y in train_loader: optimizer.zero_grad() pred model(batch_x) loss criterion(pred, batch_y) loss.backward() optimizer.step()最后是 sklearn 的一行式from sklearn.linear_model import LinearRegression sk_model LinearRegression() sk_model.fit(X_train.numpy(), y_train.numpy())三个版本的结果几乎一样。我整理了一张对比表方便你根据场景选择实现方式代码量灵活度适合场景numpy 手写中等高可自由改梯度公式理解原理、教学、面试准备PyTorch少高自动求导可扩展深度学习项目、复杂模型sklearn极少低封装完整快速验证、传统数据分析我的建议新手至少手写一遍 numpy 版本再切换到 PyTorch。因为只有手写过一次你才会真正明白backward()和optimizer.step()到底替你做了什么。3.3 评估指标怎么选R2、MSE、MAE 的适用场景模型训练完不能只看 loss要算几个更有解释性的指标。最常用是 R2决定系数它表示模型解释了多少比例的方差。from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error y_pred model(X_test).detach().numpy() y_true y_test.numpy() r2 r2_score(y_true, y_pred) mse mean_squared_error(y_true, y_pred) mae mean_absolute_error(y_true, y_pred) print(fR2: {r2:.4f}, MSE: {mse:.4f}, MAE: {mae:.4f})R2 越接近 1 说明拟合越好0 表示模型跟瞎猜差不多负数说明模型比直接取均值还差。MSE 对异常值敏感因为误差被平方了MAE 更鲁棒。如果数据里有明显的极端异常值我建议以 MAE 为主要参考避免模型被一两个异常点带偏的评价。4. 从基础 linear 到实战进阶linear 思想在现代 AI 里的影子4.1 大模型里的 Linear Decoder 与 LoRA 微调很多人以为学完 linear 代码就结束了其实 linear 是很多现代 AI 架构的核心零件。大语言模型推理的最后一步基本都有一个 linear 层把隐藏状态映射到词表维度这个层叫 Linear Decoder线性解码器。它的本质就是把 hidden size 维度的向量乘以一个矩阵输出 vocab size 维度的分数再用 softmax 转成概率。说白了这就是一个高维的y wx b。再看 LoRA 微调很多人觉得它很高深其实它玩的就是 linear 层的权重矩阵。LoRA 的基本思想是冻结原来的权重 (W)在训练时只增加两个低秩矩阵 (A) 和 (B) 来模拟权重的变化 (W W BA)。因为 (B \times A) 的参数量远小于 (W)训练开销小很多。换句话说LoRA 做的事情就是在一个 linear 权重更新过程中寻找低成本近似理解了这个你再看大模型微调的相关代码就不会一头雾水。4.2 图像模型中的 Linear Bottleneck 与 1x1 卷积linear 思想不只是出现在全连接层在卷积网络里也能看到它。MobileNetV2 提出的 Linear Bottleneck 结构核心就是在瓶颈层去掉 ReLU 激活函数。为什么当特征维度被压得很低时ReLU 会把大量负值置为 0导致信息丢失也就是所谓的 Information Loss。MobileNetV2 的做法是在低维空间只做线性变换等价于只使用conv 1x1做通道混合不经过非线性激活这保证了低维特征的信息不被丢。你看卷积核为 1x1 的卷积本质上就是对每个像素的通道做了一次线性组合和nn.Linear做的事如出一辙。所以学会 linear 代码你再看 1x1 卷积、SENet 中的通道注意力、Transformer 中的 QKV 投影会发现它们都共享同一个数学内核线性变换。这个视角能帮你建立举一反三的能力。以后遇到任何网络结构第一反应不是去看它有多少层而是去拆它内部哪些操作是 linear 的、哪些是非线性的非线性放在哪里为什么放在那里。这个分析思路非常有用。5. 常见问题与排查技巧实录5.1 训练 loss 不下降怎么办loss 不动通常有三个原因。一是学习率太小梯度每次只挪一丁点你看到 loss 曲线几乎水平这时把学习率放大 10 到 100 倍。二是数据没有标准化前面说过尺度差异大会导致梯度下降路径扭曲严重时 loss 几乎不动做了标准化后往往马上好转。三是梯度在某些位置计算出了 NaN这通常是因为学习率太大导致参数溢出缩小学习率并检查输入数据里有没有无穷大值。排查时有个非常实用的技巧在每个 epoch 打印参数梯度的范数。如果梯度范数是 0说明反向传播没有真正执行如果是 NaN说明数值不稳定缩小学习率。5.2 预测值输出全是同一个常数出现这种情况多半是模型退化了。可能是因为你手写模型时把w初始化为全零所有样本的预测结果完全一致也可能是因为训练数据中目标变量y没有差异化或者数据量太少。还有一个容易被忽略的原因训练过程中你更新的是b而不是w检查一下梯度更新公式是否写对。5.3 特征维度太大直接爆内存当特征维度过高比如文本 TF-IDF 特征动辄几万维直接做矩阵乘法可能内存报警。这时有两种思路。第一种是做特征选择或降维PCA、TruncatedSVD把线性层的输入维度降下来第二种是换成优化器训练方式使用torch.sparse稀疏矩阵或者改用 SGD 配合小 batch 减少单次占用的内存。一般我建议优先做降维因为线性模型本身就是线性变换高维稀疏特征上效果并不会因为维度高而变好太多。5.4 数据里有异常值模型被整体带偏线性回归对异常值极其敏感因为 MSE 损失中的平方项把异常点的误差放大了。一个极端值可能让拟合直线被强行拉向它。遇到这种情况我的处理顺序是先用散点图或箱线图找出异常点确认是数据错误还是真实业务场景如果是真实存在的极端值考虑改用 Huber Loss它结合了 MSE 和 MAE 的优点对异常值比 MSE 鲁棒又不至于像 MAE 那样梯度恒定不收敛。criterion nn.SmoothL1Loss() # 这就是 Huber Loss 的 PyTorch 实现这个替换非常简单但效果往往出人意料。在含异常点的数据上SmoothL1Loss 训练的模型通常比 MSE 训练的模型稳健得多。5.5 训练集效果很好测试集指标很差这是过拟合的典型表现。线性模型过拟合通常是因为特征过多而样本太少模型把训练集噪声也学进去了。解决办法包括增加训练数据、减少特征、加入正则化L2 正则就是 weight decay在优化器里设置weight_decay1e-4即可、或者简化模型。不要一上来就换复杂模型线性模型加正则化在很多场景下已经足够好而且可解释性强很多。6. 实操心得与后续扩展方向我踩过太多次 linear 代码的坑最后总结几条个人心得希望对你有直接帮助。第一条先把数据标准化做了再谈模型调优。我见过不少项目花大量时间调学习率、换优化器最后发现是特征尺度问题。标准化不是可选项是必修项。第二条手工实现一遍梯度下降比看十篇文章都有效。当你真正写完dw np.dot(X.T, error) / n_samples这行代码你才理解什么是沿梯度反方向更新参数。以后遇到任何复杂的优化器你都会自动去拆它的更新公式这个底子打好了学什么模型都快。第三条linear 代码是最容易被低估的代码。表面上它就是个线性变换但它是所有深度学习模型的积木。理解nn.Linear的参数形状、初始化方式、梯度流动能帮你迁移到 Transformer、CNN、大模型微调等场景中。我强烈建议你把 PyTorch 里的nn.Linear源码看一遍几十行代码看完你对整个框架的理解会上一个台阶。后续你可以往这些方向扩展从单变量线性回归扩展到多变量线性回归加入 categorical 特征做独热编码从线性回归跳到逻辑回归再跳到 softmax 多分类从纯线性模型加上激活函数变成单层神经网络然后堆叠成多层感知机。你会发现每走一步前面打的 linear 基础都在起作用。希望这篇文章能帮你把 linear 代码这块地基打牢后面盖什么楼都不慌。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询