快速了解 PyTorch:入门介绍
引言:迎接深度学习的浪潮
在当今科技飞速发展的时代,人工智能(AI)正以前所未有的速度改变着我们的生活。而深度学习作为AI领域最激动人心的分支之一,已经在图像识别、自然语言处理、语音识别等众多领域取得了突破性进展。要踏入这个领域,掌握一个强大、灵活且易于使用的深度学习框架至关重要。PyTorch,正是一个在学术界和工业界都广受欢迎的选择。
PyTorch 由 Facebook 的人工智能研究院(FAIR)开发并维护,以其“Pythonic”的设计哲学、动态计算图以及强大的社区支持而闻名。相比于一些老牌框架(如早期版本的 TensorFlow),PyTorch 的设计更加直观,与 Python 的原生习惯更为贴近,这使得它特别适合研究人员进行快速原型开发和实验。同时,其在生产环境中的部署能力也在不断增强。
本文旨在为完全没有 PyTorch 基础的读者提供一个快速而全面的入门指南。我们将从 PyTorch 的核心概念——张量(Tensor)——开始,逐步深入到自动微分机制(Autograd)、神经网络的构建模块(torch.nn
)、数据的处理、模型的训练流程,以及如何在GPU上加速计算。通过阅读本文,你将能够:
- 理解 PyTorch 的基本构成和核心特性。
- 掌握张量的创建、操作和重要属性。
- 理解自动微分的工作原理及其在训练中的作用。
- 学会使用
torch.nn
构建简单的神经网络模型。 - 了解如何使用优化器和损失函数。
- 熟悉 PyTorch 的数据处理工具。
- 理解并实现一个简单的模型训练循环。
- 知道如何利用 GPU 加速计算。
- 了解模型的保存与加载。
本文力求详细,力求通过理论讲解结合实际代码示例,让你不仅仅是复制代码,更能理解其背后的原理。让我们一起踏上 PyTorch 的学习之旅吧!
第一章:PyTorch 是什么?为什么选择它?
1.1 PyTorch 的定位与特点
PyTorch 是一个开源的机器学习库,主要用于构建和训练神经网络。它基于 Torch 库,但针对 Python 进行了优化。PyTorch 的核心特点包括:
- Pythonic 设计: PyTorch 的 API 设计非常贴近 Python 的编程习惯,代码易于理解和编写。
- 动态计算图: 这是 PyTorch 最显著的特点之一。计算图是根据代码的执行动态构建的,这使得调试更加容易,也允许在运行时改变网络结构(例如,在处理变长序列时)。相对而言,TensorFlow 1.x 采用静态计算图,需要先定义完整的计算图,再执行。TensorFlow 2.x 吸取了 PyTorch 的优点,也开始支持动态图(eager execution)。
- 张量计算: PyTorch 提供强大的张量计算能力,类似于 NumPy,但支持 GPU 加速。
- 自动微分 (
Autograd
): 能够自动计算张量上的所有操作的梯度,这是训练神经网络的关键。 - 丰富的生态系统: PyTorch 拥有强大的库,如
torchvision
(计算机视觉)、torchaudio
(音频处理)、torchtext
(自然语言处理),以及用于部署的 TorchScript 和 mobile 后端。 - 社区活跃: 拥有庞大且活跃的社区,遇到问题很容易找到帮助和资源。
1.2 为什么选择 PyTorch?
对于初学者和研究人员来说,PyTorch 通常被认为是更容易入门和更灵活的框架。其动态图特性使得实验和调试更加直观。对于需要快速迭代想法、尝试不同模型结构的场景,PyTorch 的优势更加明显。
对于工业界应用,PyTorch 也提供了强大的部署工具,如 TorchScript,可以将模型导出为静态图格式,方便在不同环境中部署。近年来,越来越多的公司开始在生产环境中使用 PyTorch。
当然,选择哪个框架取决于个人偏好、项目需求和团队经验。但无论如何,PyTorch 都是一个值得深入学习的优秀框架。
第二章:安装 PyTorch
安装 PyTorch 非常简单,通常推荐使用 pip 或 conda 包管理器。访问 PyTorch 官方网站 (pytorch.org),选择你的操作系统、包管理器、PyTorch 版本以及是否需要 CUDA 支持(如果你的电脑有NVIDIA显卡,强烈建议安装CUDA版本以利用GPU加速),网站会生成对应的安装命令。
以 conda 为例,安装支持 CUDA 11.8 的最新 PyTorch 版本可能类似于:
bash
conda install pytorch torchvision torchaudio cudatoolkit=11.8 -c pytorch -c conda-forge
如果不需要 GPU 支持(仅 CPU 版本):
bash
conda install pytorch torchvision torchaudio cpuonly -c pytorch
使用 pip 的话,命令会不同,同样参照官网。
安装完成后,可以在 Python 环境中导入 PyTorch 并检查版本和 CUDA 是否可用:
python
import torch
print(f"PyTorch version: {torch.__version__}")
print(f"Is CUDA available: {torch.cuda.is_available()}")
if torch.cuda.is_available():
print(f"CUDA version: {torch.version.cuda}")
print(f"CUDA device name: {torch.cuda.get_device_name(0)}")
如果 torch.cuda.is_available()
返回 True
,恭喜你,你已经可以在 GPU 上运行 PyTorch 了!
第三章:PyTorch 的核心:张量 (Tensor)
张量是 PyTorch 中最基本的数据结构。你可以将其理解为一个多维数组,与 NumPy 的 ndarray
非常相似。然而,PyTorch 的张量可以驻留在 GPU 上,并且能够自动计算梯度,这是它与 NumPy 的根本区别。
3.1 创建张量
有多种方法可以创建张量:
-
从 Python 列表或 NumPy 数组创建:
“`python
import torch
import numpy as np从列表创建
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
print(f”From list:\n{x_data}\n”)从 NumPy 数组创建
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
print(f”From NumPy:\n{x_np}\n”)
“` -
创建具有特定属性的张量:
“`python
创建一个张量,形状与另一个张量相同(默认保留 dtype)
x_like = torch.ones_like(x_data) # 保留 x_data 的形状和 dtype
print(f”Ones like:\n{x_like}\n”)创建一个张量,形状与另一个张量相同,但指定 dtype
x_like_float = torch.zeros_like(x_data, dtype=torch.float) # 保留 x_data 的形状,但 dtype 为 float
print(f”Zeros like (float):\n{x_like_float}\n”)创建具有随机值的张量
shape = (2, 3,)
rand_tensor = torch.rand(shape) # 值在 [0, 1) 之间均匀分布
print(f”Random Tensor:\n{rand_tensor}\n”)创建具有常量值的张量
ones_tensor = torch.ones(shape)
print(f”Ones Tensor:\n{ones_tensor}\n”)zeros_tensor = torch.zeros(shape)
print(f”Zeros Tensor:\n{zeros_tensor}\n”)
“` -
创建未初始化的张量:
“`python
创建一个指定形状的未初始化张量
empty_tensor = torch.empty(shape) # 值是未初始化的,不要依赖其内容
print(f”Empty Tensor:\n{empty_tensor}\n”)
“`
3.2 张量的属性
张量有几个重要的属性:
shape
或size()
:张量的形状(各维度的大小)。dtype
:张量的数据类型(如torch.float32
,torch.int64
)。device
:张量所在的设备(如cpu
,cuda:0
)。
“`python
tensor = torch.rand(3, 4)
print(f”Shape of tensor: {tensor.shape}”)
print(f”Shape of tensor: {tensor.size()}”) # size() 等同于 shape
print(f”Datatype of tensor: {tensor.dtype}”)
print(f”Device tensor is stored on: {tensor.device}”)
“`
3.3 张量的操作
张量支持各种数学运算、逻辑运算、索引、切片、形状变换等,许多操作与 NumPy 类似。
-
算术运算: 支持元素级运算 (+, -, *, /) 以及矩阵乘法。
“`python
tensor = torch.ones(4, 4)
tensor[:,1] = 0 # 修改第二列为0
print(f”Original Tensor:\n{tensor}\n”)元素级乘法
print(f”Element-wise multiplication:\n{tensor.mul(tensor)}\n”)
print(f”Element-wise multiplication (syntax sugar):\n{tensor * tensor}\n”)矩阵乘法 (仅当维度兼容时)
tensor 是 4×4, 需要一个 4xN 或 Nx4 的张量进行乘法
matrix = torch.ones(4, 2)
print(f”Matrix multiplication (@):\n{tensor @ matrix}\n”)
print(f”Matrix multiplication (matmul):\n{tensor.matmul(matrix)}\n”)矩阵与标量相乘
print(f”Scalar multiplication:\n{tensor.mul(5)}\n”)
print(f”Scalar multiplication (syntax sugar):\n{tensor * 5}\n”)
“` -
索引与切片: 与 NumPy 类似。
python
tensor = torch.rand(4, 4)
print(f"First row: {tensor[0]}\n")
print(f"First column: {tensor[:, 0]}\n")
print(f"Last column: {tensor[..., -1]}\n")
tensor[0, 1] = 100 # 修改元素
print(f"Modified tensor:\n{tensor}\n") -
形状变换:
view()
和reshape()
用于改变张量的形状。view()
要求新旧张量在内存中是连续的,而reshape()
更灵活,如果不连续会复制数据。对于初学者,通常使用reshape()
更安全。-1
在形状中表示该维度的大小由其他维度和张量总元素数推断得出。“`python
tensor = torch.ones(4, 4)
print(f”Original shape: {tensor.shape}\n”)将 4×4 的张量展平为 16 维向量
flat_tensor = tensor.view(-1)
print(f”Flattened tensor:\n{flat_tensor}\n”)
print(f”Flattened shape: {flat_tensor.shape}\n”)将 16 维向量重新塑形为 2×8 的矩阵
reshaped_tensor = flat_tensor.reshape(2, 8)
print(f”Reshaped tensor:\n{reshaped_tensor}\n”)
print(f”Reshaped shape: {reshaped_tensor.shape}\n”)
“` -
连接 (Concatenation):
torch.cat()
用于沿现有维度连接张量。torch.stack()
用于沿新维度堆叠张量。“`python
tensor = torch.ones(4, 4)
tensor_cat_rows = torch.cat([tensor, tensor, tensor], dim=0) # 在第0维(行)上连接
print(f”Concatenated rows shape: {tensor_cat_rows.shape}\n”) # 得到 12×4tensor_cat_cols = torch.cat([tensor, tensor, tensor], dim=1) # 在第1维(列)上连接
print(f”Concatenated columns shape: {tensor_cat_cols.shape}\n”) # 得到 4×12tensor_stack = torch.stack([tensor, tensor, tensor], dim=0) # 在新维度(第0维)上堆叠
print(f”Stacked shape: {tensor_stack.shape}\n”) # 得到 3x4x4
“` -
NumPy 转换: 张量与 NumPy 数组可以方便地互相转换。注意,CPU 上的张量和 NumPy 数组共享底层内存,修改一个会影响另一个。GPU 上的张量转换为 NumPy 数组时会涉及数据复制。
“`python
Tensor to NumPy
tensor = torch.ones(5)
numpy_array = tensor.numpy()
print(f”Tensor:\n{tensor}”)
print(f”NumPy Array:\n{numpy_array}\n”)Changes in the tensor reflect in the NumPy array (on CPU)
tensor.add_(1) # 原地加1
print(f”Tensor after adding 1:\n{tensor}”)
print(f”NumPy Array after tensor changes:\n{numpy_array}\n”) # NumPy Array 也改变了NumPy to Tensor
numpy_array = np.ones(5)
tensor_from_np = torch.from_numpy(numpy_array)
print(f”NumPy Array:\n{numpy_array}”)
print(f”Tensor from NumPy:\n{tensor_from_np}\n”)Changes in the NumPy array reflect in the tensor (on CPU)
np.add(numpy_array, 1, out=numpy_array) # 原地加1
print(f”NumPy Array after adding 1:\n{numpy_array}”)
print(f”Tensor from NumPy after NumPy changes:\n{tensor_from_np}\n”) # Tensor 也改变了
“`
3.4 张量与 GPU
将张量移动到 GPU 上非常简单,使用 .to()
方法:
“`python
if torch.cuda.is_available():
device = “cuda”
else:
device = “cpu”
print(f”Using device: {device}\n”)
tensor = torch.ones(5, device=”cpu”) # 默认创建在 CPU
print(f”Tensor on CPU: {tensor.device}\n”)
tensor = tensor.to(device) # 移动到 GPU (如果可用)
print(f”Tensor on {device}: {tensor.device}\n”)
直接在 GPU 上创建张量
tensor_on_gpu = torch.rand(2, 2, device=device)
print(f”Directly created on {device}:\n{tensor_on_gpu}\n”)
print(f”Device of directly created tensor: {tensor_on_gpu.device}\n”)
“`
记住:在 GPU 上执行操作时,所有参与运算的张量必须位于同一个设备上。将张量从 GPU 移回 CPU 同样使用 .to('cpu')
或 .cpu()
。
第四章:自动微分:Autograd
自动微分是 PyTorch 能够训练神经网络的核心。它通过跟踪张量上的所有操作来构建一个计算图,并利用链式法则自动计算梯度。
4.1 requires_grad
默认情况下,张量不计算梯度。要让张量参与梯度计算,需要设置 requires_grad=True
。通常,只有模型的权重和偏置等需要通过梯度更新的参数才需要设置 requires_grad=True
。
“`python
import torch
x = torch.ones(5, requires_grad=True) # 这个张量需要计算梯度
y = torch.zeros(3) # 这个张量不需要计算梯度
print(f”x requires_grad: {x.requires_grad}”)
print(f”y requires_grad: {y.requires_grad}”)
“`
通过张量进行的操作会形成一个计算图。结果张量的 grad_fn
属性记录了创建它的操作(反向传播时会用到)。
“`python
z = x + 2
print(f”z: {z}\n”)
print(f”z.grad_fn: {z.grad_fn}\n”) # z 是通过加法创建的,所以 grad_fn 是
a = z * z * 3
print(f”a: {a}\n”)
print(f”a.grad_fn: {a.grad_fn}\n”) # a 是通过乘法创建的,所以 grad_fn 是
v = a.mean() # 计算平均值,得到一个标量
print(f”v: {v}\n”)
print(f”v.grad_fn: {v.grad_fn}\n”) # v 是通过平均值操作创建的,所以 grad_fn 是
注意:如果一个张量没有任何操作依赖于 requires_grad=True 的张量,它也不需要梯度
b = torch.randn(5) # b 不跟踪梯度
c = b + 2 # c 也不跟踪梯度
print(f”c requires_grad: {c.requires_grad}”)
“`
4.2 计算梯度:.backward()
当计算图构建完成后,对于一个标量输出张量(例如,损失函数的结果),可以通过调用其 .backward()
方法来计算相对于图叶子节点(那些 requires_grad=True
的张量)的梯度。
“`python
v.backward() # 对标量 v 执行反向传播
梯度会累积到对应张量的 .grad 属性中
print(f”Gradient of x with respect to v: {x.grad}\n”)
数学上验证:
v = a.mean() = (1/5) * sum(a)
a = 3 * z * z
z = x + 2
v = (1/5) * sum(3 * (x + 2)^2)
dv/dx_i = (1/5) * d(3 * (x_i + 2)^2) / dx_i
= (1/5) * 3 * 2 * (x_i + 2) * 1
= (6/5) * (x_i + 2)
因为 x = torch.ones(5),所以 x_i = 1
dv/dx_i = (6/5) * (1 + 2) = (6/5) * 3 = 18/5 = 3.6
验证输出:
x = tensor([1., 1., 1., 1., 1.], requires_grad=True)
z = x + 2 = tensor([3., 3., 3., 3., 3.], grad_fn=)
a = z * z * 3 = tensor([27., 27., 27., 27., 27.], grad_fn=)
v = a.mean() = tensor(27., grad_fn=)
x.grad = tensor([3.6000, 3.6000, 3.6000, 3.6000, 3.6000])
结果一致。
“`
如果输出张量是向量(非标量),调用 .backward()
需要传入一个与该张量形状相同的张量作为参数,这个参数代表“梯度向量”(通常是 torch.ones_like(output_tensor)
),其作用类似于计算雅可比矩阵与该向量的乘积。但在大多数深度学习场景中,我们计算的是标量损失函数的梯度,所以直接调用 .backward()
即可。
4.3 停止梯度跟踪:torch.no_grad()
和 .detach()
在训练过程中,有些操作我们不希望记录到计算图中,例如在评估模型或更新优化器参数时。可以使用 torch.no_grad()
上下文管理器或 .detach()
方法来阻止梯度跟踪。
-
torch.no_grad()
: 在该上下文管理器内部创建或操作的张量将不会被跟踪梯度。常用于模型评估阶段。“`python
x = torch.ones(5, requires_grad=True)
print(f”x requires_grad: {x.requires_grad}”)with torch.no_grad():
y = x + 2
print(f”y requires_grad within no_grad: {y.requires_grad}”) # Falsez = x + 3
print(f”z requires_grad outside no_grad: {z.requires_grad}”) # True
“` -
.detach()
: 从当前的计算图中分离张量,返回一个 新的 张量,这个新张量不再跟踪梯度,且与原张量共享底层数据(除非形状改变等原因导致复制)。常用于在需要张量的值但不需要梯度的场景,比如在训练循环中记录损失值。“`python
x = torch.ones(5, requires_grad=True)
print(f”x requires_grad: {x.requires_grad}”)y = x.detach() # y 是 x 的一个副本,但不跟踪梯度
print(f”y requires_grad: {y.requires_grad}”) # False
print(f”y is x: {y is x}”) # False (y 是新的张量)
print(f”y == x: {(y == x).all()}”) # True (值相同)注意:修改 detach() 后的张量可能会影响原张量(如果共享内存)
但由于 y 不跟踪梯度,这些修改不会记录在计算图中
“`
4.4 梯度清零:optimizer.zero_grad()
或 tensor.grad.zero_()
梯度是会累积的。在每次反向传播之前,必须将所有参数的梯度清零,否则本次计算的梯度会与上次计算的梯度累加。通常使用优化器的 zero_grad()
方法来完成这个任务。
“`python
假设 model 是一个神经网络模型
假设 optimizer 是一个优化器
假设 loss 是本次迭代计算的损失
1. 梯度清零
optimizer.zero_grad() # 或者手动对每个需要梯度的参数执行 param.grad.zero_()
2. 前向传播
outputs = model(inputs)
loss = criterion(outputs, labels)
3. 反向传播
loss.backward()
4. 更新参数
optimizer.step()
“`
这一步是训练循环中的关键,将在后面详细介绍。
第五章:构建神经网络:torch.nn
torch.nn
是 PyTorch 中构建神经网络的核心模块。它提供了各种预定义的神经网络层(如全连接层、卷积层、循环层)、激活函数、损失函数等。
nn.Module
是所有神经网络模块(层、整个模型)的基类。一个模型或一层通常继承自 nn.Module
,并在 __init__
方法中定义其子模块或参数,在 forward
方法中定义前向传播的计算过程。
5.1 定义一个简单的神经网络
让我们定义一个简单的全连接神经网络,包含一个输入层、一个隐藏层和一个输出层。
“`python
import torch
import torch.nn as nn
import torch.nn.functional as F # F 模块通常包含函数式的操作,如激活函数、池化等
class SimpleNN(nn.Module):
def init(self, input_size, hidden_size, num_classes):
super(SimpleNN, self).init() # 调用父类构造函数
self.fc1 = nn.Linear(input_size, hidden_size) # 定义第一个全连接层 (输入 -> 隐藏)
self.relu = nn.ReLU() # 定义 ReLU 激活函数
self.fc2 = nn.Linear(hidden_size, num_classes) # 定义第二个全连接层 (隐藏 -> 输出)
def forward(self, x):
# x 是输入张量
out = self.fc1(x) # 通过第一个全连接层
out = self.relu(out) # 应用 ReLU 激活函数
out = self.fc2(out) # 通过第二个全连接层
# 对于分类问题,通常在输出层之后不加 softmax,因为交叉熵损失函数 (nn.CrossEntropyLoss) 内部包含了 softmax
return out
实例化模型
input_size = 784 # 例如,一个展平的 28×28 图像
hidden_size = 128
num_classes = 10 # 例如,10 个类别的分类问题
model = SimpleNN(input_size, hidden_size, num_classes)
print(model) # 打印模型结构
“`
5.2 nn.Module
的核心机制
__init__(self, ...)
: 在这里定义网络中需要学习参数的层或子模块(例如nn.Linear
,nn.Conv2d
,nn.BatchNorm2d
等)。这些层被注册为模型的子模块。forward(self, x)
: 在这里定义输入x
通过网络各层的计算路径。这是模型的前向传播过程。parameters()
:nn.Module
的一个重要方法,会返回模型中所有需要学习的参数(那些requires_grad=True
的张量)。优化器需要这些参数来更新模型。.to(device)
: 可以方便地将整个模型的所有参数和缓冲区移动到指定设备(CPU 或 GPU)。
“`python
将模型移动到 GPU (如果可用)
if torch.cuda.is_available():
device = torch.device(‘cuda’)
else:
device = torch.device(‘cpu’)
model.to(device)
print(f”\nModel moved to device: {next(model.parameters()).device}\n”) # 检查模型参数所在的设备
打印模型的参数 (会包含 require_grad=True 的所有张量)
print(“Model Parameters:”)
for name, param in model.named_parameters():
if param.requires_grad:
print(f”Layer: {name} | Size: {param.size()} | Requires_grad: {param.requires_grad} | Device: {param.device}”)
“`
第六章:训练的关键:损失函数与优化器
训练神经网络的目标是最小化损失函数,即衡量模型预测结果与真实标签之间差距的函数。优化器的作用是根据损失函数计算出的梯度来更新模型参数,使得损失函数逐渐减小。
6.1 损失函数 (nn.Module
的子类)
PyTorch 在 torch.nn
模块中提供了各种常用的损失函数。它们通常继承自 nn.Module
,因此可以像层一样使用,并且通常需要先实例化。
nn.CrossEntropyLoss
: 常用于多类别分类问题。它内部集成了 Softmax 激活函数和负对数似然损失。输入是模型的原始输出(logits),目标是类别索引。nn.MSELoss
: 均方误差损失,常用于回归问题。nn.BCELoss
: 二进制交叉熵损失,常用于二分类问题。输入需要经过 Sigmoid 激活。nn.BCEWithLogitsLoss
: 二进制交叉熵损失,但输入是原始 logits,内部集成了 Sigmoid。更稳定,常用于二分类。
“`python
示例:使用 CrossEntropyLoss
criterion = nn.CrossEntropyLoss()
假设 model_outputs 是模型对一批数据的预测输出 (例如,batch_size x num_classes)
假设 labels 是这批数据的真实类别标签 (例如,batch_size)
model_outputs = torch.randn(64, 10, requires_grad=True) # 示例:64个样本,10个类别
labels = torch.randint(0, 10, (64,)) # 示例:随机生成 64 个 0-9 的标签
计算损失
loss = criterion(model_outputs, labels)
print(f”\nExample Loss: {loss}”)
“`
6.2 优化器 (torch.optim
)
torch.optim
模块提供了各种优化算法,如随机梯度下降 (SGD)、Adam、Adagrad 等。优化器需要知道需要更新哪些参数以及学习率等超参数。
torch.optim.SGD
: 随机梯度下降。可以设置动量 (momentum)。torch.optim.Adam
: Adam 优化器,一种自适应学习率方法,通常效果不错且无需过多调参。torch.optim.AdamW
: Adam 的改进版,通常用于权重衰减。
实例化优化器时,通常传入 model.parameters()
和学习率。
“`python
import torch.optim as optim
实例化一个 SGD 优化器
optimizer_sgd = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
实例化一个 Adam 优化器
optimizer_adam = optim.Adam(model.parameters(), lr=0.001)
在实际训练中,通常只选择一种优化器
“`
6.3 训练循环的关键步骤回顾
在每次训练迭代(一个 batch)中,典型的训练循环包含以下步骤:
- 前向传播: 将输入数据通过模型,得到预测输出。
- 计算损失: 使用损失函数比较预测输出和真实标签,得到损失值。
- 梯度清零: 清除之前计算的梯度。这是因为 PyTorch 的梯度会累积。
- 反向传播: 调用
loss.backward()
计算损失相对于模型参数的梯度。 - 参数更新: 调用
optimizer.step()
根据梯度更新模型参数。
我们将在后面的完整示例中看到这些步骤的代码实现。
第七章:数据处理:torch.utils.data
在实际应用中,训练数据通常存储在文件或数据库中。PyTorch 提供了 torch.utils.data
模块来帮助高效地加载和处理数据。
Dataset
: 代表整个数据集。需要继承torch.utils.data.Dataset
类,并实现两个魔术方法:__len__(self)
:返回数据集的大小。__getitem__(self, idx)
:根据索引idx
获取一个样本(数据和标签)。
DataLoader
: 负责数据的批量加载、打乱 (shuffle) 和并行加载 (num_workers)。它接收一个Dataset
对象,并提供一个迭代器,每次迭代返回一个批次的数据。
“`python
示例:创建一个简单的自定义 Dataset (用于说明概念,实际应用中数据会从文件加载)
from torch.utils.data import Dataset, DataLoader
class CustomDataset(Dataset):
def init(self, num_samples=100):
# 假设我们有一些随机数据和标签
self.data = torch.randn(num_samples, 10) # 100个样本,每个样本10维
self.labels = torch.randint(0, 2, (num_samples,)) # 100个样本,标签是 0 或 1 (二分类)
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
# 返回第 idx 个样本的数据和标签
sample_data = self.data[idx]
sample_label = self.labels[idx]
return sample_data, sample_label
实例化数据集
dataset = CustomDataset(num_samples=1000)
print(f”Dataset size: {len(dataset)}”)
实例化 DataLoader
batch_size = 32
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=0) # shuffle=True 在每个 epoch 开始时打乱数据,num_workers>0 可以使用多进程加载数据
迭代 DataLoader
print(“\nIterating through DataLoader batches:”)
for i, (inputs, labels) in enumerate(dataloader):
print(f”Batch {i}: Inputs shape {inputs.shape}, Labels shape {labels.shape}”)
if i >= 2: # 打印前3个批次作为示例
break
“`
使用 DataLoader
的好处在于:
- 批量处理: 神经网络通常以批次为单位进行训练。
- 数据打乱: 防止模型学习数据的顺序,提高泛化能力。
- 并行加载: 在 CPU 上预加载数据,减少 GPU 等待时间。
第八章:整合实践:一个简单的训练示例
现在,我们将前面介绍的概念整合起来,实现一个完整的、简单的线性回归模型训练过程。
问题: 学习一个线性关系 y = 2x + 1
,加入一些噪声。
“`python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
— 1. 数据准备 —
创建一个简单的线性数据集,带噪声
class LinearDataset(Dataset):
def init(self, num_samples=100):
self.x = torch.randn(num_samples, 1) # 输入 x 是 100×1 的张量
self.y = 2 * self.x + 1 + torch.randn(num_samples, 1) * 0.1 # 目标 y = 2x + 1 + 噪声
def __len__(self):
return len(self.x)
def __getitem__(self, idx):
return self.x[idx], self.y[idx]
实例化数据集和 DataLoader
dataset = LinearDataset(num_samples=1000)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
— 2. 模型定义 —
定义一个简单的线性回归模型
class LinearRegression(nn.Module):
def init(self):
super(LinearRegression, self).init()
self.linear = nn.Linear(1, 1) # 输入特征1个,输出特征1个
def forward(self, x):
return self.linear(x)
实例化模型
model = LinearRegression()
— 3. 损失函数和优化器 —
对于回归问题,使用均方误差损失
criterion = nn.MSELoss()
使用 SGD 优化器,学习率 0.01
optimizer = optim.SGD(model.parameters(), lr=0.01)
— 4. 训练循环 —
num_epochs = 100
print(“Starting training…”)
for epoch in range(num_epochs):
total_loss = 0
# 迭代 DataLoader 获取每个 batch 的数据
for inputs, targets in dataloader:
# 将数据移动到合适设备(如果使用 GPU)
# inputs, targets = inputs.to(device), targets.to(device)
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, targets)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 参数更新
optimizer.step()
total_loss += loss.item() # 累加每个 batch 的损失
# 打印每个 epoch 的平均损失
avg_loss = total_loss / len(dataloader)
if (epoch + 1) % 10 == 0 or epoch == 0: # 每10个epoch打印一次,以及第一个epoch
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}')
print(“Training finished!”)
— 5. 查看学习到的参数 —
print(“\nLearned parameters:”)
model.linear 是 nn.Linear 模块的实例
weight 是 W (权重), bias 是 b (偏置)
由于是 y = wx + b,我们期望 W 接近 2,b 接近 1
for name, param in model.named_parameters():
if param.requires_grad:
print(f”{name}: {param.data.numpy()}”) # .data 获取张量值,.numpy() 转为 numpy 方便打印
“`
运行这段代码,你会看到损失值在逐渐下降,并且最终学习到的 linear.weight
应该接近 2,linear.bias
应该接近 1。
这个示例包含了深度学习模型训练的全部核心步骤:数据加载、模型定义、损失函数、优化器以及训练循环。虽然模型和数据非常简单,但这个流程适用于更复杂的问题和网络结构。
第九章:利用 GPU 加速计算
在深度学习中,GPU 加速是至关重要的。PyTorch 使得利用 GPU 变得非常容易。
核心方法是使用 .to(device)
。device
可以是 'cpu'
或 'cuda'
(默认是 cuda:0
)。
要使用 GPU,你需要:
- 检查 CUDA 是否可用:
torch.cuda.is_available()
。 - 定义设备:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
。 - 将模型移动到设备:
model.to(device)
。 - 将数据移动到设备: 在训练循环中,将每个 batch 的输入和标签移动到设备:
inputs, targets = inputs.to(device), targets.to(device)
。
重要: 所有参与计算的张量和模型必须在同一个设备上。如果你有多个 GPU,可以使用 torch.cuda.set_device(gpu_id)
指定当前使用的 GPU,或者使用 nn.DataParallel
来分布式训练。对于初学者,先专注于单 GPU 使用即可。
修改上面的线性回归示例以支持 GPU:
“`python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
— 设置设备 —
device = torch.device(‘cuda’ if torch.cuda.is_available() else ‘cpu’)
print(f”Using device: {device}”)
— 数据准备 (同上) —
class LinearDataset(Dataset):
def init(self, num_samples=100):
self.x = torch.randn(num_samples, 1)
self.y = 2 * self.x + 1 + torch.randn(num_samples, 1) * 0.1
def __len__(self):
return len(self.x)
def __getitem__(self, idx):
return self.x[idx], self.y[idx]
dataset = LinearDataset(num_samples=1000)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
— 模型定义 (同上) —
class LinearRegression(nn.Module):
def init(self):
super(LinearRegression, self).init()
self.linear = nn.Linear(1, 1)
def forward(self, x):
return self.linear(x)
实例化模型 并移动到设备
model = LinearRegression().to(device)
— 损失函数和优化器 (同上) —
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
— 训练循环 (修改数据移动到设备) —
num_epochs = 100
print(“Starting training…”)
for epoch in range(num_epochs):
total_loss = 0
for inputs, targets in dataloader:
# 将数据移动到设备
inputs = inputs.to(device)
targets = targets.to(device)
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, targets)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 参数更新
optimizer.step()
# loss 是一个位于 device 上的张量,.item() 将其转为 Python 数字
total_loss += loss.item()
avg_loss = total_loss / len(dataloader)
if (epoch + 1) % 10 == 0 or epoch == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}')
print(“Training finished!”)
— 查看学习到的参数 (先将模型移回 CPU 或将参数复制到 CPU numpy) —
print(“\nLearned parameters:”)
如果模型在 GPU 上,需要将其移回 CPU 或复制参数到 CPU
model.to(‘cpu’) # 将整个模型移回 CPU
for name, param in model.named_parameters():
if param.requires_grad:
print(f”{name}: {param.data.numpy()}”)
“`
将模型和数据移动到 GPU 后,计算将在 GPU 上执行,显著提高训练速度,特别是对于大型模型和数据集。
第十章:模型的保存与加载
训练好的模型需要保存以便后续使用(如进行推理或继续训练)。PyTorch 通常推荐保存和加载模型的 状态字典 (state_dict),而不是整个模型对象。状态字典是一个 Python 字典,存储了模型所有参数(权重和偏置)以及其他缓冲区 (buffers) 的状态。
10.1 保存模型
“`python
假设 model 是已经训练好的模型
指定保存路径
model_path = “linear_regression_model.pth”
保存模型的状态字典
torch.save(model.state_dict(), model_path)
print(f”\nModel state_dict saved to {model_path}”)
“`
.pth
或 .pt
是 PyTorch 模型文件的常用后缀。
10.2 加载模型
加载模型需要先实例化一个与保存模型时具有相同结构的模型对象,然后加载状态字典到这个新创建的模型中。
“`python
首先,实例化一个与保存模型时具有相同结构的空白模型
loaded_model = LinearRegression() # 确保结构与训练时一致
加载状态字典
loaded_model.load_state_dict(torch.load(model_path))
将加载的模型设置为评估模式 (dropout 和 batchnorm 等层在训练和评估模式下行为不同)
loaded_model.eval() # 切换到评估模式
print(“\nModel loaded successfully!”)
可以在加载的模型上进行推理
例如,预测一个新输入的值
new_x = torch.tensor([[5.0]]) # 注意输入形状和数据类型与训练时一致
在评估模式下,通常不需要计算梯度,可以使用 torch.no_grad()
with torch.no_grad():
prediction = loaded_model(new_x)
print(f”Prediction for x=5.0: {prediction.item()}”)
如果需要在 GPU 上加载和推理,确保模型和数据都在 GPU 上
loaded_model.to(device)
new_x = new_x.to(device)
with torch.no_grad():
prediction = loaded_model(new_x)
“`
为什么推荐保存/加载 state_dict?
- 更灵活:你可以只加载部分参数,方便进行迁移学习。
- 更安全:保存整个模型对象时,文件会包含模型的类定义,这使得在不同项目或不同版本的 PyTorch 中加载时可能出错。保存 state_dict 只保存参数数据。
保存整个模型对象:
虽然不推荐,但也可以保存和加载整个模型对象:
“`python
保存整个模型对象
torch.save(model, “entire_model.pth”)
加载整个模型对象
loaded_entire_model = torch.load(“entire_model.pth”)
loaded_entire_model.eval()
注意:加载整个模型对象时,需要确保模型类的定义在当前环境中是可用的。
“`
第十一章:PyTorch 生态与进阶方向
PyTorch 拥有一个不断壮大的生态系统,提供各种工具和库来支持深度学习的各个方面:
torchvision
: 包含了流行的数据集(如 MNIST, ImageNet)、模型架构(如 ResNet, VGG)以及图像转换工具。torchaudio
: 提供了音频数据集、模型和音频处理工具。torchtext
: 用于自然语言处理任务的数据集、模型和文本处理工具(目前处于维护模式,推荐使用第三方库如 Hugging Face 的transformers
)。- TorchScript: 用于将 PyTorch 模型转换为可序列化、可优化并在 C++ 环境中运行的静态图表示,方便生产部署。
- PyTorch Mobile: 将 PyTorch 模型部署到移动设备上。
- PyTorch Ecosystem Projects: 社区贡献的各种库,涵盖强化学习、图神经网络、概率编程等。
入门 PyTorch 后,可以进一步学习:
- 更复杂的网络架构: 卷积神经网络 (CNN) 用于图像,循环神经网络 (RNN) / Transformer 用于序列数据。
- 迁移学习: 利用预训练模型解决新任务。
- 数据增强: 提高模型的泛化能力。
- 模型评估指标: 除了损失函数,还有准确率、精确率、召回率、F1 分数等。
- 可视化工具: 如 TensorBoard (与 PyTorch 集成良好)。
- 分布式训练: 在多个 GPU 或多台机器上训练模型。
- 模型部署: 使用 TorchScript 等工具将模型部署到生产环境。
第十二章:总结与展望
恭喜你完成了 PyTorch 入门的核心内容学习!我们从 PyTorch 的基础——张量开始,深入了解了其核心特性 Autograd,学习了如何构建神经网络模型 nn.Module
,掌握了训练所需的损失函数和优化器,了解了数据加载 DataLoader
,并通过一个完整的线性回归示例将这些概念串联起来。我们还学习了如何在 GPU 上加速计算以及如何保存和加载模型。
PyTorch 以其易用性、灵活性和强大的功能,已经成为深度学习领域的主流框架之一。本文为你打开了通往 PyTorch 世界的大门,但这仅仅是一个开始。深度学习是一个充满活力且快速发展的领域,不断学习和实践是掌握它的关键。
下一步行动建议:
- 动手实践: 运行本文中的代码示例,尝试修改参数,观察结果。
- 挑战更复杂的任务: 尝试使用 PyTorch 实现一个简单的图像分类器(如 MNIST 数据集),或一个简单的文本分类器。
- 阅读官方文档和教程: PyTorch 官方文档非常详细且不断更新,是最好的学习资源之一。官方教程涵盖了各种主题和应用领域。
- 学习经典模型: 了解并尝试实现一些经典的神经网络模型架构。
- 参与社区: 加入 PyTorch 论坛、GitHub 仓库或相关的社区,与其他学习者交流,获取帮助。
深度学习之旅充满挑战,但也充满乐趣和成就感。希望本文能为你提供一个坚实的基础,助你在探索 PyTorch 和深度学习的道路上越走越远!祝你学习顺利!