Spaces:
Sleeping
Sleeping
#!/usr/bin/python3 | |
# -*- coding: utf-8 -*- | |
import math | |
from typing import List, Optional | |
import numpy as np | |
import torch | |
import torch.nn as nn | |
import torch.nn.functional as F | |
from torch.nn.modules.loss import _Loss | |
from torch.autograd import Variable | |
class ClassBalancedLoss(_Loss): | |
""" | |
https://arxiv.org/abs/1901.05555 | |
""" | |
def demo1(): | |
batch_loss: torch.FloatTensor = torch.randn(size=(2, 1), dtype=torch.float32) | |
targets: torch.LongTensor = torch.tensor([1, 2], dtype=torch.long) | |
class_balanced_loss = ClassBalancedLoss( | |
num_classes=3, | |
num_samples_each_class=[300, 433, 50], | |
reduction='mean', | |
) | |
loss = class_balanced_loss.forward(batch_loss=batch_loss, targets=targets) | |
print(loss) | |
return | |
def demo2(): | |
inputs: torch.FloatTensor = torch.randn(size=(2, 3), dtype=torch.float32) | |
targets: torch.LongTensor = torch.tensor([1, 2], dtype=torch.long) | |
focal_loss = FocalLoss( | |
num_classes=3, | |
# reduction='mean', | |
# reduction='sum', | |
reduction='none', | |
) | |
batch_loss = focal_loss.forward(inputs, targets) | |
print(batch_loss) | |
class_balanced_loss = ClassBalancedLoss( | |
num_classes=3, | |
num_samples_each_class=[300, 433, 50], | |
reduction='mean', | |
) | |
loss = class_balanced_loss.forward(batch_loss=batch_loss, targets=targets) | |
print(loss) | |
return | |
def __init__(self, | |
num_classes: int, | |
num_samples_each_class: List[int], | |
beta: float = 0.999, | |
reduction: str = 'mean') -> None: | |
super(ClassBalancedLoss, self).__init__(None, None, reduction) | |
effective_num = 1.0 - np.power(beta, num_samples_each_class) | |
weights = (1.0 - beta) / np.array(effective_num) | |
self.weights = weights / np.sum(weights) * num_classes | |
def forward(self, batch_loss: torch.FloatTensor, targets: torch.LongTensor): | |
""" | |
:param batch_loss: shape=[batch_size, 1] | |
:param targets: shape=[batch_size,] | |
:return: | |
""" | |
weights = list() | |
targets = targets.numpy() | |
for target in targets: | |
weights.append([self.weights[target]]) | |
weights = torch.tensor(weights, dtype=torch.float32) | |
batch_loss = weights * batch_loss | |
if self.reduction == 'mean': | |
loss = batch_loss.mean() | |
elif self.reduction == 'sum': | |
loss = batch_loss.sum() | |
else: | |
loss = batch_loss | |
return loss | |
class EqualizationLoss(_Loss): | |
""" | |
在图像识别中的, sigmoid 的多标签分类, 且 num_classes 类别数之外有一个 background 背景类别. | |
Equalization Loss | |
https://arxiv.org/abs/2003.05176 | |
Equalization Loss v2 | |
https://arxiv.org/abs/2012.08548 | |
""" | |
def demo1(): | |
logits: torch.FloatTensor = torch.randn(size=(3, 3), dtype=torch.float32) | |
targets: torch.LongTensor = torch.tensor([1, 2, 3], dtype=torch.long) | |
equalization_loss = EqualizationLoss( | |
num_samples_each_class=[300, 433, 50], | |
threshold=100, | |
reduction='mean', | |
) | |
loss = equalization_loss.forward(logits=logits, targets=targets) | |
print(loss) | |
return | |
def __init__(self, | |
num_samples_each_class: List[int], | |
threshold: int = 100, | |
reduction: str = 'mean') -> None: | |
super(EqualizationLoss, self).__init__(None, None, reduction) | |
self.num_samples_each_class = np.array(num_samples_each_class, dtype=np.int32) | |
self.threshold = threshold | |
def forward(self, | |
logits: torch.FloatTensor, | |
targets: torch.LongTensor | |
): | |
""" | |
num_classes + 1 对应于背景类别 background. | |
:param logits: shape=[batch_size, num_classes] | |
:param targets: shape=[batch_size] | |
:return: | |
""" | |
batch_size, num_classes = logits.size() | |
one_hot_targets = F.one_hot(targets, num_classes=num_classes + 1) | |
one_hot_targets = one_hot_targets[:, :-1] | |
exclude = self.exclude_func( | |
num_classes=num_classes, | |
targets=targets | |
) | |
is_tail = self.threshold_func( | |
num_classes=num_classes, | |
num_samples_each_class=self.num_samples_each_class, | |
threshold=self.threshold, | |
) | |
weights = 1 - exclude * is_tail * (1 - one_hot_targets) | |
batch_loss = F.binary_cross_entropy_with_logits( | |
logits, | |
one_hot_targets.float(), | |
reduction='none' | |
) | |
batch_loss = weights * batch_loss | |
if self.reduction == 'mean': | |
loss = batch_loss.mean() | |
elif self.reduction == 'sum': | |
loss = batch_loss.sum() | |
else: | |
loss = batch_loss | |
loss = loss / num_classes | |
return loss | |
def exclude_func(num_classes: int, targets: torch.LongTensor): | |
""" | |
最后一个类别是背景 background. | |
:param num_classes: int, | |
:param targets: shape=[batch_size,] | |
:return: weight, shape=[batch_size, num_classes] | |
""" | |
batch_size = targets.shape[0] | |
weight = (targets != num_classes).float() | |
weight = weight.view(batch_size, 1).expand(batch_size, num_classes) | |
return weight | |
def threshold_func(num_classes: int, num_samples_each_class: np.ndarray, threshold: int): | |
""" | |
:param num_classes: int, | |
:param num_samples_each_class: shape=[num_classes] | |
:param threshold: int, | |
:return: weight, shape=[1, num_classes] | |
""" | |
weight = torch.zeros(size=(num_classes,)) | |
weight[num_samples_each_class < threshold] = 1 | |
weight = torch.unsqueeze(weight, dim=0) | |
return weight | |
class FocalLoss(_Loss): | |
""" | |
https://arxiv.org/abs/1708.02002 | |
""" | |
def demo1(self): | |
inputs: torch.FloatTensor = torch.randn(size=(2, 3), dtype=torch.float32) | |
targets: torch.LongTensor = torch.tensor([1, 2], dtype=torch.long) | |
focal_loss = FocalLoss( | |
num_classes=3, | |
reduction='mean', | |
# reduction='sum', | |
# reduction='none', | |
) | |
loss = focal_loss.forward(inputs, targets) | |
print(loss) | |
return | |
def __init__(self, | |
num_classes: int, | |
alpha: List[float] = None, | |
gamma: int = 2, | |
reduction: str = 'mean', | |
inputs_logits: bool = True) -> None: | |
""" | |
:param num_classes: | |
:param alpha: | |
:param gamma: | |
:param reduction: (`none`, `mean`, `sum`) available. | |
:param inputs_logits: if False, the inputs should be probs. | |
""" | |
super(FocalLoss, self).__init__(None, None, reduction) | |
if alpha is None: | |
self.alpha = torch.ones(num_classes, 1) | |
else: | |
self.alpha = torch.tensor(alpha, dtype=torch.float32) | |
self.gamma = gamma | |
self.num_classes = num_classes | |
self.inputs_logits = inputs_logits | |
def forward(self, | |
inputs: torch.FloatTensor, | |
targets: torch.LongTensor): | |
""" | |
:param inputs: logits, shape=[batch_size, num_classes] | |
:param targets: shape=[batch_size,] | |
:return: | |
""" | |
batch_size, num_classes = inputs.shape | |
if self.inputs_logits: | |
probs = F.softmax(inputs, dim=-1) | |
else: | |
probs = inputs | |
# class_mask = inputs.data.new(batch_size, num_classes).fill_(0) | |
class_mask = torch.zeros(size=(batch_size, num_classes), dtype=inputs.dtype, device=inputs.device) | |
# class_mask = Variable(class_mask) | |
ids = targets.view(-1, 1) | |
class_mask.scatter_(1, ids.data, 1.) | |
if inputs.is_cuda and not self.alpha.is_cuda: | |
self.alpha = self.alpha.cuda() | |
alpha = self.alpha[ids.data.view(-1)] | |
probs = (probs * class_mask).sum(1).view(-1, 1) | |
log_p = probs.log() | |
batch_loss = -alpha*(torch.pow((1-probs), self.gamma))*log_p | |
if self.reduction == 'mean': | |
loss = batch_loss.mean() | |
elif self.reduction == 'sum': | |
loss = batch_loss.sum() | |
else: | |
loss = batch_loss | |
return loss | |
class HingeLoss(_Loss): | |
def demo1(): | |
inputs: torch.FloatTensor = torch.randn(size=(2, 3), dtype=torch.float32) | |
targets: torch.LongTensor = torch.tensor([1, 2], dtype=torch.long) | |
hinge_loss = HingeLoss( | |
margin_list=[300, 433, 50], | |
reduction='mean', | |
) | |
loss = hinge_loss.forward(inputs=inputs, targets=targets) | |
print(loss) | |
return | |
def __init__(self, | |
margin_list: List[float], | |
max_margin: float = 0.5, | |
scale: float = 1.0, | |
weight: Optional[torch.Tensor] = None, | |
reduction: str = 'mean') -> None: | |
super(HingeLoss, self).__init__(None, None, reduction) | |
self.max_margin = max_margin | |
self.scale = scale | |
self.weight = weight | |
margin_list = np.array(margin_list) | |
margin_list = margin_list * (max_margin / np.max(margin_list)) | |
self.margin_list = torch.tensor(margin_list, dtype=torch.float32) | |
def forward(self, | |
inputs: torch.FloatTensor, | |
targets: torch.LongTensor | |
): | |
""" | |
:param inputs: logits, shape=[batch_size, num_classes] | |
:param targets: shape=[batch_size,] | |
:return: | |
""" | |
batch_size, num_classes = inputs.shape | |
one_hot_targets = F.one_hot(targets, num_classes=num_classes) | |
margin_list = torch.unsqueeze(self.margin_list, dim=0) | |
batch_margin = torch.sum(margin_list * one_hot_targets, dim=-1) | |
batch_margin = torch.unsqueeze(batch_margin, dim=-1) | |
inputs_margin = inputs - batch_margin | |
# 将类别对应的 logits 值减小一点, 以形成 margin 边界. | |
logits = torch.where(one_hot_targets > 0, inputs_margin, inputs) | |
loss = F.cross_entropy( | |
input=self.scale * logits, | |
target=targets, | |
weight=self.weight, | |
reduction=self.reduction, | |
) | |
return loss | |
class HingeLinear(nn.Module): | |
""" | |
use this instead of `HingeLoss`, then you can combine it with `FocalLoss` or others. | |
""" | |
def __init__(self, | |
margin_list: List[float], | |
max_margin: float = 0.5, | |
scale: float = 1.0, | |
weight: Optional[torch.Tensor] = None | |
) -> None: | |
super(HingeLinear, self).__init__() | |
self.max_margin = max_margin | |
self.scale = scale | |
self.weight = weight | |
margin_list = np.array(margin_list) | |
margin_list = margin_list * (max_margin / np.max(margin_list)) | |
self.margin_list = torch.tensor(margin_list, dtype=torch.float32) | |
def forward(self, | |
inputs: torch.FloatTensor, | |
targets: torch.LongTensor | |
): | |
""" | |
:param inputs: logits, shape=[batch_size, num_classes] | |
:param targets: shape=[batch_size,] | |
:return: | |
""" | |
if self.training and targets is not None: | |
batch_size, num_classes = inputs.shape | |
one_hot_targets = F.one_hot(targets, num_classes=num_classes) | |
margin_list = torch.unsqueeze(self.margin_list, dim=0) | |
batch_margin = torch.sum(margin_list * one_hot_targets, dim=-1) | |
batch_margin = torch.unsqueeze(batch_margin, dim=-1) | |
inputs_margin = inputs - batch_margin | |
# 将类别对应的 logits 值减小一点, 以形成 margin 边界. | |
logits = torch.where(one_hot_targets > 0, inputs_margin, inputs) | |
logits = logits * self.scale | |
else: | |
logits = inputs | |
return logits | |
class LDAMLoss(_Loss): | |
""" | |
https://arxiv.org/abs/1906.07413 | |
""" | |
def demo1(): | |
inputs: torch.FloatTensor = torch.randn(size=(2, 3), dtype=torch.float32) | |
targets: torch.LongTensor = torch.tensor([1, 2], dtype=torch.long) | |
ldam_loss = LDAMLoss( | |
num_samples_each_class=[300, 433, 50], | |
reduction='mean', | |
) | |
loss = ldam_loss.forward(inputs=inputs, targets=targets) | |
print(loss) | |
return | |
def __init__(self, | |
num_samples_each_class: List[int], | |
max_margin: float = 0.5, | |
scale: float = 30.0, | |
weight: Optional[torch.Tensor] = None, | |
reduction: str = 'mean') -> None: | |
super(LDAMLoss, self).__init__(None, None, reduction) | |
margin_list = np.power(num_samples_each_class, -0.25) | |
margin_list = margin_list * (max_margin / np.max(margin_list)) | |
self.num_samples_each_class = num_samples_each_class | |
self.margin_list = torch.tensor(margin_list, dtype=torch.float32) | |
self.scale = scale | |
self.weight = weight | |
def forward(self, | |
inputs: torch.FloatTensor, | |
targets: torch.LongTensor | |
): | |
""" | |
:param inputs: logits, shape=[batch_size, num_classes] | |
:param targets: shape=[batch_size,] | |
:return: | |
""" | |
batch_size, num_classes = inputs.shape | |
one_hot_targets = F.one_hot(targets, num_classes=num_classes) | |
margin_list = torch.unsqueeze(self.margin_list, dim=0) | |
batch_margin = torch.sum(margin_list * one_hot_targets, dim=-1) | |
batch_margin = torch.unsqueeze(batch_margin, dim=-1) | |
inputs_margin = inputs - batch_margin | |
# 将类别对应的 logits 值减小一点, 以形成 margin 边界. | |
logits = torch.where(one_hot_targets > 0, inputs_margin, inputs) | |
loss = F.cross_entropy( | |
input=self.scale * logits, | |
target=targets, | |
weight=self.weight, | |
reduction=self.reduction, | |
) | |
return loss | |
class NegativeEntropy(_Loss): | |
def __init__(self, | |
reduction: str = 'mean', | |
inputs_logits: bool = True) -> None: | |
super(NegativeEntropy, self).__init__(None, None, reduction) | |
self.inputs_logits = inputs_logits | |
def forward(self, | |
inputs: torch.FloatTensor, | |
targets: torch.LongTensor): | |
if self.inputs_logits: | |
probs = F.softmax(inputs, dim=-1) | |
log_probs = torch.nn.functional.log_softmax(probs, dim=-1) | |
else: | |
probs = inputs | |
log_probs = torch.log(probs) | |
weighted_negative_likelihood = - log_probs * probs | |
loss = - weighted_negative_likelihood.sum() | |
return loss | |
class LargeMarginSoftMaxLoss(_Loss): | |
""" | |
Alias: L-Softmax | |
https://arxiv.org/abs/1612.02295 | |
https://github.com/wy1iu/LargeMargin_Softmax_Loss | |
https://github.com/amirhfarzaneh/lsoftmax-pytorch/blob/master/lsoftmax.py | |
参考链接: | |
https://www.jianshu.com/p/06cc3f84aa85 | |
论文认为, softmax 和 cross entropy 的组合, 没有明确鼓励对特征进行判别学习. | |
""" | |
def __init__(self, | |
reduction: str = 'mean') -> None: | |
super(LargeMarginSoftMaxLoss, self).__init__(None, None, reduction) | |
class AngularSoftMaxLoss(_Loss): | |
""" | |
Alias: A-Softmax | |
https://arxiv.org/abs/1704.08063 | |
https://github.com/woshildh/a-softmax_pytorch/blob/master/a_softmax.py | |
参考链接: | |
https://www.jianshu.com/p/06cc3f84aa85 | |
好像作者认为人脸是一个球面, 所以将向量转换到一个球面上是有帮助的. | |
""" | |
def __init__(self, | |
reduction: str = 'mean') -> None: | |
super(AngularSoftMaxLoss, self).__init__(None, None, reduction) | |
class AdditiveMarginSoftMax(_Loss): | |
""" | |
Alias: AM-Softmax | |
https://arxiv.org/abs/1801.05599 | |
Large Margin Cosine Loss | |
https://arxiv.org/abs/1801.09414 | |
参考链接: | |
https://www.jianshu.com/p/06cc3f84aa85 | |
说明: | |
相对于普通的 对 logits 做 softmax, | |
它将真实标签对应的 logit 值减去 m, 来让模型它该值调整得更大一些. | |
另外, 它还将每个 logits 乘以 s, 这可以控制各 logits 之间的相对大小. | |
根 HingeLoss 有点像. | |
""" | |
def __init__(self, | |
reduction: str = 'mean') -> None: | |
super(AdditiveMarginSoftMax, self).__init__(None, None, reduction) | |
class AdditiveAngularMarginSoftMax(_Loss): | |
""" | |
Alias: ArcFace, AAM-Softmax | |
ArcFace: Additive Angular Margin Loss for Deep Face Recognition | |
https://arxiv.org/abs/1801.07698 | |
参考代码: | |
https://github.com/huangkeju/AAMSoftmax-OpenMax/blob/main/AAMSoftmax%2BOvA/metrics.py | |
""" | |
def demo1(): | |
""" | |
角度与数值转换 | |
pi / 180 代表 1 度, | |
pi / 180 = 0.01745 | |
""" | |
# 度数转数值 | |
degree = 10 | |
result = degree * math.pi / 180 | |
print(result) | |
# 数值转数度 | |
radian = 0.2 | |
result = radian / (math.pi / 180) | |
print(result) | |
return | |
def __init__(self, | |
hidden_size: int, | |
num_labels: int, | |
margin: float = 0.2, | |
scale: float = 10.0, | |
): | |
""" | |
:param hidden_size: | |
:param num_labels: | |
:param margin: 建议取值角度为 [10, 30], 对应的数值为 [0.1745, 0.5236] | |
:param scale: | |
""" | |
super(AdditiveAngularMarginSoftMax, self).__init__() | |
self.margin = margin | |
self.scale = scale | |
self.weight = torch.nn.Parameter(torch.FloatTensor(num_labels, hidden_size), requires_grad=True) | |
nn.init.xavier_uniform_(self.weight) | |
self.cos_margin = math.cos(self.margin) | |
self.sin_margin = math.sin(self.margin) | |
# sin(a-b) = sin(a)cos(b) - cos(a)sin(b) | |
# sin(pi - a) = sin(a) | |
self.loss = nn.CrossEntropyLoss() | |
def forward(self, | |
inputs: torch.Tensor, | |
label: torch.LongTensor = None | |
): | |
""" | |
:param inputs: shape=[batch_size, ..., hidden_size] | |
:param label: | |
:return: logits | |
""" | |
x = F.normalize(inputs) | |
weight = F.normalize(self.weight) | |
cosine = F.linear(x, weight) | |
if self.training: | |
# sin^2 + cos^2 = 1 | |
sine = torch.sqrt((1.0 - torch.mul(cosine, cosine)).clamp(0, 1)) | |
# cos(a+b) = cos(a)cos(b) - sin(a)sin(b) | |
cosine_theta_margin = cosine * self.cos_margin - sine * self.sin_margin | |
# when the `cosine > - self.cos_margin` there is enough space to add margin on theta. | |
cosine_theta_margin = torch.where(cosine > - self.cos_margin, cosine_theta_margin, cosine - (self.margin * self.sin_margin)) | |
one_hot = torch.zeros_like(cosine) | |
one_hot.scatter_(1, label.view(-1, 1), 1) | |
# | |
logits = torch.where(one_hot == 1, cosine_theta_margin, cosine) | |
logits = logits * self.scale | |
else: | |
logits = cosine | |
loss = self.loss(logits, label) | |
# prec1 = accuracy(output.detach(), label.detach(), topk=(1,))[0] | |
return loss | |
class AdditiveAngularMarginLinear(nn.Module): | |
""" | |
Alias: ArcFace, AAM-Softmax | |
ArcFace: Additive Angular Margin Loss for Deep Face Recognition | |
https://arxiv.org/abs/1801.07698 | |
参考代码: | |
https://github.com/huangkeju/AAMSoftmax-OpenMax/blob/main/AAMSoftmax%2BOvA/metrics.py | |
""" | |
def demo1(): | |
""" | |
角度与数值转换 | |
pi / 180 代表 1 度, | |
pi / 180 = 0.01745 | |
""" | |
# 度数转数值 | |
degree = 10 | |
result = degree * math.pi / 180 | |
print(result) | |
# 数值转数度 | |
radian = 0.2 | |
result = radian / (math.pi / 180) | |
print(result) | |
return | |
def demo2(): | |
return | |
def __init__(self, | |
hidden_size: int, | |
num_labels: int, | |
margin: float = 0.2, | |
scale: float = 10.0, | |
): | |
""" | |
:param hidden_size: | |
:param num_labels: | |
:param margin: 建议取值角度为 [10, 30], 对应的数值为 [0.1745, 0.5236] | |
:param scale: | |
""" | |
super(AdditiveAngularMarginLinear, self).__init__() | |
self.margin = margin | |
self.scale = scale | |
self.weight = torch.nn.Parameter(torch.FloatTensor(num_labels, hidden_size), requires_grad=True) | |
nn.init.xavier_uniform_(self.weight) | |
self.cos_margin = math.cos(self.margin) | |
self.sin_margin = math.sin(self.margin) | |
# sin(a-b) = sin(a)cos(b) - cos(a)sin(b) | |
# sin(pi - a) = sin(a) | |
def forward(self, | |
inputs: torch.Tensor, | |
targets: torch.LongTensor = None | |
): | |
""" | |
:param inputs: shape=[batch_size, ..., hidden_size] | |
:param targets: | |
:return: logits | |
""" | |
x = F.normalize(inputs) | |
weight = F.normalize(self.weight) | |
cosine = F.linear(x, weight) | |
if self.training and targets is not None: | |
# sin^2 + cos^2 = 1 | |
sine = torch.sqrt((1.0 - torch.mul(cosine, cosine)).clamp(0, 1)) | |
# cos(a+b) = cos(a)cos(b) - sin(a)sin(b) | |
cosine_theta_margin = cosine * self.cos_margin - sine * self.sin_margin | |
# when the `cosine > - self.cos_margin` there is enough space to add margin on theta. | |
cosine_theta_margin = torch.where(cosine > - self.cos_margin, cosine_theta_margin, cosine - (self.margin * self.sin_margin)) | |
one_hot = torch.zeros_like(cosine) | |
one_hot.scatter_(1, targets.view(-1, 1), 1) | |
logits = torch.where(one_hot == 1, cosine_theta_margin, cosine) | |
logits = logits * self.scale | |
else: | |
logits = cosine | |
return logits | |
def demo1(): | |
HingeLoss.demo1() | |
return | |
def demo2(): | |
AdditiveAngularMarginSoftMax.demo1() | |
inputs = torch.ones(size=(2, 5), dtype=torch.float32) | |
label: torch.LongTensor = torch.tensor(data=[0, 1], dtype=torch.long) | |
aam_softmax = AdditiveAngularMarginSoftMax( | |
hidden_size=5, | |
num_labels=2, | |
margin=1, | |
scale=1 | |
) | |
outputs = aam_softmax.forward(inputs, label) | |
print(outputs) | |
return | |
if __name__ == '__main__': | |
# demo1() | |
demo2() | |