资讯动态

动手学深度学习2.0 PyTorch版:从环境搭建到模型训练实战指南

发布时间:2026/10/15 23:12:01 来源:尧图企业网站定制
简介《动手学深度学习2.0-李沐Pytorch版》是一本面向深度学习初学者与有一定经验研发人员的实践型教材要求读者具备Python编程基础。全书围绕PyTorch框架展开从线性代数、微积分、概率与自动微分等预备知识讲起逐步深入到线性回归、卷积神经网络、循环神经网络与注意力机制等现代深度学习技术并延伸至计算机视觉、自然语言处理等应用场景同时涉及监督学习、非监督学习与强化学习的差异。资源包内共1个PDF文件大小约26.65MB内容为Release 2.0.0正式版目录结构完整涵盖数据操作、数据预处理、线性代数、微积分、自动微分等章节便于按知识模块系统学习。目前已有778人学习下载。读者可借助书中大量可运行的代码示例动手理解神经网络结构、训练方法与模型实现流程并掌握用PyTorch搭建与调试深度学习模型的核心技能适合作为入门到进阶的案头参考。1. 动手学深度学习2.0为什么“跑通”比“看懂”值钱十倍如果你正在搜《动手学深度学习2.0-李沐Pytorch版》大概率不是想找一本“讲得深”的教材而是想找一条能真正把深度学习跑起来的路。我见过太多人卡在同一个地方公式能看懂论文能读个大概但一打开编辑器就不知道从哪下手。这个标题对应的内容核心价值恰恰不在“讲”而在“动手”——它把每个概念都配了可运行的 PyTorch 代码让你在调试器里看张量怎么变、梯度怎么传、损失怎么降。它适合三类人刚学完 Python 想进深度学习的新手、从 TensorFlow 转 PyTorch 的熟手、以及需要快速复现经典模型的一线工程师。不适合只想背概念应付考试的人。我自己的血泪经验是看十遍反向传播公式不如亲手把loss.backward()跑崩一次然后搞清楚为什么grad是None。这篇笔记就按“环境怎么搭、代码怎么跑、参数怎么调、坑在哪”的顺序把这条动手路线拆开讲。2. 环境搭建与最小可跑闭环先把第一个张量跑起来2.1 为什么选 PyTorch 而不是继续用静态图这个标题里“Pytorch版”四个字是关键。深度学习框架选型直接决定你调试的体验。静态图框架比如早期 TensorFlow 1.x需要先定义完整计算图再运行调试时你看到的是一个黑匣子报错信息经常指向图构建阶段而不是真正的计算阶段。PyTorch 的动态图机制让计算图在运行时才构建你可以像写普通 Python 一样逐行执行、逐行打印。我一般会这样判断如果你需要频繁改模型结构、看中间变量、做研究型实验PyTorch 的动态图省下来的调试时间远超它的性能开销。如果你是要部署一个固定结构到生产环境静态图在推理优化上仍有优势。但学习阶段动态图是唯一正确的选择——因为你需要“看见”每一步发生了什么。2.2 用 conda 建一个不污染系统的隔离环境不要直接在系统 Python 里装 PyTorch。我踩过的坑是系统里已经有 numpy 1.xPyTorch 2.x 需要 numpy 2.x装完之后整个环境崩掉连 pip 都用不了。正确做法是用 conda 建独立环境。# 创建名为 d2l 的独立环境指定 Python 3.10 conda create -n d2l python3.10 -y # 激活环境 conda activate d2l # 安装 PyTorchCPU 版本适合没有独显的机器 # 如果有 NVIDIA 显卡去 pytorch.org 查对应 CUDA 版本的安装命令 pip install torch torchvision torchaudio # 安装 d2l 包这是配套代码的辅助库 pip install d2l # 验证安装 python -c import torch; print(torch.__version__); print(torch.cuda.is_available())逻辑说明conda create的-n d2l是环境名python3.10是版本约束-y跳过确认。pip install torch默认装 CPU 版如果你有显卡需要去官网查对应 CUDA 版本的命令不要直接pip install torch然后抱怨cuda.is_available()返回False。最后一行验证会打印版本号和 GPU 是否可用。参数说明Python 版本建议 3.9 到 3.11太新可能没有预编译的 PyTorch 轮子。d2l包是配套的封装了一些绘图和数据加载函数不装的话书里的d2l.plot之类会报错。2.3 跑通第一个线性回归从数据到梯度的完整链路环境好了之后不要急着上 ResNet。先用一个线性回归把“数据→模型→损失→梯度→更新”这条链路走通。下面这段代码可以直接复制运行。import torch # 1. 构造数据y 2x 1 噪声 def synthetic_data(w, b, num_examples): X torch.normal(0, 1, (num_examples, len(w))) # 标准正态分布 y X w b # 矩阵乘法 y torch.normal(0, 0.01, y.shape) # 加噪声 return X, y.reshape((-1, 1)) true_w torch.tensor([2.0]) true_b 1.0 features, labels synthetic_data(true_w, true_b, 1000) # 2. 定义模型和损失 def linreg(X, w, b): return X w b def squared_loss(y_hat, y): return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2 # 3. 初始化参数 w torch.normal(0, 0.01, size(1, 1), requires_gradTrue) b torch.zeros(1, requires_gradTrue) # 4. 训练循环 lr 0.03 batch_size 10 for epoch in range(3): for i in range(0, len(features), batch_size): X_batch features[i:ibatch_size] y_batch labels[i:ibatch_size] loss squared_loss(linreg(X_batch, w, b), y_batch).sum() loss.backward() # 反向传播算梯度 with torch.no_grad(): # 更新时不要记录梯度 w - lr * w.grad b - lr * b.grad w.grad.zero_() # 梯度清零否则会累加 b.grad.zero_() print(fepoch {epoch1}, loss {loss.item():.6f}) print(f估计的 w: {w.detach().numpy().flatten()}, 真实 w: {true_w.numpy()}) print(f估计的 b: {b.detach().numpy()}, 真实 b: {true_b})逻辑说明requires_gradTrue告诉 PyTorch 这个张量需要计算梯度。loss.backward()自动求导梯度存在w.grad和b.grad里。torch.no_grad()包裹更新步骤因为更新参数本身不需要被记录进计算图。zero_()是必须的PyTorch 默认会累加梯度不清零的话第二次迭代的梯度是错的。参数说明lr0.03是学习率太大震荡太小收敛慢。batch_size10是每次喂给模型的样本数。torch.normal(0, 0.01, ...)是参数初始化标准差 0.01 是常见起点。跑完 3 个 epoch估计的 w 应该接近 2.0b 接近 1.0。如果 loss 不降先检查学习率是不是太大再检查zero_()有没有漏。3. 从线性模型到卷积网络把 LeNet 在 Fashion-MNIST 上跑出 85% 准确率3.1 数据加载为什么 num_workers 设 4 而不是 0数据管道是新手最容易忽略的性能瓶颈。DataLoader的num_workers参数决定用几个进程加载数据。设 0 表示在主进程加载训练时你会看到 GPU 利用率忽高忽低因为 GPU 在等数据。设 4 表示开 4 个子进程并行加载GPU 能吃饱。import torch from torch.utils.data import DataLoader from torchvision import datasets, transforms # 定义预处理转张量 归一化 trans transforms.Compose([ transforms.ToTensor(), # PIL 图像转 [0,1] 张量 transforms.Normalize((0.5,), (0.5,)) # 均值 0.5 标准差 0.5 归一化到 [-1,1] ]) # 加载 Fashion-MNIST train_data datasets.FashionMNIST( root./data, trainTrue, downloadTrue, transformtrans) test_data datasets.FashionMNIST( root./data, trainFalse, downloadTrue, transformtrans) # 创建 DataLoader train_loader DataLoader(train_data, batch_size256, shuffleTrue, num_workers4) test_loader DataLoader(test_data, batch_size256, shuffleFalse, num_workers4) # 看一个 batch 的形状 X, y next(iter(train_loader)) print(fX shape: {X.shape}, y shape: {y.shape})逻辑说明transforms.ToTensor()把 PIL 图像转成[C,H,W]格式的浮点张量值域[0,1]。Normalize((0.5,),(0.5,))做(x-0.5)/0.5把值域拉到[-1,1]这对训练稳定性有帮助。shuffleTrue只在训练集开测试集不要打乱。num_workers4在 Linux 上有效Windows 上如果报错就改成 0。参数说明batch_size256是常见起点显存不够就降到 128 或 64。num_workers一般设成 CPU 核心数的一半到全部设太大反而因为进程切换开销变慢。root./data是数据存放路径第一次运行会自动下载。3.2 LeNet 结构每个卷积层后面为什么跟池化LeNet 是卷积网络的“Hello World”。它的结构是卷积→池化→卷积→池化→全连接→全连接→输出。卷积层负责提取局部特征池化层负责降采样和增加平移不变性。import torch.nn as nn net nn.Sequential( nn.Conv2d(1, 6, kernel_size5, padding2), # 输入1通道输出6通道5x5卷积 nn.Sigmoid(), # 早期用 Sigmoid现在多用 ReLU nn.AvgPool2d(kernel_size2, stride2), # 2x2 平均池化步长2 nn.Conv2d(6, 16, kernel_size5), # 6通道进16通道出 nn.Sigmoid(), nn.AvgPool2d(kernel_size2, stride2), nn.Flatten(), # 展平成一维 nn.Linear(16*5*5, 120), # 全连接 nn.Sigmoid(), nn.Linear(120, 84), nn.Sigmoid(), nn.Linear(84, 10) # 输出10类 ) # 验证输入输出形状 X torch.randn(1, 1, 28, 28) print(net(X).shape) # 应该是 torch.Size([1, 10])逻辑说明Conv2d(1,6,5,padding2)中padding2是为了让 28x28 经过 5x5 卷积后还是 28x28282*2-5128。AvgPool2d(2,2)把 28x28 变成 14x14。第二个卷积后 14x14 变 10x10再池化变 5x5。Flatten后是16*5*5400维接全连接。参数说明kernel_size5是 LeNet 原始设计现代网络多用 3x3。Sigmoid在深层网络会梯度消失这里因为只有几层还能用换成ReLU收敛更快。Linear(84,10)输出 10 维对应 10 个类别。3.3 训练循环把准确率从 10% 拉到 85% 的关键设置import torch.optim as optim def evaluate_accuracy(net, data_loader): net.eval() # 切换到评估模式 correct, total 0, 0 with torch.no_grad(): # 评估时不计算梯度 for X, y in data_loader: y_hat net(X) correct (y_hat.argmax(axis1) y).sum().item() total y.numel() return correct / total # 损失函数和优化器 loss_fn nn.CrossEntropyLoss() # 内部包含 softmax optimizer optim.SGD(net.parameters(), lr0.1) # 随机梯度下降 num_epochs 10 for epoch in range(num_epochs): net.train() # 切换回训练模式 for X, y in train_loader: y_hat net(X) loss loss_fn(y_hat, y) optimizer.zero_grad() # 清梯度 loss.backward() # 反向传播 optimizer.step() # 更新参数 test_acc evaluate_accuracy(net, test_loader) print(fepoch {epoch1}, test acc {test_acc:.4f})逻辑说明net.train()和net.eval()切换模式影响 Dropout 和 BatchNorm 的行为。CrossEntropyLoss内部做了 softmax所以网络最后一层不要加 softmax。optimizer.zero_grad()清空上一轮梯度step()执行更新。argmax(axis1)取预测类别。参数说明lr0.1对 SGD 是常见起点太大不收敛太小收敛慢。num_epochs10在 Fashion-MNIST 上足够到 85% 左右。如果准确率卡在 10% 不动检查CrossEntropyLoss的输入是不是 logits没经过 softmax以及标签是不是[0,9]的整数。4. 避坑与排查那些让训练崩掉的参数和写法4.1 现象loss 变成 nan训练直接崩原因学习率太大梯度爆炸。或者数据里有 nan或者log(0)之类的数学运算。解决先把学习率降 10 倍试。然后在loss.backward()后加torch.nn.utils.clip_grad_norm_(net.parameters(), 1.0)做梯度裁剪。再检查数据管道用torch.isnan(X).any()确认输入没有 nan。4.2 现象GPU 利用率只有 20%训练很慢原因数据加载是瓶颈num_workers0导致 GPU 等数据。或者 batch_size 太小每次计算量不够。解决把num_workers设成 4 或 8batch_size加大到显存允许的上限。用nvidia-smi看 GPU 利用率如果一直在 0% 和 100% 之间跳就是数据管道问题。4.3 现象训练准确率很高测试准确率很低原因过拟合。模型记住了训练集但没学到泛化特征。解决加 Dropout 层、加 L2 正则化optimizer的weight_decay参数、做数据增强随机翻转、裁剪、或者直接减小模型容量。我一般先加weight_decay1e-4试不行再上 Dropout。4.4 现象RuntimeError: expected scalar type Float but found Double原因PyTorch 默认张量是 float32但 numpy 默认是 float64。从 numpy 转过来的张量是 double和模型参数类型不匹配。解决用.float()显式转换或者创建张量时指定dtypetorch.float32。这个坑在把 sklearn 数据喂给 PyTorch 时特别常见。4.5 现象显存够但报CUDA out of memory原因不是显存不够是碎片化。或者你在循环里不断创建新张量而没有释放。解决用torch.cuda.empty_cache()清缓存。更根本的是检查有没有在训练循环里累积计算图——比如把loss存进列表而不是.item()这样整个计算图都被保留显存会爆。记住只需要数值就.item()不要保留张量引用。5. 进阶技巧用 torch.utils.data 自定义数据集和混合精度训练5.1 自定义 Dataset把任意格式数据喂进 DataLoader书里的数据集是封装好的但实际工作中你面对的是 CSV、图片文件夹、HDF5。torch.utils.data.Dataset是标准接口只需要实现__len__和__getitem__。from torch.utils.data import Dataset import pandas as pd from PIL import Image class CustomImageDataset(Dataset): def __init__(self, csv_file, img_dir, transformNone): self.labels pd.read_csv(csv_file) # CSV 有两列filename, label self.img_dir img_dir self.transform transform def __len__(self): return len(self.labels) # 返回样本总数 def __getitem__(self, idx): img_path f{self.img_dir}/{self.labels.iloc[idx, 0]} image Image.open(img_path).convert(RGB) label self.labels.iloc[idx, 1] if self.transform: image self.transform(image) return image, label # 使用 dataset CustomImageDataset(labels.csv, ./images, transformtrans) loader DataLoader(dataset, batch_size32, shuffleTrue, num_workers4)逻辑说明__len__返回数据集大小DataLoader用它决定采样范围。__getitem__接收索引返回(图像张量, 标签)。transform在__getitem__里应用这样每个样本被加载时才做增强而不是预先全部处理。参数说明convert(RGB)确保三通道即使原图是灰度。iloc[idx, 0]是 pandas 的按位置索引比loc更适合这里。如果数据量大__getitem__里不要做耗时操作否则num_workers也救不了。5.2 混合精度训练用 AMP 把显存砍半、速度提三成混合精度Automatic Mixed Precision让部分计算用 float16部分用 float32。float16 省显存、计算快但容易溢出。PyTorch 的torch.cuda.amp自动处理缩放你只需要改几行。from torch.cuda.amp import autocast, GradScaler scaler GradScaler() # 梯度缩放器 for epoch in range(num_epochs): net.train() for X, y in train_loader: optimizer.zero_grad() with autocast(): # 自动选择精度 y_hat net(X) loss loss_fn(y_hat, y) scaler.scale(loss).backward() # 缩放损失再反向传播 scaler.step(optimizer) # 自动判断是否跳过更新 scaler.update() # 更新缩放因子逻辑说明autocast()上下文里的运算自动选择 float16 或 float32。scaler.scale(loss)把损失放大防止 float16 下梯度下溢。scaler.step()代替optimizer.step()如果检测到 inf/nan 就跳过这一步。scaler.update()调整缩放因子。参数说明AMP 只在 NVIDIA GPU 上有效CPU 上autocast是空操作。GradScaler的默认初始缩放因子是 65536一般不用改。如果训练不稳定可以试GradScaler(init_scale1024)降低初始值。这个技巧在显存紧张时特别有用我实测能把 batch_size 翻倍。5.3 用 TensorBoard 看训练曲线别靠猜训练时不要只盯着 loss 数字。用 TensorBoard 把 loss、准确率、学习率画出来一眼就能看出是过拟合还是欠拟合。pip install tensorboardfrom torch.utils.tensorboard import SummaryWriter writer SummaryWriter(runs/lenet_experiment) # 日志目录 for epoch in range(num_epochs): # ... 训练代码 ... writer.add_scalar(Loss/train, loss.item(), epoch) writer.add_scalar(Accuracy/test, test_acc, epoch) writer.add_scalar(Learning_rate, optimizer.param_groups[0][lr], epoch) writer.close()然后在终端运行tensorboard --logdirruns浏览器打开提示的地址。逻辑说明add_scalar记录标量第一个参数是标签第二个是值第三个是步数。param_groups[0][lr]取当前学习率如果你用了学习率调度器这条曲线能告诉你调度有没有生效。参数说明runs/lenet_experiment是日志目录不同实验用不同子目录方便对比。writer.close()必须调用否则最后几条日志可能没写进去。我习惯在每个 epoch 结束后记录一次不要每步都记否则日志文件会很大。5.4 一个我反复用的习惯先跑 10 个样本过拟合在正式训练之前我一般会拿 10 个样本跑 100 个 epoch看模型能不能把这 10 个样本的 loss 降到接近 0。如果降不下去说明模型结构或训练逻辑有 bug不用浪费时间去跑全量数据。这个“过拟合小样本”的检查能提前暴露 80% 的低级错误比如标签对不上、损失函数用错、梯度没回传。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑