CornerNet代码解析——损失函数
CornerNet代码解析——损失函数
⽂章⽬录
前⾔
今天要解析的是CornerNet的Loss层源码,论⽂中Loss的解析在这:
总体损失
总体的损失函数如下图所⽰,三个输出分别对应三部分损失,每部分损失有着对应的权重。接下来分别讲述每⼀块的损失。
源码中将Loss写成⼀个类:class AELoss,在CornerNet\models\py_utils\kp.py中.
class AELoss(nn.Module):
def__init__(lf, pull_weight=1, push_weight=1, regr_weight=1, focal_loss=_neg_loss):
super(AELoss, lf).__init__()
# pull_weight = α
lf.pull_weight = pull_weight
# push_weight = β
lf.push_weight = push_weight
# regr_weight = γ
<_weight = regr_weight
# 这其实就是heatmap的loss
lf.focal_loss = focal_loss
# 这其实就是embedding的loss
lf.ae_loss = _ae_loss
# 这其实就是offt的loss
<_loss = _regr_loss
def forward(lf, outs, targets):
stride =6
# ::跳着选
'''
⾸先明确两个输⼊:outs和targets
outs:这是⽹络的预测结果,outs是⼀个列表,列表维度为12,outs[0::stride]这些是表⽰列表的
切⽚操作,意思是隔stride(6)个跳着选。举个例⼦outs = [1,2,3,4,5,6,7,8,9,10,11,12],
outs[0::6]=[1, 7],其实这12个事6个两两成对,也就是左上⾓的heatmap有两个,右下⾓的heatmap有两个
左上⾓的embedding有两个,右下⾓的embedding有两个,左上⾓的offt有两个,右下⾓的offt有两个,
共12个,为什么要两份?应该跟上⾯的nstack有关,上述的nstack=2,所以循环出来outs不是6,⽽是12,
映射到论⽂就是跟这句话:we also add intermediate supervision in training。这是中继监督,具体是啥
我也还在看。也就是说下⾯的6个都是列表,每个列表⾥⾯都含有两个tensor,具体维度如下:
'''
# 两个都是[batch_size, 类别数, 128, 128]
tl_heats = outs[0::stride]
# 两个都是[batch_size, 类别数, 128, 128]
br_heats = outs[1::stride]
# 两个都是[batch_size, 128, 1]
tl_tags = outs[2::stride]
# 两个都是[batch_size, 128, 1]
br_tags = outs[3::stride]
# 两个都是[batch_size, 128, 2]
tl_regrs = outs[4::stride]
# 两个都是[batch_size, 128, 2]
br_regrs = outs[5::stride]
'''
targets是gt,标准答案,也是个列表,但就只有下⾯5个,没有两份
具体维度如下
'''
# [batch_size, 类别数, 128, 128]
gt_tl_heat = targets[0]
# [batch_size, 类别数, 128, 128]
gt_br_heat = targets[1]
# [3, 128]
gt_mask = targets[2]
# [3, 128, 2]
gt_tl_regr = targets[3]
# [3, 128, 2]
gt_br_regr = targets[4]
上述就是传⼊的预测值和真实值,Loss也就是计算预测的和真实之间的误差,当Loss值越⼩,那么说明⽹络预测的结果越好。接下去有了预测和真实值,具体分析三个部分的Loss。
1、Heatmap的损失
,接下来是源码理解:
这部分代码在CornerNet\models\py_utils\kp.py中
# focal loss
focal_loss =0
# 到这⾥将heatmap经过sigmoid,将值映射到0-1之间,变成keypoint的响应值,还是列表,
# 维度还是[batch_size, 类别数, 128, 128]
tl_heats =[_sigmoid(t)for t in tl_heats]
br_heats =[_sigmoid(b)for b in br_heats]
# 在CornerNet\models\py_utils\kp_utils.py中详细讲述了focal_loss,这个focal loss就是_neg_loss,形参有体现 focal_loss += lf.focal_loss(tl_heats, gt_tl_heat)
focal_loss += lf.focal_loss(br_heats, gt_br_heat)
接着去到CornerNet\models\py_utils\kp_utils.py中详细讲述focal_loss:
'''
⾸先清楚函数的输⼊:
preds是列表:(2,),表⽰⼀个列表中含两个tensor,每个tensor的维度是(batch_size, 类别数, 128, 128) gt是tensor:(batch_size, 类别数, 128, 128)
'''
def_neg_loss(preds, gt):
# pos_inds是0、1tensor,维度[3,7,128,128]。
# eq函数是遍历gt这个tensor每个element,和1⽐较,如果等于1,则返回1,否则返回0
pos_inds = gt.eq(1)
# otherwi则是表明ycij第c个通道的(i,j)坐标上值不为1
# 遍历gt这个tensor每个element,和1⽐较,如果⼩于1,则返回1,否则返回0
neg_inds = gt.lt(1)
# 总结下上⾯两个变量:上⾯这两个0-1位置互补
# 回头看这两个变量,再结合公式1,公式1后⾯有两个判断条件:if ycij=1 and otherwi
# 这⾥就是那两个判断条件,ycij=1表⽰第c个通道的(i,j)坐标上值为1,也即是gt中这个位置有⽬标# 也就是pos_inds是ycij=1,neg_inds是otherwi
# torch.pow是次幂函数,其中gt[neg_inds]表⽰取出neg_inds中值为1的gt的值
# 所以gt[neg_inds]就变成⼀个向量了,那么维度就等于neg_inds中有多少为1的
# 可以neg_inds.sum()看看,1 - gt[neg_inds]就是单纯的⽤1减去每个element,
# 然后每个element开4次⽅,就成了neg_weights,这个neg_weights是⼀维向量
# 把gt中每个⼩于1的数字取出来,然后⽤1减去,在开⽅,那不是更⼩了,
# 就是原来就很⼩,现在⼜降权。
# gt[neg_inds]就是公式(1)中的Ycij
# neg_weights就是公式(1)中的(1-ycij)^β,β就是4
neg_weights = torch.pow(1- gt[neg_inds],4)
loss =0
# 循环2次,因为preds是⼀个列表,有2部分,每部分放着⼀个tensor,每个tensor的
# 维度为[batch_size,类别数,128,128],也就是pred维度为[batch_size,类别数,128,128]
for pred in preds:
# ⾸先记住pos_inds中的1就是gt中有⽬标的地⽅,neg_inds中的1是gt中没有⽬标的地⽅
# 将gt认为有⽬标的地⽅,pred也按这个地⽅取出数值,变成向量,pos_inds有多少个1,
# pos_pred就多少维(⼀⾏向量)
pos_pred = pred[pos_inds]
# 将gt认为没有⽬标的地⽅,pred也按这个地⽅取出数值,变成向量,neg_inds有多少个1,
# neg_pred就多少维(⼀⾏向量)
neg_pred = pred[neg_inds]
# 以上出现的pos_xxx, neg_xxx,命名的意思就是正样本positive和负样本negative
# 这⾥对应的是论⽂中的公式(1),也就是heatmap的loss
# 可以先根据公式把相应的变量确认下:pos_pred就是公式中的Pcij。
# neg_pred就是公式中的要经过⼆维⾼斯的Pcij,neg_weights就是(1-ycij)^β
pos_loss = torch.log(pos_pred)* torch.pow(1- pos_pred,2)
neg_loss = torch.log(1- neg_pred)* torch.pow(neg_pred,2)* neg_weights
# gt的那个tensor中,值为1的个数,num_pos对应公式(1)中的N
num_pos = pos_inds.float().sum()
# 累加
pos_loss = pos_loss.sum()
neg_loss = neg_loss.sum()
# pos_pred是⼀维的。统计pos_pred中的元素个数,单纯的数个数⽽已,
# 就算pos_pred中值为0的,也算⼀个
if lement()==0:
loss = loss - neg_loss
el:
# ⽤减号体现公式(1)中的-1
loss = loss -(pos_loss + neg_loss)/ num_pos
# 返回最终的heatmap的loss
return loss
2、Embedding的损失
,接下来是源码理解:
接着回到CornerNet\models\py_utils\kp.py,看怎么调⽤embedding的loss:
# tag loss
# 初始化为0
pull_loss =0
push_loss =0
# tl_tags、br_tags是列表,⾥⾯有两个tensor,每个tensor的维度为[batch_size, 128, 1]
# 论⽂中说到的embedding是⼀维向量。也就是说,维度表⽰:⼀个batch_size⼀张图,⽤128*1的矩阵表⽰??
# 那么这个for循环,循环2次,每次进去的是[batch_size, 128, 1]的tl_tag, br_tag
for tl_tag, br_tag in zip(tl_tags, br_tags):
pull, push = lf.ae_loss(tl_tag, br_tag, gt_mask)
pull_loss += pull
push_loss += push
# 算出来的loss乘以相应的权重
pull_loss = lf.pull_weight * pull_loss
push_loss = lf.push_weight * push_loss
接着去到CornerNet\models\py_utils\kp_utils.py中详细讲述ae_loss: