ARTICLE DETAIL

资讯详情

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

PyTorch线性模型实践:从计算图到反向传播的深度学习核心闭环

PyTorch线性模型实践:从计算图到反向传播的深度学习核心闭环 先说结论这是我在“PyTorch 深度学习实践”系列里最推荐新手反复看的一讲甚至可以说把这一讲彻底吃透后面很多内容都会顺理成章。第 3 讲表面上只讲了一个最简单的线性模型y x * w但它是整套课程第一次真正把“数据 → 前向传播 → 损失函数 → 反向传播 → 参数更新 → 迭代训练”这条主线完整串起来。无论你以后做 CNN、RNN 还是 Transformer底层跑的都是这套流程。我见过不少朋友一上来就啃复杂的神经网络结构结果遇到 loss 不降、梯度爆炸、参数不更新这类问题完全不知道从哪里排查。根本原因就是没理解最基础的训练闭环是怎么运作的。这一讲恰好就是把整个闭环用最少的代码、最直白的例子给你拆开讲清楚。如果你属于以下情况这一讲会是你的最佳起点刚装好 PyTorch想在动手写模型前把核心机制搞清楚或者已经跑通了一些现成代码但对backward()、w.grad、zero_grad()这些操作还是一知半解又或者你想自己实现一个最简单的训练脚本用它作为以后调试复杂模型的“最小可复现模板”。下面我会按课程主线结合自己的复现经验和踩坑记录把这一讲里的核心知识、完整代码、关键细节以及常见问题都过一遍保证你不仅能看懂还能自己跑起来、改起来。1. 核心思路拆解为什么从线性模型开始1.1 “学习”的本质是什么在很多人的印象里深度学习是特别高深的东西好像一上来就要面对几百万个参数、几十层网络。但这一讲就一个目的戳破这层滤镜。深度学习里的“学习”本质上就是不断调整参数让模型的预测结果逼近真实结果。第 3 讲选择了最简单的一种映射关系y x * w只有一个特征x、一个参数w没有偏置b没有激活函数没有任何网络结构可言。但正是这种“极简”反而最适合看清学习的本质。想象一下你手里有一组已知答案的数据x1时y2x2时y4x3时y6。显然规律是y2x但如果机器不知道这个规律它只能先瞎猜一个w比如猜w1然后根据猜的结果和真实答案之间的差距不断微调w直到预测结果足够接近真实答案。这就是“学习”。这个看似简单的过程拆开后有四个关键部分模型、损失函数、优化方法、训练循环。第 3 讲里每一行代码都对应这些部分中的一个理解了这个对应关系你就不会觉得代码是一堆离散的魔法了。1.2 为什么用均值平方误差做损失函数课程里损失函数用的是loss (y_pred - y) ** 2这其实就是单个样本的均方误差。为什么要取平方两个原因第一预测值和真实值之间的误差有正有负直接相加会互相抵消平方后可以统一衡量偏离程度第二平方函数是凸函数对于线性模型来说损失曲面只有唯一的全局最低点这让我们可以非常直观地观察训练过程。你在后面会接触到torch.nn.MSELoss它才是工程中更常用的封装。但这一讲选择手写是为了让你看清损失的“原材料”是什么就是预测值和真实值之间的差距。如果这一步没想明白后面引入交叉熵、自定义损失函数时就会更吃力。1.3 训练集为什么要这么小看到x_data [1.0, 2.0, 3.0]只有三个样本时可能有朋友会觉得“这也太儿戏了”。但这一讲的核心目的不是训练出一个好模型而是演示训练流程。小数据集能让每一步计算都肉眼可见甚至可以手动笔算验证结果。等你用同样的逻辑去跑几千几万条数据时就能确信流程本身没有错。我自己的体会是学习一个新框架或新机制时千万不要一上来就上大数据集。先用几个能心算验证的样本把逻辑跑通再放大规模。这个习惯帮我排掉了无数低级 bug。2. 核心机制图解张量、计算图与自动求导2.1 张量带“记忆”的数字第 3 讲里最重要的概念升级就是从普通 Python 数字变成了 PyTorch 张量。普通数字只能参与运算但张量能帮你记住“这个数是怎么算出来的”它保存了运算历史也就是计算图。代码里的w torch.tensor([1.0], requires_gradTrue)这个操作等于告诉 PyTorch“这个参数非常重要以后所有经过它的运算我都要知道它对最终损失的贡献有多大。”这种贡献就是梯度。如果你把requires_grad漏了后面调用backward()时会直接报错或者梯度为None这是新手最容易踩的坑之一。张量和 Python 浮点数的另一个区别是张量在运算时会自动搭建一幅计算图。比如y_pred x * w这个乘法操作会被记录在图上loss (y_pred - y) ** 2也会被记录。当你调用loss.backward()时PyTorch 就沿着这幅图向前回溯用链式法则算出每个叶子节点也就是w的梯度。2.2 计算图与反向传播的关系计算图这个概念听着抽象但其实可以类比成一条流水线。原材料是x和w经过“乘法机器”变成y_pred再经过“平方差机器”变成loss。正向传播就是让原材料从前往后流产出最终结果。反向传播则是从最终结果往回走计算每一台机器对最终结果的“责任占比”。对于只有一个参数w的情况loss.backward()算出的w.grad实际上就是dloss/dw。如果你把loss展开这个导数是2 * (x * w - y) * x。用手算一遍再对比代码输出的grad值你会发现完全一致。这种“代码输出”和“手算结果”互相印证的感觉特别重要它能帮你建立对自动求导工具的信任。2.3 关键 API 的注意事项PyTorch 里跟梯度有关的 API 就那几个但坑不少。我直接把踩过的坑和正确用法列出来。requires_gradTrue创建张量时告诉 PyTorch这个变量参与运算时要把操作记录到计算图里用于反向传播。backward()从损失开始反向传播计算所有requires_gradTrue的张量的梯度。grad保存梯度值是一个张量。zero_grad()把梯度清零。注意 PyTorch 的梯度是累积的不清零的话下一轮梯度会叠加到本轮上训练会直接崩。注意PyTorch 里默认是动态计算图每次backward()之后计算图会被释放。这也是它和静态图框架最大的区别之一对调试自定义模型特别友好。3. 线性模型的完整实现与每行代码解析3.1 环境准备这一讲对 PyTorch 版本要求很低早期版本和最新的 2.x 都能跑通。如果你还没装环境建议直接用 Anaconda 创建一个新环境Python 版本选 3.9 以上然后用pip install torch安装 CPU 版即可。GPU 版本需要根据自己的 CUDA 版本去 PyTorch 官网选对应命令CPU 版做课程练习完全够用。3.2 完整代码与逐行解读下面这份代码就是第 3 讲的完整版本我加了一些注释。强烈建议自己动手敲一遍不要直接复制粘贴因为敲代码的过程本身就是理解的过程。import torch # 1. 准备数据 # x_data 和 y_data 是 Python 列表 x_data [1.0, 2.0, 3.0] y_data [2.0, 4.0, 6.0] # 2. 定义模型参数 # 将 w 定义成张量requires_gradTrue 表示需要计算梯度 w torch.tensor([1.0], requires_gradTrue) # 3. 定义前向传播函数 def forward(x): return x * w # 这里 x 是列表但列表乘以张量会自动转换 # 4. 定义损失函数 def loss(x, y): y_pred forward(x) return (y_pred - y) ** 2 # 5. 训练过程 print(predict (before training):, 4, forward(4).item()) for epoch in range(100): # 每个 epoch 取一个样本计算损失 l loss(x_data[0], y_data[0]) l.backward() # 反向传播计算 w 的梯度 print(grad:, w.grad.item()) # 6. 参数更新 w.data w.data - 0.01 * w.grad.data w.grad.data.zero_() # 梯度清零重要 print(predict (after training):, 4, forward(4).item()) print(loss:, l.item())这里有个细节forward(4)的参数是 Python 整数但在函数内部会与张量w相乘自动被转换为张量所以可以直接得到结果。如果你习惯更严谨的写法可以自己改成torch.tensor(4.)。3.3 为什么w.grad.data.zero_()这么重要这个点值得单独拎出来说。如果不调用zero_grad()第二次backward()时梯度会累加。比如第一次梯度是-2第二次梯度是-1那第二次w.grad会变成-3而不是-1。PyTorch 之所以设计成这种模式是为了支持某些特殊场景比如大 batch 拆成小 batch 累加梯度。但在普通训练里不清零梯度会直接导致参数更新方向和步长乱掉loss 不降甚至飙升。3.4 训练输出解读与收敛表现训练过程中至少要看三个指标loss、grad、w。理想情况下loss不断下降grad绝对值不断变小w不断逼近真实值2。我本地跑出来的输出大概长这样epoch 1, grad: -2.0, loss: 1.0 epoch 2, grad: -1.96, loss: 0.9604 ... epoch 99, grad: -0.0000, loss: 0.0000loss 降得非常快前几个 epoch 就已经接近 0 了。这说明线性模型 小数据集的收敛速度远超你想象也让新手更容易看到“正向反馈”。如果你的输出不是这个趋势优先检查梯度清零和数据列是否对应。4. 实验扩展画损失曲线与理解学习率4.1 用 Matplotlib 画出收敛过程光看打印的数字还是不够直观。我强烈建议你加上可视化把每个 epoch 的 loss 画出来。代码很简单import matplotlib.pyplot as plt epoch_list [] loss_list [] for epoch in range(100): l loss(x_data[0], y_data[0]) l.backward() w.data w.data - 0.01 * w.grad.data w.grad.data.zero_() epoch_list.append(epoch) loss_list.append(l.item()) plt.plot(epoch_list, loss_list) plt.xlabel(epoch) plt.ylabel(loss) plt.show()你会看到一条快速下降后趋平的曲线。这张图就是“模型在学习”的最直观证据。后面课程里所有模型训练都离不开这个损失曲线越早习惯看它越好。4.2 学习率调大调小会发生什么这一讲里学习率固定为0.01。如果你手动把它改成0.1会发现 loss 快速下降但可能在最低点附近震荡改成1.0甚至更大loss 会瞬间变成nan因为参数更新步长过大直接冲出了损失函数的有效区域。这就是“梯度爆炸”的最简模型。反过来如果学习率设成0.0001loss 会下降得非常缓慢100 个 epoch 可能还没收敛到理想值。通过这个实验你会对“学习率”这个超参数产生非常直观的感知太大震荡或发散太小收敛太慢。后面你用torch.optim.SGD时会用到lr参数那时你会感谢现在亲手调过的这一课。4.3 换成y 2x 1的小练习课程只做了无偏置的y x * w但现实中的线性模型一般都有偏置b。你可以自己动手把代码改成y x * w b定义两个张量w和b都要requires_gradTrue然后在forward里返回x * w b。注意更新参数时要同时更新w和b并且它们的梯度都要清零。这个小练习会让你对“多个参数”的训练有初步感觉也为后面实现多输入特征做铺垫。5. 常见问题与排查技巧实录5.1 问题速查表我把新手在复现第 3 讲时的常见现象、原因和解决办法整理成一张表建议直接收藏。现象可能原因解决办法loss 不降反升学习率太大调小学习率比如从 0.01 改为 0.001loss 变成 nan梯度爆炸或除零检查数据减小学习率grad 一直为 0忘记requires_gradTrue检查张量定义每次 loss 都一样参数没更新检查参数更新语句是否在循环内grad 数值巨大学习率太大或数据量纲差异大减小学习率或归一化数据反向传播报错计算图中存在不被支持的运算检查 forward 函数简化表达式5.2 几个踩坑心得我补充几个课程里没细讲但实际一定会遇到的细节。第一使用w.data而不是w参与更新是为了不破坏计算图。如果直接操作w - 0.01 * w.grad可能会在叶节点上引发原地修改报错。第二item()方法可以把只含一个元素的张量转成 Python 浮点数方便打印和保存。如果不转打印出来是tensor(2.)不美观也不方便做后续数值判断。第三如果你的 PyTorch 版本比较新更推荐用with torch.no_grad():包裹参数更新代码块明确告诉 PyTorch“这个操作不需要计算梯度”这是工程上的标准写法。课程里用w.data是为了教学直观两种方式都能跑通但理解它们的区别能让你少踩很多环境的坑。5.3 从线性模型到神经网络的思维过渡第 3 讲虽然只做了线性模型但它的思想已经完整覆盖了深度学习的核心流程数据准备、前向传播、损失计算、反向传播、参数更新、迭代循环。后面的课程里网络结构会越来越复杂但训练闭环基本不变。你只是把“手写forward乘法”换成了“调用torch.nn.Linear等模块”把“手写参数更新”换成了“调用torch.optim.SGD”把“手写损失”换成了“调用torch.nn.MSELoss”。所以学完这一讲后我特别建议你做一个总结把课程里的手写部分逐个替换成 PyTorch 官方封装看看每个封装对应的是哪个手写步骤。一旦完成了这种“手写 ←→ 封装”的对应你会发现阅读大部分开源代码都不再发怵。6. 资源与后续学习建议6.1 配套资源推荐我按个人经验推荐几个和这一讲互补的资源。第一课程配套的 PPT 和代码可以在 GitHub 上找到搜索“PyTorch 深度学习实践”即可。第二PyTorch 官方文档里的AUTOGRAD教程很短但写得很明白适合在学完第 3 讲后立刻阅读。第三如果你喜欢边看边敲搜索“动手学深度学习”在线版它里面的“线性回归从零开始实现”章节和第 3 讲内容高度互补能帮你把视野拓宽到真实数据集上。6.2 下一步学什么按课程大纲第 3 讲之后通常就是反向传播的完整推导以及用torch.nn模块重写线性回归。我建议你带着以下问题去学如果不用手写梯度PyTorch 的autograd到底自动做了什么为什么说反向传播是链式法则的高效实现从手写梯度到torch.optim.SGD中间省了哪些事想清楚这些问题后你会发现自己对深度学习训练流程的理解已经迈过了一个大台阶。我到现在还记得第一次看到那个简单的抛物线损失曲线老老实实下降时的感觉那种“原来机器学习就是这样一点点学出来”的踏实感是刷多少视频都换不来的。这套课程能火这么多年很大程度上就是因为它是真的把每一个概念都拆到你能亲手验证的程度。如果你也是刚迈入深度学习大门的人第 3 讲值得你放慢速度一行一行敲、一遍一遍跑把每一步都搞通透。
返回列表