尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PyTorch深度学习入门索引:从张量到CNN的训练循环实践地图

PyTorch深度学习入门索引:从张量到CNN的训练循环实践地图 刚开始啃深度学习框架那阵子我最大的困惑不是数学而是东西太多不知道从哪下手。张量、自动求导、梯度下降、反向传播、卷积、循环网络每个词单拎出来都能搜到一堆解释可它们之间怎么串起来、先学哪个后学哪个、哪些是必须动手敲一遍才能记住的全靠自己摸索。后来我把这套 PyTorch 深度学习实践课程的笔记从头到尾整理成一份带索引的学习地图按难度和依赖关系重新编排配合关键词检索才终于把看视频都能懂、一动手就废这个毛病给治住。这篇东西就是那份索引的完整整理版我会把 PyTorch 入门阶段的核心节点、环境搭建的取舍、张量与自动求导的关键理解、从线性模型一路走到卷积网络的实操路径以及我自己踩过的坑全部摊开讲清楚。不管你是完全没碰过 PyTorch 的小白还是已经能跑通几段 demo 但基础不牢的同学都能按图索骥找到自己该补的那一块。1. 这份索引真正解决的问题把看懂了和写得出之间的鸿沟填上我最初学 PyTorch 的方式特别典型打开一套入门课程从头看到尾每个视频都听懂了合上电脑却发现自己连一个完整的训练循环都写不出来。问题不在课程本身而在于知识点是线性的、被视频节奏牵着走的可真正写代码时你需要的是随时跳到某个具体环节去查的能力。一节讲张量创建的课你可能在三周后调 bug 时才突然需要回看一节讲反向传播的课你要等到亲手写loss.backward()报错时才真正理解它的约束。这种用的时候才回头找的需求天然就需要一份索引而不是一条线性播放列表。1.1 为什么零散的听课笔记必须重新组织成索引听课笔记天生是碎的。你可能在视频里记下卷积核尺寸用 3x3 比较常见又在另一处记下CrossEntropyLoss 自带 Softmax这两条笔记分散在不同页真正用的时候根本对不上。把它们重新按数据准备—模型定义—损失与优化—训练循环—评估这条主线归档好处是你需要的某一类信息会聚在一起查一次就能拿到相关的全部细节。我整理索引时有个习惯每一条知识点后面都标一个最小可运行示例哪怕只有三行代码。因为深度学习里光看公式很容易产生我懂了的错觉只有写出能跑的东西知识才真正落地。1.2 不同基础的人应该怎么用这份索引零基础的同学建议老老实实按顺序走先过环境搭建再打张量基础然后线性模型、梯度下降、反向传播一路推过去中间任何一个环节觉得虚就停下来手写一遍。已经会 NumPy、只是想转 PyTorch 的人可以跳过大部分数学直觉部分重点看张量的自动求导、nn.Module的组装方式、Dataset/DataLoader的用法这三块是 PyTorch 区别于纯 NumPy 的核心。至于已经能跑通项目、只是基础不牢的人我建议直接奔着训练循环里的工程细节和常见报错这两块去把梯度清零、item()取标量、CrossEntropyLoss不用手动 Softmax 这些细节逐一核对往往能发现几个一直没注意的隐性问题。提示索引的价值在于检索不在于通读。你完全可以从目录跳到任意一节把那一节当作独立文档来用这比从头再刷一遍视频效率高得多。另外我想强调一点深度学习入门阶段最容易被忽视的其实是依赖关系。比如你不理解计算图和链式法则看loss.backward()就只是记住一行咒语你不理解Softmax和交叉熵的数学关系就永远搞不清为什么 PyTorch 的CrossEntropyLoss里没有手动 softmax。索引的意义之一就是把这些隐藏的依赖显式地标出来让你知道要看懂 A得先掌握 B。2. 环境搭建这道坎Anaconda、conda 与 PyTorch 安装的现实取舍我见过太多人在环境这一步就劝退了而且劝退的方式往往特别憋屈——不是代码逻辑难是装完 PyTorch 之后import torch报错、或者装成了 CPU 版却以为自己有 GPU 加速。环境问题的恶心之处在于它的报错信息通常跟真正的原因隔了十万八千里。所以这一节我打算把为什么这么做讲透而不是甩几条命令让你照抄。2.1 为什么优先用 conda 虚拟环境而不是全局 pip直接pip install torch装到系统 Python 里短期看最省事长期看是个灾难。原因很现实不同的项目对 PyTorch 版本、Python 版本、CUDA 版本的组合要求可能互相冲突一旦全局只有一个环境你为了跑 A 项目升级了 PyTorchB 项目就可能直接崩掉。conda 虚拟环境的价值就在于隔离——每个项目一套依赖互不污染删掉重建成本极低。创建环境这件事本身很简单conda create -n pytorch python3.10 conda activate pytorch选 Python 3.10 是个折中太新的版本某些科学计算库的 wheel 还没跟上太老的又可能错过一些新特性。装好之后所有pip和conda操作都记得先activate否则你会非常困惑我明明装了为什么找不到。2.2 CPU 版还是 GPU 版先问自己三个问题很多人一上来就想装 GPU 版觉得快但没想清楚自己的机器到底支不支持。我会先问三个问题第一有没有独立显卡型号是什么第二显卡的驱动和对应的运行库版本是否满足框架要求第三当前阶段的学习内容是否真的用得上。学线性回归、逻辑回归这种小模型CPU 版完全够用跑 MNIST 手写数字识别也就慢一点真正需要 GPU 的一般是图像、大语言模型这类计算密集任务。装错版本的代价是代码里写了device cuda运行时报没有可用设备然后你花半天排查最后发现装的是 CPU 版。区分方法很直接装完之后跑一句验证import torch print(torch.__version__) print(torch.cuda.is_available()) # GPU 版且配置正确才会是 True如果第二行是False要么你装的是 CPU 版要么驱动/版本没对上。这里有个经验不要迷信最新版最好PyTorch 和 CUDA 版本之间存在明确的对应关系去官网用它的选择器勾一下比你自己猜要靠谱得多。2.3 安装完必做的验证与三个高频报错环境装好之后别急着写模型先做三步验证能import torch、torch.cuda.is_available()返回符合预期的结果、随便建一个张量做一次简单运算。这三步能过的后续基本不会出环境问题。下面这张表是我自己整理的高频报错对照遇到时可以直接查报错现象大概率原因处理方向No module named torch没激活对应环境或装到了别的 Python 里conda activate后确认which python指向环境内cuda is not available装的 CPU 版或驱动与版本不匹配卸载重装对应版本的 GPU 包核对驱动导入卡顿或 DLL 加载失败混用 conda 与 pip 安装同一批包保持同一环境内安装方式一致重建环境注意同一个环境里 conda 和 pip 混着装同一个包很容易造成动态库冲突。装 PyTorch 全家桶时要么全程 conda要么全程 pip别来回横跳。还有个小技巧如果你在编辑器里开发记得把编辑器解释器手动指向你创建的 conda 环境否则会出现终端里能跑、编辑器里报找不到模块的经典错位。这个问题乍看玄学本质就是编辑器和终端用了两个不同的 Python。3. 张量基础把 Tensor 理解成会记笔记的 NumPy 数组如果让我用一句话概括 PyTorch 张量我会说它就是一个会自动记录自己来历的 NumPy 数组。普通数组做运算算完就完了张量做运算如果参与运算的某一方要求记录梯度它就会顺手在背后搭一张谁从谁算出来的关系网这张网就是后面自动求导的基础。理解这一点很多看着奇怪的 API 行为就顺了。3.1 创建、形状与 dtype新手最常翻车的三个点创建张量的方式很多torch.tensor()从列表建、torch.zeros()/torch.ones()建全零全一、torch.arange()建序列、torch.randn()建随机数。真正容易翻车的是下面三个点。第一是dtype默认整数张量和浮点张量不能随便混算模型参数几乎都要求是浮点如果你用torch.tensor([1, 2, 3])建的默认是整型喂进网络就会报类型错误得显式写成浮点或加.float()。第二是形状(N,)和(N, 1)在线性层眼里是两个完全不同的东西前者常被当成一批样本后者才是 N 个单特征样本维度对不上时报错往往发生在很难定位的一行。第三是.view()和.reshape()的区别前者要求内存连续遇到转置过的张量会报错后者会自动处理生产代码里我更倾向.reshape()图省心。import torch a torch.tensor([1.0, 2.0, 3.0]) b torch.zeros(2, 3) c torch.arange(0, 10, 2) print(a.dtype, b.shape, c) x torch.randn(4, 1) # 4 个样本每个 1 个特征 print(x.shape)3.2 requires_grad 与计算图到底记了什么requires_gradTrue是张量从普通数组变成可训练参数的开关。一旦打开之后对它的每一次运算都会被记录成计算图上的一个节点。你可能好奇它到底记了什么其实记的就是这个结果是由哪些张量、经过什么运算得来的。反向传播时PyTorch 从最终的标量损失出发沿着这张图往回走用链式法则逐层求导把梯度存进每个叶子张量的.grad里。理解这一点有个直接好处你知道为什么每次迭代前必须把梯度清零。因为.grad是累加的如果不清零上一轮的梯度会叠加到这一轮上模型越训越乱而且这种错误不会报错只会让你困惑为什么 loss 不降。所以optimizer.zero_grad()这一步不是可选的仪式它是有明确数学原因的。3.3 广播机制方便背后的隐形陷阱广播机制让不同形状的张量能自动对齐做运算比如一个(4, 3)的张量加一个(3,)的张量后者会自动扩展成(4, 3)。方便是真方便坑也是真坑因为它会悄无声息地改变结果形状。我最常遇到的场景是本想算一个样本内积结果因为维度没对齐广播成了一个大矩阵代码照样跑结果全错还不报错。排查这类问题的办法是养成打印形状的习惯尤其是模型里每一层的输入输出。如果某一步你觉得输出形状应该是某个样子就立刻print(x.shape)核对别等 loss 异常了再回头查。广播是双刃剑用对了省事用错了就是最难查的那类 bug。4. 从线性模型到梯度下降手推一遍比直接调包值钱在哪这一块是很多人想跳过的部分。既然nn.Linear一行就能建好线性层optim.SGD一行就能做优化为什么还要手推线性模型和梯度下降我给出的理由很朴素因为后面 90% 的报错和调参困惑根子都在这里。你不理解损失函数在衡量什么就不知道 loss 为什么不降你不理解学习率的作用就不知道模型为什么不收敛或者震荡发散。4.1 线性模型与损失函数的几何直觉线性模型y w*x b你中学就会难的是把它放进学习这个语境里理解。这里的 w 和 b 不是给定的是要通过数据自动找出来的。损失函数就是用来打分预测值和真实值差得越远损失越大。最常用的均方误差MSE就是把每个样本的预测误差平方后取平均。为什么平方而不是取绝对值一是平方可导、处处光滑方便求梯度二是它对大误差更敏感模型会更努力去修正离谱的预测。你可以把训练过程想象成在一张参数—损失的山谷地图上找最低点。w 和 b 是坐标损失是海拔训练就是不断往海拔更低的方向走。这个比喻后面理解梯度下降和优化器会一直用得上。4.2 梯度下降更新公式的逐项拆解梯度下降的更新公式看着吓人拆开其实就三样东西w w - learning_rate * gradientgradient损失对当前参数的导数告诉你往哪个方向走损失会增大。负号我们要减小损失所以往相反方向走。learning_rate步长。步子太小收敛慢得像蜗牛步子太大一步跨过谷底损失来回震荡甚至越来越大。理解了这三样你就明白为什么学习率是最需要调的超参数。我自己的经验是入门练手时学习率从0.01或0.1开始试太大就往下调看 loss 曲线是不是在稳定下降。如果 loss 一路涨或者疯狂抖动八成是学习率太大。4.3 为什么我坚持先手写再调包手写一遍的好处是把隐藏的步骤变成显式的代码。调包时optimizer.step()一句话就更新了参数你看不见里面发生了什么手写时你得自己算梯度、自己乘学习率、自己减每一步都在你眼皮底下。等你再回头看optim.SGD就会秒懂它帮你做了哪几件事。我通常的做法是先用最笨的循环把线性回归跑通确认自己理解了前向、损失、反向、更新这四个动作然后再用nn.Module和optim重写一遍对比两版代码找出被封装掉的部分。这个先拆后装的过程比直接抄一段训练循环有价值太多。5. 反向传播与自动求导PyTorch 最核心的那根神经如果 PyTorch 只能保留一个特性我会选自动求导。它是整个框架的立身之本也是把手写梯度这件苦差事彻底外包出去的关键。很多人对它的理解停留在调.backward()就有梯度了但只要遇到稍微复杂一点的场景比如梯度不更新、梯度爆炸、或者需要冻结某些层这种表面理解就会立刻崩溃。5.1 计算图的前向与反向传播前向传播就是你正常做运算从输入一路算到输出PyTorch 在旁边默默把这张运算关系图搭好。反向传播则从最终的标量损失出发沿着图反向走用链式法则一层层求导把每个参数对应的梯度算出来。这里有个硬性约束.backward()只能对标量调用或者要传入一个和输出同形状的梯度参数。新手最常见的报错就是loss 不是标量原因往往是没对 batch 的损失取平均或求和。链式法则听着抽象其实就一句话复合函数的导数等于各层导数的乘积。y (w*x b)再套一个平方损失损失对 w 的导数就是损失对外层求导 × 外层对 w 求导一层层乘下来。PyTorch 帮你把这些乘法都做完了但你知道它在乘什么才能在出错时定位问题。5.2 backward 的调用时机与梯度累加的坑backward()的调用时机有几个讲究。第一它必须在计算完标量损失之后调且损失必须来自带有梯度的计算图如果你用了torch.no_grad()或把张量.detach()了反向就断了。第二每次迭代要先把上一轮的梯度清零否则梯度会累加前面已经说过。第三推理或评估阶段记得用with torch.no_grad():包起来一方面省显存和计算另一方面避免误建计算图。我踩过的一个坑是想在循环里多次反向却忘了清零结果梯度越来越离谱采。还有一种情况是同一个网络被调用多次、又共享了参数梯度累加反而成了特性这时你就要主动控制清零时机。总之梯度是累加的这件事你必须刻在脑子里。5.3 用链式法则手算一个小例子做验证想确认自己真的懂了最有效的方式是手算一个极小的例子再和 PyTorch 的结果对一遍。比如设x2, w3前向y w*x 6损失取y^2 36那么损失对 w 的导数就是2*y*x 2*6*2 24。然后在 PyTorch 里建对应的张量跑一次.backward()打印w.grad看是不是 24。对上了说明你对链式法则和反向传播的理解是扎实的对不上正好借这个例子把哪一步错了找出来。这种手算对拍的习惯我从学这门课一直保留到现在。它比看十遍推导都管用因为你是用代码验证了自己的理解而不是被动接受。6. 逻辑回归、多维输入与多分类从玩具数据走向真实问题线性回归解决的是预测一个连续值但真实任务里更多的是判断属于哪一类。这就引出了逻辑回归和多分类。这一块的难点不在模型结构而在损失函数和输出层的搭配尤其是Softmax和交叉熵之间的关系几乎每个人第一次学都会绕进去。6.1 逻辑斯蒂回归与交叉熵的搭配逻辑回归名字里有回归干的却是二分类的活。做法是在线性输出后面套一个 Sigmoid 函数把结果压到 0 到 1 之间当作属于正类的概率。输出是概率了损失就不能再用 MSE得用二元交叉熵BCE。为什么因为 MSE 配 Sigmoid 时误差大梯度反而可能很小学不动交叉熵在预测错得离谱时梯度很大能快速纠正。这里有个实操经验Sigmoid 加 BCE 的组合里PyTorch 提供了数值更稳定的版本BCEWithLogitsLoss它把 Sigmoid 和交叉熵合在一起算避免了先算 Sigmoid 可能出现的数值溢出。能用它就尽量用稳。6.2 多维特征输入与 nn.Module 的组装套路真实数据几乎不可能只有一个特征。当输入从标量变成向量线性层就写成nn.Linear(in_features, out_features)权重从标量变成矩阵。nn.Module的价值这时候就体现出来了你可以把网络定义成一个类在__init__里声明有哪些层在forward里规定数据怎么流过这些层。这个声明层 定义前向的两段式结构是整个 PyTorch 模型代码的标准骨架。写nn.Module时我常提醒自己两件事第一层要在__init__里定义别放到forward里临时建否则每次前向都重建一遍参数根本存不住第二forward里尽量用 PyTorch 的张量运算别掺 NumPy否则计算图会断。6.3 多分类任务与 CrossEntropyLoss 的隐藏细节多分类把二分类的 Sigmoid 换成 Softmax输出每个类别的概率取最大的那个作为预测。这里有个必须记住的点PyTorch 的CrossEntropyLoss内部已经包含了 Softmax所以你的网络最后一层只需要输出原始 logits千万不要再手动加一层 Softmax否则相当于做了两次训练会异常。另一个细节是标签的格式。CrossEntropyLoss期望的标签是类别索引比如 0 到 9而不是 one-hot 向量。如果你用 one-hot 喂进去要么报错要么结果完全不对。我第一次做多分类时就栽在这上面调了半天才发现标签格式不对。7. 数据加载、卷积网络与训练循环让模型真正跑起来前面都是在单步验证概念这一节把它们组装成能真正跑起来的完整项目。真实训练循环里有大量工程细节处理不好就会出现代码能跑但模型不学的尴尬局面而且这类问题往往没有任何报错全靠你的经验去嗅。7.1 Dataset 与 DataLoader 的分工与常见误用PyTorch 把数据加载拆成两层Dataset负责第 i 个样本长什么样DataLoader负责一次给我多少个、要不要打乱、用几个进程加载。这个分工的好处是你可以自由定制数据读取逻辑而不用改训练代码。用DataLoader时有几个高频误用训练集开shuffleTrue但验证集和测试集一定别开否则评估结果不稳定batch_size太大显存炸、太小训练不稳通常从 32 或 64 试起num_workers在 Windows 上有时反而更慢得实测。7.2 卷积层的参数、输出尺寸公式与感受野卷积神经网络CNN处理图像的核心是卷积层。你最需要记住的是输出尺寸公式输出尺寸 (输入尺寸 - 卷积核 2 * padding) / 步长 1这个公式能帮你快速判断一层卷积之后特征图变成多大避免维度对不上。卷积核负责提取局部特征感受野指一个输出像素能看到多大范围的输入——层数越深感受野越大能捕捉的模式越全局。我调试 CNN 时最常犯的错就是算错维度在建完Conv2d之后先手动喂一个假数据跑一遍前向打印每层输出的形状确认无误再正式训练。7.3 epoch、batch 与训练循环里的工程细节epoch是把整个训练集过一遍batch是一次喂给模型的样本数。一个 epoch 包含若干个 batch每个 batch 走一次前向、损失、反向、更新。完整的训练循环骨架大致是这样for epoch in range(num_epochs): model.train() for inputs, labels in train_loader: optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() model.eval() with torch.no_grad(): # 在验证集上评估 ...几个容易被忽略的细节model.train()和model.eval()要在正确的位置切换它们会影响 Dropout 和 BatchNorm 的行为损失累加时用loss.item()取标量别直接把张量加进列表否则计算图会一直挂着、显存暴涨每个 epoch 结束后打印平均损失和验证精度才能判断模型是欠拟合还是过拟合。8. 复现这套课程时我踩过并总结的几条经验整理完这份索引之后我自己又从头复现了一遍把每一个环节的坑都重新趟了一次下面几条是印象最深的。第一条别在环境上省时间。我见过太多人卡在安装阶段靠零散搜索东拼西凑最后环境一团糟。老老实实建虚拟环境、按官网选择器装、装完立刻验证三件事这一步扎实了后面能省下你十倍的时间。第二条手算对拍是最快的理解方式。反向传播、卷积输出尺寸、梯度累加这些概念光看是学不会的你必须在纸上或者代码里算一遍和框架给出的结果对上号理解才会真正长在身上。第三条培养打印形状的肌肉记忆。深度学习里绝大多数报错和静默错误根子都是形状不对。每写一段涉及维度变换的代码就顺手打一次形状比事后 debug 划算得多。第四条让 loss 曲线说话。模型不训练时先看 loss 是震荡、不降还是爆炸分别对应学习率过大、梯度消失或结构问题。把 loss 曲线当作诊断工具比盲目调参高效太多。最后说个后续可以延伸的方向。这份索引覆盖的是入门阶段的核心骨架等你把线性模型、自动求导、逻辑回归、多分类、CNN 都跑通之后就可以往循环神经网络、注意力机制这些方向扩展了。到那时你会发现前面打下的张量和自动求导的地基会在理解复杂结构时反复派上用场——这也是我一直建议新手不要在基础阶段偷懒的原因。真正把地基夯实了后面每上一层楼都会轻松很多。
返回列表