☰
PyTorch官方指南第二部分深度拆解:自动求导与神经网络训练
2026/10/13 8:13:37 网站建设 项目流程

1. 为什么我会去翻译这份官方指南,以及它到底讲什么

先把话说在前面:PyTorch这本官方指南,网上流传的中文版本不少,但大多数是机翻、跳段、甚至把老版本API留在里面的。我花了两周时间,把官方指南第二部分完整翻译了一遍,并且每个示例代码都在本地跑通验证过。这篇博文就是这次翻译工作的记录,包括我踩过的坑、术语处理原则,以及第二部分核心内容的深度拆解。

如果你是刚学PyTorch没几个月的新手,或者已经会用但一直对自动求导、nn.Module的内部机制模模糊糊的朋友,这份内容对你会有实际帮助。第二部分的官方定位是“从零构建、训练一个神经网络”,但它用的讲解顺序和很多中文教程不一样,它先讲张量和自动求导层面的底层运行原理,再往上堆神经网络模块。这个顺序本身就是刻意的,理解了这个顺序,你后面看任何PyTorch代码都会轻松很多。

我翻译的是哪个“第二部分”?就是官方教程里紧接环境安装和基础张量操作之后的那一大块:从autograd自动求导,到torch.nn模块的搭建,再到一个完整图像分类器的训练过程。这块内容在官方文档里被叫作“Learning PyTorch”的核心章节,几乎覆盖了PyTorch每日开发百分之八十的接口使用场景。用一句话概括:它教你从张量层面理解梯度计算,再从模块层面理解网络搭建,最后给你一个能跑起来的完整训练流程。

顺带交代一下翻译这个事情的背景。之前我一直在用PyTorch做图像方面的模型训练,但多数时候是复制粘贴官方示例再改改,出了梯度异常、显存溢出这类问题,经常只能靠试。某天偶然翻到官方指南的原始英文页面,才发现很多进阶概念官方其实讲得很清楚,只是没人认真翻译过。于是我决定把第二部分完整过一遍,翻译不是目的,逼自己逐行读懂才是目的。事实证明这个方法是有效的,下面把整个过程和核心内容都展开讲。

2. 翻译工作的整体设计思路:术语、结构与验证方案

2.1 术语处理的三个必须原则

翻译PyTorch技术文档,最怕的是术语混乱。同一篇文档里,如果你一会儿把“gradient”翻成“梯度”,一会儿翻成“斜率”,读者马上就懵。我给自己定了几条硬规矩。

第一,核心术语保持中文翻译但首次出现时标注英文原文。比如“自动求导(autograd)”、“模块(module)”、“损失函数(loss function)”。这样做的好处是,读者看中文能理解概念,看到英文能对应上实际代码里的类名和函数名。因为代码里不会写中文,你总得让读者知道“损失函数”在代码里长什么样。

第二,不翻译专有名词。Tensor、Module、DataLoader、Optimizer这些类名保持原样。强行翻译成“张量类”、“数据加载器”反而增加记忆负担,因为代码里使用的是英文。这是一个很重要的翻译原则:中文部分是帮助理解,英文部分是为了直接对应代码书写。

第三,API文档部分的翻译严格遵循“每个参数名保留英文、参数说明用中文”。参数名如果你擅自翻译,读者拿去官方查文档就对不上了,这是翻译技术文档的大忌。

2.2 结构布局:为什么官方把自动求导放在神经网络之前

这本指南第二部分的结构顺序非常讲究,翻译的时候我不建议打乱。它首先花大量篇幅讲autograd,接着介绍一个最简单的自定义网络如何定义,然后再给出损失函数、优化器、训练循环。最后用CIFAR-10分类器把前面所有知识点串起来。

这个顺序背后的逻辑是:神经网络的一切行为都可以归结为张量的前向计算和梯度反传。如果你不理解自动求导如何处理一波张量之间的依赖关系,你就无法理解为什么调用loss.backward()之后模型的参数.grad属性里会多出东西。更关键的是,你无法理解某些看似玄学的报错,比如“梯度为None”、“inplace操作导致反向传播失败”。这些坑的根源都在autograd层面,不在神经网络层面。

所以我翻译的时候,在原文基础上额外增加了一段“前置背景说明”,放在autograd章节之前,用日常语言解释计算图和梯度记录是怎么回事。这不是画蛇添足,而是对中文读者非常必要的桥梁。原版英文教程默认读者习惯了英文表述里的抽象概念,中文读者往往需要更具体的生活化类比。

想象一下:计算图就是一张流水线工单,每个张量操作就是一道加工工序,而autograd模块就是贴在每道工序旁边的记录员。当你执行z = torch.matmul(x, w) + b时,记录员记下了“z由x、w、b经过矩阵乘法和加法产生”。调用loss.backward()时,记录员沿着工单反着走一遍,把每个原料应该承担多少“责任”(也就是梯度)计算出来。这个类比我在翻译稿里保留了,收到的反馈普遍说一下子就想通了。

2.3 每个代码示例都必须在翻译稿里标注验证环境

翻译做完之后,我做的第一件事是逐段运行原文代码。官方文档有个问题是,不同版本的API细节会有微小变化,老版本写法在新版PyTorch里可能已经废弃。我的验证环境用的是常见的稳定版本组合:PyTorch 2.x配合CUDA版本的torchvision,操作系统用的是Linux环境,因为训练CIFAR-10分类器需要安装torchvision数据集,Windows下虽然也能跑但数据下载路径容易出幺蛾子。在翻译稿开头我就把这个环境信息写清楚了,避免读者在自己电脑上复现时遇到版本差异导致的报错。

3. 核心内容拆解:自动求导与模块构建的每个关键细节

3.1 autograd机制的完整理解与常见误区

第二部分开篇的autograd部分是全篇的基础。官方代码里给出一个非常小的例子:一个五元素输入x,一个三元素输出y的矩阵运算,然后手动设置了requires_grad=True的核心参数。翻译过程中,我发现很多人对这个参数的理解停留在“打开了自动求导开关”的层面,这远远不够。

requires_grad=True的本质是告诉autograd:“请在这个张量的操作链条上记录梯度信息”。如果某个张量requires_grad=False,它对计算图来说就是“死的”,反向传播的路径会在它这里截断。一个常见的连锁问题是:从requires_grad=True的模型参数计算出的新张量,默认也会requires_grad=True,这一点官方文档讲得清楚,但很多人没注意到。这意味着你在写中间变量时,如果不小心让某个原本不需要梯度的中间结果挂上了梯度追踪,后向计算时会白白增加开销。

翻译时我补充了一个排查技巧:用tensor.requires_grad属性逐个检查,定位哪个变量意外打开了梯度追踪。有一次我训练一个多分支网络时发现某个中间特征图被重复计算了多次梯度,原因就是中间变量链路上某个节点被赋给了requires_grad=True的默认值。这在官方英文文档里是藏在注释里的一句话,不翻出来没人会注意。

官方向导还专门讲了一个重要概念——在反向传播之后,计算图会被释放。这意味着你如果反复调用backward()而不重新计算前向过程,第二次调用会报错,提示计算图已经被释放。翻译时这里我特别加粗提醒,并且补充了实践中最常见的两个应对手段:一是对非标量张量调用backward时需要传入一个与形状一致的权重参数;二是如果想保留计算图供后续操作使用,需要在backward时设置retain_graph=True。这两个细节是中文社区提问里出现频率极高的点。

3.2 nn.Module与forward方法:为什么不能直接调用net(inputs)

官方指南第二部分进入torch.nn之后,第一个重量级对象就是nn.Module。所有网络结构都要继承这个类,并且实现自己的forward方法。翻译这部分时我思考最多的是,怎么把“为什么用net(x)而不用net.forward(x)”这个看似无关紧要的问题讲明白。

其实这是nn.Module设计上最巧妙的一点。nn.Module重写了Python的__call__方法,在内部先执行一系列钩子(hook)逻辑,再调用你的forward方法。这意味着你自定义网络的前向计算,实际被包了一层“前置处理”和“后置处理”。翻译稿里我明确标注了:直接调用forward会绕过这些内置逻辑,导致某些注册好的组件失效。这个细节很多教程压根不提,但理解它,你才能看懂为什么切换训练/评估模式要调用net.train()和net.eval(),为什么这两个方法能自动影响BatchNorm和Dropout层的行为。

在网络定义部分,官方给出的经典示例是一个两层卷积加三层全连接的小网络。翻译时我把每一层的输入输出尺寸变化单独整理成了表格,这一点对新手特别重要。很多人在抄这个示例时,死活算不清全连接层第一个参数为什么是16 * 5 * 5,其实算清楚只需要三步:

第一步,确认输入图像尺寸,官方示例的CIFAR-10是3通道32×32的图像;第二步,逐层计算卷积和池化后的尺寸变化,第一个卷积层后变成6通道28×28,池化后14×14,第二个卷积层后16通道10×10,池化后5×5;第三步,把通道数乘上最后特征图的宽高,得到16×5×5=400,这就是全连接层的输入维度。翻译稿里我把这个推算过程完整展开写了一遍,读者反馈说这是全篇对他们帮助最大的段落之一,因为网上太多教程直接扔出400这个数字,完全不管它怎么来的。

3.3 损失函数、优化器与训练循环的关键参数选择

第二部分接着介绍了损失函数和优化器。官方示例用了交叉熵损失(CrossEntropyLoss)和随机梯度下降(SGD)。这里有个很容易被忽略但非常重要的细节:CrossEntropyLoss内部包含了Softmax计算。这意味着你在定义网络最后一层输出时,不需要自己再手动加Softmax,直接输出原始logits即可。但如果你在推理阶段需要概率值,就得自己调用softmax函数。

翻译这部分时,我还补充了一个官方文档默认读者已经知道、但实际上很多人不清楚的点:损失函数的选择和激活函数之间存在连锁关系。比如官方示例的多分类任务里,输出层用线性层输出原始分数,配合交叉熵损失,这在数学上数值更稳定。如果你把网络结构改成最后一层接上ReLU,损失值可能出现奇怪的波动。这不是模型问题,而是网络设计与损失函数不匹配。

优化器部分,我花了比较多时间处理SGD参数的中文表述。动量momentum是SGD里最重要的超参数,它的作用是让参数更新方向不仅考虑当前梯度,还考虑上一次的更新方向,从而抑制震荡、加速收敛。官方文档给了momentum=0.9这个默认推荐值,我翻译时补充解释了为什么0.9是常用值——这是众多实验验证出来的一个相对稳妥的取值,它让历史梯度的贡献以0.9的系数衰减,既能平滑更新轨迹,又不会让历史信息主导当前更新。

训练循环的框架,官方用了三层循环嵌套:外层是遍历epoch,中层是遍历DataLoader取出的批次,内层执行前向、计算损失、反向传播、参数更新。翻译时我逐行注释了代码,把每个函数的作用标注在代码块里,这样读者在看训练循环时不会迷失。

4. 实操过程:从翻译到完整跑通CIFAR-10分类器的全流程

4.1 翻译用的辅助工具与效率方法

整个翻译过程,我用的工具组合其实很朴素:一个支持纯文本编辑的编辑器,一份英文原文PDF导出,以及本地可运行的PyTorch环境。没有用任何机翻工具直接出稿,因为技术文档的机翻错误率太高,尤其是涉及专有名词和代码块的地方。我采取的方式是先通读一遍原文,把整体章节结构画出大纲,再按章节逐段翻译。翻译完一个章节,就立刻运行对应的示例代码,确认代码无误后,把运行结果截图或者记录在翻译稿备注里。

这里有个技巧值得分享:先翻译代码上方的说明文字,再翻译代码本身周围的注释,最后回头统一校对一遍术语。我发现自己第一次翻译时经常在“tensor”(张量)和“tensor”(张量对象)这类语境之间绷不住,导致全文不统一。第二遍统一校对就是为了解决这个问题。另外,我把所有代码都从原文复制出来而不是手工重敲,避免引入无中生有的语法错误。

4.2 复现训练流程:数据加载到训练循环的逐步实现

完整跑通官方第二部分,最关键的一步是CIFAR-10数据集的加载。官方使用torchvision的transforms做数据预处理。翻译这部分时,我对transforms.Compose这个API做了额外解释,因为新手经常忽略它的一个重要特性:管道里的每个transform是按顺序执行的,前一个的输出是后一个的输入。官方示例把图像先转为Tensor,再按均值方差做归一化。这个归一化的均值方差是CIFAR-10数据集预先统计好的:三个通道的均值分别是0.4914、0.4822、0.4465,方差分别是0.2470、0.2435、0.2616。官方直接把数值写成了一串列表,没有解释来历。翻译稿里我标注了这些数值是数据集的全局统计量,并提醒:如果是自己的数据集,这些数值需要自己统计,不能套用CIFAR-10的值。

数据加载完整流程如下:

第一步,用torchvision.datasets.CIFAR10指定下载目录和训练/测试标记,直接实例化会触发自动下载;第二步,把数据集传给DataLoader,并设定批量大小batch_size、是否打乱shuffle、以及多进程加载的线程数num_workers;第三步,在训练循环里枚举trainloader。这里有个细节,训练集和测试集的DataLoader设置不同,训练集shuffle=True是为了让每个epoch看到的数据顺序都不同,防止模型记住顺序,测试集shuffle=False是为了每次评估时数据顺序一致,保证评估结果可复现。

我在翻译稿中还特别强调了一个坑:num_workers不要盲目调大。官方示例没有展开讲这个参数,但实际使用时,在Windows上num_workers如果设置成大于0,容易出现子进程加载异常。翻译稿里我建议Windows用户先设置num_workers=0,跑通之后再尝试调大。这属于那种官方文档不会写、但实际开发大概率会遇到的本地适配问题。

4.3 训练循环里的四个固定动作及其作用

训练循环是官方第二部分里最核心的实操内容,每个epoch内的四个固定动作必须养成肌肉记忆。我用表格整理在翻译稿里:

动作代码作用
梯度清零optimizer.zero_grad()清空上一轮计算保留的梯度值,避免累积
前向传播outputs = net(inputs)计算输入数据经过网络的输出
反向传播loss.backward()计算所有requires_grad参数的梯度
参数更新optimizer.step()根据优化器规则用梯度更新参数

梯度清零是新手最容易漏的一步。漏掉它的典型表现是loss曲线异常震荡,最终训练失败。原理是autograd在每次backward时会把新的梯度累加到参数的.grad属性上,不清零的话,每一轮的梯度都是历史梯度之和,参数更新方向完全被带偏。官方文档在一个小角落里用一句代码提到了manual_zero_grad这个内部机制,大多数人不注意就会略过。

还有一个细节是我翻译时额外标注的:net.train()和net.eval()必须成对使用。CIFAR-10分类器示例里虽然没有用到Dropout和BatchNorm,但官方在分类器训练的末尾提到了这两行代码。如果你把这个示例扩展到更大网络,却发现训练和测试效果差异莫名其妙,多半是忘了在测试前调用net.eval()。翻译时我专门写了一段“测试阶段的易错点”,提醒读者不要在测试阶段沿用训练模式的配置。

4.4 完整示例的结构解剖:一个小而全的图像分类器

官方第二部分最后给出的完整示例,是把前面所有内容整合成的一个约四五十行代码的小型分类器。翻译时我没有直接照搬官方顺序,而是把代码拆成了五段,每段加了一段背景说明:卷积特征提取部分、全连接分类部分、训练循环部分、测试评估部分、结果展示部分。

卷积特征提取部分要重点关注的是nn.Conv2d的参数:输入通道数、输出通道数、卷积核大小。官方示例第一层是nn.Conv2d(3, 6, 5),含义是输入3通道、输出6通道、卷积核5×5。这里我补充了一个推理示例:如果输入的图像是32×32×3,卷积之后输出尺寸是(32-5)/1+1=28,所以得到28×28×6的特征图。这种逐步推算不会出现在官方英文文档里,但对中文读者理解卷积核尺寸和特征图尺寸的关系极其重要。

全连接分类部分需要留意view操作。view(-1, 16 * 5 * 5)的作用是把多维特征图展平成一维向量。翻译时我提醒读者,这里使用的“-1”表示“根据另一个维度自动推断”,当时特征图的尺寸是16通道5×5,所以展平后每个样本是400维向量。如果前面任何一层的参数修改了,这个400就必须跟着变,这是全连接网络最让人头疼的维度对接问题。

5. 翻译复核与常见问题速查:给复现者的一份排查清单

5.1 我在翻译和复现中实际遇到的五个典型问题

折腾两周,踩过的坑不少,整理成速查表放在翻译稿的附录里,这里挑五个最有代表性的展开说。

第一个问题是数据集下载卡在进度条不动。这个问题的根源是CIFAR-10数据集首次下载需要联网获取压缩包,网络状况不好时很容易卡住。解决方式是在代码里设置好download=True后,先手动把数据集压缩包下载好放到指定目录,再重新运行。手动放置时要注意文件名的完整性和压缩包后缀,不要解压,torchvision会自己处理。

第二个问题是经典的数据加载器报错“DataLoader worker”异常。这个通常出现在Windows系统上,根本原因是多进程数据加载器和主程序之间的通信机制在某些环境不兼容。我处理的方式是在ifname== 'main':保护块里放置训练代码,同时设置num_workers=0直接规避。翻译稿附录里我把完整写法贴出来了,读者可以直接抄。

第三个问题是训练过程中Loss值变成负的或者NaN。这个大概率是数据归一化出了问题。CIFAR-10数据集如果忘记了transforms.Normalize这一步,像素值范围是0到255,网络输入数值过大,收敛过程就会数值不稳定。此外,如果学习率设置过大,梯度更新一步跳出合理范围,也会出现NaN。处理方法是检查数据预处理管道,并把学习率调低一个量级试试。

第四个问题是测试集准确率远低于训练过程的表现。原因通常是训练循环里用了训练模式,测试时没有切换到评估模式。也就是说网络中的Dropout层还在运行,BatchNorm还在用当前批次的统计量。解决办法就是在测试代码前加一行net.eval()。

第五个问题是反向传播时报出“element 0 of tensors does not require grad”的错误提示。这个错误通常意味着损失函数计算过程中断开了梯度链路。常见原因是对某些无requires_grad的变量调用了backward。检查方法是沿着损失函数从最后一层往前捋,逐个确认每一层的参数是否requires_grad=True。

5.2 对官方文档的版本差异处理与资源建议

翻译过程中我注意到,官方文档在不同版本之间有一些细微表述和代码片段的差异。比如早期版本里optimizer的类型提示写法、DataLoader部分参数默认值的不同,旧版本教程和最新稳定版之间确实存在API细节上的差异。我的处理原则是:所有代码以英文官网最新稳定版的文档为准,并在翻译稿开头写明使用的PyTorch版本。这样读者在复现时如果遇到API名称不一致,至少知道该去哪个版本的文档里找对应说明。

翻译完整个第二部分后,我给自己的后续规划是继续做第三部分内容,也就是数据并行、保存加载模型这些进阶主题。这部分官方文档同样有不少值得细读的地方,尤其是模型保存和加载的细节很容易写错,这种内容翻出来对照中文更容易理解细节。如果你也在考虑阅读这本官方指南,我的建议是:不要跳过翻译过程直接看结论,因为翻译这个动作本身,会让你注意到很多平时读英文文档一扫而过的细节,那些细节往往就是线上模型出问题时的救命线索。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询