简介《Deep Learning with PyTorch Step-by-Step》2022年v1.1.1版电子书面向深度学习初学者与希望系统掌握PyTorch的开发者提供从基础概念到实战项目的循序渐进学习路径。内容涵盖神经网络原理、激活函数与损失函数等基础知识PyTorch环境搭建与数据处理图像分类、自然语言处理等实战案例并延伸至迁移学习、生成对抗网络、注意力机制等进阶主题附录另附资源链接与参考文献。资源包共1个PDF文件大小约33.58MB单文件即完整书籍便于离线阅读与检索。目前已有216人学习适合计算机专业学生、转行人工智能领域的职场人士以及有编程基础但缺乏深度学习经验的读者可借助书中示例代码与案例讲解逐步完成模型构建与训练实践为后续深入人工智能方向打下基础。1. 从零手推梯度到 nn.Module这份 2022 版 PyTorch 教程到底值不值得啃很多人学 PyTorch 的路径是反的先背nn.Module、optimizer.step()、loss.backward()三件套跑通一个 MNIST 就以为会了结果模型不收敛时完全不知道从哪查。这份 2022 年的《Deep Learning with PyTorch Step-by-Step》走的是另一条路——它先用一整章Chapter 0把梯度下降拆成 Step 0 到 Step 5用最原始的 Python 循环手算预测、损失、梯度、参数更新把损失曲面和学习率的影响画出来再在 Chapter 1 里把同样的流程换成 Tensor、autograd、Optimizer、Model 逐层替换。换句话说它先让你看清黑匣子里的齿轮怎么转再告诉你 PyTorch 把这些齿轮封装成了什么。适合两类人一是能跑 demo 但说不清zero_grad()为什么必须调的人二是准备带新人、需要一套可复现教学路径的从业者。下面按我实际拆书复现的顺序讲重点落在环境怎么搭、代码怎么抄、哪几个地方最容易翻车。2. 环境与配套仓库把 v1.1.1 的 Notebook 跑起来2.1 版本对齐为什么不能直接 pip install torch 了事这本书的正文写于 2021 年底到 2022 年初v1.1.1 是 2022-02-12 的修订版。它配套的是一套 Jupyter Notebook书里每个代码块基本都能在 notebook 里找到对应单元。问题在于2022 年的 PyTorch 和现在的版本之间隔了好几个大版本torch.utils.data、TensorBoard的接口、torchviz的兼容性都动过。我的做法是不追最新按书里的环境描述锁一个接近当年的版本区间先把 notebook 跑通再谈升级。书里 Setup Guide 给的是 Anaconda conda 虚拟环境 PyTorch TensorBoard GraphViz/Torchviz可选 Git Jupyter 这条链路。这套组合在 2022 年是标准配置现在依然可用只是 PyTorch 的安装命令要按你的 CUDA 情况改。下面是我复现时用的环境脚本CPU 和 GPU 两套都给了。# 创建并激活虚拟环境Python 版本锁在 3.9兼容性最稳 conda create -n pytorch-step python3.9 -y conda activate pytorch-step # CPU 版本没有独显或只想跑通逻辑时用这个 pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cpu # GPU 版本按你的 CUDA 版本选这里以 cu117 为例 # pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117 # 配套工具notebook、可视化、计算图 pip install jupyter tensorboard torchviz graphviz matplotlib numpy pandas这段脚本的关键点有三个。第一python3.9不是随便选的3.10 之后部分旧版 torchvision 的 wheel 会缺3.9 是那个年代最保险的落点。第二--index-url指向 PyTorch 官方 wheel 源比走默认 PyPI 快且不会装到不匹配的 CUDA 变体。第三torchviz依赖系统级的 GraphViz光pip install不够还得装 GraphViz 本体Windows 装 exe 并加 PATHmacOS 用brew install graphvizLinux 用apt install graphviz否则make_dot会报ExecutableNotFound。提示如果你只想验证书里的梯度下降逻辑CPU 版本完全够用Chapter 0 和 Chapter 1 的合成数据量很小GPU 反而增加设备搬运的干扰。2.2 仓库结构与 Notebook 的对应关系书里提到的 Official Repository 是一套按章节组织的 notebook。我拆的时候发现它的组织逻辑是「一章一个主 notebook配套若干辅助脚本」Chapter 0 对应梯度下降可视化Chapter 1 对应简单回归问题后面 Part I 继续往图像、文本方向走。你不需要一次全下按你要复现的章节取对应 notebook 即可。跑 notebook 之前有个容易忽略的点书里的代码大量依赖matplotlib的内联绘图和numpy的随机种子。如果你在本地 Jupyter 里跑记得先执行%matplotlib inline并且把随机种子固定住否则每次跑出来的损失曲线形状都不一样你会误以为是代码有问题。我一般会在 notebook 开头加一个 cellimport numpy as np import torch import random # 固定三个随机源保证每次复现结果一致 def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) set_seed(42) print(torch:, torch.__version__, | cuda:, torch.cuda.is_available())set_seed里对torch.cuda.manual_seed_all做了条件判断是因为 CPU 环境下这个调用虽然不报错但没意义加个判断让脚本在两种环境下都能干净跑完。打印版本那一行是我自己的习惯出问题时第一眼就能确认是不是环境串了。2.3 用 conda 环境隔离避免「昨天还能跑」血泪经验不要在你的 base 环境里装这本书的依赖。书里为了演示torchviz和TensorBoard会引入一些版本敏感的包一旦和你其他项目的 torch 版本打架排查成本极高。conda 环境的好处是删了重建只要两分钟conda env remove -n pytorch-step一句就干净了。我见过有人直接在系统 Python 里装最后 torch 和 torchvision 版本错位报的错是undefined symbol查了半天才发现是环境脏了。3. Chapter 0 的梯度下降五步法手算一遍再交给 autograd3.1 Step 0 到 Step 5把训练循环拆成可打印的中间量Chapter 0 的价值在于它不让你用任何 PyTorch 的高级封装纯用 numpy 和 Python 循环把线性回归的梯度下降走一遍。它的五步是Step 0 随机初始化参数Step 1 算预测Step 2 算损失Step 3 算梯度Step 4 更新参数Step 5 重复。每一步的中间结果都能打印出来看这是它和大多数教程最大的区别——大多数教程直接给你一个for epoch in range(...)的黑盒。我按书里的思路重写了一个最小版本你可以直接抄去跑import numpy as np # 合成数据y 2x 1 加一点噪声 np.random.seed(42) x np.random.rand(100, 1) y 2 * x 1 0.1 * np.random.randn(100, 1) # Step 0: 随机初始化 w 和 b w np.random.randn(1) b np.random.randn(1) lr 0.1 for epoch in range(200): # Step 1: 前向计算预测值 y_pred w * x b # Step 2: 均方误差损失 loss ((y_pred - y) ** 2).mean() # Step 3: 对 w 和 b 求偏导链式法则手推 grad_w (2 * (y_pred - y) * x).mean() grad_b (2 * (y_pred - y)).mean() # Step 4: 梯度下降更新 w - lr * grad_w b - lr * grad_b if epoch % 50 0: print(fepoch {epoch:3d} | loss {loss:.4f} | w {w[0]:.3f} | b {b[0]:.3f})这段代码里grad_w和grad_b的推导是重点损失对w的偏导是2*(y_pred-y)*x的均值对b的偏导是2*(y_pred-y)的均值。书里把这一步画在损失曲面上让你看到每次更新是在曲面上往低处走一小步。lr0.1是我调过的值书里演示过低学习率走得太慢、高学习率震荡、极高学习率直接发散三种情况你可以把lr改成 0.01、0.5、2.0 各跑一次观察 loss 曲线的形态变化这比看文字描述直观得多。3.2 学习率与特征缩放两个让新手翻车的参数书里专门用一节讲「Bad Feature」和标准化这是很多人忽略但实际项目里天天遇到的坑。当你的特征量纲差异大比如一个特征是 0 到 1另一个是 0 到 10000损失曲面会变成一个狭长的椭圆梯度下降在长轴方向震荡、短轴方向爬得极慢看起来就像「模型不收敛」。书里的解法是对特征做标准化我把它落成可复用的函数def standardize(x): # 按列减均值除标准差注意用训练集统计量 mu x.mean(axis0) sigma x.std(axis0) return (x - mu) / sigma, mu, sigma x_scaled, mu, sigma standardize(x) # 预测新样本时要用同一组 mu/sigma不能重新算 def predict(x_new, w, b, mu, sigma): return w * ((x_new - mu) / sigma) b这里的关键是mu和sigma必须从训练集算出来并保存推理时复用。我见过有人在推理阶段对测试集重新算均值和标准差结果线上指标和离线对不上查了两天才发现是数据泄漏。书里没把这个坑写得很重但实际工程里这是高频事故点。3.3 从手算到 autogradbackward、grad、zero_ 的对应关系Chapter 1 做的事是把 Chapter 0 的手算逐步替换成 PyTorch 的机制。对应关系是手算的grad_w对应loss.backward()之后w.grad里的值手算的w - lr * grad_w对应optimizer.step()而optimizer.zero_grad()对应的是「每次更新前把上一轮的梯度清零」这个手算版本里天然不存在、但 PyTorch 必须显式做的动作。为什么 PyTorch 要显式清零因为backward()默认是累加梯度而不是覆盖。书里用zero_()演示了这一点。如果你忘了清零梯度会一轮轮叠加表现为 loss 先降后爆或者参数更新幅度越来越大。这是新手最常见的翻车点之一我在第 5 章会单独列出来。import torch # 用 Tensor 重写requires_gradTrue 让 autograd 接管 x_t torch.tensor(x, dtypetorch.float32) y_t torch.tensor(y, dtypetorch.float32) w_t torch.randn(1, requires_gradTrue) b_t torch.randn(1, requires_gradTrue) optimizer torch.optim.SGD([w_t, b_t], lr0.1) for epoch in range(200): optimizer.zero_grad() # 清空上一轮梯度必须放在 backward 前 y_pred w_t * x_t b_t loss ((y_pred - y_t) ** 2).mean() loss.backward() # 反向传播梯度写入 w_t.grad / b_t.grad optimizer.step() # 按 lr 更新参数optimizer.zero_grad()放在backward()之前是标准写法放在step()之后也行但不要漏。requires_gradTrue只对需要学习的参数设输入数据x_t不需要设了反而浪费显存。4. 从 Tensor 到 nn.Module封装层级与 state_dict 的实操4.1 嵌套模型与 Sequential什么时候该拆层书里从裸 Tensor 过渡到nn.Module时先讲了嵌套模型Nested Models再讲Sequential。这个顺序是有讲究的Sequential适合线性堆叠但一旦你的模型有分支、有跳连、有自定义 forward 逻辑就得回到nn.Module手写forward。我一般建议新手先用Sequential跑通再改成显式nn.Module这样能看清__init__里注册的层和forward里的调用是怎么对应的。import torch.nn as nn class SimpleRegressor(nn.Module): def __init__(self): super().__init__() # 注册层这些层的参数会自动出现在 parameters() 里 self.linear nn.Linear(1, 1) def forward(self, x): return self.linear(x) model SimpleRegressor() print(list(model.parameters())) # 能看到 weight 和 bias print(model.state_dict().keys()) # 保存/加载时用的键名state_dict()返回的是有序字典键名形如linear.weight、linear.bias。保存模型时用torch.save(model.state_dict(), path)加载时先实例化同结构模型再load_state_dict。不要直接torch.save(model)存整个对象那样会把类的路径也序列化进去换目录或改类名就加载失败这是另一个高频坑。4.2 设备管理与 train/eval 模式书里有一节讲 Device核心是model.to(device)和tensor.to(device)要一致。我补一个实际项目里的习惯把 device 判断封装成一个变量所有张量创建时都带上它避免 CPU/GPU 混用报Expected all tensors to be on the same device。device torch.device(cuda if torch.cuda.is_available() else cpu) model SimpleRegressor().to(device) x_t x_t.to(device) y_t y_t.to(device) # 训练时切 train 模式推理时切 eval 模式 model.train() # ... 训练循环 ... model.eval() with torch.no_grad(): # 推理不建计算图省显存 pred model(x_t)model.train()和model.eval()对本书前两章的简单线性模型影响不大但一旦用到 Dropout 和 BatchNorm 就是生死攸关的。我习惯在训练循环开头写model.train()在验证循环开头写model.eval()哪怕当前模型没有这些层养成习惯后面少踩坑。4.3 用 TensorBoard 看损失曲线而不是 print书里 Setup Guide 装了 TensorBoard但正文里 print 用得更多。实际调试时把 loss 写进 TensorBoard 比刷屏 print 有用得多尤其是你要对比不同学习率下的曲线形态。from torch.utils.tensorboard import SummaryWriter writer SummaryWriter(runs/exp1) for epoch in range(200): # ... 训练一步 ... writer.add_scalar(loss/train, loss.item(), epoch) writer.add_scalar(param/w, model.linear.weight.item(), epoch) writer.close() # 终端执行tensorboard --logdir runsadd_scalar的第二个参数必须是 Python 标量所以loss.item()不能省直接传 tensor 会报类型错。--logdir指向runs父目录TensorBoard 会自动把子目录当成不同实验并列展示方便你对比lr0.01和lr0.1两条曲线。5. 避坑与排查五个我实际踩过的坑5.1 现象loss 变成 nan 或突然爆炸原因通常是学习率过大或者输入特征没标准化导致梯度量级失控。书里 Chapter 0 的「Very High Learning Rate」一节演示的就是这个。解决先把lr降一个数量级重跑同时对输入做标准化确认 loss 曲线是平滑下降而不是锯齿震荡。如果降 lr 后仍然 nan检查数据里有没有 inf 或极端离群值。5.2 现象参数更新了但 loss 不降原因大概率是忘了optimizer.zero_grad()梯度在累加等效学习率越来越大或者方向被历史梯度带偏。解决在backward()前加zero_grad()打印w.grad看每轮梯度是否在合理量级。另一个可能是requires_grad没设对参数根本没进计算图grad是None。5.3 现象Expected all tensors to be on the same device原因模型在 GPU 上输入张量还在 CPU或者反过来。解决统一用device变量创建张量时.to(device)模型也.to(device)。排查时打印next(model.parameters()).device和x.device对比。5.4 现象加载模型报Missing key(s) in state_dict原因保存和加载时的模型结构不一致常见于改了层名或加了新层。解决先print(model.state_dict().keys())和保存文件的键名对比确认结构一致再加载。如果只是部分层不同用load_state_dict(..., strictFalse)并检查返回的 missing/unexpected 列表。5.5 现象notebook 里图不显示或 torchviz 报错原因没执行%matplotlib inline或者系统没装 GraphViz 本体。解决notebook 首行加%matplotlib inlinetorchviz报ExecutableNotFound时装 GraphViz 并确认dot -V在终端能跑通。6. 进阶验证用梯度检验确认你的手推公式没写错书里 Chapter 0 的手推梯度是教学重点但你怎么确认自己推的grad_w公式是对的我一般用数值梯度做交叉验证这也是很多框架内部测试用的方法。原理很简单对参数加一个极小扰动eps用(loss(weps) - loss(w-eps)) / (2*eps)近似导数和解析梯度对比误差在1e-6量级就算通过。def numeric_grad(f, x, eps1e-6): # 中心差分近似导数 return (f(x eps) - f(x - eps)) / (2 * eps) # 以 w 为例固定 b验证解析梯度 def loss_w(w_val): y_pred w_val * x b return ((y_pred - y) ** 2).mean() w_test 1.5 analytic (2 * (w_test * x b - y) * x).mean() numeric numeric_grad(loss_w, w_test) print(fanalytic {analytic:.8f} | numeric {numeric:.8f} | diff {abs(analytic-numeric):.2e})eps不能太大也不能太小太大截断误差明显太小浮点精度不够1e-6是常用折中。diff在1e-6以下说明公式没问题如果差好几个数量级多半是链式法则漏了一项或者均值的位置放错了。这个检验我建议在写完任何自定义 loss 或自定义 backward 后都跑一遍比盯着公式看靠谱。另一个验证手段是对比 numpy 手算版和 PyTorch autograd 版的最终参数。用同一组初始值和同一学习率跑 200 轮w和b应该收敛到非常接近的值差异来自浮点精度和更新顺序。如果差很多说明两边的更新逻辑有出入回去逐行对。验证项方法通过标准解析梯度正确性中心差分对比相对误差 1e-5手算与 autograd 一致性同初值同 lr 跑固定轮数参数差 1e-3学习率合理性扫 0.001 到 1.0 看 loss 曲线存在明显下降区间设备一致性打印参数与输入 device完全一致从那以后我每次写自定义损失函数或者手推梯度都强制先跑一遍数值梯度检验再往下接训练循环这个习惯帮我省掉了至少两次「训练半天不收敛、最后发现公式少乘 2」的返工。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?