国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

yolov5損失函數(shù)詳解【附代碼】

這篇具有很好參考價值的文章主要介紹了yolov5損失函數(shù)詳解【附代碼】。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

本文章將結合代碼對yolov5損失函數(shù)部分進行詳細說明,包含其中的樣本匹配問題。如果還需要學習關于yolov5其他部分內容,可以參考我其他文章。

yolov5語義分割:

????????????????yolov5圖像分割中的NMS處理

????????????????yolov5圖像分割Segmentation函數(shù)

yolov5 trt 學習:

????????????????yolov5 trt推理【python版】

yolov5剪枝:

????????????????yolov5剪枝

通過yaml修改yolov5:

????????????????通過yaml修改yolov5網絡

iou樣本匹配:

????????????????以yolov4 loss為例講解iou樣本匹配

????????????????NMS預測框的篩選


目錄

?ComputeLoss

?樣本匹配--build_targets函數(shù)

box_loss

?cls_loss

obj_loss?


下面的3個feature_map是仿照v5的head隨機產生的輸出。為了方便后面代碼講解,這里我設置的num_classes為1?。

feature_map1 = torch.rand([batch_size, 3, 80, 80, 5 + num_classes])
feature_map2 = torch.rand([batch_size, 3, 40, 40, 5 + num_classes])
feature_map3 = torch.rand([batch_size, 3, 20, 20, 5 + num_classes])
pred = [feature_map1, feature_map2, feature_map3]

而標簽target如下【也就是通過dataloader處理后的數(shù)據(jù)集】

?我這里的target也是我隨便舉例的,可以看到他的shape為[3,6],也就是【num_obj, batch_idex+classes+xywh】。這里的num_obj表示當前圖像中有出現(xiàn)了幾個目標,batch_idex是第幾個圖像或者說第幾個batch的索引,比如我這里batch是2,但這個是第一張圖像的target信息,class表示當前目標是什么類【注意和num_classes區(qū)分】,后面的xywh就是box信息。

targets = torch.tensor([[0.00000, 0.00000, 0.04204, 0.21125, 0.08408, 0.36503],
                        [0.00000, 0.00000, 0.14960, 0.24400, 0.23867, 0.36503],
                        [0.00000, 0.00000, 0.36253, 0.24517, 0.21995, 0.39545]])

?ComputeLoss

下面這一部分是computeLoss定義的初始化參數(shù)。

yolo系列的損失函數(shù)通常為三個部分。cls_loss[分類],obj_loss[置信度loss],loc_loss[box loss].

前兩者從代碼中可以看到采用二分類交叉熵?!具@里不說focalLoss】

self.balance可以理解為三個head部分的權重,即分配給小中大目標的權重[80*80head預測小目標,40*40預測中目標,20*20預測小目標]。

na:表示anchors的數(shù)量

nc:num_classes

nl:head的數(shù)量

class ComputeLoss:
    sort_obj_iou = False

    # Compute losses
    def __init__(self, model, autobalance=False):
        device = next(model.parameters()).device  # get model device
        h = model.hyp  # 獲取超參數(shù)

        # 損失函數(shù)定義,cls:二分類交叉熵, obj:二分類交叉熵
        BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['cls_pw']], device=device))
        BCEobj = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['obj_pw']], device=device))

        # Class label smoothing https://arxiv.org/pdf/1902.04103.pdf eqn 3
        self.cp, self.cn = smooth_BCE(eps=h.get('label_smoothing', 0.0))  # positive, negative BCE targets

        # Focal loss
        g = h['fl_gamma']  # focal loss gamma
        if g > 0:
            BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)

        m = de_parallel(model).model[-1]  # Detect() module
        # balance用于判斷是否輸出為3層,如果是返回[4.0, 1.0, 0.4], 否則返回[4, 1, 0.25, 0.06, 0.02]  這些value值是給小中大目標Head給的權重
        self.balance = {3: [4.0, 1.0, 0.4]}.get(m.nl, [4.0, 1.0, 0.25, 0.06, 0.02])  # P3-P7
        self.ssi = list(m.stride).index(16) if autobalance else 0  # stride 16 index 返回步長為16的索引
        self.BCEcls, self.BCEobj, self.gr, self.hyp, self.autobalance = BCEcls, BCEobj, 1.0, h, autobalance
        self.na = m.na  # number of anchors
        self.nc = m.nc  # number of classes
        self.nl = m.nl  # number of layers
        self.anchors = m.anchors
        self.device = device

?樣本匹配--build_targets函數(shù)

?樣本的匹配需要看build_targets函數(shù)。函數(shù)中的p是三個預測層,targets數(shù)據(jù)集的真實值。

tcls,tbox,indices,anch是用來存放匹配結果。

gain是用來后面target縮放到特征層上。

    def build_targets(self, p, targets):
        # Build targets for compute_loss(), input targets(image,class,x,y,w,h)
        na, nt = self.na, targets.shape[0]  # number of anchors, targets.  anchor數(shù)量, target數(shù)量
        tcls, tbox, indices, anch = [], [], [], []
        gain = torch.ones(7, device=self.device)  # normalized to gridspace gain

ai可以理解為anchor的索引,我們知道yolov5有3個head,每個head上3種anchor,因此這里就相當于給每個head上的anchor編號為0,1,2.通過view將其以列的形式排列,利用repeat函數(shù)復制nt列[這里的nt就是target中的目標數(shù)].?

ai = torch.arange(na, device=self.device).float().view(na, 1).repeat(1, nt)
ai tensor([[0., 0., 0.],
           [1., 1., 1.],
           [2., 2., 2.],
        ...,
                       ])
行數(shù)等于target數(shù)量,shape is [target[0], 1]

?通過將targets和前面的ai進行cat拼接操作,那么就可以相當于給每個目標都分配了anchor的索引。

targets = torch.cat((targets.repeat(na, 1, 1), ai[..., None]), 2)  # append anchor indices

可以看到給target中的三個目標均分配了0,1,2的索引【后面會利用這個索引來具體看樣本和哪個anchor是匹配的】

給每個target分配anchor索引

tensor([[[0.00000, 0.00000, 0.04204, 0.21125, 0.08408, 0.36503, 0.00000],
? ? ? ? ? ? [0.00000, 0.00000, 0.14960, 0.24400, 0.23867, 0.36503, 0.00000],
? ? ? ? ? ? [0.00000, 0.00000, 0.36253, 0.24517, 0.21995, 0.39545, 0.00000]],

? ? ? ? [? ?[0.00000, 0.00000, 0.04204, 0.21125, 0.08408, 0.36503, 1.00000],
? ? ? ? ? ? [0.00000, 0.00000, 0.14960, 0.24400, 0.23867, 0.36503, 1.00000],
? ? ? ? ? ? [0.00000, 0.00000, 0.36253, 0.24517, 0.21995, 0.39545, 1.00000]],

? ? ? ? [? ?[0.00000, 0.00000, 0.04204, 0.21125, 0.08408, 0.36503, 2.00000],
? ? ? ? ? ? [0.00000, 0.00000, 0.14960, 0.24400, 0.23867, 0.36503, 2.00000],
? ? ? ? ? ? [0.00000, 0.00000, 0.36253, 0.24517, 0.21995, 0.39545, 2.00000]]])

targets:[num_obj, 6],repeat(3,1,1)表示復制3個1行1列,則repeat后 targets shape變?yōu)閇3,num_obj,6]
ai = [[[0.],
      [0.],
      [0.]..]
      
      [[1.],
      [1.],
      [1.]...]
      
      [[2.],
      [2.]
      [2.]...]]
      ai[...,None] shape [3, num_obj, 1]
cat后targets shape [3, num_obj,6+1]=[3,num_obj,7]
也就是targets[...,:6]保存的是targets信息,targets[...,6:]保存的是對于anchors索引

?接下來是遍歷三個head進行target和anchor的匹配來確定正樣本。

比如在遍歷第一個head的時候shape為【batch,3,80,80,5+num_classes】.

那么此時的gain通過下面操作將變?yōu)椤?,1,80,80,80,80,1】。

        for i in range(self.nl):
            anchors, shape = self.anchors[i], p[i].shape
            # torch.tensor(shape)=[batch,3,80,80,85]
            gain[2:6] = torch.tensor(shape)[[3, 2, 3, 2]]  # xyxy gain  獲取特征層的w和h[80,80,80,80]

大家可以回看target中的box信息,可以看到這些最初的box信息值范圍是0~1的,但此時我們的特征層head w和h是80*80,這肯定是不匹配的,所以可以通過targets * gain將這些Box縮放到特征層上得到真實的尺寸。?

# Match targets to anchors  先驗框和target框的匹配
t = targets * gain  # shape(3,n,7)  將targets中的box縮放到特征層上

那么此時得到的t如下??梢钥吹絙ox信息縮放到80*80特征層上具體是多少了。?

tensor([[[ 0.00000, ?0.00000, ?3.36320, 16.90000, ?6.72640, 29.20240, ?0.00000],
? ? ? ? ?[ 0.00000, ?0.00000, 11.96800, 19.52000, 19.09360, 29.20240, ?0.00000],
? ? ? ? ?[ 0.00000, ?0.00000, 29.00240, 19.61360, 17.59600, 31.63600, ?0.00000]],

? ? ? ? [[ 0.00000, ?0.00000, ?3.36320, 16.90000, ?6.72640, 29.20240, ?1.00000],
? ? ? ? ?[ 0.00000, ?0.00000, 11.96800, 19.52000, 19.09360, 29.20240, ?1.00000],
? ? ? ? ?[ 0.00000, ?0.00000, 29.00240, 19.61360, 17.59600, 31.63600, ?1.00000]],

? ? ? ? [[ 0.00000, ?0.00000, ?3.36320, 16.90000, ?6.72640, 29.20240, ?2.00000],
? ? ? ? ?[ 0.00000, ?0.00000, 11.96800, 19.52000, 19.09360, 29.20240, ?2.00000],
? ? ? ? ?[ 0.00000, ?0.00000, 29.00240, 19.61360, 17.59600, 31.63600, ?2.00000]]])

yolov5中的樣本匹配與之前yolov4或者SSD的樣本匹配不同,yolov5采用的是寬高比例的匹配策略,不同于iou匹配。

具體做法是:(1)target的寬高與anchor的寬高比得到ratio1【對應代碼中的r】

? ? ? ? ? ? ? ? ? ? ? (2)anchor的寬高與target的寬高比得到ratio1【對應代碼中的1/r】

? ? ? ? ? ? ? ? ? ? ? (3)取上面兩個比值的最大值與閾值比較【默認為4對應代碼中anchor_t】,如果小于該閾值那么認為匹配成功。

r = t[..., 4:6] / anchors[:, None]  # wh ratio
j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t']  # compare

此時得到的j【也就是和閾值相比較】均為False,就是表示沒有匹配成功,也就是可以認為,此時的target中所有的目標不能被80*80的head所預測【或者說這些目標中沒有小目標】

tensor([[False, False, False],
? ? ? ? [False, False, False],
? ? ? ? [False, False, False]])

當遍歷到第二個head,也就是大小為40*40的head上時。target也需要縮放到40*40上面,此時為:

tensor([[[ 0.00000, ?0.00000, ?1.68160, ?8.45000, ?3.36320, 14.60120, ?0.00000],
? ? ? ? ? ? ?[ 0.00000, ?0.00000, ?5.98400, ?9.76000, ?9.54680, 14.60120, ?0.00000],
? ? ? ? ? ? ?[ 0.00000, ?0.00000, 14.50120, ?9.80680, ?8.79800, 15.81800, ?0.00000]],

? ? ? ? [? ? [ 0.00000, ?0.00000, ?1.68160, ?8.45000, ?3.36320, 14.60120, ?1.00000],
? ? ? ? ? ? ?[ 0.00000, ?0.00000, ?5.98400, ?9.76000, ?9.54680, 14.60120, ?1.00000],
? ? ? ? ? ? ?[ 0.00000, ?0.00000, 14.50120, ?9.80680, ?8.79800, 15.81800, ?1.00000]],

? ? ? ? [? ? [ 0.00000, ?0.00000, ?1.68160, ?8.45000, ?3.36320, 14.60120, ?2.00000],
? ? ? ? ? ? ?[ 0.00000, ?0.00000, ?5.98400, ?9.76000, ?9.54680, 14.60120, ?2.00000],
? ? ? ? ? ? ?[ 0.00000, ?0.00000, 14.50120, ?9.80680, ?8.79800, 15.81800, ?2.00000]]])

此時通過寬高比閾值篩選得到:也就是可以知道target中目標可以被40*40這個head的第0與第2號anchor所匹配,這些anchor所匹配的樣本就是我們要的正樣本GT.

tensor([[ True, False, False],
? ? ? ? ? ?[False, False, False],
? ? ? ? ? ?[ True, ?True, ?True]])?

通過上面的mask過濾一下targets.

?tensor([[ 0.00000, ?0.00000, ?1.68160, ?8.45000, ?3.36320, 14.60120, ?0.00000],
? ? ? ? ? ?[ 0.00000, ?0.00000, ?1.68160, ?8.45000, ?3.36320, 14.60120, ?2.00000],
? ? ? ? ? [ 0.00000, ?0.00000, ?5.98400, ?9.76000, ?9.54680, 14.60120, ?2.00000],
? ? ? ? ? ?[ 0.00000, ?0.00000, 14.50120, ?9.80680, ?8.79800, 15.81800, ?2.00000]])

得到過濾后目標的中心點坐標?

gxy = t[:, 2:4]  # grid xy

?得到中心點相對于邊界的距離

gxi = gain[[2, 3]] - gxy

jk和lm是判斷gxy的中心點更偏向哪里.?

j, k = ((gxy % 1 < g) & (gxy > 1)).T
l, m = ((gxi % 1 < g) & (gxi > 1)).T
j = torch.stack((torch.ones_like(j), j, k, l, m))

????????得到的j如下,包含當前網格有五個cell,第一行保留所有的gtbox,第二行表示左邊的cell中的gt,第三行是表示上方的cell中的gt,第四行是右邊cell的網格,第五行是下方的cell中的gt。這里與v3和v4不同在于之前的yolo是目標落在哪個head的cell就由該cell進行預測,而v5通過增加鄰近的cell來預測,這樣就是相當于增加了正樣本的數(shù)量。

tensor([[ True, ?True, ?True, ?True],
? ? ? ? [False, False, False, False],
? ? ? ? [ True, ?True, False, False],
? ? ? ? [ True, ?True, ?True, ?True],
? ? ? ? [False, False, ?True, ?True]])?

?

在yolov5中不僅僅用了中心點進行預測,還采用了距離中心點網格最近的兩個網格,所以是有五種情況【四周的網格和當前中心的網格】同時用上面的j過濾,這樣就可以得出哪些網格有目標

t = t.repeat((5, 1, 1))[j]
offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]
    def build_targets(self, p, targets):
        # Build targets for compute_loss(), input targets(image,class,x,y,w,h)
        na, nt = self.na, targets.shape[0]  # number of anchors, targets.  anchor數(shù)量, target數(shù)量
        tcls, tbox, indices, anch = [], [], [], []
        gain = torch.ones(7, device=self.device)  # normalized to gridspace gain
        '''
        ai tensor([[0., 0., 0.],
                [1., 1., 1.],
                [2., 2., 2.],
                ...,
                ])
        行數(shù)等于target數(shù)量,shape is [target[0], 1]
        '''
        ai = torch.arange(na, device=self.device).float().view(na, 1).repeat(1, nt)  # same as .repeat_interleave(nt)
        '''
        targets:[num_obj, 6],repeat(3,1,1)表示復制3個1行1列,則repeat后 targets shape變?yōu)閇3,num_obj,6]
        ai = [[[0.],
              [0.],
              [0.]..]
              
              [[1.],
              [1.],
              [1.]...]
              
              [[2.],
              [2.]
              [2.]...]]
              ai[...,None] shape [3, num_obj, 1]
        cat后targets shape [3, num_obj,6+1]=[3,num_obj,7]
        也就是targets[...,:6]保存的是targets信息,targets[...,6:]保存的是對于anchors索引
        '''
        targets = torch.cat((targets.repeat(na, 1, 1), ai[..., None]), 2)  # append anchor indices

        g = 0.5  # bias
        off = torch.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

        for i in range(self.nl):
            anchors, shape = self.anchors[i], p[i].shape
            # torch.tensor(shape)=[batch,3,80,80,85]
            gain[2:6] = torch.tensor(shape)[[3, 2, 3, 2]]  # xyxy gain  獲取特征層的w和h[80,80,80,80]

            # Match targets to anchors  先驗框和target框的匹配
            t = targets * gain  # shape(3,n,7)  將targets中的box縮放到特征層上
            if nt:
                # Matches
                '''
                yolov5采用寬高比例的匹配策略,不同于iou匹配。
                target的寬高與anchors寬高對應相除得到ratio1
                anchors與target的寬高相處得到ratio2[也就是代碼中的1/r]
                取兩個ratio最大值作為最后的寬高比,該寬高比和設定的閾值(默認為4[anchor_t])比較,小于該閾值的anchor則為匹配到的anchor
                '''
                r = t[..., 4:6] / anchors[:, None]  # wh ratio
                j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t']  # compare
                # j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t']  # iou(3,n)=wh_iou(anchors(3,2), gwh(n,2))
                t = t[j]  # filter

                # Offsets
                gxy = t[:, 2:4]  # grid xy
                gxi = gain[[2, 3]] - gxy  # inverse
                j, k = ((gxy % 1 < g) & (gxy > 1)).T
                l, m = ((gxi % 1 < g) & (gxi > 1)).T
                j = torch.stack((torch.ones_like(j), j, k, l, m))
                t = t.repeat((5, 1, 1))[j]
                offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]
            else:
                t = targets[0]
                offsets = 0

            # Define
            bc, gxy, gwh, a = t.chunk(4, 1)  # (image, class), grid xy, grid wh, anchors
            a, (b, c) = a.long().view(-1), bc.long().T  # anchors, image, class
            gij = (gxy - offsets).long()
            gi, gj = gij.T  # grid indices

            # Append
            indices.append((b, a, gj.clamp_(0, shape[2] - 1), gi.clamp_(0, shape[3] - 1)))  # image, anchor, grid
            tbox.append(torch.cat((gxy - gij, gwh), 1))  # box
            anch.append(anchors[a])  # anchors
            tcls.append(c)  # class

        return tcls, tbox, indices, anch

通過上面的樣本匹配操作我們會得到4個值。

tcls:存儲target中的類別,tbox:gt中的box信息,indices:當前gtbox屬于第幾張圖像,gtbox與anchor的對應關系以及所屬的cell坐標。anchors:anchor信息

獲得b:圖像;a:anchor, gj:gi? Cell的縱坐標與橫坐標

b, a, gj, gi = indices[i]

tobj是用來后面存儲gt中的目標信息,shape[batch_size,3, 80,80]?

tobj = torch.zeros(pi.shape[:4], dtype=pi.dtype, device=self.device)

如果目標存在,獲取當前head所預測的中心坐標pxy,pwh,以及類置信度?

pxy, pwh, _, pcls = pi[b, a, gj, gi].split((2, 2, 1, self.nc), 1)  # target-subset of predictions

box_loss

利用iou獲取box_loss。?

                # Regression
                pxy = pxy.sigmoid() * 2 - 0.5
                pwh = (pwh.sigmoid() * 2) ** 2 * anchors[i]
                pbox = torch.cat((pxy, pwh), 1)  # predicted box
                iou = bbox_iou(pbox, tbox[i], CIoU=True).squeeze()  # iou(prediction, target)
                lbox += (1.0 - iou).mean()  # iou loss

?cls_loss

                if self.nc > 1:  # cls loss (only if multiple classes)
                    t = torch.full_like(pcls, self.cn, device=self.device)  # targets
                    t[range(n), tcls[i]] = self.cp
                    lcls += self.BCEcls(pcls, t)  # BCE

obj_loss?

pi是指當前head層,pi[...,4]即取出conf這一維度與tobj做交叉熵。

obji = self.BCEobj(pi[..., 4], tobj)

再與所對應的權重相乘,得到obj_loss?

lobj += obji * self.balance[i]

最后返回Loss?

        lbox *= self.hyp['box']
        lobj *= self.hyp['obj']
        lcls *= self.hyp['cls']
        bs = tobj.shape[0]  # batch size

        return (lbox + lobj + lcls) * bs, torch.cat((lbox, lobj, lcls)).detach()

loss: ?tensor([10.73431]) ?loss_item: ?tensor([0.06105, 5.30610, 0.00000])?文章來源地址http://www.zghlxwxcb.cn/news/detail-785933.html

到了這里,關于yolov5損失函數(shù)詳解【附代碼】的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯(lián)網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包