深度学习这个大坑,很多人不是不想入,而是被环境配置、张量操作、反向传播一波劝退。尤其是 PyTorch 这套框架,网上教程要么只讲 API 不解释原理,要么一上来就是大段模型代码,新手根本跟不上。本文打算换一种方式,用“三天吃透”的思路,把 PyTorch 从环境搭建到完整训练流程拆开讲清楚:第一天搞定张量与自动求导,第二天学会用 nn.Module 搭网络,第三天跑通一个完整的图像分类实战并解决常见报错。内容覆盖 PyTorch 安装、数据加载、模型定义、训练验证、GPU 加速以及高频排错,适合零基础入门,也适合想快速捡起 PyTorch 的开发者在项目中直接复用。
1. PyTorch 到底是什么,为什么大家都在用
1.1 从“研究框架”到“工业标配”
PyTorch 是一个基于 Python 的深度学习框架,核心是“动态计算图”。通俗地讲,你在写代码的时候,模型结构是逐行构建的,每一行张量运算都会被框架自动记录,反向传播时梯度会自动算好。这种“define by run”的方式,让调试变得非常自然,print中间结果、打断点、随意改网络结构都能做到,所以学术研究和工业落地都大量选择它。
和静态图框架相比,PyTorch 的入门成本低得多。你不需要先定义完整计算图再执行,而是“写 Python 就是写模型”。这也是很多高校课程、Kaggle 比赛、开源项目默认使用 PyTorch 的原因。
1.2 PyTorch 在深度学习生态中的位置
深度学习框架解决的核心问题有三个:张量计算、自动求导、模型部署。PyTorch 在这三块都有成熟方案:
- 张量计算:
torch.Tensor对标 NumPy 的ndarray,但支持 GPU 加速。 - 自动求导:
torch.autograd记录计算图,自动计算梯度。 - 模型部署:
torch.jit、torch.onnx、TorchScript 等工具链可以把训练好的模型导出到生产环境。
另外,Hugging Face Transformers、diffusers、Ultralytics YOLO 等主流模型库底层都基于 PyTorch,学会它之后再学这些高级库会顺畅很多。
1.3 三天学习路线的设计思路
很多教程喜欢把概念一次性倒完,这是新手最容易放弃的原因。我建议把 PyTorch 拆成三层:
| 学习阶段 | 核心任务 | 关键知识点 |
|---|---|---|
| 第一天 | 理解计算单元 | 张量、数据类型、GPU 张量、自动求导 |
| 第二天 | 学会搭建模型 | nn.Module、线性层、卷积层、激活函数 |
| 第三天 | 跑通完整流程 | Dataset、DataLoader、训练循环、评估、模型保存 |
每一层只解决一个问题,再通过实战代码串联起来。下面按这个路线展开。
2. 环境准备:从零搭建 PyTorch 开发环境
2.1 安装方式选型
在开始写代码之前,先保证环境干净。推荐使用 Anaconda 创建独立虚拟环境,避免和系统 Python 环境互相污染。
安装步骤大致如下:
- 安装 Anaconda 或 Miniconda。
- 创建 Python 虚拟环境。
- 安装 CUDA 版或 CPU 版 PyTorch。
- 验证安装。
2.2 使用 conda 创建虚拟环境
打开终端,执行:
conda create -n pytorch_env python=3.10 -y conda activate pytorch_env这里建议指定 Python 版本,不要直接装最新版,因为 PyTorch 对 Python 版本的适配存在轻微滞后。示例中选用 Python 3.10 是一个兼容性较好的常见配置。
2.3 安装 PyTorch
PyTorch 的安装命令建议到 PyTorch 官网根据操作系统和 CUDA 版本生成。以常见环境为例:
# CPU 版本 pip install torch torchvision torchaudio # CUDA 12.x 版本(示例,以官网生成的实际命令为准) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121如果你使用 NVIDIA GPU,建议先确认自己的 CUDA 驱动版本:
nvidia-smi注意:nvidia-smi显示的 CUDA 版本是驱动支持的最高版本,安装 PyTorch 时选择小于等于这个版本的 CUDA 即可,不一定要求完全一致。如果机器没有 NVIDIA GPU,安装 CPU 版本也一样能学完本文所有内容,只是训练速度会慢一些。
2.4 验证安装是否成功
python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"预期输出类似:
2.3.0 True其中第二行如果是True,说明 GPU 可用;如果输出False,说明当前安装的是 CPU 版本或 GPU 驱动未正确识别。遇到这个问题先不要慌,后文专门有一节讲排查思路。
2.5 在 Jupyter Notebook 或 IDE 中配置
想用 Jupyter Notebook 的话,在虚拟环境中执行:
pip install jupyter notebook python -m ipykernel install --user --name pytorch_env之后启动 Jupyter 时选择pytorch_env内核即可。平时写代码推荐 VS Code + Python 插件,选中解释器路径为虚拟环境里的 Python,代码补全和调试体验都很好。
3. 第一天:张量与自动求导
3.1 认识 Tensor:深度学习的“最小单元”
PyTorch 中最基础的数据结构是torch.Tensor,可以理解为一个支持 GPU 加速的多维数组。它和 NumPy 数组很像,但多了自动求导和设备管理能力。
创建张量的几种常用方式:
import torch # 从列表创建 a = torch.tensor([1, 2, 3]) print(a) # 创建全零、全一张量 zeros = torch.zeros(2, 3) ones = torch.ones(2, 3) # 创建随机张量 rand = torch.rand(2, 3) randn = torch.randn(2, 3) # 标准正态分布 # 创建指定数据类型的张量 float_tensor = torch.tensor([1.0, 2.0], dtype=torch.float32)运行结果:
tensor([1, 2, 3])这里要注意数据类型这个概念。深度学习中最常用的是torch.float32,也就是单精度浮点数。模型参数一般默认是 float32,如果数据是整数类型,参与某些计算时会报错,需要先转换。
3.2 Tensor 的基本运算
张量支持加减乘除、矩阵乘法、维度变换等常见操作,语法和 NumPy 保持高度一致:
a = torch.tensor([[1.0, 2.0], [3.0, 4.0]]) b = torch.tensor([[5.0, 6.0], [7.0, 8.0]]) # 逐元素运算 print(a + b) print(a * b) # 矩阵乘法 print(torch.matmul(a, b)) print(a @ b) # 等价写法 # 维度变换 print(a.reshape(4, 1)) print(a.T) # 转置 # 聚合操作 print(a.sum()) print(a.mean())需要特别提醒:*是逐元素相乘,@或torch.matmul才是矩阵乘法。很多新手在写全连接层时把这两者搞混,导致输出维度完全不对。
3.3 梯度计算:autograd 的核心用法
自动求导是 PyTorch 最核心的能力。只要把张量的requires_grad设为True,PyTorch 就会在后续运算中自动构建计算图,我们只需要调用backward()就能得到梯度。
看一个最简单的例子,求函数 ( y = x^2 + 2x + 1 ) 在 ( x = 3 ) 处的导数:
x = torch.tensor(3.0, requires_grad=True) y = x ** 2 + 2 * x + 1 y.backward() print(x.grad) # 输出 tensor(8.0)手动验证一下:( y' = 2x + 2 ),代入 ( x=3 ) 结果是 8,和输出一致。
在实际训练中,计算图通常是这样的流程:
- 前向传播:输入数据经过模型得到预测值。
- 计算损失:预测值和真实标签之间的差距。
- 反向传播:调用
loss.backward(),框架自动计算每个参数的梯度。 - 更新参数:优化器根据梯度更新模型参数。
上面的例子虽然简单,但已经包含了 PyTorch 训练过程的本质:变量、运算、梯度、更新。
3.4 在 GPU 上使用张量
深度学习之所以能处理大规模数据,离不开 GPU 并行计算。把张量放到 GPU 上很简单:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") x = torch.randn(1000, 1000, device=device) y = torch.matmul(x, x)这里的关键是检查torch.cuda.is_available()的结果。在后续书写训练代码时,通常会在开头定义device,然后把模型和数据统一移动到该设备上。
如果希望代码在有没有 GPU 的机器上都能运行,就用上面的device写法,不要硬编码"cuda"。
4. 第二天:用 nn.Module 搭建神经网络
4.1 为什么需要 nn.Module
手写张量运算能理解原理,但真正搭建神经网络时,我们还需要管理大量参数、层结构和前向传播逻辑。PyTorch 提供了torch.nn.Module作为所有神经网络模型的基类,只要继承它,框架会自动跟踪模型中的参数和子模块。
一个最简单的自定义模型如下:
import torch.nn as nn class MyModel(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(10, 1) def forward(self, x): return self.fc(x) model = MyModel() print(model)__init__方法里定义网络结构,forward方法里定义数据如何从输入变成输出。PyTorch 会根据forward的执行过程自动反向传播,不需要手动写梯度。
4.2 常用网络层
搭建常见网络结构时,以下几类层会频繁用到:
import torch.nn as nn # 全连接层:将 784 维映射到 128 维 fc = nn.Linear(784, 128) # 卷积层:输入 3 通道,输出 16 通道,卷积核 3x3 conv = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1) # 池化层:2x2 最大池化 pool = nn.MaxPool2d(kernel_size=2, stride=2) # 激活函数 relu = nn.ReLU()解释一下卷积层参数:
in_channels:输入通道数,RGB 图像就是 3。out_channels:卷积核数量,也是输出通道数。kernel_size:卷积核大小。padding:边缘填充,常用 1 保持特征图尺寸不变。
池化层的作用是降采样,减少特征图尺寸,同时保留主要特征。最大池化是取区域内最大值,能够提取纹理、边缘等显著信息。CNN 中卷积层负责提取特征,池化层负责压缩特征,两者交替堆叠是图像任务的经典结构。
4.3 组装一个简单的 CNN
把上面的层组合起来,可以搭出一个适合小规模图像分类任务的卷积神经网络:
import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self, num_classes=10): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 16, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(2, 2), nn.Conv2d(16, 32, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(2, 2), ) self.classifier = nn.Sequential( nn.Flatten(), nn.Linear(32 * 8 * 8, 128), nn.ReLU(), nn.Linear(128, num_classes), ) def forward(self, x): x = self.features(x) x = self.classifier(x) return x注意self.classifier中nn.Linear(32 * 8 * 8, 128)的输入维度是根据特征图尺寸算出来的。如果输入图片是 3×32×32,经过两次MaxPool2d尺寸变为 8×8,通道数为 32,所以展平后是32 * 8 * 8。如果换了输入尺寸,这里要相应调整。
4.4 损失函数与优化器
模型输出的是原始得分,需要和真实标签计算“差距”,这个差距就是损失。分类任务最常用的是交叉熵损失:
criterion = nn.CrossEntropyLoss()回归任务一般用均方误差:
criterion = nn.MSELoss()优化器负责根据梯度更新参数。最常用的是 Adam:
import torch.optim as optim optimizer = optim.Adam(model.parameters(), lr=0.001)model.parameters()返回模型中所有可学习参数,lr是学习率。学习率控制每次参数更新的步长,太大容易震荡不收敛,太小训练速度慢。0.001 是很多任务中比较稳妥的初始值。
5. 第三天:跑通完整训练流程
5.1 准备数据集:Dataset 与 DataLoader
PyTorch 提供了torch.utils.data.Dataset和DataLoader。Dataset负责定义“如何读取一条数据”,DataLoader负责把数据集打包成批量数据,并支持打乱顺序、多进程加载。
以 MNIST 手写数字识别为例,PyTorch 自带该数据集,使用起来非常方便:
import torch from torch.utils.data import DataLoader from torchvision import datasets, transforms transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,)) ]) train_dataset = datasets.MNIST( root="./data", train=True, download=True, transform=transform ) test_dataset = datasets.MNIST( root="./data", train=False, download=True, transform=transform ) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)这里transform做了两件事:把图片从 PIL 对象转成张量,再做标准化,让像素值范围从 [0, 1] 变成 [-1, 1],有利于模型收敛。
实际项目中如果使用自己的图片数据,一般用torchvision.datasets.ImageFolder配合文件夹目录结构,或者自定义 Dataset 类。自定义 Dataset 需要实现__len__和__getitem__两个方法。
5.2 训练一个真实模型:MNIST 手写数字识别
MNIST 是深度学习的“Hello World”。每张图片是 28×28 的灰度图,目标是识别 0 到 9 的数字。这里实现一个完整的可运行脚本,包含训练、验证、模型保存三个部分。
import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transforms device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print("使用设备:", device) # 1. 准备数据 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,)) ]) train_dataset = datasets.MNIST( root="./data", train=True, download=True, transform=transform ) test_dataset = datasets.MNIST( root="./data", train=False, download=True, transform=transform ) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False) # 2. 定义模型 class MLP(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(28 * 28, 128) self.fc2 = nn.Linear(128, 10) self.relu = nn.ReLU() def forward(self, x): x = x.view(x.size(0), -1) # 展平为 (batch_size, 784) x = self.relu(self.fc1(x)) x = self.fc2(x) return x model = MLP().to(device) # 3. 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # 4. 训练循环 epochs = 5 for epoch in range(epochs): model.train() total_loss = 0.0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) # 梯度清零 optimizer.zero_grad() # 前向传播 outputs = model(images) # 计算损失 loss = criterion(outputs, labels) # 反向传播 loss.backward() # 更新参数 optimizer.step() total_loss += loss.item() avg_loss = total_loss / len(train_loader) print(f"Epoch [{epoch + 1}/{epochs}] Loss: {avg_loss:.4f}") # 5. 验证 model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in test_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print(f"测试集准确率: {100 * correct / total:.2f}%") # 6. 保存模型 torch.save(model.state_dict(), "mnist_mlp.pth")5.3 训练循环中的关键代码解读
上面的训练代码虽然不长,但每行都有明确作用。逐段解释:
optimizer.zero_grad():PyTorch 的梯度是累积的,如果不每次清零,下一轮会把之前的梯度累加进去,参数更新会乱掉。loss.backward():触发反向传播,计算所有参数的梯度。optimizer.step():根据梯度更新参数。model.train()和model.eval():切换训练/评估模式。某些层(如 Dropout、BatchNorm)在两种模式下行为不同,必须准确切换。with torch.no_grad():验证阶段不需要计算梯度,关闭梯度记录可以节省内存、加快速度。
5.4 运行结果与准确率说明
按默认设置训练 5 个 epoch,简单的两层 MLP 在 MNIST 测试集上通常能达到 97% 到 98% 的准确率。GPU 约十几秒跑完,CPU 也只需要几分钟,非常适合作为第一个完整实验。
如果想进一步把准确率提升到 99% 以上,可以换成卷积网络、增加 epoch、加入 Dropout 等正则化手段。但理解训练循环本身,比盲目刷高准确率更重要。
5.5 多分类任务输出维度与 CrossEntropyLoss 的匹配
这里有一个很常见的坑:CrossEntropyLoss的输入和标签格式。
- 模型输出:形状为
(batch_size, num_classes)的原始得分,不要手动加 Softmax,因为CrossEntropyLoss内部已包含 Softmax 操作。 - 标签:形状为
(batch_size,)的整数张量,每个元素是 0 到 9 的类别索引。
如果输出维度是(batch_size,)而标签是(batch_size, 1),或者反过来,都会报维度不匹配的错误。遇到这类报错优先检查这两个张量的形状。
6. 常见问题与排查思路
6.1 常见报错速查表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
torch.cuda.is_available()返回 False | 安装了 CPU 版 PyTorch,或 GPU 驱动未装好 | 运行nvidia-smi检查驱动,使用官网命令重新安装 CUDA 版 PyTorch |
| 训练时 A 卡不识别 | PyTorch 默认只支持 NVIDIA CUDA,AMD 需用 ROCm 版本 | 改用 CPU 或换用 ROCm 版 PyTorch,按官方文档操作 |
RuntimeError: shape '[...]' is invalid for input of size [...] | 张量维度变换错误 | 打印x.shape检查数据流,重点检查view、Linear的输入尺寸 |
CUDA out of memory | batch size 过大或显存不足 | 调小 batch size,减少模型复杂度,或使用混合精度训练 |
loss变成nan | 学习率过大、数据包含异常值、梯度爆炸 | 调小学习率,检查数据是否有 NaN,添加梯度裁剪 |
torch.load加载模型报错提示weights_only | PyTorch 2.6 起torch.load默认weights_only=True | 如果只是加载权重,用默认即可;如果要加载完整对象,显式传weights_only=False并确认数据可信 |
6.2 PyTorch 2.6 权重加载变化
这里单独说一下weights_only参数。在 PyTorch 2.6 中,torch.load的默认行为发生了变化,weights_only默认变为True,目的是提高安全性,防止反序列化恶意 pickle 文件。
如果你的代码之前是这样写的:
model.load_state_dict(torch.load("model.pth"))保存时用的是state_dict的话,在 2.6 中一般不需要改动。如果你保存的是完整模型对象,并且确实需要加载,可以显式指定:
state = torch.load("model.pth", weights_only=False)这里提醒一下:weights_only=False会使用 pickle 反序列化,只加载自己信任的模型文件,不要加载来路不明的文件。
6.3 GPU 驱动安装后没反应怎么办
如果你在 Ubuntu 等 Linux 系统上安装完 NVIDIA 驱动后执行nvidia-smi没反应,通常原因有:
- 驱动安装完成后没有重启系统。
- 内核模块未加载,执行
sudo modprobe nvidia尝试加载。 - 安全启动(Secure Boot)阻止了驱动加载,需要在 BIOS 中关闭或给模块签名。
- 安装的驱动版本与 GPU 型号、内核版本不匹配。
排查顺序建议:先重启,再执行nvidia-smi;如果还不行,查看内核日志确认报错信息;最后考虑重装驱动或更换版本。这一步属于环境问题,和 PyTorch 本身无关,但也确实是新手最常见的一道坎。
7. 最佳实践与工程建议
7.1 代码组织与项目结构
训练脚本不要全部堆在一个文件里。推荐按功能拆分目录:
project/ ├── configs/ # 超参数配置 │ └── config.yaml ├── data/ # 数据集 ├── models/ # 网络结构定义 │ └── mlp.py ├── utils/ # 工具函数 │ ├── dataset.py │ └── trainer.py ├── train.py # 训练入口 └── requirements.txt这样拆分的好处是:模型结构、数据处理、训练逻辑互不干扰,后续换数据集或换模型时改动范围小。
7.2 超参数管理
学习率、batch size、epoch 这些超参数不要写死在代码里。建议用配置文件或命令行参数管理:
import argparse parser = argparse.ArgumentParser() parser.add_argument("--lr", type=float, default=0.001) parser.add_argument("--batch_size", type=int, default=64) parser.add_argument("--epochs", type=int, default=10) args = parser.parse_args()这样调参时不需要改代码,也能方便地记录每次实验的配置。
7.3 模型保存与加载
推荐只保存state_dict而不是整个模型,因为前者只包含参数,体积小、兼容性好、跨环境更稳定:
# 保存 torch.save(model.state_dict(), "model.pth") # 加载 model = MLP() model.load_state_dict(torch.load("model.pth")) model.eval()加载后记得调用model.eval()。如果要在训练基础上继续微调,则调用model.train()。这是一个容易被忽略但影响很大的细节。
7.4 可复现性设置
深度学习涉及大量随机性,为了实验可以复现,建议在训练脚本开头设置随机种子:
import random import numpy as np import torch def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42)虽然某些 GPU 运算仍存在非确定性,但设置种子可以显著提高复现概率。
7.5 训练时的日志与监控
训练不要只看 loss,还要监控验证集指标。建议周期性打印或记录以下信息:
- 当前 epoch、总 epoch 数。
- 训练 loss 平均值。
- 验证集准确率/损失。
- 当前学习率。
如果使用 TensorBoard,可以这样写:
from torch.utils.tensorboard import SummaryWriter writer = SummaryWriter("runs/experiment_1") # 在每个 epoch 结束后 writer.add_scalar("Loss/train", avg_loss, epoch) writer.add_scalar("Acc/test", accuracy, epoch)可视化帮助判断模型是欠拟合、过拟合还是学习率不合适,是工程化训练中很有用的手段。
7.6 安全与生产环境注意点
- 加载预训练模型时确认来源可信,避免使用包含恶意 pickle 对象的文件。
- 在 GPU 服务器上多人共用时,设置
CUDA_VISIBLE_DEVICES指定 GPU,避免显存冲突。
CUDA_VISIBLE_DEVICES=0 python train.py- 生产环境推理时使用
torch.no_grad()并考虑转为 TorchScript 或 ONNX 格式,提高性能和部署效率。 - 模型文件要纳入版本管理,同时记录训练代码版本和数据版本,保证可追溯。
8. 总结与下一步学习路线
到这里,你已经走完了 PyTorch 从环境搭建到完整训练的基本链路。回顾一下三天路线中的关键点:
- 第一天理解张量:PyTorch 的所有计算都建立在 Tensor 上,
requires_grad和backward()构成了自动求导的基础。 - 第二天理解模型:
nn.Module是模型容器,forward定义前向传播,结合nn.Linear、nn.Conv2d、nn.MaxPool2d这些基础层可以搭出任意常见网络结构。 - 第三天理解训练闭环:Dataset 加载数据,DataLoader 批量迭代,训练循环完成前向、反向、参数更新,最后用验证集评估泛化能力。
完成本文的 MNIST 实战后,下一步建议按以下顺序深入:
- 把 MLP 换成 CNN,对比准确率和训练速度,理解卷积和池化对图像任务的作用。
- 自己实现一个自定义 Dataset,加载本地图片文件夹,完成一个真实项目的分类任务。
- 尝试训练过程中加入正则化(Dropout、Weight Decay)和数据增强,观察过拟合现象的变化。
- 学习 PyTorch 官方提供的
torchvision.models预训练模型,做迁移学习。 - 进阶方向:学习
torch.utils.data的优化、混合精度训练、多 GPU 分布式训练、模型导出部署。
在新手阶段,最容易踩的坑集中在 GPU 环境配置和维度不匹配上。前者按nvidia-smi、torch.cuda.is_available()的顺序排查;后者养成本打印shape的习惯就能解决大半。学习深度学习框架不能只看不写,建议把本文第二个完整代码复制到本地,改一改网络层数、调一调学习率,多跑几次,你会比看十篇教程收获都大。如果本文对你有帮助,可以收藏备用,也欢迎在实践中反复对照这份排错清单。