其中一些常見的損失函數(shù)包括:
分類損失(cls_loss):該損失用于判斷模型是否能夠準(zhǔn)確地識(shí)別出圖像中的對(duì)象,并將其分類到正確的類別中。
置信度損失(obj_loss):該損失用于衡量模型預(yù)測(cè)的框(即包含對(duì)象的矩形)與真實(shí)框之間的差異。
邊界框損失(box_loss):該損失用于衡量模型預(yù)測(cè)的邊界框與真實(shí)邊界框之間的差異,這有助于確保模型能夠準(zhǔn)確地定位對(duì)象。
這些損失函數(shù)在訓(xùn)練模型時(shí)被組合使用,以優(yōu)化模型的性能。通過使用這些損失函數(shù),YOLOv5可以準(zhǔn)確地識(shí)別圖像中的對(duì)象,并將其定位到圖像中的具體位置。
1. 導(dǎo)入需要的包
importoneflowasflow importoneflow.nnasnn fromutils.metricsimportbbox_iou fromutils.oneflow_utilsimportde_parallel
2. smooth_BCE
這個(gè)函數(shù)是一個(gè)標(biāo)簽平滑的策略(trick),是一種在 分類/檢測(cè) 問題中,防止過擬合的方法。
如果要詳細(xì)理解這個(gè)策略的原理,請(qǐng)參閱博文:《trick 1》Label Smoothing(標(biāo)簽平滑)—— 分類問題中錯(cuò)誤標(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兩個(gè)值分別代表正樣本和負(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
通常會(huì)用在分類損失當(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
這個(gè)函數(shù)是BCE函數(shù)的一個(gè)替代,是yolov5作者的一個(gè)實(shí)驗(yàn)性的函數(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)就是檢測(cè)成正樣本了但是檢測(cè)錯(cuò)了 """ 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時(shí)(網(wǎng)絡(luò)預(yù)測(cè)說這里有個(gè)obj但是gt說這里沒有),dx=1=>alpha_factor=0=>loss=0 #這種就是檢測(cè)成正樣本了但是檢測(cè)錯(cuò)了(falsepositive)或者missinglabel的情況這種情況不應(yīng)該過多的懲罰->loss=0 dx=pred-true#reduceonlymissinglabeleffects #如果采樣絕對(duì)值的話會(huì)減輕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è)計(jì)的主要思路: 希望那些hard examples對(duì)損失的貢獻(xiàn)變大,使網(wǎng)絡(luò)更傾向于從這些樣本上學(xué)習(xí)。防止由于easy examples過多,主導(dǎo)整個(gè)損失函數(shù)。
優(yōu)點(diǎn):
解決了one-stage object detection中圖片中正負(fù)樣本(前景和背景)不均衡的問題;降低簡(jiǎn)單樣本的權(quán)重,使損失函數(shù)更關(guān)注困難樣本;函數(shù)公式:
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用于削弱簡(jiǎn)單樣本對(duì)loss的貢獻(xiàn)程度 self.alpha=alpha#參數(shù)alpha用于平衡正負(fù)樣本個(gè)數(shù)不均衡的問題 self.reduction=loss_fcn.reduction#self.reduction:控制FocalLoss損失輸出模式sum/mean/none默認(rèn)是Mean #focalloss中的BCE函數(shù)的reduction='None'BCE不使用Sum或者M(jìn)ean #需要將Focalloss應(yīng)用于每一個(gè)樣本之中 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ù)項(xiàng) #返回最終的loss=BCE*兩個(gè)參數(shù)(看看公式就行了和公式一模一樣) loss=loss*alpha_factor*modulating_factor #最后選擇focalloss返回的類型默認(rèn)是mean ifself.reduction=="mean": returnloss.mean() elifself.reduction=="sum": returnloss.sum() else:#'none' returnloss
這個(gè)函數(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 )
6. ComputeLoss類
6.1 __init__函數(shù)
sort_obj_iou=False#后面篩選置信度損失正樣本的時(shí)候是否先對(duì)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)簽值 #請(qǐng)參考: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個(gè)檢測(cè)頭分別對(duì)應(yīng)產(chǎn)生的3個(gè)輸出特征圖 m=de_parallel(model).model[-1]#Detect()module """self.balance用來實(shí)現(xiàn)obj,box,clsloss之間權(quán)重的平衡 {3:[4.0,1.0,0.4]}表示有三個(gè)layer的輸出,第一個(gè)layer的weight是4.0,第二個(gè)1.0,第三個(gè)以此類推。 如果有5個(gè)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 #三個(gè)檢測(cè)頭的下采樣率m.stride:[8,16,32].index(16):求出下采樣率stride=16的索引 #這個(gè)參數(shù)會(huì)用來自動(dòng)計(jì)算更新3個(gè)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每個(gè)grid_cell的anchor數(shù)量=3 self.nc=m.nc#numberofclasses數(shù)據(jù)集的總類別=80 self.nl=m.nl#numberoflayers檢測(cè)頭的個(gè)數(shù)=3 #anchors:形狀[3,3,2]代表3個(gè)featuremap每個(gè)featuremap上有3個(gè)anchor(w,h) #這里的anchors尺寸是相對(duì)featuremap的 self.anchors=m.anchors self.device=device
6.2 build_targets
這個(gè)函數(shù)是用來為所有GT篩選相應(yīng)的anchor正樣本。
篩選條件是比較GT和anchor的寬比和高比,大于一定的閾值就是負(fù)樣本,反之正樣本。
篩選到的正樣本信息(image_index, anchor_index, gridy, gridx),傳入 __call__ 函數(shù),
通過這個(gè)信息去篩選 pred 里每個(gè) grid 預(yù)測(cè)得到的信息,保留對(duì)應(yīng) grid_cell 上的正樣本。
通過 build_targets 篩選的 GT 中的正樣本和 pred 篩選出的對(duì)應(yīng)位置的預(yù)測(cè)樣本 進(jìn)行計(jì)算損失。
補(bǔ)充理解:
這個(gè)函數(shù)的目的是為了每個(gè) GT 匹配對(duì)應(yīng)的高質(zhì)量 Anchor 正樣本參與損失計(jì)算,
j = flow.max(r, 1. / r).max(2)[0] < self.hyp["anchor_t"] 這步的比較是為了將 GT 分配到不同層上去檢測(cè),(詳細(xì)解釋請(qǐng)看下面的逐行代碼注釋)
后面的步驟是為了確定在這層檢測(cè)的 GT 中心坐標(biāo),
進(jìn)而確定這個(gè) GT 在這層哪個(gè) grid cell 進(jìn)行檢測(cè)。
做到這一步也就做到了為每個(gè) GT 匹配 Anchor 正樣本的目的。
#--------------------------------------------------------- #build_targets函數(shù)用于獲得在訓(xùn)練時(shí)計(jì)算loss所需要的目標(biāo)框,也即正樣本。與yolov3/v4的不同,yolov5支持跨網(wǎng)格預(yù)測(cè)。 #對(duì)于任何一個(gè)GTbbox,三個(gè)預(yù)測(cè)特征層上都可能有先驗(yàn)框匹配,所以該函數(shù)輸出的正樣本框比傳入的targets(GT框)數(shù)目多 #具體處理過程: #(1)首先通過bbox與當(dāng)前層anchor做一遍過濾。對(duì)于任何一層計(jì)算當(dāng)前bbox與當(dāng)前層anchor的匹配程度,不采用IoU,而采用shape比例。如果anchor與bbox的寬高比差距大于4,則認(rèn)為不匹配,此時(shí)忽略相應(yīng)的bbox,即當(dāng)做背景; #(2)根據(jù)留下的bbox,在上下左右四個(gè)網(wǎng)格四個(gè)方向擴(kuò)增采樣(即對(duì)bbox計(jì)算落在的網(wǎng)格所有anchors都計(jì)算loss(并不是直接和GT框比較計(jì)算loss)) #注意此時(shí)落在網(wǎng)格不再是一個(gè),而是附近的多個(gè),這樣就增加了正樣本數(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]的作用只是得到每個(gè)featuremap的shape 預(yù)測(cè)框由模型構(gòu)建中的三個(gè)檢測(cè)頭Detector返回的三個(gè)yolo層的輸出 tensor格式list列表存放三個(gè)tensor對(duì)應(yīng)的是三個(gè)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ù)測(cè)值p是三個(gè)yolo層每個(gè)grid_cell(每個(gè)grid_cell有三個(gè)預(yù)測(cè)值)的預(yù)測(cè)值,后面肯定要進(jìn)行正樣本篩選 :paramstargets:數(shù)據(jù)增強(qiáng)后的真實(shí)框[63,6][num_target,image_index+class+xywh]xywh為歸一化后的框 :returntcls:表示這個(gè)target所屬的classindex tbox:xywh其中xy為這個(gè)target對(duì)當(dāng)前grid_cell左上角的偏移量 indices:b:表示這個(gè)target屬于的imageindex a:表示這個(gè)target使用的anchorindex gj:經(jīng)過篩選后確定某個(gè)target在某個(gè)網(wǎng)格中進(jìn)行預(yù)測(cè)(計(jì)算損失)gj表示這個(gè)網(wǎng)格的左上角y坐標(biāo) gi:表示這個(gè)網(wǎng)格的左上角x坐標(biāo) anch:表示這個(gè)target所使用anchor的尺度(相對(duì)于這個(gè)featuremap)注意可能一個(gè)target會(huì)使用大小不同anchor進(jìn)行計(jì)算 """ #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)前層的哪個(gè)anchor匹配 #需要在3個(gè)anchor上都進(jìn)行訓(xùn)練所以將標(biāo)簽賦值na=3個(gè) #ai代表3個(gè)anchor上在所有的target對(duì)應(yīng)的anchor索引就是用來標(biāo)記下當(dāng)前這個(gè)target屬于哪個(gè)anchor #[1,3]->[3,1]->[3,314]=[na,nt]三行第一行63個(gè)0第二行63個(gè)1第三行63個(gè)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] #對(duì)每一個(gè)featuremap:這一步是將target復(fù)制三份對(duì)應(yīng)一個(gè)featuremap的三個(gè)anchor #先假設(shè)所有的target都由這層的三個(gè)anchor進(jìn)行檢測(cè)(復(fù)制三份)再進(jìn)行篩選并將ai加進(jìn)去標(biāo)記當(dāng)前是哪個(gè)anchor的target #targets.shape=[3,314,7] targets=flow.cat((targets.repeat(na,1,1),ai[...,None]),2)#appendanchorindices #這兩個(gè)變量是用來擴(kuò)展正樣本的因?yàn)轭A(yù)測(cè)框預(yù)測(cè)到target有可能不止當(dāng)前的格子預(yù)測(cè)到了 #可能周圍的格子也預(yù)測(cè)到了高質(zhì)量的樣本我們也要把這部分的預(yù)測(cè)信息加入正樣本中 #設(shè)置網(wǎng)格中心偏移量 g=0.5#bias #附近的4個(gè)框 #以自身+周圍左上右下4個(gè)網(wǎng)格=5個(gè)網(wǎng)格用來計(jì)算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 #對(duì)每個(gè)檢測(cè)層進(jìn)行處理 #遍歷三個(gè)feature篩選gt的anchor正樣本 foriinrange(self.nl):#self.nl:numberofdetectionlayersDetect的個(gè)數(shù)=3 #anchors:當(dāng)前featuremap對(duì)應(yīng)的三個(gè)anchor尺寸(相對(duì)featuremap)[3,2] anchors,shape=self.anchors[i],p[i].shape #gain:保存每個(gè)輸出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的歸一化尺度放縮到相對(duì)當(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)前層的三個(gè)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中最大的那個(gè) #.max(2)返回寬比高比兩者中較大的一個(gè)值和它的索引[0]返回較大的一個(gè)值 #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)屬于哪張圖片正樣本對(duì)應(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個(gè)離target中心最近的兩個(gè)格子 #可能周圍的格子也預(yù)測(cè)到了高質(zhì)量的樣本我們也要把這部分的預(yù)測(cè)信息加入正樣本中 #除了target所在的當(dāng)前格子外,還有2個(gè)格子對(duì)目標(biāo)進(jìn)行檢測(cè)(計(jì)算損失) #也就是說一個(gè)目標(biāo)需要3個(gè)格子去預(yù)測(cè)(計(jì)算損失) #首先當(dāng)前格子是其中1個(gè)再從當(dāng)前格子的上下左右四個(gè)格子中選擇2個(gè) #用這三個(gè)格子去預(yù)測(cè)這個(gè)目標(biāo)(計(jì)算損失) #featuremap上的原點(diǎn)在左上角向右為x軸正坐標(biāo)向下為y軸正坐標(biāo) #gridxy取target中心的坐標(biāo)xy(相對(duì)featuremap左上角的坐標(biāo)) #gxy.shape=[555,2] gxy=t[:,2:4]#gridxy #inverse得到target中心點(diǎn)相對(duì)于右下角的坐標(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)不能在邊上此時(shí)就沒有4個(gè)格子了) #j:[555]bool如果是True表示當(dāng)前target中心點(diǎn)所在的格子的左邊格子也對(duì)該target進(jìn)行回歸(后續(xù)進(jìn)行計(jì)算損失) #k:[555]bool如果是True表示當(dāng)前target中心點(diǎn)所在的格子的上邊格子也對(duì)該target進(jìn)行回歸(后續(xù)進(jìn)行計(jì)算損失) j,k=((gxy%11)).T #篩選中心坐標(biāo)距離當(dāng)前grid_cell的右、下方偏移小于g=0.5且中心坐標(biāo)必須大于1(坐標(biāo)不能在邊上此時(shí)就沒有4個(gè)格子了) #l:[555]bool如果是True表示當(dāng)前target中心點(diǎn)所在的格子的右邊格子也對(duì)該target進(jìn)行回歸(后續(xù)進(jìn)行計(jì)算損失) #m:[555]bool如果是True表示當(dāng)前target中心點(diǎn)所在的格子的下邊格子也對(duì)該target進(jìn)行回歸(后續(xù)進(jìn)行計(jì)算損失) l,m=((gxi%11)).T #j.shape=[5,555] j=flow.stack((flow.ones_like(j),j,k,l,m)) #得到篩選后所有格子的正樣本格子數(shù)<=3*555?都不在邊上等號(hào)成立 ????????????????#?t:?[555,?7]?->復(fù)制5份target[5,555,7]分別對(duì)應(yīng)當(dāng)前格子和左上右下格子5個(gè)格子 #使用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)格的中心相對(duì)于這個(gè)要預(yù)測(cè)的真實(shí)框所在網(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ù)測(cè)真實(shí)框的網(wǎng)格所在的左上角坐標(biāo)(有左上右下的網(wǎng)格) #gij.shape=[1659,2] gij=(gxy-offsets).contiguous().long() #這里的拆分我們可以用下面的示例代碼來進(jìn)行解釋: #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為這個(gè)target對(duì)當(dāng)前grid_cell左上角的偏移量 tbox.append(flow.cat((gxy-gij,gwh),1))#box anch.append(anchors[a])#anchors對(duì)應(yīng)的所有anchors tcls.append(c)#class returntcls,tbox,indices,anch
6.3 __call__函數(shù)
這個(gè)函數(shù)相當(dāng)于 forward 函數(shù),在這個(gè)函數(shù)中進(jìn)行損失函數(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ù)測(cè)框由模型構(gòu)建中的Detect層返回的三個(gè)yolo層的輸出(注意是訓(xùn)練模式才返回三個(gè)yolo層的輸出) tensor格式list列表存放三個(gè)tensor對(duì)應(yīng)的是三個(gè)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ù)測(cè)值p是三個(gè)yolo層每個(gè)grid_cell 的預(yù)測(cè)值(每個(gè)grid_cell有三個(gè)預(yù)測(cè)值),后面要進(jìn)行正樣本篩選 :paramstargets:數(shù)據(jù)增強(qiáng)后的真實(shí)框[314,6][num_object,batch_index+class+xywh] :paramsloss*bs:整個(gè)batch的總損失(一個(gè)列表)進(jìn)行反向傳播 :paramsflow.cat((lbox,lobj,lcls,loss)).detach(): 回歸損失、置信度損失、分類損失和總損失這個(gè)參數(shù)只用來可視化參數(shù)或保存信息 """ #初始化各個(gè)部分損失始化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 #每一個(gè)都是列表,有featuremap個(gè) #都是當(dāng)前這個(gè)featuremap中3個(gè)anchor篩選出的所有的target(3個(gè)grid_cell進(jìn)行預(yù)測(cè)) #tcls:表示這個(gè)target所屬的classindex #tbox:xywh其中xy為這個(gè)target對(duì)當(dāng)前grid_cell左上角的偏移量 #indices:b:表示這個(gè)target屬于的imageindex #a:表示這個(gè)target使用的anchorindex #gj:經(jīng)過篩選后確定某個(gè)target在某個(gè)網(wǎng)格中進(jìn)行預(yù)測(cè)(計(jì)算損失) #gj表示這個(gè)網(wǎng)格的左上角y坐標(biāo) #gi:表示這個(gè)網(wǎng)格的左上角x坐標(biāo) #anch:表示這個(gè)target所使用anchor的尺度(相對(duì)于這個(gè)featuremap) #可能一個(gè)target會(huì)使用大小不同anchor進(jìn)行計(jì)算 """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依次遍歷三個(gè)featuremap的預(yù)測(cè)輸出pi fori,piinenumerate(p):#layerindex,layerpredictions #這里通過pi形狀為[16,3,80,80,85]進(jìn)行解析 """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個(gè)featuremap的grid_cell(gi,gj)對(duì)應(yīng)的預(yù)測(cè)值 #用這個(gè)預(yù)測(cè)值與我們篩選的這個(gè)grid_cell的真實(shí)框進(jìn)行預(yù)測(cè)(計(jì)算損失) #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只計(jì)算所有正樣本的回歸損失 #新的公式:pxy=[-0.5+cx,1.5+cx]pwh=[0,4pw]這個(gè)區(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是這個(gè)target對(duì)當(dāng)前grid_cell左上角的偏移量[0,1]而pbox.T是一個(gè)歸一化的值 #就是要用這種方式訓(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()不會(huì)更新iou梯度iou并不是反向傳播的參數(shù)所以不需要反向傳播梯度信息 #iou.shape=[1659] iou=iou.detach().clamp(0).type(tobj.dtype) #這里對(duì)iou進(jìn)行排序再做一個(gè)優(yōu)化:當(dāng)一個(gè)正樣本出現(xiàn)多個(gè)GT的情況也就是同一個(gè)grid中有兩個(gè)gt(密集型且形狀差不多物體) #TheremaybeseveralGTsmatchthesameanchorwhencalculateComputeLossinthescenewithdensetargets ifself.sort_obj_iou: #https://github.com/ultralytics/yolov5/issues/3605 #TheremaybeseveralGTsmatchthesameanchorwhencalculateComputeLossinthescenewithdensetargets j=iou.argsort() #如果同一個(gè)grid出現(xiàn)兩個(gè)GT那么經(jīng)過排序之后每個(gè)grid中的score_iou都能保證是最大的 #(小的會(huì)被覆蓋因?yàn)橥粋€(gè)grid坐標(biāo)肯定相同)那么從時(shí)間順序的話,最后一個(gè)總是和最大的iou去計(jì)算loss b,a,gj,gi,iou=b[j],a[j],gj[j],gi[j],iou[j] #預(yù)測(cè)信息有置信度但是真實(shí)框信息是沒有置信度的所以需要我們?nèi)藶榈慕o一個(gè)標(biāo)準(zhǔn)置信度 #self.gr是iouratio[0,1]self.gr越大置信度越接近iouself.gr越小置信度越接近1(人為加大訓(xùn)練難度) ifself.gr1: ????????????????????iou?=?(1.0?-?self.gr)?+?self.gr?*?iou ????????????????tobj[b,?a,?gj,?gi]?=?iou??#?iou?ratio ????????????????#?Classification?只計(jì)算所有正樣本的分類損失? ????????????????#?self.nc?=?80 ????????????????if?self.nc?>1:#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篩選到的正樣本對(duì)應(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ù)樣本)一起計(jì)算損失的 obji=self.BCEobj(pi[...,4],tobj) #每個(gè)featuremap的置信度損失權(quán)重不同要乘以相應(yīng)的權(quán)重系數(shù)self.balance[i] #一般來說,檢測(cè)小物體的難度大一點(diǎn),所以會(huì)增加大特征圖的損失系數(shù),讓模型更加側(cè)重小物體的檢測(cè) lobj=lobj+(obji*self.balance[i])#objloss ifself.autobalance: #自動(dòng)更新各個(gè)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ù)對(duì)各個(gè)損失進(jìn)行平衡防止總損失被某個(gè)損失主導(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:整個(gè)batch的總損失 #.detach()利用損失值進(jìn)行反向傳播 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ù),計(jì)算損失:
loss, loss_items = compute_loss(pred, targets.to(device)) # loss scaled by batch_size
總結(jié)
我們認(rèn)為 yolov5/one-yolov5 工程實(shí)現(xiàn)最重要的就是 ComputeLoss 類了。但代碼其實(shí)還是非常難的,尤其 build_target 里面花里胡哨的矩陣操作和slice操作非常多, pytorch或者oneflow不熟的人會(huì)看的比較痛苦,但是如果你堅(jiān)持看下來我們的注釋再加上自己的冥想,應(yīng)該是能想明白的。
審核編輯:劉清
-
predator
+關(guān)注
關(guān)注
0文章
4瀏覽量
3864
原文標(biāo)題:《YOLOv5全面解析教程》?十二,Loss 計(jì)算詳細(xì)解析
文章出處:【微信號(hào):GiantPandaCV,微信公眾號(hào):GiantPandaCV】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論