在深度学习与人工智能领域,PyTorch已成为研究者与开发者手中的利剑,以其灵活高效的特性,不断推动着新技术的边界。对于每一位致力于掌握PyTorch精髓的学习者来说,深入了解其核心操作不仅是提升技能的关键,也是迈向高级应用与创新研究的必经之路。本文精心梳理了PyTorch的核心操作,这不仅是一份全面的技术指南,更是每一个PyTorch实践者的智慧锦囊,建议收藏!
一、张量创建和基本操作
1.张量创建
(1) 从Python列表或Numpy数组创建张量
使用torch.tensor()函数可以直接从Python列表创建张量。并且,PyTorch设计时考虑了与NumPy的互操作性,也可以使用torch.tensor()函数从NumPy数组创建张量。
import numpy as np
import torch
# 从列表创建张量
list_data = [1, 2, 3, 4]
tensor_from_list = torch.tensor(list_data)
print(tensor_from_list) # tensor([1, 2, 3, 4])
# 从NumPy数组创建张量
np_array = np.array([1, 2, 3])
tensor_from_np_tensor = torch.tensor(np_array)
print(tensor_from_np_tensor) # tensor([1, 2, 3], dtype=torch.int32)
(2) 使用固定数值创建张量
- torch.zeros(shape)和torch.ones(shape):创建指定形状的全零或全一张量。
'''
tensor([[0., 0., 0.],
[0., 0., 0.]])
'''
zeros_tensor = torch.zeros(2, 3)
'''
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
'''
ones_tensor = torch.ones(3, 4)
- torch.rand(shape):创建指定形状的随机浮点数张量(0到1之间)或标准正态分布的张量。
'''
tensor([[0.7632, 0.9953, 0.8954],
[0.8681, 0.7707, 0.8806]])
'''
random_tensor = torch.rand(2, 3)
'''
tensor([[ 0.1873, -1.6907, 0.4717, 1.0271],
[-1.0680, 0.7490, -0.5693, 0.6490],
[ 0.0429, -1.5796, -2.3312, -0.2733]])
'''
normal_tensor = torch.randn(3, 4)
- torch.arange(start, end=None, step=1, dtype=None, layout=torch.strided, device=None, requires_grad=False):创建一个等差序列张量,默认step为1。
arange_tensor = torch.arange(1, 10) #tensor([1, 2, 3, 4, 5, 6, 7, 8, 9])
2.张量的基本操作
(1) 张量索引和切片
张量的索引和切片类似于Python列表,可以访问和修改张量的特定元素或子集。
# 创建一个张量
tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])
# 索引单个元素
element = tensor[0, 0] # 1
# 切片
slice_1 = tensor[0, :] # [1, 2, 3]
slice_2 = tensor[:, 1] # [2, 5]
# 修改元素
tensor[0, 0] = 7
print(tensor) # [[7, 2, 3], [4, 5, 6]]
(2) 形状操作
- shape属性获取张量的形状:
shape = tensor.shape # torch.Size([2, 3])
- unsqueeze(dim)在指定维度添加一个大小为1的新维度:
expanded_tensor = tensor.unsqueeze(0) # 添加一个新维度,形状变为[1, 2, 3]
- reshape(shape)或view(shape)改变张量的形状:
reshaped_tensor = tensor.reshape(6) # 变为形状为[6]的一维张量
- transpose(dim0, dim1)交换指定的两个维度:
transposed_tensor = tensor.transpose(0, 1) # 交换第一和第二维度
(3) 数学运算
PyTorch张量支持广泛的数学运算,包括基本的算术运算、元素级运算、矩阵运算等。以tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])为例。
基本运算(适用于标量、一维或多维张量):
addition = tensor + tensor # [[2, 4, 6], [8, 10, 12]]。
subtraction = tensor - tensor #[[0, 0, 0], [0, 0, 0]]
multiplication = tensor * tensor #[[1, 4, 9], [16, 25, 36]]
division = tensor / tensor #[[1, 1, 1], [1, 1, 1]]
元素级运算(*运算符在这种情况下表示逐元素乘法,而不是矩阵乘法):
elementwise_product = tensor * tensor # [[1, 4, 9], [16, 25, 36]]
矩阵运算(使用@运算符或torch.matmul()):
matrix_product = tensor @ tensor.t() # 或者 torch.matmul(tensor, tensor.t())
# tensor @ tensor.t() 结果是一个标量,因为张量是方阵,且张量与其转置的点积等于1*4 + 2*5 + 3*6 = 4 + 10 + 18 = 32
广播机制(使得不同形状的张量能够进行运算):
'''
结果是一个2x3的张量,其中每个元素都是tensor对应位置的元素加上1
broadcasted_addition = [[2, 3, 4], [5, 6, 7]]
'''
broadcasted_addition = tensor + torch.tensor([1, 1, 1])
比较运算(返回布尔张量):
# 返回一个2x3的布尔张量,所有元素都为False,因为每个元素都不大于自身。
greater_than = tensor > tensor
聚合运算(如求和、平均、最大值、最小值等):
sum = torch.sum(tensor) # 返回张量所有元素的总和,即1+2+3+4+5+6=21。
mean = torch.mean(tensor) #返回张量的平均值,即21/6=3.5。
# 返回最大值张量[4, 5, 6]和最大值的索引[1, 1, 1],因为第二行的所有元素都是最大值。
max_value, max_index = torch.max(tensor, dim=0) # 按列求最大值和对应索引
二、自动求导
在深度学习中,自动求导(automatic differentiation)是关键步骤,用于计算损失函数对模型参数的梯度。PyTorch提供了自动求导机制,可以轻松地定义和计算复杂的神经网络。
1.张量的requires_grad属性
在PyTorch中,requires_grad属性用于决定张量是否应该在计算过程中跟踪其操作,以便进行自动求导。如果计算张量的梯度,就需要设置requires_grad=True。
import torch
# 创建一个张量并设置requires_grad=True
x = torch.tensor([1.0, 2.0], requires_grad=True)
# Original tensor: tensor([1., 2.], requires_grad=True)
print("Original tensor:", x)
2.张量操作与计算图
在PyTorch中,张量操作与计算图紧密相关,因为计算图是实现自动求导(autograd)的关键。当一个张量的requires_grad属性被设置为True时,PyTorch会记录对该张量的所有操作,形成一个计算图。这个图描述了从输入张量到输出张量的计算路径,每个节点代表一个张量,边代表操作。
import torch
# 创建一个需要梯度的张量
x = torch.tensor([1.0, 2.0], requires_grad=True)
# 张量操作
y = x + 2 # 加法操作
z = y * y * 3 # 乘法操作
out = z.mean() # 平均值操作
此时,计算图已经隐含地构建完成,记录了从x到out的所有操作。其中,x是计算图的起点,out是终点。
3.计算梯度
要计算梯度,只需对计算图的最终输出调用.backward()方法。
# 计算梯度
out.backward()
# 现在,x的梯度已经计算出来了,可以通过x.grad属性获取
print("Gradient of x with respect to the output:", x.grad)
out.backward()会沿着计算图反向传播,计算所有涉及张量的梯度。在本例中,x.grad将会给出x相对于out的梯度,即out关于x的偏导数。这在训练神经网络时非常有用,因为可以用来更新网络的权重。
4.阻止梯度追踪
在某些场景下,比如验证模型或计算某些不需要更新参数的中间结果时,阻止梯度追踪可以减少内存消耗和提高效率。使用.detach()或torch.no_grad()是实现这一目的的有效手段。
- 使用.detach()方法: 返回一个与原始张量数值相同的新张量,但不跟踪梯度。
new_tensor = original_tensor.detach()
- 使用torch.no_grad()上下文管理器。
with torch.no_grad():
# 在此区域内进行的操作不会追踪梯度
intermediate_result = some_operation(original_tensor)
5.控制梯度计算的上下文管理器
torch.autograd.set_grad_enabled(True|False) 是另一个强大的工具,用于全局控制是否在代码的特定部分进行梯度计算。相比.detach()和torch.no_grad(),它提供了更多的灵活性,因为它允许在代码的不同部分动态开启或关闭梯度追踪,这对于复杂的模型调试、性能优化或混合精度训练等场景特别有用。
import torch
# 默认情况下,梯度追踪是开启的
print(f"当前梯度追踪状态: {torch.is_grad_enabled()}") # 输出: True
# 使用set_grad_enabled(False)关闭梯度追踪
with torch.autograd.set_grad_enabled(False):
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x * 2
print(f"在上下文中,梯度追踪状态: {torch.is_grad_enabled()}") # 输出: False
print(f"y的requires_grad属性: {y.requires_grad}") # 输出: False
# 离开上下文后,梯度追踪状态恢复到之前的状态
print(f"离开上下文后,梯度追踪状态: {torch.is_grad_enabled()}") # 输出: True
# 这里x的梯度追踪仍然是开启的,除非在其他地方被改变
6.优化器与自动求导结合
在训练神经网络模型时,通常会使用优化器(optimizer)来更新模型的参数。优化器利用自动求导计算出的梯度来调整参数,以最小化损失函数。以下是一个使用随机梯度下降(SGD)优化器的简单示例:
import torch
from torch import nn, optim
# 定义一个简单的线性模型
model = nn.Linear(2, 1) # 输入2个特征,输出1个值
# 假设有一些输入数据和标签
inputs = torch.randn(10, 2) # 10个样本,每个样本2个特征
labels = torch.randn(10, 1) # 10个样本,每个样本1个标签
# 创建优化器,指定要更新的模型参数
optimizer = optim.SGD(model.parameters(), lr=0.01) # 学习率为0.01
# 前向传播
outputs = model(inputs)
# 损失函数
loss = nn.MSELoss()(outputs, labels)
# 反向传播并计算梯度
loss.backward()
# 使用优化器更新参数
optimizer.step()
# 清除梯度(防止梯度累积)
optimizer.zero_grad()
三、神经网络层
在PyTorch中,nn.Module是构建神经网络模型的核心类,它提供了一个模块化的框架,可以方便地组合各种层和操作。
1.创建自定义神经网络层:
创建自定义神经网络层是PyTorch中常见的做法。以下是如何创建一个名为CustomLayer的自定义层,它包含一个线性层和一个激活函数(例如ReLU):
import torch
import torch.nn as nn
class CustomLayer(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(CustomLayer, self).__init__()
# 创建线性层
self.linear = nn.Linear(input_size, hidden_size)
# 创建ReLU激活函数
self.relu = nn.ReLU()
# 创建输出线性层(如果需要的话,例如对于分类任务)
self.output_linear = nn.Linear(hidden_size, output_size)
def forward(self, x):
# 应用线性变换
x = self.linear(x)
# 应用ReLU激活函数
x = self.relu(x)
# 如果需要,可以添加更多的操作,例如另一个线性层
x = self.output_linear(x)
return x
其中,CustomLayer类继承自nn.Module,并在__init__方法中定义了两个线性层(一个输入层和一个输出层)以及一个ReLU激活函数。forward方法描述了输入到输出的计算流程:首先,输入通过线性层,然后通过ReLU激活,最后通过输出线性层。如果只需要一个线性变换和激活,可以去掉output_linear。
使用这个自定义层,可以像使用内置层一样在模型中实例化和使用它:
input_size = 10
hidden_size = 20
output_size = 5
model = CustomLayer(input_size, hidden_size, output_size)
2.构建复杂的神经网络模型
构建复杂的神经网络模型通常涉及到将多个基本层或者自定义层按照特定顺序组合起来。以下是一个使用自定义SimpleLayer类来构建多层感知机(MLP)的示例:
import torch
import torch.nn as nn
class SimpleLayer(nn.Module):
def __init__(self, input_size, output_size):
super(SimpleLayer, self).__init__()
self.linear = nn.Linear(input_size, output_size)
self.relu = nn.ReLU()
def forward(self, x):
x = self.linear(x)
x = self.relu(x)
return x
class MLP(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(MLP, self).__init__()
# 第一层:输入层到隐藏层
self.layer1 = SimpleLayer(input_dim, hidden_dim)
# 第二层:隐藏层到输出层
self.layer2 = SimpleLayer(hidden_dim, output_dim)
def forward(self, x):
x = self.layer1(x) # 第一层前向传播
x = self.layer2(x) # 第二层前向传播
return x
# 实例化一个MLP模型
input_dim = 784 # 假设输入维度为784(例如,MNIST数据集)
hidden_dim = 128 # 隐藏层维度
output_dim = 10 # 输出维度(例如,10类分类问题)
model = MLP(input_dim, hidden_dim, output_dim)
# 打印模型结构
print(model)
在以上代码中,MLP类定义了一个包含两个SimpleLayer实例的多层感知机。第一个SimpleLayer接收输入数据并转换到隐藏层空间,第二个SimpleLayer则负责从隐藏层空间映射到输出层。通过这种方式,可以灵活地堆叠多个层来构造复杂的神经网络模型,每增加一层,模型的表达能力就可能增强,从而能学习到更复杂的输入-输出映射关系。
3.模块的嵌套和子模块
在PyTorch中,nn.Module的嵌套和子模块是构建复杂神经网络架构的关键。下面是一个名为ComplexModel的示例,它包含了两个子模块:一个CustomLayer和一个MLP:
import torch
import torch.nn as nn
class ComplexModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(ComplexModel, self).__init__()
# 创建一个CustomLayer实例
self.custom_layer = CustomLayer(input_size, hidden_size, output_size)
# 创建一个MLP实例
self.mlp = MLP(hidden_size, hidden_size, output_size)
def forward(self, x):
x = self.custom_layer(x)
x = self.mlp(x)
return x
# 实例化一个ComplexModel
input_size = 784 # 假设输入维度为784
hidden_size = 128 # 隐藏层维度
output_size = 10 # 输出维度
model = ComplexModel(input_size, hidden_size, output_size)
# 打印模型结构
print(model)
其中,ComplexModel类包含两个子模块:custom_layer和mlp。custom_layer是一个自定义层,而mlp是一个多层感知机。当在ComplexModel的forward方法中调用这些子模块时,它们的计算将按顺序进行,并且所有子模块的参数和梯度都会被自动跟踪。这种模块化的方法使得代码易于理解和维护,同时可以方便地重用和组合现有的层和模型。
4.访问模块的参数
在PyTorch中,nn.Module类提供了两种方法来访问模型的参数:parameters()和named_parameters()。这两个方法都可以用来遍历模型的所有参数,但它们的区别在于返回的内容。
- parameters()方法: 返回一个可迭代的生成器,其中每个元素是一个张量,代表模型的一个参数。
model = ComplexModel()
for param in model.parameters():
print(param)
- named_parameters()方法: 返回一个可迭代的生成器,其中每个元素是一个元组,包含参数的名称和对应的张量。
model = ComplexModel()
for name, param in model.named_parameters():
print(f"Name: {name}, Parameter: {param}")
在实际应用中,通常使用named_parameters()方法,因为这样可以同时获取参数的名称,这对于调试和可视化模型参数很有用。
5.模型的保存与加载
要保存模型的状态字典,可以使用state_dict()方法,然后使用torch.save()将其写入磁盘。加载模型时,首先创建一个模型实例,然后使用load_state_dict()方法加载保存的参数。
- 保存模型:
torch.save(model.state_dict(), 'model.pth')
- 加载模型:
model = ComplexModel(input_size, hidden_size, output_size)
model.load_state_dict(torch.load('model.pth'))
6.模型的设备移动
使用to()方法可以将模型及其所有参数移到GPU或CPU上。如果设备可用,它会尝试将模型移动到GPU上,否则保留在CPU上。将模型移动到GPU(如果可用):
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
7.自定义层和操作
要创建自定义的神经网络层或操作,可以继承nn.Module,再实现新的前向传播逻辑,包括新的数学函数、正则化、注意力机制等。例如,假设要创建一个自定义的归一化层:
class CustomNormalization(nn.Module):
def __init__(self, dim):
super(CustomNormalization, self).__init__()
self.dim = dim
def forward(self, x):
mean = x.mean(dim=self.dim, keepdim=True)
std = x.std(dim=self.dim, keepdim=True)
return (x - mean) / (std + 1e-8)
model.add_module('custom_normalization', CustomNormalization(1))
首先定义了一个新的层CustomNormalization,它计算输入张量在指定维度上的平均值和标准差,然后对输入进行归一化。这个新层可以像其他任何nn.Module实例一样添加到模型中,并在forward方法中使用。
四、优化器
在 PyTorch 中,优化器(Optimizer)是用于更新神经网络模型参数的工具。优化器基于模型参数的梯度信息来调整参数,从而最小化或最大化某个损失函数。PyTorch 提供了多种优化器,包括随机梯度下降(SGD)、Adam、RMSprop 等。
1.SGD
SGD是最基础的优化算法,它根据梯度的方向逐步调整模型参数,以减少损失函数的值。在PyTorch中,可以通过以下方式创建一个SGD优化器:
import torch.optim as optim
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
其中,model.parameters()用于获取模型的所有可学习参数。lr是学习率,决定了参数更新的步长。momentum是动量项,默认为0,可以加速学习过程并有助于跳出局部最小值。
2.Adam
Adam(Adaptive Moment Estimation)是另一种广泛使用的优化器,它结合了动量和自适应学习率的优点,可自动调整每个参数的学习率,通常不需要手动调整学习率衰减。
optimizer = optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999))
其中,betas是两个超参数,分别控制了一阶矩和二阶矩估计的衰减率。
3.RMSprop
RMSprop(Root Mean Square Propagation)也是自适应学习率的一种方法,它主要根据历史梯度的平方根来调整学习率。
optimizer = optim.RMSprop(model.parameters(), lr=0.001, alpha=0.99)
其中,alpha是平滑常数,用于计算梯度的移动平均。
五、损失函数
损失函数(Loss Function)在机器学习和深度学习领域扮演着核心角色,它是评估模型预测质量的重要标准。损失函数量化了模型预测值与真实标签之间的偏差,训练过程本质上是通过优化算法(如梯度下降)不断调整模型参数,以最小化这个损失值。PyTorch,作为一个强大的深度学习框架,内置了多种损失函数,以适应不同类型的机器学习任务,主要包括但不限于以下几类:
1.均方误差损失(Mean Squared Error, MSE)
用于回归问题,计算预测值与真实值之间差的平方的平均值。
import torch
import torch.nn as nn
# 假设模型输出和真实标签
outputs = model(inputs) # 模型的预测输出
targets = labels # 真实标签
# 定义损失函数
mse_loss = nn.MSELoss()
# 计算损失
loss = mse_loss(outputs, targets)
2.交叉熵损失(Cross-Entropy)
用于分类问题,计算预测概率分布与真实标签之间的交叉熵损失。
# 假设模型输出为每个类别的概率分布
outputs = model(inputs)
# 确保标签是类别索引(而非one-hot编码),对于多分类问题
targets = torch.LongTensor(labels) # 确保标签是整数
cross_entropy_loss = nn.CrossEntropyLoss()
loss = cross_entropy_loss(outputs, targets)
3.二元交叉熵损失(Binary Cross-Entropy)
二元交叉熵损失通常用于二分类问题,其中每个样本属于两个类别之一。
# 假设二分类问题,直接输出概率
outputs = model(inputs) # 输出已经是概率
targets = labels.float() # 标签转换为float,二分类通常为0或1
bce_loss = nn.BCELoss()
loss = bce_loss(outputs, targets)
如果两类样本数量严重不平衡,可以使用加权二元交叉熵损失(Weighted Binary Cross-Entropy Loss)来调整不同类别的权重,以确保模型在训练时对较少出现的类别给予更多关注。
import torch
import torch.nn as nn
# 假设有两类,其中类0的样本较少
num_samples = [100, 1000] # 类别0有100个样本,类别1有1000个样本
weights = [1 / num_samples[0], 1 / num_samples[1]] # 计算类别权重
# 创建一个加权二元交叉熵损失函数
weighted_bce_loss = nn.BCEWithLogitsLoss(weight=torch.tensor(weights))
# 假设model是模型,inputs是输入数据,labels是二进制标签(0或1)
outputs = model(inputs)
labels = labels.float() # 将标签转换为浮点数,因为BCEWithLogitsLoss期望的是概率
# 计算加权损失
loss = weighted_bce_loss(outputs, labels)
4.K-L 散度损失(Kullback-Leibler Divergence Loss)
衡量两个概率分布的差异,常用于生成模型训练,如VAEs和GANs中的鉴别器部分。
Kullback-Leibler散度(KLDivLoss)是一种衡量两个概率分布之间差异的方法,常用于信息论和机器学习中。在PyTorch中,nn.KLDivLoss是实现这一概念的模块,用于比较预测概率分布(通常是softmax函数的输出)与目标概率分布或“真实”分布。
KLDivLoss的数学定义为:
这里,(P)是真实分布,而(Q)是预测或近似分布。KLDivLoss总是非负的,并且只有当两个分布完全相同时才为零。
import torch
import torch.nn as nn
# 假设有两个概率分布,preds是模型预测的概率分布,targets是实际的概率分布
preds = torch.randn(3, 5).softmax(dim=1) # 预测概率分布,使用softmax转换
targets = torch.randn(3, 5).softmax(dim=1) # 真实概率分布
# 初始化KLDivLoss实例,reduction参数定义了损失的聚合方式,可以是'mean'、'sum'或'none'
criterion = nn.KLDivLoss(reduction='batchmean') # 'batchmean'表示对批量数据求平均
# 计算KLDivLoss
loss = criterion(preds.log(), targets) # 注意:preds应该取对数,因为KLDivLoss默认期望log_softmax的输出
print('KLDivLoss:', loss.item())
5.三元组损失(Triplet Margin Loss)
三元组损失(Triplet Margin Loss)是深度学习中用于学习特征表示的一种损失函数,尤其在人脸识别、图像检索等领域广泛应用。它的目标是学习到一个特征空间,在这个空间中,同类别的样本之间的距离小于不同类别样本之间的距离,且保持一定的边际差(margin)。
三元组损失函数的数学定义为:
其中,
- a是锚点样本的特征向量
- p是正样本的特征向量
- n是负样本的特征向量
- d(x, y)表示样本x和样本y之间的距离(通常是欧氏距离或余弦距离)
- m是预设的边际值,用于保证正样本和负样本之间的差距至少为m。
这个损失函数的目的是最小化所有满足的三元组的损失,这样就能确保锚点样本与正样本的距离小于与负样本的距离至少m个单位。
在PyTorch中,可以使用torch.nn.TripletMarginLoss来实现三元组损失。
import torch
import torch.nn as nn
# 设置随机种子以获得可复现的结果
torch.manual_seed(42)
# 假设特征维度
feature_dim = 128
# 生成随机三元组数据
num_triplets = 10
anchors = torch.randn(num_triplets, feature_dim)
positives = torch.randn(num_triplets, feature_dim)
negatives = torch.randn(num_triplets, feature_dim)
# 将它们组合成形状为 (num_triplets, 3, feature_dim) 的张量
triplets = torch.stack((anchors, positives, negatives), dim=1)
# 初始化三元组损失函数,设置边际值m
triplet_loss = nn.TripletMarginLoss(margin=1.0)
# 计算损失
loss = triplet_loss(triplets[:, 0], triplets[:, 1], triplets[:, 2])
print('Triplet Margin Loss:', loss.item())
6.使用损失函数进行训练
在训练循环中,通过计算模型输出与真实标签的损失,并调用反向传播和优化器更新参数来训练模型。
output = model(inputs)
loss = criterion(output, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
六、数据加载与预处理
在PyTorch中,数据加载与预处理是构建深度学习模型不可或缺的环节,它确保数据以高效、规范的方式被送入模型进行训练或测试。
1.数据集的定义
在PyTorch中,数据集的定义通常通过创建一个新的类来实现,这个类继承自torch.utils.data.Dataset。这个自定义类需要实现以下两个核心方法:
- __len__方法:这个方法返回数据集中的样本数量。它告诉外界调用者数据集中有多少个样本可以用来训练或测试模型。
- __getitem__方法:这个方法根据给定的索引返回一个样本数据及其对应的标签(如果有)。它允许按需访问数据集中的任意一个样本,通常包括数据的加载和必要的预处理。
一个基本的数据集定义示例如下:
from torch.utils.data import Dataset
class CustomDataset(Dataset):
def __init__(self, data, labels, transform=None):
self.data = data
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
sample = {'data': self.data[idx], 'label': self.labels[idx]}
if self.transform:
sample = self.transform(sample)
return sample
其中,CustomDataset类接收3个参数:data和labels、transform,分别代表数据集中的样本数据和对应的标签及预处理变换。在__init__方法中,将这些参数存储在类的属性中,以便在__getitem__方法中访问。__len__方法返回数据集的长度,即样本数量。__getitem__方法通过索引index获取对应的数据和标签。
此外,为了增强数据的多样性和模型的泛化能力,可以在数据集类中集成数据预处理逻辑,或者通过传递一个变换对象(如torchvision.transforms中的变换)来动态地对数据进行变换,如旋转、缩放、裁剪等。
2.数据加载器
数据加载器(DataLoader)在PyTorch中是一个非常重要的组件,它负责从数据集中高效地加载数据,并为模型训练和验证提供批次(batch)数据。DataLoader类位于torch.utils.data模块中,提供了以下几个关键功能:
- 批量加载:它能够将数据集分割成多个小批量(batch),这是深度学习训练过程中的标准做法,有助于提高训练效率和内存利用率。
- 数据混洗:通过设置shuffle=True,可以在每个训练epoch开始前随机打乱数据集的顺序,增加模型训练的随机性,有助于提高模型的泛化能力。
- 多线程加载:通过num_workers参数,可以在后台使用多个线程并发地加载数据,减少数据I/O等待时间,进一步加速训练过程。
- 内存节省:DataLoader通过按需加载数据(即仅在训练过程中需要时才从磁盘加载数据到内存),避免一次性将整个数据集加载到内存中,这对于大规模数据集尤为重要。
创建一个DataLoader实例的基本用法如下:
from torch.utils.data import DataLoader
from your_dataset_module import YourCustomDataset
custom_dataset = CustomDataset(data, labels,transform=...)
# 创建DataLoader实例
data_loader = DataLoader(
dataset=custom_dataset, # 数据集实例
batch_size=32, # 每个批次的样本数
shuffle=True, # 是否在每个epoch开始时打乱数据
num_workers=4, # 使用的子进程数,用于数据加载(0表示不使用多线程)
drop_last=False, # 如果数据集大小不能被batch_size整除,是否丢弃最后一个不完整的batch
)
# 使用data_loader在训练循环中迭代获取数据
for inputs, labels in data_loader:
# 在这里执行模型训练或验证的代码
pass
3.数据预处理与转换
在PyTorch中,torchvision.transforms模块提供了丰富的预处理和转换功能,以下是一些常用的转换操作:
(1) 常见的预处理与转换操作:
- Resize:调整图像大小到指定尺寸,例如,transforms.Resize((256, 256))会将图像调整为256x256像素。
- CenterCrop:从图像中心裁剪出指定大小的区域,如transforms.CenterCrop(224)。
- RandomCrop:随机从图像中裁剪出指定大小的区域,增加了数据多样性,有利于模型学习。
- RandomHorizontalFlip:以一定概率水平翻转图像,是常用的数据增强手段之一。
- RandomRotation:随机旋转图像一定角度,进一步增强数据多样性。
- ToTensor:将PIL图像或numpy数组转换为PyTorch的Tensor,并将颜色通道从RGB调整为Tensorflow所期望的格式(HWC -> CHW)。
- Normalize:对图像像素值进行标准化,通常使用特定数据集(如ImageNet)的均值和标准差,例如transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])。
(2) 组合变换
为了简化应用多个变换的过程,可以使用Compose类将多个变换操作组合在一起,形成一个变换管道,如:
transform = transforms.Compose([
transforms.Resize((256, 256)),
transforms.RandomCrop((224, 224)),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
# 将转换应用于数据集
dataset = CustomDataset(data, labels, transform=transform)
七、模型的保存与加载
在PyTorch中,保存和加载模型是通过torch.save()和torch.load()函数完成的。以下是详细的解释和代码示例:
1.模型的保存
模型的参数和状态可以保存为状态字典(state_dict),或者保存整个模型对象,包括模型结构和参数。
- 保存状态字典(仅参数):
# 定义模型
model = SimpleModel()
# 保存状态字典
torch.save(model.state_dict(), 'model_state.pth')
- 保存整个模型(结构+参数):
# 保存整个模型
torch.save(model, 'model.pth')
2.模型的加载
加载模型时,可以单独加载状态字典并重新构建模型,或者直接加载整个模型。
- 加载状态字典并重建模型:
# 加载状态字典
loaded_state_dict = torch.load('model_state.pth')
# 创建相同结构的新模型
new_model = SimpleModel()
# 将加载的状态字典加载到新模型
new_model.load_state_dict(loaded_state_dict)
- 加载整个模型:
# 加载整个模型
loaded_model = torch.load('model.pth')
3.跨设备加载模型
如果模型在GPU上训练并保存,但在CPU上加载,可以使用map_location参数:
# 在CPU上加载GPU上保存的模型
loaded_model = torch.load('model.pth', map_location=torch.device('cpu'))
4.保存与加载模型的结构和参数
在保存整个模型时,模型的结构和参数都会被保存。
# 保存整个模型(包括结构和参数)
torch.save(model, 'model.pth')
# 加载整个模型
loaded_model = torch.load('model.pth')
5.仅保存与加载模型的结构
如果只想保存和加载模型的结构而不包含参数,可以使用 torch.save 时设置 save_model_obj=False。
# 保存模型结构
torch.save(model, 'model_structure.pth', save_model_obj=False)
# 加载模型结构
loaded_model_structure = torch.load('model_structure.pth')
6.仅保存和加载模型的参数
如果只想保存和加载模型参数而不包含模型结构,可以使用 torch.save 时设置 save_model_obj=False。
# 保存模型参数
torch.save(model.state_dict(), 'model_parameters.pth')
# 加载模型参数
loaded_parameters = torch.load('model_parameters.pth')
model.load_state_dict(loaded_parameters)
八、学习率调整
学习率调整是深度学习模型训练过程中的一个重要环节,它有助于模型在训练过程中找到更好的权重。PyTorch 提供了torch.optim.lr_scheduler模块来实现各种学习率调整策略。
1.StepLR
StepLR是一种基础且常用的学习率调整策略,它按照预定的周期(通常是按 epoch 计算)来调整学习率。其中,step_size参数指定了衰减发生的时间间隔。比如,如果step_size=5,则学习率每过5个epoch就会调整一次。 gamma参数控制了每次调整时学习率的衰减比例。如果gamma=0.1,这意味着每到达一个step_size,学习率就会乘以0.1,也就是衰减到原来的10%。
import torch.optim as optim
from torch.optim.lr_scheduler import StepLR
# 初始化模型和优化器
model = YourModel()
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
# 创建 StepLR 调度器
scheduler = StepLR(optimizer, step_size=5, gamma=0.1)
# 训练循环
for epoch in range(num_epochs):
# 训练过程...
# 在每个epoch结束时调用scheduler的step()方法来更新学习率
scheduler.step()
如上示例中,学习率会在每个第5、10、15...个epoch后自动减少为原来的10%,直到训练结束。
2.MultiStepLR
MultiStepLR 与 StepLR 类似,都是基于周期(epoch)来调整学习率,但提供了更灵活的衰减时间点控制。通过 milestones 参数,可以精确指定学习率应该在哪些特定的周期数下降。gamma 参数则决定了每次在这些指定周期学习率下降的比例。
例如,如果设置milestones=[10, 20, 30]和gamma=0.1,则:
- 在训练的第10个epoch结束后,学习率会首次乘以0.1,即减少到原来的10%。
- 接着,在第20个epoch后,学习率再次乘以0.1,相对于初始值衰减为原来的1%。
- 最后,在第30个epoch后,学习率又一次乘以0.1,最终相对于初始值衰减为原来的0.1%。
这种方式允许根据训练过程中的性能变化或预期的学习曲线,更加精细地控制学习率的下降时机,有助于模型更好地收敛或避免过拟合。下面是使用 MultiStepLR 的简单示例代码:
import torch.optim as optim
from torch.optim.lr_scheduler import MultiStepLR
# 初始化模型和优化器
model = YourModel()
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
# 创建 MultiStepLR 调度器
scheduler = MultiStepLR(optimizer, milestones=[10, 20, 30], gamma=0.1)
# 训练循环
for epoch in range(num_epochs):
# 训练过程...
# 每个epoch结束时调用scheduler的step()方法检查是否需要调整学习率
scheduler.step()
3.ExponentialLR
ExponentialLR 是一种学习率调整策略,它使学习率按照指数函数的方式逐渐衰减。与 StepLR 和 MultiStepLR 在特定的周期突然改变学习率不同,ExponentialLR 在每个训练步骤(或每个epoch,具体取决于调度器的更新频率)后,都按照一个固定的比率逐渐减少学习率。这对于需要平滑降低学习率,以更细致地探索解空间或在训练后期缓慢逼近最优解的场景非常有用。
使用 ExponentialLR 的代码示例如下:
import torch.optim as optim
from torch.optim.lr_scheduler import ExponentialLR
# 初始化模型和优化器
model = YourModel()
optimizer = optim.SGD(model.parameters(), lr=initial_lr, momentum=0.9)
# 创建 ExponentialLR 调度器
# 其中 gamma 参数指定了学习率衰减的速率,例如 gamma=0.9 表示每经过一个调整周期,学习率变为原来的 90%
scheduler = ExponentialLR(optimizer, gamma=0.9)
# 训练循环
for epoch in range(num_epochs):
# 训练过程...
# 每个epoch结束时调用scheduler的step()方法更新学习率
scheduler.step()
其中,initial_lr即为设定的初始学习率,gamma=0.9 表示每次更新后,学习率都会乘以0.9,因此学习率会以指数形式逐渐减小。
4.使用学习率调整器
在PyTorch中使用学习率调整器(Learning Rate Scheduler)是一个提高模型训练效率和性能的有效策略。下面以StepLR为例展示如何使用学习率调整器。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import StepLR
# 假设的模型定义
class SimpleModel(nn.Module):
def __init__(self):
super(SimpleModel, self).__init__()
self.linear = nn.Linear(10, 1)
def forward(self, x):
return self.linear(x)
# 实例化模型和优化器
model = SimpleModel()
optimizer = optim.SGD(model.parameters(), lr=0.1)
# 创建学习率调整器
scheduler = StepLR(optimizer, step_size=5, gamma=0.1) # 每5个epoch学习率减半
# 训练循环
num_epochs = 30
for epoch in range(num_epochs):
# 假设的训练步骤,这里简化处理
for batch_idx, (data, target) in enumerate(train_loader):
optimizer.zero_grad()
output = model(data)
loss = F.mse_loss(output, target)
loss.backward()
optimizer.step()
# 每个epoch结束时更新学习率
scheduler.step()
print(f"Epoch [{epoch+1}/{num_epochs}], LR: {scheduler.get_last_lr()[0]}")
九、模型评估
模型评估是机器学习项目中不可或缺的一环,它旨在量化模型在未知数据上的表现,确保模型具有良好的泛化能力。以下是模型评估的关键步骤。
1.设置模型为评估模式
在PyTorch中,通过调用model.eval()方法,模型会被设置为评估模式。这一步骤至关重要,因为它会影响到某些层的行为,比如关闭Dropout层和Batch Normalization层的训练时特有的特性,确保模型的预测是确定性的,并且不会影响模型的内部状态。
model.eval()
2.使用验证集或测试集进行推理
遍历验证集或测试集,对输入数据进行前向传播,得到模型的预测输出。
model.eval()
with torch.no_grad():
for inputs, labels in dataloader:
outputs = model(inputs)
# 进行后续处理..
3.计算性能指标
(1) 准确率(Accuracy)
准确率(Accuracy)是机器学习中最基本且直观的性能评估指标之一,尤其适用于多分类问题。它定义为模型正确分类的样本数占总样本数的比例。
import torch
# 假设 outputs 和 labels 都是形状为 (batch_size,) 的张量
# 对于多分类问题,outputs 通常包含每个类别的概率,需要获取预测类别
_, predicted = torch.max(outputs.data, 1)
# 将预测和真实标签转换为相同的数据类型(例如,都转为整数)
if isinstance(predicted, torch.Tensor):
predicted = predicted.cpu().numpy()
if isinstance(labels, torch.Tensor):
labels = labels.cpu().numpy()
# 计算并输出准确率
accuracy = (predicted == labels).sum() / len(labels)
print(f'Accuracy: {accuracy}')
(2) 精确度(Precision)
精确度(Precision)是模型预测为正类的样本中,真正为正类的比例。
from sklearn.metrics import precision_score
# 假设 predictions 和 true_labels 是形状为 (n_samples,) 的数组
# predictions 是模型的预测结果,true_labels 是对应的真值标签
# 如果是多分类问题,labels 参数是所有类别的列表
# 二分类问题
precision_binary = precision_score(true_labels, predictions)
# 多分类问题,宏平均
precision_macro = precision_score(true_labels, predictions, average='macro')
# 多分类问题,微平均
precision_micro = precision_score(true_labels, predictions, average='micro')
print(f'Binary Precision: {precision_binary}')
print(f'Macro Average Precision: {precision_macro}')
print(f'Micro Average Precision: {precision_micro}')
(3) 召回率(Recall)
召回率(Recall),也称为灵敏度或真正率,衡量的是模型识别出的所有正类样本中,正确识别的比例。
from sklearn.metrics import recall_score
# 假设 predictions 和 true_labels 是形状为 (n_samples,) 的数组
# predictions 是模型的预测结果,true_labels 是对应的真值标签
# 对于多分类问题,labels 参数是所有类别的列表
# 二分类问题
recall_binary = recall_score(true_labels, predictions)
# 多分类问题,宏平均
recall_macro = recall_score(true_labels, predictions, average='macro')
# 多分类问题,微平均
recall_micro = recall_score(true_labels, predictions, average='micro')
print(f'Binary Recall: {recall_binary}')
print(f'Macro Average Recall: {recall_macro}')
print(f'Micro Average Recall: {recall_micro}')
(4) F1分数(F1 Score)
F1分数是精确度(Precision)和召回率(Recall)的调和平均值,旨在提供一个综合评价指标,特别是对于类别不平衡的数据集。
from sklearn.metrics import f1_score
# 假设 predictions 和 true_labels 是形状为 (n_samples,) 的数组
# predictions 是模型的预测结果,true_labels 是对应的真值标签
# 对于多分类问题,labels 参数是所有类别的列表
# 二分类问题
f1_binary = f1_score(true_labels, predictions)
# 多分类问题,宏平均
f1_macro = f1_score(true_labels, predictions, average='macro')
# 多分类问题,微平均
f1_micro = f1_score(true_labels, predictions, average='micro')
print(f'Binary F1 Score: {f1_binary}')
print(f'Macro Average F1 Score: {f1_macro}')
print(f'Micro Average F1 Score: {f1_micro}')
4.ROC曲线
ROC曲线(Receiver Operating Characteristic Curve)是一种评估二分类模型性能的方法,特别是在正负样本比例不平衡或者对假阳性(False Positives, FP)和假阴性(False Negatives, FN)的代价不等同的场景下特别有用。ROC曲线通过改变决策阈值,展示了模型在不同阈值下的真正例率(True Positive Rate, TPR)与假正例率(False Positive Rate, FPR)之间的关系。
from sklearn.metrics import roc_curve, auc
import matplotlib.pyplot as plt
# 假设 y_true 是二分类的真值标签,y_scores 是模型的得分或概率输出
y_true = [0, 1, 1, 0, 1, 0, 1, 1, 0, 1] # 真实标签
y_scores = [0.1, 0.4, 0.35, 0.8, 0.6, 0.1, 0.9, 0.7, 0.2, 0.5] # 模型得分
# 计算ROC曲线的点
fpr, tpr, thresholds = roc_curve(y_true, y_scores)
# 计算ROC曲线下面积(AUC)
roc_auc = auc(fpr, tpr)
# 绘制ROC曲线
plt.plot(fpr, tpr, label=f'ROC curve (area = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], 'k--') # 绘制随机猜测的ROC曲线
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic')
plt.legend(loc="lower right")
plt.show()