CornerNet代码解析——损失函数
CornerNet代碼解析——損失函數(shù)
文章目錄
- CornerNet代碼解析——損失函數(shù)
- 前言
- 總體損失
- 1、Heatmap的損失
- 2、Embedding的損失
- 3、Offset的損失
前言
今天要解析的是CornerNet的Loss層源碼,論文中Loss的解析在這:CornerNet的損失函數(shù)原理
總體損失
總體的損失函數(shù)如下圖所示,三個(gè)輸出分別對應(yīng)三部分損失,每部分損失有著對應(yīng)的權(quán)重。接下來分別講述每一塊的損失。
源碼中將Loss寫成一個(gè)類:class AELoss,在CornerNet\models\py_utils\kp.py中.
class AELoss(nn.Module):def __init__(self, pull_weight=1, push_weight=1, regr_weight=1, focal_loss=_neg_loss):super(AELoss, self).__init__()# pull_weight = αself.pull_weight = pull_weight# push_weight = βself.push_weight = push_weight# regr_weight = γself.regr_weight = regr_weight# 這其實(shí)就是heatmap的lossself.focal_loss = focal_loss# 這其實(shí)就是embedding的lossself.ae_loss = _ae_loss# 這其實(shí)就是offset的lossself.regr_loss = _regr_lossdef forward(self, outs, targets):stride = 6# ::跳著選'''首先明確兩個(gè)輸入:outs和targetsouts:這是網(wǎng)絡(luò)的預(yù)測結(jié)果,outs是一個(gè)列表,列表維度為12,outs[0::stride]這些是表示列表的切片操作,意思是隔stride(6)個(gè)跳著選。舉個(gè)例子outs = [1,2,3,4,5,6,7,8,9,10,11,12],outs[0::6]=[1, 7],其實(shí)這12個(gè)事6個(gè)兩兩成對,也就是左上角的heatmap有兩個(gè),右下角的heatmap有兩個(gè)左上角的embedding有兩個(gè),右下角的embedding有兩個(gè),左上角的offset有兩個(gè),右下角的offset有兩個(gè),共12個(gè),為什么要兩份?應(yīng)該跟上面的nstack有關(guān),上述的nstack=2,所以循環(huán)出來outs不是6,而是12,映射到論文就是跟這句話:we also add intermediate supervision in training。這是中繼監(jiān)督,具體是啥我也還在看。也就是說下面的6個(gè)都是列表,每個(gè)列表里面都含有兩個(gè)tensor,具體維度如下:'''# 兩個(gè)都是[batch_size, 類別數(shù), 128, 128]tl_heats = outs[0::stride]# 兩個(gè)都是[batch_size, 類別數(shù), 128, 128]br_heats = outs[1::stride]# 兩個(gè)都是[batch_size, 128, 1]tl_tags = outs[2::stride]# 兩個(gè)都是[batch_size, 128, 1]br_tags = outs[3::stride]# 兩個(gè)都是[batch_size, 128, 2]tl_regrs = outs[4::stride]# 兩個(gè)都是[batch_size, 128, 2]br_regrs = outs[5::stride]'''targets是gt,標(biāo)準(zhǔn)答案,也是個(gè)列表,但就只有下面5個(gè),沒有兩份具體維度如下'''# [batch_size, 類別數(shù), 128, 128]gt_tl_heat = targets[0]# [batch_size, 類別數(shù), 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]
上述就是傳入的預(yù)測值和真實(shí)值,Loss也就是計(jì)算預(yù)測的和真實(shí)之間的誤差,當(dāng)Loss值越小,那么說明網(wǎng)絡(luò)預(yù)測的結(jié)果越好。接下去有了預(yù)測和真實(shí)值,具體分析三個(gè)部分的Loss。
1、Heatmap的損失
Heatmap損失的理論理解在這,接下來是源碼理解:
這部分代碼在CornerNet\models\py_utils\kp.py中
# focal lossfocal_loss = 0# 到這里將heatmap經(jīng)過sigmoid,將值映射到0-1之間,變成keypoint的響應(yīng)值,還是列表,# 維度還是[batch_size, 類別數(shù), 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中詳細(xì)講述了focal_loss,這個(gè)focal loss就是_neg_loss,形參有體現(xiàn)focal_loss += self.focal_loss(tl_heats, gt_tl_heat)focal_loss += self.focal_loss(br_heats, gt_br_heat)
接著去到CornerNet\models\py_utils\kp_utils.py中詳細(xì)講述focal_loss:
'''
首先清楚函數(shù)的輸入:
preds是列表:(2,),表示一個(gè)列表中含兩個(gè)tensor,每個(gè)tensor的維度是(batch_size, 類別數(shù), 128, 128)
gt是tensor:(batch_size, 類別數(shù), 128, 128)
'''
def _neg_loss(preds, gt):# pos_inds是0、1tensor,維度[3,7,128,128]。# eq函數(shù)是遍歷gt這個(gè)tensor每個(gè)element,和1比較,如果等于1,則返回1,否則返回0pos_inds = gt.eq(1)# otherwise則是表明ycij第c個(gè)通道的(i,j)坐標(biāo)上值不為1# 遍歷gt這個(gè)tensor每個(gè)element,和1比較,如果小于1,則返回1,否則返回0neg_inds = gt.lt(1)# 總結(jié)下上面兩個(gè)變量:上面這兩個(gè)0-1位置互補(bǔ)# 回頭看這兩個(gè)變量,再結(jié)合公式1,公式1后面有兩個(gè)判斷條件:if ycij=1 and otherwise# 這里就是那兩個(gè)判斷條件,ycij=1表示第c個(gè)通道的(i,j)坐標(biāo)上值為1,也即是gt中這個(gè)位置有目標(biāo)# 也就是pos_inds是ycij=1,neg_inds是otherwise# torch.pow是次冪函數(shù),其中g(shù)t[neg_inds]表示取出neg_inds中值為1的gt的值# 所以gt[neg_inds]就變成一個(gè)向量了,那么維度就等于neg_inds中有多少為1的# 可以neg_inds.sum()看看,1 - gt[neg_inds]就是單純的用1減去每個(gè)element,# 然后每個(gè)element開4次方,就成了neg_weights,這個(gè)neg_weights是一維向量# 把gt中每個(gè)小于1的數(shù)字取出來,然后用1減去,在開方,那不是更小了,# 就是原來就很小,現(xiàn)在又降權(quán)。# gt[neg_inds]就是公式(1)中的Ycij# neg_weights就是公式(1)中的(1-ycij)^β,β就是4neg_weights = torch.pow(1 - gt[neg_inds], 4)loss = 0# 循環(huán)2次,因?yàn)閜reds是一個(gè)列表,有2部分,每部分放著一個(gè)tensor,每個(gè)tensor的# 維度為[batch_size,類別數(shù),128,128],也就是pred維度為[batch_size,類別數(shù),128,128]for pred in preds:# 首先記住pos_inds中的1就是gt中有目標(biāo)的地方,neg_inds中的1是gt中沒有目標(biāo)的地方# 將gt認(rèn)為有目標(biāo)的地方,pred也按這個(gè)地方取出數(shù)值,變成向量,pos_inds有多少個(gè)1,# pos_pred就多少維(一行向量)pos_pred = pred[pos_inds]# 將gt認(rèn)為沒有目標(biāo)的地方,pred也按這個(gè)地方取出數(shù)值,變成向量,neg_inds有多少個(gè)1,# neg_pred就多少維(一行向量)neg_pred = pred[neg_inds]# 以上出現(xiàn)的pos_xxx, neg_xxx,命名的意思就是正樣本positive和負(fù)樣本negative# 這里對應(yīng)的是論文中的公式(1),也就是heatmap的loss# 可以先根據(jù)公式把相應(yīng)的變量確認(rèn)下:pos_pred就是公式中的Pcij。# neg_pred就是公式中的要經(jīng)過二維高斯的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的那個(gè)tensor中,值為1的個(gè)數(shù),num_pos對應(yīng)公式(1)中的Nnum_pos = pos_inds.float().sum()# 累加pos_loss = pos_loss.sum()neg_loss = neg_loss.sum()# pos_pred是一維的。統(tǒng)計(jì)pos_pred中的元素個(gè)數(shù),單純的數(shù)個(gè)數(shù)而已,# 就算pos_pred中值為0的,也算一個(gè)if pos_pred.nelement() == 0:loss = loss - neg_losselse:# 用減號體現(xiàn)公式(1)中的-1loss = loss - (pos_loss + neg_loss) / num_pos# 返回最終的heatmap的lossreturn loss
2、Embedding的損失
Heatmap損失的理論理解在這,接下來是源碼理解:
接著回到CornerNet\models\py_utils\kp.py,看怎么調(diào)用embedding的loss:
# tag loss# 初始化為0pull_loss = 0push_loss = 0# tl_tags、br_tags是列表,里面有兩個(gè)tensor,每個(gè)tensor的維度為[batch_size, 128, 1]# 論文中說到的embedding是一維向量。也就是說,維度表示:一個(gè)batch_size一張圖,用128*1的矩陣表示??# 那么這個(gè)for循環(huán),循環(huán)2次,每次進(jìn)去的是[batch_size, 128, 1]的tl_tag, br_tagfor tl_tag, br_tag in zip(tl_tags, br_tags):pull, push = self.ae_loss(tl_tag, br_tag, gt_mask)pull_loss += pullpush_loss += push# 算出來的loss乘以相應(yīng)的權(quán)重pull_loss = self.pull_weight * pull_losspush_loss = self.push_weight * push_loss
接著去到CornerNet\models\py_utils\kp_utils.py中詳細(xì)講述ae_loss:
'''
embedding的損失
輸入:tag0、tag1為左上右下各一個(gè)[batch_size, 128, 1]的tensor,再來一個(gè)gt中的mask,這個(gè)mask是
0、1矩陣,維度[batch_size, 128],也就是一張圖用128維來表示??????
'''
def _ae_loss(tag0, tag1, mask):# mask是[batch_size, 128],這個(gè)就是第一維全部相加(sum),就是把每個(gè)batch的128個(gè)數(shù)字相加,所以num的# 維度是[batch_size, 1],1是128個(gè)數(shù)字的值相加變成一個(gè)數(shù)字,而mask還是0-1矩陣,所以這個(gè)num代表了# 每張圖有多少個(gè)1.這個(gè)num代表公式(4)和(5)中的Nnum = mask.sum(dim=1, keepdim=True).float()# 先看torch.squeeze() 這個(gè)函數(shù)主要對數(shù)據(jù)的維度進(jìn)行壓縮,去掉維數(shù)為1的的維度# 所以tag0和tag1的維度變成了[batch_size, 128],和mask一樣# tag0就是公式(4)中的etktag0 = tag0.squeeze()# tag0就是公式(4)中的ebktag1 = tag1.squeeze()# 單純的求平均而已,這個(gè)tag_mean對應(yīng)公式(4)和(5)中的ek,維度不變tag_mean = (tag0 + tag1) / 2# 這里能夠體現(xiàn)是同類別的,因?yàn)槔奂又挥幸淮?#xff0c;也就是Lpull用來縮小# 同類別左上右下角點(diǎn)的embedding vector的距離# 公式(4)前半段tag0 = torch.pow(tag0 - tag_mean, 2) / (num + 1e-4)# 這句能體現(xiàn)累加,這里tag0已經(jīng)是單個(gè)數(shù)字tag0 = tag0[mask].sum()# 公式(4)后半段tag1 = torch.pow(tag1 - tag_mean, 2) / (num + 1e-4)# 這句能體現(xiàn)累加,這里tag1已經(jīng)是單個(gè)數(shù)字tag1 = tag1[mask].sum()# 總的Lpullpull = tag0 + tag1# Lpush# 這里能夠體現(xiàn)是不同類別的,因?yàn)槔奂佑袃纱?#xff0c;公式(5)中的j不等于k,也就是Lpush用來擴(kuò)大# 不同類別左上右下角點(diǎn)的embedding vector的距離# 這時(shí)候mask的維度由[3,128]-->[3,128,128]mask = mask.unsqueeze(1) + mask.unsqueeze(2)# 遍歷mask這個(gè)tensor每個(gè)element,和2比較,如果等于2,則返回1,否則返回0,但為啥是2呢?mask = mask.eq(2)# num的維度[3, 1]-->[3, 1, 1]num = num.unsqueeze(2)# num2的維度[3, 1, 1],num2表示公式(5)中的N(N-1)num2 = (num - 1) * num# dist是公式(5)中絕對值之間的運(yùn)算# dist維度[3, 128, 128]=[3, 1, 128]-[3, 128, 1]dist = tag_mean.unsqueeze(1) - tag_mean.unsqueeze(2)# 1表示公式(5)三角形dist = 1 - torch.abs(dist)# 公式(5)就是relu,所以計(jì)算方式直接套reludist = nn.functional.relu(dist, inplace=True)dist = dist - 1 / (num + 1e-4)dist = dist / (num2 + 1e-4)# 這時(shí)候mask的維度[3,128,128],dist維度[3,128,128]dist = dist[mask]# sum之后就變成一個(gè)數(shù)字了push = dist.sum()# 返回兩個(gè)loss,兩個(gè)tensor的數(shù)字return pull, push
3、Offset的損失
Heatmap損失的理論理解在這,接下來是源碼理解:
接著回到CornerNet\models\py_utils\kp.py,看怎么調(diào)用offset的loss:
# offsets lossregr_loss = 0# tl_regrs、br_regrs是列表,里面有兩個(gè)tensor,每個(gè)tensor的維度為[batch_size, 128, 2]# 維度表示:一個(gè)batch_size一張圖,用128*2的矩陣表示??# 那么這個(gè)for循環(huán),循環(huán)2次,每次進(jìn)去的是[batch_size, 128, 2]的tl_regr, br_regrfor tl_regr, br_regr in zip(tl_regrs, br_regrs):regr_loss += self.regr_loss(tl_regr, gt_tl_regr, gt_mask)regr_loss += self.regr_loss(br_regr, gt_br_regr, gt_mask)regr_loss = self.regr_weight * regr_loss# 總的lossloss = (focal_loss + pull_loss + push_loss + regr_loss) / len(tl_heats)# unsqueeze(i) 表示將第i維設(shè)置為1維return loss.unsqueeze(0)
接著去到CornerNet\models\py_utils\kp_utils.py中詳細(xì)講述regr_loss:
'''
輸入:regr偏移量,維度[batch_size, 128, 2],gt_regr維度[batch_size, 128, 2]
mask維度[batch_size, 128]
'''
def _regr_loss(regr, gt_regr, mask):# 公式(3)的Nnum = mask.float().sum()# mask.unsqueeze(2)維度[batch_size, 128, 1]# mask的維度[batch_size, 128, 2]mask = mask.unsqueeze(2).expand_as(gt_regr)# 取出mask中1對應(yīng)的位置,然后在預(yù)測的偏移量和真實(shí)的偏移量中取出這些位置的值# 此時(shí)二者的維度變?yōu)橐痪S向量regr = regr[mask]gt_regr = gt_regr[mask]# 直接調(diào)用自帶的SmoothL1Lossregr_loss = nn.functional.smooth_l1_loss(regr, gt_regr, size_average=False)# 最后除Nregr_loss = regr_loss / (num + 1e-4)return regr_loss
總結(jié)
以上是生活随笔為你收集整理的CornerNet代码解析——损失函数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CornerNet:实现demo、可视化
- 下一篇: 数字图像处理——第四章 频率域滤波