多标签损失之Hamming Loss(PyTorch和sklearn)、Focal Loss、交叉熵和ASL损失
多标签损失
- 多标签评价指标之Hamming Loss
- 多标签评价指标之Focal Loss
- 多标签分类的交叉熵
- Asymmetric Loss (ASL) 损失
各个损失函数的计算公式,网上有很多文章了,此处就不一一介绍了。
多标签评价指标之Hamming Loss
PyTorch实现的Hamming Loss和sklearn实现的Hamming Loss代码实现对比
import torch
from sklearn.metrics import hamming_loss
def multi_label_classification_hamming_loss(preds, targets):
"""
计算多标签分类Hamming Loss的函数。
:param preds: 预测的概率值,大小为 [batch_size, num_classes]
:param targets: 目标标签值,大小为 [batch_size, num_classes]
:return: 多标签分类Hamming Loss的值,大小为 [1]
"""
# 将概率值转换为二进制标签(0或1)
binary_preds = torch.round(torch.sigmoid(preds))
# 计算Hamming Loss
hamming_loss = 1 - (binary_preds == targets).float().mean()
return hamming_loss
# 定义预测值和目标标签值
preds = torch.tensor([[0.1, 0.9, 0.3],
[0.8, 0.2, 0.6],
[0.4, 0.5, 0.7]])
targets = torch.tensor([[0, 1, 0],
[1, 0, 1],
[0, 1, 1]])
# 计算多标签分类Hamming Loss
loss = multi_label_classification_hamming_loss(preds, targets)
# 对比sklearn中的Hamming Loss计算结果
sklearn_loss = hamming_loss(targets.numpy(), torch.round(torch.sigmoid(preds)).numpy(), sample_weight=None)
print("PyTorch实现的Hamming Loss:", loss.item())
print("sklearn实现的Hamming Loss:", sklearn_loss)
输出结果:
PyTorch实现的Hamming Loss: 0.4444444179534912
sklearn实现的Hamming Loss: 0.4444444444444444
使用PyTorch中的torch.sigmoid将预测概率值转换为二进制标签,然后通过比较预测标签与目标标签的不一致情况来计算Hamming Loss。最后,输出PyTorch实现的Hamming Loss和sklearn实现的Hamming Loss两个指标的结果。
多标签评价指标之Focal Loss
定义了一个FocalLoss的类,其中gamma是调节因子,alpha是类别权重。在前向传播时,我们先计算出二元交叉熵损失,并根据该损失计算出每个样本的焦点因子(pt)。然后,我们将pt和交叉熵损失的权重调整后,计算最终的Focal Loss。
以下代码实现Focal Loss的多标签分类
import torch
import torch.nn.functional as F
class FocalLoss(torch.nn.Module):
def __init__(self, gamma=2, alpha=None):
super(FocalLoss, self).__init__()
self.gamma = gamma
self.alpha = alpha
def forward(self, input, target):
ce_loss = F.binary_cross_entropy_with_logits(input, target, reduction='none')
pt = torch.exp(-ce_loss)
focal_loss = ((1 - pt) ** self.gamma * ce_loss).mean()
if self.alpha is not None:
alpha_t = self.alpha[target]
focal_loss = alpha_t * focal_loss
return focal_loss
# 定义预测值和目标标签值
preds = torch.tensor([[0.1, 0.9, 0.3],
[0.8, 0.2, 0.6],
[0.4, 0.5, 0.7]])
targets = torch.tensor([[0, 1, 0],
[1, 0, 1],
[0, 1, 1]], dtype=torch.float32)
focal_loss = FocalLoss(gamma=2, alpha=None)
loss = focal_loss(preds, targets)
print(loss)
输出结果:
tensor(0.1430)
多标签分类的交叉熵
为了解决这样的数据不平衡主要有两种方法,一种是数据层面上(数据增强,样本的过采样欠采样等)。另一种是从算法层名上对loss操作可以选择加权loss,focalLoss()
等,这里面引入到苏剑林大神的文章——将“softmax+交叉熵
”推广到多标签分类问题——可以缓解这个数据不平衡问题。
数学原理见详解:将“softmax+交叉熵”推广到多标签分类问题
以下代码实现多标签分类的交叉熵
import torch
import torch.nn as nn
def multilabel_categorical_crossentropy(y_true, y_pred):
"""多标签分类的交叉熵
说明:y_true和y_pred的shape一致,y_true的元素非0即1,
1表示对应的类为目标类,0表示对应的类为非目标类。
警告:请保证y_pred的值域是全体实数,换言之一般情况下y_pred
不用加激活函数,尤其是不能加sigmoid或者softmax!预测
阶段则输出y_pred大于0的类。如有疑问,请仔细阅读并理解
本文。
"""
y_pred = (1 - 2 * y_true) * y_pred
y_pred_neg = y_pred - y_true * 1e12
y_pred_pos = y_pred - (1 - y_true) * 1e12
zeros = torch.zeros_like(y_pred[..., :1])
y_pred_neg = torch.cat([y_pred_neg, zeros], axis=-1)
y_pred_pos = torch.cat([y_pred_pos, zeros], axis=-1)
neg_loss = torch.logsumexp(y_pred_neg, dim=-1)
pos_loss = torch.logsumexp(y_pred_pos, dim=-1)
return neg_loss + pos_loss
# 定义y_true和y_pred
y_pred = torch.tensor([[0.9, 0.1, 0.8, 0.3], [0.3, 0.2, 0.5, 0.7], [0.7, 0.6, 0.1, 0.3]])
y_true = torch.tensor([[1, 0, 1, 0], [0, 0, 1, 1], [1, 0, 0, 0]])
# 计算多标签分类的交叉熵
loss = multilabel_categorical_crossentropy(y_true, y_pred)
print(loss)
输出结果:
tensor([1.4417, 1.6109, 1.5661])
Asymmetric Loss (ASL) 损失
定义了一个 AsymmetricLoss 类,它包含了 Asymmetric Loss 损失函数的实现。在 init 方法中,我们定义了一些超参数,包括 gamma_pos、gamma_neg 和 eps,以及指定了损失函数的归一化方式 reduction。在 forward 方法中,我们首先根据目标值 targets 来计算正类和负类的权重 pos_weight 和 neg_weight,然后根据公式计算损失值 loss。最后,我们根据 reduction 参数来决定损失值的归一化方式。
PyTorch 实现 Asymmetric Loss
损失函数的多标签分类代码:
import torch
import torch.nn as nn
class AsymmetricLoss(nn.Module):
def __init__(self, gamma_pos=0.5, gamma_neg=3.0, eps=0.1, reduction='mean'):
super(AsymmetricLoss, self).__init__()
self.gamma_pos = gamma_pos
self.gamma_neg = gamma_neg
self.eps = eps
self.reduction = reduction
def forward(self, inputs, targets):
pos_weight = targets * (1 - self.gamma_pos) + self.gamma_pos
neg_weight = (1 - targets) * (1 - self.gamma_neg) + self.gamma_neg
loss = -pos_weight * targets * torch.log(inputs + self.eps) - neg_weight * (1 - targets) * torch.log(
1 - inputs + self.eps)
if self.reduction == 'mean':
return torch.mean(loss)
elif self.reduction == 'sum':
return torch.sum(loss)
else:
return loss
# Example usage
num_classes = 4
batch_size = 4
# Generate random inputs and targets
predicted = torch.tensor([[0.9, 0.1, 0.8, 0.3], [0.3, 0.2, 0.5, 0.7], [0.7, 0.6, 0.1, 0.3]], requires_grad=True)
target = torch.tensor([[1, 0, 1, 0], [0, 0, 1, 1], [1, 0, 0, 0]], dtype=torch.float32)
# Define the model and loss function
model = nn.Linear(num_classes, num_classes) # 两个线性层和ReLU和Sigmoid激活函数的简单多标签分类模型
loss_fn = AsymmetricLoss()
# Compute the loss
outputs = model(predicted)
loss = loss_fn(torch.sigmoid(outputs), target)
print(loss)
输出结果:
tensor(0.4989, grad_fn=<MeanBackward0>)