PyTorch 入门介绍:从零开始学习深度学习利器
引言:为何选择 PyTorch?
在人工智能领域,深度学习已成为推动技术进步的核心引擎。从图像识别、自然语言处理到推荐系统和自动驾驶,深度学习无处不在。而要构建和训练复杂的神经网络模型,一个强大、灵活且易于使用的深度学习框架是必不可少的工具。在众多框架中,PyTorch 以其“Pythonic”的风格、动态计算图和卓越的研究友好性脱颖而出,迅速成为学术界和工业界的热门选择。
本文将带你踏上 PyTorch 的学习之旅,从最基础的概念开始,逐步深入到构建和训练一个简单的神经网络模型。无论你是一名学生、研究人员还是希望转型的开发者,只要具备基本的 Python 编程知识,本文都将是你迈入 PyTorch 世界的坚实第一步。
我们将详细探讨以下内容:
- PyTorch 是什么? – 了解 PyTorch 的定义和背景。
- 为何选择 PyTorch? – 探究 PyTorch 的核心优势。
- 环境搭建 – 如何安装和配置 PyTorch。
- 张量 (Tensors) – PyTorch 的基础数据结构及其操作。
- 自动微分 (Autograd) – PyTorch 实现自动梯度计算的“魔法”。
- 构建神经网络 (torch.nn) – 如何使用 PyTorch 构建模型。
- 数据加载与预处理 (torch.utils.data) – 如何高效地处理数据。
- 模型训练循环 – 编写一个完整的训练过程。
- 模型保存与加载 – 如何保存和恢复你的模型。
- GPU 加速 – 利用硬件优势加速训练。
- 更进一步 – PyTorch 生态系统和学习资源推荐。
通过阅读本文,你将对 PyTorch 的核心组件和工作流程有一个清晰的认识,并能够开始构建你自己的深度学习项目。
1. PyTorch 是什么?
PyTorch 是一个开源的机器学习库,主要用于构建和训练深度神经网络。它由 Facebook (现 Meta) 的 AI 研究团队开发并维护。PyTorch 的设计理念是提供一个灵活且高效的平台,既适用于深度学习的研究探索,也能够支持生产环境的应用部署。
从技术角度看,PyTorch 提供了:
- 强大的张量计算:支持 GPU 加速,类似 NumPy,但功能更强大。
- 动态计算图:允许在运行时构建和修改计算图,这为调试和一些复杂的模型结构(如 RNN)带来了极大的便利。
- 深度学习模块:
torch.nn
模块提供了构建神经网络所需的各种层、激活函数、损失函数等组件。 - 自动微分:
autograd
模块能够自动计算张量上的所有操作的梯度,这对于实现反向传播算法至关重要。 - 一套丰富的工具集:包括数据加载、优化器、模型保存与加载等功能。
2. 为何选择 PyTorch?核心优势解析
PyTorch 之所以受到广泛欢迎,主要归功于以下几个显著的优势:
2.1 Pythonic 的风格
PyTorch 的 API 设计非常符合 Python 的编程习惯,直观且易于理解。如果你熟悉 Python 及其科学计算库(如 NumPy),学习 PyTorch 会感觉非常自然和顺畅。它不像一些其他框架那样需要学习一套全新的编程范式,而是让你感觉就像在使用一个功能强大的 Python 库。这种亲和力降低了入门门槛,使得开发者可以更快地上手。
2.2 动态计算图 (Dynamic Computation Graph)
这是 PyTorch 相较于早期 TensorFlow 等框架(静态图)的一个核心特点。在 PyTorch 中,计算图是根据你的代码执行顺序动态构建的。每一次前向传播都会构建一个新的计算图。
优点:
- 易于调试: 你可以像调试普通 Python 代码一样设置断点、检查中间变量的值,这在构建复杂模型或排查问题时非常方便。
- 灵活性高: 对于控制流(如循环、条件判断)依赖较强的模型(如自然语言处理中的 RNN),动态图可以很自然地处理变长输入序列等情况。模型结构可以根据输入数据或计算过程动态调整。
- 研究友好: 研究人员可以快速地进行模型设计和实验,迭代速度快。
虽然现代 TensorFlow 也引入了 Eager Execution 模式来支持动态图,但 PyTorch 在这方面起步较早,并将其作为默认行为。
2.3 活跃的社区与丰富的资源
PyTorch 拥有一个庞大且活跃的全球社区。这意味着当你遇到问题时,很容易在官方论坛、GitHub Issues、Stack Overflow 等平台上找到答案或获得帮助。大量的教程、开源项目和预训练模型也使得学习和应用 PyTorch 变得更加容易。
2.4 强大的 GPU 加速能力
PyTorch 对 GPU 加速提供了原生且高效的支持。利用 NVIDIA 的 CUDA 技术,PyTorch 可以将大量的并行计算任务转移到 GPU 上执行,极大地缩短模型训练时间,这对于处理大规模数据集和复杂模型至关重要。将计算从 CPU 转移到 GPU 通常只需要几行代码。
2.5 生产环境支持
虽然 PyTorch 最初在研究领域更受欢迎,但近年来其在生产环境的应用也越来越广泛。通过 TorchScript,PyTorch 提供了将模型从 Python 转化为可独立运行、跨平台部署的格式的能力,支持 C++, Java, mobile (iOS/Android) 等多种环境。此外,还有 TorchServe 等工具用于模型部署。
2.6 丰富的功能库与生态系统
PyTorch 不仅仅是核心库,它还拥有一个不断壮大的生态系统,包括 TorchVision (计算机视觉)、TorchText (自然语言处理)、TorchAudio (音频处理) 等领域特定的库,以及 PyTorch Lightning (简化训练代码)、Ignite (高级训练抽象) 等工具,极大地提高了开发效率。
综上所述,PyTorch 凭借其易用性、灵活性、高性能和强大的社区支持,成为了进行深度学习研究和应用开发的优秀选择。
3. 环境搭建:开始你的 PyTorch 之旅
开始使用 PyTorch 的第一步是安装它。PyTorch 团队提供了非常方便的安装方式,支持 pip 和 conda 包管理器,并且可以根据你的操作系统、Python 版本以及是否需要 GPU 支持来选择合适的安装命令。
访问 PyTorch 官方安装指南页面 (https://pytorch.org/get-started/locally/
) 是获取最准确安装命令的最佳方式。以下是一个通用的安装步骤说明:
-
确认 Python 环境: PyTorch 需要 Python 环境。建议使用 Anaconda 或 Miniconda 来管理你的 Python 环境,这有助于避免包之间的冲突。创建一个新的虚拟环境是一个好习惯:
bash
conda create -n my_pytorch_env python=3.8
conda activate my_pytorch_env
或者使用venv
:
bash
python -m venv my_pytorch_env
source my_pytorch_env/bin/activate # macOS/Linux
# my_pytorch_env\Scripts\activate # Windows -
选择安装方式:
- 使用 pip: 简单易用,但管理依赖可能不如 conda 方便。
- 使用 conda: 推荐,尤其是需要 GPU 支持时,conda 能更好地管理 CUDA 等底层依赖。
-
确定需求:
- 操作系统: Windows, macOS, Linux。
- 包管理器: pip 或 conda。
- Python 版本: 选择与你环境兼容的版本。
- 计算平台:
- CPU Only: 如果你的电脑没有 NVIDIA GPU 或不需要 GPU 加速,选择此项。
- CUDA: 如果你的电脑有 NVIDIA GPU,并且安装了相应的 NVIDIA 驱动,选择与你的 CUDA 版本兼容的 PyTorch 版本。检查你的 CUDA 版本可以在终端输入
nvcc --version
(如果安装了 CUDA Toolkit)或在 NVIDIA 控制面板/nvidia-smi
输出中查看。
-
生成安装命令: 访问 PyTorch 官网的安装页面,根据你的选择(OS, Package, Python, Compute Platform)会自动生成对应的安装命令。例如:
- conda 安装 (CUDA 11.8):
bash
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia - pip 安装 (CUDA 11.8):
bash
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 - conda 安装 (仅 CPU):
bash
conda install pytorch torchvision torchaudio cpuonly -c pytorch - pip 安装 (仅 CPU):
bash
pip install torch torchvision torchaudio
- conda 安装 (CUDA 11.8):
-
执行安装命令: 在你激活的虚拟环境中执行生成的命令。
-
验证安装: 安装完成后,可以在 Python 解释器中验证是否安装成功:
python
import torch
print(torch.__version__) # 打印 PyTorch 版本
print(torch.cuda.is_available()) # 检查 CUDA 是否可用
如果torch.__version__
打印出版本号,且torch.cuda.is_available()
在有 GPU 环境下返回True
,则说明安装成功。
至此,你的 PyTorch 环境就搭建好了,可以开始编写代码了。
4. 张量 (Tensors):PyTorch 的基础数据结构
张量是 PyTorch 中最核心的数据结构,它是各种计算的载体。你可以将张量理解为一个多维数组,它与 NumPy 的 ndarray
非常相似,但在功能上更为强大,特别是支持 GPU 加速和自动微分。
4.1 创建张量
PyTorch 提供了多种创建张量的方法:
-
从 Python 列表或 NumPy 数组创建:
“`python
import torch
import numpy as np从列表创建
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
print(x_data)从 NumPy 数组创建
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
print(x_np)
“` -
创建具有特定属性的张量:
“`python
# 创建一个形状为 (2, 3) 的张量,所有元素为 1
x_ones = torch.ones(2, 3)
print(x_ones)创建一个形状为 (2, 3) 的张量,所有元素为 0
x_zeros = torch.zeros(2, 3)
print(x_zeros)创建一个形状为 (2, 3) 的随机张量(服从标准正态分布)
x_rand = torch.rand(2, 3)
print(x_rand)创建一个形状为 (2, 3) 的随机张量(服从均匀分布在 [0, 1) 之间)
x_uniform = torch.rand(2, 3)
print(x_uniform)创建一个形状为 (2, 3) 的随机整数张量
x_randint = torch.randint(0, 10, (2, 3)) # 整数范围 [0, 10), 形状 (2, 3)
print(x_randint)
“` -
基于现有张量创建: 可以保留现有张量的属性(形状、数据类型等),仅改变值。
“`python
# 创建一个与 x_data 形状和数据类型相同,但值为 1 的张量
x_like_ones = torch.ones_like(x_data)
print(x_like_ones)创建一个与 x_data 形状和数据类型相同,但值为随机的张量
x_like_rand = torch.rand_like(x_data, dtype=torch.float) # 可以覆盖数据类型
print(x_like_rand)
“`
4.2 张量的属性
张量拥有重要的属性,用于描述其形状、数据类型和存储设备:
python
tensor = torch.ones(4, 4)
print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")
.shape
: 张量的形状,一个元组,表示每个维度的尺寸。.dtype
: 张量的数据类型,如torch.float32
,torch.int64
,torch.bool
等。默认通常是float32
。.device
: 张量所在的设备,如cpu
或cuda:0
(表示第一个 GPU)。
4.3 张量的操作
张量支持各种数学运算、索引、切片、形状变换等操作,很多操作与 NumPy 类似。
-
数学运算:
“`python
tensor = torch.ones(4, 4)
print(f”Original Tensor:\n{tensor}”)加法
print(f”Tensor + 5:\n{tensor + 5}”) # 广播 (Broadcasting)
print(f”Tensor + Tensor:\n{tensor + tensor}”)乘法 (元素级乘法)
print(f”Tensor * Tensor:\n{tensor * tensor}”)
矩阵乘法
tensor2 = torch.rand(4, 4)
print(f”Matrix multiplication:\n{tensor @ tensor2}”)
print(f”Matrix multiplication (alternative):\n{torch.matmul(tensor, tensor2)}”)更多运算:abs(), sin(), cos(), exp(), log(), sqrt() 等
print(f”Absolute value:\n{torch.abs(tensor – 2)}”)
“` -
索引和切片: 类似于 NumPy 数组。
“`python
tensor = torch.arange(16).reshape(4, 4)
print(f”Original Tensor:\n{tensor}”)print(f”First row: {tensor[0]}”)
print(f”First column: {tensor[:, 0]}”)
print(f”Last column: {tensor[:, -1]}”)
print(f”Subgrid 1×2: {tensor[0:1, 1:3]}”) # 行 0, 列 1 和 2
print(f”Element at (0, 0): {tensor[0, 0].item()}”) # .item() 获取标量值
“` -
形状变换: 改变张量的形状,但不改变数据。
“`python
tensor = torch.arange(12) # 形状 (12,)
print(f”Original tensor:\n{tensor}”)reshape: 改变形状,元素总数必须匹配
reshaped_tensor = tensor.reshape(3, 4) # 形状 (3, 4)
print(f”Reshaped tensor:\n{reshaped_tensor}”)view: 类似于 reshape,但通常不复制数据(如果可能)
viewed_tensor = tensor.view(4, 3) # 形状 (4, 3)
print(f”Viewed tensor:\n{viewed_tensor}”)注意: reshape 是 view 的更灵活版本,推荐使用 reshape
squeeze/unsqueeze: 增加/减少维度为 1 的维度
tensor = torch.randn(1, 3, 1, 4) # 形状 (1, 3, 1, 4)
squeezed_tensor = tensor.squeeze() # 形状 (3, 4)
print(f”Squeezed tensor shape: {squeezed_tensor.shape}”)tensor = torch.randn(3, 4) # 形状 (3, 4)
unsqueezed_tensor = tensor.unsqueeze(0) # 在第 0 维增加一个维度,形状 (1, 3, 4)
print(f”Unsqueeze(0) tensor shape: {unsqueezed_tensor.shape}”)
unsqueezed_tensor = tensor.unsqueeze(1) # 在第 1 维增加一个维度,形状 (3, 1, 4)
print(f”Unsqueeze(1) tensor shape: {unsqueezed_tensor.shape}”)
“` -
连接和拆分:
“`python
tensor1 = torch.arange(6).reshape(2, 3)
tensor2 = torch.ones(2, 3)
print(f”Tensor1:\n{tensor1}”)
print(f”Tensor2:\n{tensor2}”)连接 (按维度 0 – 行)
stacked_rows = torch.cat([tensor1, tensor2], dim=0) # 形状 (4, 3)
print(f”Stacked rows:\n{stacked_rows}”)连接 (按维度 1 – 列)
stacked_cols = torch.cat([tensor1, tensor2], dim=1) # 形状 (2, 6)
print(f”Stacked columns:\n{stacked_cols}”)堆叠 (增加新维度进行连接)
stacked_new_dim = torch.stack([tensor1, tensor2], dim=0) # 形状 (2, 2, 3)
print(f”Stacked on new dim:\n{stacked_new_dim}”)
“`
4.4 张量与 NumPy 的转换
PyTorch 张量和 NumPy 数组可以方便地相互转换:
“`python
PyTorch 张量转 NumPy
tensor = torch.ones(5)
numpy_array = tensor.numpy()
print(f”Tensor: {tensor}”)
print(f”NumPy array: {numpy_array}”)
修改 NumPy 数组,会反映到原始张量 (共享内存)
numpy_array[0] = 100
print(f”Tensor after changing NumPy: {tensor}”)
NumPy 数组转 PyTorch 张量
numpy_array = np.zeros(5)
tensor_from_numpy = torch.from_numpy(numpy_array)
print(f”NumPy array: {numpy_array}”)
print(f”Tensor from NumPy: {tensor_from_numpy}”)
修改 NumPy 数组,会反映到新的张量 (共享内存)
numpy_array[0] = 99
print(f”Tensor from NumPy after changing NumPy: {tensor_from_numpy}”)
“`
注意: 当张量在 CPU 上时,与 NumPy 数组的转换通常是共享内存的。但在 GPU 上时,转换会涉及数据复制。
4.5 张量在设备间的转移
将张量从 CPU 转移到 GPU (反之亦然) 对于利用 GPU 进行加速至关重要。
“`python
检查是否有 GPU
if torch.cuda.is_available():
device = “cuda”
else:
device = “cpu”
print(f”Using device: {device}”)
创建一个张量
tensor = torch.randn(4, 4)
将张量转移到 GPU (如果可用)
tensor_gpu = tensor.to(device)
print(f”Tensor on device: {tensor_gpu.device}”)
将张量转移回 CPU
tensor_cpu = tensor_gpu.to(“cpu”)
print(f”Tensor on device: {tensor_cpu.device}”)
“`
重要: 在进行任何操作时,确保参与计算的所有张量都在同一个设备上,否则会报错。通常的做法是将模型和数据都移到同一个设备上。
5. 自动微分 (Autograd):梯度计算的魔法
自动微分是深度学习框架的核心功能之一,它允许模型根据损失函数自动计算模型参数的梯度,这是训练神经网络所需反向传播算法的基础。PyTorch 的 autograd
模块提供了这个能力。
5.1 理解自动微分的原理
autograd
记录对张量执行的所有操作,并构建一个计算图。图中的节点代表张量,边代表操作。当需要计算梯度时(通过调用 .backward()
方法),autograd
会沿着这个图从输出张量反向遍历到输入张量,使用链式法则自动计算所有中间变量以及最终目标张量的梯度。
5.2 如何使用 Autograd
要让 PyTorch 追踪一个张量的计算历史并计算其梯度,需要在创建张量时设置 requires_grad=True
:
“`python
import torch
创建一个需要计算梯度的张量
x = torch.tensor(2.0, requires_grad=True)
print(x)
print(x.requires_grad) # True
“`
对于通过操作从 requires_grad=True
的张量派生出的张量,其 requires_grad
属性也会自动为 True
:
“`python
y = x + 3
print(y)
print(y.requires_grad) # True
z = y * y * 2
mean_z = z.mean()
print(z)
print(z.requires_grad) # True
print(mean_z)
print(mean_z.requires_grad) # True
“`
何时不需要追踪梯度?
在模型的推理阶段或进行评估时,我们通常不需要计算梯度,以节省计算资源和内存。可以使用 torch.no_grad()
上下文管理器或 .detach()
方法来停止梯度追踪:
“`python
x = torch.tensor(2.0, requires_grad=True)
y = x + 3
with torch.no_grad():
z = y * y * 2
print(z)
print(z.requires_grad) # False (在 no_grad 环境下创建的张量不追踪梯度)
a = y * y * 2 # 在 no_grad 外,追踪梯度
print(a.requires_grad) # True
b = a.detach() # 从计算图中分离张量,后续操作不再追踪梯度
print(b)
print(b.requires_grad) # False
``
detach()方法返回一个与原张量相同但不再参与梯度计算的新张量。改变 detached 张量**不会**影响原张量,反之亦然(因为它们可能共享底层存储,但计算图连接中断)。
no_grad()` 是更常用的方式,因为它简洁且适用于一个代码块。
5.3 计算梯度
调用输出张量的 .backward()
方法可以计算所有叶子张量(即用户创建的、requires_grad=True
的张量)的梯度。梯度值会累加到这些叶子张量的 .grad
属性中。
“`python
x = torch.tensor(2.0, requires_grad=True)
y = x + 3
z = y * y * 2
mean_z = z.mean() # 通常对一个标量(如损失函数)调用 backward()
对标量 mean_z 执行反向传播
mean_z.backward()
检查 x 的梯度
print(x.grad) # 输出 dz/dx
计算过程:
z = 2 * y^2
y = x + 3
dz/dy = 4 * y
dy/dx = 1
dz/dx = dz/dy * dy/dx = 4 * y * 1 = 4 * (x + 3)
当 x = 2 时, y = 5, dz/dx = 4 * 5 = 20
对于 mean_z = sum(z) / N (这里只有一个元素,N=1), d(mean_z)/dx = d(z)/dx / N
所以 mean_z.backward() 计算的是 d(mean_z)/dx
mean_z = z.mean() = (2 * (x+3)^2) / 1 = 2 * (x+3)^2
d(mean_z)/dx = 2 * 2 * (x+3) * 1 = 4 * (x+3)
x=2 时,梯度为 4 * (2+3) = 20
“`
梯度清零:
在训练循环中,梯度会在每次 backward()
调用后累加。因此,在每个训练迭代开始时,必须手动将模型的参数梯度清零,否则上一次迭代的梯度会影响当前迭代的梯度计算。
“`python
假设这是训练循环中的一步
… 前向传播计算 loss …
loss.backward() # 计算梯度
梯度已经计算并存储在参数的 .grad 属性中
在下一个迭代开始前,需要清零
optimizer.zero_grad() # 这是更常用的方式,通过优化器清零所有参数的梯度
或者手动清零某个张量 (参数):
x.grad = None # 或者 x.grad.zero_()
“`
我们将在训练循环部分更详细地讨论这一点。
autograd
是 PyTorch 实现高效、灵活反向传播的关键,使得构建和训练复杂的神经网络模型变得相对容易。
6. 构建神经网络 (torch.nn)
torch.nn
是 PyTorch 中专门用于构建神经网络的模块。它提供了一系列预定义的层(如全连接层、卷积层)、激活函数、损失函数等,并以 nn.Module
作为所有模型和层的基类。
6.1 nn.Module
nn.Module
是 PyTorch 中所有神经网络模块(层、整个模型)的基类。继承 nn.Module
的类需要实现两个核心方法:
__init__(self, ...)
: 构造函数,在这里定义模型的各种子模块(层)。这些子模块会被自动注册为模型的参数,并可以通过.parameters()
方法访问。forward(self, x)
: 定义模型的前向传播逻辑。输入x
经过一系列层和操作后,返回模型的输出。
下面是一个简单的全连接神经网络示例:
“`python
import torch
import torch.nn as nn
import torch.nn.functional as F # 通常用于激活函数等无参数操作
定义一个简单的前馈神经网络
class SimpleNN(nn.Module):
def init(self, input_size, hidden_size, output_size):
super(SimpleNN, self).init() # 调用父类构造函数
# 定义层
self.linear1 = nn.Linear(input_size, hidden_size) # 全连接层: input_size -> hidden_size
self.relu = nn.ReLU() # ReLU 激活函数
self.linear2 = nn.Linear(hidden_size, output_size) # 全连接层: hidden_size -> output_size
def forward(self, x):
# 定义前向传播过程
x = self.linear1(x)
x = self.relu(x)
x = self.linear2(x)
return x
创建模型实例
input_dim = 10
hidden_dim = 5
output_dim = 3
model = SimpleNN(input_dim, hidden_dim, output_dim)
print(model) # 打印模型结构
“`
6.2 常用层 (Common Layers)
torch.nn
提供了丰富的层类型:
- 全连接层 (Linear Layer):
nn.Linear(in_features, out_features)
实现 $y = xW^T + b$ 线性变换。 - 卷积层 (Convolutional Layer):
nn.Conv1d
,nn.Conv2d
,nn.Conv3d
用于处理序列、图像、视频等网格状数据。 - 池化层 (Pooling Layer):
nn.MaxPool1d
,nn.MaxPool2d
,nn.MaxPool3d
,nn.AvgPool2d
用于下采样,减少特征图尺寸。 - 激活函数 (Activation Functions):
nn.ReLU
,nn.Sigmoid
,nn.Tanh
,nn.Softmax
,nn.LeakyReLU
等,通常放在线性层之后引入非线性。PyTorch 也推荐使用torch.nn.functional
中的函数版本(如F.relu
),因为它们没有参数需要管理。 - 循环层 (Recurrent Layers):
nn.RNN
,nn.LSTM
,nn.GRU
用于处理序列数据。 - BatchNorm 层:
nn.BatchNorm1d
,nn.BatchNorm2d
用于规范化层输入,加速训练并提高稳定性。 - Dropout 层:
nn.Dropout
用于随机关闭神经元,防止过拟合。
6.3 损失函数 (Loss Functions)
损失函数衡量模型的输出与真实标签之间的差距。训练的目标是最小化损失函数。torch.nn
提供了常用的损失函数:
- 交叉熵损失 (Cross Entropy Loss):
nn.CrossEntropyLoss
用于多类别分类问题,通常与模型的最后一层的对数几率输出 (logits) 一起使用。 - 均方误差损失 (Mean Squared Error Loss):
nn.MSELoss
用于回归问题。 - 二元交叉熵损失 (Binary Cross Entropy Loss):
nn.BCELoss
,nn.BCEWithLogitsLoss
用于二元分类问题。BCEWithLogitsLoss
结合了 Sigmoid 和 BCELoss,数值更稳定。
“`python
示例:交叉熵损失
loss_fn = nn.CrossEntropyLoss()
模拟模型输出 (logits) 和真实标签
假设这是一个有 3 个类别的分类问题,批量大小为 2
logits = torch.randn(2, 3) # 模型输出的对数几率
labels = torch.tensor([1, 0]) # 真实标签 (类别索引)
loss = loss_fn(logits, labels)
print(f”Cross Entropy Loss: {loss.item()}”)
“`
6.4 优化器 (Optimizers)
优化器的作用是根据模型参数的梯度来更新参数,从而最小化损失函数。torch.optim
模块提供了各种优化算法:
- 随机梯度下降 (SGD):
torch.optim.SGD(model.parameters(), lr=learning_rate)
- Adam:
torch.optim.Adam(model.parameters(), lr=learning_rate)
- RMSprop:
torch.optim.RMSprop(model.parameters(), lr=learning_rate)
选择合适的优化器和学习率 (learning rate, lr) 对训练效果至关重要。Adam 通常是一个不错的起始选择。优化器需要接收模型需要优化的参数列表,这可以通过 model.parameters()
获取。
“`python
创建一个优化器
假设模型参数是 model.parameters()
learning_rate = 0.01
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
训练循环中的优化步骤:
optimizer.zero_grad() # 清零之前累积的梯度
… loss.backward() … # 计算当前梯度
optimizer.step() # 根据梯度更新参数
“`
构建神经网络就是将上述组件(层、激活函数、损失函数、优化器)有机地组合起来,定义模型的结构和训练过程。
7. 数据加载与预处理 (torch.utils.data)
在训练深度学习模型时,通常需要处理大量数据。高效地加载、预处理和批量处理数据是提高训练效率的关键。PyTorch 的 torch.utils.data
模块提供了 Dataset
和 DataLoader
这两个核心工具来解决这个问题。
7.1 Dataset
Dataset
是一个抽象类,表示数据集。任何自定义的数据集都需要继承 Dataset
并实现两个方法:
__len__(self)
: 返回数据集的大小(样本数量)。__getitem__(self, index)
: 根据索引index
获取数据集中的一个样本(通常是数据和对应的标签)。
PyTorch 也提供了许多常见数据集的实现,例如 torchvision.datasets
中的 MNIST、CIFAR10 等。
“`python
示例:一个简单的自定义 Dataset
from torch.utils.data import Dataset
class CustomDataset(Dataset):
def init(self, data, labels):
self.data = data # 数据列表或数组
self.labels = labels # 标签列表或数组
def __len__(self):
return len(self.data)
def __getitem__(self, index):
# 返回第 index 个数据样本和标签
return self.data[index], self.labels[index]
模拟一些数据
dummy_data = [torch.randn(5) for _ in range(100)] # 100个5维向量
dummy_labels = [torch.randint(0, 2, (1,)).item() for _ in range(100)] # 100个二分类标签
创建数据集实例
my_dataset = CustomDataset(dummy_data, dummy_labels)
访问数据集中的样本
print(f”Dataset size: {len(my_dataset)}”)
sample_data, sample_label = my_dataset[0]
print(f”First sample data: {sample_data}, label: {sample_label}”)
“`
7.2 DataLoader
DataLoader
封装了数据集,提供了迭代访问数据的功能,并支持批量处理、数据打乱 (shuffling) 和多进程并行加载数据。它是训练过程中实际使用的数据迭代器。
“`python
from torch.utils.data import DataLoader
创建 DataLoader
batch_size = 16
dataloader = DataLoader(my_dataset, batch_size=batch_size, shuffle=True)
迭代访问数据
for batch_idx, (data, labels) in enumerate(dataloader):
# data 是一个形状为 (batch_size, 5) 的张量
# labels 是一个形状为 (batch_size,) 的张量
print(f”Batch {batch_idx}: Data shape {data.shape}, Labels shape {labels.shape}”)
# 在这里可以将 data 和 labels 喂给模型进行训练
if batch_idx >= 2: # 只打印前3个 batch
break
“`
DataLoader 的重要参数:
dataset
: 需要加载的数据集对象 (Dataset
实例)。batch_size
: 每个批次包含的样本数量。shuffle
: 是否在每个 epoch 开始时打乱数据(训练时通常设置为True
)。num_workers
: 使用多少个子进程来加载数据(可以加速数据加载,尤其是在 CPU 密集型预处理较多时,Windows 下可能需要设置为 0 或较小值)。drop_last
: 如果数据集大小不能被batch_size
整除,是否丢弃最后一个不完整的批次(训练时通常设置为True
,保证每个批次大小一致)。
使用 Dataset
和 DataLoader
可以有效地管理和加载训练数据,是构建高效训练流程的关键步骤。
8. 模型训练循环
将前面介绍的组件(模型、损失函数、优化器、数据加载器)组合起来,就构成了完整的模型训练循环。一个典型的 PyTorch 训练循环包括以下几个步骤,并在多个 Epoch(遍历整个数据集的次数)和 Batch(每个 Epoch 内的批次)中重复执行:
“`python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import numpy as np
1. 准备数据 (使用上面定义的 CustomDataset 和 DataLoader)
模拟一些数据
dummy_data = [torch.randn(10) for _ in range(1000)] # 1000个10维向量
dummy_labels = [torch.randint(0, 3, (1,)).item() for _ in range(1000)] # 1000个3分类标签
my_dataset = CustomDataset(dummy_data, dummy_labels)
batch_size = 32
dataloader = DataLoader(my_dataset, batch_size=batch_size, shuffle=True, num_workers=0) # num_workers=0 for simplicity
2. 定义模型 (使用上面定义的 SimpleNN)
input_dim = 10
hidden_dim = 20
output_dim = 3 # 3个类别
model = SimpleNN(input_dim, hidden_dim, output_dim)
3. 定义损失函数和优化器
loss_fn = nn.CrossEntropyLoss() # 交叉熵损失适用于多分类
learning_rate = 0.01
optimizer = optim.Adam(model.parameters(), lr=learning_rate) # 使用 Adam 优化器
4. 设置设备 (GPU 或 CPU)
device = “cuda” if torch.cuda.is_available() else “cpu”
print(f”Using device: {device}”)
model.to(device) # 将模型移动到指定设备
5. 定义训练函数 (一个 epoch 的过程)
def train_one_epoch(dataloader, model, loss_fn, optimizer, device):
model.train() # 设置模型为训练模式 (会启用 Dropout, BatchNorm 等层的训练行为)
total_loss = 0
correct_predictions = 0
total_samples = 0
for batch_idx, (inputs, labels) in enumerate(dataloader):
# 5a. 将数据移动到设备
inputs, labels = inputs.to(device), labels.to(device)
# 5b. 前向传播
outputs = model(inputs)
loss = loss_fn(outputs, labels)
# 5c. 反向传播与优化
optimizer.zero_grad() # 清零梯度
loss.backward() # 计算梯度
optimizer.step() # 更新模型参数
# 5d. 记录统计信息
total_loss += loss.item() * inputs.size(0) # 累计批量损失
_, predicted = torch.max(outputs.data, 1) # 获取预测类别
total_samples += labels.size(0)
correct_predictions += (predicted == labels).sum().item() # 累计正确预测数
# 打印进度 (可选)
if (batch_idx + 1) % 50 == 0:
print(f" Batch {batch_idx+1}/{len(dataloader)}, Loss: {loss.item():.4f}")
avg_loss = total_loss / total_samples
accuracy = correct_predictions / total_samples
print(f"Epoch finished. Avg Loss: {avg_loss:.4f}, Accuracy: {accuracy:.4f}")
return avg_loss, accuracy
6. 定义评估函数 (一个 epoch 的过程, 不计算梯度)
def evaluate(dataloader, model, loss_fn, device):
model.eval() # 设置模型为评估模式 (会关闭 Dropout, BatchNorm 等层的训练行为)
total_loss = 0
correct_predictions = 0
total_samples = 0
# 在评估阶段禁用梯度计算,节省内存和计算
with torch.no_grad():
for inputs, labels in dataloader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
loss = loss_fn(outputs, labels)
total_loss += loss.item() * inputs.size(0)
_, predicted = torch.max(outputs.data, 1)
total_samples += labels.size(0)
correct_predictions += (predicted == labels).sum().item()
avg_loss = total_loss / total_samples
accuracy = correct_predictions / total_samples
print(f"Evaluation finished. Avg Loss: {avg_loss:.4f}, Accuracy: {accuracy:.4f}")
return avg_loss, accuracy
7. 运行训练循环
num_epochs = 10
print(“\nStarting training…”)
for epoch in range(num_epochs):
print(f”\nEpoch {epoch+1}/{num_epochs}”)
# 训练阶段
train_loss, train_acc = train_one_epoch(dataloader, model, loss_fn, optimizer, device)
# (可选) 评估阶段 - 通常在训练集上训练,在验证集上评估
# 为了简化,这里省略了独立的验证集和 DataLoader
# eval_loss, eval_acc = evaluate(val_dataloader, model, loss_fn, device)
# print(f"Validation Loss: {eval_loss:.4f}, Validation Accuracy: {eval_acc:.4f}")
print(“\nTraining finished.”)
“`
这是一个基本的训练循环骨架。在实际应用中,你可能还需要:
- 验证集 (Validation Set): 在每个 epoch 结束后在验证集上评估模型性能,用于超参数调优和早期停止 (early stopping)。
- 学习率调度器 (Learning Rate Scheduler): 根据 epoch 或损失变化动态调整学习率。
- 训练可视化: 使用 TensorBoard 或 Weights & Biases 等工具记录损失、准确率、参数分布等信息。
- 模型检查点 (Checkpointing): 定期保存模型的状态,以便在训练中断后可以恢复或选择最佳模型。
9. 模型保存与加载
训练好的模型通常需要保存下来,以便后续进行推理或继续训练。PyTorch 提供了两种主要的保存和加载模型的方式:
-
保存/加载整个模型 (不推荐):
“`python
# 保存
torch.save(model, ‘entire_model.pth’)加载
loaded_model = torch.load(‘entire_model.pth’)
loaded_model.eval() # 加载后通常设置为评估模式
“`
缺点: 这种方法保存了模型的类定义和参数,不够灵活,可能在加载时需要原始的模型类文件,并且难以跨设备或跨版本 PyTorch 使用。 -
保存/加载模型的状态字典 (State Dictionary) (推荐):
状态字典是一个 Python 字典,它将每一层映射到其对应的参数张量(权重和偏置)。这种方式只保存模型的学习到的参数,不保存模型结构。加载时需要先创建模型结构的实例,然后载入状态字典。“`python
保存状态字典
确保模型已经在 CPU 上,或者在保存时指定 map_location
model.to(‘cpu’) # 如果模型在 GPU 上
torch.save(model.state_dict(), ‘model_state_dict.pth’)
加载状态字典
先创建模型的实例,结构需要与保存时一致
loaded_model = SimpleNN(input_dim, hidden_dim, output_dim)
加载状态字典,并将其应用到模型实例
如果之前模型在 GPU 上保存,现在加载到 CPU,需要 map_location
loaded_model.load_state_dict(torch.load(‘model_state_dict.pth’, map_location=torch.device(‘cpu’)))
如果设备一致或无 GPU 转移,直接加载
loaded_model.load_state_dict(torch.load(‘model_state_dict.pth’))
loaded_model.eval() # 加载后通常设置为评估模式
print(“Model loaded successfully using state_dict”)
“`
优点: 这种方式更加灵活,只保存参数,与模型定义分离,易于在不同项目、不同设备之间移植,也是进行迁移学习的常用方式。
通常推荐使用保存和加载模型的 state_dict
的方法。
你也可以保存优化器的状态字典,以便从中断的地方继续训练:
“`python
保存模型和优化器的状态
torch.save({
‘epoch’: epoch,
‘model_state_dict’: model.state_dict(),
‘optimizer_state_dict’: optimizer.state_dict(),
‘loss’: loss,
# … 其他需要保存的信息 …
}, ‘checkpoint.pth’)
加载检查点
checkpoint = torch.load(‘checkpoint.pth’)
model.load_state_dict(checkpoint[‘model_state_dict’])
optimizer.load_state_dict(checkpoint[‘optimizer_state_dict’])
loaded_epoch = checkpoint[‘epoch’]
loaded_loss = checkpoint[‘loss’]
可以从 loaded_epoch + 1 继续训练
“`
10. GPU 加速
利用 GPU 进行深度学习训练是其高性能的关键。PyTorch 对 GPU 的支持非常方便。
-
检查 GPU 是否可用:
python
if torch.cuda.is_available():
device = torch.device("cuda") # 默认使用第一个 GPU
# device = torch.device("cuda:1") # 使用第二个 GPU
print("GPU is available")
else:
device = torch.device("cpu")
print("GPU not available, using CPU") -
将模型和张量移动到设备:
在进行计算之前,需要将模型和所有相关的张量数据都移动到同一个设备上。python
model.to(device)
inputs, labels = inputs.to(device), labels.to(device)
确保所有参与运算的张量都在同一个设备上,否则会引发运行时错误。 -
多 GPU: 对于更大的模型或数据集,可以使用
nn.DataParallel
或更灵活的torch.distributed
进行多 GPU 训练,但这超出了入门范畴。
通过简单的 .to(device)
调用,你就可以将模型的计算转移到 GPU 上,享受并行计算带来的巨大加速。
11. 更进一步与资源推荐
本文带你了解了 PyTorch 的核心概念和基本使用流程。但这仅仅是冰山一角。PyTorch 还有一个庞大且不断发展的生态系统。
- 领域库:
torchvision
(计算机视觉),torchtext
(自然语言处理),torchaudio
(音频处理) 提供了各自领域常用的数据集、模型和转换工具。 - 高级框架: PyTorch Lightning 和 Ignite 等库在 PyTorch 基础上提供了更高层面的抽象,可以进一步简化训练循环、处理多 GPU、分布式训练等复杂任务。
- 部署: TorchScript 用于模型序列化和跨平台部署,支持 C++, mobile 等。TorchServe 用于快速构建模型服务 API。
- 生态工具: 如 Torch.compile (加速模型运行)、Profild (性能分析) 等。
推荐的学习资源:
- PyTorch 官方文档: 这是最权威和全面的资源,包含了 API 参考、教程和指南。(
https://pytorch.org/docs/stable/index.html
) - PyTorch 官方教程: 提供了从基础到高级的各种教程,是实践学习的好去处。(
https://pytorch.org/tutorials/
) - PyTorch 论坛: 遇到问题时,可以在社区中寻求帮助。(
https://discuss.pytorch.org/
) - GitHub 上的开源项目: 阅读和学习他人用 PyTorch 实现的项目是提高技能的有效途径。
- 在线课程: Coursera, fast.ai, Udacity 等平台提供了高质量的深度学习和 PyTorch 课程。
总结
本文详细介绍了 PyTorch 的入门知识,包括其核心优势、环境搭建、基础数据结构张量、自动微分机制、构建神经网络的模块 (torch.nn
)、数据加载工具 (torch.utils.data
)、模型训练循环的实现、模型的保存与加载,以及 GPU 加速的使用方法。
通过学习这些内容,你已经掌握了使用 PyTorch 进行深度学习开发的基础。PyTorch 的动态性、Pythonic 的风格和强大的社区支持使其成为进行研究和开发深度学习应用的绝佳工具。
深度学习是一个实践性很强的领域,最好的学习方法是动手实践。尝试用 PyTorch 构建并训练一个简单的图像分类器(如在 MNIST 或 CIFAR10 数据集上),或者一个文本分类模型,将本文学到的知识应用到实际项目中。在实践中,你会遇到各种新的问题和挑战,解决它们的过程将极大地提升你的 PyTorch 技能和深度学习理解。
祝你在 PyTorch 的学习和实践旅程中一切顺利!