Aunque un poco desactualizado, si implementa YOLOv1 usted mismo, definitivamente obtendrá algo (2) Función de pérdida

Se puede imaginar la importancia del diseño de la función de pérdida: diseñar una buena función objetivo no solo puede entrenar un modelo más preciso, sino también acelerar el entrenamiento del modelo. Al comienzo de la detección de objetivos por contacto, un gran desafío para mí es comprender la función de pérdida, que es bastante engorrosa. Echemos un breve vistazo al diseño de la función de pérdida de YOLOv1.

yo C O O r d i = 0 S 2 j = 0 B 1 i j o b j [ ( x i x ^ i ) 2 + ( y i y ^ i ) 2 ] + λ c o o r d i = 0 S 2 j = 0 B 1 i j o b j [ ( w i w ^ i ) 2 + ( h i h ^ i ) 2 ] + i = 0 S 2 j = 0 B 1 i j o b j ( C i C ^ i ) 2 + λ n o o b j i = 0 S 2 j = 0 B 1 i j n o o b j ( C i C ^ i ) 2 + i = 0 S 1 i j o b j ( p i ( c ) p ^ i ( c ) ) 2 \lambda_{coord} \sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{obj} \left[ (x_i - \hat{x }_i)^2 + (y_i - \hat{y}_i)^2 \right]\\ + \lambda_{coord} \sum_{i=0}^{S^2}\sum_{j=0}^ B \mathbb{1}_{ij}^{obj} \left[ (\sqrt{w_i} - \sqrt{\hat{w}_i})^2 + (\sqrt{h_i} - \sqrt{\hat {h}_i})^2 \right]\\ + \sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{obj} ( C_i - \hat{C}_i)^2\\ + \lambda_{noobj} \sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij} ^{noobj} (C_i - \hat{C}_i)^2\\ + \sum_{i=0}^S \mathbb{1}_{ij}^{obj} (p_i(c) - \hat{ p}_i(c))^2\\

Para obtener más detalles, consulte Serie de detección de objetivos - Interpretación detallada de YOLOv1 (1)

#导入依赖库
from matplotlib.pyplot import cla
import torch
import torch.nn as nn

from utils import intersection_over_union
def __init__(self,S=7,B=2,C=20):
    super(YOLOLoss,self).__init__()

    self.mse = nn.MSELoss(reduction="sum")
    # 网络的数量
    self.S = S 
    # 每个网络输出边界框数量
    self.B = B
    # 每个网格预测类别数
    self.C = C

    self.lambda_noobj = 0.5
    # 对定位损失添加权重
    self.lambda_coord = 5
  • Si hay una confianza de destino en el cuadro delimitador predicho, debido a que la mayoría de los cuadros delimitadores no contienen objetivos, la probabilidad de pérdida de confianza para la cuadrícula sin objetivo reduce su proporción en la función de pérdidaself.lambda_noobj = 0.5
  • Agregue peso a la pérdida de posicionamiento, aumente el peso de la pérdida de posicionamientoself.lambda_coord = 5

Lea el código en detalle

predictions = predictions.reshape(-1,self.S,self.S,self.C + self.B*5)

输入 shape 为 (batch_size,features) 转换为 (batch_size,cellSize,cellSize,(class_numbers + bbox_numbers * (confidence + x1,y1,w,h)))

iou_b1 = intersection_over_union(preds[...,(self.C + 1):(self.C + 5)],target[...,(self.C + 1):(self.C + 5)])
iou_b2 = intersection_over_union(preds[...,(self.C + 6):(self.C + 10)],target[...,(self.C + 1):(self.C + 5)])

在预测 self.C + 1 到 self.C + 5 为第一个预测框中,中心坐标和边界框宽高,而 self.C + 6 到 self.C + 10,为第二个边界框的位置信息,这两边界框分别和标注对应网格位置的边界框做交并比,注意可能边界框并没有目标

ious = torch.cat([iou_b1.unsqueeze(0), iou_b2.unsqueeze(0)],dim=0)

输出 iou_b1 tensor 为标量,为标量增加一个维度,然后再 0 维度上对 iou_b1 和 iou_b2 进行堆叠

# a 和 b 分别是 intersection_over_union 函数输出交并比的值,然后使用 unsqueeze 扩展维度 [0.6] 和 [0.75], 然后对其进行堆叠
a = torch.tensor(0.6)
b = torch.tensor(0.75)
c = torch.cat([a.unsqueeze(0),b.unsqueeze(0)])
c
iou_maxes, bestbox = torch.max(ious,dim=0)

比较两个边界框 IoU 返回值 iou_maxes 返回 IoU 最大的值 bestbox 返回最大值对应索引

iou_maxes, bestbox = torch.max(c,dim=0)
print(iou_maxes,bestbox) #tensor(0.7500) tensor(1)

对于 target 结构为 (batch_size,cellSize,cellSize,(class_numbers + confidence + (x1,y1,w,h))),

exists_box = target[...,self.C].unsqueeze(3)
a = torch.randint(0,2,size=(2,7,7,85))
exists_box = a[...,80]
exists_box.shape # torch.Size([2, 7, 7])
exists_box = exists_box.unsqueeze(3)
exists_box.shape #torch.Size([2, 7, 7, 1])
exists_box

对于解读神经网络源码时,需要清楚地了解每一个步骤输入和输出数据的 shape

    box_preds = exists_box * (
        (bestbox * preds[...,(self.C + 6):(self.C + 10)] + (1-bestbox)*preds[...,(self.C + 1):(self.C + 5)])
    )

位置损失

tamaño_y_pos_2.jpg

位置损失包括两个部分,一个是中心点误差,另一个是宽高的位置损失。这里 1 i j o b j \mathbb{1}_{ij}^{obj} 是一个符号函数,也就是当有目标是为 1 否则为 0,也就是只计算存在目标的网格预测边界框和真实边界框的中心点和宽度和高度的差值。

这里 bestbox 是取值为 0 或者 1 ,表示第一个 bbox 还是第二个 bbox 是与 target 的 Iou 的值最大,所以 bestbox 为 0 也就是保留第一个 bbox,所以 (1 - bestbox)=1 那么也就是保留 preds[...,(self.C + 1):(self.C + 5)] 反之亦然。

这里 exists_box 表示只计算有目标的网格的位置损失。

box_preds[...,2:4] = torch.sign(box_preds[...,2:4]) * torch.sqrt(torch.abs(box_preds[...,2:4] + 1e-6))
        box_targets[...,2:4] = torch.sqrt(box_targets[...,2:4])

在 pytorch 对于一个负数进行开方运算会得到一个 nan

a = torch.tensor(-1.0)
torch.sqrt(a) #

所以这里先对其取绝对值,然后再进行开方运算后再用 torch.sign(a) 将符号还给输出结果

a = torch.tensor(-1.0)
torch.sign(a)*torch.sqrt(torch.abs(a)) #tensor(-1.)

用均方根来计算位置损失

box_loss = self.mse(
    #(n,S,S,4)->(N*S*S,4)
    torch.flatten(box_preds,end_dim=-2),
    torch.flatten(box_targets,end_dim=-2),
)
a = torch.randn((2,7,7,4))
torch.flatten(a,end_dim=-2).shape #torch.Size([98, 4])

目标损失

007.png

+ i = 0 S 2 j = 0 B 1 i j o b j ( C i C ^ i ) 2 + λ n o o b j i = 0 S 2 j = 0 B 1 i j n o o b j ( C i C ^ i ) 2 + \sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{obj} (C_i - \hat{C}_i)^2\\ + \lambda_{noobj} \sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{noobj} (C_i - \hat{C}_i)^2\\

每一个网格负责预测目标,首先会给出一个网格中是否包含目标置信度。YOLO 系列这样的 one stage 目标检测属于 dense 预测,也就是每个网格都会产生候选框,可能之前也叫做预测框。

网格置信度目标损失也分为 2 个部分进行计算,一个有目标的另一个是没有目标,通过通过权重

pred_box = (
    bestbox * preds[...,(self.C + 5):(self.C + 6)] + (1-bestbox) * preds[...,(self.C ):(self.C + 1)]
)

首先计算 pred_box 也就是对于每一个网格保留其置信度较高的概率值。

# (N*S*S,1)
# 类别为,
object_loss = self.mse(
    torch.flatten(exists_box * pred_box),
    torch.flatten(exists_box * target[...,(self.C):(self.C + 1)])
)

Luego, calcule el valor de probabilidad de confianza de la cuadrícula objetivo y este valor de probabilidad, aquí debe estar la diferencia entre la probabilidad de 1.0, el siguiente es el cálculo del valor de pérdida de confianza de la ausencia de la cuadrícula objetivo, esta vez necesita considerar la confianza de los dos bbox respectivamente Calcule el valor de pérdida tomando la diferencia al cuadrado con el valor verdadero, que es 0.

# (N,S,S,1) -> (N,S*S)
# 
no_object_loss = self.mse(
    torch.flatten((1 - exists_box) * preds[...,(self.C ):(self.C + 1)], start_dim=1),
    torch.flatten((1 - exists_box) * target[..., (self.C ):(self.C + 1)],start_dim=1)
)
no_object_loss += self.mse(
    torch.flatten((1 - exists_box) * preds[...,(self.C + 5):(self.C + 6)], start_dim=1),
    torch.flatten((1 - exists_box) * target[..., (self.C ):(self.C + 1)],start_dim=1)
)

Pérdida de categoría

clase.jpg

i = 0 S 1 i j o b j ( p i ( c ) p ^ i ( c ) ) 2 \sum_{i=0}^S \mathbb{1}_{ij}^{obj} (p_i(c) - \hat{p}_i(c))^2\\

La pérdida de clase es relativamente simple, así que no la explicaré demasiado aquí.

class_loss = self.mse(
    torch.flatten(exists_box*predictions[...,:self.C],end_dim=-2),
    torch.flatten(exists_box*target[...,:self.C],end_dim=-2)
)

loss = (
    self.lambda_coord * box_loss
    + object_loss
    + self.lambda_noobj * no_object_loss
    + class_loss
)

return loss

Finalmente, la pérdida de posición, la pérdida de objetivo y la pérdida de clase deben sumarse y sumarse para obtener la pérdida total.

código completo

class YoloLoss(nn.Module):
    def __init__(self, S=7,B=2,C=80):
        self.mse = nn.MSELoss(reduction="sum")
        # 网络的数量
        self.S = S 
        # 每个网络输出边界框数量
        self.B = B
        # 每个网格预测类别数
        self.C = C
        # 在预测边界框中是否存在目标的置信度,因为大部分边界框都是不包含目标,所以对于没有目标网格置信度概率损失减少其在损失函数所占比重
        self.lambda_noobj = 0.5
        # 对定位损失添加权重,增加定位损失权重
        self.lambda_coord = 5
        
    def forward(self, preds, target):
        # 输入 shape 为 (batch_size,features) 转换为 (batch_size,cellSize,cellSize,(class_numbers + bbox_numbers * (confidence + x1,y1,w,h)))         
        preds = preds.reshape(-1,self.S,self.S,self.C + self.B*5)
        
        # 在预测 self.C + 1 到 self.C + 5 为第一个预测框中,中心坐标和边界框宽高,而    self.C + 6 到 self.C + 10 
        # 为第二个边界框的位置信息,这两边界框分别和标注对应网格位置的边界框做交并比,注意可能边界框并没有目标        
        iou_b1 = intersection_over_union(preds[...,(self.C + 1):(self.C + 5)],target[...,(self.C + 1):(self.C + 5)])
        iou_b2 = intersection_over_union(preds[...,(self.C + 6):(self.C + 10)],target[...,(self.C + 1):(self.C + 5)])
        
        #输出 iou_b1 tensor 为标量,为标量增加一个维度,然后再 0 维度上对 iou_b1 和 iou_b2 进行堆叠
        ious = torch.cat([iou_b1.unsqueeze(0), iou_b2.unsqueeze(0)],dim=0)
        # 比较两个边界框 IoU 返回值 iou_maxes 返回 IoU 最大的值 bestbox 返回最大值对应索引
        iou_maxes, bestbox = torch.max(ious,dim=0)
        # 对于 target 结构为 (batch_size,cellSize,cellSize,(class_numbers + confidence + (x1,y1,w,h)))        
        exists_box = target[...,self.C].unsqueeze(3) 
        
        # 位置损失
        
        #          
        box_preds = exists_box * (
            (bestbox * preds[...,(self.C + 6):(self.C + 10)] + (1-bestbox)*preds[...,(self.C + 1):(self.C + 5)])
        )
        
        box_targets = exists_box * target[...,(self.C  + 1):(self.C + 5)]
        
        box_preds[...,2:4] = torch.sign(box_preds[...,2:4]) * torch.sqrt(torch.abs(box_preds[...,2:4] + 1e-6))
        box_targets[...,2:4] = torch.sqrt(box_targets[...,2:4])
        
        box_loss = self.mse(
            #(n,S,S,4)->(N*S*S,4)
            torch.flatten(box_preds,end_dim=-2),
            torch.flatten(box_targets,end_dim=-2),
        )
        
        """
        目标损失
        """
        # 保留 IoU 值高
        pred_box = (
            bestbox * preds[...,(self.C + 5):(self.C + 6)] + (1-bestbox) * preds[...,(self.C ):(self.C + 1)]
        )
        
        # (N*S*S,1)
        # 类别为,
        object_loss = self.mse(
            torch.flatten(exists_box * pred_box),
            torch.flatten(exists_box * target[...,(self.C):(self.C + 1)])
        )

        # (N,S,S,1) -> (N,S*S)
        # 
        no_object_loss = self.mse(
            torch.flatten((1 - exists_box) * preds[...,(self.C ):(self.C + 1)], start_dim=1),
            torch.flatten((1 - exists_box) * target[..., (self.C ):(self.C + 1)],start_dim=1)
        )
        
        #
        no_object_loss += self.mse(
            torch.flatten((1 - exists_box) * preds[...,(self.C + 5):(self.C + 6)], start_dim=1),
            torch.flatten((1 - exists_box) * target[..., (self.C ):(self.C + 1)],start_dim=1)
        )
        
        class_loss = self.mse(
            torch.flatten(exists_box*predictions[...,:self.C],end_dim=-2),
            torch.flatten(exists_box*target[...,:self.C],end_dim=-2)
        )

        loss = (
            self.lambda_coord * box_loss
            + object_loss
            + self.lambda_noobj * no_object_loss
            + class_loss
        )

        return loss
        

Estoy participando en el reclutamiento del programa de firma de creadores de la Comunidad Tecnológica de Nuggets, haga clic en el enlace para registrarse y enviar .

Supongo que te gusta

Origin juejin.im/post/7123086708098203679
Recomendado
Clasificación