
简介这份资源是《Deep Learning with PyTorch》的官方样本PDF面向希望入门PyTorch框架、掌握深度学习项目实践的开发者与学习者尤其适合具备一定Python基础、想通过动手示例理解模型训练全流程的读者。压缩包内仅含1个PDF文件大小约399KB内容为英文原版样章涵盖训练循环构建、训练统计信息收集、tqdm进度报告、DataLoader与Dataset使用以及一个完整的回归模型项目包括数据集描述、模型搭建、训练与数据预处理优化等关键环节。目前已有138人学习下载可作为快速了解该书结构、判断是否深入学习的参考。通过这份样本读者能提前接触PyTorch动态计算图、GPU加速张量运算与神经网络API等核心概念并借助回归项目示例理解从数据准备到模型改进的完整思路为后续系统学习打下基础。1. 一份只有 32 页的 PyTorch 样章为什么值得先啃透拿到deep-learning-with-pytorch-sample.pdf的时候我第一反应是「就这」——32 页目录占了两页免责声明和版权页又占一页真正能看的正文其实集中在三个章节第 8 章「Creating a Training Loop for Your Models」、第 12 章「Project: Building a Regression Model in PyTorch」、第 18 章「Training a PyTorch Model with DataLoader and Dataset」。但恰恰是这三章把 PyTorch 入门最容易翻车的三个环节全踩了一遍训练循环怎么写才不丢梯度、回归项目怎么从裸模型调到能看、DataLoader 和 Dataset 怎么配合才不拖慢 GPU。这份样章不是教程全集它更像一份「路线图切片」。完整版覆盖了从张量操作、Autograd、多层感知机到 CNN、LSTM、超参搜索、模型可视化等一整套内容而样章只抽了其中三段。对已经能跑通nn.Linear但一写训练循环就心里没底的人来说这三段正好补上「从能跑到跑对」的缺口。它适合两类人一是刚学完 PyTorch 基础 API、准备做第一个端到端项目的新手二是写过几轮训练脚本、但每次都要回头翻旧代码的熟手——样章里的训练循环模板和 DataLoader 用法可以直接当脚手架用。2. 训练循环的骨架从零写一个不丢梯度的 loop2.1 为什么zero_grad必须放在backward之前样章第 8 章开篇就点了一件事训练一个深度学习模型核心元素只有四个——前向传播、计算损失、反向传播、更新参数。听起来像废话但真正动手写的时候顺序错一步模型要么不收敛要么收敛到一半突然炸掉。最常见的翻车现场是zero_grad的位置。PyTorch 的梯度是累加的默认不会自动清零。如果你把optimizer.zero_grad()写在loss.backward()后面那第一次迭代没问题第二次迭代的梯度就会叠加上第一次的残留等效于把学习率悄悄放大了一倍。短跑几轮看不出来跑上几十个 epoch 后 loss 曲线会突然抖成锯齿。我一般会固定成这个顺序# 训练循环的标准骨架 for epoch in range(num_epochs): model.train() # 切换到训练模式启用 dropout / BN for batch_idx, (features, targets) in enumerate(train_loader): features features.to(device) targets targets.to(device) optimizer.zero_grad() # 1. 清空上一轮梯度 outputs model(features) # 2. 前向传播 loss criterion(outputs, targets) # 3. 计算损失 loss.backward() # 4. 反向传播梯度写入 param.grad optimizer.step() # 5. 按梯度更新参数逻辑说明zero_grad放在最前面保证每次backward写入的梯度都是干净的。model.train()不是可有可无的装饰如果你的网络里有Dropout或BatchNorm不切模式会让推理行为混进训练验证集指标会莫名其妙地偏低。参数说明device建议在循环外定义一次比如device torch.device(cuda if torch.cuda.is_available() else cpu)循环内只做.to(device)避免每次迭代都重新判断。batch_idx如果不用可以写成_但保留它方便后面接 tqdm 或手动打印进度。2.2 用 tqdm 报告进度但别让它拖慢训练样章专门用了一节讲 tqdm这个选择很实在。训练循环最难受的不是写代码是等结果——尤其是 CPU 上跑一个稍大的全连接网络几分钟没有任何输出你会怀疑是不是死循环了。tqdm 解决的就是这个「黑匣子」问题。但 tqdm 用不好会反过来拖慢训练。我见过有人在每个 batch 里更新一次 tqdm 的set_postfix还把当前 loss 转成 Python float 再格式化结果 GPU 利用率从 90% 掉到 60%。原因是频繁的字符串格式化和终端刷新会阻塞主线程。比较稳的写法是外层用tqdm包住 epoch内层只在每 N 个 batch 更新一次 postfix。from tqdm import tqdm for epoch in tqdm(range(num_epochs), descEpoch): model.train() running_loss 0.0 for batch_idx, (features, targets) in enumerate(train_loader): features, targets features.to(device), targets.to(device) optimizer.zero_grad() outputs model(features) loss criterion(outputs, targets) loss.backward() optimizer.step() running_loss loss.item() # 每 50 个 batch 刷新一次显示减少终端 IO if batch_idx % 50 0: tqdm.write(fEpoch {epoch} | Batch {batch_idx} | Loss {running_loss/(batch_idx1):.4f})逻辑说明loss.item()把单元素张量转成 Python 浮点数避免把计算图带进累加变量导致显存泄漏。tqdm.write比直接print更安全它不会打断进度条的渲染。参数说明batch_idx % 50里的 50 不是固定值如果你的数据集小、batch 少可以改成 10如果 batch 上千改成 100 或 200。原则是让终端每秒刷新不超过两次。提示如果你在 Jupyter Notebook 里跑tqdm 的tqdm.notebook子模块比标准版更稳但样章里的代码是脚本风格直接from tqdm import tqdm就行。3. 回归项目实战从裸模型到加预处理的完整链路3.1 数据集加载与模型搭建的最小闭环样章第 12 章给了一个回归项目的完整流程描述数据集、搭模型、训练、然后用预处理改善效果。虽然样章没贴出完整数据但从上下文看这是一个典型的表格回归任务——特征列是数值型目标列是连续值。这类任务在工业场景里比图像分类更常见但网上教程反而少所以这一章的价值被低估了。我按样章的思路补一个可复现的最小闭环。假设数据已经读进一个pandas.DataFrame特征列叫feat_1到feat_n目标列叫target。import torch import torch.nn as nn import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 1. 读数据并划分 df pd.read_csv(your_regression_data.csv) X df.drop(columns[target]).values.astype(float32) y df[target].values.astype(float32).reshape(-1, 1) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) # 2. 标准化回归任务里这一步比模型结构更影响结果 scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test) # 注意测试集只能用 train 的统计量 # 3. 转成张量 X_train torch.tensor(X_train, dtypetorch.float32) y_train torch.tensor(y_train, dtypetorch.float32) X_test torch.tensor(X_test, dtypetorch.float32) y_test torch.tensor(y_test, dtypetorch.float32)逻辑说明StandardScaler的fit_transform只在训练集上调用测试集用transform。这是回归项目里最容易犯的错之一——如果测试集也fit_transform等于把测试集的均值方差泄漏进了训练过程评估指标会虚高。参数说明astype(float32)是必须的PyTorch 默认吃 float32如果传 float64 会在第一次前向传播时报 dtype 不匹配。reshape(-1, 1)把目标从一维转成二维因为nn.Linear的输出是(batch, 1)不 reshape 会导致广播错误。模型部分样章用的是全连接网络。我一般从两层隐藏层起步class RegressionNet(nn.Module): def __init__(self, input_dim): super().__init__() self.net nn.Sequential( nn.Linear(input_dim, 64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 1) # 回归输出 1 维不加激活 ) def forward(self, x): return self.net(x) model RegressionNet(input_dimX_train.shape[1]) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3)逻辑说明回归任务的输出层不能加Sigmoid或Softmax否则输出被限制在固定区间遇到目标值超出范围的数据直接学不动。MSELoss是最常用的回归损失如果目标有异常值可以换成nn.L1Loss或nn.SmoothL1Loss。参数说明lr1e-3是 Adam 的常用起点如果 loss 下降太慢可以调到3e-3如果震荡就降到1e-4。隐藏层维度 64 和 32 不是金标准特征维度高的时候第一层可以设成 128 或 256。3.2 预处理到底改善了什么一个可对比的实验样章在「Improving the Model with Preprocessing」一节里强调预处理能提升效果但没展开说提升在哪。我补一个对比实验的设计你可以直接套。配置特征处理目标处理预期现象基线无无loss 下降慢容易卡在某个值仅特征标准化StandardScaler无收敛加快但最终精度提升有限特征目标标准化StandardScalerStandardScaler收敛快且稳定MSE 显著下降特征标准化目标对数变换StandardScalerlog1p目标偏态严重时效果最好目标标准化容易被忽略。如果目标值范围是 0 到 100000而特征标准化后范围是 -3 到 3MSE 的梯度会被大数值主导模型前期基本在拟合均值。把目标也标准化后梯度尺度一致收敛速度会明显不同。# 目标标准化训练集 fit测试集 transform预测后要逆变换 y_scaler StandardScaler() y_train_scaled y_scaler.fit_transform(y_train) y_test_scaled y_scaler.transform(y_test) # 训练完成后评估时把预测值逆变换回原始尺度 model.eval() with torch.no_grad(): pred_scaled model(X_test) pred y_scaler.inverse_transform(pred_scaled.numpy()) mse ((pred - y_test.numpy()) ** 2).mean() print(fTest MSE (original scale): {mse:.4f})逻辑说明inverse_transform是必须的否则你报的 MSE 是标准化尺度下的和业务方理解的「误差多少」对不上。model.eval()和torch.no_grad()一起用前者切换 BN/Dropout 行为后者关闭计算图节省显存。参数说明如果目标有零值或负值log1p不能用得换成np.sign(y) * np.log1p(np.abs(y))这种带符号的变换。逆变换时对应写np.sign(pred) * np.expm1(np.abs(pred))。4. DataLoader 与 Dataset把数据管道从瓶颈里捞出来4.1 为什么你的 GPU 利用率只有 30%样章第 18 章讲 DataLoader 和 Dataset这一章解决的是训练循环之外的问题数据供给。很多人写完训练循环后发现 GPU 利用率上不去第一反应是模型太小其实更常见的原因是 DataLoader 在拖后腿——每个 batch 都要等 CPU 读完文件、做完增强、再拷贝到显存。DataLoader 的核心参数只有四个需要盯batch_size、shuffle、num_workers、pin_memory。from torch.utils.data import Dataset, DataLoader class TabularDataset(Dataset): def __init__(self, features, targets): self.features features self.targets targets def __len__(self): return len(self.features) def __getitem__(self, idx): return self.features[idx], self.targets[idx] train_dataset TabularDataset(X_train, y_train) train_loader DataLoader( train_dataset, batch_size64, shuffleTrue, # 训练集打乱验证集/测试集不打乱 num_workers4, # 子进程数一般设为 CPU 核数的一半 pin_memoryTrue # 锁页内存加速 CPU 到 GPU 的拷贝 )逻辑说明__getitem__返回的是单个样本DataLoader 负责拼成 batch。shuffleTrue只在训练集开验证集和测试集开了会导致评估结果不可复现。pin_memoryTrue在 GPU 训练时几乎总是该开它让数据放在锁页内存里to(device)的拷贝会快一截。参数说明num_workers不是越大越好。设成 0 表示在主进程里读数据调试时用这个设成 4 到 8 是常见区间超过 CPU 物理核数反而会因为进程切换开销变慢。Windows 上num_workers 0需要把训练代码放在if __name__ __main__:里否则会无限递归创建子进程。4.2 Dataset 类的三个易错点样章里 Dataset 的示例很简洁但实际写的时候有三个地方容易翻车。第一__len__返回的是样本数不是 batch 数。有人把len(self.features) // batch_size写进去结果 DataLoader 只遍历了部分数据训练集越跑越少。第二__getitem__里不要做太重的操作。如果每个样本都要读一次磁盘文件num_workers再大也救不了。常见做法是在__init__里把数据全部读进内存或者用lmdb、webdataset这类格式做顺序读。第三返回的张量 dtype 要统一。如果features是 float64、targets是 float32拼 batch 的时候 PyTorch 会报错或者隐式转换隐式转换在 GPU 上会触发同步拖慢速度。class TabularDataset(Dataset): def __init__(self, features, targets): # 在初始化时统一 dtype避免 __getitem__ 里反复转换 self.features torch.as_tensor(features, dtypetorch.float32) self.targets torch.as_tensor(targets, dtypetorch.float32) def __len__(self): return self.features.shape[0] # 明确返回样本数 def __getitem__(self, idx): return self.features[idx], self.targets[idx]逻辑说明torch.as_tensor比torch.tensor更省内存如果输入已经是张量它不会复制。在__init__里做 dtype 统一__getitem__就只剩索引操作子进程的负担最小。参数说明如果数据量特别大__init__里全读进内存不现实可以只存文件路径在__getitem__里读。但这时候num_workers要适当调大并且考虑用prefetch_factorPyTorch 1.7控制每个 worker 预取的 batch 数。注意pin_memoryTrue和num_workers 0同时用时如果自定义 Dataset 返回的是 Python 对象而不是张量锁页内存的优势会消失。确保__getitem__返回的是张量或张量元组。5. 避坑与排查样章没写但一定会遇到的五个问题5.1 loss 变成 nan先查学习率再查数据现象训练几个 batch 后 loss 突然变成nan之后再也降不下来。原因最常见的是学习率太大导致梯度爆炸其次是输入数据里有inf或nan前向传播直接污染整个 batch。解决先把学习率降一个数量级试一轮。如果还不行在__getitem__或数据加载后加一句assert not torch.isnan(features).any()定位到具体是哪个样本的问题。回归任务里目标值如果有极端异常值MSE 损失会产生巨大梯度可以考虑先做目标截断或换SmoothL1Loss。5.2 验证集 loss 比训练集低不一定是好事现象训练集 loss 0.5验证集 loss 0.3看起来模型泛化很好。原因如果验证集没开shuffle而训练集开了且验证集样本分布更集中就会出现这种「假象」。另一种可能是 Dropout 在训练时拉高了训练 loss而验证时关闭了 Dropout。解决先确认验证集的shuffleFalse并且model.eval()在验证前调用了。如果两者都正确再检查验证集是不是太小——小验证集的 loss 波动本来就大单次结果说明不了问题。5.3 GPU 显存够但报 OOM计算图没释放现象显存明明有 8G模型只有几百万参数但跑几个 batch 就 OOM。原因在训练循环里把loss或outputs累加进了 Python 列表计算图一直挂在显存里没释放。解决累加 loss 用loss.item()不要用loss本身。如果需要在训练中途做验证验证部分包在with torch.no_grad():里并且验证完把中间变量显式del掉。5.4 DataLoader 在 Windows 上卡死现象num_workers 0时程序卡在第一个 batch没有任何报错。原因Windows 的进程启动方式是spawn子进程会重新导入主模块如果训练代码不在if __name__ __main__:保护下就会无限递归。解决把训练入口包进if __name__ __main__:或者临时把num_workers设成 0 确认是不是这个问题。5.5 模型保存后加载结果不一致现象训练完保存state_dict重新加载后预测结果和保存前对不上。原因保存时模型在train()模式加载后没有调eval()Dropout 和 BatchNorm 的行为不一致。解决加载后固定加model.eval()。如果保存的是整个模型torch.save(model)而不是state_dict还要注意类定义必须和保存时完全一致否则反序列化会失败。我一般只存state_dict加载时先实例化模型再load_state_dict。6. 把样章当脚手架三个可以直接搬进项目的技巧样章只有 32 页但它给的三个章节恰好覆盖了 PyTorch 项目里最常复用的三段代码。我的习惯是把它们拆成三个独立文件train_loop.py、dataset.py、regression_template.py新项目直接复制改参数。第一个技巧是训练循环的「最小可复现版本」。不要一上来就加早停、检查点、学习率调度先把zero_grad - forward - loss - backward - step这五步跑通确认 loss 在降再加其他功能。样章第 8 章的代码就是这个最小版本我每次开新项目都会先跑一遍它确认环境和数据管道没问题。第二个技巧是 DataLoader 的参数组合。我整理了一个速查表按场景直接选场景batch_sizenum_workerspin_memoryshuffleCPU 调试160FalseTrue单 GPU 表格数据644TrueTrue单 GPU 图像数据328TrueTrue多 GPU按显存调8TrueTrue验证/测试1284TrueFalse第三个技巧是回归项目的评估闭环。样章只讲到训练但实际交付时业务方要的是原始尺度上的误差。我习惯在训练脚本最后加一段def evaluate(model, X_test, y_test, y_scalerNone): model.eval() with torch.no_grad(): pred model(X_test) if y_scaler is not None: pred y_scaler.inverse_transform(pred.numpy()) y_true y_scaler.inverse_transform(y_test.numpy()) else: pred pred.numpy() y_true y_test.numpy() mae abs(pred - y_true).mean() rmse ((pred - y_true) ** 2).mean() ** 0.5 return {MAE: mae, RMSE: rmse}逻辑说明inverse_transform把预测和目标都还原到原始尺度算出来的 MAE 和 RMSE 才能直接写进报告。如果目标做过对数变换逆变换要用对应的expm1。参数说明y_scaler为None时表示没做目标标准化直接输出原始预测。这个函数不依赖训练时的任何中间变量可以单独导入到评估脚本里用。从那以后我每次拿到新的 PyTorch 项目都会先找一份类似的样章或最小示例把训练循环、DataLoader、评估这三段跑通再往里填业务逻辑。这份deep-learning-with-pytorch-sample.pdf的价值不在于它讲了多少而在于它把最容易被跳过的基础环节单独拎了出来。希望帮到你。本文还有配套的精品资源点击获取