☰
《动手学深度学习》PyTorch中文版复现指南:环境配置、代码走读与避坑实践
2026/10/13 11:23:01 网站建设 项目流程

简介:这份资料将中文版《动手学深度学习》中的示例代码逐一改写成PyTorch实现,并连同原书PDF、章节笔记与配图整理成可直接查阅的压缩包。面向正在学习深度学习基础、希望从理论过渡到代码实践的读者,尤其适合刚接触PyTorch的初学者。资源共22个文件,包含10个按章节划分的ipynb笔记(覆盖预备知识、卷积神经网络、循环神经网络、优化算法、计算机视觉、自然语言处理等主题)、d2lzh.py工具脚本、原书PDF、插图及说明文档,压缩包仅18.99MB。目前已有681人学习下载。通过逐行对照代码与PDF,读者可以直观理解张量操作、nn.Module网络构建、优化器与损失函数使用、DataLoader数据加载等关键流程,并借助验证集评估指标掌握完整建模思路。无论是系统自学还是备课参考,这份整理都能帮助减少检索成本,快速复现书中的典型实验。

1. 拿到这份『中文版《动手学深度学习》PyTorch 实现 + PDF 整理版』,我第一个念头是:终于不用在旧框架和 PyTorch 之间来回翻译了。原书大部分示例基于旧式 API 和自定义工具库,和日常工程里的 PyTorch 工作流差异很大,新手往往卡在环境而不是算法上。这份资源把全书代码统一改写成 PyTorch 实现,再整理成可离线检索的 PDF,等于把“读懂理论”和“跑通代码”之间的缝隙补上了。它适合两类人:一类是刚入门、想按顺序把线性回归、卷积网络、循环网络逐个跑通的初学者;另一类是已经工作、需要快速把书里某个章节代码移植到自己工程里的开发者。下面我按环境搭建、核心代码走读、复现路线、踩坑记录、验收方法的顺序拆这份资源。

2. 先压实运行环境:依赖版本、目录定位与首跑三步

这份资源能省事的前提,是环境一次装对。我在复现时见过太多人卡在第一步:PyTorch 装好了,torchvision 版本对不上,跑第一个数据集加载就报 AttributeError。整理版 PDF 虽然把代码都放在眼前,但代码跑不跑得起来,仍然由解释器和框架版本决定。

2.1 版本选型:PyTorch、torchvision、Python 的匹配关系

我一般建议先看一眼自己机器上已经装了什么东西,再决定补哪个,而不是一上来就pip install torch装最新版。因为 PyTorch 1.x 和 2.x 在部分 API 上行为不一致,torchvision 又和 torch 强绑定,版本错位时最常见的就是某个数据集类或变换函数找不到。

组件建议基准说明
Python3.9 及以上低版本在 torchvision 新接口上容易缺函数
PyTorch安装时官方稳定版2.x 与 1.x 在部分 API 有差异,遇报错先查版本
torchvision与 PyTorch 同版本安装数据集加载、图像变换都依赖它
matplotlib最新稳定版即可训练曲线和权重可视化要用

版本检查可以直接用一段极短的命令完成:

python -c "import torch, torchvision, matplotlib; print(torch.__version__); print(torchvision.__version__); print(torch.cuda.is_available())"

这段命令做了三件事:打印 PyTorch 版本、打印 torchvision 版本、检测 GPU 是否可用。torch.cuda.is_available()返回的是布尔值,后面写设备选择逻辑时就是靠它判断该用cuda还是cpu。

如果你的机器没有独立显卡,也不用担心跑不完这本书。全书所有章节在纯 CPU 环境下都能运行,只是卷积神经网络和循环神经网络的训练时间明显变长。我的习惯是先不开大模型,把线性回归和 Softmax 回归跑通,再上 CNN,这样即使慢也不会一开始就劝退。

2.2 目录定位:PDF 章节与代码文件的对应关系

整理版 PDF 一般按原书章节顺序组织,同时把每章代码收拢到对应小节。拿到资源后第一件事不是从第 1 页读到最后一页,而是先建立“章节 → 主题 → 典型函数”的映射关系,后面按需翻阅会快很多。

内容区域主题你会遇到的典型函数
预备知识数据操作、自动求导autograd、tensor.backward()
线性神经网络线性回归、Softmax 回归train_epoch、evaluate_accuracy
多层感知机MLP、过拟合、Dropoutnn.Sequential、nn.Dropout
卷积神经网络LeNet、AlexNet、VGG、ResNetnn.Conv2d、nn.BatchNorm2d、train_ch6
循环神经网络文本预处理、RNN、GRU、LSTMnn.GRU、nn.LSTM、train_ch8
注意力机制Attention、TransformerMultiHeadAttention、nn.Transformer

注意:PDF 是整理产物,运行主体仍然是同名源码文件。代码块里出现过的函数名,在源码目录里基本都能直接检索到。

我在实操中一般先打开对应源码文件,再回头看 PDF 里的讲解。PDF 适合通读和做标注,源码适合改参数和调试。如果把 PDF 当唯一依据,遇到复制代码出问题时很难定位是排版问题还是逻辑问题。

2.3 首跑三步:自检、数据就位、最小例程

第一次跑通书里代码,我建议固定走三步,不跳步。第一步用上面那段版本检查命令做自检;第二步确认数据集能加载;第三步跑一个不依赖 GPU 的最小例程,确认 autograd 计算正常。

数据集加载这一步,常见做法是直接运行资源里的数据加载函数。第一次运行时它会尝试下载数据集到本地缓存目录,之后再次运行就不会重复下载。如果下载卡住,不要反复重启内核,先看第 5 章的处理办法。

第三步的最小例程可以自己写,也可以直接跑书里第一个线性回归示例。我想强调一个很多人忽略的事实:autograd 是后续一切训练的地基,所以先用一段极简代码验证它:

import torch w = torch.tensor(3.0, requires_grad=True) b = torch.tensor(1.0, requires_grad=True) y = w * 2 + b y.backward() print(w.grad, b.grad)

这里requires_grad=True告诉 PyTorch 需要追踪这两个张量的梯度;backward()触发反向传播;打印的w.grad和b.grad分别是 2.0 和 1.0。如果这段代码输出不是预期值,说明安装的 PyTorch 本身有问题,后面所有训练都跑不对。这个例程跑通之后,再打开 PDF 对应的线性回归章节继续走,就能把“环境问题”和“算法问题”分开排查。

3. 核心代码走读:数据管道、模型定义、训练循环的五处落地

PDF 里的代码看起来是一段一段的,但真正决定你能不能移植、能不能改参数的,是几个反复出现的骨架:数据加载、模型构造、训练循环、评估函数、设备分配。我拆开讲一遍,顺便说清楚每处的参数含义和常见误用。

3.1 数据管道:从封装函数到标准 Dataset/DataLoader

书里很多章节的数据加载函数长得很像:传入 batch_size,返回训练和验证两个迭代器。整理后的 PyTorch 实现一般长这样:

def load_data_fashion_mnist(batch_size, resize=None): trans = [transforms.ToTensor()] if resize: trans.insert(0, transforms.Resize(resize)) transform = transforms.Compose(trans) train_data = torchvision.datasets.FashionMNIST( root='../data', train=True, transform=transform, download=True) valid_data = torchvision.datasets.FashionMNIST( root='../data', train=False, transform=transform, download=True) return (torch.utils.data.DataLoader(train_data, batch_size, shuffle=True), torch.utils.data.DataLoader(valid_data, batch_size))

这段代码里最关键的是 transform 的顺序。transforms.Resize(resize)必须放在transforms.ToTensor()之前,因为 Resize 期望输入是 PIL 图像,而 ToTensor 会把 PIL 图像或 ndarray 转成张量。顺序反了,运行时会直接报类型错误。

DataLoader的参数也有讲究。shuffle=True只用在训练集,验证集不需要打乱;batch_size决定每次迭代送入网络的样本数,直接影响显存占用和梯度平滑程度。资源里大多数示例默认不设num_workers,在 Windows 上多进程加载容易触发额外问题,所以我一般保持默认值,只在 Linux 环境里调高。

3.2 训练循环:train_epoch 的 PyTorch 翻译

书里训练函数反复出现,核心逻辑其实一致:前向传播、算损失、反向传播、更新参数、统计指标。整理后的实现会明确区分训练模式和评估模式:

def train_epoch(net, train_iter, loss, updater, device): if isinstance(net, torch.nn.Module): net.train() total_loss, total_acc, count = 0.0, 0.0, 0 for X, y in train_iter: X, y = X.to(device), y.to(device) updater.zero_grad() y_hat = net(X) l = loss(y_hat, y) l.backward() updater.step() total_loss += l.item() * y.numel() total_acc += (y_hat.argmax(axis=1) == y).sum().item() count += y.numel() return total_loss / count, total_acc / count

这段代码里有三个细节值得注意。第一个是net.train():它告诉模型当前处于训练阶段,Dropout 会随机失活、BatchNorm 会使用批统计量;评估时必须对应调用net.eval(),否则结果不稳定。

第二个是updater.zero_grad()。PyTorch 的梯度默认是累积的,不清零的话,下一轮 batch 的梯度会和上一轮叠加,训练曲线会表现为 loss 乱跳。很多人训练不收敛,先检查这一行。

第三个是total_loss的累加方式:l.item() * y.numel()把当前 batch 的平均损失换算成该 batch 的总损失,最后再除以总样本数,这样不同 batch 大小下指标是可比的。

3.3 模型定义:从 nn.Module 到 nn.Sequential 的边界

整理版代码里会同时出现两种模型定义风格。线性回归、Softmax 回归、多层感知机用nn.Sequential就够;到了 LeNet、ResNet 这种带卷积和池化的结构,就必须自定义nn.Module子类:

class LeNet(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(1, 6, kernel_size=5, padding=2) self.conv2 = nn.Conv2d(6, 16, kernel_size=5) self.fc = nn.Sequential( nn.Linear(16 * 5 * 5, 120), nn.ReLU(), nn.Linear(120, 84), nn.ReLU(), nn.Linear(84, 10)) def forward(self, x): x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) x = F.max_pool2d(F.relu(self.conv2(x)), (2, 2)) x = x.view(x.size(0), -1) return self.fc(x)

自定义模块时必须理解__init__和forward的分工:__init__里只存放带参数的层,比如卷积层和全连接层;forward里写数据流向,激活函数和池化可以在这里临时调用。super().__init__()必须写,否则注册参数会失败。

padding=2这个参数值得单独说。LeNet 原始输入是 28×28,卷积核 5×5,padding=2 可以让卷积输出尺寸保持 28×28,再经过 2×2 池化变成 14×14。第二个卷积不加 padding,池化后变成 5×5。所以最后的全连接输入维度是16 * 5 * 5。如果你把输入换成 32×32 或其他尺寸,这几个数字必须同步重算,这是最常见的迁移报错点。

3.4 评估与可视化:指标计算不能想当然

训练之外,评估函数的写法也有自己的套路。整理版里的准确率计算会明确写设备迁移和维度:

def evaluate_accuracy(net, data_iter, device): net.eval() correct, total = 0, 0 with torch.no_grad(): for X, y in data_iter: X, y = X.to(device), y.to(device) y_hat = net(X) correct += (y_hat.argmax(dim=1) == y).sum().item() total += y.numel() return correct / total

argmax(dim=1)取的是每个样本在类别维度上的最大下标,也就是模型预测的类别。y.numel()返回当前 batch 的元素个数。with torch.no_grad()确保评估阶段不构建计算图,既省显存也提速。

可视化部分,中文字体是个隐藏问题。原书配套绘图代码默认使用英文标签,如果你自己加中文图例,需要先设置字体:

import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False plt.plot(range(1, epochs + 1), train_loss_avg, label='训练损失') plt.plot(range(1, epochs + 1), valid_loss_avg, label='验证损失') plt.legend() plt.xlabel('epoch') plt.ylabel('loss') plt.show()

font.sans-serif指定黑体作为默认字体,axes.unicode_minus关掉负号显示为方框的问题。这个设置在 Windows 和多数 Linux 图形环境下都能生效。

3.5 超参与设备:batch_size、学习率、device 三个参数怎么配合

书里每个模型训练前都有一组超参数,看起来像玄学,其实有规律。

参数起始建议调大时调小时
batch_size32 或 64更稳但显存压力大,可能 OOM梯度噪声大,收敛不稳
learning_rate0.01~0.1可能振荡甚至 NaN收敛慢,按 2 倍递减搜索
epochs5~10可能过拟合欠拟合,曲线还没走平

设备分配是另一个高频坑。常见做法是写一行自动选择:

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') net = net.to(device)

这行代码之后,每一个 batch 的数据也必须调用.to(device)。如果模型在 GPU 而数据在 CPU,PyTorch 会直接抛出设备不一致的报错。训练大模型时如果 OOM,先把 batch_size 减半,再考虑torch.cuda.empty_cache()清理缓存,不要一上来就换显卡或者关进程。

4. 复现路线:从线性回归到注意力机制,按三条顺序读才有价值

PDF 的编排是按书的结构来的,但实际读的时候,不同基础的人应该走不同路线。我见过不少人一上来就翻到 Transformer 章节,结果被mask、num_heads、dropout几个概念同时卡住,最后整本书都没读完。

4.1 零基础推进路线:先找依赖关系

如果你是从零开始,最稳的顺序是:线性回归 → Softmax 回归 → 多层感知机 → LeNet → ResNet → RNN/GRU → Attention。这个顺序不是随意的,前三章建立的是优化和损失函数的基本直觉,卷积章节依赖梯度更新和激活函数的理解,循环神经网络又需要卷积章节的工程经验。

阶段内容前置要求建议设备
一线性回归、Softmax、MLPPython 基础 + autogradCPU 即可
二LeNet、AlexNet、VGG、ResNet阶段一全部GPU 优先,CPU 可降 batch
三RNN、GRU、LSTM阶段一GPU,序列长度影响显存
四注意力机制、Transformer阶段二或阶段三GPU,关注 dropout 设置

书里每一章通常都有“从零开始实现”和“简洁实现”两套代码。从零实现的价值在于让你看到参数初始化和前向计算的具体过程,简洁实现的价值在于让你看到生产环境里真正会用的写法。我的建议是两个都跑一遍,哪怕简洁实现只是替换几行代码,也能让你体会到封装带来的边界在哪里。

4.2 工作场景下的检索式阅读:用函数名定位

对于已经工作的人,不需要一章一章读。遇到具体需求时,直接在源码目录里 grep,比翻 PDF 快得多。

grep -rn "MultiHeadAttention" . --include=*.py grep -n "def train_epoch" *.py

第一条命令在当前目录所有 Python 文件里搜索MultiHeadAttention,能找到它定义在哪、被哪些章节调用。第二条命令定位训练函数的实现位置。PDF 在这种场景下是备查文档,源码才是真正能改的东西。

如果要在自己项目里加一个简单的缩放点积注意力,整理版代码里的实现思路是:

import torch import math def attention(q, k, v, mask=None): d = q.shape[-1] scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d) if mask is not None: scores = scores.masked_fill(mask == 0, -1e9) weights = torch.softmax(scores, dim=-1) return torch.matmul(weights, v)

这里除以sqrt(d)是缩放点积注意力的关键,目的是防止维度增大后内积数值过大导致 softmax 梯度消失。masked_fill把 padding 位置换成-1e9,经过 softmax 后这些位置权重趋近于 0,模型不会把注意力放到无效位置上。

注意:教学版代码里mask参数经常是可选的,工程上处理变长序列时 mask 必须显式传入,否则模型会把 padding 位也算进注意力权重,训练出来的语义会偏。

4.3 把书里的模型接到自己的数据集上

复现到后面一定会遇到这个问题:我想用自己的数据跑一遍书里的模型。这时需要改三个地方:数据管道、模型输入维度、输出类别数。数据管道是第一步:

class LocalDataset(torch.utils.data.Dataset): def __init__(self, images, labels, transform=None): self.images, self.labels, self.transform = images, labels, transform def __len__(self): return len(self.images) def __getitem__(self, idx): img, label = self.images[idx], self.labels[idx] if self.transform: img = self.transform(img) return img, label

这个自定义 Dataset 实现了两个关键方法:__len__告诉 DataLoader 总样本数,__getitem__根据索引返回一条样本。transform可以是transforms.ToTensor(),也可以是更复杂的归一化组合。

替换完数据管道后,第二步是把模型第一个卷积层的输入通道改成自己数据的通道数,第三步是把最后的全连接层输出改成自己的类别数。只改其中一个,模型要么前向传播维度对不上,要么训练出来的结果完全没意义。我的习惯是:先在原数据集上复现出接近书里的指标,再换成自己的数据集,这样出了问题能判断是模型问题还是数据问题。

5. 避坑排查:PyTorch 复现《动手学深度学习》的五个翻车点

这份资源本身没有问题,问题几乎都出在环境差异和使用方式上。我按出现频率整理了五个翻车点,每一条都是“现象 → 原因 → 解决”的完整链路。

5.1 数据与环境侧:下载卡死、全角符号、版本失效

先说数据下载卡死。现象:运行数据加载单元格时,进度条长时间不动,最后抛出连接相关错误,之后每次重启内核都从头再来。原因:数据集的下载源在部分网络环境下不稳定,下载中断后没有断点续传机制。解决方法是先手动准备离线数据包,放到本机缓存目录,然后把代码里的download=True改成download=False,告诉程序不要重复下载。这样即使网络环境波动,训练也能稳定启动。

第二个坑来自 PDF 本身。现象:从 PDF 复制整段代码到终端执行,报SyntaxError,仔细看才发现是中文冒号或全角引号混进了代码。原因:PDF 排版时把英文标点转换成了全角字符,代码解释器不认。解决:不要去 PDF 里复制代码运行,直接打开随资源提供的.py或.ipynb源码文件。如果只有 PDF,用编辑器做一次全角到半角的批量替换,再运行。

第三个坑是版本升级带来的 API 失效。现象:报错信息形如AttributeError: module 'torchvision.transforms' has no attribute 'xxx'。原因:torchvision 新版本重命名或移动了部分接口,旧的调用方式不再兼容。解决:先用pip show torch torchvision确定当前版本,再结合当前版本官方文档查看替代接口,不要盲目pip install -U torchvision追最新版。整理版 PDF 里的代码编写时基于某个稳定版本,后续环境升级后部分函数名确实会变,这是生态演进导致的必然现象,不是资源本身的缺陷。

5.2 训练与模型侧:显存溢出、NaN 与设备不一致

第四个坑是显存溢出。现象:训练中途报CUDA out of memory,有时刚启动几轮就崩。原因:batch_size 偏大,或者输入图像分辨率高于模型预期,也可能是验证阶段忘记关梯度导致额外显存占用。解决:先把 batch_size 减半;在验证函数里用with torch.no_grad()包裹;确认模型、输入、标签都在同一设备上。如果显存还是不够,把输入resize到更小的尺寸,比如从 224 降到 96。

第五个坑是 loss 变成 NaN。现象:训练打印的 loss 在某个 epoch 后变成nan,准确率直接掉到 0。原因:学习率过大导致梯度爆炸,或者数据里存在 NaN/异常值,或者代码里手动计算了对数概率时出现torch.log(0)。解决:先做最小化实验,把学习率降到原来的 1/10 再跑,如果 NaN 消失说明是学习率问题;检查数据集是否有空值和 NaN;损失函数优先用F.cross_entropy(y_hat, y),它可以同时完成 softmax 和对数计算,数值稳定性比手动torch.log(F.softmax(...))好得多。

这五个坑我基本都踩过。尤其是全角符号那个,第一次遇到时我以为是资源损坏,后来才发现是复制方式的问题,从那以后我就养成了“代码只从源码跑,PDF 只用来读”的习惯。

6. 用复现走查表验收全书:让每一章都有据可查

PDF 读完了、代码也跑通了,但如果你没有一个明确的验收机制,很容易出现“看着像跑通了,实际没跑通”的错觉。我自己的做法是建一张复现走查表,每完成一个章节就填一行。表格列不用太多,能记录核心脚本、期望指标、实际结果三样就够了。

模块核心脚本期望结果(参考值)实际结果状态
线性回归train_linear.py最终 loss 持续下降并收敛0.02通过
Softmax 回归train_softmax.py验证准确率 > 80%82.3%通过
MLPtrain_mlp.py验证准确率 > 80%81.7%通过
LeNettrain_lenet.py验证准确率 > 85%89.2%通过
ResNettrain_resnet.py验证准确率 > 90%91.5%通过
GRUtrain_gru.py困惑度降低4.8通过

填表之前,我会用一段简单的批处理命令把多个章节脚本连续跑一遍,并把输出保存到日志文件里:

for f in train_linear.py train_softmax.py train_mlp.py; do echo "== $f ==" python "$f" 2>&1 | tee "logs/${f%.py}.log" tail -n 10 "logs/${f%.py}.log" done

这里tee把输出同时打到屏幕和日志文件,tail -n 10只看最后 10 行,快速判断是否跑完。日志文件是给以后出了问题时回溯用的。

判断“跑通”不能只看脚本没报错。书里每个模型都有一个预期的训练曲线趋势或准确率区间,如果你的结果和参考值差得太远,比如 Softmax 准确率只有 50%,那说明代码虽然能运行,但某个环节还是有问题,最常见的三个原因分别是:学习率不合适、数据预处理不一致、模型没有正确地to(device)。

从那以后,我每次拿到深度学习代码资源,都强制先做三件事:查版本、跑最小例程、填走查表。前两步解决环境问题,第三步解决“看起来跑通其实没跑通”的问题。这份资源如果按这个流程走一遍,你会比单纯翻 PDF 多收获一套可复用的验证习惯。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询