简介:这份资源是《深度学习与PyTorch入门实战》视频教程的配套源代码与PPT合集,面向希望系统入门深度学习、掌握PyTorch框架的初学者与进阶开发者,覆盖机器学习、卷积神经网络、循环神经网络、生成对抗网络及图卷积网络等核心方向。压缩包共142个文件,约85.9MB,包含55份PDF课件、39个Python源码、15张PNG图示,以及ipynb笔记本、XML配置、TXT说明等辅助文件,课件与代码一一对应,便于边看边练。资源中提供了Cifar10与ResNet18实战、LSTM、图卷积网络GCN等典型实验的完整实现,并附有PyTorch安装与国内源配置说明,可帮助读者快速搭建环境、理解模型结构并复现训练流程。目前已有2678人学习下载,适合作为课程配套练习与项目实战的参考材料。
1. 这套 PyTorch 实战源码包,到底适合谁拆
如果你正在找一份能直接跑起来的深度学习入门材料,而不是那种只讲公式推导、看完还是不知道怎么搭网络的教程,那这套 PyTorch 配套源码和 PPT 值得花时间拆一遍。它覆盖了从张量操作、自动求导,到卷积神经网络、循环神经网络、生成对抗网络这几条主线,每一块都有对应的可运行代码和课件。适合两类人:一是刚学完 Python 和基础数学、想找一个完整项目把 machine-learning tutorial 这条路走通的新手;二是已经用过其他框架、想快速切到 PyTorch 生态、需要一份能对照着改参数和验证想法的从业者。源码包的价值不在于代码多长,而在于它把每个概念的输入输出、维度变化、损失计算都摊开给你看,省掉了自己从零搭脚手架的时间。
2. 环境搭建与源码结构:先把跑通成本压到最低
拿到一个源码包,最怕的是环境装了半天、依赖冲突一堆,最后连第一个脚本都跑不起来。这套材料用的是 PyTorch 经典版本接口,整体依赖不算重,但有几个版本匹配的细节如果忽略,后面会反复翻车。
2.1 依赖清单与版本选择逻辑
先看它需要什么。核心依赖就三块:PyTorch 本体、数值计算库、可视化工具。常见做法是用 conda 建一个独立环境,避免和系统里已有的包打架。我一般会先确认 CUDA 版本,再决定装哪个 PyTorch 轮子,因为 CPU 版和 GPU 版的安装命令完全不同。
# 创建独立环境,Python 版本建议 3.8 到 3.10 conda create -n pytorch_tutorial python=3.9 -y conda activate pytorch_tutorial # 安装 PyTorch,这里以 CUDA 11.8 为例 # 如果机器没有 NVIDIA 显卡,把 cu118 换成 cpu 即可 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 配套的数值计算和绘图库 pip install numpy matplotlib pandas scikit-learn这段命令的逻辑很直接:先隔离环境,再装框架,最后补辅助库。参数上唯一需要你确认的是--index-url后面的 CUDA 版本号,它必须和本机驱动支持的版本对上。装完之后用一行代码验证:
import torch # 检查 GPU 是否可用,输出 True 说明 CUDA 配置成功 print(torch.cuda.is_available()) # 打印当前 PyTorch 版本,方便和教程里的接口对照 print(torch.__version__)如果torch.cuda.is_available()返回 False,先别急着怀疑代码,大概率是驱动版本和 CUDA 运行时不匹配。这时候要么升级驱动,要么直接换 CPU 版跑,入门阶段的网络规模用 CPU 完全扛得住。
2.2 源码目录怎么读才不迷路
这套源码的目录结构是按知识模块切的,不是按文件类型堆在一起。常见做法是每个大主题一个文件夹,里面放对应的训练脚本、模型定义和数据加载逻辑。你拿到手之后,不要从第一个文件开始顺序读,而是先找入口脚本。
| 目录/文件类型 | 对应内容 | 建议阅读顺序 |
|---|---|---|
| 基础张量操作脚本 | 张量创建、索引、广播、自动求导 | 第 1 顺位 |
| CNN 相关目录 | 卷积层、池化、图像分类训练流程 | 第 2 顺位 |
| RNN 相关目录 | 序列建模、LSTM/GRU 单元、文本处理 | 第 3 顺位 |
| GAN 相关目录 | 生成器与判别器交替训练 | 第 4 顺位 |
| PPT 课件 | 每个模块的理论铺垫和图示 | 配合代码同步看 |
读代码的时候有个习惯很管用:先把模型定义的forward函数看一遍,确认输入输出的张量形状,再回头看训练循环里的损失计算和反向传播。这样你不会被一堆数据预处理代码带偏,能快速抓住这个脚本到底在干什么。
2.3 第一个可运行脚本的验证方法
不要一上来就跑最复杂的 GAN,先用最基础的张量脚本确认环境没问题。找一个做线性回归或简单全连接网络的脚本,把 epoch 调小,比如改成 5,看损失有没有下降。
# 以最简单的全连接网络为例,验证训练流程是否通畅 import torch import torch.nn as nn # 定义一个单层网络,输入维度 10,输出维度 1 model = nn.Linear(10, 1) # 使用均方误差损失,适合回归任务 criterion = nn.MSELoss() # 学习率设为 0.01,入门阶段这个值比较稳 optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 造一批假数据,batch 大小为 16 x = torch.randn(16, 10) y = torch.randn(16, 1) for epoch in range(5): optimizer.zero_grad() # 清空上一轮梯度 output = model(x) # 前向传播 loss = criterion(output, y)# 计算损失 loss.backward() # 反向传播求梯度 optimizer.step() # 更新参数 print(f"epoch {epoch}, loss {loss.item():.4f}")这段代码的关键在于把训练循环的五个步骤拆清楚了:清梯度、前向、算损失、反向、更新。参数上lr是学习率,入门阶段从 0.01 或 0.001 起步比较安全;batch size影响梯度稳定性,太小会震荡,太大会吃内存。跑通这个,说明你的环境、数据流、反向传播链路都是通的,再去碰 CNN 和 RNN 就有底了。
3. CNN 与 RNN 模块:从图像分类到序列建模的实操拆解
中间这两块是整套材料的重头戏。卷积神经网络负责空间特征提取,循环神经网络负责时序依赖建模,两者的代码结构和调试思路差别很大,分开拆更清楚。
3.1 卷积神经网络的张量维度变化
CNN 最容易让人晕的是维度。一张图片进来是[batch, channel, height, width],经过卷积层之后通道数变了、尺寸变了,到了全连接层又要展平。很多新手在这一步报错,就是因为没跟踪形状。
import torch import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self): super().__init__() # 第一个卷积层:输入 3 通道,输出 16 通道,卷积核 3x3 self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1) # 池化层:2x2 窗口,把空间尺寸减半 self.pool = nn.MaxPool2d(2, 2) # 第二个卷积层:16 通道进,32 通道出 self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1) # 全连接层:假设输入图片是 32x32,经过两次池化变成 8x8 self.fc = nn.Linear(32 * 8 * 8, 10) def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) # 第一次卷积+池化 x = self.pool(torch.relu(self.conv2(x))) # 第二次卷积+池化 x = x.view(x.size(0), -1) # 展平,保留 batch 维度 x = self.fc(x) # 全连接分类 return x # 模拟一个 batch 的输入,4 张 32x32 的 RGB 图片 input_tensor = torch.randn(4, 3, 32, 32) model = SimpleCNN() output = model(input_tensor) print(output.shape) # 期望输出 torch.Size([4, 10])这里的关键参数是padding=1,它让卷积后的尺寸不变,否则每卷一次就缩小一圈,后面展平的时候维度对不上。x.view(x.size(0), -1)里的-1是让 PyTorch 自动算展平后的长度,但前提是你前面的卷积和池化输出尺寸要算对。如果报shape mismatch,先打印x.shape再检查全连接层的输入维度。
3.2 循环神经网络处理序列数据的输入格式
RNN 和 CNN 的输入逻辑完全不同。CNN 关心空间维度,RNN 关心时间步。输入通常是[seq_len, batch, input_size],如果你把 batch 放在第一位,训练会变得很慢甚至出错。
import torch import torch.nn as nn class SimpleRNN(nn.Module): def __init__(self, input_size, hidden_size, output_size): super().__init__() # 定义 LSTM 层,batch_first=False 表示输入格式为 [seq, batch, feature] self.rnn = nn.LSTM(input_size, hidden_size, batch_first=False) # 输出层把隐藏状态映射到类别数 self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): # x 的形状:[序列长度, batch大小, 特征维度] out, (h_n, c_n) = self.rnn(x) # 取最后一个时间步的输出做分类 last_out = out[-1, :, :] return self.fc(last_out) # 模拟输入:序列长度 20,batch 大小 8,每个时间步特征 16 维 seq_input = torch.randn(20, 8, 16) model = SimpleRNN(input_size=16, hidden_size=32, output_size=5) result = model(seq_input) print(result.shape) # 期望输出 torch.Size([8, 5])这段代码里batch_first=False是默认值,也是很多教程推荐的格式。如果你习惯 batch 在前,就把它设成 True,同时调整输入形状。out[-1, :, :]取的是最后一个时间步的隐藏状态,适合做序列分类;如果是做序列到序列的任务,就要保留所有时间步的输出。参数上hidden_size决定记忆容量,太小会欠拟合,太大会过拟合且训练慢,入门阶段 32 到 128 之间比较常见。
3.3 训练循环里的损失函数与优化器选择
CNN 和 RNN 的训练循环骨架是一样的,但损失函数和优化器的搭配有讲究。分类任务用交叉熵,回归任务用均方误差;优化器从 SGD 到 Adam 各有适用场景。
# 分类任务的典型训练配置 criterion = nn.CrossEntropyLoss() # 内部自带 softmax,不要手动再加 optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) # 训练循环片段 for epoch in range(10): model.train() # 切换到训练模式,影响 dropout 和 batchnorm for batch_x, batch_y in dataloader: optimizer.zero_grad() pred = model(batch_x) loss = criterion(pred, batch_y) loss.backward() optimizer.step() model.eval() # 切换到评估模式CrossEntropyLoss的输入是未经过 softmax 的 logits,如果你在模型最后加了 softmax,再传进来就会导致损失计算错误,这是很常见的翻车点。Adam的学习率一般设 1e-3 或 1e-4,比 SGD 更容易收敛,但泛化性能有时不如调好的 SGD。入门阶段先用 Adam 把流程跑通,再回头对比 SGD 的效果。
4. GAN 模块与常见报错:生成对抗网络的训练节奏怎么控
GAN 是这套材料里最考验调试耐心的部分。生成器和判别器交替训练,任何一方的节奏不对,整个训练就会崩。很多新手跑 GAN 的时候看到损失不降反升,或者生成结果全是噪声,基本都是训练策略的问题。
4.1 生成器与判别器的交替训练逻辑
GAN 的核心是让两个网络互相博弈。判别器要尽量区分真实数据和生成数据,生成器要尽量骗过判别器。代码上就是两个优化器交替更新。
import torch import torch.nn as nn # 假设生成器和判别器已经定义好 # generator 和 discriminator 分别是两个网络实例 criterion = nn.BCELoss() # 二分类交叉熵,判别真假 optimizer_g = torch.optim.Adam(generator.parameters(), lr=2e-4) optimizer_d = torch.optim.Adam(discriminator.parameters(), lr=2e-4) for epoch in range(50): for real_data, _ in dataloader: batch_size = real_data.size(0) real_label = torch.ones(batch_size, 1) # 真实样本标签为 1 fake_label = torch.zeros(batch_size, 1) # 生成样本标签为 0 # 第一步:训练判别器 optimizer_d.zero_grad() output_real = discriminator(real_data) loss_d_real = criterion(output_real, real_label) noise = torch.randn(batch_size, 100) # 生成器输入噪声 fake_data = generator(noise) output_fake = discriminator(fake_data.detach()) # 断开梯度,只更新判别器 loss_d_fake = criterion(output_fake, fake_label) loss_d = loss_d_real + loss_d_fake loss_d.backward() optimizer_d.step() # 第二步:训练生成器 optimizer_g.zero_grad() output_fake = discriminator(fake_data) # 这里不 detach,梯度要传回生成器 loss_g = criterion(output_fake, real_label) # 生成器希望判别器判为真 loss_g.backward() optimizer_g.step()这段代码里有两个关键点:训练判别器时对fake_data用了.detach(),目的是切断梯度,不让生成器被误更新;训练生成器时则不能 detach,否则梯度传不回去。lr=2e-4是 GAN 常用的学习率,比普通分类任务小,因为两个网络互相影响,步子太大会震荡。
4.2 模式崩溃与损失震荡的排查思路
GAN 训练中最常见的两个问题是模式崩溃和损失震荡。模式崩溃的表现是生成器只输出少数几种样本,多样性极差;损失震荡则是判别器和生成器的损失来回跳,没有收敛趋势。
排查的时候先看数据。如果真实数据本身多样性不足,生成器学不到分布,先检查数据集。再看网络容量,生成器和判别器的参数量差距太大,一方碾压另一方,训练就会失衡。常见做法是让判别器稍弱于生成器,比如减少判别器的层数或降低它的学习率。还有一个技巧是给判别器的输入加噪声,让它的判断不那么绝对,给生成器留出学习空间。
4.3 训练日志里该盯哪几个指标
跑 GAN 不能只看最终生成结果,训练过程中的日志更有诊断价值。我一般会记录四个值:判别器在真实数据上的损失、判别器在生成数据上的损失、生成器的损失、以及判别器输出的平均概率。
| 指标 | 正常表现 | 异常表现与可能原因 |
|---|---|---|
| 判别器真实损失 | 缓慢下降后稳定 | 持续为 0,判别器过强 |
| 判别器生成损失 | 与真实损失接近 | 一直很高,生成器太弱 |
| 生成器损失 | 波动但整体下降 | 持续上升,训练节奏失衡 |
| 判别器输出均值 | 真实样本接近 1,生成样本接近 0 | 两者都接近 0.5,判别器没学到东西 |
如果判别器输出均值长期停在 0.5 附近,说明它根本没区分开真假样本,这时候要么调大判别器的学习率,要么检查数据预处理是不是把关键特征归一化掉了。
5. 避坑与排查:跑这套源码时最容易翻车的五个地方
这一章是我自己踩过的坑,也是周围人问得最多的问题。每个都按现象、原因、解决三步写,你遇到类似报错可以直接对号入座。
5.1 报错 “RuntimeError: shape invalid for input of size”
现象:训练脚本跑到全连接层时报形状不匹配,提示输入尺寸和权重尺寸对不上。
原因:卷积和池化层的输出尺寸没有算对,导致展平后的长度和全连接层定义的输入维度不一致。常见于改了输入图片尺寸但没改全连接层。
解决:在forward里展平之前打印x.shape,确认实际输出尺寸,然后反推全连接层的输入维度。或者用nn.AdaptiveAvgPool2d把空间尺寸固定下来,避免手动计算。
5.2 损失不下降,准确率停在随机水平
现象:训练了好几个 epoch,损失几乎不变,准确率在 10% 左右晃(十分类任务)。
原因:学习率太大导致梯度爆炸,或者数据标签没有对齐,或者损失函数用错。比如分类任务用了 MSE 而不是交叉熵。
解决:先把学习率降到 1e-4 试一轮,看损失有没有变化。再检查数据加载器里图片和标签是不是一一对应。最后确认损失函数和任务类型匹配,分类用交叉熵,回归用均方误差。
5.3 GPU 显存溢出 “CUDA out of memory”
现象:训练一开始就报显存不够,或者跑几个 batch 之后突然溢出。
原因:batch size 太大,或者没有用torch.no_grad()包住评估阶段,导致计算图一直累积。
解决:先把 batch size 减半,这是最快见效的办法。评估和推理阶段一定要加with torch.no_grad():,它不会构建计算图,能省大量显存。如果还不够,考虑用梯度累积模拟大 batch。
5.4 RNN 训练时梯度消失或爆炸
现象:序列任务中,长序列的损失几乎不下降,或者损失突然变成 NaN。
原因:普通 RNN 在长序列上容易出现梯度消失,LSTM 和 GRU 缓解了这个问题,但如果序列特别长,仍然可能爆炸。
解决:优先用 LSTM 或 GRU 替代普通 RNN。训练时加梯度裁剪,限制梯度的最大范数:
# 在 loss.backward() 之后、optimizer.step() 之前加这一行 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)max_norm=1.0是常用值,超过这个范数的梯度会被按比例缩小,防止参数更新过猛。
5.5 生成对抗网络训练几轮后生成结果全是噪声
现象:GAN 刚开始还能生成一点有结构的图像,跑了几十轮之后全变成随机噪声。
原因:判别器太强,生成器梯度消失,或者生成器和判别器的学习率没有调好。
解决:降低判别器的学习率,或者减少判别器的更新次数,比如每训练两次生成器才训练一次判别器。另外检查生成器的输入噪声维度是否足够,噪声太小会导致生成多样性不足。
6. 进阶用法:把源码改造成自己的实验模板
跑通源码只是第一步,真正有价值的是把它变成你自己的实验底座。我一般会做三件事:把模型定义和训练循环解耦、加一套统一的日志记录、写一个参数配置文件。这样换数据集或改网络结构的时候,不用在代码里到处翻。
6.1 用配置文件管理超参数
把学习率、batch size、epoch 数这些抽到一个 YAML 或 JSON 文件里,训练脚本只读配置,不写死数值。这样你做对比实验的时候,改一个文件就能跑一组新参数,不用动代码。
import yaml # 读取配置文件 with open("config.yaml", "r") as f: config = yaml.safe_load(f) # 从配置里取参数 lr = config["training"]["learning_rate"] batch_size = config["training"]["batch_size"] hidden_size = config["model"]["hidden_size"]对应的config.yaml大概长这样:
training: learning_rate: 0.001 batch_size: 32 epochs: 50 model: hidden_size: 64 num_layers: 2这样做的好处是实验可复现。你跑过的每一组参数都留在配置文件里,回头对比结果的时候不会记混。
6.2 加一套轻量的训练日志
不要只靠 print,用 logging 模块把关键信息写到文件里,包括每个 epoch 的损失、准确率、学习率、耗时。这样训练中断了也能从日志里判断问题出在哪。
import logging logging.basicConfig( filename="train.log", level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) # 在训练循环里记录 logging.info(f"epoch {epoch}, loss {loss.item():.4f}, acc {acc:.2f}")日志里我还会记一个东西:每个 epoch 结束时的学习率。如果你用了学习率衰减策略,这个值能帮你判断衰减是不是太激进。
6.3 验证改造后代码是否正确的三个检查点
改完代码之后,不要直接跑完整训练,先用小数据验证。第一个检查点:用一个 batch 的数据跑一次前向和反向,确认没有形状错误。第二个检查点:把模型设成 eval 模式,用同样的输入跑两次,确认输出一致,说明没有随机性干扰。第三个检查点:把学习率设成 0,跑几个 epoch,损失应该完全不变,如果变了说明优化器或数据加载有问题。
这三个检查点花不了几分钟,但能帮你排除大部分低级错误。从那以后我每次改完训练脚本,都强制走一遍这三步,比直接跑完整训练再回头 debug 省时间得多。希望这套源码和这些排查思路能帮你少走点弯路。
本文还有配套的精品资源,点击获取