资讯动态

PyTorch深度学习教程配套源码:CNN/RNN/GAN训练循环与排错

发布时间:2026/10/25 10:49:30 来源:尧图企业网站定制
简介面向深度学习初学者与希望系统掌握 PyTorch 实战的开发者这份视频课程配套资料提供可对照运行的源代码、课件与实验素材帮助读者从环境配置、模型搭建到训练调参逐步落地内容覆盖卷积神经网络、循环神经网络、生成对抗网络与图卷积等典型主题。压缩包共 142 个文件约 85.9MB包含 55 个 PDF 课件、39 个 Python 脚本并配有 PNG 图示、Notebook 交互笔记、CSV 数据以及 XML、CFG、TXT 等工程与说明文件便于按章节查阅代码、复现实验和整理学习笔记。资源中附有 Anaconda、CUDA 10.0 与国内镜像源配置说明可减少环境准备阶段的阻碍代码与课件相互对应适合跟课练习、对照调试和课后复盘。目前已有 2679 人学习下载适合需要补齐工程细节、查阅课件要点或系统梳理 PyTorch 建模流程的读者使用。1. 一份配套源码能省掉多少折腾PyTorch 深度学习教程的实战定位很多人学 PyTorch 的第一道坎来自脚本跑不起来而非反向传播公式本身。这份 PyTorch 深度学习教程的配套源码和 PPT把入门实战里讲到的模型都落成了可运行的脚本覆盖卷积神经网络、循环神经网络和生成对抗网络几条主线幻灯片还能当复习提纲。它适合刚学完 Python 和线性代数、想跑通第一个训练循环的人也适合带课的人直接拿去课堂演示。网上不少教程的代码和视频对不上号这份资源最大的价值是代码目录和课程章节一一对应看到哪一节就翻到对应目录跑一遍出问题也能定位到具体那几行。下面按工程骨架、核心模块、踩坑记录、二次开发四个层次拆开讲。2. 工程骨架与技术栈先看清目录再动手拿到一份教程代码最怕上来就python train.py报错一屏接一屏连从哪查都不知道。比较稳妥的顺序是先看依赖文件、再看目录分层、最后确认数据往哪放这三步走完后面跑脚本心里才有底。2.1 依赖环境与目录布局这套 PyTorch 教程代码的主线依赖不复杂Python 3.8 以上、PyTorch 本体、torchvision 负责数据集和图像变换、numpy 和 matplotlib 做数值与可视化、tqdm 看进度条处理图片时再加 pillow。我一般会先建独立虚拟环境避免和系统里已有的包版本打架尤其是 CUDA 版本这块装错一次够折腾半天。# 新建独立环境避免污染系统 Python python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate # 先装 PyTorch 和 torchvision版本按显卡驱动选 pip install torch torchvision # 再补教程里常用的辅助库 pip install numpy matplotlib tqdm pillow安装命令本身没什么玄学关键在于torch和torchvision的版本要配套。CUDA 11.8 的驱动配了 CPU 版 torch跑起来不报错但慢得让人怀疑人生torch.cuda.is_available()一眼就能验证。装完先跑一句python -c import torch; print(torch.__version__, torch.cuda.is_available())输出版本号和True再往下走。目录结构通常是按课程章节切的方便对照视频找代码pytorch-tutorial-code/ ├── chapter02_linear/ # 线性回归、softmax 回归 ├── chapter05_cnn/ # 卷积神经网络图像分类 ├── chapter07_rnn/ # 循环神经网络序列建模 ├── chapter09_gan/ # 生成对抗网络 ├── utils/ # 公共工具随机种子、日志、可视化 ├── data/ # 数据集存放目录 └── requirements.txtdata/目录一般不放仓库里脚本第一次运行时会自动下载数据集比如 CIFAR-10、MNIST 这类公开数据。utils/里通常藏着set_seed、AverageMeter、save_checkpoint这类小工具读代码时先扫一眼这里后面看训练脚本会省很多猜测。2.2 训练脚本的通用骨架教程里每一章的入口脚本长得都差不多解析参数、建模型、准备数据、写训练循环、跑验证、存权重。把这套骨架吃透换数据集、换网络结构都只是替换其中一两个函数。下面这段是精简后的通用结构实际代码会更长但主干就是这么几块。import argparse import torch from torch import nn from torch.utils.data import DataLoader def build_model(cfg): 按配置返回模型方便后续替换网络结构 if cfg.arch cnn: from models.cnn import SimpleCNN return SimpleCNN(num_classescfg.num_classes) raise ValueError(funknown arch: {cfg.arch}) def train_one_epoch(model, loader, criterion, optimizer, device): model.train() # 切到训练模式影响 Dropout/BatchNorm total_loss, total_num 0.0, 0 for x, y in loader: x, y x.to(device, non_blockingTrue), y.to(device, non_blockingTrue) optimizer.zero_grad() # 清空上一轮梯度漏了会累加 logits model(x) loss criterion(logits, y) loss.backward() optimizer.step() total_loss loss.item() * x.size(0) # 按样本数加权避免最后一个 batch 偏小 total_num x.size(0) return total_loss / total_num def main(): parser argparse.ArgumentParser() parser.add_argument(--arch, defaultcnn) parser.add_argument(--batch-size, typeint, default128) parser.add_argument(--epochs, typeint, default50) parser.add_argument(--lr, typefloat, default1e-3) parser.add_argument(--device, defaultcuda if torch.cuda.is_available() else cpu) cfg parser.parse_args() device torch.device(cfg.device) model build_model(cfg).to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lrcfg.lr) # 数据加载和训练循环在此展开几个参数值得单独说。loss.item()会从计算图里取出标量但只取当前 batch 的平均损失乘上x.size(0)才是这个 batch 的总损失最后除以总样本数得到的才是整个 epoch 的真实均值。non_blockingTrue只有在pin_memoryTrue的 DataLoader 配 GPU 时才有意义CPU 训练时可以去掉。argparse里把--device做成可覆盖的是为了在 GPU 被占满时能临时退回 CPU 调试不至于改代码。2.3 随机种子与可复现性教程代码跑出来的结果每次不一样很多人第一反应是模型写错了。深度学习本身有随机性数据打乱、权重初始化、Dropout 都会引入噪声但做对比实验时至少要保证同一份代码两次运行结果接近。惯常做法是在入口脚本最前面固定随机种子。import random import numpy as np import torch def set_seed(seed: int 42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) # 多卡时也要固定 torch.backends.cudnn.deterministic True # 卷积算法可复现 torch.backends.cudnn.benchmark False # 关闭自动调优换确定性deterministicTrue会让 cuDNN 只选确定性算法代价是部分卷积实现变慢benchmarkFalse同理关掉自动选最快卷积算法的行为。训练初期调结构时我会打开这两个开关等结构定下来、只看最终精度时再关掉换速度。如果只固定torch.manual_seed却忘了cuda.manual_seed_all多卡环境下仍然会出现结果漂移。3. 卷积网络模块拆解从一个图像分类脚本看训练循环CNN 这一章通常是整套教程里代码量最大、也最值得逐行读的部分。它把数据增强、模型定义、训练验证、学习率调度、权重保存串成了一条完整链路后面 RNN 和 GAN 章节都是在这条链路上换零件。3.1 模型定义与 forward 链路入门教程里的 CNN 一般不会一上来就堆 ResNet而是用两到三个卷积块把Conv2d → BatchNorm2d → ReLU → MaxPool2d这个组合讲清楚。下面这个结构对 32×32 的输入比较友好通道数从 3 爬到 64空间尺寸缩两次。import torch from torch import nn class SimpleCNN(nn.Module): def __init__(self, num_classes10): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size3, padding1), # 32x32 - 32x32 nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # - 16x16 nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # - 8x8 ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(64 * 8 * 8, 256), # 尺寸必须和 feature map 对上 nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(256, num_classes), ) def forward(self, x): x self.features(x) return self.classifier(x)padding1配kernel_size3是保持空间尺寸不变的常用组合两次MaxPool2d(2)把 32 变成 16 再变成 8所以全连接层输入是64 * 8 * 8。这个数字一旦算错报错信息会是mat1 and mat2 shapes cannot be multiplied新手很容易卡在这里。inplaceTrue的 ReLU 省一点显存但在需要保留原始激活做可视化的场景下要关掉。Dropout(0.5)放在全连接层前是分类任务的常规操作卷积层里一般不用 Dropout靠 BatchNorm 做正则。3.2 训练循环与验证验证函数和训练函数最大的区别是model.eval()加torch.no_grad()。前者把 BatchNorm 切到用滑动统计量、把 Dropout 关掉后者切断计算图省显存。两个都漏掉的话验证精度会明显偏低而且显存占用虚高。torch.no_grad() def evaluate(model, loader, criterion, device): model.eval() correct, total, loss_sum 0, 0, 0.0 for x, y in loader: x, y x.to(device), y.to(device) logits model(x) loss_sum criterion(logits, y).item() * x.size(0) pred logits.argmax(dim1) # 取分数最大的类别 correct (pred y).sum().item() total x.size(0) return loss_sum / total, correct / totallogits.argmax(dim1)返回的是每个样本的预测类别索引(pred y).sum().item()统计猜对的个数。这里没有做 softmax因为CrossEntropyLoss内部已经包含 log-softmax模型输出 logits 就行多做一次 softmax 反而会让数值不稳定。训练循环里每跑完一个 epoch 调一次evaluate把 loss 和 acc 记下来画曲线时能看出过拟合是从第几个 epoch 开始的。3.3 数据加载与增强图像分类的精度提升一半靠数据增强。教程里通常会演示随机裁剪、随机翻转、归一化这套组合顺序不能乱先做几何变换再ToTensor把 PIL 图变成张量最后Normalize。from torchvision import datasets, transforms from torch.utils.data import DataLoader train_tf transforms.Compose([ transforms.RandomCrop(32, padding4), # 先补边再随机裁保留边缘信息 transforms.RandomHorizontalFlip(), # 水平翻转概率默认 0.5 transforms.ToTensor(), # HWC uint8 - CHW float [0,1] transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), # 归一化到 [-1,1] ]) train_set datasets.CIFAR10(rootdata, trainTrue, downloadTrue, transformtrain_tf) train_loader DataLoader( train_set, batch_size128, shuffleTrue, num_workers4, pin_memoryTrue, # 锁页内存配合 GPU 加速数据传输 )Normalize的均值方差按数据集统计量填教程里统一用 0.5 是为了演示方便实际项目里应该用训练集算出来的真实均值和标准差。num_workers4在 Linux 上通常没问题Windows 下如果写在if __name__ __main__:外面会反复启动子进程这点后面避坑章节会细说。pin_memoryTrue只在 GPU 训练时有收益纯 CPU 环境可以去掉。3.4 优化器、调度器与权重保存CNN 章节一般会用 Adam 起步再展示 SGD momentum 的对比。学习率调度用余弦退火比较常见训练后期学习率降下来精度还能再抬一点。optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) for epoch in range(50): train_loss train_one_epoch(model, train_loader, criterion, optimizer, device) val_loss, val_acc evaluate(model, val_loader, criterion, device) scheduler.step() # 每个 epoch 结束后更新学习率 torch.save({ epoch: epoch, model: model.state_dict(), # 只存参数不存整个对象 optimizer: optimizer.state_dict(), scheduler: scheduler.state_dict(), }, fckpt/epoch_{epoch}.pt)存state_dict()而不是整个model好处是加载时不依赖原来的类定义路径换目录、换机器都不容易出问题。weight_decay1e-4是 Adam 下的常规起点太大模型欠拟合太小又压不住过拟合。T_max50要和总 epoch 数对齐设成 10 的话学习率很早就降到接近零后面几十轮基本白跑。断点续训时把三个state_dict一起恢复学习率才能接着原来的节奏走。4. 循环网络与生成模型RNN 与 GAN 两条线的实现差异CNN 处理的是网格结构RNN 和 GAN 处理的是序列和分布代码骨架相似但训练节奏完全不同。这两章分开看容易放一起对比反而能看清 PyTorch 里哪些是通用套路、哪些是任务特有的写法。4.1 RNN 序列建模hidden state 怎么传字符级语言模型是教程里最常见的 RNN 示例输入一段字符序列预测下一个字符。核心是nn.Embedding把字符索引变成向量nn.LSTM沿时间步滚动nn.Linear把 hidden state 映射回词表大小。class CharRNN(nn.Module): def __init__(self, vocab_size, embed_dim128, hidden_dim256, num_layers2): super().__init__() self.embed nn.Embedding(vocab_size, embed_dim) self.rnn nn.LSTM( embed_dim, hidden_dim, num_layersnum_layers, batch_firstTrue, # 输入形状 (batch, seq, feature) dropout0.3, # 多层 LSTM 时生效单层会被忽略 ) self.fc nn.Linear(hidden_dim, vocab_size) def forward(self, x, hiddenNone): x self.embed(x) # (B, T) - (B, T, E) out, hidden self.rnn(x, hidden) # out: (B, T, H) logits self.fc(out) # (B, T, V) return logits, hiddenbatch_firstTrue是个容易忽略的参数默认 LSTM 期望(seq, batch, feature)设成 True 后输入输出都按(batch, seq, feature)排和 DataLoader 出来的形状一致少一次 transpose。hidden在训练时通常传None让 PyTorch 自动初始化做文本生成时才需要把上一步的 hidden 传回来保持上下文。序列太长时不能整个序列一次反传常见做法是按固定长度切段每段反传一次hidden 截断后继续传也就是常说的 TBPTT教程里一般会在注释里标出来。损失函数用CrossEntropyLoss时要注意形状对齐logits是(B, T, V)标签是(B, T)需要logits.view(-1, vocab_size)和target.view(-1)拉平后再算。4.2 GAN 对抗训练生成器与判别器的节奏GAN 的代码量不大坑主要在训练循环。生成器和判别器要分开优化判别器更新时生成器的输出必须detach()否则梯度会顺着判别器回流到生成器两边一起动训练直接乱套。class Generator(nn.Module): def __init__(self, z_dim100, img_dim784): super().__init__() self.net nn.Sequential( nn.Linear(z_dim, 256), nn.LeakyReLU(0.2, inplaceTrue), nn.Linear(256, 512), nn.LeakyReLU(0.2, inplaceTrue), nn.Linear(512, img_dim), nn.Tanh(), # 输出压到 [-1,1]和输入归一化对齐 ) def forward(self, z): return self.net(z)训练循环的骨架如下判别器和生成器各算一次 loss、各走一次stepfor real, _ in loader: real real.view(real.size(0), -1).to(device) # (B,1,28,28) - (B,784) bsz real.size(0) # ---- 判别器真样本判 1假样本判 0 ---- z torch.randn(bsz, z_dim, devicedevice) fake G(z).detach() # 关键切断生成器梯度 d_loss criterion(D(real), torch.ones(bsz, 1, devicedevice)) \ criterion(D(fake), torch.zeros(bsz, 1, devicedevice)) optimizer_D.zero_grad() d_loss.backward() optimizer_D.step() # ---- 生成器骗判别器判 1 ---- z torch.randn(bsz, z_dim, devicedevice) g_loss criterion(D(G(z)), torch.ones(bsz, 1, devicedevice)) optimizer_G.zero_grad() g_loss.backward() optimizer_G.step()detach()那行是整段代码里最容易漏的漏了之后判别器 loss 照样下降但生成器学出来的东西全是噪声看 loss 曲线根本发现不了只能靠定期存图肉眼判断。GAN 的优化器一般用 Adamlr2e-4betas(0.5, 0.999)beta1 调低是为了减少动量带来的震荡。判别器太强时生成器梯度会消失常见做法是判别器每更新两次、生成器更新一次或者给判别器加标签平滑。4.3 三条线的调参侧重点CNN、RNN、GAN 虽然共用一套 PyTorch API调参时盯的指标和瓶径完全不同。下面这张表是读教程代码时整理的对比实际调的时候可以按这个顺序排查。维度CNN 分类RNN 语言模型GAN常用优化器Adam 起步后期换 SGDAdamAdamlr2e-4betas(0.5,0.999)关键监控指标验证集准确率困惑度、生成样本D/G loss 平衡、样本质量归一化手段BatchNorm2dLayerNorm、Dropout谱归一化、权重裁剪显存主要占用大 batch 高分辨率序列长度 × hidden 维度双模型同时驻留显存典型失败模式过拟合、学习率过大梯度消失、长依赖学不到模式崩塌、判别器过强CNN 的调参空间主要在数据增强和学习率调度模型结构定下来后改动收益有限。RNN 的瓶颈在序列长度和 hidden 维度梯度消失靠 LSTM 的门控缓解但序列一长还是得上注意力机制。GAN 没有明确的收敛指标D loss 和 G loss 此消彼长是常态得靠定期采样看图判断教程里通常会配一个save_image的工具函数。5. 常见问题排查环境、显存、梯度三类高频翻车教程代码跑不通八成不是模型写错而是环境或数据形状对不上。下面这几条是我在复现这类教程时反复遇到的每条按现象、原因、解决三步写照着排查能省不少时间。5.1 torch.cuda.is_available() 返回 False现象脚本里写了.to(device)但device一直是cpu训练速度慢到无法接受nvidia-smi里也看不到进程。原因通常有三种装的是纯 CPU 版 torch显卡驱动版本低于 PyTorch 要求的 CUDA 版本虚拟环境里同时存在多个 torch 版本import torch导入的不是刚装的那个。解决办法是先确认torch.__version__里带不带cu后缀再跑torch.version.cuda看编译时的 CUDA 版本最后对照nvidia-smi右上角的驱动支持版本。三者对不上就重装对应版本的 torch不要试图靠改环境变量绕过去。5.2 训练几个 batch 就显存溢出现象报CUDA out of memory但 batch size 明明设得很小模型参数量也不大。原因往往是显存里残留了上一个进程的缓存或者验证阶段忘了torch.no_grad()计算图一直挂着不释放。另一种情况是保存了带梯度的中间变量比如在列表里累积 loss 张量而不是loss.item()。解决办法先nvidia-smi看是不是有僵尸进程占着显存有就kill掉检查验证函数有没有加torch.no_grad()把total_loss loss改成total_loss loss.item()。这三步走完还爆再考虑梯度累积或混合精度训练不要一上来就换小模型。5.3 loss 卡在某个值不动现象训练 loss 前几个 epoch 降一点之后几乎水平验证精度也不涨。原因可能是学习率太大导致在最优点附近震荡也可能是数据没打乱、每个 batch 都是同一类样本还可能是标签和输出维度对不上但恰好没报错。GAN 里则是判别器太强生成器梯度接近零。解决办法先把学习率降一个数量级试两轮确认 DataLoader 的shuffleTrue打印一个 batch 的logits.shape和target.shape对齐GAN 场景下调低判别器学习率或减少判别器更新频率。loss 不降时不要急着改模型结构先把这几点排掉。5.4 DataLoader 多进程卡死或报错现象脚本跑到for x, y in loader就停住或者 Windows 下报RuntimeError: An attempt has been made to start a new process...。原因在 Windows 和 macOS 上多进程启动方式和 Linux 不同num_workers 0时子进程会重新 import 主模块如果主逻辑没放在if __name__ __main__:下面就会无限递归启动进程。解决办法把训练入口包进if __name__ __main__:Windows 下先把num_workers设为 0 验证逻辑通不通通了再往上加如果数据集对象里有不可序列化的成员也会导致子进程启动失败检查一下自定义 Dataset 的__init__里有没有打开的文件句柄。5.5 保存的模型加载时报 key 不匹配现象load_state_dict报Missing key(s)或Unexpected key(s)明明存的时候没改结构。原因通常是保存时用了torch.save(model, path)存整个对象加载时换到了另一台机器或改了类名也可能是训练时用了nn.DataParallel存下来的 key 前面多了module.前缀。解决办法统一用state_dict()保存如果已有带module.前缀的权重加载前做一次 key 重命名把module.去掉结构确实改过的话用strictFalse加载并打印缺失的 key确认是不是只缺最后那个分类层。6. 二次开发把教程代码改成自己的实验脚手架教程代码能跑通只是起点真正要用起来得改成能反复做实验的脚手架。我一般先做两件事把散落在脚本里的超参数抽成配置再给训练循环加断点续训和日志。6.1 用配置类收拢超参数argparse适合命令行覆盖但参数一多就乱。常见做法是加一个 dataclass 或 YAML 配置把模型、数据、训练三组参数分开脚本里只读配置不写魔法数字。from dataclasses import dataclass, field dataclass class TrainConfig: batch_size: int 128 epochs: int 50 lr: float 1e-3 weight_decay: float 1e-4 device: str cuda ckpt_dir: str ckpt seed: int 42 dataclass class DataConfig: root: str data num_classes: int 10 num_workers: int 4这样换数据集时只改DataConfig换模型时只改build_model里的分支实验记录也好对齐。配置类不替代argparse可以在argparse解析完之后用命令行值覆盖 dataclass 字段两者配合用。6.2 断点续训与日志断点续训的关键是把模型、优化器、调度器、当前 epoch、最佳精度一起存恢复时按顺序加载。日志方面至少记录每个 epoch 的训练 loss、验证 loss、验证精度和学习率后面画曲线、对比实验都靠它。def save_ckpt(path, model, optimizer, scheduler, epoch, best_acc): torch.save({ model: model.state_dict(), optimizer: optimizer.state_dict(), scheduler: scheduler.state_dict(), epoch: epoch, best_acc: best_acc, }, path) def load_ckpt(path, model, optimizer, scheduler, device): ckpt torch.load(path, map_locationdevice) model.load_state_dict(ckpt[model]) optimizer.load_state_dict(ckpt[optimizer]) scheduler.load_state_dict(ckpt[scheduler]) return ckpt[epoch], ckpt[best_acc]map_locationdevice不能省GPU 上存的权重拿到 CPU 机器上加载时不加这个参数会直接报错。恢复后从epoch 1开始训练不要从头再来否则调度器的步数会和实际 epoch 对不上。6.3 验证改造是否生效改完脚手架别急着跑长训练先用小数据验证链路通不通把训练集截到几百张、epoch 设成 2、batch size 设成 8跑一遍看 loss 有没有下降、权重有没有存下来、断点能不能恢复。这一步花不了几分钟但能排掉大部分形状不匹配和路径问题。验证通过后再放大数据量和 epoch出问题时也容易判断是数据规模导致的还是代码改动导致的。我在改这类教程代码时踩过一次坑为了让日志更详细在训练循环里把每个 batch 的 loss 都 append 到一个列表里跑长训练时内存一路涨到把进程撑爆。从那以后我每次加日志都会先确认存的是标量还是张量长时间训练前强制用小数据跑一遍内存观察。希望这些经验帮到你少走几步弯路。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑