CornerNet代码解析——损失函数
CornerNet代碼解析——損失函數(shù)
文章目錄
- CornerNet代碼解析——損失函數(shù)
- 前言
- 總體損失
- 1、Heatmap的損失
- 2、Embedding的損失
- 3、Offset的損失
前言
今天要解析的是CornerNet的Loss層源碼,論文中Loss的解析在這:CornerNet的損失函數(shù)原理
總體損失
總體的損失函數(shù)如下圖所示,三個輸出分別對應三部分損失,每部分損失有著對應的權(quán)重。接下來分別講述每一塊的損失。
源碼中將Loss寫成一個類: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# 這其實就是heatmap的lossself.focal_loss = focal_loss# 這其實就是embedding的lossself.ae_loss = _ae_loss# 這其實就是offset的lossself.regr_loss = _regr_lossdef forward(self, outs, targets):stride = 6# ::跳著選'''首先明確兩個輸入:outs和targetsouts:這是網(wǎng)絡的預測結(jié)果,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有兩個,左上角的offset有兩個,右下角的offset有兩個,共12個,為什么要兩份?應該跟上面的nstack有關(guān),上述的nstack=2,所以循環(huán)出來outs不是6,而是12,映射到論文就是跟這句話:we also add intermediate supervision in training。這是中繼監(jiān)督,具體是啥我也還在看。也就是說下面的6個都是列表,每個列表里面都含有兩個tensor,具體維度如下:'''# 兩個都是[batch_size, 類別數(shù), 128, 128]tl_heats = outs[0::stride]# 兩個都是[batch_size, 類別數(shù), 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, 類別數(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]
上述就是傳入的預測值和真實值,Loss也就是計算預測的和真實之間的誤差,當Loss值越小,那么說明網(wǎng)絡預測的結(jié)果越好。接下去有了預測和真實值,具體分析三個部分的Loss。
1、Heatmap的損失
Heatmap損失的理論理解在這,接下來是源碼理解:
這部分代碼在CornerNet\models\py_utils\kp.py中
# focal lossfocal_loss = 0# 到這里將heatmap經(jīng)過sigmoid,將值映射到0-1之間,變成keypoint的響應值,還是列表,# 維度還是[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中詳細講述了focal_loss,這個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中詳細講述focal_loss:
'''
首先清楚函數(shù)的輸入:
preds是列表:(2,),表示一個列表中含兩個tensor,每個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這個tensor每個element,和1比較,如果等于1,則返回1,否則返回0pos_inds = gt.eq(1)# otherwise則是表明ycij第c個通道的(i,j)坐標上值不為1# 遍歷gt這個tensor每個element,和1比較,如果小于1,則返回1,否則返回0neg_inds = gt.lt(1)# 總結(jié)下上面兩個變量:上面這兩個0-1位置互補# 回頭看這兩個變量,再結(jié)合公式1,公式1后面有兩個判斷條件:if ycij=1 and otherwise# 這里就是那兩個判斷條件,ycij=1表示第c個通道的(i,j)坐標上值為1,也即是gt中這個位置有目標# 也就是pos_inds是ycij=1,neg_inds是otherwise# torch.pow是次冪函數(shù),其中g(shù)t[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的數(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次,因為preds是一個列表,有2部分,每部分放著一個tensor,每個tensor的# 維度為[batch_size,類別數(shù),128,128],也就是pred維度為[batch_size,類別數(shù),128,128]for pred in preds:# 首先記住pos_inds中的1就是gt中有目標的地方,neg_inds中的1是gt中沒有目標的地方# 將gt認為有目標的地方,pred也按這個地方取出數(shù)值,變成向量,pos_inds有多少個1,# pos_pred就多少維(一行向量)pos_pred = pred[pos_inds]# 將gt認為沒有目標的地方,pred也按這個地方取出數(shù)值,變成向量,neg_inds有多少個1,# neg_pred就多少維(一行向量)neg_pred = pred[neg_inds]# 以上出現(xiàn)的pos_xxx, neg_xxx,命名的意思就是正樣本positive和負樣本negative# 這里對應的是論文中的公式(1),也就是heatmap的loss# 可以先根據(jù)公式把相應的變量確認下: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的那個tensor中,值為1的個數(shù),num_pos對應公式(1)中的Nnum_pos = pos_inds.float().sum()# 累加pos_loss = pos_loss.sum()neg_loss = neg_loss.sum()# pos_pred是一維的。統(tǒng)計pos_pred中的元素個數(shù),單純的數(shù)個數(shù)而已,# 就算pos_pred中值為0的,也算一個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是列表,里面有兩個tensor,每個tensor的維度為[batch_size, 128, 1]# 論文中說到的embedding是一維向量。也就是說,維度表示:一個batch_size一張圖,用128*1的矩陣表示??# 那么這個for循環(huán),循環(huán)2次,每次進去的是[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乘以相應的權(quán)重pull_loss = self.pull_weight * pull_losspush_loss = self.push_weight * push_loss
接著去到CornerNet\models\py_utils\kp_utils.py中詳細講述ae_loss:
'''
embedding的損失
輸入:tag0、tag1為左上右下各一個[batch_size, 128, 1]的tensor,再來一個gt中的mask,這個mask是
0、1矩陣,維度[batch_size, 128],也就是一張圖用128維來表示??????
'''
def _ae_loss(tag0, tag1, mask):# mask是[batch_size, 128],這個就是第一維全部相加(sum),就是把每個batch的128個數(shù)字相加,所以num的# 維度是[batch_size, 1],1是128個數(shù)字的值相加變成一個數(shù)字,而mask還是0-1矩陣,所以這個num代表了# 每張圖有多少個1.這個num代表公式(4)和(5)中的Nnum = mask.sum(dim=1, keepdim=True).float()# 先看torch.squeeze() 這個函數(shù)主要對數(shù)據(jù)的維度進行壓縮,去掉維數(shù)為1的的維度# 所以tag0和tag1的維度變成了[batch_size, 128],和mask一樣# tag0就是公式(4)中的etktag0 = tag0.squeeze()# tag0就是公式(4)中的ebktag1 = tag1.squeeze()# 單純的求平均而已,這個tag_mean對應公式(4)和(5)中的ek,維度不變tag_mean = (tag0 + tag1) / 2# 這里能夠體現(xiàn)是同類別的,因為累加只有一次,也就是Lpull用來縮小# 同類別左上右下角點的embedding vector的距離# 公式(4)前半段tag0 = torch.pow(tag0 - tag_mean, 2) / (num + 1e-4)# 這句能體現(xiàn)累加,這里tag0已經(jīng)是單個數(shù)字tag0 = tag0[mask].sum()# 公式(4)后半段tag1 = torch.pow(tag1 - tag_mean, 2) / (num + 1e-4)# 這句能體現(xiàn)累加,這里tag1已經(jīng)是單個數(shù)字tag1 = tag1[mask].sum()# 總的Lpullpull = tag0 + tag1# Lpush# 這里能夠體現(xiàn)是不同類別的,因為累加有兩次,公式(5)中的j不等于k,也就是Lpush用來擴大# 不同類別左上右下角點的embedding vector的距離# 這時候mask的維度由[3,128]-->[3,128,128]mask = mask.unsqueeze(1) + mask.unsqueeze(2)# 遍歷mask這個tensor每個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)中絕對值之間的運算# 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,所以計算方式直接套reludist = nn.functional.relu(dist, inplace=True)dist = dist - 1 / (num + 1e-4)dist = dist / (num2 + 1e-4)# 這時候mask的維度[3,128,128],dist維度[3,128,128]dist = dist[mask]# sum之后就變成一個數(shù)字了push = dist.sum()# 返回兩個loss,兩個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是列表,里面有兩個tensor,每個tensor的維度為[batch_size, 128, 2]# 維度表示:一個batch_size一張圖,用128*2的矩陣表示??# 那么這個for循環(huán),循環(huán)2次,每次進去的是[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中詳細講述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ō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、可视化
- 下一篇: 数字图像处理——第四章 频率域滤波