☰
PyTorch深度学习实战:364页《动手学深度学习》高效学习与避坑指南
2026/10/20 7:27:54 网站建设 项目流程

1. 为什么这本364页的PyTorch深度学习手册值得你熬夜啃

第一次看到这个标题的时候,我正坐在工位上啃一份满是数学公式的深度学习教材,翻了三章还在推导反向传播的链式法则,代码一行没写。那种感觉就像你想学做菜,结果菜谱前五十页全在讲小麦的分子结构和美拉德反应的化学方程式——不是说这些不重要,而是对于一个想尽快上手炒个蛋炒饭的人来说,门槛设得太高了。

这本364页的PyTorch版《动手学深度学习》开源PDF,解决的就是这个“从想学到能动手”之间的鸿沟。它不是那种让你先啃完三百页数学再碰代码的传统教材,而是从第一页开始就让你跑代码、看结果、调参数,在动手的过程中理解原理。整本书围绕PyTorch这个当前学术界和工业界都在用的深度学习框架展开,覆盖了从线性回归、多层感知机、卷积神经网络、循环神经网络到注意力机制和Transformer的完整知识链路。

适合谁来读?如果你是有一定Python基础、想进入深度学习领域但被各种数学推导劝退的开发者,这本书几乎是为你量身定做的。如果你已经用过TensorFlow或者Keras,想转到PyTorch生态,这本书也能帮你快速建立PyTorch的思维习惯。甚至如果你是在校学生,正在做课程项目或者准备毕设,需要快速搭建一个能跑的模型,这本书里的代码示例可以直接拿来改。

我花了大概两周时间把这本书从头到尾过了一遍,边看边跑代码,踩了不少坑,也总结了一些让学习效率翻倍的方法。下面我把整个学习路径拆开来讲,包括这本书的知识体系怎么组织的、PyTorch代码实操中有哪些关键细节、遇到报错怎么排查,以及我自己总结的一套“不熬夜也能啃完”的节奏安排。

2. 全书知识体系拆解与学习路径规划

2.1 从线性回归到深度网络的递进逻辑

这本书的章节编排遵循了一个非常清晰的递进逻辑:先让你理解最简单的模型,然后逐步增加复杂度,每一步都在前一步的基础上自然延伸。第一章到第三章讲的是深度学习的基础设施——数据操作、线性代数、自动求导和线性回归。这部分看起来简单,但恰恰是最容易被跳过然后后面处处卡壳的地方。

我见过太多人直接跳到卷积神经网络那一章,结果连torch.tensor的广播机制都没搞明白,写出来的代码维度对不上,报错信息看半天不知道哪里出了问题。所以我的建议是,前三章一定要老老实实过一遍,尤其是自动求导那部分。PyTorch的autograd机制是整个框架的核心,理解了它,后面所有模型的训练循环你都能自己推导出来。

第四章到第六章进入多层感知机、模型选择和卷积神经网络。这里开始出现真正的“深度学习”味道了。多层感知机那章会讲到激活函数的作用、反向传播的推导,以及如何用PyTorch的nn.Module来组织模型结构。卷积神经网络那章是重点中的重点,从卷积核的计算方式、填充和步幅的影响、多输入多输出通道的处理,到池化层和经典网络结构(LeNet、AlexNet、VGG、ResNet),每一节都有对应的代码实现。

第七章到第九章覆盖循环神经网络、注意力机制和优化算法。循环神经网络部分会讲到序列数据的处理、梯度消失和梯度爆炸问题,以及LSTM和GRU的设计思路。注意力机制那章是整本书的高潮,从注意力汇聚到多头注意力,再到Transformer架构的完整实现,这部分代码量比较大,但跑通之后你对现代大模型的理解会上一个台阶。

2.2 每章配套代码的运行环境搭建

这本书的代码全部基于PyTorch,官方推荐用Jupyter Notebook来运行,因为可以边看讲解边执行代码块,交互性很好。我自己的环境配置是这样的:Python 3.9以上,PyTorch 1.12以上版本,配合torchvision和torchaudio。如果你有NVIDIA显卡,建议装CUDA版本的PyTorch,训练速度会快很多;如果没有,CPU版本也能跑,只是卷积神经网络那几章的训练时间会从几秒变成几分钟。

安装命令很简单,去PyTorch官网找到对应你系统的安装命令,用pip或者conda装就行。我习惯用conda创建独立环境,避免和系统里的其他包冲突:

conda create -n d2l python=3.9 conda activate d2l pip install torch torchvision torchaudio pip install jupyter d2l

这里有个小细节:d2l这个包是这本书的作者封装的一些工具函数,比如数据加载、绘图、计时等,装了这个包之后书里的代码才能直接运行。很多人第一次跑代码报ModuleNotFoundError: No module named 'd2l',就是因为漏装了它。

注意:如果你用的是Apple Silicon芯片的Mac,PyTorch从1.12版本开始支持MPS加速,可以在代码里把设备设为mps来利用GPU,但部分算子可能还不完善,遇到报错就切回CPU。

2.3 学习节奏安排与时间分配建议

364页听起来很多,但实际读起来比想象中快,因为大量篇幅是代码和输出结果,真正需要精读的文字部分大概占一半。我自己的节奏是每天花两到三小时,两周左右过完第一遍。具体分配是这样的:前三章基础部分花两天,每天两小时,重点是理解自动求导和线性回归的训练循环;卷积神经网络部分花四天,因为涉及的网络结构多,每个都值得跑一遍看看效果;循环神经网络和注意力机制花三天,这部分代码复杂度高,需要多调试;优化算法和剩余章节花两天。

如果你时间更紧,可以跳过一些经典网络的详细实现,直接看ResNet和Transformer的代码,因为这两个是现代深度学习中最常用的结构。但线性回归、多层感知机和卷积的基础部分不能跳,它们是理解后续所有内容的前提。

3. PyTorch核心操作与代码实操要点

3.1 张量操作中的广播机制与维度变换

PyTorch的张量操作是整本书的基础,几乎每一章的代码都会涉及。其中最容易出错也最重要的两个概念是广播机制和维度变换。广播机制允许不同形状的张量进行运算,比如一个形状为(3, 1)的张量和一个形状为(1, 4)的张量相加,结果会是(3, 4)。这个机制在实现损失函数和注意力计算时非常常用,但如果不理解它的规则,很容易得到意料之外的结果。

我踩过的一个坑是在实现softmax的时候,没有指定维度,结果对整批数据做了归一化而不是对每个样本做归一化。正确的做法是torch.softmax(X, dim=1),其中dim=1表示对每一行做softmax。类似的还有sum、mean、max等操作,都要明确指定维度。

维度变换方面,view、reshape、permute和transpose是最常用的几个函数。view要求张量在内存中是连续的,而reshape没有这个限制,所以我现在基本都用reshape,省得记哪些操作会破坏连续性。permute用于交换多个维度,比如把(batch, channel, height, width)变成(batch, height, width, channel),在可视化的时候经常用到。

import torch # 广播机制示例 a = torch.tensor([[1], [2], [3]]) # 形状 (3, 1) b = torch.tensor([[10, 20, 30, 40]]) # 形状 (1, 4) c = a + b # 形状 (3, 4) print(c) # 维度变换示例 x = torch.randn(2, 3, 4, 5) # (batch, channel, height, width) y = x.permute(0, 2, 3, 1) # (batch, height, width, channel) print(y.shape)

提示:当你遇到维度不匹配的报错时,第一件事是在报错行之前打印所有涉及张量的shape,这样能快速定位是哪个张量的维度出了问题。

3.2 自动求导机制的理解与使用禁忌

PyTorch的自动求导是它最核心的功能之一,理解它的工作原理能帮你避免很多隐蔽的bug。简单来说,当你创建一个张量时设置requires_grad=True,PyTorch会记录所有对它的操作,形成一个计算图。调用backward()时,它会沿着这个计算图反向传播,计算每个需要梯度的张量的梯度。

这里有几个使用禁忌需要特别注意。第一,不要在训练循环中累积计算图。每次迭代开始时,必须调用optimizer.zero_grad()清空之前的梯度,否则梯度会累加,导致模型更新方向错误。第二,如果你只是想评估模型而不想计算梯度,一定要用with torch.no_grad():包裹前向传播,否则会白白消耗显存和计算资源。第三,对同一个计算图多次调用backward()会报错,因为PyTorch默认在反向传播后释放计算图,如果需要保留,要设置retain_graph=True。

# 正确的训练循环结构 for X, y in data_iter: optimizer.zero_grad() # 清空梯度 y_hat = model(X) loss = loss_fn(y_hat, y) loss.backward() # 反向传播 optimizer.step() # 更新参数 # 评估时关闭梯度计算 with torch.no_grad(): y_hat = model(X_test)

我在实现自定义损失函数的时候还遇到过一个坑:如果损失函数中有需要学习的参数,必须确保这些参数也被注册到优化器中,否则它们不会更新。PyTorch的nn.Parameter会自动注册,但如果你用普通张量,就需要手动添加到优化器的参数列表里。

3.3 模型定义与nn.Module的组织方式

这本书里定义模型有两种方式:一种是直接用nn.Sequential把层串起来,适合简单的线性堆叠结构;另一种是继承nn.Module并实现__init__和forward方法,适合复杂的网络结构。我建议从一开始就养成用nn.Module的习惯,因为实际项目中几乎不会用纯Sequential的结构。

继承nn.Module的时候,有几个细节容易出错。第一,所有包含可学习参数的层必须在__init__中定义,不能放在forward里,否则每次前向传播都会重新创建层,参数无法累积更新。第二,forward方法的输入输出都是张量,不要在里面做数据加载或者打印日志的操作,这些应该放在训练循环里。第三,如果你需要访问中间层的输出,可以在forward里把中间结果存到self的属性上,但要注意这会影响计算图的释放。

import torch.nn as nn class MyModel(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim): super().__init__() self.fc1 = nn.Linear(input_dim, hidden_dim) self.relu = nn.ReLU() self.fc2 = nn.Linear(hidden_dim, output_dim) def forward(self, x): x = self.fc1(x) x = self.relu(x) x = self.fc2(x) return x

注意:super().__init__()这一行绝对不能省,否则PyTorch无法正确注册子模块,调用parameters()时会返回空列表,模型根本没法训练。

4. 训练过程调优与常见报错排查

4.1 学习率选择与优化器参数配置

学习率是深度学习中最难调的参数之一,设大了损失震荡不收敛,设小了训练慢得像蜗牛。这本书里主要用了三种优化器:SGD、Momentum和Adam。SGD最基础,但收敛慢;Momentum在SGD基础上加了动量,能加速收敛;Adam自适应调整每个参数的学习率,通常开箱即用效果就不错。

我的经验是,对于大多数任务,先用Adam,学习率设0.001,跑一遍看看损失下降曲线。如果损失下降太慢,调到0.01试试;如果损失震荡,降到0.0001。对于卷积神经网络,学习率可以设大一点,因为卷积层的梯度通常比较稳定;对于循环神经网络和Transformer,学习率要设小一点,因为这些结构对梯度变化更敏感。

还有一个技巧是使用学习率调度器,比如torch.optim.lr_scheduler.StepLR,每隔几个epoch把学习率乘以一个衰减因子。这样前期用大学习率快速下降,后期用小学习率精细调整。书里在优化算法那章有详细的对比实验,你可以自己跑一遍看看不同调度策略的效果差异。

optimizer = torch.optim.Adam(model.parameters(), lr=0.001) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5) for epoch in range(num_epochs): train_one_epoch() scheduler.step() # 每个epoch后调整学习率

4.2 过拟合与欠拟合的判断与应对

过拟合和欠拟合是训练过程中最常见的两个问题。判断方法很简单:看训练损失和验证损失的变化趋势。如果训练损失持续下降但验证损失开始上升,说明过拟合了;如果两者都居高不下,说明欠拟合。

过拟合的应对手段主要有三种:增加数据量、使用正则化、使用Dropout。增加数据量最直接但成本最高;正则化(L2正则)通过在损失函数中加上权重的平方和来限制模型复杂度,PyTorch的优化器自带weight_decay参数;Dropout在训练时随机丢弃一部分神经元,强迫网络学习更鲁棒的特征。书里在多层感知机那章有Dropout的完整实现,代码很简洁,几行就能加到你的模型里。

欠拟合的应对手段相对简单:增加模型复杂度(加层、加宽)、减小正则化强度、增加训练轮数。有时候欠拟合是因为学习率太小导致模型没学动,调大学习率就能解决。

问题类型训练损失验证损失应对手段
过拟合持续下降先降后升增加数据、L2正则、Dropout
欠拟合居高不下居高不下增加模型复杂度、减小正则、增大学习率
正常稳定下降稳定下降保持当前配置

4.3 典型报错信息与快速定位方法

跑这本书的代码时,我遇到过几类典型的报错,这里整理一下排查思路。

第一类是维度不匹配,报错信息通常是RuntimeError: mat1 and mat2 shapes cannot be multiplied或者size mismatch。这种问题九成是因为前一层输出的特征数和后一层期望的输入特征数对不上。排查方法是在报错行之前打印张量的shape,然后对照网络结构检查每一层的输入输出维度。

第二类是设备不一致,报错信息是RuntimeError: Expected all tensors to be on the same device。这是因为模型在GPU上但数据在CPU上,或者反过来。解决方法是在训练循环开始前把数据和模型都移到同一个设备上:X, y = X.to(device), y.to(device),model = model.to(device)。

第三类是梯度为None,报错信息是RuntimeError: element 0 of tensors does not require grad。这通常是因为你在计算损失之前用了torch.no_grad(),或者输入张量的requires_grad没有设为True。检查一下前向传播是否在no_grad上下文之外,以及模型参数是否确实需要梯度。

第四类是显存不足,报错信息是CUDA out of memory。解决方法有几种:减小batch size、用torch.cuda.empty_cache()清空缓存、把模型改成半精度(model.half())、或者用梯度累积来模拟更大的batch size。

提示:遇到报错不要慌,先看报错信息的最后一行,那里通常有具体的错误类型和位置。然后从报错位置往上找,看是哪个张量的操作出了问题。大部分报错都能通过打印shape和device定位到原因。

5. 从理论到落地:用书中知识完成一个完整项目

5.1 项目选题与数据准备

学完这本书之后,最好的巩固方式是自己做一个完整的项目。选题不用太复杂,比如图像分类、文本情感分析、或者简单的目标检测都可以。我自己的做法是找一个Kaggle上的入门竞赛数据集,用书里学到的卷积神经网络或者Transformer来跑一遍。

数据准备阶段有几个关键点。第一,数据集的划分要合理,训练集、验证集、测试集的比例通常是7:2:1或者8:1:1。第二,数据预处理要统一,比如图像数据要归一化到相同的均值和标准差,文本数据要统一分词和编码方式。第三,数据加载用torch.utils.data.DataLoader,配合Dataset类来封装,这样可以方便地实现批处理、打乱和多线程加载。

from torch.utils.data import Dataset, DataLoader class MyDataset(Dataset): def __init__(self, data, labels, transform=None): self.data = data self.labels = labels self.transform = transform def __len__(self): return len(self.data) def __getitem__(self, idx): x = self.data[idx] y = self.labels[idx] if self.transform: x = self.transform(x) return x, y dataset = MyDataset(train_data, train_labels, transform=transforms.ToTensor()) dataloader = DataLoader(dataset, batch_size=32, shuffle=True, num_workers=4)

5.2 模型搭建与训练循环编写

模型搭建部分,我建议先从简单的结构开始,比如一个三层的卷积网络,跑通了再逐步增加复杂度。不要一上来就搭ResNet或者Transformer,因为调试成本太高,出了问题很难定位是模型结构的问题还是训练配置的问题。

训练循环的编写有几个固定套路:每个epoch开始时把模型设为训练模式(model.train()),然后遍历dataloader,每个batch做前向传播、计算损失、反向传播、更新参数。每个epoch结束后把模型设为评估模式(model.eval()),在验证集上计算指标。如果验证指标连续几个epoch没有提升,就提前停止训练,避免过拟合。

def train(model, train_loader, val_loader, epochs, lr): optimizer = torch.optim.Adam(model.parameters(), lr=lr) criterion = nn.CrossEntropyLoss() best_acc = 0.0 for epoch in range(epochs): model.train() for X, y in train_loader: X, y = X.to(device), y.to(device) optimizer.zero_grad() output = model(X) loss = criterion(output, y) loss.backward() optimizer.step() model.eval() correct = 0 total = 0 with torch.no_grad(): for X, y in val_loader: X, y = X.to(device), y.to(device) output = model(X) _, predicted = torch.max(output, 1) total += y.size(0) correct += (predicted == y).sum().item() acc = correct / total print(f"Epoch {epoch+1}, Val Acc: {acc:.4f}") if acc > best_acc: best_acc = acc torch.save(model.state_dict(), "best_model.pth")

5.3 模型评估与结果可视化

模型训练完之后,评估和可视化是必不可少的环节。评估指标根据任务类型选择:分类任务用准确率、精确率、召回率、F1分数;回归任务用均方误差、平均绝对误差;目标检测用mAP。可视化方面,分类任务可以画混淆矩阵和ROC曲线,回归任务可以画预测值vs真实值的散点图。

书里在模型选择那章讲了K折交叉验证,这是一个非常实用的技巧,尤其适合数据量不大的场景。把数据分成K份,每次用其中一份做验证,其余做训练,最后取平均结果。这样能更可靠地评估模型的泛化能力,避免因为一次划分的偶然性导致评估偏差。

我自己的习惯是,在最终提交之前,一定要在测试集上跑一遍,看看模型在完全没见过的数据上的表现。如果测试集表现明显差于验证集,说明模型过拟合了,需要回头调整正则化或者增加数据。如果测试集表现和验证集差不多,那就可以放心了。

6. 高效学习这本书的独家心得与避坑指南

6.1 不要陷入数学推导的泥潭

这本书的一个特点是,它把数学推导放在附录或者正文的边栏里,正文主要讲代码实现和直觉理解。我的建议是第一遍读的时候跳过所有数学推导,先把代码跑通,看到结果,建立感性认识。第二遍再回头看数学,这时候你已经知道每个公式对应的是哪行代码,理解起来会快很多。

比如反向传播的链式法则,如果你一上来就啃公式,很容易被偏导数和雅可比矩阵搞晕。但如果你先跑一遍线性回归的代码,看到loss.backward()之后参数确实更新了,再去理解背后的数学,就会觉得顺理成章。这种“先动手后理论”的学习路径,对工程背景的人特别友好。

6.2 善用官方论坛和社区资源

这本书有配套的官方论坛,里面有很多人提问和解答。遇到报错的时候,先把报错信息复制到论坛搜索框里搜一下,大概率已经有人遇到过同样的问题并且有解决方案。如果没有,再自己发帖提问,记得附上报错信息、代码片段和你的环境配置,这样别人才能帮你定位问题。

另外,GitHub上的issue区也值得关注,有些问题是书里的代码和最新版PyTorch不兼容导致的,issue区通常会有修复方案或者workaround。我遇到过好几次因为PyTorch版本更新导致API变化的情况,都是在issue区找到的解决方法。

6.3 建立自己的代码片段库

学这本书的过程中,你会反复用到一些代码模式,比如训练循环、数据加载、模型保存和加载、指标计算等。建议你把这些代码整理成自己的片段库,用的时候直接复制粘贴,省去重复劳动。我自己的片段库里有十几个常用的模板,包括分类训练模板、回归训练模板、图像增强模板、文本预处理模板等。

整理片段库还有一个好处是,你会逐渐形成自己的代码风格和最佳实践。比如我现在写训练循环,一定会加上梯度裁剪(torch.nn.utils.clip_grad_norm_)来防止梯度爆炸,一定会用tqdm来显示进度条,一定会记录每个epoch的损失和指标到日志文件。这些习惯都是在反复实践中养成的,一开始可能觉得麻烦,但长期来看能省下大量调试时间。

提示:梯度裁剪在训练循环神经网络和Transformer时几乎是必须的,因为这两类结构容易出现梯度爆炸。clip_grad_norm_的参数通常设1.0或者5.0,具体值可以根据梯度范数的分布来调整。

6.4 从“跑通代码”到“理解设计”的进阶路径

跑通书里的代码只是第一步,真正的提升在于理解每个设计选择背后的原因。比如为什么卷积神经网络要用池化层?为什么Transformer要用多头注意力而不是单头?为什么BatchNorm能加速训练?这些问题书里都有解释,但你需要自己动手做对比实验才能深刻理解。

我的做法是,每学完一个结构,就自己改几个参数跑一遍,看看效果有什么变化。比如把卷积核从3x3改成5x5,把注意力头数从8改成4,把Dropout率从0.5改成0.2,观察训练曲线和最终指标的变化。这种“控制变量”的实验方法,能帮你建立起对超参数的直觉,以后遇到新任务时就知道大概该设什么值。

还有一个进阶方法是复现经典论文。书里提到的LeNet、AlexNet、VGG、ResNet、Transformer都有对应的原始论文,你可以试着不看书的代码,自己根据论文描述实现一遍,然后和书里的实现对比。这个过程会很痛苦,但收获也最大,因为你会被迫理解每一个细节,而不是简单地复制粘贴。

7. 学习资源扩展与后续进阶方向

7.1 配套视频与习题的配合使用

这本书有配套的视频课程,如果你看文字容易走神,可以配合视频一起看。视频的好处是老师会现场写代码、现场调试,你能看到遇到报错时是怎么排查的,这个过程中有很多隐性的经验是文字里写不出来的。我的建议是,先自己看文字跑代码,遇到卡住的地方再去看对应的视频片段,这样效率最高。

书里每章后面都有习题,有些是推导题,有些是编程题。编程题一定要动手做,哪怕你觉得很简单。我一开始也觉得有些习题太基础,跳过了,结果后来在实际项目中遇到类似问题,才发现当时如果做过习题,现在就能直接套用。习题的作用是帮你把知识从“看懂”变成“会写”,这个转化过程不能省。

7.2 从PyTorch到深度学习工程化的延伸

学完这本书之后,你对PyTorch和深度学习的基本概念已经有了扎实的理解。下一步可以往工程化方向延伸,比如学习如何用PyTorch Lightning来组织训练代码,如何用ONNX导出模型做推理部署,如何用TensorBoard或者Weights & Biases做实验跟踪。

PyTorch Lightning是一个很值得学的工具,它把训练循环、验证循环、日志记录、模型保存这些重复代码都封装好了,你只需要定义模型和配置优化器就行。用Lightning重写书里的训练代码,代码量能减少一半以上,而且可读性和可维护性都更好。

部署方面,ONNX是一个通用的模型交换格式,可以把PyTorch模型导出成ONNX,然后在不同的推理引擎上运行。书里没有涉及这部分内容,但如果你想把模型用到实际产品里,这是必须掌握的技能。导出的过程不复杂,用torch.onnx.export就行,但要注意有些自定义算子可能不支持,需要提前测试。

7.3 保持动手习惯的长期建议

深度学习是一个实践性极强的领域,光看书不写代码,过两周就忘光了。我的建议是给自己定一个“每周一模型”的计划,每周选一个书里的模型,自己从头实现一遍,不看书里的代码,写完再对比。这个过程能帮你发现哪些地方其实没真正理解,哪些API记错了,哪些细节忽略了。

另外,可以参加一些在线的机器学习竞赛,用真实数据集练手。竞赛的好处是有明确的评估指标和排行榜,你能看到自己的模型和别人的差距,也能从别人的解决方案里学到新技巧。刚开始不用追求名次,把完整流程跑通、提交结果,就已经比只看书强很多了。

我自己到现在还保持着每个月至少跑一个完整项目的习惯,有时候是复现论文,有时候是参加竞赛,有时候是帮朋友解决一个实际问题。深度学习这个领域变化很快,新模型、新工具层出不穷,但底层的东西——张量操作、自动求导、优化算法、网络结构——是相对稳定的。把这本书里的基础打牢,后面学新东西就会快很多。

最后分享一个我自己的小技巧:每次学完一个新模型,我会用自己的话写一篇简短的笔记,包括这个模型解决什么问题、核心设计是什么、代码实现的关键点在哪里、我踩过哪些坑。写笔记的过程就是强迫自己梳理思路的过程,而且以后忘了可以随时翻看。这些笔记积累下来,就是我自己的“深度学习手册”,比任何一本书都更贴合我的实际需求。

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

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

立即咨询