PyTorch深度学习实战:从张量运算到CNN可运行代码 简介《动手学深度学习2.0-李沐Pytorch版》是一本面向深度学习初学者与进阶研发人员的实践型教材聚焦PyTorch框架下的监督学习、线性回归及自然语言处理等核心任务系统解决理论理解难、代码实现弱、应用场景模糊等学习痛点。资源为单文件PDF格式共1个26.65MB高清电子书内容完整覆盖预备知识线性代数、微积分、自动微分、现代模型CNN、RNN、注意力机制及工程实践数据预处理、内存优化、张量运算目录结构清晰含大量可运行代码示例与原理图解。已有781人学习下载适合希望扎实掌握PyTorch底层操作、构建CV/NLP项目能力并深入理解模型训练全流程的学习者。1. 这不是一本“讲完就忘”的深度学习书它用 PyTorch 把线性回归到注意力机制全跑通专治“看懂了代码却写不出模型”的毕设/课设焦虑你有没有过这种经历花三天啃完 softmax 公式一合上书连nn.Linear(784, 10)和F.softmax(logits, dim1)哪个该在前、哪个要加dim都开始怀疑人生或者在做图像分类课设时把数据加载、归一化、模型定义、训练循环拼凑起来后loss 不降反升debug 到凌晨三点最后发现只是transforms.ToTensor()忘了放在transforms.Normalize()前面——这种“玄学翻车”本质不是手慢而是缺一套从零启动、每步可验证、每个张量形状都亲手掐指算过的完整链路。《动手学深度学习2.0-李沐PyTorch版》Release 2.0.02023年2月发布正是为解决这个痛点而生它不假设你已会调库而是带着你用原生 PyTorch 张量操作手写线性回归梯度更新再对比torch.nn简洁实现它不只告诉你 CNN 是什么而是让你从nn.Conv2d的in_channels/out_channels/kernel_size参数含义出发亲手搭出能跑通 MNIST 的三层卷积网络并实时打印每一层输出的 shape它甚至把 Kaggle 房价预测这种真实项目拆成“数据清洗→缺失值插补→类别编码→K 折交叉验证→提交格式校验”七步闭环。这不是理论教材而是一份可执行、可调试、可答辩的深度学习工程手册——适合正在赶毕设 deadline 的本科生、需要快速交付 NLP 分类模块的初级算法工程师、或是想真正搞懂autograd如何构建计算图的转行者。它解决的不是“要不要学”而是“学了之后明天早上能不能让模型在自己电脑上跑起来”。2. 总体架构设计三层知识结构与四大能力闭环拒绝碎片化学习这本书的骨架不是按“CNN→RNN→Transformer”堆砌技术名词而是用一套清晰的三层递进结构把所有内容锚定在“问题驱动—工具落地—能力内化”的闭环上。第一层是预备知识层第2章它不讲抽象数学而是直接用 PyTorch 张量演示为什么x y能自动广播但x y要求严格维度对齐为什么x.sum()默认压平所有轴而x.sum(dim0)才是按列求和为什么y x * 2; y.backward()后x.grad是2但y x ** 2; y.backward()后却是2*x——这些不是习题答案而是你在写loss.backward()时真正要理解的底层逻辑。第二层是核心模型层第3–4章它用“同一任务、两种实现”的对照法贯穿始终线性回归3.2节从零实现 vs 3.3节简洁实现、softmax 回归3.6 vs 3.7、多层感知机4.2 vs 4.3。这种设计强迫你直面差异——比如从零实现里你要手动初始化W和b、写y_hat torch.matmul(X, W) b而简洁实现中nn.Linear自动完成权重管理model(X)就返回结果。第三层是工程实践层第5–6章及后续它把“能跑”升级为“能用”第5章讲清楚state_dict()如何保存/加载模型参数不是整个对象、torch.load()为何要指定map_location才能在 CPU 上加载 GPU 训练的模型、nn.Sequential和自定义nn.Module在模块复用上的本质区别第6章则把 CNN 拆解为“感受野怎么算→padding 如何影响输出尺寸→stride 怎么控制下采样步长”三个可测量的物理量。这三层不是并列关系而是能力闭环预备知识让你读懂报错信息如RuntimeError: mat1 and mat2 shapes cannot be multiplied核心模型让你写出第一个可用模型工程实践则确保你能把它部署到实际场景比如把训练好的文本分类模型封装成 API 接口。我带过的某高校课程设计小组有同学坚持先通读第2章所有张量操作示例再动手写第3章线性回归结果在第三天就独立完成了房价预测的 baseline 模型——因为当X.shape (1000, 13)、W.shape (13, 1)时他心里已经预演过X W的结果必然是(1000, 1)不会在维度报错上卡住两小时。2.1 预备知识层用 PyTorch 张量重写线性代数与微积分告别“纸上谈兵”这一层的核心思想是所有数学概念必须映射到 PyTorch 的具体 tensor 操作上。以“矩阵-向量积”为例2.3.8节书中不只给出公式 $ \mathbf{y} \mathbf{A}\mathbf{x} $而是立刻给出三行可运行代码import torch # 定义 3x2 矩阵 A 和 2x1 向量 x A torch.tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) x torch.tensor([[7.0], [8.0]]) # 方法1使用 torch.mv() —— 专为矩阵-向量积设计 y1 torch.mv(A, x.squeeze()) # 注意x 需为 1D 向量 print(torch.mv result:, y1) # tensor([23., 53., 83.]) # 方法2使用 torch.matmul() —— 更通用的矩阵乘法 y2 torch.matmul(A, x) print(torch.matmul result:, y2.flatten()) # tensor([23., 53., 83.]) # 方法3手动实现验证理解 y3 torch.zeros(3, 1) for i in range(3): for j in range(2): y3[i] A[i, j] * x[j] print(manual result:, y3.flatten()) # tensor([23., 53., 83.])这段代码的价值远超“学会三种写法”。它强制你思考为什么torch.mv()要求x是 1D 向量squeeze()的作用是什么为什么torch.matmul(A, x)返回(3,1)而y1是(3,)手动实现里的双重循环恰恰对应了矩阵乘法的定义——当你在写 CNN 的nn.Conv2d时weight的 shape 是(out_channels, in_channels, kH, kW)而输入x是(N, C_in, H, W)它们的“卷积运算”本质上就是一种带滑动窗口的、高维的矩阵-向量积。再看微积分部分2.4节书中用torch.autograd直接演示链式法则定义x torch.tensor(2.0, requires_gradTrue)然后y x**2z y * 3最后z.backward()此时x.grad就是dz/dx dz/dy * dy/dx 3 * 2x 12。这个过程不是黑匣子而是你可以print(y.grad)看到dz/dy 3print(x.grad)看到最终结果。这种“所见即所得”的设计让梯度消失4.8.1节不再是一个抽象术语——当你把 10 层 MLP 的W全部初始化为0.01然后观察每层grad.mean().item()从0.001一路衰减到1e-12你就真正理解了为什么需要 Xavier 初始化。2.2 核心模型层“从零开始”与“简洁实现”双轨对照暴露所有隐藏假设这是全书最具教学张力的设计。以 softmax 回归第3.6–3.7节为例它用完全相同的 MNIST 数据集在两个平行章节中分别实现。从零开始实现3.6节要求你亲手处理每一个细节# 3.6.2 定义 softmax 操作注意这里做了数值稳定化 def softmax(X): X_exp torch.exp(X - torch.max(X, dim1, keepdimTrue)[0]) # 减去每行最大值 partition X_exp.sum(dim1, keepdimTrue) return X_exp / partition # 广播除法 # 3.6.3 定义模型纯张量运算无 nn.Module def net(X): return softmax(torch.matmul(X.reshape((-1, num_inputs)), W) b) # 3.6.4 定义损失函数交叉熵手动实现 def cross_entropy(y_hat, y): return -torch.log(y_hat[range(len(y_hat)), y]) # 取真实标签位置的概率 # 3.6.5 分类精度避免 argmax 错误 def accuracy(y_hat, y): if len(y_hat.shape) 1 and y_hat.shape[1] 1: y_hat y_hat.argmax(axis1) # 取概率最大索引 cmp y_hat.type(y.dtype) y return float(cmp.type(y.dtype).sum())这段代码里埋着三个关键教学点第一softmax中的torch.max(X, dim1, keepdimTrue)[0]不是可选项而是防止exp(1000)溢出的数值稳定化必需步骤第二cross_entropy里y_hat[range(len(y_hat)), y]是 PyTorch 特有的高级索引它等价于“对每个样本取其真实标签对应位置的概率值”比torch.nn.functional.cross_entropy少了一层封装但让你看清损失计算的本质第三accuracy函数里y_hat.argmax(axis1)的axis1明确指定了按行即每个样本取最大值避免新手常犯的argmax()默认压平的错误。而紧接着的简洁实现3.7节则用nn.Sequential和nn.CrossEntropyLoss封装# 3.7.1 初始化注意nn.Linear 自动完成 net nn.Sequential(nn.Flatten(), nn.Linear(num_inputs, num_outputs)) # 3.7.3 优化算法SGD但注意 weight_decay 参数 trainer torch.optim.SGD(net.parameters(), lr0.1, weight_decay0.001) # 3.7.4 训练循环loss 计算更简洁 for X, y in train_iter: trainer.zero_grad() l loss(net(X), y) # loss 是 nn.CrossEntropyLoss() 实例 l.backward() trainer.step()对照这两段你会立刻意识到nn.CrossEntropyLoss内部已经集成了softmax和log计算所以net(X)输出的是 raw logits而非概率weight_decay0.001对应的是 L2 正则化它在trainer.step()时自动加到梯度上——这正是第4.5节“权重衰减”的伏笔。这种双轨设计让“为什么用nn.CrossEntropyLoss而不用nn.BCEWithLogitsLoss”、“weight_decay和手动在 loss 里加l2_reg有什么区别”这类问题不再需要查文档而是在代码对比中自然浮现答案。2.3 工程实践层GPU 加速、模型保存、自定义层——把“能跑”变成“能交”第5章是整本书的“临门一脚”它解决的是学生交毕设、工程师交模块时最头疼的问题如何让本地跑通的代码在服务器上不报错如何把训练好的模型给别人用如何把业务逻辑封装成可复用的模块以 GPU 使用5.6节为例书中不只告诉你tensor.cuda()而是强调三个关键检查点# 5.6.1 检查设备可用性避免硬编码 cuda device torch.device(cuda if torch.cuda.is_available() else cpu) print(fUsing device: {device}) # 5.6.2 张量迁移注意.to(device) 返回新 tensor原 tensor 不变 X_gpu X.to(device) # X 仍是 CPU tensor W_gpu W.to(device) y_gpu torch.matmul(X_gpu, W_gpu) # 所有参与运算的 tensor 必须在同一设备 # 5.6.3 模型迁移nn.Module 的 to() 方法会递归迁移所有参数 net_gpu net.to(device) y_pred net_gpu(X_gpu) # 输入和模型必须同设备这段代码揭示了一个血泪经验torch.cuda.is_available()必须在程序开头就检查而不是等到loss.backward()报错才想起.to(device)是非原地操作必须重新赋值而net.to(device)会自动把net内部所有nn.Parameter和nn.Buffer迁移到 GPU——如果你手动写了net.W.data net.W.data.cuda()反而会破坏nn.Module的参数管理机制。再看模型保存5.5节书中明确区分了两种场景只保存参数推荐用于部署和保存完整模型用于断点续训# 场景1只保存参数轻量、安全、跨平台 torch.save(net.state_dict(), mlp_params.pth) # 保存字典{0.weight: ..., 0.bias: ...} # 加载时需先创建相同结构的 net再 load_state_dict net_new nn.Sequential(nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 10)) net_new.load_state_dict(torch.load(mlp_params.pth)) # 场景2保存完整模型包含结构、参数、优化器状态 torch.save({ epoch: 10, model_state_dict: net.state_dict(), optimizer_state_dict: trainer.state_dict(), loss: 0.123, }, checkpoint.pth) # 加载时需重建 optimizer checkpoint torch.load(checkpoint.pth) net.load_state_dict(checkpoint[model_state_dict]) trainer.load_state_dict(checkpoint[optimizer_state_dict])这个区分至关重要。毕设答辩时导师问“你的模型怎么部署”你如果说“我把整个net.pkl发过去”就暴露了对生产环境的无知而回答“我导出state_dict对方用相同nn.Sequential结构加载即可”则显示出工程素养。最后自定义层5.4节教你如何把业务逻辑模块化。比如一个文本分类任务需要先做词嵌入、再做 LSTM、最后接分类头你可以写class TextClassifier(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim) # 可训练层 self.lstm nn.LSTM(embed_dim, hidden_dim, batch_firstTrue) self.classifier nn.Linear(hidden_dim, num_classes) def forward(self, x): # x shape: (batch, seq_len) x self.embedding(x) # - (batch, seq_len, embed_dim) _, (h_n, _) self.lstm(x) # h_n shape: (1, batch, hidden_dim) return self.classifier(h_n.squeeze(0)) # - (batch, num_classes) # 使用时只需一行 model TextClassifier(vocab_size10000, embed_dim100, hidden_dim128, num_classes2)这种写法让“模型”不再是nn.Sequential的堆砌而是一个有明确输入输出、可单元测试、可版本管理的组件——这正是工业级代码和课设代码的本质分水岭。3. 关键模块实现从线性回归到 CNN手把手写出可运行、可调试的 PyTorch 代码这一章不讲“应该怎么做”而是聚焦“你马上就能抄的代码”每一段都附带我在带项目时反复验证过的参数说明和避坑点。我们从最基础的线性回归开始一路走到能处理图像的 CNN所有代码均来自书中对应章节的 PyTorch 实现并针对常见翻车点做了强化注释。3.1 线性回归从生成数据到训练收敛四步闭环验证线性回归3.1–3.3节是全书的第一个完整闭环它用最简单的模型教会你最复杂的工程思维。下面这段代码是我给某高校毕设小组梳理的标准流程它确保你能在 10 分钟内看到 loss 下降import torch from torch.utils import data import numpy as np # 3.2.1 生成数据集y 2*x1 - 3.4*x2 4.2 noise n_train, n_test 1000, 100 true_w torch.tensor([2.0, -3.4]) true_b 4.2 features torch.randn((n_train n_test, 2)) labels torch.matmul(features, true_w) true_b labels torch.normal(0, 0.01, labels.shape) # 添加高斯噪声 # 3.2.2 读取数据集使用 DataLoader 实现小批量 def load_array(data_arrays, batch_size, is_trainTrue): dataset data.TensorDataset(*data_arrays) return data.DataLoader(dataset, batch_size, shuffleis_train) batch_size 10 train_iter load_array((features[:n_train], labels[:n_train]), batch_size) test_iter load_array((features[n_train:], labels[n_train:]), batch_size, False) # 3.2.3 初始化参数W 和 b 必须是 requires_gradTrue w torch.normal(0, 0.01, size(2, 1), requires_gradTrue) b torch.zeros(1, requires_gradTrue) # 3.2.4 定义模型纯张量运算 def linreg(X, w, b): return torch.matmul(X, w) b # 3.2.5 定义损失函数均方误差MSE def squared_loss(y_hat, y): return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2 # 3.2.6 定义优化算法手动 SGD理解梯度更新本质 def sgd(params, lr, batch_size): with torch.no_grad(): # 关闭梯度计算避免污染 for param in params: param - lr * param.grad / batch_size # 注意除以 batch_size param.grad.zero_() # 清零梯度否则累积 # 3.2.7 训练循环关键指标监控 lr 0.03 num_epochs 10 net linreg loss squared_loss for epoch in range(num_epochs): for X, y in train_iter: l loss(net(X, w, b), y) # 前向传播 l.sum().backward() # 反向传播注意l 是 (batch,)需 sum sgd([w, b], lr, batch_size) # 更新参数 with torch.no_grad(): train_l loss(net(features[:n_train], w, b), labels[:n_train]) print(fepoch {epoch 1}, loss {float(train_l.mean()):.4f})参数说明与避坑lr 0.03是经验值太大导致 loss 震荡太小收敛极慢param.grad / batch_size是关键因为squared_loss计算的是每个样本的 lossl.sum().backward()得到的是 batch 总梯度必须除以batch_size才能得到平均梯度param.grad.zero_()必须在每次backward()后立即执行否则梯度会累加导致爆炸l.sum().backward()中的.sum()不可省略因为l是(batch_size,)张量backward()要求标量输出。3.2 Softmax 回归MNIST 图像分类实战打通数据加载到评估全流程Softmax 回归3.4–3.7节是第一个处理真实数据的任务。书中用 MNIST手写数字作为载体完整展示了从数据下载、预处理、模型训练到精度评估的工业级流程。以下代码整合了 3.5、3.6、3.7 节的核心可直接运行import torch from torch import nn from torch.utils import data from torchvision import transforms import torchvision # 3.5.1 读取数据集使用 torchvision 自带的 MNIST trans transforms.Compose([ transforms.ToTensor(), # 必须第一将 PIL.Image 转为 [0,1] 的 float32 tensor transforms.Normalize((0.1307,), (0.3081,)) # 第二用 MNIST 统计均值/标准差归一化 ]) mnist_train torchvision.datasets.MNIST( root../data, trainTrue, transformtrans, downloadTrue) mnist_test torchvision.datasets.MNIST( root../data, trainFalse, transformtrans, downloadTrue) # 3.5.2 读取小批量batch_size256 是平衡速度与内存的经验值 train_iter data.DataLoader(mnist_train, 256, shuffleTrue) test_iter data.DataLoader(mnist_test, 256, shuffleFalse) # 3.7.1 定义模型Flatten Linear784-10 net nn.Sequential(nn.Flatten(), nn.Linear(784, 10)) # 3.7.2 初始化nn.Linear 默认使用 Kaiming 初始化已足够好 # 3.7.3 优化器SGD weight_decay 防过拟合 trainer torch.optim.SGD(net.parameters(), lr0.1, weight_decay0.001) # 3.7.4 损失函数CrossEntropyLoss 自动包含 softmax 和 log loss nn.CrossEntropyLoss(reductionnone) # none 保留每个样本 loss便于分析 # 训练函数封装了 3.7.4 的逻辑 def train_epoch_ch3(net, train_iter, loss, updater): metric Accumulator(3) # 累加器正确预测数、预测总数、loss总和 for X, y in train_iter: y_hat net(X) l loss(y_hat, y) updater.zero_grad() l.mean().backward() # mean() 将 batch loss 转为标量 updater.step() metric.add(float(l.sum()), accuracy(y_hat, y), y.numel()) return metric[0] / metric[1], metric[2] / metric[1] # 评估函数3.7.4 的 test 部分 def evaluate_accuracy(net, data_iter): metric Accumulator(2) for X, y in data_iter: metric.add(accuracy(net(X), y), y.numel()) return metric[0] / metric[1] # Accumulator 类书中 3.2.7 提到用于精确统计 class Accumulator: def __init__(self, n): self.data [0.0] * n def add(self, *args): self.data [a float(b) for a, b in zip(self.data, args)] def reset(self): self.data [0.0] * len(self.data) def __getitem__(self, idx): return self.data[idx] # 开始训练10 epoch 足够看到效果 num_epochs 10 for epoch in range(num_epochs): train_metrics train_epoch_ch3(net, train_iter, loss, trainer) test_acc evaluate_accuracy(net, test_iter) print(fepoch {epoch 1}, loss {train_metrics[1]:.3f}, train acc {train_metrics[0]:.3f}, test acc {test_acc:.3f})关键设计点transforms.ToTensor()必须在Normalize之前否则Normalize会对uint8整数操作导致错误nn.CrossEntropyLoss(reductionnone)的none模式让你能计算每个样本的 loss这对分析难样本如经常被错分的数字“4”和“9”至关重要Accumulator类避免了len(train_iter)这种陷阱——因为DataLoader的最后一个 batch 可能不足batch_size用metric[0]/metric[1]才是真正的准确率。3.3 卷积神经网络从nn.Conv2d参数解析到完整 CNN 架构实现第6章是计算机视觉的基石。书中不直接给你一个ResNet而是从最原始的nn.Conv2d参数讲起让你亲手搭出能跑通的 CNN。以下代码基于 6.1–6.3 节实现了一个三层卷积网络LeNet-5 简化版并详细解释每个参数的物理意义import torch from torch import nn # 6.1.1 理解 nn.Conv2d 参数核心 # 输入X.shape (N, C_in, H_in, W_in) (1, 1, 28, 28) for MNIST # 输出Y.shape (N, C_out, H_out, W_out) # 其中 H_out floor((H_in 2*padding - kernel_size) / stride) 1 # W_out floor((W_in 2*padding - kernel_size) / stride) 1 # 构建 LeNet-5 简化版6.2节 class LeNet(nn.Module): def __init__(self, num_classes10): super().__init__() # 第一层卷积1-6 通道kernel 5x5padding2 保证 H,W 不变 self.conv1 nn.Conv2d(1, 6, kernel_size5, padding2) # out: (1,6,28,28) self.relu1 nn.ReLU() self.pool1 nn.AvgPool2d(kernel_size2, stride2) # out: (1,6,14,14) # 第二层卷积6-16 通道kernel 5x5无 padding self.conv2 nn.Conv2d(6, 16, kernel_size5) # out: (1,16,10,10) self.relu2 nn.ReLU() self.pool2 nn.AvgPool2d(kernel_size2, stride2) # out: (1,16,5,5) # 全连接层16*5*5400 输入120-84-10 输出 self.fc1 nn.Linear(16 * 5 * 5, 120) self.relu3 nn.ReLU() self.fc2 nn.Linear(120, 84) self.relu4 nn.ReLU() self.fc3 nn.Linear(84, num_classes) def forward(self, x): x self.pool1(self.relu1(self.conv1(x))) x self.pool2(self.relu2(self.conv2(x))) x torch.flatten(x, 1) # 压平 channel 后的维度(N, 16*5*5) x self.relu3(self.fc1(x)) x self.relu4(self.fc2(x)) x self.fc3(x) return x # 实例化并验证 shape net LeNet() X torch.randn(1, 1, 28, 28) for layer in [net.conv1, net.relu1, net.pool1, net.conv2, net.relu2, net.pool2]: X layer(X) print(f{layer.__class__.__name__}: {X.shape}) # 输出Conv2d: torch.Size([1, 6, 28, 28]) → ReLU: same → AvgPool2d: [1, 6, 14, 14] → ... # 最终 flatten 后torch.Size([1, 400])这段代码的精华在于forward函数中的torch.flatten(x, 1)。flatten(1)表示从第 1 维channel 维开始压平保留第 0 维batch 维这确保了无论x的 spatial shape 是(16,5,5)还是(16,4,4)都能正确变为(N, 400)输入全连接层。这是新手最容易写错的地方——写成x.view(x.shape[0], -1)虽然也能工作但flatten更语义化且在x有额外维度时更鲁棒。书中 6.3 节还专门对比了nn.MaxPool2d和nn.AvgPool2d对特征图的影响指出在浅层 CNN 中AvgPool2d能更好保留背景信息这对 MNIST 这类简单任务更友好。4. 常见问题排查那些让毕设答辩前夜崩溃的 5 个高频翻车点这一章不是泛泛而谈“注意细节”而是列出我在指导 20 个毕设/课设项目时学生踩得最多、最痛、最耽误进度的 5 个具体问题。每个问题都按“现象→原因→解决”展开确保你看到就能对号入座、立刻修复。4.1 现象训练 loss 不下降甚至震荡或爆炸原因学习率设置错误或梯度未归一化解决用学习率范围测试LR Finder并检查梯度尺度这是最经典的翻车现场。现象是loss从2.3开始10 个 epoch 后还是2.2或者忽高忽低如1.8→3.1→1.5→4.0。根本原因往往不是模型不行而是学习率lr太大或太小。lr0.001对 Adam 可能合适但对 SGD 就太小lr0.1对线性回归可能爆炸但对 ResNet 却是起点。书中 11.6 节虽未在输入目录中列出但 Release 2.0.0 包含介绍了学习率范围测试LR Finder# 简化版 LR Finder基于 11.6 节思想 def find_lr(net, train_iter, loss_fn, optimizer, init_lr1e-6, final_lr10, num_iter100): lr_mult (final_lr / init_lr) ** (1 / num_iter) lr init_lr optimizer.param_groups[0][lr] lr lrs, losses [], [] for i, (X, y) in enumerate(train_iter): if i num_iter: break y_hat net(X) l loss_fn(y_hat, y).mean() l.backward() optimizer.step() optimizer.zero_grad() lrs.append(lr) losses.append(l.item()) lr * lr_mult optimizer.param_groups[0][lr] lr return lrs, losses # 使用画出 losses vs lrs 曲线选择 loss 下降最快且未震荡的 lr lrs, losses find_lr(net, train_iter, loss, trainer) plt.plot(lrs, losses) plt.xscale(log) plt.xlabel(Learning Rate) plt.ylabel(Loss) plt.show()血泪经验我带过的一个 NLP 毕设同学用lr0.01训练 LSTMloss 一直不降。用 LR Finder 画图后发现loss在lr0.0003附近开始陡降到0.001达到最低点之后又上升——他立刻改用lr0.00083 个 epoch 后 loss 就降到0.4。另一个原因是梯度未归一化当batch_size1时l.mean().backward()和l.sum().backward()差 1 倍当batch_size32时差 32 倍。书中 3.2.6 节的sgd函数明确写了/ batch_size这就是为什么“手动实现”比“调库”更能暴露问题。4.2 现象RuntimeError: Expected all tensors to be on the same device原因张量或模型未统一迁移到 GPU解决全局设备管理 断言检查这个报错几乎出现在每个第一次用 GPU 的人身上。现象是代码在 CPU 上跑得好好的加了cuda()就崩。原因很具体X.cuda()了但W还在 CPU或者net.cuda()了但X还在 CPU。书中 5.6.2 节强调所有参与计算的 tensor 和 model 必须在同一 device。解决方案不是到处写.cuda()而是建立全局设备变量并用断言检查p a hrefhttps://download.csdn.net/download/qq_43361801/91475678 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p