资讯动态

线性回归与最小训练闭环:从数据到模型的深度学习入门实践

发布时间:2026/10/6 4:44:57 来源:尧图企业网站定制
“最小训练闭环”这个说法是我自己啃李沐的《动手学深度学习》时总结出来的一条主线。深度学习的概念实在太多了——模型、损失函数、优化器、数据集、评估指标如果一上来就想着“每一个都彻底弄懂再往下走”大概率没几天就卡死在半路。而线性回归Linear Regression之所以被放在全书最前面不只是因为它数学上简单更在于它能组成一个真正意义上的“最小训练闭环”数据、模型、损失、优化、评估五个环节每样都只有最基础的一个版本代码量不到一百行在普通笔记本上几十秒就能跑完。走通这个闭环之后后面再学CNN、RNN、Transformer你都会发现它们其实还是同一套流程换的只是数据和模型结构而已。这篇文章我想把这条闭环拆开讲透为什么选线性回归起步、四要素各自的职责、最小可运行代码长什么样、第一次跑训练最容易在哪里翻车以及跑通之后你得能回答哪几个问题才算是真懂了。内容主要基于李沐《动手学深度学习》的线性回归章节但我会把原书里“略过没细说”的那些坑和理由补出来给正在照着书学的朋友当一份陪跑笔记。1. 为什么要走最小闭环线性回归的门槛设计1.1 一个神经元本质上就是线性回归很多初学者会把线性回归当成“高中统计学内容”觉得深度学习第一章讲这个很没劲。但李沐在书里第一页就点破了一个关键事实神经网络里的单个神经元在还没有加激活函数的时候做的就是线性变换也就是 \(y wx b\)。你后面见到的多层感知机、卷积网络里的全连接层甚至注意力机制里那个打分函数底层都逃不开这个线性变换。这也是为什么全书不看图像、不做情感分析偏要用一个合成数据集来教线性回归——因为在这个模型里没有任何多余的“魔法”。输入是几个数值特征输出是一个数值中间加点随机噪声整个逻辑透明到可以拿计算器手算。拿这样一个模型去理解训练过程每一步都能看见、能复现、能验证。等以后模型变成几百万参数的黑盒子你再想回头看清楚梯度是怎么流动的就难了。我当时读到这里最大的感受是线性回归不是深度学习的“前置知识”它本身就是深度学习的建筑砖块。把它彻底搞懂后面看任何一个模型你都能在结构图里认出“哪一块在做线性变换哪一块加了非线性哪一块在做归一化”。这种拆解能力比记住某个特定网络的架构重要得多。1.2 三种解法在同一个小模型上排排坐线性回归另一个难得的属性在于它是少数同时拥有解析解和数值解的主流模型。解析解是 \(w^* (X^TX)^{-1}X^Ty\)直接用矩阵运算一步算出最优权重数值解则是梯度下降从随机的初始参数出发靠反复迭代一步步逼近。两个方法算出来的 \(w\) 几乎一模一样——这给了我们一个极其宝贵的“标准答案参照系”。我在学这一节时特意做了个小实验先用解析解求出真实最优权重再打印梯度下降每一轮迭代后的参数放在一张表里对比。你能肉眼看明白梯度下降是怎么一步一步“摸”到最优解附近的这种体会比看任何原理图都深刻。更重要的是这个设计为后面所有章节铺好了路。以后遇到没有解析解的模型比如带ReLU的多层感知机你不会慌因为你已经在线性回归上建立过“梯度下降能把参数推到正确位置”的直觉。所谓的“深度学习很难”很大程度是在这个闭环没有建立起来的时候所有概念都悬浮着互相没有锚点而线性回归就是那个最坚固的锚点。2. 把模型拆开看数据、模型、损失、优化四要素2.1 合成数据里的“上帝视角”D2L第一个实验用的数据是合成的。李沐的做法很巧妙先自己设定一组真实的权重和偏置 \(w [2, -3.4]b 4.2\)然后用随机生成的 \(X\) 去计算 \(y Xw b\)最后再加上一个服从正态分布的噪声项得到带噪声的标签。这个设计的教学价值极高。真实项目里你永远不知道数据背后真正的“规律”是什么只能靠损失下降来判断模型好不好但合成数据让你手握标准答案——训练结束后把学到的 \(w\) 拿出来对比 \([2, -3.4]\)差多少一目了然。这就是所谓的“上帝视角”。有个细节容易被忽略为什么 \(X\) 要服从均值为0、方差为1的标准正态分布两个原因。第一特征尺度统一梯度下降不会因为某个维度过大而失衡第二前面说的噪声项也是均值0的高斯噪声这正好让后面要讲的MSE在数学上站得住脚。李沐没有直接说“我们要做特征标准化”而是通过数据设计把这个雷提前拆掉了实际项目里你是躲不掉的后面我会专门讲这个问题。2.2 为什么损失函数偏偏选MSE最小训练闭环四个组件里损失函数是最容易被“想当然”对待的一个。很多人觉得MSE就是“误差的平方取平均”公式会用就行。但李沐在书里用了一个非常优雅的推导当我们假设噪声服从均值为0的高斯分布时从最大似然估计出发最大化对数似然函数化简到最后等价于最小化均方误差。换句话说选MSE不是拍脑袋的约定而是在“误差服从高斯分布”这个前提下的最优选择。这个推导第一次读可能觉得绕但它让你记住一件很重要的事损失函数的选择本质上是对数据生成方式的一种假设。以后你学图像分类模型输出变成概率你会发现那里用交叉熵而不是MSE因为你要匹配的离散分布做最大似然而不是连续的噪声模型。这一节我还想强调一个学习习惯遇到损失函数先问“它假设了什么”而不是“它怎么算”。带着这个思路去看后面Softmax回归、注意力机制里的各种损失你就不会觉得它们是零散的公式拼盘。2.3 优化算法为什么偏偏是小批量随机梯度下降理论上我们可以一次性把所有样本放进损失函数用整个训练集的平均梯度更新一次参数这叫批量梯度下降。但这有两个现实问题数据量大时每更新一步都要看完全部数据代价太高在复杂的非凸模型上它容易停在比较差的局部区域。另一种极端是纯随机梯度下降每个样本更新一次。优点是方向灵活缺点是梯度噪声极大训练曲线一路毛刺还完全浪费了现代GPU的批量并行算力。小批量随机梯度下降mini-batch SGD是综合方案一次取一小批数据常见32、64、128算这批数据的平均梯度更新一次参数。怎么直观理解批量大小就像你想知道一部电影的口碑只问一个人太片面问一千个人又太慢问二三十个人正好能得出个靠谱结论。机器学习的超参数十有八九都可以拿这种生活常识去类比比死记硬背有效得多。D2L在从零实现的一节里用的就是最朴素的小批量SGD。你会看到它手动实现了参数更新逻辑代码不过几行但这里面藏着整个深度学习训练过程最核心的动作利用梯度下降方向让参数往损失更低的地方挪一小步。3. 代码里的最小训练闭环照着写就能跑通3.1 环境准备一台没有独显的笔记本就够了如果你正在纠结“学深度学习要不要先买显卡”线性回归这一章给了你一个非常明确的答案不需要。D2L这本书的PyTorch版设计得很克制线性回归的数据量极小CPU跑完全程也就几十秒我就是在公司一台没有任何独显的办公笔记本上跑通的。环境方面用conda建一个干净的环境装好PyTorch和d2l配套包即可。不要在这个阶段浪费时间折腾GPU驱动等真正开始跑卷积网络再考虑也不迟。有一个很容易踩的坑直接用pip装最新版PyTorch时可能会自动装上错误版本的CUDA依赖反而把CPU环境搞糟。如果你暂时没有GPU直接选CPU版本的PyTorch安装即可干净省事。环境不会成为你学习这章的瓶颈不用焦虑。3.2 从合成数据到数据迭代器原书第一个可运行代码块是生成合成数据的函数。我把它精简成核心几行import torch from torch.utils import data def synthetic_data(w, b, num_examples): 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))然后通过torch.utils.data.TensorDataset把 \(X\) 和 \(y\) 绑成一个数据集再用DataLoader按设定好的batch_size逐批取数据。这里有两个细节值得专门说shuffleTrue一定要设置。否则每个epoch内取到的批次永远是固定顺序训练后期的梯度方向会很单调收敛得更慢。这个参数丢了你不会报错但训练效果会莫名其妙地变差。reshape((-1, 1))这一步不能省。PyTorch里做矩阵运算和损失计算时对张量形状非常敏感标签如果不整理成列向量后面广播机制可能把你的损失算出一个奇怪的形状来报错都报得让人摸不着头脑。3.3 手写模型和梯度下降的价值D2L这一章分两遍讲同一件事第一遍是完全手写实现第二遍用高层API几行搞定。我强烈建议你至少完整看一遍手写版不要直接跳到简洁实现。手写版强迫你把每个环节都摆到桌面上def linreg(X, w, b): return torch.matmul(X, w) b def squared_loss(y_hat, y): return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2 def sgd(params, lr, batch_size): with torch.no_grad(): for param in params: param - lr * param.grad / batch_size param.grad.zero_()这段代码里我最想拆开讲的是sgd函数里的lr * param.grad / batch_size。为什么除一个batch_size因为前面的squared_loss返回的是每个样本的损失累加后再除以样本数得到的才是平均梯度。如果你忘了这个除法学习率会被无形放大一个batch的倍数损失曲线就会在“好像能降”和“忽高忽低”之间反复横条。另一个容易看漏的点是param.grad.zero_()。默认情况下梯度是自动累积的如果不手动清零下一批数据算出来的梯度会加到旧梯度上参数更新方向越偏越离谱。这个坑在第一章就能遇到早点刻进肌肉记忆后面少掉一堆头发。3.4 训练循环三轮就够了吗D2L默认训练3个epoch。初看会觉得“3轮也太少了吧”但合成数据就是这么简单——三遍下来 \(w\) 已经非常接近真值损失从0.03量级降到0.0001量级。整个训练循环不用GPU不用调参几秒钟跑完。简洁实现里的训练循环长这样for epoch in range(num_epochs): for X, y in data_iter: l loss(net(X), y) trainer.zero_grad() l.mean().backward() trainer.step()注意这里的loss是nn.MSELoss()默认返回的是每个样本的损失向量所以backward()前要先.mean()转成标量否则PyTorch会直接报错。很多人第一次跑这节就是在.mean()附近卡住的。跑完打印训练损失看到它一路下降那一刻你就算正式走通了人生中第一个最小训练闭环。整个过程比你煮一碗泡面还快但意义完全不同——从这一刻起“训练一个模型”对你不应该再是黑箱而是一条看得见每一步的流水线。4. 把bug练出来第一次训练最常见的翻车现场4.1 学习率太大损失曲线原地起飞学习率 \(lr\) 是这章最容易踩的坑没有之一。D2L给的默认值0.03看起来温和但你只要手一抖调大一点比如设成0.5你会亲眼看到损失每轮都在变大最后直接变成nan。原因并不复杂学习率太大每一步参数更新都会跨过最优点跳到损失更高的位置下一轮继续大步跨越如此反复损失自然原地起飞。修复方法也很简单把学习率降到0.01甚至0.005重新跑一遍损失就会乖乖下降。我自己后来养成了一个习惯跑任何新模型第一次都先把学习率往小设置确认损失能稳定下降之后再逐步调大找到“训练速度和稳定性”之间的平衡点。千万别迷信“大学习率训练得快”对大多数第一次跑通的实验来说稳定收敛比速度重要得多。4.2 梯度叠加效应为什么明明写了反向传播还是越训越糟前面手写实现里专门出现了一行param.grad.zero_()注释讲的是“清除上一次梯度”。这个操作的危险性在你第一次忘记写的时候才会真正理解。PyTorch的设计里.grad在每次backward()之后是累加的而不是覆盖的。也就是说如果你忘了清零第一个batch的梯度还在第二个batch的梯度又叠上来第三批再叠……参数更新方向会被历史梯度越带越偏损失曲线表现为不降反升、忽上忽下极其有迷惑性——毕竟你的代码逻辑看起来每行都是对的。那为什么PyTorch要把梯度设计成累加因为有些高级场景比如梯度累积、RNN反向传播到更长的时间步确实需要这种机制。但对刚入门的人来说这只需要背一句口诀反向传播之前先清零梯度要么在优化器上调用zero_grad()要么在参数上手动.grad.zero_()。4.3 特征尺度不统一梯度下降教你做人合成数据里的 \(X\) 用的是标准正态分布所以这一节的训练过程顺风顺水。但现实里如果你有两个特征第一个在[0, 1]范围第二个在[0, 10000]范围梯度下降会非常痛苦。原因是损失对两个权重的梯度量级差了好几个数量级你调学习率只能照顾量级更大的那个维度小量级的那个维度几乎不动。打个比方就像一个人同时要爬缓坡和陡坡。步长太大时在陡坡上直接摔下去步长太小时缓坡上又几乎看不出在移动。唯一的办法是先做标准化让每个维度均值接近0、方差接近1。我在这章练完之后硬是给自己加了个作业把合成数据的 \(X\) 换成不均一的尺度重新跑一遍训练亲眼对比那种“想收敛又收不动”的折磨感。有了这个体验以后在任何模型里看到Normalize、StandardScaler你都不会觉得那是可有可无的预处理了。4.4 损失还不满意先怀疑数据处理而不是模型第一种翻车是损失从初期就不降第二种更隐蔽让人更头大损失降到某个水平之后怎么加epoch都不动了。遇到这种情况大多数人第一反应是“模型是不是不够强”但对线性回归这种容量很低的模型来说问题八成出在数据或者超参数而不是模型结构。我建议按照这个顺序排查第一看学习率是不是太小损失下降得比蜗牛还慢第二看特征尺度是否统一第三看DataLoader的shuffle是否被误关了第四看数据里面是不是混进了NaN或者极端值。顺序不能乱因为前三项都是写进代码就能立刻改的最后一项需要多花心思检查数据生成部分。真排查下来你会发现新手遇到的大部分“模型不收敛”背后站着的都是“数据处理不到位”而不是算法问题。5. 闭环验收三个问题确认你真的懂了5.1 为什么学到的w不等于真实w训练结束后你学到的 \(w\) 可能是[1.998, -3.397]而真实值是[2, -3.4]。误差很小但不会完全相等。原因就是数据里那个0.01标准差的高斯噪声——只要数据带噪声任何模型都不可能从有限样本里精确还原出真实参数。你能学到的只是“数据中的趋势”而不是“上帝手中的真理”。这个结论看似简单但它为后面所有章节铺垫了重要的直觉模型天然有不确定性参数估计与真实规律之间的距离取决于数据量、噪声大小和模型容量。数据越多噪声越小学到的参数越接近真值数据少、噪声大偏差就大。你以后学偏差-方差权衡、正则化、过拟合时会发现全都起于这个最朴素的观察。5.2 batch_size设成1会怎样把batch_size改成1训练曲线会变成非常剧烈的锯齿状。每一次更新都只看了单个样本方向噪声很大但有趣的是最终大概率还是能收敛到差不多的地方只是路径曲折得多。反过来把batch_size改成整个数据集的大小每个epoch只更新一次曲线非常平滑但参数走得很慢。这个小实验的价值在于你亲手验证了“批量大小是一个稳定性和速度之间的旋钮”。它太小训练太吵太大训练太稳但太慢。绝大多数框架默认取32到256就是这个折中的结果。等到以后模型变大、单样本显存占用激增时你会发现在你面前可妥协的超参数里batch_size总是第一个被拿来开刀的。5.3 把偏置b去掉会发生什么第三个建议亲手做的实验是把模型里的偏置 \(b\) 删掉重新训练。数据里明明带着4.2的偏置模型却不允许学一个偏置项。为了硬拟合这个关系模型只能把偏置的效果强行塞进 \(w\) 里而且得到的 \(w\) 不仅不再接近真实值还会依赖 \(X\) 的分布。这是个很直观的“模型结构与数据规律不匹配”的实例。你亲手试过这一把之后再去看书里讲“为什么要保留偏置项”、后面学“为什么要加归一化层”都会觉得顺理成章。深度学习里很多看似很玄的组件从来没有黑魔法都是从一个类似“少了偏置会怎样”的小问题生长出来的。6. 闭环扩大从线性回归到整个深度学习6.1 非线性模型的表达力为什么需要“拐弯”线性回归假设 \(y\) 与 \(x\) 之间是直线关系。可是现实世界的大多数关系不是直线——房价不完全等于面积乘单价图像分类更不可能用一条直线把猫和狗分开。深度学习能在过去十几年大放异彩核心突破之一就是在每层线性变换之后加一个非线性激活函数ReLU、sigmoid这类让多层叠加后的函数能模拟任意复杂的曲线。你可以把这个过程理解成“很多小段直线拼起来只要段数足够多任何曲线都能逼近”。所以学线性回归的正确姿势不是停留在“这是一个回归工具”而是把它想成“一个没接激活函数的单层神经网络”。当你在它后面加一层ReLU再叠几层你已经走在通往多层感知机的路上了。D2L在后面的章节做的正是这件事并不突兀。6.2 分类任务里损失函数为什么要换线性回归解决的是连续数值预测比如房价、气温。可一旦任务变成图像分类模型输出变成了“属于每个类别的得分”这时候MSE就不适用了。原因还是回到我们在第2章讲的“损失函数本质是对数据生成过程的假设”分类的真实标签是离散的类别而不是连续数值于是最优损失顺理成章地变成了交叉熵。李沐教材最让我佩服的地方就在这里它从头到尾坚持用“数据生成假设 → 最大似然估计 → 损失函数”这条逻辑线而不是直接塞给你“分类就是交叉熵”的结论。你在线性回归一章打下的这个思维模式后面几乎每一章都会被反复调用。不要嫌这一章简单简单之处藏着整个学科最深的方法论。6.3 学习节奏不要急着往前冲学完这一章我其实不建议你当天就冲去学卷积网络。先把最小训练闭环自己重装几遍改改batch_size、调调学习率、把噪声标准差从0.01改成0.1甚至把损失函数临时换成L1绝对值损失观察训练曲线和最终参数的变化。这些折腾不需要GPU一台普通笔记本就够了但效果比多刷一遍视频强十倍。我当时就是在反复对比中发现原来“数据加载器为什么要shuffle”、“梯度为什么要清零”、“学习率为什么不能太大”这些问题的答案全都在指尖的操作里而不是在文档里。把这些答案变成肌肉记忆之后再往下读多层感知机、卷积网络你会发现自己已经能回答“数据怎么流动”“参数怎么更新”这类通用问题剩下的只是理解新模型的结构本身速度自然就快了。我自己最深的体会还是那句话李牧在书里反复强调“不要只读要动手跑”。线性回归这一章如果你只是把代码复制粘贴跑一遍看损失从0.03降到0.0001那只能算是看了个结果。但如果你去把噪声改成0.1、把batch_size改成1、把学习率调成0.5亲手制造几个bug再亲手把它们修好这章才真正属于你了。所谓最小训练闭环不只是代码上的最短路径更是你在脑袋里建立起来的第一张深度学习地图。以后的每一次扩展都只是在这张地图上添加新的标注而已。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑