PyTorch 基础:快速了解深度学习框架
引言:深度学习的浪潮与 PyTorch 的崛起
在当今人工智能飞速发展的时代,深度学习(Deep Learning)无疑是最受瞩目的技术之一。它在图像识别、自然语言处理、语音识别、推荐系统等众多领域取得了突破性进展,深刻地改变着我们的生活和工作方式。而支撑这些进步的基石,正是各种强大的深度学习框架。
TensorFlow、Keras、Caffe、MXNet 等框架曾是主流,但近年来,一个后起之秀——PyTorch,以其独特的优势和灵活的设计,迅速赢得了学术界和工业界的广泛青睐,成为了许多研究人员和工程师进行深度学习开发的首选工具。
PyTorch 由 Facebook(现 Meta)人工智能研究院(FAIR)开发并维护,它提供了一个强大的张量计算(Tensor computation)库,支持 GPU 加速,并且构建于一个基于 Autograd 系统的深度神经网络。其“Python First”的设计理念、动态计算图(Dynamic Computation Graph)特性以及简洁易用的 API,使得原型开发和实验变得异常高效和直观。
本文将带领大家快速入门 PyTorch,深入了解其最核心的基础概念:张量(Tensor)、自动微分(Autograd)以及构建神经网络的基本模块(nn.Module
)。通过掌握这些基石,你将能够理解 PyTorch 的工作原理,并为后续更复杂的深度学习模型的学习和开发打下坚实的基础。
第一部分:张量(Tensor)——PyTorch 的数据基石
在深度学习中,所有的数据,无论是输入图像的像素值、文本数据的词向量、模型的权重和偏置,还是中间计算结果,都被表示为多维数组。在 PyTorch 中,这种多维数组就被称为张量(Tensor)。
张量在概念上类似于 NumPy 数组,但 PyTorch 的张量具备一个 NumPy 数组所不具备的关键能力:它们可以在 GPU 上运行,从而大幅提升计算速度,并且它们是构建自动微分系统的核心。
理解张量,是掌握 PyTorch 的第一步。
1. 张量的基本概念
- 维度(Dimension/Rank): 张量的维度是其轴的数量。例如,一个标量(Scalar)是 0 维张量,一个向量(Vector)是 1 维张量,一个矩阵(Matrix)是 2 维张量。一个包含多张图片的集合可以表示为一个 4 维张量(数量 x 颜色通道 x 高 x 宽)。
- 形状(Shape): 张量的形状是一个描述每个维度大小的元组。例如,一个 3×4 的矩阵的形状是
(3, 4)
。 - 数据类型(Data Type): 张量存储的数据的类型,如浮点数 (
torch.float32
或torch.float
)、整数 (torch.int64
或torch.long
)、布尔值 (torch.bool
) 等。深度学习中最常用的是浮点数类型。 - 设备(Device): 张量可以存储在 CPU 或 GPU 上。
device
属性指示了张量所在的设备。
2. 创建张量
PyTorch 提供了多种创建张量的方法:
-
从 Python 列表或 NumPy 数组创建:
“`python
import torch
import numpy as np从列表创建
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
print(f”从列表创建的张量:\n{x_data}”)从 NumPy 数组创建
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
print(f”从 NumPy 数组创建的张量:\n{x_np}”)
``
torch.tensor()是最常用的创建方法,它可以根据输入数据自动推断形状和数据类型。
torch.from_numpy()` 可以将 NumPy 数组转换为 PyTorch 张量,并且它们共享内存,修改一个会影响另一个。 -
创建具有特定属性的张量:
“`python
# 创建形状为 (2, 3) 的全 1 张量,指定数据类型
x_ones = torch.ones(2, 3, dtype=torch.float32)
print(f”全 1 张量:\n{x_ones}”)创建形状为 (2, 3) 的全 0 张量
x_zeros = torch.zeros(2, 3)
print(f”全 0 张量:\n{x_zeros}”)创建形状为 (2, 3) 的随机张量 (服从均匀分布)
x_rand = torch.rand(2, 3)
print(f”随机张量 (均匀分布):\n{x_rand}”)创建形状为 (2, 3) 的随机张量 (服从标准正态分布)
x_randn = torch.randn(2, 3)
print(f”随机张量 (正态分布):\n{x_randn}”)创建形状为 (2, 3) 的未初始化张量
x_empty = torch.empty(2, 3)
print(f”未初始化张量:\n{x_empty}”) # 内容是随机的,取决于内存状态创建形状和数据类型与现有张量相同的张量 (如全 1)
x_like_ones = torch.ones_like(x_data)
print(f”类似 x_data 的全 1 张量:\n{x_like_ones}”)
“`
3. 张量的属性
创建张量后,可以通过其属性获取信息:
“`python
tensor = torch.rand(3, 4)
print(f”形状 (Shape): {tensor.shape}”)
print(f”数据类型 (Data type): {tensor.dtype}”)
print(f”设备 (Device): {tensor.device}”)
“`
4. 张量的操作
张量支持各种数学运算、索引、切片、变形等操作,这些操作与 NumPy 非常相似。
-
数学运算:
“`python
x = torch.tensor([[1, 2], [3, 4]])
y = torch.tensor([[5, 6], [7, 8]])逐元素相加
z1 = x + y
z2 = torch.add(x, y)
print(f”逐元素相加:\n{z1}”)逐元素相乘
z3 = x * y
z4 = torch.mul(x, y)
print(f”逐元素相乘:\n{z3}”)矩阵乘法
要求 x 的列数等于 y 的行数
matrix_mul = torch.matmul(x, y) # 或使用 @ 运算符: x @ y
print(f”矩阵乘法:\n{matrix_mul}”)inplace 操作 (操作结果直接修改原张量,通常以 _ 结尾)
print(f”修改前的 x:\n{x}”)
x.add_(y) # x = x + y
print(f”修改后的 x (inplace add):\n{x}”)
“` -
索引和切片:
“`python
tensor = torch.ones(4, 4)
print(f”原始张量:\n{tensor}”)获取第一行
row_0 = tensor[0]
print(f”第一行:\n{row_0}”)获取第一列
col_0 = tensor[:, 0]
print(f”第一列:\n{col_0}”)获取子区域 (前两行,后两列)
sub_tensor = tensor[0:2, 2:4]
print(f”子区域:\n{sub_tensor}”)修改元素 (inplace)
tensor[0, 0] = 100
print(f”修改后的张量:\n{tensor}”)
“` -
改变形状(Reshaping):
“`python
tensor = torch.arange(12) # 创建一个包含 0-11 的 1维张量
print(f”原始 1维张量:\n{tensor}”)改变形状为 3×4 的矩阵
reshaped_tensor = tensor.view(3, 4) # view 要求数据在内存中是连续的
或者使用 reshape (更灵活,不要求连续)
reshaped_tensor = tensor.reshape(3, 4)
print(f”改变形状为 3×4:\n{reshaped_tensor}”)
改变形状为 4×3 的矩阵
reshaped_tensor_2 = tensor.view(4, 3)
print(f”改变形状为 4×3:\n{reshaped_tensor_2}”)展平 ( Flatten )
flattened_tensor = reshaped_tensor.view(-1) # 使用 -1 让 PyTorch 自动推断维度大小
print(f”展平后的张量:\n{flattened_tensor}”)增加维度 (如从 2D 矩阵变成 3D 张量)
unsqueeze_tensor = reshaped_tensor.unsqueeze(0) # 在第 0 维增加一个维度
print(f”增加维度:\n{unsqueeze_tensor}, Shape: {unsqueeze_tensor.shape}”)移除维度 (如从 3D 张量移除大小为 1 的维度)
squeezed_tensor = unsqueeze_tensor.squeeze(0) # 移除第 0 维
或者使用 squeeze() 移除所有大小为 1 的维度
squeezed_tensor = unsqueeze_tensor.squeeze()
print(f”移除维度:\n{squeezed_tensor}, Shape: {squeezed_tensor.shape}”)
“` -
连接(Concatenation):
“`python
tensor1 = torch.ones(2, 3)
tensor2 = torch.zeros(2, 3)按行连接 (dim=0)
concat_rows = torch.cat([tensor1, tensor2], dim=0)
print(f”按行连接:\n{concat_rows}, Shape: {concat_rows.shape}”)按列连接 (dim=1)
concat_cols = torch.cat([tensor1, tensor2], dim=1)
print(f”按列连接:\n{concat_cols}, Shape: {concat_cols.shape}”)
“`
5. CPU 与 GPU
PyTorch 张量可以在 CPU 或 GPU 上进行计算。将张量移动到 GPU 可以显著加速深度学习模型的训练过程。
首先,检查是否有可用的 GPU:
python
if torch.cuda.is_available():
device = torch.device("cuda")
print("CUDA is available! Using GPU.")
else:
device = torch.device("cpu")
print("CUDA not available. Using CPU.")
将张量移动到指定设备:
“`python
tensor = torch.rand(3, 4)
将张量移动到 GPU (如果可用) 或 CPU
tensor_on_device = tensor.to(device)
print(f”张量所在的设备: {tensor_on_device.device}”)
将模型的所有参数移动到 GPU
model.to(device)
“`
注意: 只有位于同一设备上的张量才能进行相互操作。如果一个张量在 CPU 上,另一个在 GPU 上,直接进行数学运算会导致错误,需要先将它们移到同一设备上。
6. 张量与 NumPy 的转换
PyTorch 张量和 NumPy 数组之间可以方便地相互转换:
“`python
PyTorch 张量转 NumPy 数组
tensor = torch.ones(5)
numpy_array = tensor.numpy()
print(f”PyTorch 张量转 NumPy 数组:\n{numpy_array}, Type: {type(numpy_array)}”)
NumPy 数组转 PyTorch 张量
numpy_array = np.array([1, 2, 3, 4, 5])
tensor_from_numpy = torch.from_numpy(numpy_array)
print(f”NumPy 数组转 PyTorch 张量:\n{tensor_from_numpy}, Type: {type(tensor_from_numpy)}”)
“`
注意: CPU 上的张量和 NumPy 数组共享底层内存位置。修改其中一个,另一个也会改变。GPU 上的张量则不共享内存,转换时会进行数据拷贝。
第二部分:自动微分(Autograd)——PyTorch 的核心魔法
深度学习模型的训练过程本质上是一个优化问题,目标是找到一组模型参数,使得模型在训练数据上的损失函数达到最小值。这个优化过程通常依赖于梯度下降及其变种算法,而这些算法的核心是计算损失函数关于模型参数的梯度。
手动计算复杂模型的梯度既繁琐又容易出错。PyTorch 的 Autograd 系统正是为了解决这个问题而设计的,它能够自动计算任何张量操作的梯度。这是 PyTorch 作为深度学习框架最强大和最重要的特性之一。
1. Autograd 的基本原理
Autograd 的核心是构建一个计算图(Computation Graph)。当你对张量进行操作时,PyTorch 会在后台记录下这些操作,形成一个有向无环图(DAG,Directed Acyclic Graph)。图的节点是张量,边是操作函数。
当调用张量的 .backward()
方法时,Autograd 会沿着这个计算图从输出张量(通常是损失函数)开始,反向遍历图中的边,利用链式法则(Chain Rule)计算出所有需要梯度的叶子节点张量(通常是模型的参数)的梯度。
2. requires_grad 属性
并非所有张量都需要计算梯度。例如,模型的输入数据和标签就不需要计算梯度。只有那些需要在训练过程中通过梯度下降进行更新的参数(如权重和偏置)才需要计算梯度。
通过设置张量的 requires_grad=True
属性,可以告诉 PyTorch 需要跟踪这个张量的计算历史并计算其梯度。默认情况下,张量的 requires_grad
是 False
。
“`python
创建一个需要梯度的张量 (例如,模拟模型参数)
x = torch.tensor(2.0, requires_grad=True)
print(f”x requires_grad: {x.requires_grad}”)
创建一个不需要梯度的张量 (例如,模拟输入数据)
y = torch.tensor(3.0)
print(f”y requires_grad: {y.requires_grad}”)
对需要梯度的张量进行操作,结果张量默认也需要梯度
z = x * 2
print(f”z (x*2) requires_grad: {z.requires_grad}”) # True
对不需要梯度的张量进行操作,结果张量不需要梯度
w = y * 2
print(f”w (y*2) requires_grad: {w.requires_grad}”) # False
混合操作:如果操作涉及至少一个 requires_grad=True 的张量,结果张量也 requires_grad=True
v = x + y
print(f”v (x+y) requires_grad: {v.requires_grad}”) # True
“`
3. 计算梯度 (.backward())
当计算图构建完成后,可以通过调用作为计算图最终输出的张量(通常是损失张量)的 .backward()
方法来启动反向传播,计算梯度。
“`python
x = torch.tensor(2.0, requires_grad=True)
y = x*2 + 3x + 1 # 构建计算图: y = f(x)
计算 y 关于 x 的梯度
y.backward() # 如果 y 是一个标量,直接调用 backward()
print(f”梯度 dy/dx: {x.grad}”) # x.grad 会存储梯度值
``
y = x2 + 3x + 1
在这个例子中,。对
x求导得到
dy/dx = 2x + 3。当
x=2时,
dy/dx = 2*2 + 3 = 7`。
“`python
实际运行
x = torch.tensor(2.0, requires_grad=True)
y = x*2 + 3x + 1
y.backward() # 计算梯度
print(f”梯度 dy/dx @ x=2: {x.grad}”) # 输出 tensor(7.)
“`
注意:
* backward()
方法通常只用于标量输出(损失函数通常是一个标量)。如果输出是一个非标量张量,需要为其指定一个 gradient
参数,表示需要计算其关于输入的雅可比矩阵的向量-积(Jacobian-vector product)。对于大多数深度学习场景,我们关注的是损失函数关于参数的梯度,损失函数是标量,所以直接调用 .backward()
即可。
* 梯度会累加到叶子节点的 .grad
属性上。因此,在每次反向传播之前,需要清零之前的梯度,以避免梯度累积带来的错误。这通常通过 optimizer.zero_grad()
或 tensor.grad.zero_()
来实现。
4. 梯度清零
在典型的训练循环中,我们需要在每次迭代开始时清零梯度,然后在计算损失后进行反向传播计算新的梯度,最后使用优化器更新参数。
“`python
假设有一个简单的线性模型 y = wx + b
w = torch.tensor(1.0, requires_grad=True)
b = torch.tensor(0.5, requires_grad=True)
x_data = torch.tensor(2.0)
target = torch.tensor(5.0)
训练迭代 1
前向传播
y_pred = w * x_data + b # y_pred = 1.0 * 2.0 + 0.5 = 2.5
计算损失 (MSE)
loss = (y_pred – target)2 # loss = (2.5 – 5.0)2 = (-2.5)**2 = 6.25
print(f”迭代 1 前的梯度 w.grad: {w.grad}, b.grad: {b.grad}”) # None 或上次的值
反向传播
loss.backward()
print(f”迭代 1 后的梯度 w.grad: {w.grad}, b.grad: {b.grad}”)
loss = (wx + b – target)^2
d(loss)/dw = 2 * (wx + b – target) * x
d(loss)/db = 2 * (wx + b – target) * 1
@ w=1, b=0.5, x=2, target=5:
d(loss)/dw = 2 * (1*2 + 0.5 – 5) * 2 = 2 * (2.5 – 5) * 2 = 2 * (-2.5) * 2 = -10
d(loss)/db = 2 * (1*2 + 0.5 – 5) * 1 = 2 * (-2.5) * 1 = -5
清零梯度 (在下一次迭代前必须执行)
w.grad.zero_()
b.grad.zero_()
print(f”清零后的梯度 w.grad: {w.grad}, b.grad: {b.grad}”)
训练迭代 2 (省略参数更新部分,只展示梯度清零的必要性)
前向传播 (假设 w, b 保持不变,实际训练中会更新)
y_pred = w * x_data + b # 仍然 2.5
loss = (y_pred – target)**2 # 仍然 6.25
反向传播
loss.backward() # 如果不清零,这里的梯度会累加到 w.grad 和 b.grad 上
print(f”迭代 2 (未清零前) 后的梯度 w.grad: {w.grad}, b.grad: {b.grad}”)
如果不清零,w.grad 会变成 -10 + (-10) = -20,b.grad 会变成 -5 + (-5) = -10
因为我们清零了,所以现在 w.grad 和 b.grad 应该再次是 -10 和 -5
“`
5. 停止梯度跟踪
在某些情况下,你可能希望停止 Autograd 跟踪计算历史,例如:
* 在模型评估或推理阶段,不需要计算梯度,这可以提高性能和减少内存消耗。
* 在训练循环中,有时需要对张量进行操作,但不希望这些操作被记录在计算图中(例如,计算损失函数的值用于监控)。
有两种主要方式可以停止梯度跟踪:
-
torch.no_grad()
上下文管理器: 在with torch.no_grad():
块中执行的操作,其结果张量将不具有requires_grad=True
,即使输入张量具有此属性。
python
x = torch.tensor(2.0, requires_grad=True)
with torch.no_grad():
y = x * 2
print(f"y (在 no_grad 中): {y}, requires_grad: {y.requires_grad}") # False -
.detach()
方法: 从计算图中分离出一个张量,返回一个新的张量,该张量与原张量共享数据,但不再是计算图的一部分,因此不需要梯度。
python
x = torch.tensor(2.0, requires_grad=True)
y = x * 2
z = y.detach()
print(f"z (从 y detach): {z}, requires_grad: {z.requires_grad}") # False
print(f"y requires_grad: {y.requires_grad}") # y 仍然是 True
detach()
常用于需要将计算结果作为 NumPy 数组使用(因为 NumPy 数组不支持梯度)或需要将结果用于不希望被记录在计算图中的后续操作。
理解并熟练使用 Autograd 是进行深度学习训练的关键。它让 PyTorch 能够自动完成复杂的梯度计算,使开发者能够专注于模型的设计和实验。
第三部分:构建神经网络——使用 nn.Module
在 PyTorch 中,构建神经网络的核心是 torch.nn
模块。它提供了各种预定义的神经网络层(如线性层、卷积层、激活函数等)、损失函数以及一个基础类 nn.Module
,用于构建更复杂的模型。
1. nn.Module
的作用
nn.Module
是所有神经网络模块的基类。一个模块可以包含其他模块(构成嵌套结构),也可以包含模型参数(张量,如权重和偏置)。
继承自 nn.Module
的好处包括:
* 参数管理: 它会自动跟踪模块中定义的所有 nn.Parameter
或其他 nn.Module
中的参数,方便通过 .parameters()
或 .named_parameters()
方法访问。
* GPU 支持: 可以方便地使用 .to(device)
方法将整个模块及其所有参数移动到 GPU 或其他设备上。
* 训练/评估模式: 可以使用 .train()
和 .eval()
方法来设置模块的模式,这会影响一些特殊层(如 Dropout 和 BatchNorm)的行为。
* 保存/加载: 提供了方便的方法来保存和加载模块的状态字典 (.state_dict()
, .load_state_dict()
)。
2. 构建一个简单的神经网络
构建一个神经网络通常涉及以下几个步骤:
- 继承
nn.Module
基类。 - 在
__init__
方法中定义网络的层或其他子模块。这些层通常是nn.Linear
,nn.Conv2d
,nn.ReLU
,nn.MaxPool2d
等nn.Module
的实例。注意: 参数层(如nn.Linear
)的权重和偏置都是requires_grad=True
的张量,会自动被nn.Module
跟踪。 - 在
forward
方法中定义数据在网络中的前向传播路径。这是模型的核心逻辑,描述了输入数据如何依次经过各个层得到输出。
让我们构建一个简单的多层感知机(MLP):
“`python
import torch
import torch.nn as nn
import torch.nn.functional as F # 常用的激活函数、池化等操作也可以在这里找到
定义设备
device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)
定义一个简单的 MLP 类
class SimpleMLP(nn.Module):
def init(self, input_size, hidden_size, output_size):
super(SimpleMLP, self).init() # 调用父类的构造函数
# 定义线性层
self.fc1 = nn.Linear(input_size, hidden_size) # 全连接层 1: input_size -> hidden_size
self.relu = nn.ReLU() # ReLU 激活函数
self.fc2 = nn.Linear(hidden_size, output_size) # 全连接层 2: hidden_size -> output_size
def forward(self, x):
# 定义前向传播过程
out = self.fc1(x) # 数据通过第一个全连接层
out = self.relu(out) # 应用 ReLU 激活
out = self.fc2(out) # 数据通过第二个全连接层
return out
创建模型实例
input_dim = 784 # 例如 MNIST 图像展平后的维度
hidden_dim = 128
output_dim = 10 # 例如 10 个类别
model = SimpleMLP(input_dim, hidden_dim, output_dim)
将模型移动到设备
model.to(device)
print(“模型结构:”)
print(model)
检查模型参数 (参数会自动注册)
print(“\n模型参数:”)
for name, param in model.named_parameters():
if param.requires_grad: # 只打印需要梯度的参数
print(f”参数名: {name}, 形状: {param.shape}”)
模拟一个输入张量 (批量大小为 64, 输入维度 784)
dummy_input = torch.randn(64, input_dim).to(device)
进行一次前向传播
output = model(dummy_input)
print(f”\n输入形状: {dummy_input.shape}”)
print(f”输出形状: {output.shape}”)
“`
在这个例子中:
* __init__
中定义了两个线性层 fc1
和 fc2
,以及一个 ReLU 激活函数。
* forward
方法接收输入张量 x
,依次通过 fc1
、relu
和 fc2
层,最后返回输出。
* 当我们调用 model(dummy_input)
时,实际上是调用了 model.forward(dummy_input)
方法。
3. 常用的层和函数
torch.nn
模块提供了大量的预定义层和函数:
- 线性层:
nn.Linear(in_features, out_features)
- 卷积层:
nn.Conv1d
,nn.Conv2d
,nn.Conv3d
- 池化层:
nn.MaxPool1d
,nn.MaxPool2d
,nn.MaxPool3d
,nn.AvgPool2d
- 激活函数:
nn.ReLU
,nn.Sigmoid
,nn.Tanh
,nn.LeakyReLU
,nn.Softmax
(也可以在torch.nn.functional
中找到对应的函数版本,如F.relu
) - 归一化层:
nn.BatchNorm1d
,nn.BatchNorm2d
- Dropout 层:
nn.Dropout
(用于防止过拟合) - 循环神经网络层:
nn.RNN
,nn.LSTM
,nn.GRU
- 嵌入层:
nn.Embedding
(用于将离散数据如词索引映射到连续向量)
这些层都是 nn.Module
的子类,可以像乐高积木一样组合起来构建复杂的网络结构。
第四部分:损失函数(Loss Functions)和优化器(Optimizers)
1. 损失函数(Loss Functions)
损失函数衡量了模型预测输出与真实标签之间的差距。在训练过程中,我们希望最小化这个损失。torch.nn
模块也提供了多种常用的损失函数。
一些常见的损失函数:
* 均方误差(MSE)损失: nn.MSELoss()
– 用于回归问题,计算预测值和目标值之间差的平方的平均值。
* 交叉熵损失(Cross-Entropy Loss): nn.CrossEntropyLoss()
– 常用于多类别分类问题。它结合了 LogSoftmax 和 NLLLoss(负对数似然损失),通常用于模型的输出是原始分数(logits)而非概率的情况。
* 二元交叉熵损失(Binary Cross-Entropy Loss): nn.BCELoss()
或 nn.BCEWithLogitsLoss()
– 用于二元分类问题。BCEWithLogitsLoss
更稳定,并且将 Sigmoid 层集成在了损失函数中。
使用损失函数:
“`python
创建一个损失函数实例
loss_fn = nn.CrossEntropyLoss() # 用于分类问题
模拟模型的预测输出 (logits) 和真实标签
predictions = torch.randn(10, 5, requires_grad=True) # 批量大小 10, 5个类别
targets = torch.empty(10, dtype=torch.long).random_(5) # 10个样本的类别索引 (0-4)
计算损失值
loss = loss_fn(predictions, targets)
print(f”计算的损失值: {loss.item()}”) # 使用 .item() 获取标量张量的 Python 数值
“`
注意: 大多数 PyTorch 损失函数期望模型的原始输出(如线性层的输出)作为输入,而不是经过 Softmax 或 Sigmoid 转换后的概率。查阅文档以确认具体损失函数的输入要求。
2. 优化器(Optimizers)
优化器的作用是根据计算得到的梯度来更新模型参数,以使损失函数最小化。torch.optim
模块提供了各种优化算法。
一些常见的优化器:
* 随机梯度下降(SGD): torch.optim.SGD(params, lr=...)
– 最基本的优化器,通过学习率控制更新步长。可以添加动量(momentum)。
* Adam: torch.optim.Adam(params, lr=...)
– 一种自适应学习率优化算法,通常在实践中表现良好。
* Adagrad, RMSprop, AdamW 等: 其他各种优化算法。
创建优化器:
“`python
假设 model 是前面定义的 SimpleMLP 实例
创建 SGD 优化器,需要传入模型参数和学习率
model.parameters() 会返回一个迭代器,包含模型中所有 requires_grad=True 的参数
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
创建 Adam 优化器
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
“`
优化器的工作流程:
在每个训练迭代中:
1. 清零梯度: 调用 optimizer.zero_grad()
清除之前计算的梯度。
2. 前向传播: 将输入数据通过模型得到预测输出。
3. 计算损失: 使用损失函数计算预测输出和真实标签之间的损失。
4. 反向传播: 调用 loss.backward()
计算损失关于模型所有 requires_grad=True
参数的梯度。这些梯度会存储在对应参数的 .grad
属性中。
5. 参数更新: 调用 optimizer.step()
根据存储在 .grad
中的梯度和优化器的算法更新模型参数。
第五部分:将所有组件整合:一个简单的训练示例
现在我们已经了解了 PyTorch 的核心组件:张量、Autograd、nn.Module
、损失函数和优化器。是时候将它们组合起来,看看如何训练一个简单的模型了。
我们将使用一个简单的线性回归问题作为例子:找到最佳的 w
和 b
,使得 y = w*x + b
能够最好地拟合给定的数据。
“`python
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt # 用于可视化
1. 准备数据
生成一些线性关系的数据 y = 2*x + 1 + 噪声
torch.manual_seed(42) # 设置随机种子,以便结果可复现
X_train = torch.randn(100, 1) * 10 # 100个样本,特征维度 1
y_train = 2 * X_train + 1 + torch.randn(100, 1) * 2 # 加上一些噪声
2. 定义模型
使用 nn.Linear 构建一个简单的线性模型
class LinearRegressionModel(nn.Module):
def init(self):
super(LinearRegressionModel, self).init()
self.linear = nn.Linear(1, 1) # 输入维度 1, 输出维度 1
def forward(self, x):
return self.linear(x)
model = LinearRegressionModel()
将模型移动到设备 (如果可用 GPU 会快很多,但这里数据量小影响不大)
device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)
model.to(device)
X_train = X_train.to(device)
y_train = y_train.to(device)
print(“初始模型参数:”)
for name, param in model.named_parameters():
print(f”{name}: {param.data}”)
3. 定义损失函数和优化器
criterion = nn.MSELoss() # 均方误差损失,适合回归问题
optimizer = optim.SGD(model.parameters(), lr=0.01) # SGD 优化器,学习率 0.01
4. 训练模型
num_epochs = 100 # 训练迭代次数
print(“\n开始训练…”)
for epoch in range(num_epochs):
# 前向传播
outputs = model(X_train)
# 计算损失
loss = criterion(outputs, y_train)
# 反向传播和优化
optimizer.zero_grad() # 清零梯度
loss.backward() # 计算梯度
optimizer.step() # 更新参数
# 打印训练信息
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
print(“\n训练结束.”)
print(“最终模型参数:”)
for name, param in model.named_parameters():
print(f”{name}: {param.data}”)
5. 结果可视化
将数据移回 CPU 进行可视化
predicted = model(X_train).detach().cpu().numpy() # 使用 detach() 停止梯度跟踪并转为 NumPy
X_train_np = X_train.cpu().numpy()
y_train_np = y_train.cpu().numpy()
plt.plot(X_train_np, y_train_np, ‘bo’, label=’Original data’, markersize=5)
plt.plot(X_train_np, predicted, ‘r-‘, label=’Fitted line’)
plt.legend()
plt.xlabel(‘X’)
plt.ylabel(‘y’)
plt.title(‘Linear Regression with PyTorch’)
plt.grid(True)
plt.show()
“`
这个示例代码展示了一个完整的 PyTorch 训练流程:数据准备、模型定义、损失函数和优化器选择、以及最重要的训练循环。训练循环中的四个核心步骤——清零梯度、前向传播、计算损失、反向传播和更新参数——是所有 PyTorch 模型训练的基础模式。
第六部分:PyTorch 的特点与优势回顾
通过前面的学习,我们已经接触到了 PyTorch 的几个核心优势:
- Pythonic 设计: PyTorch 的 API 设计非常符合 Python 的习惯,易于学习和使用。
- 动态计算图: PyTorch 使用动态计算图,这意味着计算图是在前向传播过程中实时构建的。这使得调试更加容易(可以直接使用 Python 的 pdb 调试器),并且可以处理变长的输入序列等复杂情况。这与 TensorFlow 1.x 的静态图形成鲜明对比(尽管 TensorFlow 2.x 也引入了动态图)。
- 易于调试: 由于动态图特性,当出现错误时,可以直接在运行过程中定位到出错的代码行。
- 强大的社区和生态: PyTorch 拥有一个活跃的社区,提供了丰富的教程、文档和第三方库(如
torchvision
,torchaudio
,torchtext
,transformers
等),覆盖了计算机视觉、自然语言处理、音频处理等多个领域。 - 研究友好: 其灵活性和易用性使得 PyTorch 在学术研究领域非常受欢迎,许多最新的研究成果都首先在 PyTorch 中实现。
第七部分:超越基础:接下来可以学习什么?
掌握了张量、Autograd 和 nn.Module
这些基础知识后,你已经具备了继续深入学习 PyTorch 的能力。接下来可以探索的主题包括:
- 数据加载: 使用
torch.utils.data.Dataset
和DataLoader
高效地加载和批量处理数据。 - 更多层类型: 学习更复杂的网络层,如卷积层、池化层、循环层、Transformer 层等,以及如何将它们组合起来构建现代深度学习模型。
- 模型保存与加载: 学习如何保存模型的参数或整个模型,以及如何在需要时重新加载它们。
- GPU 加速: 更深入地理解如何在 GPU 上高效地进行训练。
- 训练技巧: 学习学习率调度、正则化(Dropout, Weight Decay)、梯度裁剪、优化器选择等训练过程中的常用技巧。
- 可视化: 使用 TensorBoard 或 matplotlib 等工具可视化训练过程、模型结构和结果。
- 预训练模型与迁移学习: 学习如何利用他人训练好的大型模型进行迁移学习。
- 分布式训练: 学习如何在多台机器或多个 GPU 上训练模型以加速。
- 部署: 了解如何将训练好的 PyTorch 模型部署到服务器、移动设备或边缘设备上(如使用 TorchScript, ONNX, PyTorch Mobile)。
- 特定领域库: 深入学习
torchvision
,torchtext
,torchaudio
等 PyTorch 官方库,或 Hugging Face 的transformers
等第三方库。
结论
PyTorch 作为一个强大、灵活且易于使用的深度学习框架,为研究人员和开发者提供了构建、训练和部署深度学习模型的坚实基础。本文详细介绍了 PyTorch 的核心概念:张量是数据的载体,Autograd 是梯度计算的魔法,nn.Module
是构建网络结构的基石,而损失函数和优化器则是驱动模型学习的关键。
通过一个简单的线性回归示例,我们展示了这些组件如何在实际训练流程中协同工作。掌握这些基础知识,你已经迈出了使用 PyTorch 进行深度学习实践的第一步。
深度学习是一个充满活力和快速发展的领域,持续学习和实践是掌握它的关键。鼓励你在 PyTorch 官方文档、教程以及各种在线资源中继续探索,动手实践,构建自己的模型,解决实际问题。
祝你在 PyTorch 的世界里学习顺利,取得丰硕成果!