前言
今天继续写代码解读,下面就是loss损失函数的解读,让我们一起学习,至于损失函数都哟什么,大家可以去看我翻译的原文,我先把损失函数部分粘贴过来。
损失函数
文字组件预测损失为由两个损失组成,计算公式为:
其中Lreg是平滑的L1 [20]回归损失,Lcls是交叉熵分类损失。 分类损失的计算公式为:
其中Ltr代表TR的损失; Ltcrp仅计算TR内部的像素,而Ltcrn仅计算TR外部的像素。 Ltcrn用于抑制TCR中的背景噪声。 以这种方式,获得的TCR可以有益于后处理步骤。 OHEM [22]用于TR损耗,其中正负之间的比例设置为3:1。 在我们的实验中,权重λ1和λ2分别根据经验设置为1.0和0.5。
由于非TCR区域缺少高度和方向属性,因此我们仅计算TCR区域的回归损失:
其中hki,sinθ和cosθ是真实值,hˆ ki,ˆ sinθ和ˆ cosθ是相应的预测值; Ω表示TCR中的一组正元素; h是真值框中文本组件的高度。 权重log(h + 1)对于大规模文本组件的高度回归很有帮助。 在我们的论文中,将超参数β设置为1.0。
总代码
import torch
import torch.nn as nn
import torch.nn.functional as F
class TextLoss(nn.Module):
def __init__(self):
super().__init__()
@staticmethod
def ohem(predict, target, train_mask, negative_ratio=3.):
pos = (target * train_mask).byte()
neg = ((1 - target) * train_mask).byte()
n_pos = pos.float().sum()
if n_pos.item() > 0:
loss_pos = F.cross_entropy(predict[pos], target[pos], reduction='sum')
loss_neg = F.cross_entropy(predict[neg], target[neg], reduction='none')
n_neg = min(int(neg.float().sum().item()), int(negative_ratio * n_pos.float()))
else:
loss_pos = torch.tensor(0.)
loss_neg = F.cross_entropy(predict[neg], target[neg], reduction='none')
n_neg = 100
loss_neg, _ = torch.topk(loss_neg, n_neg)
return (loss_pos + loss_neg.sum()) / (n_pos + n_neg).float()
@staticmethod
def smooth_l1_loss(inputs, target, sigma=9.0):
try:
diff = torch.abs(inputs - target)
less_one = (diff < 1.0 / sigma).float()
loss = less_one * 0.5 * diff ** 2 * sigma \
+ torch.abs(torch.tensor(1.0) - less_one) * (diff - 0.5 / sigma)
loss = torch.mean(loss) if loss.numel() > 0 else torch.tensor(0.0)
except Exception as e:
print('RPN_REGR_Loss Exception:', e)
loss = torch.tensor(0.0)
return loss
def gcn_loss(self, gcn_data):
# gcn loss
gcn_pred = gcn_data[0]
labels = gcn_data[1].view(-1).long()
loss = F.cross_entropy(gcn_pred, labels) # *torch.tensor(0.0)
return loss
def forward(self, inputs, gcn_data, train_mask, tr_mask, tcl_mask, radii_map, sin_map, cos_map):
"""
calculate textsnake loss
:param inputs: (Variable), network predict, (BS, 8, H, W)
:param gcn_data: (Variable), (gcn_pred ,gtmat_batch)
:param tr_mask: (Variable), TR target, (BS, H, W)
:param tcl_mask: (Variable), TCL target, (BS, H, W)
:param sin_map: (Variable), sin target, (BS, H, W)
:param cos_map: (Variable), cos target, (BS, H, W)
:param radii_map: (Variable), radius target, (BS, H, W)
:param train_mask: (Variable), training mask, (BS, H, W)
:return: loss_tr, loss_tcl, loss_radii, loss_sin, loss_cos
"""
tr_pred = inputs[:, :2].permute(0, 2, 3, 1).contiguous().view(-1, 2) # (BSxHxW, 2)
tcl_pred = inputs[:, 2:4].permute(0, 2, 3, 1).contiguous().view(-1, 2) # (BSxHxW, 2)
sin_pred = inputs[:, 4].contiguous().view(-1) # (BSxHxW,)
cos_pred = inputs[:, 5].contiguous().view(-1) # (BSxHxW,)
# regularize sin and cos: sum to 1
scale = torch.sqrt(1.0 / (sin_pred ** 2 + cos_pred ** 2 + 0.0001))
sin_pred = sin_pred * scale
cos_pred = cos_pred * scale
top_pred = inputs[:, 6].contiguous().view(-1) # (BSxHxW,)
bot_pred = inputs[:, 7].contiguous().view(-1) # (BSxHxW,)
train_mask = train_mask.contiguous().view(-1) # (BSxHxW,)
tr_mask = tr_mask.contiguous().view(-1)
tcl_mask = tcl_mask[:, :, :, 0].contiguous().view(-1)
sin_map = sin_map.contiguous().view(-1)
cos_map = cos_map.contiguous().view(-1)
top_map = radii_map[:, :, :, 0].contiguous().view(-1)
bot_map = radii_map[:, :, :, 1].contiguous().view(-1)
# loss_tr = F.cross_entropy(tr_pred[train_mask], tr_mask[train_mask].long())
loss_tr = self.ohem(tr_pred, tr_mask.long(), train_mask.long())
loss_tcl = torch.tensor(0.)
tr_train_mask = train_mask * tr_mask
tr_neg_mask = 1 - tr_train_mask
if tr_train_mask.sum().item() > 0:
loss_tcl_pos = F.cross_entropy(tcl_pred[tr_train_mask], tcl_mask[tr_train_mask].long())
loss_tcl_neg = F.cross_entropy(tcl_pred[tr_neg_mask], tcl_mask[tr_neg_mask].long())
loss_tcl = loss_tcl_pos #+ loss_tcl_neg
# geometry losses
loss_radii = torch.tensor(0.)
loss_sin = torch.tensor(0.)
loss_cos = torch.tensor(0.)
tcl_train_mask = train_mask * tcl_mask
if tcl_train_mask.sum().item() > 0:
ones = torch.ones_like(top_pred[tcl_mask]).float()
loss_top = F.smooth_l1_loss(top_pred[tcl_mask] / (top_map[tcl_mask]+0.01), ones, reduction='none')
loss_bot = F.smooth_l1_loss(bot_pred[tcl_mask] / (bot_map[tcl_mask]+0.01), ones, reduction='none')
rad_map = top_map[tcl_mask] + bot_map[tcl_mask]
# loss_radii = torch.mean(torch.log10(rad_map+1.0)*(loss_top+loss_bot))
loss_radii = torch.mean(loss_top + loss_bot)
# loss_radii=torch.tensor(0);
loss_sin = self.smooth_l1_loss(sin_pred[tcl_mask], sin_map[tcl_mask])
loss_cos = self.smooth_l1_loss(cos_pred[tcl_mask], cos_map[tcl_mask])
# ## Graph convolution loss
gcn_loss = self.gcn_loss(gcn_data)
# gcn_loss = torch.tensor(0.)
return loss_tr, loss_tcl, loss_sin, loss_cos, loss_radii, gcn_loss
ohem
先看用于TR损耗的ohem。
pos = (target * train_mask).byte()
neg = ((1 - target) * train_mask).byte()
先算出积极数和消极数(就是正样本和负样本)的真值,算法是正样本的比例*总数再求比特数。负样本数则是(1-正样本比例)*总数再求比特数。
n_pos = pos.float().sum()
对正样本求和
if n_pos.item() > 0:
loss_pos = F.cross_entropy(predict[pos], target[pos], reduction='sum')
loss_neg = F.cross_entropy(predict[neg], target[neg], reduction='none')
n_neg = min(int(neg.float().sum().item()), int(negative_ratio * n_pos.float()))
else:
loss_pos = torch.tensor(0.)
loss_neg = F.cross_entropy(predict[neg], target[neg], reduction='none')
n_neg = 100
loss_neg, _ = torch.topk(loss_neg, n_neg)
判断一下正样本个数,如果正样本真值大于0求一下真值和预测值的交叉熵,然后求一个最小值在(负样本真值)和(正样本*negative_ratio),说一下negative_ratio这个参数,他是3,因为通常来说,正负样本比例是1:3是最好的(参看难例挖掘)。
否则的话正损失为0,负损失为交叉熵结果。
最后这个就是取出负样本的前100个最大的。
return (loss_pos + loss_neg.sum()) / (n_pos + n_neg).float()
最后结果:(正样本加负样本前100损失总和)/(样本总和)
接下来就是:Smooth L1 Loss损失很有意思。
smooth_l1_loss
try异常处理部分就不提了,就是做一个尝试,如果可以运行就运行,不行的话就输出异常,不会出现程序错误,这个没啥说的。我们说函数,先把函数的公式写出来
其中x为真值和预测值差值为diff。
diff = torch.abs(inputs - target)
这就是那个差值X。
less_one = (diff < 1.0 / sigma).float()
这是我见过最有意思的函数,它的作用是判断,如果小于1.0 / sigma,less_one就等于1.后面我们会说他怎么用。
loss = less_one * 0.5 * diff ** 2 * sigma \
+ torch.abs(torch.tensor(1.0) - less_one) * (diff - 0.5 / sigma)
可以看到这是一个计算式我们做一个假设,假设diff < 1.0 / sigma,那么这个式子就会变为:less_one * 0.5 * diff ** 2 * sigma因为后面的: torch.abs(torch.tensor(1.0) - less_one)等于0,如果diff >1.0 / sigma式子变为:(diff - 0.5 / sigma)正好完成了判断。这里有一个问题就是为什么要把判断条件除以9再乘以9我也有疑惑。但是结果不变。
loss = torch.mean(loss) if loss.numel() > 0 else torch.tensor(0.0)
这句话就是判断一下损失是否为0。为0就返回0,不为0返回损失。
然后是GCN损失。
gcn_loss
def gcn_loss(self, gcn_data):
# gcn loss
gcn_pred = gcn_data[0]
labels = gcn_data[1].view(-1).long()
loss = F.cross_entropy(gcn_pred, labels) # *torch.tensor(0.0)
return loss
gcn损失不讲了就是交叉熵损失。
最后是我们的forward了这块也是,说不是特别清楚。略讲。
forward
tr_pred = inputs[:, :2].permute(0, 2, 3, 1).contiguous().view(-1, 2) # (BSxHxW, 2)
tcl_pred = inputs[:, 2:4].permute(0, 2, 3, 1).contiguous().view(-1, 2) # (BSxHxW, 2)
sin_pred = inputs[:, 4].contiguous().view(-1) # (BSxHxW,)
cos_pred = inputs[:, 5].contiguous().view(-1) # (BSxHxW,)
这是数据类型转换,基本就是取出需要的数据,就不详细说了。
scale = torch.sqrt(1.0 / (sin_pred ** 2 + cos_pred ** 2 + 0.0001))
sin_pred = sin_pred * scale
cos_pred = cos_pred * scale
这里有意思第一句是cos平方加sin平方。我想了半天他也是1.然后在乘以他,好吧我们略过。
top_pred = inputs[:, 6].contiguous().view(-1) # (BSxHxW,)
bot_pred = inputs[:, 7].contiguous().view(-1) # (BSxHxW,)
train_mask = train_mask.contiguous().view(-1) # (BSxHxW,)
tr_mask = tr_mask.contiguous().view(-1)
tcl_mask = tcl_mask[:, :, :, 0].contiguous().view(-1)
sin_map = sin_map.contiguous().view(-1)
cos_map = cos_map.contiguous().view(-1)
top_map = radii_map[:, :, :, 0].contiguous().view(-1)
bot_map = radii_map[:, :, :, 1].contiguous().view(-1)
这块还是取所需要的数据。
loss_tr = self.ohem(tr_pred, tr_mask.long(), train_mask.long())
这是利用前面我们说的ohem函数求TR损耗。
loss_tcl = torch.tensor(0.)
tr_train_mask = train_mask * tr_mask
tr_neg_mask = 1 - tr_train_mask
和前面的操作一样,求一下正负样本。如果这个数大于0:
if tr_train_mask.sum().item() > 0:
loss_tcl_pos = F.cross_entropy(tcl_pred[tr_train_mask], tcl_mask[tr_train_mask].long())
loss_tcl_neg = F.cross_entropy(tcl_pred[tr_neg_mask], tcl_mask[tr_neg_mask].long())
loss_tcl = loss_tcl_pos #+ loss_tcl_neg
用交叉熵求一下tcl损失。
loss_radii = torch.tensor(0.)
loss_sin = torch.tensor(0.)
loss_cos = torch.tensor(0.)
tcl_train_mask = train_mask * tcl_mask
求一下tcl_train_mask如果这个数大于0,用交叉熵求一下loss_top和 loss_bot,合并为loss_radii,然后求loss_sin和loss_cos(前面总的损失介绍都有介绍)
if tcl_train_mask.sum().item() > 0:
ones = torch.ones_like(top_pred[tcl_mask]).float()
loss_top = F.smooth_l1_loss(top_pred[tcl_mask] / (top_map[tcl_mask]+0.01), ones, reduction='none')
loss_bot = F.smooth_l1_loss(bot_pred[tcl_mask] / (bot_map[tcl_mask]+0.01), ones, reduction='none')
rad_map = top_map[tcl_mask] + bot_map[tcl_mask]
# loss_radii = torch.mean(torch.log10(rad_map+1.0)*(loss_top+loss_bot))
loss_radii = torch.mean(loss_top + loss_bot)
# loss_radii=torch.tensor(0);
loss_sin = self.smooth_l1_loss(sin_pred[tcl_mask], sin_map[tcl_mask])
loss_cos = self.smooth_l1_loss(cos_pred[tcl_mask], cos_map[tcl_mask])
gcn损失
gcn_loss = self.gcn_loss(gcn_data)
最后返回所有损失。
return loss_tr, loss_tcl, loss_sin, loss_cos, loss_radii, gcn_loss
最后
最后说一下,为啥讲的不是每一句都那么详细,两点:一是了解不是特别清楚,原文说的也是,我怕讲错、二是,一半看代码注解就是为了做优化,所以结构更重要,只要你找得到关键的损失函数,就可以改。所以没必要太详细本人能力有限,先写到这。谢谢各位