0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Loss計算詳細(xì)解析

jf_pmFSk4VX ? 來源:GiantPandaCV ? 2023-01-13 14:38 ? 次閱讀

其中一些常見的損失函數(shù)包括:

分類損失(cls_loss):該損失用于判斷模型是否能夠準(zhǔn)確地識別出圖像中的對象,并將其分類到正確的類別中。

置信度損失(obj_loss):該損失用于衡量模型預(yù)測的框(即包含對象的矩形)與真實框之間的差異。

邊界框損失(box_loss):該損失用于衡量模型預(yù)測的邊界框與真實邊界框之間的差異,這有助于確保模型能夠準(zhǔn)確地定位對象。

這些損失函數(shù)在訓(xùn)練模型時被組合使用,以優(yōu)化模型的性能。通過使用這些損失函數(shù),YOLOv5可以準(zhǔn)確地識別圖像中的對象,并將其定位到圖像中的具體位置。

1. 導(dǎo)入需要的包

importoneflowasflow
importoneflow.nnasnn

fromutils.metricsimportbbox_iou
fromutils.oneflow_utilsimportde_parallel

2. smooth_BCE

這個函數(shù)是一個標(biāo)簽平滑的策略(trick),是一種在 分類/檢測 問題中,防止過擬合的方法。

如果要詳細(xì)理解這個策略的原理,請參閱博文:《trick 1》Label Smoothing(標(biāo)簽平滑)—— 分類問題中錯誤標(biāo)注的一種解決方法.

smooth_BCE函數(shù)代碼:

#標(biāo)簽平滑
defsmooth_BCE(eps=0.1):#https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441
"""用在ComputeLoss類中
標(biāo)簽平滑操作[1,0]=>[0.95,0.05]
:paramseps:平滑參數(shù)
:returnpositive,negativelabelsmoothingBCEtargets兩個值分別代表正樣本和負(fù)樣本的標(biāo)簽取值
原先的正樣本=1負(fù)樣本=0改為正樣本=1.0-0.5*eps負(fù)樣本=0.5*eps
"""
#returnpositive,negativelabelsmoothingBCEtargets
return1.0-0.5*eps,0.5*eps

通常會用在分類損失當(dāng)中,如下ComputeLoss類的__init__函數(shù)定義:

self.cp,self.cn=smooth_BCE(eps=h.get("label_smoothing",0.0))#positive,negativeBCEtargets

ComputeLoss類的__call__函數(shù)調(diào)用:

#Classification
ifself.nc>1:#clsloss(onlyifmultipleclasses)
t=flow.full_like(pcls,self.cn,device=self.device)#targets

#t[range(n),tcls[i]]=self.cp
t[flow.arange(n,device=self.device),tcls[i]]=self.cp

lcls=lcls+self.BCEcls(pcls,t)#BCE

3. BCEBlurWithLogitsLoss

這個函數(shù)是BCE函數(shù)的一個替代,是yolov5作者的一個實驗性的函數(shù),可以自己試試效果。

使用起來直接在ComputeLoss類的__init__函數(shù)中替代傳統(tǒng)的BCE函數(shù)即可:

classBCEBlurWithLogitsLoss(nn.Module):
"""用在ComputeLoss類的__init__函數(shù)中
BCEwithLogitLoss()withreducedmissinglabeleffects.
https://github.com/ultralytics/yolov5/issues/1030
Theideawastoreducetheeffectsoffalsepositive(missinglabels)就是檢測成正樣本了但是檢測錯了
"""
def__init__(self,alpha=0.05):
super(BCEBlurWithLogitsLoss,self).__init__()
self.loss_fcn=nn.BCEWithLogitsLoss(reduction='none')#mustbenn.BCEWithLogitsLoss()
self.alpha=alpha

defforward(self,pred,true):
loss=self.loss_fcn(pred,true)
pred=flow.sigmoid(pred)#probfromlogits
#dx=[-1,1]當(dāng)pred=1true=0時(網(wǎng)絡(luò)預(yù)測說這里有個obj但是gt說這里沒有),dx=1=>alpha_factor=0=>loss=0
#這種就是檢測成正樣本了但是檢測錯了(falsepositive)或者missinglabel的情況這種情況不應(yīng)該過多的懲罰->loss=0
dx=pred-true#reduceonlymissinglabeleffects
#如果采樣絕對值的話會減輕pred和gt差異過大而造成的影響
#dx=(pred-true).abs()#reducemissinglabelandfalselabeleffects
alpha_factor=1-flow.exp((dx-1)/(self.alpha+1e-4))
loss*=alpha_factor
returnloss.mean()

4. FocalLoss

FocalLoss損失函數(shù)來自 Kaiming He在2017年發(fā)表的一篇論文:Focal Loss for Dense Object Detection. 這篇論文設(shè)計的主要思路: 希望那些hard examples對損失的貢獻變大,使網(wǎng)絡(luò)更傾向于從這些樣本上學(xué)習(xí)。防止由于easy examples過多,主導(dǎo)整個損失函數(shù)。

優(yōu)點:

解決了one-stage object detection中圖片中正負(fù)樣本(前景和背景)不均衡的問題;降低簡單樣本的權(quán)重,使損失函數(shù)更關(guān)注困難樣本;函數(shù)公式:
pYYBAGPA_WqAGfAUAABnSferxys709.jpg

FocalLoss函數(shù)代碼:

classFocalLoss(nn.Module):
"""用在代替原本的BCEcls(分類損失)和BCEobj(置信度損失)
Wrapsfocallossaroundexistingloss_fcn(),i.e.criteria=FocalLoss(nn.BCEWithLogitsLoss(),gamma=1.5)
論文:https://arxiv.org/abs/1708.02002
https://blog.csdn.net/qq_38253797/article/details/116292496
TFimplementationhttps://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
"""
#Wrapsfocallossaroundexistingloss_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#mustbenn.BCEWithLogitsLoss()定義為多分類交叉熵?fù)p失函數(shù)
self.gamma=gamma#參數(shù)gamma用于削弱簡單樣本對loss的貢獻程度
self.alpha=alpha#參數(shù)alpha用于平衡正負(fù)樣本個數(shù)不均衡的問題
self.reduction=loss_fcn.reduction#self.reduction:控制FocalLoss損失輸出模式sum/mean/none默認(rèn)是Mean
#focalloss中的BCE函數(shù)的reduction='None'BCE不使用Sum或者Mean
#需要將Focalloss應(yīng)用于每一個樣本之中
self.loss_fcn.reduction="none"#requiredtoapplyFLtoeachelement

defforward(self,pred,true):
#正常BCE的loss:loss=-log(p_t)
loss=self.loss_fcn(pred,true)
#p_t=flow.exp(-loss)
#loss*=self.alpha*(1.000001-p_t)**self.gamma#non-zeropowerforgradientstability

#TFimplementationhttps://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
pred_prob=flow.sigmoid(pred)#probfromlogits
p_t=true*pred_prob+(1-true)*(1-pred_prob)
alpha_factor=true*self.alpha+(1-true)*(1-self.alpha)
modulating_factor=(1.0-p_t)**self.gamma#這里代表Focalloss中的指數(shù)項
#返回最終的loss=BCE*兩個參數(shù)(看看公式就行了和公式一模一樣)
loss=loss*alpha_factor*modulating_factor
#最后選擇focalloss返回的類型默認(rèn)是mean
ifself.reduction=="mean":
returnloss.mean()
elifself.reduction=="sum":
returnloss.sum()
else:#'none'
returnloss

這個函數(shù)用在代替原本的BCEcls和BCEobj:

#Focalloss
g=h["fl_gamma"]#focallossgammag=0代表不用focalloss
ifg>0:
BCEcls,BCEobj=FocalLoss(BCEcls,g),FocalLoss(BCEobj,g)

5. QFocalLoss

公式:

QFocalLoss函數(shù)代碼:

classQFocalLoss(nn.Module):
"""用來代替FocalLoss
QFocalLoss來自GeneralFocalLoss論文:https://arxiv.org/abs/2006.04388
WrapsQualityfocallossaroundexistingloss_fcn(),
i.e.criteria=FocalLoss(nn.BCEWithLogitsLoss(),gamma=1.5)
"""
#WrapsQualityfocallossaroundexistingloss_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#mustbenn.BCEWithLogitsLoss()
self.gamma=gamma
self.alpha=alpha
self.reduction=loss_fcn.reduction
self.loss_fcn.reduction="none"#requiredtoapplyFLtoeachelement

defforward(self,pred,true):
loss=self.loss_fcn(pred,true)

pred_prob=flow.sigmoid(pred)#probfromlogits
alpha_factor=true*self.alpha+(1-true)*(1-self.alpha)
modulating_factor=flow.abs(true-pred_prob)**self.gamma
loss=loss*(alpha_factor*modulating_factor)

ifself.reduction=="mean":
returnloss.mean()
elifself.reduction=="sum":
returnloss.sum()
else:#'none'
returnloss

使用 QFolcalLoss 直接在 ComputeLoss 類中使用 QFolcalLoss替換掉 FocalLoss 即可:(也就是說用 QFolcalLoss 替換如下圖代碼處的FocalLoss )

6e42f676-7b9a-11ed-8abf-dac502259ad0.png

6. ComputeLoss類

6.1 __init__函數(shù)

sort_obj_iou=False#后面篩選置信度損失正樣本的時候是否先對iou排序
#Computelosses
def__init__(self,model,autobalance=False):
#獲取模型所在的設(shè)備
device=next(model.parameters()).device
#獲取模型的超參數(shù)
h=model.hyp
#定義分類損失和置信度損失
BCEcls=nn.BCEWithLogitsLoss(pos_weight=flow.tensor([h["cls_pw"]],device=device))
BCEobj=nn.BCEWithLogitsLoss(pos_weight=flow.tensor([h["obj_pw"]],device=device))
#標(biāo)簽平滑eps=0代表不做標(biāo)簽平滑->cp=1cn=0/eps!=0代表做標(biāo)簽平滑
#cp代表正樣本的標(biāo)簽值cn代表負(fù)樣本的標(biāo)簽值
#請參考:Classlabelsmoothinghttps://arxiv.org/pdf/1902.04103.pdfeqn3
self.cp,self.cn=smooth_BCE(eps=h.get("label_smoothing",0.0))#positive,negativeBCEtargets

#Focalloss
g=h["fl_gamma"]#FocalLoss的超參數(shù)gamma
ifg>0:
#如果g>0將分類損失和置信度損失(BCE)都換成FocalLoss損失函數(shù)
BCEcls,BCEobj=FocalLoss(BCEcls,g),FocalLoss(BCEobj,g)
#m:返回的是模型的3個檢測頭分別對應(yīng)產(chǎn)生的3個輸出特征圖
m=de_parallel(model).model[-1]#Detect()module

"""self.balance用來實現(xiàn)obj,box,clsloss之間權(quán)重的平衡
{3:[4.0,1.0,0.4]}表示有三個layer的輸出,第一個layer的weight是4.0,第二個1.0,第三個以此類推。
如果有5個layer的輸出,那么權(quán)重分別是[4.0,1.0,0.25,0.06,0.02]
"""
self.balance={3:[4.0,1.0,0.4]}.get(m.nl,[4.0,1.0,0.25,0.06,0.02])#P3-P7
#三個檢測頭的下采樣率m.stride:[8,16,32].index(16):求出下采樣率stride=16的索引
#這個參數(shù)會用來自動計算更新3個featuremap的置信度損失系數(shù)self.balance
self.ssi=list(m.stride).index(16)ifautobalanceelse0#stride16index
self.BCEcls,self.BCEobj,self.gr,self.hyp,self.autobalance=(
BCEcls,
BCEobj,
1.0,
h,
autobalance,
)
self.na=m.na#numberofanchors每個grid_cell的anchor數(shù)量=3
self.nc=m.nc#numberofclasses數(shù)據(jù)集的總類別=80
self.nl=m.nl#numberoflayers檢測頭的個數(shù)=3
#anchors:形狀[3,3,2]代表3個featuremap每個featuremap上有3個anchor(w,h)
#這里的anchors尺寸是相對featuremap的
self.anchors=m.anchors
self.device=device

6.2 build_targets

這個函數(shù)是用來為所有GT篩選相應(yīng)的anchor正樣本。

篩選條件是比較GT和anchor的寬比和高比,大于一定的閾值就是負(fù)樣本,反之正樣本。

篩選到的正樣本信息(image_index, anchor_index, gridy, gridx),傳入 __call__ 函數(shù),

通過這個信息去篩選 pred 里每個 grid 預(yù)測得到的信息,保留對應(yīng) grid_cell 上的正樣本。

通過 build_targets 篩選的 GT 中的正樣本和 pred 篩選出的對應(yīng)位置的預(yù)測樣本 進行計算損失。

補充理解:

這個函數(shù)的目的是為了每個 GT 匹配對應(yīng)的高質(zhì)量 Anchor 正樣本參與損失計算,

j = flow.max(r, 1. / r).max(2)[0] < self.hyp["anchor_t"] 這步的比較是為了將 GT 分配到不同層上去檢測,(詳細(xì)解釋請看下面的逐行代碼注釋)

后面的步驟是為了確定在這層檢測的 GT 中心坐標(biāo),

進而確定這個 GT 在這層哪個 grid cell 進行檢測。

做到這一步也就做到了為每個 GT 匹配 Anchor 正樣本的目的。

#---------------------------------------------------------
#build_targets函數(shù)用于獲得在訓(xùn)練時計算loss所需要的目標(biāo)框,也即正樣本。與yolov3/v4的不同,yolov5支持跨網(wǎng)格預(yù)測。
#對于任何一個GTbbox,三個預(yù)測特征層上都可能有先驗框匹配,所以該函數(shù)輸出的正樣本框比傳入的targets(GT框)數(shù)目多
#具體處理過程:
#(1)首先通過bbox與當(dāng)前層anchor做一遍過濾。對于任何一層計算當(dāng)前bbox與當(dāng)前層anchor的匹配程度,不采用IoU,而采用shape比例。如果anchor與bbox的寬高比差距大于4,則認(rèn)為不匹配,此時忽略相應(yīng)的bbox,即當(dāng)做背景;
#(2)根據(jù)留下的bbox,在上下左右四個網(wǎng)格四個方向擴增采樣(即對bbox計算落在的網(wǎng)格所有anchors都計算loss(并不是直接和GT框比較計算loss))
#注意此時落在網(wǎng)格不再是一個,而是附近的多個,這樣就增加了正樣本數(shù)。
#yolov5沒有conf分支忽略閾值(ignore_thresh)的操作,而yoloy3/v4有。
#--------------------------------------------------------

defbuild_targets(self,p,targets):

"""所有GT篩選相應(yīng)的anchor正樣本
這里通過
p:list([16,3,80,80,85],[16,3,40,40,85],[16,3,20,20,85])
targets:targets.shape[314,6]
解析build_targets(self,p,targets):函數(shù)
Buildtargetsforcompute_loss()
:paramsp:p[i]的作用只是得到每個featuremap的shape
預(yù)測框由模型構(gòu)建中的三個檢測頭Detector返回的三個yolo層的輸出
tensor格式list列表存放三個tensor對應(yīng)的是三個yolo層的輸出
如:list([16,3,80,80,85],[16,3,40,40,85],[16,3,20,20,85])
[bs,anchor_num,grid_h,grid_w,xywh+class+classes]
可以看出來這里的預(yù)測值p是三個yolo層每個grid_cell(每個grid_cell有三個預(yù)測值)的預(yù)測值,后面肯定要進行正樣本篩選
:paramstargets:數(shù)據(jù)增強后的真實框[63,6][num_target,image_index+class+xywh]xywh為歸一化后的框
:returntcls:表示這個target所屬的classindex
tbox:xywh其中xy為這個target對當(dāng)前grid_cell左上角的偏移量
indices:b:表示這個target屬于的imageindex
a:表示這個target使用的anchorindex
gj:經(jīng)過篩選后確定某個target在某個網(wǎng)格中進行預(yù)測(計算損失)gj表示這個網(wǎng)格的左上角y坐標(biāo)
gi:表示這個網(wǎng)格的左上角x坐標(biāo)
anch:表示這個target所使用anchor的尺度(相對于這個featuremap)注意可能一個target會使用大小不同anchor進行計算
"""
#Buildtargetsforcompute_loss(),inputtargets(image,class,x,y,w,h)
#na=3;nt=314
na,nt=self.na,targets.shape[0]#numberofanchors,targets

tcls,tbox,indices,anch=[],[],[],[]
#gain.shape=[7]
gain=flow.ones(7,device=self.device)#normalizedtogridspacegain
#ai.shape=(na,nt)生成anchor索引
#anchor索引,后面有用,用于表示當(dāng)前bbox和當(dāng)前層的哪個anchor匹配
#需要在3個anchor上都進行訓(xùn)練所以將標(biāo)簽賦值na=3個
#ai代表3個anchor上在所有的target對應(yīng)的anchor索引就是用來標(biāo)記下當(dāng)前這個target屬于哪個anchor
#[1,3]->[3,1]->[3,314]=[na,nt]三行第一行63個0第二行63個1第三行63個2
#ai.shape=[3,314]
ai=flow.arange(na,device=self.device).float().view(na,1).repeat(1,nt)#sameas.repeat_interleave(nt)

#[314,6][3,314]->[3,314,6][3,314,1]->[3,314,7]7:[image_index+class+xywh+anchor_index]
#對每一個featuremap:這一步是將target復(fù)制三份對應(yīng)一個featuremap的三個anchor
#先假設(shè)所有的target都由這層的三個anchor進行檢測(復(fù)制三份)再進行篩選并將ai加進去標(biāo)記當(dāng)前是哪個anchor的target
#targets.shape=[3,314,7]
targets=flow.cat((targets.repeat(na,1,1),ai[...,None]),2)#appendanchorindices
#這兩個變量是用來擴展正樣本的因為預(yù)測框預(yù)測到target有可能不止當(dāng)前的格子預(yù)測到了
#可能周圍的格子也預(yù)測到了高質(zhì)量的樣本我們也要把這部分的預(yù)測信息加入正樣本中
#設(shè)置網(wǎng)格中心偏移量
g=0.5#bias
#附近的4個框
#以自身+周圍左上右下4個網(wǎng)格=5個網(wǎng)格用來計算offsets
off=(
flow.tensor(
[
[0,0],
[1,0],
[0,1],
[-1,0],
[0,-1],#j,k,l,m
#[1,1],[1,-1],[-1,1],[-1,-1],#jk,jm,lk,lm
],
device=self.device,
).float()
*g
)#offsets
#對每個檢測層進行處理
#遍歷三個feature篩選gt的anchor正樣本
foriinrange(self.nl):#self.nl:numberofdetectionlayersDetect的個數(shù)=3
#anchors:當(dāng)前featuremap對應(yīng)的三個anchor尺寸(相對featuremap)[3,2]
anchors,shape=self.anchors[i],p[i].shape

#gain:保存每個輸出featuremap的寬高->gain[2:6]=flow.tensor(shape)[[3,2,3,2]]
#[1,1,1,1,1,1,1]->[1,1,112,112,112,112,1]=image_index+class+xywh+anchor_index
gain[2:6]=flow.tensor(p[i].shape,device=self.device)[[3,2,3,2]].float()#xyxygain
#Matchtargetstoanchors
#t.shape=[3,314,7]將target中的xywh的歸一化尺度放縮到相對當(dāng)前featuremap的坐標(biāo)尺度
#[3,314,image_index+class+xywh+anchor_index]
t=targets*gain#shape(3,n,7)
ifnt:#如果有目標(biāo)就開始匹配
#Matches
#所有的gt與當(dāng)前層的三個anchor的寬高比(w/wh/h)
#r.shape=[3,314,2]
r=t[...,4:6]/anchors[:,None]#whratio
#篩選條件GT與anchor的寬比或高比超過一定的閾值就當(dāng)作負(fù)樣本
#flow.max(r,1./r)=[3,314,2]篩選出寬比w1/w2w2/w1高比h1/h2h2/h1中最大的那個
#.max(2)返回寬比高比兩者中較大的一個值和它的索引[0]返回較大的一個值
#j.shape=[3,314]False:當(dāng)前anchor是當(dāng)前gt的負(fù)樣本True:當(dāng)前anchor是當(dāng)前gt的正樣本
j=flow.max(r,1/r).max(2)[0]model.hyp['iou_t']#iou(3,n)=wh_iou(anchors(3,2),gwh(n,2))
#根據(jù)篩選條件j,過濾負(fù)樣本,得到所有g(shù)t的anchor正樣本(batch_size張圖片)
#知道當(dāng)前gt的坐標(biāo)屬于哪張圖片正樣本對應(yīng)的idx也就得到了當(dāng)前gt的正樣本anchor
#t:[3,314,7]->[555,7][num_Positive_sample,image_index+class+xywh+anchor_index]
t=t[j]#filter
#Offsets篩選當(dāng)前格子周圍格子找到2個離target中心最近的兩個格子
#可能周圍的格子也預(yù)測到了高質(zhì)量的樣本我們也要把這部分的預(yù)測信息加入正樣本中
#除了target所在的當(dāng)前格子外,還有2個格子對目標(biāo)進行檢測(計算損失)
#也就是說一個目標(biāo)需要3個格子去預(yù)測(計算損失)
#首先當(dāng)前格子是其中1個再從當(dāng)前格子的上下左右四個格子中選擇2個
#用這三個格子去預(yù)測這個目標(biāo)(計算損失)
#featuremap上的原點在左上角向右為x軸正坐標(biāo)向下為y軸正坐標(biāo)
#gridxy取target中心的坐標(biāo)xy(相對featuremap左上角的坐標(biāo))
#gxy.shape=[555,2]
gxy=t[:,2:4]#gridxy
#inverse得到target中心點相對于右下角的坐標(biāo)gain[[2,3]]為當(dāng)前featuremap的wh
#gxi.shape=[555,2]
gxi=gain[[2,3]]-gxy#inverse
#篩選中心坐標(biāo)距離當(dāng)前grid_cell的左、上方偏移小于g=0.5
#且中心坐標(biāo)必須大于1(坐標(biāo)不能在邊上此時就沒有4個格子了)
#j:[555]bool如果是True表示當(dāng)前target中心點所在的格子的左邊格子也對該target進行回歸(后續(xù)進行計算損失)
#k:[555]bool如果是True表示當(dāng)前target中心點所在的格子的上邊格子也對該target進行回歸(后續(xù)進行計算損失)
j,k=((gxy%11)).T
#篩選中心坐標(biāo)距離當(dāng)前grid_cell的右、下方偏移小于g=0.5且中心坐標(biāo)必須大于1(坐標(biāo)不能在邊上此時就沒有4個格子了)
#l:[555]bool如果是True表示當(dāng)前target中心點所在的格子的右邊格子也對該target進行回歸(后續(xù)進行計算損失)
#m:[555]bool如果是True表示當(dāng)前target中心點所在的格子的下邊格子也對該target進行回歸(后續(xù)進行計算損失)
l,m=((gxi%11)).T
#j.shape=[5,555]
j=flow.stack((flow.ones_like(j),j,k,l,m))
#得到篩選后所有格子的正樣本格子數(shù)<=3*555?都不在邊上等號成立
????????????????#?t:?[555,?7]?->復(fù)制5份target[5,555,7]分別對應(yīng)當(dāng)前格子和左上右下格子5個格子
#使用j篩選后t的形狀:[1659,7]
t=t.repeat((5,1,1))[j]
#flow.zeros_like(gxy)[None]:[1,555,2]off[:,None]:[5,1,2]=>[5,555,2]
#得到所有篩選后的網(wǎng)格的中心相對于這個要預(yù)測的真實框所在網(wǎng)格邊界
#(左右上下邊框)的偏移量,然后通過j篩選最終offsets的形狀是[1659,2]
offsets=(flow.zeros_like(gxy)[None]+off[:,None])[j]
else:
t=targets[0]
offsets=0

#Define
#bc.shape=[1659,2]
#gxy.shape=[1659,2]
#gwh.shape=[1659,2]
#a.shape=[1659,1]
bc,gxy,gwh,a=t.chunk(4,1)#(image,class),gridxy,gridwh,anchors

#a,(b,c)=a.long().view(-1),bc.long().T#anchors,image,class
#a.shape=[1659]
#(b,c).shape=[1659,2]
a,(b,c)=(
a.contiguous().long().view(-1),
bc.contiguous().long().T,
)#anchors,image,class

#gij=(gxy-offsets).long()
#預(yù)測真實框的網(wǎng)格所在的左上角坐標(biāo)(有左上右下的網(wǎng)格)
#gij.shape=[1659,2]
gij=(gxy-offsets).contiguous().long()
#這里的拆分我們可以用下面的示例代碼來進行解釋:
#importoneflowasflow

#x=flow.randn(3,2)
#y,z=x.T
#print(y.shape)
#print(z.shape)

#=>oneflow.Size([3])
#=>oneflow.Size([3])

#因此:
#gi.shape=[1659]
#gj.shape=[1659]
gi,gj=gij.T#gridindices

#Append

#indices.append((b,a,gj.clamp_(0,shape[2]-1),gi.clamp_(0,shape[3]-1)))#image,anchor,grid
#gi.shape=[1659]
#gj.shape=[1659]
gi=gi.clamp(0,shape[3]-1)
gj=gj.clamp(0,shape[2]-1)
#b:imageindexa:anchorindexgj:網(wǎng)格的左上角y坐標(biāo)gi:網(wǎng)格的左上角x坐標(biāo)
indices.append((b,a,gj,gi))#image,anchor,grid
#tbix:xywh其中xy為這個target對當(dāng)前grid_cell左上角的偏移量
tbox.append(flow.cat((gxy-gij,gwh),1))#box
anch.append(anchors[a])#anchors對應(yīng)的所有anchors
tcls.append(c)#class

returntcls,tbox,indices,anch

6.3 __call__函數(shù)

這個函數(shù)相當(dāng)于 forward 函數(shù),在這個函數(shù)中進行損失函數(shù)的前向傳播。

def__call__(self,p,targets):#predictions,targets
"""
這里通過輸入
p:list([16,3,80,80,85],[16,3,40,40,85],[16,3,20,20,85])
targets:targets.shape[314,6]
為例解析__call__函數(shù)

:paramsp:預(yù)測框由模型構(gòu)建中的Detect層返回的三個yolo層的輸出(注意是訓(xùn)練模式才返回三個yolo層的輸出)
tensor格式list列表存放三個tensor對應(yīng)的是三個yolo層的輸出
如:([16,3,80,80,85],[16,3,40,40,85],[16,3,20,20,85])
[bs,anchor_num,grid_h,grid_w,xywh+class+classes]
可以看出來這里的預(yù)測值p是三個yolo層每個grid_cell
的預(yù)測值(每個grid_cell有三個預(yù)測值),后面要進行正樣本篩選
:paramstargets:數(shù)據(jù)增強后的真實框[314,6][num_object,batch_index+class+xywh]
:paramsloss*bs:整個batch的總損失(一個列表)進行反向傳播
:paramsflow.cat((lbox,lobj,lcls,loss)).detach():
回歸損失、置信度損失、分類損失和總損失這個參數(shù)只用來可視化參數(shù)或保存信息
"""
#初始化各個部分損失始化lcls,lbox,lobj三種損失值tensor([0.])
#lcls.shape=[1]
lcls=flow.zeros(1,device=self.device)#classloss
#lbox.shape=[1]
lbox=flow.zeros(1,device=self.device)#boxloss
#lobj.shape=[1]
lobj=flow.zeros(1,device=self.device)#objectloss
#獲得標(biāo)簽分類,邊框,索引,anchors
#每一個都是列表,有featuremap個
#都是當(dāng)前這個featuremap中3個anchor篩選出的所有的target(3個grid_cell進行預(yù)測)
#tcls:表示這個target所屬的classindex
#tbox:xywh其中xy為這個target對當(dāng)前grid_cell左上角的偏移量
#indices:b:表示這個target屬于的imageindex
#a:表示這個target使用的anchorindex
#gj:經(jīng)過篩選后確定某個target在某個網(wǎng)格中進行預(yù)測(計算損失)
#gj表示這個網(wǎng)格的左上角y坐標(biāo)
#gi:表示這個網(wǎng)格的左上角x坐標(biāo)
#anch:表示這個target所使用anchor的尺度(相對于這個featuremap)
#可能一個target會使用大小不同anchor進行計算
"""shape
p:list([16,3,80,80,85],[16,3,40,40,85],[16,3,20,20,85])
targets:[314,6]
tcls:list([1659],[1625],[921])
tbox:list([1659,4],[1625,4],[921,4])
indices:list(list([1659],[1659],[1659],[1659]),list([1625],[1625],[1625],[1625]),list([921],[921],[921],[921]))
anchors:list([1659,2],[1625,2],[921,2])
"""
tcls,tbox,indices,anchors=self.build_targets(p,targets)#targets

#Losses依次遍歷三個featuremap的預(yù)測輸出pi
fori,piinenumerate(p):#layerindex,layerpredictions
#這里通過pi形狀為[16,3,80,80,85]進行解析
"""shape
b:[1659]
a:[1659]
gj:[1659]
gi:[1659]
"""
b,a,gj,gi=indices[i]#image,anchor,gridy,gridx

#tobj=flow.zeros(pi.shape[:4],dtype=pi.dtype,device=self.device)#targetobj
#初始化target置信度(先全是負(fù)樣本后面再篩選正樣本賦值)
#tobj.shape=[16,3,80,80]
tobj=flow.zeros((pi.shape[:4]),dtype=pi.dtype,device=self.device)#targetobj
#n=1659
n=b.shape[0]#numberoftargets
ifn:
#精確得到第b張圖片的第a個featuremap的grid_cell(gi,gj)對應(yīng)的預(yù)測值
#用這個預(yù)測值與我們篩選的這個grid_cell的真實框進行預(yù)測(計算損失)
#pxy,pwh,_,pcls=pi[b,a,gj,gi].tensor_split((2,4,5),dim=1)
"""shape
pxy:[1659,2]
pwh:[1659,2]
_:[1659,1]
pcls:[1659,80]
"""
pxy,pwh,_,pcls=pi[b,a,gj,gi].split((2,2,1,self.nc),1)#target-subsetofpredictions

#Regressionloss只計算所有正樣本的回歸損失
#新的公式:pxy=[-0.5+cx,1.5+cx]pwh=[0,4pw]這個區(qū)域內(nèi)都是正樣本
#Getmorepositivesamples,accelerateconvergenceandbemorestable
#pxy.shape=[1659,2]
pxy=pxy.sigmoid()*2-0.5
#https://github.com/ultralytics/yolov3/issues/168
#pwh.shape=[1659,2]
pwh=(pwh.sigmoid()*2)**2*anchors[i]#和論文里不同這里是作者自己提出的公式
#pbox.shape=[1659,4]
pbox=flow.cat((pxy,pwh),1)#predictedbox
#這里的tbox[i]中的xy是這個target對當(dāng)前grid_cell左上角的偏移量[0,1]而pbox.T是一個歸一化的值
#就是要用這種方式訓(xùn)練傳回loss修改梯度讓pbox越來越接近tbox(偏移量)
#iou.shape=[1659]
iou=bbox_iou(pbox,tbox[i],CIoU=True).squeeze()#iou(prediction,target)
#lbox.shape=[1]
lbox=lbox+(1.0-iou).mean()#iouloss

#Objectness
#iou.detach()不會更新iou梯度iou并不是反向傳播的參數(shù)所以不需要反向傳播梯度信息
#iou.shape=[1659]
iou=iou.detach().clamp(0).type(tobj.dtype)
#這里對iou進行排序再做一個優(yōu)化:當(dāng)一個正樣本出現(xiàn)多個GT的情況也就是同一個grid中有兩個gt(密集型且形狀差不多物體)
#TheremaybeseveralGTsmatchthesameanchorwhencalculateComputeLossinthescenewithdensetargets
ifself.sort_obj_iou:
#https://github.com/ultralytics/yolov5/issues/3605
#TheremaybeseveralGTsmatchthesameanchorwhencalculateComputeLossinthescenewithdensetargets
j=iou.argsort()
#如果同一個grid出現(xiàn)兩個GT那么經(jīng)過排序之后每個grid中的score_iou都能保證是最大的
#(小的會被覆蓋因為同一個grid坐標(biāo)肯定相同)那么從時間順序的話,最后一個總是和最大的iou去計算loss
b,a,gj,gi,iou=b[j],a[j],gj[j],gi[j],iou[j]
#預(yù)測信息有置信度但是真實框信息是沒有置信度的所以需要我們?nèi)藶榈慕o一個標(biāo)準(zhǔn)置信度
#self.gr是iouratio[0,1]self.gr越大置信度越接近iouself.gr越小置信度越接近1(人為加大訓(xùn)練難度)
ifself.gr1:#clsloss(onlyifmultipleclasses)
#targets原本負(fù)樣本是0這里使用smoothlabel就是cn
#t.shape=[1659,80]
t=flow.full_like(pcls,self.cn,device=self.device)#targets

#t[range(n),tcls[i]]=self.cp篩選到的正樣本對應(yīng)位置值是cp

t[flow.arange(n,device=self.device),tcls[i]]=self.cp
#lcls.shape=[1]
lcls=lcls+self.BCEcls(pcls,t)#BCE

#Appendtargetstotextfile
#withopen('targets.txt','a')asfile:
#[file.write('%11.5g'*4%tuple(x)+'
')forxinflow.cat((txy[i],twh[i]),1)]
#置信度損失是用所有樣本(正樣本+負(fù)樣本)一起計算損失的
obji=self.BCEobj(pi[...,4],tobj)
#每個featuremap的置信度損失權(quán)重不同要乘以相應(yīng)的權(quán)重系數(shù)self.balance[i]
#一般來說,檢測小物體的難度大一點,所以會增加大特征圖的損失系數(shù),讓模型更加側(cè)重小物體的檢測
lobj=lobj+(obji*self.balance[i])#objloss

ifself.autobalance:
#自動更新各個featuremap的置信度損失系數(shù)
self.balance[i]=self.balance[i]*0.9999+0.0001/obji.detach().item()

ifself.autobalance:
self.balance=[x/self.balance[self.ssi]forxinself.balance]
#根據(jù)超參中的損失權(quán)重參數(shù)對各個損失進行平衡防止總損失被某個損失主導(dǎo)
"""shape
lbox:[1]
lobj:[1]
lcls:[1]
"""
lbox*=self.hyp["box"]
lobj*=self.hyp["obj"]
lcls*=self.hyp["cls"]
bs=tobj.shape[0]#batchsize

#loss=lbox+lobj+lcls平均每張圖片的總損失
#loss*bs:整個batch的總損失
#.detach()利用損失值進行反向傳播
return(lbox+lobj+lcls)*bs,flow.cat((lbox,lobj,lcls)).detach()

使用:

train.py初始化損失函數(shù)類:

compute_loss = ComputeLoss(model) # init loss class

調(diào)用執(zhí)行損失函數(shù),計算損失:

loss, loss_items = compute_loss(pred, targets.to(device)) # loss scaled by batch_size

總結(jié)

我們認(rèn)為 yolov5/one-yolov5 工程實現(xiàn)最重要的就是 ComputeLoss 類了。但代碼其實還是非常難的,尤其 build_target 里面花里胡哨的矩陣操作和slice操作非常多, pytorch或者oneflow不熟的人會看的比較痛苦,但是如果你堅持看下來我們的注釋再加上自己的冥想,應(yīng)該是能想明白的。






審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • predator
    +關(guān)注

    關(guān)注

    0

    文章

    4

    瀏覽量

    3855

原文標(biāo)題:《YOLOv5全面解析教程》?十二,Loss 計算詳細(xì)解析

文章出處:【微信號:GiantPandaCV,微信公眾號:GiantPandaCV】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    AssetsLibrary框架詳細(xì)解析—— 基本概覽

    AssetsLibrary框架詳細(xì)解析(一) —— 基本概覽
    發(fā)表于 04-29 15:12

    Photos框架詳細(xì)解析

    Photos框架詳細(xì)解析(一) —— 基本概覽
    發(fā)表于 05-06 12:34

    詳細(xì)解析STM32的外部中斷

    說明:本文旨在詳細(xì)解析STM32的外部中斷,以實現(xiàn)按鍵觸發(fā)外部中斷。其中包含“編程流程”、“程序代碼”、“代碼解析”、“原理分析”、“小結(jié)”五部分。一、編程流程要實現(xiàn)STM32外部中斷,按照基本流程
    發(fā)表于 08-13 07:50

    雙積分電路詳細(xì)解析,絕對實用

    雙積分電路詳細(xì)解析,絕對實用
    發(fā)表于 02-28 08:24

    VSWR/Return Loss Conversion Ta

    VSWR/Return Loss Conversion Table Professional Wireless Products
    發(fā)表于 03-25 09:16 ?14次下載

    Return Loss Headromm

    Return Loss Headromm return loss is related to the impedance of a cable.To understand return loss we must first d
    發(fā)表于 03-31 09:57 ?15次下載

    Reduced Termination Loss by Ac

    Reduced Termination Loss by Active Synthesis of Output Impedance Abstract: In high-speed trans
    發(fā)表于 10-06 12:34 ?1293次閱讀
    Reduced Termination <b class='flag-5'>Loss</b> by Ac

    Cable-Loss Solutions

    Cable-Loss Solutions Abstract: This application note describes a method to compensate
    發(fā)表于 12-25 17:01 ?1728次閱讀
    Cable-<b class='flag-5'>Loss</b> Solutions

    Low-Loss LED Driver Improves a

    Low-Loss L
    發(fā)表于 06-27 23:23 ?1621次閱讀
    Low-<b class='flag-5'>Loss</b> LED Driver Improves a

    信噪比與噪聲的詳細(xì)解析

    信噪比與噪聲的詳細(xì)解析。
    發(fā)表于 05-17 11:09 ?12次下載

    斯坦福學(xué)者提出GIoU,目標(biāo)檢測任務(wù)的新Loss

    作者做了一系列的實驗(針對分割任務(wù)和分類任務(wù)有一定 loss 的調(diào)整設(shè)計,不過論文中沒有詳細(xì)給出)結(jié)果是 IoU loss 可以輕微提升使用 MSE 作為 loss 的表現(xiàn),而 GIo
    的頭像 發(fā)表于 03-11 09:09 ?8081次閱讀

    ADMV4530 Board Loss

    ADMV4530 Board Loss
    發(fā)表于 01-30 13:19 ?2次下載
    ADMV4530 Board <b class='flag-5'>Loss</b>

    EDID詳細(xì)解析資料匯總

    EDID詳細(xì)解析資料匯總
    發(fā)表于 09-23 15:30 ?22次下載

    NLP類別不均衡問題之loss大集合

      NLP 任務(wù)中,數(shù)據(jù)類別不均衡問題應(yīng)該是一個極常見又頭疼的的問題了。最近在工作中也是碰到這個問題,花了些時間梳理并實踐了下類別不均衡問題的解決方式,主要實踐了下“魔改”loss(focal loss, GHM loss, d
    的頭像 發(fā)表于 01-31 16:52 ?712次閱讀

    NLP類別不均衡問題之loss合集

    NLP 任務(wù)中,數(shù)據(jù)類別不均衡問題應(yīng)該是一個極常見又頭疼的的問題了。最近在工作中也是碰到這個問題,花了些時間梳理并實踐了下類別不均衡問題的解決方式,主要實踐了下“魔改”loss(focal loss, GHM loss, dic
    的頭像 發(fā)表于 02-23 14:10 ?489次閱讀
    NLP類別不均衡問題之<b class='flag-5'>loss</b>合集