PyTorch入门:从张量计算到模型部署的完整链路
如果你已经装了 PyTorch,却发现自己盯着终端里蹦出来的Using CPU发愣;如果你跟着教程跑通了 MNIST,但换一个真实数据集就不知道代码该怎么改;如果只是想在 Windows 上装个 GPU 版 PyTorch,结果被 CUDA、cuDNN、conda、pip 的组合拳劝退了一下午——那这篇内容就是给你写的。
PyTorch 入门这件事,最大的误区不是“数学太难”,也不是“代码看不懂”,而是很多人把它当成一门需要“背 API”的课来学。你背了torch.nn.Linear、torch.optim.Adam、model.train(),却不知道它们为什么出现在那个位置,不知道数据从哪来、梯度怎么算、loss 为什么不降,更不知道一次跑通之后怎么让它稳定反复地跑。
我对 PyTorch 入门的核心判断是:它真正值得学的不是某一个函数或某一种网络,而是一条“数据流水线 → 张量计算 → 自动求导 → 模型训练 → 部署验证”的完整链路。这条链路你能从头到尾自己搭一遍,才算真正入了深度学习的门;否则你只是在“运行别人的代码”。
下面我会按照这条链路来拆解。不堆公式,不炫技,重点放在“为什么这样做”和“真实落地时你会踩什么坑”。
1. 先想清楚:PyTorch 到底在帮你解决什么
很多入门教程第一节课就扔出“PyTorch 是一个开源的深度学习框架”,听完之后你依然不知道它有什么用。换一个问法:如果没有 PyTorch,你想训练一个神经网络,需要自己写哪些东西?
至少三样:一是高效的多维数组运算,二是自动求梯度,三是把计算搬到 GPU 上加速。这三件事如果全都从零写,哪怕只是实现一个最简单的全连接网络,也足够消耗掉你几周时间。
1.1 它不是“深度学习全家桶”,而是三块积木
PyTorch 的核心可以拆成三层来理解:
- 张量(Tensor):这是 PyTorch 的数据基本单位,你可以先把它理解成“能自动记录版本、能搬到 GPU 上的多维数组”。它和 NumPy 的
ndarray很像,但多了一个关键能力:支持自动求导。 - 自动求导(autograd):这是 PyTorch 最核心的引擎。你定义好前向计算过程后,它帮你自动记录计算图,反向传播时自动算出每个参数的梯度。没有它,深度学习训练的门槛会高一个数量级。
- 神经网络模块(torch.nn):它把层、激活函数、损失函数、优化器都封装成了积木。你不需要自己写卷积的前向计算,也不需要自己写 SGD 更新公式。
这三块加起来,才是 PyTorch 的完整价值。很多人只看到了第三块,觉得它是一个“模型仓库”,这就本末倒置了。
1.2 入门路径:别用背单词的方式学框架
我见过太多人学 PyTorch 的方式是:今天学torch.Tensor,明天学torch.nn.Conv2d,后天学torch.utils.data.Dataset,一个 API 一个 API 地刷,刷到后面全忘了。
更好的路径应该是以项目为主轴,每遇到一个问题,再去查对应 API:
- 先用一个 20 行代码的线性回归理解 Tensor、梯度、优化器之间的关系。
- 再用手写数字识别理解 Dataset、DataLoader、模型定义、训练循环的完整流程。
- 然后换一个真实的小型图像数据集,体会“换数据”会带来哪些新问题。
- 最后再考虑 GPU、混合精度、模型部署这些工程化话题。
这个路径的核心是:每一层都建立在一个“你能跑通的最小系统”之上,而不是在空中学函数。
2. 环境搭建:不要从“最全配置”开始,从“能跑”开始
环境配置是 PyTorch 入门劝退率最高的环节。Windows、Linux、Mac 各有各的问题;显卡驱动、CUDA、cuDNN、PyTorch 版本之间还有兼容关系。很多人卡在这里不是因为笨,而是因为一上来就想配一个“最完整、最先进、什么都能跑”的环境。
2.1 一张最小清单
如果你是第一次接触,我建议你先装 CPU 版。理由很简单:CPU 版一定能跑通,能让你先专注于学 PyTorch 本身,而不是和驱动搏斗。等第一个 demo 跑通了,再回头装 GPU 版,效率反而更高。
最小环境清单:
| 组件 | 建议 | 说明 |
|---|---|---|
| 操作系统 | Windows / Ubuntu / macOS 均可 | 后续深度学习实践,Ubuntu 的坑相对少 |
| Python | 3.9 ~ 3.12 之间 | 不要装最新的 Python 3.13,很多依赖可能还没跟上 |
| 包管理 | pip 或 conda 均可 | 新手用 Anaconda 里自带的 conda 更省心 |
| PyTorch | 先装 CPU 版 | 一行命令,几分钟搞定 |
| 集成开发环境 | VS Code / PyCharm 均可 | 关键是能看变量和报错信息 |
常见的 CPU 版安装命令(以官方源为例):
pip install torch torchvision torchaudio如果你用 conda:
conda install pytorch torchvision torchaudio cpuonly -c pytorch注意:不要直接复制网上老教程里的安装命令,PyTorch 的安装命令会根据操作系统、CUDA 版本、包管理器不同而变化。最靠谱的方式是打开 PyTorch 官网,选择你的系统配置,复制它生成的那条命令。
2.2 GPU 版本:先弄清楚 CUDA、cuDNN、PyTorch 三者关系
很多人装 GPU 版失败,是因为没搞清楚三者的关系:
- GPU 驱动是操作系统层面的,显卡插上就能用,但驱动版本不能太老。
- CUDA Toolkit是英伟达提供的并行计算平台。PyTorch 的 GPU 版会自带一部分 CUDA 运行时,不一定需要你再单独装完整版。
- cuDNN是深度学习加速库,PyTorch 安装包里也会带上兼容版本。
所以一个常见的误解是:“我必须先去英伟达官网装 CUDA Toolkit,才能装 PyTorch GPU 版。”其实 PyTorch 官方预编译包通常已经包含了运行所需的 CUDA 库。你真正需要确认的是:显卡驱动是否足够新,PyTorch 版本对应的 CUDA 版本是否和驱动兼容。
安装命令示例(以 CUDA 12.x 为例):
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121安装后,用这一行代码验证 GPU 是否真的被识别:
import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果torch.cuda.is_available()输出False,优先检查三件事:
- 显卡驱动是否装了,NVIDIA 驱动面板能不能打开。
- PyTorch 是不是装成了 CPU 版,
torch.__version__里如果带+cpu后缀,说明装错了。 - 系统里是不是有多个 Python 环境,包装到了另一个环境里。
注意:在 Windows 下最容易出的问题不是“版本太新”,而是“Anaconda 和 pip 混装导致包环境不一致”。建议在创建虚拟环境时固定用同一个包管理器,不要一会儿 pip 一会儿 conda。
2.3 几个特殊场景的处理思路
热词里出现了几个典型问题:“AMD 安装 PyTorch CUDA”“安装 PyTorch 时手机开了热点下载很慢”“Ubuntu 24.04 配置深度学习环境”。
- AMD 显卡:PyTorch 官方现在也支持 ROCm,但支持范围和稳定性不如 CUDA。如果你用的是 AMD 显卡,且刚入门,建议先装 CPU 版,或者直接用云平台跑 GPU 实例,避免在驱动兼容性上消耗太多时间。如果你想在 AMD GPU 上跑 PyTorch,先确认你的显卡型号在 ROCm 支持列表里,安装过程要严格对照 PyTorch 官方文档,不要依赖第三方整合包。
- 下载慢:这是国内常见问题。核心解法是换国内镜像源或换网络环境。pip 官方源下载慢,可以直接用清华、阿里等镜像:
另外 PyTorch 的pip install torch torchvision torchaudio -i https://pypi.tuna.tsinghua.edu.cn/simple.whl包很大,下载中断后要避免反复重传,pip 会做缓存,重试时通常能续传。 - Ubuntu 24.04:这类新系统版本刚发布时,部分底层库和驱动的兼容性需要适配。配置深度学习环境时,优先看 PyTorch 官方文档说明,不要盲目安装系统软件源里的旧版本驱动。
环境这块,我给的建议就一句话:能跑通比跑得“高级”重要得多。先用 CPU 版把流程跑通,再考虑 GPU 加速,这是效率最高的路径。
3. Tensor:所有操作最后都落到“数据在哪里、类型是什么、形状对不对”
很多初学者对 Tensor 的理解停留在“它是一个多维数组”,然后用起来却总报错。报错的核心原因,几乎可以归结成三类:数据不在同一个设备上、数据类型不匹配、形状对不上。
3.1 从创建到搬运:把 Tensor 理解成“有类型的矩阵 + 设备标记”
一个 Tensor 至少包含三部分信息:
- 值:具体的数据
- dtype:float32、float64、int64、bool
- device:数据在 CPU 还是 GPU 上
这三者缺一不可。你看报错信息时,只要先确认这三件事,通常就找到问题根源了。
常见的创建方式:
import torch # 从 Python 列表创建 a = torch.tensor([1, 2, 3]) # 创建全零/全一张量 b = torch.zeros(3, 4) c = torch.ones(2, 2) # 创建随机张量 d = torch.randn(3, 3) # 和 NumPy 互转 import numpy as np arr = np.array([1, 2, 3]) tensor_from_np = torch.from_numpy(arr)3.2 形状、dtype、device 三个视角的排查
如果你遇到一个 Tensor 相关报错,不要直接问“为什么报错”,先按以下顺序自查:
- 形状(shape):两个矩阵要做矩阵乘法,维度对不对?要做拼接,拼接维度的其他维度对不对?
- 数据类型(dtype):常见报错是
Expected object of scalar type Float but got scalar type Double,说明你把float64传给了一个期望float32的模型。解决办法是给数据加.float()。 - 设备(device):常见报错是
Expected all tensors to be on the same device,意思是模型在 GPU 上、数据还在 CPU 上,或者反过来。解决办法是统一调用.to(device)。
我见过很多人在这一步栽跟头,其实不是不懂原理,而是没有养成“先看报错前两行,再判断是哪类问题”的习惯。PyTorch 的报错信息通常已经很明确,关键是你能不能把它归到这三类里。
3.3 先掌握这几个基本操作
初学者不需要背所有 Tensor API,但有几个操作要非常熟练:
tensor.shape:查看形状tensor.reshape()/tensor.view():改变形状tensor.transpose():交换维度tensor.cat():拼接tensor.mean()/tensor.sum():聚合tensor.to(device):搬运设备tensor.detach():从计算图中分离
这些操作足够覆盖你 90% 的入门场景。剩下的 API,遇到具体问题时再查也不迟。
4. autograd:PyTorch 入门真正的分水岭
如果说 Tensor 是 PyTorch 的“数据仓库”,那 autograd 就是它的“动力引擎”。不理解 autograd,你只是“会用 PyTorch 跑代码”;理解了 autograd,你才能“自己写得出来”。
4.1 为什么说“能训练”和“会用框架”是两回事
很多人跑通了第一个训练循环,但问他一个问题就卡住了:loss.backward() 到底执行了什么?
从表面看,它只是反传了梯度。但真正重要的是:它会沿着计算图,从 loss 一步步回溯到每个参数,计算出 loss 对每个参数的偏导数。这些导数随后被优化器用来更新参数。
你不用手推链式法则,但你要知道:PyTorch 之所以能自动求导,是因为它在每次前向计算时,偷偷记录了一张“计算图”。
import torch x = torch.tensor(2.0, requires_grad=True) y = x ** 2 + 3 * x y.backward() print(x.grad) # 结果是 2*x + 3 = 7这个例子里,requires_grad=True告诉 PyTorch:你要追踪所有和x相关的操作,并为后续的反向传播做准备。
4.2 计算图与 requires_grad 的直观理解
可以把计算图理解成一张“流水线台账”。你每做一次运算,PyTorch 就在后台记录“这一步是谁和谁算出来的”。反向传播时,它按图索骥,把梯度从输出端一路传回输入端。
这里有两个实际意义:
- 如果你不需要对某个张量求梯度,就用
torch.no_grad()包起来或者设置requires_grad=False。推理阶段、计算准确率时,一定要加no_grad(),否则会白白占用内存。 - 如果你想把某个张量从计算图里“拆”出来,用
.detach()。这在一些特殊训练技巧里有大用。
4.3 梯度清零、累积和 no_grad 的工程意义
PyTorch 的梯度默认是累积的。也就是说,如果你不手动清零,每跑一次backward(),梯度都会加在之前的梯度上。
所以在训练循环里,你一定会看到这行代码:
optimizer.zero_grad()它必须在backward()之前调用,才能保证每个 batch 的梯度都是干净的。如果你忘记清零,loss 的下降曲线会非常奇怪,甚至直接发散。
推理阶段(验证集、测试集)的正确姿势是:
with torch.no_grad(): outputs = model(inputs) # 计算准确率等指标,不做梯度计算4.4 源码阅读该从哪里下手
热词里有“源码解读”,很多人一听源码就头大。其实 PyTorch 的源码阅读不需要从底层 C++ 开始,入门阶段建议按这个顺序:
- 先读
torch.nn.Module的基本用法,弄懂__init__里定义层、forward里定义计算过程。 - 再看一个小型模型的
forward,比如 LeNet,理解数据形状在每一层之间是怎么流动的。 - 然后看
torch.optim下的一个优化器参数更新逻辑,比如 SGD。 - 最后再挑战一个自定义
Dataset和DataLoader的实现,理解数据批处理流程。
你不用一行行读,而是要带着问题去读:“如果我想改这个模块,应该改哪里?”源码阅读的意义不是看它写了什么,而是搞清楚哪里是边界、哪里可以扩展、哪里是写死的默认行为。
5. Dataset 与 DataLoader:真正决定项目能不能跑的往往是数据流水线
模型结构再花哨,如果数据供给不上来或者供给错了,训练照样完蛋。Dataset 和 DataLoader 是 PyTorch 数据流水线的两个核心组件。
5.1 批量处理图像时最容易出错的边界问题
当我们使用 torchvision 里的数据集时,代码看起来很简单:
from torchvision import datasets, transforms transform = transforms.Compose([ transforms.Resize((32, 32)), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)但换到自定义数据集时,最容易出错的就是“图片大小不一致”,或者“标签和图片路径没对齐”。
建议从一开始就写一个统一的规则:
- 所有图片先经过
Resize统一尺寸。 - 使用
ImageFolder时,目录结构必须是根目录/类别名/图片文件.jpg。 - 每训练完一个 epoch,抽几张训练样本的
input和label打印出来看一眼,确认数据没喂错。
5.2 batch_size、shuffle、num_workers 的中庸配置
DataLoader 里最常用的参数有四个:
dataloader = DataLoader( dataset, batch_size=32, # 每次取多少条样本 shuffle=True, # 每个 epoch 是否打乱顺序 num_workers=2, # 用几个子进程加载数据 pin_memory=True # 是否锁页内存,可以加速 CPU 到 GPU 的传输 )- batch_size:太小则梯度更新不稳定、训练慢;太大会显存溢出。常见起点是 32、64、128。显存不够时优先减半。
- shuffle:训练集建议
True,测试集建议False。 - num_workers:如果数据读取太慢,可以增大这个值,但不是越大越好,Windows 下偶尔会引发多进程报错,可以先设为 0 跑通,再逐步调大。
- pin_memory:如果数据在 GPU 上训练,通常建议设置
True。
注意:先不要一上来就把
num_workers拉满,它会显著增加内存占用和进程管理开销。先用小样本把训练流程跑通,再加并行加载。
5.3 数据不均衡和样本量不足时的工程应对
热词里有“深度学习样本数量少的缺点”,这是一个非常现实的工程问题。样本量少时,最直接的问题是模型容易过拟合,训练集 loss 很低,验证集准确率上不去。
应对手段通常有这几类:
- 数据增强:对图像做随机翻转、裁剪、旋转、颜色扰动等。torchvision 的
transforms提供了现成组件。 - 模型简化:减少层数、减小隐藏层维度、加 Dropout。
- 正则化:在 loss 里加权重衰减(weight decay)。
- 迁移学习:先加载一个在大数据集上预训练好的模型,只训练最后几层,这样可以用很小的数据量取得相对可用的结果。
这一类问题没有银弹。关键是你要通过实验观察验证集的表现,而不是只盯着训练集 loss 是否下降。
6. 一个真正可以跑通的项目:从 LeNet 到 CIFAR-10
到了这一步,你终于可以从“知道”变成“做到”。我们用一个非常经典的组合来走完整条流水线:LeNet 网络 + CIFAR-10 数据集。
6.1 最小可运行代码
下面这段代码示例覆盖了:数据加载、模型定义、训练循环、验证循环。建议不要直接复制跑完就完事,而是手动敲一遍,一行一行理解它的作用。
import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transforms # 1. 数据预处理 transform = transforms.Compose([ transforms.Resize((64, 64)), # LeNet 经典输入是 32x32,这里放到 64 更稳 transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) batch_size = 64 trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) testset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) trainloader = DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=2) testloader = DataLoader(testset, batch_size=batch_size, shuffle=False, num_workers=2) # 2. 定义 LeNet 模型 class LeNet(nn.Module): def __init__(self, num_classes=10): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 6, kernel_size=5, stride=1, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2), nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2), ) self.classifier = nn.Sequential( nn.Linear(16 * 14 * 14, 120), nn.ReLU(), nn.Linear(120, 84), nn.ReLU(), nn.Linear(84, num_classes) ) def forward(self, x): x = self.features(x) x = x.view(x.size(0), -1) x = self.classifier(x) return x # 3. 初始化模型、损失函数、优化器 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = LeNet().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) # 4. 训练循环 num_epochs = 5 for epoch in range(num_epochs): model.train() running_loss = 0.0 for i, (images, labels) in enumerate(trainloader): images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 100 == 99: print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}], Loss: {running_loss/100:.4f}') running_loss = 0.0 # 5. 验证 model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in testloader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print(f'Test Accuracy: {100 * correct / total:.2f}%')这段代码有几个关键细节,值得你反复看:
model.train()和model.eval()不是可有可无的摆设。有些层(如 Dropout、BatchNorm)在训练和推理阶段的行为不一样。optimizer.zero_grad()必须在每次 backward 之前,防止梯度累积。torch.max(outputs, 1)返回每一行最大值和对应下标,下标就是预测类别。with torch.no_grad()在验证阶段禁用梯度计算,既省内存,又避免意外修改计算图。
6.2 每一步都要能回答“为什么”
很多人跑通之后,我会建议他做一件事:改一个参数,观察会怎样。比如:
- 把
batch_size从 64 改成 16,训练时间、准确率会有什么变化? - 把 SGD 换成 Adam,loss 下降速度有什么不同?
- 去掉
transforms.Normalize那行,模型的 loss 还降得下去吗? - 把 LeNet 的
padding=2改成padding=0,会报什么错?
这些实验比刷十节课都管用。因为你只有亲手制造过错误、亲手解决过错误,才能真正理解框架为什么要这样设计。
6.3 训练日志怎么看:loss 下降缓慢可能不是模型问题
训练过程中最常见的问题就是 loss 不降,或者降得极慢。很多人第一反应是调模型结构,但排查顺序应该更理性:
- 先确认数据没问题:打印一两张图片和对应标签,看是不是图片和标签错位了。
- 再确认 loss 在正常范围:如果是分类任务,刚初始化时 loss 大约在
log(类别数)附近,如果偏差很大,说明初始化或数据处理有问题。 - 然后看 LR 是否合适:学习率太大容易发散,太小则下降很慢。常见起点是
0.01或0.001。 - 最后才是改模型结构:很多“loss 不降”的案例,最后查出来是数据归一化做错了,或者标签从 1 开始而不是从 0 开始。
7. 从项目到工程:训练稳定之外,还要考虑部署和精度选型
一旦模型在验证集上表现满意,下面就要考虑“怎么把模型用起来”。这一步也是深度学习和普通软件工程分道扬镳的地方。
7.1 模型保存格式:pt / pth / onnx 的区别
PyTorch 里常见的模型保存格式有几种:
.pt/.pth:PyTorch 原生的模型文件。通常有两种保存策略:只保存模型参数,或保存整个模型。.onnx:开放神经网络交换格式,可以跨框架导出到 TensorRT、ONNX Runtime、OpenVINO 等推理引擎,部署时更通用。
最简单的保存和加载方式:
# 保存参数(推荐) torch.save(model.state_dict(), "model.pth") # 加载参数 model = LeNet() model.load_state_dict(torch.load("model.pth")) model.eval()注意,state_dict只保存参数,不保存模型结构,加载时需要先实例化一个同结构的模型。如果你只依赖整模型保存,在框架版本升级后可能遇到兼容性问题。
7.2 部署必懂的浮点数:FP32、FP16、BF16、TF32
热词里有一个专业词组:“FP32、FP16、BF16、TF32 浮点数格式详解与实战选型”。这个内容在模型部署和加速训练时绕不开。简洁版解释如下:
- FP32:单精度浮点数,默认的训练精度。稳定,但占用显存大、计算相对慢。
- FP16:半精度浮点数,能用一半显存,计算更快,但精度范围较小,容易出现溢出或精度损失。深度学习加速卡通常对 FP16 有专门优化。
- BF16:Brain Floating Point,指数位比 FP16 多、尾数位少,表示的范围比 FP16 大很多,更适合训练场景。它在较新的 GPU 上支持良好。
- TF32:TensorFloat-32,是某些 GPU 上的一种特殊格式,介于 FP32 和 FP16 之间,主要用于矩阵乘法加速。它不是一个完全独立的存储格式,更像是一种在 Tensor Core 上加速 FP32 矩阵乘法的截断策略。
选型思路大致是:
- 训练阶段,默认用 FP32。显存不够再考虑混合精度(AMP),PyTorch 自带
torch.cuda.amp可以方便地把部分计算自动切到 FP16/BF16。 - 推理阶段,如果希望减小模型体积和提升速度,再把模型转成 FP16 或 INT8 量化。但每一轮精度压缩后都要在真实数据上验证准确率变化,不能只看文件名。
注意:混合精度不是把整个模型强制转换成 FP16,而是由框架自动选择哪些计算可以用更低的精度,哪些还必须用 FP32。强制转换常常导致梯度不稳定,这也是很多人在迁移到低精度训练时踩坑的原因。
7.3 视觉项目里的常见三方库边界
热词里提到“计算机视觉三方库”。实际工程中,PyTorch 之外还有几个常见视觉库,它们常常混用,但边界要搞清楚:
| 库 | 定位 | 和 PyTorch 的关系 |
|---|---|---|
| OpenCV | 图像读取、预处理、传统视觉算法 | 通常在进入模型前处理图像 |
| torchvision | 数据集、预训练模型、图像变换工具 | PyTorch 官方视觉库 |
| Pillow | 基础图像读写和处理 | 更轻量,常用于数据预处理 |
| albumentations | 数据增强库 | 可以搭配 PyTorch 使用,增强策略更丰富 |
如果你只是做分类、检测、分割的标准流程,torchvision 足够;如果涉及复杂的数据增强或传统视觉预处理,可以考虑引入库的方式。但不要一上来就把所有库都装一遍,项目用到什么再加什么。
7.4 谈到“深度学习检测加速”,先分清训练加速还是推理加速
热词里有“Halcon 深度学习检测加速”“深度学习模型部署”这类词。我想强调一个容易混淆的地方:训练加速和推理加速是两回事。
- 训练加速:目标是尽量缩短模型收敛时间。手段包括 GPU 并行、混合精度、分布式训练、更高效的 DataLoader。
- 推理加速:目标是让模型在部署环境里尽快给出结果。手段包括模型量化、剪枝、蒸馏、使用专门的推理引擎。
很多人误以为“模型部署就是把这个 pth 文件加载进来 then 跑一下”,结果到了生产环境发现延迟不达标。这是两种思维模式,越早区分越好。
8. 遇到问题别慌,按这条链路排查
PyTorch 报错信息千奇百怪,但大多数问题都可以用同一条链路来排查。我把这套链路总结成一个“从现象到根因”的顺序:
现象(报错 / 卡住 / 结果异常) → 输入(数据格式、路径、标签) → 环境(Python 版本、PyTorch 版本、CUDA 是否可用) → 数据管道(Dataset、DataLoader、形状、dtype) → 模型(forward、参数初始化、层配置) → 损失函数(loss 值范围、是否收敛) → 设备(CPU/GPU 是否一致、显存是否溢出) → 日志(从哪个 epoch/step 开始异常)8.1 一个实际的报错案例走查
假设你遇到这个报错:
RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu!按上面的链路来排查:
- 现象已经告诉你:tensor 不在同一个设备上。
- 输入:检查你传入模型的 images 和 labels 有没有都执行了
.to(device)。 - 环境:确认
torch.cuda.is_available()是否返回True。 - 数据管道:检查 DataLoader 返回的 tensor 有没有混用 CPU 和 GPU。
- 模型:查看模型初始化时是否某些 buffer(如 BatchNorm 的 running_mean)留在了 CPU 上。
- loss 和设备:确认 criterion 计算时输入是否都在 GPU 上。
大多数情况下,这个报错就是“某个地方忘了.to(device)”,或者你用了多个模型、多个 tensor,有一部分没搬。
8.2 排查时最容易忽略的三个点
- 模型里的 buffer 和参数可能不在同一设备。特别是使用了 BatchNorm 或某些带缓存状态的层时,
model.to(device)会把参数和 buffer 一起搬,但如果你手动创建了额外的 tensor,就要自己负责搬运。 - DataLoader 的 num_workers 在 Windows 下有特殊坑。Windows 下多进程加载数据时,必须把主逻辑放在
if __name__ == "__main__":里,否则可能无限递归启动子进程。 - loss 变成 NaN 不一定是学习率太大。先检查数据里有没有 NaN 值、标签是不是越界了、损失函数有没有用到
log(0)之类的操作。
8.3 防患于未然:养成三个小习惯
- 每个新项目开始前,先跑一个 batch 的数据通过模型,确认形状和 loss 值正常,再开始完整训练。
- 每一个 epoch 结束时打印验证指标,不要只靠训练 loss 判断模型好坏。
- 保存模型时把
epoch、optimizer、best_acc一起保存,方便恢复训练和对比。
结语:先把最小闭环跑通,再谈优化
回看整篇文章,我没有给你一份完整的 API 手册,也没有写一个复杂的大项目。我只想帮你建立一条主线:深度学习入门,不是学会 PyTorch 的每个函数,而是建立一条从数据到模型到训练再到部署的完整路径。
在这条路径上,你每一步都会遇到问题。遇到问题的第一反应不应该是我打开搜索引擎搜“报错怎么解决”,而是先想清楚:我现在卡在哪一层?是数据没对,是模型结构有问题,还是只是设备没对齐?
如果你能看到这里,我建议你的下一步只有一件事:把上面那个 LeNet + CIFAR-10 的代码自己敲一遍,然后做三个修改实验(改 batch size、改优化器、去掉 normalize)。做完这三个实验,你对 PyTorch 的理解会比刷十遍教程更深。
