ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PyTorch手把手实现线性回归,打通深度学习训练闭环

PyTorch手把手实现线性回归,打通深度学习训练闭环 说实话很多人觉得线性回归是统计学课本里“退学费”的内容和深度学习这种听起来高大上的东西八竿子打不着。但真上手之后你会发现它恰恰是进入深度学习的“11”看着简单却把数据、模型、损失函数、优化器这一整套训练流程都装进去了。不管你是翻“鱼书”《深度学习入门基于Python的理论与实现》还是刷“动手深度学习”的教程线性回归基本都排在第一课。原因很简单——你搞懂它后面CNN、Transformer这些花活背后的训练逻辑就通了搞不懂它后面大概率在调参玄学里打转。这篇文章不整虚的你照着一步步敲就能在PyTorch里从一个神经元开始把线性回归完整训练出来。文章适合刚入门深度学习、准备跑第一个模型、或者已经会用框架但没搞懂原理的朋友。我会把数学、代码、踩坑经验揉在一起讲尽量让你看完能从“我知道有这个东西”变成“我真会实现它”。1. 线性回归值得重新认识一遍1.1 它在深度学习全家桶里的准确位置线性回归解决的是回归问题也就是用连续数值预测连续数值。比如根据房间面积预测房价根据广告投放量预测销售额根据上一时刻的流量预测下一时刻的拥堵指数这些都是典型的回归问题。在深度学习视角下线性回归还有一个更重要的身份它是最简化的神经网络。你随便翻开一个神经网络模型的示意图最基础的神经元就是“加权求和加偏置”写成公式就是 y w1x1 w2x2 ... wn*xn b。这不就是多元线性回归吗深度学习框架里的全连接层Linear层本质就是对输入做线性变换后面再接一个非线性激活函数。而线性回归等于跳过了激活函数只保留线性变换部分所以它经常被解释成“没有激活函数的单层神经网络”。把这个关系打通之后你再看全连接层的参数矩阵、偏置向量就不会觉得陌生了。1.2 “一条直线”之外多维输入的线性回归很多人对线性回归的印象还停留在 y kx b一条直线拟合散点图。这个印象没错但它只是最特殊的一维情况。实际工程里输入特征几乎都是多维的比如预测房价要看面积、楼层、朝向、学区、房龄等等这时模型变成y w1x1 w2x2 ... wn*xn b写成矩阵形式就是 y Xw b其中 X 是样本矩阵w 是权重向量。这就和深度学习里的全连接层完全对齐了输入可以理解为特征向量权重可以理解为神经元里的连接权重。我之前做过简化版的温度场预测实验把空间坐标和时序位置作为输入传感器读出的温度作为输出第一版用的就是线性回归。用它能快速验证数据质量、特征是否有效、训练流程是否通畅效果稳定以后再上更复杂的网络。这种“先用简单模型跑通全流程”的思路在很多深度学习实战项目里都适用包括热搜里提到的“深度学习预测流场”这类物理场问题早期版本也经常用线性模型做baseline。1.3 为什么所有教材都把线性回归放第一课因为训练一个神经网络的核心闭环在线性回归上就已经完整出现了喂数据算预测值算预测值和真实值之间的差距损失根据差距反向传递梯度更新模型参数重复这个过程直到损失降到合理范围。很多新手一上来就啃卷积神经网络看了一堆卷积、池化、感受野代码是跑通了但心里是虚的因为最底层的“模型-损失-优化”闭环没有建立。线性回归没有复杂的网络结构是所有环节暴露得最清楚的教学工具。它特别适合回答这类问题模型参数到底存在哪损失函数到底算的是什么反向传播传回去的是什么参数更新更新了什么这些问题在几行代码里就能看见答案。2. 数学别怕线性回归到底在解什么2.1 目标函数拟合到什么程度才算“好”线性回归的目标是找一组参数 w 和 b让预测值尽可能接近真实值。那怎么定义“接近”最常用的是均方误差Mean Squared Error简称MSE也叫L2损失L(w, b) (1/n) * Σ(y_i - ŷ_i)²其中 y_i 是真实值ŷ_i 是模型预测值。为什么用平方而不是直接用绝对误差有两点考量平方误差对大的误差惩罚更狠。一个差2的样本贡献4差4的样本贡献16这样一来优化器会更优先处理那些严重偏离的样本。平方误差处处可导求梯度非常方便。绝对误差在零点不可导优化时会有麻烦。从几何视角看最小化平方误差就是在做“最小二乘拟合”让所有样本点到拟合直线的垂直距离平方和最小。这就是线性回归最基本的解法思路。2.2 最小二乘的解析解公式很美但应用有限如果只聊数学线性回归是有唯一最优解的。把损失函数写成矩阵形式 L ||Xw - y||²对 w 求导并令导数为0可以直接解出w* (XᵀX)⁻¹Xᵀy这个解析解很漂亮只要 XᵀX 可逆一步就能得到全局最优参数不需要训练。但实际深度学习场景里这个公式几乎用不上。原因有三个特征矩阵可能非常大算 XᵀX 的逆矩阵代价极高。如果特征之间存在强相关性XᵀX 会接近奇异求逆数值不稳定。更关键的是这种求解方式无法推广到带非线性激活函数的神经网络。神经网络损失函数不是凸函数没有一步到位的解析解必须靠迭代优化。所以深度学习时代的核心迭代解法是下面这个词梯度下降。2.3 梯度下降深度学习训练的真正引擎梯度下降的思路特别生活化。你在山里被蒙着眼要走到最低谷怎么办最笨但有效的办法就是每走一步就感觉一下哪个方向更陡然后往那个方向迈一步。数学里梯度指向的是函数值上升最快的方向所以往梯度的反方向走就是下降最快的方向。参数更新公式可以写为w w - lr * ∂L/∂w这里的 lr 是学习率控制每一步迈多大。∂L/∂w 是损失对权重的梯度它告诉模型“现在这一步到底应该往哪个方向调整”。在PyTorch里这套过程被封装得很好。你只管写好模型前向计算定义损失函数调用 loss.backward() 计算梯度再调用 optimizer.step() 更新参数。新手必须搞明白的是optimizer.step() 做的事本质上就是在执行 w w - lr * 梯度 这个公式。我再补充一个直觉对于单变量线性回归 y wx b梯度下降会同时更新 w 和 b。如果学习率设置得太大参数会来回震荡不收敛设置得太小收敛速度慢得让人怀疑人生。后面我会给一组实际可用的经验值别凭空瞎设。3. 手把手实现用PyTorch从零跑通线性回归3.1 环境准备别让环境把第一个模型劝退建议先不考虑GPU用CPU完全可以把线性回归训练起来。创建虚拟环境这一步别省Python项目依赖冲突是浪费时间的头号杀手。conda create -n linear-reg python3.10 -y conda activate linear-reg pip install torch numpy matplotlib如果你已经有Ubuntu 20.04这类Linux环境并且想上GPU跑就需要额外装对应版本的CUDA、cuDNN和GPU版的PyTorch这个坑比较大新手很容易装完发现 torch.cuda.is_available() 是 False。我的建议是第一遍跑通管线用CPU就行之后真的需要再折腾GPU版本否则很容易在环境和你的耐心之间二选一。用云平台或者Colab也行好处是不用配本地环境打开浏览器就能跑。差别只在于运行环境代码逻辑完全一样。3.2 造一份“真实感”足够的数据没有真实数据集的时候合成数据是学习训练机制的最好工具。你提前预设真实参数训练完看看模型能不能把它学回来整个过程像做实验一样直观。我们造1000个样本特征 x 在 [-2, 2] 之间均匀分布真实关系是 y 2x 1再加上一点高斯噪声模拟现实环境import torch torch.manual_seed(42) x torch.linspace(-2, 2, 1000).reshape(-1, 1) true_w 2.0 true_b 1.0 y true_w * x true_b torch.randn(x.shape) * 0.3注意两点。第一x 的形状是 [1000, 1]也就是1000个样本每个只有1个特征这是nn.Linear期望的二维输入格式很多新手在这里踩坑。第二shuffle数据集防止模型“记住”固定顺序不过对于简单随机分布影响不大但养成习惯很重要。再划分训练集和验证集n_train 800 x_train, y_train x[:n_train], y[:n_train] x_val, y_val x[n_train:], y[n_train:]3.3 模型定义、损失函数、优化器与训练循环用PyTorch定义单层线性模型就三行核心代码。nn.Linear(1, 1) 表示输入维度是1、输出维度是1模型内部自动初始化了一个权重和一个偏置import torch.nn as nn import torch.optim as optim model nn.Linear(1, 1) loss_fn nn.MSELoss() optimizer optim.SGD(model.parameters(), lr0.1)训练循环是核心中的核心。我建议新手把每一行都看明白而不是直接复制epochs 200 for epoch in range(epochs): model.train() y_pred model(x_train) loss loss_fn(y_pred, y_train) optimizer.zero_grad() loss.backward() optimizer.step() if (epoch 1) % 20 0: print(fepoch {epoch1}, loss {loss.item():.4f})这段代码做了什么前向传播先算预测值再用均方误差算预测值和真实值的差距然后清零梯度反向传播求出每个参数的梯度最后让优化器按梯度方向更新一次参数。跑完后你会看到loss在逐步下降。我实际跑出来的结果大概几十轮后loss就会降到0.1以下模型学到的权重和偏置会逐渐逼近预设的2和1但不会完全精确因为数据里加了噪声模型只能逼近真实分布不能精确还原每个点。如果你把学习率调成1.5loss会先降后开始震荡甚至爆掉变大。调成0.0001你会发现200轮结束后损失还很高。这两个翻车实验我建议亲自试一遍对学习率的理解会立刻从“听过”变成“感受过”。3.4 把训练结果画出来亲眼看到“学会”数据、代码、数字都还不够直观画图是验证模型学到的关键一步import matplotlib.pyplot as plt plt.scatter(x.numpy(), y.numpy(), s2, alpha0.5, labeltrue data) plt.plot(x.numpy(), model(x).detach().numpy(), colorred, linewidth2, labelfitted line) plt.scatter(x_train.numpy(), y_train.numpy(), s2, alpha0.3, labeltraining points) plt.legend() plt.show() print(learned w:, model.weight.item()) print(learned b:, model.bias.item())画出来的图里原始数据是一条带噪声的带状分布红色拟合线从随机初始状态逐步逼近带状中心的真实直线。这一眼就能看出训练是否成功。除了肉眼看数值上可以用R²决定系数评估拟合质量R²越接近1说明模型解释的数据方差比例越高公式和实现都不复杂y_val_pred model(x_val).detach().numpy() ss_res ((y_val.numpy() - y_val_pred) ** 2).sum() ss_tot ((y_val.numpy() - y_val.numpy().mean()) ** 2).sum() r2 1 - ss_res / ss_tot print(R² on valid:, r2)合成场景里验证集R²通常能到0.9以上。这意味着你已经用深度学习框架完成了一次完整的“数据-模型-损失-优化”闭环这个闭环就是后面所有深度学习模型训练的基本模板。4. 从线性回归到深度学习模型选型与扩展思路4.1 什么时候该用线性回归什么时候必须换线性回归最实用的场景之一是当baseline。在任何深度学习任务里不管问题多复杂先跑一个线性模型作为性能底线永远是个好习惯。如果线性模型已经能到90分那花大力气搞深度学习可能没必要或者方向错了如果线性模型只有40分你再上复杂模型就有了明确的对比参照。但线性回归的局限也很明显它假设特征和目标之间是线性关系。现实世界往往不是这样房价对面积的边际效应递减广告投放效果有饱和区间这些都是典型的非线性关系。这个时候强行用线性回归模型会欠拟合。遇到这种情况一个渐进的扩展路径是这样的先尝试多项式特征把 x 变成 x、x²、x³ 的组合再喂给线性模型如果还不够再加激活函数变成真正的多层神经网络。这比一上来就堆复杂网络要稳妥得多。4.2 加一个激活函数你就从线性回归走进了深度学习你可以把线性回归的输出接上一个ReLU激活函数然后就变成了单个神经元组成的非线性模型y ReLU(wx b)。再加一个隐藏层就是经典的多层感知机MLP。我见过很多人一开始就想搭一个七八层的网络结果调参调到怀疑人生。更合理的方式是先在线性回归基础上加一层隐藏层看看效果提升多少不够再加一层。每加一层都在验证“这个复杂度是否真的带来收益”而不是盲目往深了堆。尤其是回归任务里的连续输出层通常不加激活函数保持线性输出。这也延续了线性回归的直觉模型可以预测任意范围的连续值。而分类任务最后一般接Softmax或者Sigmoid输出概率两者任务类型不同别搞混。4.3 回归模型评估与调优要点线性回归做baseline时评估指标要选对。下面是我常用的回归指标速查表指标公式特点MSEΣ(y - ŷ)² / n常用对大误差敏感和训练损失一致RMSE√MSE量纲和原始y一致容易解读MAEΣ|y - ŷ| / n对大误差不敏感更稳健R²1 - SS_res / SS_tot表示模型解释了多少比例的数据方差调优层面我建议按顺序做第一对特征做归一化标准做法是减均值除标准差否则数值尺度差很多的特征会让梯度下降非常缓慢第二调整学习率观察loss曲线是下降还是震荡第三加L2正则化在PyTorch里就是给优化器传入weight_decay参数第四训练过程中记录验证集loss一旦验证集loss开始回升马上停这就是早停法。一个特别容易忽略的点是固定随机种子。深度学习里有太多随机因素数据划分、参数初始化、数据洗牌。固定种子的目的在于让你的实验可复现否则你调参时根本无法判断效果提升是来自模型改动还是运气爆棚。每次实验开头写一行 torch.manual_seed(42) 的收益远超你的想象。5. 避坑手册我踩过的线性回归和深度学习新手坑5.1 常见问题速查表现象、原因、解法问题现象可能原因解决方案loss不降一直在初始值附近学习率太小梯度没传回来数据没喂对调大学习率检查optimizer是否包了model参数打印梯度值loss震荡降一点马上反弹学习率太大调小学习率常见范围0.001到0.1loss变成NaN学习率爆炸输入包含缺失值或无穷值降学习率清洗数据检查数据里是否有nan训练loss很低验证loss很高模型过拟合加L2正则化增加数据量实施早停参数w、b学不到真实值数据没shuffle噪声太大数据量太少固定随机种子增加数据量多次尝试初始化batch size理解错一个epoch只更新一次把整个数据集当成一个batch梯度更新频率低用小批次训练比如batch size取32或64表格里第一个问题最隐蔽。loss不降不一定是学习率问题很可能是梯度根本没有回传。排查方法是打印模型参数的梯度每个训练回合后如果 model.weight.grad 是None那就说明反向传播没有生效先查数据形状是不是 [batch, features]再查模型输出和真实标签形状是不是一致。5.2 三条新手最该记住的实操经验第一先画数据再建模。拿到的第一件事不是写模型而是把特征和标签的散点图画出来。如果连数据长什么样都不知道后面所有调参都是盲人摸象。第二先跑通再调优。第一版代码的目标是能用不是最好。先小数据量、小epoch数跑通全流程确认能输出loss并且逐步下降再悠着加大规模。很多人一上来就大规模训练结果一个维度错误卡半天浪费的时间全花在调试上。第三盯住loss曲线而不是盯着代码发呆。训练过程中每隔几个epoch打印一次lossloss曲线就是你的“心电图”。下降平滑说明健康剧烈震荡说明学习率过大原地不动说明有小毛病NaN直接重来。5.3 用AI工具辅助写代码时的注意点现在很多人用codex这类AI工具辅助写深度学习代码确实能省不少事。让它生成数据、模型、训练循环这些模板化代码效率很高。但我必须提醒几件事AI生成的代码不保证环境和版本适配。我之前见过AI给出的代码用的还是旧版register_parameter写法直接跑就报错。你不要无脑复制至少能看懂训练循环里三行关键代码loss.backward() 是算梯度optimizer.zero_grad() 是清梯度optimizer.step() 是更新参数。还有一个更常见的坑AI写代码时你问它效果怎么样它只会给你一个“看起来合理”的loss曲线。真实效果必须跑你自己的数据打印真实的loss、可视化真实的拟合结果才行。AI是工具不是你的实验替身。它能帮你写代码却不能帮你理解数据和模型之间的微妙关系。5.4 环境配置和云平台的一点省心建议再补充一句环境方面的事。网上关于GPU版深度学习环境配置的资料很多但新手的首要任务不是把环境配到最豪华而是先让自己的代码跑起来。有条件用云平台就用云平台本地CPU能跑就先本地跑千万别一上来就在Ubuntu里编译CUDA环境问题能劝退至少三成新手。等你确实想要GPU加速了再按官方文档一步步装驱动、CUDA、cuDNN、PyTorch并验证 torch.cuda.is_available() 返回True。顺序别乱版本别贪新与PyTorch官方匹配合适版本是核心原则。我个人带新人入门时最常让他们做一件事把合成数据的真实参数埋好训练完打印出模型学到的权重和偏置然后观察这两个数和真实值有多接近。这件事看起来简单但它逼着你把从数据到模型、从损失到梯度再到参数更新的整条链路翻了个底朝天。等你能解释清楚为什么权重能学回2附近、损失是怎么一步步降下去的注意力机制、残差结构这些复杂概念对你来说只剩下“往上堆层”的熟悉感不会再觉得深不可测。深度学习里90%的模型都有同一个训练骨架线性回归就是把这个骨架洗得最干净的那盘菜值得认认真真吃透它。
返回列表