赞
踩
class FocalLoss(nn.Module): def __init__(self, apply_nonlin=None, alpha=None, gamma=2, balance_index=0, smooth=1e-5, size_average=True,cuda=False): super(FocalLoss, self).__init__() self.apply_nonlin = apply_nonlin self.alpha = alpha self.gamma = gamma self.balance_index = balance_index self.smooth = smooth self.size_average = size_average self.cuda = cuda if self.smooth is not None: if self.smooth < 0 or self.smooth > 1.0: raise ValueError('smooth value should be in [0,1]') def forward(self, pred, target): if self.apply_nonlin is not None: pred = self.apply_nonlin(pred) num_class = pred.shape[1] #判断pred的维度是否大于2 if pred.dim() > 2: # N,C,d1,d2 -> N,C,m (m=d1*d2*...) #将输入的维度修改为N,C,d1,d2 -> N,C,m pred = pred.view(pred.size(0), pred.size(1), -1) #-->在上面的pred函数中我们得到pred的维度是N,C,m, #-->permute函数是百年换tensor的维度 -- N,C,m --> N,m,C #--contiguous函数会拷贝一份变换前的输入 pred = pred.permute(0, 2, 1).contiguous() #-->同样是将输入同一输入维度--N,m,C --> N*m行C列 限定在一个矩阵内 pred = pred.view(-1, pred.size(-1)) # torch.squeeze(input,dim,out) #--> torch.squeeze 同样是压缩数据的维度 target = torch.squeeze(target, 1) #--> 将输出的target维度限制1列 target = target.view(-1, 1) #print(pred.shape, target.shape) alpha = self.alpha if alpha is None: #-->alpha-全1(维度为:输入类别数行,1列) alpha = torch.ones(num_class, 1) # isinstance() 函数,是Python中的一个内置函数,用来判断一个函数是否是一个已知的类型。 #isinstance(a,(str,int,list)) a的类型是元组中的一个,结果返回 True #如果对象的类型与参数二的类型相同则返回 True,否则返回 False。 elif isinstance(alpha, (list, np.ndarray)): # assert 检查程序,不符合条件即终止程序 assert len(alpha) == num_class # 将alpha的维度限制在num_class行1列,具体数值为alpha alpha = torch.FloatTensor(alpha).view(num_class, 1) #alpha.sum()是一个数,因为alpha为n行一列的数,所以alpha.sum是alpha的和 alpha = alpha / alpha.sum() elif isinstance(alpha, float): #alpha是Num_class行1列的全一的矩阵 alpha = torch.ones(num_class, 1) # 一般alpha取0.25,故alpha等于n行1列全是0.75的数 alpha = alpha * (1 - self.alpha) alpha[self.balance_index] = self.alpha else: raise TypeError('Not support alpha type') ''' if cuda: alpha = torch.from_numpy(alpha).type(torch.FloatTensor).cuda() else: alpha = torch.from_numpy(alpha).type(torch.FloatTensor) ''' #if alpha.device != pred.device: alpha = alpha.to(pred.device) #转变成long类型 idx = target.cpu().long() one_hot_key = torch.FloatTensor(target.size(0), num_class).zero_() one_hot_key = one_hot_key.scatter_(1, idx, 1) #if one_hot_key.device != pred.device: one_hot_key = one_hot_key.to(pred.device) if self.smooth: one_hot_key = torch.clamp( one_hot_key, self.smooth / (num_class - 1), 1.0 - self.smooth) pt = (one_hot_key * pred).sum(1) + self.smooth logpt = pt.log() gamma = self.gamma alpha = alpha[idx] alpha = torch.squeeze(alpha) loss = -1 * alpha * torch.pow((1 - pt), gamma) * logpt if self.size_average: loss = loss.mean() else: loss = loss.sum() return loss
版本二
class focal_loss(nn.Module): def __init__(self, alpha=0.25, gamma=2, num_classes=5, size_average=True): """ focal_loss损失函数, -α(1-yi)**γ *ce_loss(xi,yi) 步骤详细的实现了 focal_loss损失函数. :param alpha: 阿尔法α,类别权重. 当α是列表时,为各类别权重,当α为常数时,类别权重为[α, 1-α, 1-α, ....],常用于 目标检测算法中抑制背景类 , retainnet中设置为0.255 :param gamma: 伽马γ,难易样本调节参数. retainnet中设置为2 :param num_classes: 类别数量 :param size_average: 损失计算方式,默认取均值 """ super(focal_loss, self).__init__() self.size_average = size_average if isinstance(alpha, list): assert len(alpha) == num_classes # α可以以list方式输入,size:[num_classes] 用于对不同类别精细地赋予权重 print(" --- Focal_loss alpha = {}, 将对每一类权重进行精细化赋值 --- ".format(alpha)) self.alpha = torch.Tensor(alpha) else: assert alpha < 1 # 如果α为一个常数,则降低第一类的影响,在目标检测中为第一类 print(" --- Focal_loss alpha = {} ,将对背景类进行衰减,请在目标检测任务中使用 --- ".format(alpha)) self.alpha = torch.zeros(num_classes) self.alpha[0] += alpha self.alpha[1:] += (1 - alpha) # α 最终为 [ α, 1-α, 1-α, 1-α, 1-α, ...] size:[num_classes] self.gamma = gamma def forward(self, preds, labels): """ focal_loss损失计算 :param preds: 预测类别. size:[B,N,C] or [B,C] 分别对应与检测与分类任务, B批次, N检测框数, C类别数 :param labels: 实际类别. size:[B,N] or [B] [B*N个标签(假设框中有目标)],[B个标签] :return: """ # 固定类别维度,其余合并(总检测框数或总批次数),preds.size(-1)是最后一个维度 preds = preds.view(-1, preds.size(-1)) self.alpha = self.alpha.to(preds.device) # 使用log_softmax解决溢出问题,方便交叉熵计算而不用考虑值域 preds_logsoft = F.log_softmax(preds, dim=1) # log_softmax是softmax+log运算,那再exp就算回去了变成softmax preds_softmax = torch.exp(preds_logsoft) # 这部分实现nll_loss ( crossentropy = log_softmax + nll) preds_softmax = preds_softmax.gather(1, labels.view(-1, 1)) preds_logsoft = preds_logsoft.gather(1, labels.view(-1, 1)) self.alpha = self.alpha.gather(0, labels.view(-1)) # torch.pow((1-preds_softmax), self.gamma) 为focal loss中 (1-pt)**γ # torch.mul 矩阵对应位置相乘,大小一致 loss = -torch.mul(torch.pow((1 - preds_softmax), self.gamma), preds_logsoft) # torch.t()求转置 loss = torch.mul(self.alpha, loss.t()) # print(loss.size()) [1,5] if self.size_average: loss = loss.mean() else: loss = loss.sum() return loss
从上面的公式中我们可以看出,Focal Loss 只支持 0/1 这样的离散类别 label(二分类&多分类问题,0就是一类,1就是一类)。但对于 smooth (比如标签平滑)的 label(分数:0 ~ 1之间)是无能为力的,因此就引申出了 Quality Focal Loss (QFL):
Q
F
L
(
σ
)
=
−
a
t
∗
∣
y
−
σ
∣
β
∗
[
(
1
−
y
)
l
o
g
(
1
−
σ
)
+
y
l
o
g
(
σ
)
]
QFL(\sigma) = -a_t * |y - \sigma|^\beta * [(1 - y)log(1 - \sigma)+ylog(\sigma)]
QFL(σ)=−at∗∣y−σ∣β∗[(1−y)log(1−σ)+ylog(σ)]
F
L
(
p
t
)
=
−
a
t
(
1
−
p
t
)
γ
×
C
E
(
p
t
)
FL(p_t) = - a_t (1- p_t) ^\gamma \times CE(p_t)
FL(pt)=−at(1−pt)γ×CE(pt)
其中,
y
y
y是smooth(标签平滑技术)后的label(0~1),
σ
\sigma
σ是预测结果。拆分一下:
a
t
=
y
∗
a
+
(
1
−
y
)
∗
(
1
−
a
)
a_t = y *a + (1-y) * (1 - a)
at=y∗a+(1−y)∗(1−a) //平衡正负样本
∣
y
−
σ
∣
β
|y - \sigma|^\beta
∣y−σ∣β //平衡难易样本
C
E
(
y
,
σ
)
=
−
[
(
1
−
y
)
l
o
g
(
1
−
σ
)
+
y
l
o
g
(
σ
)
]
CE(y,\sigma) = -[(1-y)log(1-\sigma) + ylog(\sigma)]
CE(y,σ)=−[(1−y)log(1−σ)+ylog(σ)] //CELoss
相比较Focal Loss损失函数,平衡正负样本由最初的
a
t
=
0.25
a_t=0.25
at=0.25变成了
a
t
=
y
∗
a
+
(
1
−
y
)
∗
(
1
−
a
)
a_t = y *a + (1-y) * (1 - a)
at=y∗a+(1−y)∗(1−a) ,平衡难易样本由
(
1
−
p
t
)
γ
(1-p_t)^\gamma
(1−pt)γ变成了
∣
y
−
σ
∣
β
|y - \sigma|^\beta
∣y−σ∣β,最后都是基于CELoss交叉熵损失函数。比如在YOLOv4中,如果对分类损失直接使用Focal Loss存在一定的问题,在实际的训练中,我使用Focal Loss损失函数,训练产生的总损失明显不收敛且不稳定,因为在v4中使用了标签平滑技术,但是QFocal Loss解决了这个问题,总的来说对于发一些应用型的论文期刊,这个改进还是很值得的。
class QFocalLoss(nn.Module): # Wraps Quality focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5) def __init__(self, loss_fcn, gamma=1.5, alpha=0.25): super().__init__() self.loss_fcn = loss_fcn # 基于 nn.BCEWithLogitsLoss() self.gamma = gamma self.alpha = alpha self.reduction = loss_fcn.reduction self.loss_fcn.reduction = 'none' # 需要将 FL 应用于每个元素 def forward(self, pred, true): loss = self.loss_fcn(pred, true) pred_prob = torch.sigmoid(pred) # prob from logits alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha) modulating_factor = torch.abs(true - pred_prob) ** 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。