☰
三天吃透PyTorch:从张量、自动求导到完整训练实战
2026/10/26 11:20:13 网站建设 项目流程

深度学习这个大坑,很多人不是不想入,而是被环境配置、张量操作、反向传播一波劝退。尤其是 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 环境互相污染。

安装步骤大致如下:

  1. 安装 Anaconda 或 Miniconda。
  2. 创建 Python 虚拟环境。
  3. 安装 CUDA 版或 CPU 版 PyTorch。
  4. 验证安装。

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,和输出一致。

在实际训练中,计算图通常是这样的流程:

  1. 前向传播:输入数据经过模型得到预测值。
  2. 计算损失:预测值和真实标签之间的差距。
  3. 反向传播:调用loss.backward(),框架自动计算每个参数的梯度。
  4. 更新参数:优化器根据梯度更新模型参数。

上面的例子虽然简单,但已经包含了 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 memorybatch size 过大或显存不足调小 batch size,减少模型复杂度,或使用混合精度训练
loss变成nan学习率过大、数据包含异常值、梯度爆炸调小学习率,检查数据是否有 NaN,添加梯度裁剪
torch.load加载模型报错提示weights_onlyPyTorch 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没反应,通常原因有:

  1. 驱动安装完成后没有重启系统。
  2. 内核模块未加载,执行sudo modprobe nvidia尝试加载。
  3. 安全启动(Secure Boot)阻止了驱动加载,需要在 BIOS 中关闭或给模块签名。
  4. 安装的驱动版本与 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 实战后,下一步建议按以下顺序深入:

  1. 把 MLP 换成 CNN,对比准确率和训练速度,理解卷积和池化对图像任务的作用。
  2. 自己实现一个自定义 Dataset,加载本地图片文件夹,完成一个真实项目的分类任务。
  3. 尝试训练过程中加入正则化(Dropout、Weight Decay)和数据增强,观察过拟合现象的变化。
  4. 学习 PyTorch 官方提供的torchvision.models预训练模型,做迁移学习。
  5. 进阶方向:学习torch.utils.data的优化、混合精度训练、多 GPU 分布式训练、模型导出部署。

在新手阶段,最容易踩的坑集中在 GPU 环境配置和维度不匹配上。前者按nvidia-smi、torch.cuda.is_available()的顺序排查;后者养成本打印shape的习惯就能解决大半。学习深度学习框架不能只看不写,建议把本文第二个完整代码复制到本地,改一改网络层数、调一调学习率,多跑几次,你会比看十篇教程收获都大。如果本文对你有帮助,可以收藏备用,也欢迎在实践中反复对照这份排错清单。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询