Focal loss代码实现补充:NLLLoss损失函数

本文详细解析了PyTorch中NLLLoss及CrossEntropyLoss的原理与应用,包括log_softmax函数的作用、NLLLoss的具体计算方式,并说明了在实际应用中如何正确选择这些损失函数。

torch.nn.NLLLOSS官方链接:NLLLoss — PyTorch 1.12 documentation

  • 首先对比Log_softmax, Nllloss,CrossEntropyLoss函数的表达式

log_softmax:

Softmax() 函数的值域是 [ 0 , 1 ] 公式:
σ ( z ) j = e z j ∑ k = 1 n e z k \sigma(z)_{j} = \frac{e^{z_{j}}}{\sum_{k=1}^ne^{z_{k}}} σ(z)j=</

<think> 我们已知Focal Loss主要用于解决类别不平衡问题。在PyTorch中,我们可以通过自定义损失函数类来实现Focal Loss。 根据引用[2],Focal Loss的一个实现方式是结合nn.BCEWithLogitsLoss(即带sigmoid的二元交叉熵)和Focal Loss的调节因子(gamma和alpha)。 同时,引用[1]和引用[3]也提供了Focal Loss实现思路。 注意:Focal Loss有两种形式,一种是二分类,另一种是多分类。在图像分类任务中,如果是二分类,我们可以使用二元交叉熵为基础;如果是多分类,通常使用交叉熵(但Focal Loss也可以推广到多分类,例如RetinaNet中使用的多类别Focal Loss)。 由于用户没有明确是二分类还是多分类,这里我们以二分类为例(多分类可以类似推广,但需要调整)。假设我们使用二元分类的Focal Loss,其公式为: FL(p_t) = -alpha * (1 - p_t)^gamma * log(p_t) 其中,p_t是模型预测该类别为1的概率(对于正样本),对于负样本,则为1-p_t。但实际上,我们可以统一写成: FL = -alpha * (1 - p_t)^gamma * log(p_t) 对于正样本 而对于负样本,我们也可以写成同样的形式(但注意p_t是模型预测该样本属于其真实标签的概率,对于负样本,其真实标签为0,所以p_t就是1-sigmoid(logit),即sigmoid(logit)是预测为正的概率,那么负样本的p_t就是1-sigmoid(logit)) 在实现上,我们可以先计算二元交叉熵损失,然后乘以调节因子(1 - p_t)^gamma,同时用alpha调节正负样本的权重。 步骤: 1. 定义FocalLoss类,继承nn.Module。 2. 在构造函数中,设置gamma和alpha(注意alpha可以是一个数,也可以是一个tensor,例如[负样本权重, 正样本权重])。 3. 前向传播中,计算二元交叉熵损失(使用logits,即模型最后层输出,不经过sigmoid),但我们需要计算sigmoid后的概率p。 4. 计算p_t:对于正样本,p_t = p(预测为正的概率);对于负样本,p_t = 1-p。实际上,p_t = p * target + (1-p) * (1-target) ?不,更直接地,我们可以将每个样本的真实类别对应的概率提取出来。但注意,二元交叉熵中,我们通常用目标标签0和1。 我们可以这样:p_t = torch.where(target == 1, p, 1-p) 5. 计算损失:loss = -alpha * (1 - p_t)^gamma * log(p_t) 但是注意,这里log(p_t)就是交叉熵中的对数部分。实际上,整个Focal Loss可以看作是交叉熵损失乘以调制因子(1-p_t)^gamma,然后再乘以alpha(alpha_t,即对每个样本根据其类别赋予权重)。 另一种实现方式(如引用[2])是使用已有的二元交叉熵损失函数(nn.BCEWithLogitsLoss)计算出的基础损失,然后乘以调制因子。但是注意,BCEWithLogitsLoss已经将sigmoid和交叉熵结合,并且返回的是每个样本的损失(均值或求和)。然而,Focal Loss需要先计算每个样本的交叉熵,然后乘以调制因子,所以我们可以自己计算每一步。 考虑到数值稳定性,我们通常使用logits来计算(利用sigmoid的稳定计算)。 这里我们采用直接计算的方式: 对于每个样本i: p = sigmoid(logit_i) # 预测为正类的概率 # 计算交叉熵损失的基础部分:对于正样本,损失是-log(p);对于负样本,损失是-log(1-p) ce_loss = F.binary_cross_entropy_with_logits(logit_i, target_i, reduction='none') # 注意,上面返回的是每个样本的损失值(标量),这个值等于:对于正样本:-log(p),负样本:-log(1-p) 但是,我们还需要计算p_t,即: p_t = p * target_i + (1-p) * (1-target_i) # 这个式子不对,因为当target_i=1时,p_t=p;当target_i=0时,p_t=1-p。所以可以写成: p_t = torch.exp(-ce_loss) # 因为ce_loss = -log(p_t) => p_t = exp(-ce_loss) 然后调制因子:factor = (1 - p_t) ** gamma 然后乘以alpha_t:这里alpha_t需要根据target_i来设定,如果是正样本,则乘以alpha(正样本的权重),负样本乘以(1-alpha)或者另一个权重(比如0.75和0.25,根据引用[2])。通常,我们可以预先设定一个alpha,然后: alpha_t = alpha * target_i + (1-alpha) * (1-target_i) # 但这样写的话,当target_i=1时,alpha_t=alpha;target_i=0时,alpha_t=1-alpha。但注意,我们可能希望正样本用alpha,负样本用另一个权重,比如beta=1-alpha,也可以单独设置。 因此,最终该样本的focal loss为:fl_loss = alpha_t * factor * ce_loss 但是,引用[2]中给出的实现方式如下(注意:它使用了nn.BCEWithLogitsLoss,但设置reduction='none',然后自己加调制因子): bce = loss_fcn(input, target) # 这里loss_fcn是nn.BCEWithLogitsLoss(reduction='none') p_t = torch.exp(-bce) # 计算p_t factor = (1 - p_t) ** gamma loss = factor * bce if alpha is not None: # 这里alpha_t是一个与target形状相同的张量,其中正样本位置为alpha,负样本位置为1-alpha alpha_t = alpha * target + (1 - alpha) * (1 - target) loss = alpha_t * loss return loss.mean() # 或者根据需要的reduction方式 注意:上面的alpha_t的定义与之前描述一致。但注意,alpha可以是0.75,那么正样本权重为0.75,负样本权重为0.25(因为1-0.75=0.25)。 因此,我们可以按照这个思路实现。 具体代码实现如下(以二分类为例): class FocalLoss(nn.Module): def __init__(self, alpha=0.75, gamma=2, reduction='mean'): super(FocalLoss, self).__init__() self.alpha = alpha self.gamma = gamma self.reduction = reduction # 基础损失函数,二元交叉熵(带logits),注意设置reduction='none',这样我们得到每个样本的损失值 self.bce = nn.BCEWithLogitsLoss(reduction='none') def forward(self, inputs, targets): # 计算基础交叉熵损失(每个样本的损失) bce_loss = self.bce(inputs, targets) # 计算p_t p_t = torch.exp(-bce_loss) # 计算调制因子 factor = (1 - p_t) ** self.gamma # 计算Focal Loss(还没有乘以alpha) loss = factor * bce_loss # 如果设置了alpha if self.alpha is not None: # 创建alpha_t张量,形状与targets相同 alpha_t = self.alpha * targets + (1 - self.alpha) * (1 - targets) loss = alpha_t * loss # 根据reduction参数进行汇总 if self.reduction == 'mean': return loss.mean() elif self.reduction == 'sum': return loss.sum() else: return loss 注意:这个实现中,alpha是一个介于0和1之间的数,表示正样本的权重。如果数据集中正样本很少,我们可以设置alpha>0.5,以增加正样本的权重。 但是,在多分类任务中,Focal Loss实现略有不同。如果是多分类(例如有C个类别),我们通常使用softmax和交叉熵。此时,Focal Loss可以写为: FL = -alpha * (1 - p_t)^gamma * log(p_t) 其中,p_t是模型预测正确类别的概率。我们可以类似地计算。 由于用户的问题是关于图像训练,可能是多分类。这里我们假设用户可能需要多分类的Focal Loss。因此,我们再给出一个多分类版本的Focal Loss。 多分类Focal Loss实现(参考RetinaNet的损失函数): class MultiFocalLoss(nn.Module): def __init__(self, alpha=None, gamma=2, reduction='mean'): super(MultiFocalLoss, self).__init__() self.gamma = gamma self.alpha = alpha # 这里alpha可以是一个列表,表示每个类别的权重 self.reduction = reduction def forward(self, inputs, targets): # inputs: 模型的原始输出,没有经过softmax,形状为[N, C] # targets: 每个样本的真实类别,形状为[N],每个元素是类别索引(0到C-1) log_softmax = F.log_softmax(inputs, dim=1) logpt = -log_softmax.gather(1, targets.view(-1,1)).view(-1) # 取出每个样本正确类别的log_prob,并取负号(因为log_softmax是负的,我们取负得到正数) pt = torch.exp(logpt) # 得到每个样本正确类别的概率p_t # 计算调制因子 factor = (1 - pt) ** self.gamma # 如果设置了alpha if self.alpha is not None: # 将alpha转换为与targets相同设备和形状的张量 alpha_t = self.alpha[targets].to(inputs.device) # 假设self.alpha是一个长度为C的列表或张量 logpt = alpha_t * logpt loss = factor * logpt # 注意:这里logpt是交叉熵的一部分(即-log(p_t)),所以factor乘以logpt就是调制后的损失 if self.reduction == 'mean': return loss.mean() elif self.reduction == 'sum': return loss.sum() else: return loss 注意:多分类版本中,我们使用F.log_softmax和gather来获取正确类别的对数概率。然后计算调制因子,并乘以alpha(如果提供)。 但是,上面的多分类版本中,logpt是交叉熵中的对数部分(即负的对数概率),所以整个损失就是factor * logpt(注意logpt是正的,因为取了负号)。而标准交叉熵就是logpt(即-log(p_t)),所以Focal Loss就是在交叉熵前乘了(1-p_t)^gamma和alpha(如果有的话)。 然而,注意:在多分类中,alpha通常是一个列表,表示每个类别的权重(用于解决类别不平衡),所以我们需要根据targets索引得到每个样本的alpha_t。 用户需要根据自己的任务选择二分类还是多分类Focal Loss。 现在,假设用户要在训练代码中加入Focal Loss,步骤如下: 1. 定义Focal Loss类(根据任务选择二分类或多分类)。 2. 在训练代码中,将原来的损失函数(如CrossEntropyLoss)替换为FocalLoss。 3. 注意设置合适的alpha和gamma(通常gamma=2,alpha可以根据类别频率设置,如正样本比例小的类别设置较大的alpha)。 示例(以二分类为例): # 创建模型 model = ... # 定义优化器 optimizer = ... # 实例化Focal Loss criterion = FocalLoss(alpha=0.75, gamma=2) for epoch in range(epochs): for images, labels in train_loader: images, labels = images.to(device), labels.to(device).float() # 二分类时,labels需要是float(因为BCEWithLogitsLoss要求) outputs = model(images) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() 注意:二分类任务中,模型的输出通常是一个通道(即每个样本输出一个值),而标签是0或1。多分类任务中,模型的输出是C个通道,标签是0到C-1的整数。 如果用户的任务是多分类,则需要使用多分类的Focal Loss,并将标签设置为整数(不需要one-hot),然后使用MultiFocalLoss。 另外,引用[3]中提到,在PyTorch中,F.nll_loss实现了对target的one-hot encoding。但在我们的多分类Focal Loss中,我们使用了gather,所以不需要one-hot编码。 最后,关于alpha的设置:我们可以根据数据集中每个类别的频率来设置,比如类别i的权重alpha_i = 1 / frequency_i,然后归一化(或者使用其他平衡策略)。 由于用户没有提供具体的任务,我们给出两种实现,用户根据任务选择。 下面我们提供完整的二分类和多分类Focal Loss实现代码,并说明如何加入训练代码。 </think>在PyTorch训练代码中添加Focal Loss需要以下步骤,我将结合引用内容提供完整解决方案: ### 1. 定义Focal Loss类 ```python import torch import torch.nn as nn import torch.nn.functional as F class FocalLoss(nn.Module): def __init__(self, alpha=0.75, gamma=2.0, reduction='mean'): """ alpha: 正样本权重系数(0-1之间) gamma: 困难样本聚焦参数(γ≥0) reduction: 损失计算方式('mean'或'sum') """ super(FocalLoss, self).__init__() self.alpha = alpha self.gamma = gamma self.reduction = reduction def forward(self, inputs, targets): # 计算二元交叉熵(不自动求平均) bce_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduction='none') # 计算概率值 pt = torch.exp(-bce_loss) # p_t = exp(-BCE) # 计算Focal Loss核心项 focal_term = (1 - pt) ** self.gamma # 加入alpha权重 alpha_factor = self.alpha * targets + (1 - self.alpha) * (1 - targets) loss = alpha_factor * focal_term * bce_loss # 选择损失计算方式 if self.reduction == 'mean': return loss.mean() elif self.reduction == 'sum': return loss.sum() return loss ``` ### 2. 在训练循环中使用Focal Loss ```python # 初始化模型和优化器 model = YourImageModel() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 实例化Focal Loss (参数根据任务调整) criterion = FocalLoss(alpha=0.75, gamma=2.0) # 引用[1][2]中的典型参数 for epoch in range(num_epochs): for images, labels in train_loader: # 前向传播 outputs = model(images) # 计算Focal Loss loss = criterion(outputs, labels.float()) # 注意标签需转为float # 反向传播 optimizer.zero_grad() loss.backward() optimizer.step() # 打印训练信息 if (i+1) % 100 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}') ``` ### 关键参数说明(根据引用[1][2]): 1. **alpha (α)**: - 取值范围:0-1 - 作用:调节正负样本权重 - 设置建议: - α接近1:强调正样本(当正样本稀少时使用) - α接近0:强调负样本 - 典型值:0.75(引用[2]) 2. **gamma (γ)**: - 取值范围:≥0 - 作用:聚焦困难样本 - 设置建议: - γ=0:退化为标准交叉熵 - γ>0:增加困难样本权重 - 典型值:2.0(引用[1][2]) ### 3. 多分类任务适配 对于多分类任务(如语义分割),需调整实现: ```python class MultiClassFocalLoss(nn.Module): def __init__(self, alpha=None, gamma=2.0, reduction='mean'): super().__init__() self.gamma = gamma self.alpha = alpha # 应为各类别权重的Tensor self.reduction = reduction def forward(self, inputs, targets): log_softmax = F.log_softmax(inputs, dim=1) ce_loss = F.nll_loss(log_softmax, targets, reduction='none') # 引用[3] pt = torch.exp(-ce_loss) if self.alpha is not None: alpha = self.alpha[targets] # 按目标索引权重 focal_loss = alpha * (1-pt)**self.gamma * ce_loss else: focal_loss = (1-pt)**self.gamma * ce_loss if self.reduction == 'mean': return focal_loss.mean() elif self.reduction == 'sum': return focal_loss.sum() return focal_loss ``` ### 应用场景建议(引用[2]): - 目标检测(如RetinaNet) - 医学图像分析 - 存在严重类别不平衡的分类任务 - 当正负样本比例超过1:1000时效果显著 ### 注意事项: 1. 标签格式: - 二分类:`labels.float()` (形状[batch_size]) - 多分类:`labels.long()` (形状[batch_size]) 2. 参数调整: ```python # 根据类别频率设置alpha (示例:正样本占比10%) positive_ratio = 0.1 criterion = FocalLoss(alpha=positive_ratio, gamma=1.5) ``` 3. 梯度监控: ```python # 检查梯度 print(f"Max grad: {torch.max(model.fc.weight.grad).item()}") ``` > 实现参考了引用[1][2][3]的核心思想,结合了BCEWithLogitsLoss的数值稳定性(引用[2])和NLLLoss的特性(引用[3]),通过调制因子$(1-p_t)^\gamma$降低易分样本权重[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

矮人三等

秀儿,是你吗秀儿?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值