pytorch模型训练demo代码
在PyTorch中,模型训练通常涉及几个关键步骤:定义模型、定义损失函数、选择优化器、准备数据加载器、编写训练循环。以下是一个简单的PyTorch模型训练演示代码,该代码实现了一个用于手写数字识别(使用MNIST数据集)的简单神经网络。
首先,确保你已经安装了PyTorch和torchvision(用于加载MNIST数据集)。
import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader # 定义模型 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.fc1 = nn.Linear(28*28, 500) # 输入层到隐藏层 self.relu = nn.ReLU() self.fc2 = nn.Linear(500, 10) # 隐藏层到输出层 def forward(self, x): x = x.view(-1, 28*28) # 展平图像 x = self.relu(self.fc1(x)) x = self.fc2(x) return x # 准备数据 transform = transforms.Compose([ transforms.ToTensor(), # 转换为Tensor transforms.Normalize((0.5,), (0.5,)) # 归一化 ]) train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True) train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True) # 实例化模型、损失函数和优化器 model = Net() criterion = nn.CrossEntropyLoss() # 多分类交叉熵损失 optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) # 训练模型 num_epochs = 5 for epoch in range(num_epochs): for i, (images, labels) in enumerate(train_loader): # 前向传播 outputs = model(images) loss = criterion(outputs, labels) # 反向传播和优化 optimizer.zero_grad() # 清零梯度 loss.backward() # 反向传播 optimizer.step() # 更新参数 if (i+1) % 100 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(train_loader)}], Loss: {loss.item():.4f}') print('Finished Training')
在这个例子中,我们定义了一个简单的全连接神经网络
Net
,它包含两个全连接层和一个ReLU激活函数。我们使用MNIST数据集进行训练,该数据集包含了手写数字的图像。我们使用了交叉熵损失函数和SGD优化器。在训练循环中,我们遍历数据加载器中的每个批次,对每个批次执行前向传播以计算损失,然后执行反向传播以计算梯度,并使用优化器更新模型的参数。每处理100个批次,我们就打印当前的损失值,以便监控训练过程。
loss反向传播发生了什么(★)
在PyTorch中,loss.backward()
是一个非常重要的函数,它用于计算损失函数(loss
)关于所有可训练参数(即需要梯度的参数)的梯度。这个过程是自动微分(Automatic Differentiation)的一部分,它允许PyTorch自动计算复杂的数学表达式(如神经网络中的损失函数)的梯度。下面是loss.backward()
具体做了什么的一个详细解释:
计算梯度:当你调用
loss.backward()
时,PyTorch会首先计算损失函数loss
关于其所有输入参数的梯度。在神经网络中,这些输入参数通常包括模型的权重和偏置(即模型的可训练参数)。梯度累积:对于每个可训练参数,PyTorch会计算其梯度,并将这些梯度累加到该参数的
.grad
属性中。这意味着,如果你多次调用loss.backward()
(在梯度被清零之前),梯度会被累加。这在你想要累积多个小批量(mini-batches)的梯度时很有用,但通常情况下,你需要在每次迭代后清零梯度,以避免梯度累积。不影响原始数据:
loss.backward()
只计算梯度,并不会改变模型参数(权重和偏置)的值。参数的更新通常是通过优化器(如SGD、Adam等)来完成的,这些优化器使用.grad
属性中的梯度来更新参数。图执行:在PyTorch中,计算图(computation graph)用于跟踪所有操作的顺序和依赖关系。当你调用
loss.backward()
时,PyTorch会遍历这个图,从损失函数开始,反向传播梯度直到到达所有可训练的参数。注意:为了正确计算梯度,你需要确保在调用
loss.backward()
之前,损失函数loss
的所有输入都是requires_grad=True
的。这通常意味着这些输入是模型的输出,而模型的参数(如权重和偏置)在初始化时自动设置为requires_grad=True
。梯度清零:在每次迭代或每个小批量之后,你需要使用
optimizer.zero_grad()
来清零所有参数的梯度,以确保下一次迭代或小批量使用的是新的梯度。总结来说,
loss.backward()
是PyTorch中实现反向传播的关键步骤,它计算损失函数关于所有可训练参数的梯度,并将这些梯度累加到参数的.grad
属性中,为后续的参数更新做准备。
可以查看“pytorch模型训练demo代码”,
# 反向传播和优化
optimizer.zero_grad() # 清零梯度
loss.backward() # 反向传播
optimizer.step() # 更新参数我们先把之前累计的梯度清零;然后调用反向传播,这个时候pytorch会遍历“计算图”,从损失函数开始,反向传播梯度直到到达所有可训练的参数;然后我们利用优化器来更新参数。
数学基础-熵
让我们从理解熵这个术语开始。通常,我们用熵来表示无序或不确定性,它是对概率分布为p(X)的随机变量X进行测量的:
负号是用来使总数量为正的。
一个概率分布的熵值越大,表明该分布的不确定性越大。同样,值越小,分布越确定。
BCELoss
Binary Cross Entropy Loss(二分类交叉熵损失函数),是深度学习中处理二分类问题时常用的损失函数之一
一、BCELoss的基本概念
BCELoss用于衡量模型预测结果(通常是一个概率值,取值范围为0到1)与真实标签(取值为0或1)之间的差异。它基于信息熵的概念,通过最小化损失函数来优化模型参数,使模型预测结果更加接近真实标签。
二、BCELoss的计算公式
BCELoss的计算公式为:
其中,N 是样本数量,yi 是第 i 个样本的真实标签,y^i 是第 i 个样本的预测概率。这个公式实际上是对每个样本的损失进行平均(当
reduction='mean'
时),也可以选择不平均(当reduction='none'
时)或求和(当reduction='sum'
时)。
二元交叉熵适合作为损失函数来最小化它的值。对于输出概率p的分类模型,我们使用二元交叉熵损失。元素属于1类(或正类)的概率= p那么,元素属于0类(或负类)的概率= 1 - p
那么,定义输出标签y(可以取0和1)和预测概率p的交叉熵损失(这也叫做对数损失)为:
思考:为什么二元交叉熵适合作为分类问题的损失函数?
因为p->0时,L->0;而p->1时,L->0;所以可以收敛。
为了计算概率p,我们可以使用Sigmoid函数。这里,z是输入特征的函数:
Sigmoid函数的取值范围为[0,1]
,适合计算概率。
torch.nn.BCELoss
这个类接受一些可选参数,如
weight
(用于对损失函数中的每个元素进行加权)、reduction
(指定输出的格式,包括'none'、'mean'、'sum')等。import torch import torch.nn as nn # 创建二分类交叉熵损失函数 loss_fn = nn.BCELoss(reduction='mean') # 默认reduction为'mean' # 模型预测结果(需要经过sigmoid函数处理,确保值在0到1之间) # 这里为了示例直接给出,实际中应该是模型的输出 y_hat = torch.tensor([0.2, 0.8, 0.6, 0.3], requires_grad=True) # 真实标签 y = torch.tensor([0, 1, 1, 0], dtype=torch.float32) # 计算损失 loss = loss_fn(y_hat, y) # 打印损失 print(loss)
注意:在实际应用中,由于BCELoss要求输入的概率值在0到1之间,因此通常会在模型输出后接一个sigmoid函数来确保这一点。
然而,在PyTorch中,还有一个更方便的损失函数
BCEWithLogitsLoss
,它将sigmoid函数和BCELoss结合在了一起,可以直接接受模型的原始输出(即logits)作为输入,并自动应用sigmoid函数后再计算损失。这样做的好处是数值上更加稳定,且计算效率更高。
nn.BCEWithLogitsLoss
nn.BCEWithLogitsLoss
是 PyTorch 中的一个损失函数,特别适用于二元分类问题。该函数结合了 Sigmoid 激活函数和 Binary Cross-Entropy (BCE) 损失函数,旨在提高训练效率和数值稳定性。以下是对nn.BCEWithLogitsLoss
的详细解析:一、定义与功能
nn.BCEWithLogitsLoss
是 PyTorch 中的一个类,它实现了将 Sigmoid 激活函数和二元交叉熵损失函数合并的功能。这个损失函数接受两个输入:模型的原始输出(未经 Sigmoid 激活)和目标(真实)标签,然后自动计算损失值。由于它在内部集成了 Sigmoid 激活函数,因此可以避免在正向和反向传播过程中可能出现的梯度爆炸或梯度消失问题。二、计算过程
nn.BCEWithLogitsLoss
的计算过程大致如下:
Sigmoid 激活:首先,对模型的原始输出应用 Sigmoid 激活函数,将其映射到 (0, 1) 区间内,表示每个样本属于正类的概率。
二元交叉熵损失计算:然后,使用二元交叉熵损失函数计算模型预测概率与真实标签之间的差异。二元交叉熵损失函数的公式为:
[
\text{BCEWithLogitsLoss}(x, y) = -\frac{1}{n} \sum_{i=1}^{n} \left[ y_i \cdot \log(\sigma(x_i)) + (1 - y_i) \cdot \log(1 - \sigma(x_i)) \right]
]其中,x 表示模型的输出结果(是未经 Sigmoid 激活的 logits),y 表示真实标签,σ 表示 Sigmoid 函数(\sigma(x_i) = \frac{1}{1 + e^{-x_i}}),n 表示样本数量。
参数处理:根据
size_average
、reduce
和reduction
参数(在较新版本的 PyTorch 中,通常只使用reduction
参数),对损失值进行平均或求和操作。三、优点与特性
数值稳定性:由于内部集成了 Sigmoid 函数,
nn.BCEWithLogitsLoss
可以避免直接计算 log(1−p) 时的数值稳定性问题。梯度计算:它能够自动计算 Sigmoid 函数的梯度,减轻了开发者的负担,并有助于梯度在反向传播过程中的正确传递。
联合优化:因为 Sigmoid 函数是包含在损失函数内部的,所以可以与其他层一起进行端到端的联合优化,简化了模型的设计和训练过程。
处理样本不平衡:通过
weight
和pos_weight
参数,可以处理样本不平衡的问题,提高模型对少数类样本的学习能力。四、使用示例
以下是一个使用
nn.BCEWithLogitsLoss
的简单示例:import torch import torch.nn as nn # 创建模型输出和目标标签 inputs = torch.randn(10, 1, requires_grad=True) # 假设有10个样本,每个样本的输出是一个实数 targets = torch.randint(2, (10, 1), dtype=torch.float) # 目标标签,0或1 print(inputs.view(1,-1)) print(targets.view(1,-1)) # 定义损失函数 criterion = nn.BCEWithLogitsLoss() # 计算损失 loss = criterion(inputs, targets) # 反向传播 loss.backward() print(f"Loss: {loss.item()}") print(f"Gradients: {inputs.grad.view(1,-1)}")
在这个示例中,我们首先生成了一个包含10个随机实数的张量作为模型的输出,并生成了一个包含0和1的随机整数张量作为真实标签。然后,我们创建了一个
nn.BCEWithLogitsLoss
的实例,并将模型的输出和真实标签传递给该实例以计算损失值。最后,我们调用loss.backward()
方法进行反向传播。五、总结
nn.BCEWithLogitsLoss
是 PyTorch 中一个用于二元分类问题的强大损失函数,它通过结合 Sigmoid 激活函数和二元交叉熵损失函数,提高了训练的效率和数值稳定性。在实际应用中,它可以方便地处理样本不平衡等问题,并与其他层一起进行端到端的联合优化。六、参数理解(☆)
torch.nn.BCEWithLogitsLoss
的pos_weight
参数用于处理数据集中正负样本不平衡的问题。当数据集中的正样本(即目标标签为1的样本)和负样本(目标标签为0的样本)数量差异很大时,模型可能会偏向于多数类(通常是负样本),导致对少数类(正样本)的预测性能不佳。为了缓解这个问题,pos_weight
参数允许用户对正样本的损失进行加权,从而增加模型对正样本的关注度。具体来说,
pos_weight
参数可以是一个标量或一个与类别数(在多标签分类中)相同长度的张量。但在二分类问题中,它通常是一个标量,表示正样本损失的权重系数。例如,如果正样本的数量是负样本数量的三分之一,那么你可以将pos_weight
设置为3,这样正样本的每个损失值都会被乘以3,从而在训练过程中给予正样本更多的重视。需要注意的是,
pos_weight
的应用方式是在计算二元交叉熵损失之前,先对正样本的损失进行加权。这样,即使数据集中正负样本的数量不平衡,模型也能通过调整权重来更好地学习如何区分两个类别。此外,
torch.nn.BCEWithLogitsLoss
还提供了其他参数,如weight
(用于为不同类别的样本设置权重,但在二分类中通常不使用)和reduction
(用于指定损失的计算方式,如'mean'表示计算损失的平均值,'sum'表示计算损失的总和,'none'表示不应用任何约简并返回每个样本的损失值)。然而,在大多数情况下,用户主要关注的是pos_weight
参数,以便处理数据集中的不平衡问题。
nn.BCEWithLogitsLoss的原理
我们自定义一个函数来实现
nn.BCEWithLogitsLoss
的功能,便于我们理解它的原理。但我们的实现并未完全达到 PyTorch 内置函数的效率和数值稳定性。在实际应用中,你应该直接使用 PyTorch的
nn.BCEWithLogitsLoss
类,因为它已经过优化,能够处理各种边界情况和数值稳定性问题。以下是一个简化的
BCEWithLogitsLoss
实现示例:import torch import torch.nn as nn # 假设的模型输出(logits) logits = torch.tensor([[1.0, -1.0, 2.0], [-0.5, 0.5, -1.5]], requires_grad=True) # 真实的标签(注意,这些应该是介于 0 和 1 之间的概率,但在二元分类中,我们通常使用 0 和 1) targets = torch.tensor([[1, 0, 1], [0, 1, 0]], dtype=torch.float32) # 显式地计算 Sigmoid 函数 def sigmoid(x): return 1 / (1 + torch.exp(-x)) probs = sigmoid(logits) epsilon = 1e-9 bce_loss_manual = -torch.mean(targets * torch.log(probs + epsilon) + (1 - targets) * torch.log(1 - probs + epsilon)) # 注意:上面的 1e-9 是为了避免 log(0) 的情况,PyTorch 内部也有类似的处理。 # 使用 PyTorch 内置的 BCEWithLogitsLoss # 计算二元交叉熵损失 # 注意:PyTorch 的 BCE 损失期望输入的概率在 [epsilon, 1-epsilon] 范围内, # 其中 epsilon 是一个很小的数,以避免 log(0)。这里我们直接使用 sigmoid 的输出, # PyTorch 内部会处理这个问题。 bce_with_logits_loss = nn.BCEWithLogitsLoss(reduction='mean')(logits, targets) # 输出结果,以验证我们的手动实现 print("手动计算的 BCE 损失(已应用 Sigmoid):", bce_loss_manual.item()) # 注意:这行代码在上面的代码中未直接定义,但你可以通过取消注释相关行来计算 print("使用 PyTorch 内置的 BCEWithLogitsLoss:", bce_with_logits_loss.item())
CrossEntropyLoss
一、定义与应用场景
- CrossEntropyLoss(交叉熵损失):
- 定义:交叉熵损失函数是信息论中的一个概念,用于度量两个概率分布之间的差异。在机器学习中,它常作为损失函数来衡量模型预测的概率分布与真实标签的概率分布之间的差异。
- 应用场景:适用于多分类问题,也可以用于二分类问题(但此时与BCEloss等价)。在多分类问题中,CrossEntropyLoss通常与softmax函数结合使用,将模型的输出转换为概率分布。
二、计算公式
CrossEntropyLoss(交叉熵损失):
对于多分类问题,其一般形式为:其中,yic 是第 i 个样本对于类别 c 的真实标签(通常采用one-hot编码),p(yic) 是模型预测第 i 个样本属于类别 c 的概率,C 是类别总数,N 是样本总数。
对于二分类问题,CrossEntropyLoss可以简化为与BCEloss相同的公式。
torch.nn.CrossEntropyLoss
定义模型:确保你的模型输出层没有应用softmax或log-softmax。输出应该是未经归一化的logits。
定义损失函数:使用
torch.nn.CrossEntropyLoss
作为你的损失函数。前向传播和损失计算:将模型的输出(logits)和真实标签(通常是整数,表示每个样本的类别索引)传递给损失函数。
import torch import torch.nn as nn import torch.nn.functional as F # 假设你有一个简单的模型 class SimpleModel(nn.Module): def __init__(self, input_size, num_classes): super(SimpleModel, self).__init__() self.fc = nn.Linear(input_size, num_classes) # 假设只有一个全连接层 def forward(self, x): return self.fc(x) # 直接返回logits # 实例化模型 model = SimpleModel(input_size=10, num_classes=3) # 定义损失函数 criterion = nn.CrossEntropyLoss() # 假设你有一些输入数据和真实标签 inputs = torch.randn(1, 10) # 假设批次大小为1,输入特征维度为10 targets = torch.tensor([1]) # 假设真实标签是类别1(注意是整数索引) # 前向传播得到logits logits = model(inputs) # 计算损失 loss = criterion(logits, targets) print(loss)
在这个例子中,
nn.CrossEntropyLoss
接收logits和真实标签作为输入,并自动计算交叉熵损失。你不需要(也不应该)在将logits传递给损失函数之前显式地应用softmax或log-softmax。注意
- 真实标签应该是整数,表示每个样本的类别索引。
- 如果你的模型输出层是
nn.LogSoftmax
,那么你应该使用nn.NLLLoss
(负对数似然损失)而不是nn.CrossEntropyLoss
,因为nn.CrossEntropyLoss
期望的输入是logits而不是log-probabilities。但是,在大多数情况下,直接输出logits并使用nn.CrossEntropyLoss
是更常见的做法。
softmax
Softmax函数,也称为归一化指数函数,是数学和机器学习领域,尤其是概率论和神经网络中广泛使用的一种函数。它的主要功能是将一个含任意实数的K维向量“压缩”到另一个K维实向量中,使得这个新向量的每个元素都在(0,1)之间,并且所有元素的和为1,从而形成一个概率分布。
定义与公式
Softmax函数的公式为:
其中,zi是输入向量z的第i个元素,n是向量的维度(或类别数)。这个公式确保了输出向量的每个元素都是非负的,并且它们的和为1,满足概率分布的要求。
应用领域
Softmax函数在多分类问题中有着广泛的应用,如图像分类、文本分类、语音识别等。在神经网络中,Softmax函数通常用于输出层,将神经网络的原始输出(logits)转换为概率分布,以便于进行类别的预测和损失的计算。
特点与优势
- 归一化:Softmax函数能够将任意实数值的向量转换为概率分布,这是其最重要的特点之一。
- 可解释性:由于输出是概率分布,因此Softmax函数的输出具有很好的可解释性,可以直观地表示每个类别的预测概率。
- 稳定性:在处理大数值时,Softmax函数可能会遇到数值稳定性问题(如数值溢出)。然而,通过一些技巧(如减去输入向量中的最大值)可以有效地缓解这个问题。
- 与交叉熵损失函数的结合:Softmax函数经常与交叉熵损失函数联合使用,以衡量模型预测的概率分布与真实标签之间的差异,并在训练过程中通过反向传播算法来优化模型参数。
示例
假设我们有一个三维向量z=[1,2,3],应用Softmax函数后,可以得到一个三维概率分布p=[softmax(1),softmax(2),softmax(3)]。通过计算,我们可以得到p≈[0.0900,0.2447,0.6652],这个概率分布表示了输入向量z对应于三个类别的预测概率。
总结
Softmax函数是机器学习和神经网络中非常重要的一个函数,它能够将神经网络的原始输出转换为概率分布,从而方便地进行多分类问题的预测和损失的计算。在实际应用中,Softmax函数经常与交叉熵损失函数联合使用,并通过反向传播算法来优化模型参数。
nn.CrossEntropyLoss的原理
在PyTorch中,
nn.CrossEntropyLoss
是一个非常常用的损失函数,用于多分类问题。它内部结合了nn.LogSoftmax()
和nn.NLLLoss()
的功能,即首先应用 softmax 函数将原始输出转换为概率分布,然后计算负对数似然损失(Negative Log Likelihood Loss)。下面,我们将用 Python 和 NumPy 来模拟实现这个损失函数。首先,我们需要了解
nn.CrossEntropyLoss
的工作原理。给定模型的原始输出(logits)和目标标签(通常是整数),这个损失函数会:
- 对 logits 应用 softmax 函数以获取概率分布。
- 计算目标类别的负对数概率。
- 对所有样本的损失进行平均或求和(取决于
reduction
参数)。下面是使用 NumPy 模拟
nn.CrossEntropyLoss
的实现:import numpy as np import torch import torch.nn as nn def softmax(x): """ 计算 softmax 函数 """ e_x = np.exp(x - np.max(x, axis=1, keepdims=True)) return e_x / e_x.sum(axis=1, keepdims=True) def cross_entropy_loss(logits, targets, reduction='mean'): """ 模拟 PyTorch 的 nn.CrossEntropyLoss 参数: - logits: 模型的原始输出,形状为 (batch_size, num_classes) - targets: 目标标签,形状为 (batch_size,) - reduction: 指定损失的计算方式,'mean' 或 'sum' 返回: - 损失值 """ # 确保 logits 和 targets 都是 numpy 数组 logits = np.asarray(logits, dtype=np.float32) print('logits {}'.format(logits)) targets = np.asarray(targets, dtype=np.int64) print('targets {}'.format(targets)) # 计算 softmax probs = softmax(logits) print('softmax {}'.format(probs)) # 获取目标类别的概率 targets_shape = np.arange(targets.shape[0]) print('targets_shape {}'.format(targets_shape)) targets_probs = probs[targets_shape, targets] print('targets_probs {}'.format(targets_probs)) loss = -np.log(targets_probs) print('loss {}'.format(loss)) # 根据 reduction 参数处理损失 if reduction == 'mean': return np.mean(loss) elif reduction == 'sum': return np.sum(loss) else: raise ValueError("reduction 必须是 'mean' 或 'sum'") logits = np.array([[2.0, 1.0, 0.1], [1.0, 0.2, 1.5]]) targets = np.array([0, 2]) celoss_self = cross_entropy_loss(logits, targets, reduction='mean') celoss = nn.CrossEntropyLoss(reduction='mean') celoss_correct = celoss(torch.Tensor(logits), torch.tensor(targets)) print('compare : celoss_self {} celoss_correct {}'.format(celoss_self, celoss_correct))
在这个示例中,我们首先定义了一个
softmax
函数来计算概率分布,然后定义了cross_entropy_loss
函数来模拟nn.CrossEntropyLoss
。这个函数首先使用softmax
将 logits 转换为概率,然后计算目标类别的负对数概率,最后根据reduction
参数返回损失的平均值或总和。注意:这个实现是为了教学和演示目的。在实际应用中,直接使用 PyTorch 或 TensorFlow 等深度学习框架提供的损失函数会更方便、更高效。
BCELoss vs CELoss
主要区别
- 应用场景:BCEloss专门用于二分类问题,而CrossEntropyLoss既可用于多分类问题,也可用于二分类问题(但在二分类问题中,两者本质上是等价的)。
- 计算细节:虽然两者在二分类问题中的计算公式相同,但在多分类问题中,CrossEntropyLoss需要考虑所有类别的预测和真实标签,而BCEloss则只关注两个类别(0和1)。
- 使用方式:在PyTorch等深度学习框架中,BCEloss通常与sigmoid激活函数结合使用(因为sigmoid函数可以将输出转换为概率),而CrossEntropyLoss则通常与softmax函数结合使用(softmax函数将输出转换为概率分布)。然而,需要注意的是,在PyTorch中,使用CrossEntropyLoss时,模型输出不需要先经过softmax函数,因为CrossEntropyLoss内部已经包含了softmax操作(实际上是log-softmax,然后与外部的负号结合形成交叉熵损失)。
综上所述,BCEloss和CrossEntropyLoss在定义、应用场景和计算公式上存在一定差异,但在二分类问题中,两者可以视为等价。在实际应用中,应根据具体问题选择合适的损失函数。
Focal Loss(★)
Focal Loss论文指的是《Focal Loss for Dense Object Detection》,该论文由Tsung-Yi Lin、Priya Goyal、Ross Girshick、Kaiming He和Piotr Dollar等人在ICCV 2017会议上提出。以下是关于该论文的详细概述:
一、论文背景与动机
在目标检测领域,主要分为两大类算法:两阶段检测器(Two-stage detector)和单阶段检测器(One-stage detector)。两阶段检测器(如Faster R-CNN)通过先生成候选区域(Region Proposal),再对这些区域进行分类和回归,可以达到较高的准确率但速度较慢。而单阶段检测器(如YOLO、SSD)则直接对图片中的每个可能位置进行密集采样并预测边界框,速度快但准确率相对较低。
作者认为,单阶段检测器在训练过程中遇到的极端前景背景类别不均衡(extreme foreground-background class imbalance)是导致其精度低于两阶段检测器的主要原因。具体来说,在一张图像中生成的成千上万的候选位置中,只有很少一部分是包含目标的,这导致了正负样本数量极不均衡,且大多数负样本都是容易分类的,使得模型在训练过程中无法有效学习。
二、Focal Loss的提出
为了解决上述问题,作者提出了Focal Loss,这是一种在标准交叉熵损失(Cross Entropy Loss)基础上进行改进的损失函数。Focal Loss通过引入两个参数:αt(平衡因子)和γ(调制因子),来降低易分类样本的权重,使模型更加关注难分类样本。
Focal Loss的公式为:
其中,pt是模型预测为真实类别的概率。
三、Focal Loss的优势
- 解决类别不均衡问题:通过调整αt和γ的值,Focal Loss可以有效地平衡正负样本的权重,并降低易分类样本的权重,使模型更加关注难分类样本。
- 提高模型性能:在实验中,作者使用Focal Loss训练了一个名为RetinaNet的单阶段检测器,该检测器在保持单阶段检测器速度优势的同时,达到了与两阶段检测器相当的精度。
- 增强模型的泛化能力:Focal Loss允许模型在预测时承担一些风险,从而增强了模型的泛化能力。在处理小尺寸对象或癌症检测等任务时,这种特性尤为重要。
四、实验验证与结果
作者在COCO数据集上进行了实验,验证了Focal Loss的有效性。实验结果表明,在相同的网络结构和训练设置下,使用Focal Loss训练的RetinaNet在精度上超过了其他单阶段检测器,并与两阶段检测器相当。同时,作者还探索了不同αt和γ值对模型性能的影响,并发现αt=0.25和γ=2时效果最佳。
五、结论与展望
Focal Loss的提出为单阶段检测器提供了一种有效的解决方案,使其能够在保持速度优势的同时达到与两阶段检测器相当的精度。未来,随着目标检测技术的不断发展,Focal Loss有望在更多领域得到应用和推广。
参考文献
- 《Focal Loss for Dense Object Detection》,Tsung-Yi Lin, Priya Goyal, Ross Girshick, Kaiming He, Piotr Dollar, ICCV 2017。
yolov5实现focal loss
import torch.nn as nn
import torch
class FocalLoss(nn.Module):
# Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
def __init__(self, loss_fcn, gamma=2.0, alpha=0.25):
"""Initializes FocalLoss with specified loss function, gamma, and alpha values; modifies loss reduction to
'none'.
"""
super().__init__()
self.loss_fcn = loss_fcn # must be nn.BCEWithLogitsLoss()
self.gamma = gamma
self.alpha = alpha
self.reduction = loss_fcn.reduction
self.loss_fcn.reduction = "none" # required to apply FL to each element
def forward(self, pred, true):
"""Calculates the focal loss between predicted and true labels using a modified BCEWithLogitsLoss."""
loss = self.loss_fcn(pred, true)
# p_t = torch.exp(-loss)
# loss *= self.alpha * (1.000001 - p_t) ** self.gamma # non-zero power for gradient stability
# TF implementation https://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
pred_prob = torch.sigmoid(pred) # prob from logits
# p_t pred_true 预测值贴近于标签真实值的概率,也就是难易样本
# 当标签true=1,pred_prob->1,那么p_t->1;当标签true=0,pred_prob->0,那么p_t->1
p_t = true * pred_prob + (1 - true) * (1 - pred_prob)
# 正负样本系数 提高正样本对loss的贡献 true=1时正样本 alpha_factor=alpha,true=0时负样本,alpha_factor=1-alpha
alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
# 调制系数 难易分类样本 p_t趋于1时属于易分类样本,对loss贡献小,p_t趋于0时属于难分类样本,对loss贡献大
modulating_factor = (1.0 - p_t) ** self.gamma
loss *= alpha_factor * modulating_factor
if self.reduction == "mean":
return loss.mean()
elif self.reduction == "sum":
return loss.sum()
else: # 'none'
return loss
if __name__ == '__main__':
# 假设的模型输出(logits)
preds = torch.tensor([[3e-2, 32.1, 6e-4]], requires_grad=True)
# 真实的标签
true_value = torch.tensor([[0, 1, 0]], dtype=torch.float32)
floss = FocalLoss(nn.BCEWithLogitsLoss(pos_weight=torch.tensor(1.0, device='cpu')))
loss = floss(preds, true_value)
print(loss)