论文地址:https://arxiv.org/pdf/2212.12741.pdf
代码地址:https://github.com/SanaNazari/LMFLoss
LMFLOSS是一种用于不平衡医学图像分类的混合损失函数。它是由Focal Loss和LDAM Loss的线性组合构成的,旨在更好地处理不平衡数据集。Focal Loss通过强调难以分类的样本来提高模型的性能,而LDAM Loss则考虑了数据集的类别分布来调整权重。
先来简单回顾下,对于类别不均衡问题,以往的方法是如何解决的。大体上主要有两种,即以数据为中心驱动和以算法为中心的解决方案。
数据策略
以数据为中心的类别不均衡解决方法主要有两种:过采样和欠采样。过采样试图为少数类别生成人工数据点,而欠采样旨在消除多数类别的样本。
算法策略
算法层面的策略,特别是在深度学习领域,主要侧重于开发损失函数来应对类不平衡问题,而不是直接操纵数据。一种简单的方式便是为每个类别都设置相应的权重,以便与多数类别相比,少数类别样本的错误分类受到更严重的惩罚。另一种方法是为每个训练样本自适应地设置一个唯一的权重,以便硬样本获得更高的权重。
作者便提出了一种称为 Large Margin aware Focal (LMF) Loss 的新型损失函数,以缓解医学成像中的类不平衡问题。该损失函数动态地同时考虑硬样本和类分布。
说到类别不均衡的损失函数,不得不提的便是 Focal Loss。对于分类问题,大家常用的便是交叉熵损失 BCE Loss,该损失函数对所有类别均一视同仁,即赋予同等的权重学习。而 Focal Loss 主要就是交叉熵损失改进的,通过引入 和 两个调节因子来调整样本数量和样本难易程度,以便模型专注于学习少数类。具体公式如下:
《 Learning imbalanced datasets with label-distribution-aware margin loss 》 这篇文章中提出了另一项减轻类不平衡问题的工作,称为标签分布感知边距(LDAM)损失。作者建议对少数类引入比多数类更强的正则化,以减少它们的泛化误差。如此一来,损失函数保持了模型学习多数类并强调少数类的能力。LDAM 损失侧重于每个类的最小边际和获得每个类和统一标签测试错误,而不是鼓励大多数类训练样本与决策边界的大边距。换句话说,它只会鼓励少数群体获得相对较大的利润。此外,作者提出了用于获得多个类别 1、2、...、k 的类别相关边距的公式: .
这里 j∈1,...,k 表示特定类,表示每个类别的样本数,C为固定的常数。现在,让我们定义出一个样本对 (x,y),x 为样本,y为对应的标签,同时给定一个模型 f。考虑下面这个函数映射:;我们令 ,这里对于每一个类别j∈1,...,k 都有 。因此,LDAM 损失便可以定义为:
Focal Loss 创建了一种机制,可以更加强调模型难以分类的样本;通常,来自少数群体的样本将属于这一类。相比之下,LDAM Loss 通过考虑数据集的类别分布来判断权重。我们假设与单独使用每个功能相比,同时利用这两个功能可以产生有效的结果。因此,作者提出的 Large Margin aware Focal (LMF) 损失是 Focal 损失和由两个超参数加权的 LDAM 的线性组合,公式如下:
这里,α 和 β 是常数,被认为是可以调整的超参数。 因此,本文提出的损失函数在单个框架中联合优化了两个独立的损失函数。通过反复试验,作者发现将相同的权重分配给两个组件会产生良好的结果。
# -*- coding: utf-8 -*-
"""
Created on Wed May 24 17:03:06 2023
@author: Sana
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
from ..builder import LOSSES
class FocalLoss(nn.Module):
def __init__(self, alpha, gamma=2):
super().__init__()
self.alpha = alpha
self.gamma = gamma
def forward(self, output, target):
num_classes = output.size(1)
assert len(self.alpha) == num_classes, \
'Length of weight tensor must match the number of classes'
logp = F.cross_entropy(output, target, self.alpha)
p = torch.exp(-logp)
focal_loss = (1 - p) ** self.gamma * logp
return torch.mean(focal_loss)
class LDAMLoss(nn.Module):
def __init__(self, cls_num_list, max_m=0.5, weight=None, s=30):
"""
max_m: The appropriate value for max_m depends on the specific dataset and the severity of the class imbalance.
You can start with a small value and gradually increase it to observe the impact on the model's performance.
If the model struggles with class separation or experiences underfitting, increasing max_m might help. However,
be cautious not to set it too high, as it can cause overfitting or make the model too conservative.
s: The choice of s depends on the desired scale of the logits and the specific requirements of your problem.
It can be used to adjust the balance between the margin and the original logits. A larger s value amplifies
the impact of the logits and can be useful when dealing with highly imbalanced datasets.
You can experiment with different values of s to find the one that works best for your dataset and model.
"""
super(LDAMLoss, self).__init__()
m_list = 1.0 / np.sqrt(np.sqrt(cls_num_list))
m_list = m_list * (max_m / np.max(m_list))
m_list = torch.cuda.FloatTensor(m_list)
self.m_list = m_list
assert s > 0
self.s = s
self.weight = weight
def forward(self, x, target):
index = torch.zeros_like(x, dtype=torch.uint8)
index.scatter_(1, target.data.view(-1, 1), 1)
index_float = index.type(torch.cuda.FloatTensor)
batch_m = torch.matmul(self.m_list[None, :], index_float.transpose(0, 1))
batch_m = batch_m.view((-1, 1))
x_m = x - batch_m
output = torch.where(index, x_m, x)
return F.cross_entropy(self.s * output, target, weight=self.weight)
@LOSSES.register_module()
class LMFLoss(nn.Module):
def __init__(self, cls_num_list, weight, alpha=1, beta=1, gamma=2, max_m=0.5, s=30):
super().__init__()
self.focal_loss = FocalLoss(weight, gamma)
self.ldam_loss = LDAMLoss(cls_num_list, max_m, weight, s)
self.alpha = alpha
self.beta = beta
def forward(self, output, target):
focal_loss_output = self.focal_loss(output, target)
ldam_loss_output = self.ldam_loss(output, target)
total_loss = self.alpha * focal_loss_output + self.beta * ldam_loss_output
return total_loss
参考:Focal Loss 后继之秀 | LMFLOSS:专治解决不平衡医学图像分类的新型混合损失函数