当前位置: 代码迷 >> 综合 >> YOLO v1 计算流程--基于pytorch
  详细解决方案

YOLO v1 计算流程--基于pytorch

热度:96   发布时间:2023-10-25 11:40:21.0

YOLO v1 计算流程–基于pytorch

  • 个人理解TOLO v1的计算有如下几个关键部分:
    • 1.图像预处理
      • YOLO v1要求图像的大小是一致的448 * 448 因此读取图像后需要对图像进行预处理
    • 2.图像的前向传播
      • 前向传播部分由两部分组成:特征提取和输出构建
      • 特征提取可以使用原文章中基于DartNet的特征提取方式,也可以采用其他网络诸如VGG或者ResNet等
      • 输出构建时YOLO v1的精华,是YOLO网络的主要思想核心,基于划分好的网格构建bounding box、confidence和class维度,每个网格构建一套
    • 3.损失函数的计算:
      • 损失函数均基于均方根误差计算
      • YOLO v1的损失函数包括三部分:bounding box坐标位置损失、置信度损失(网格中是否包含object中心),class损失(基于one hot编码计算)
图像预处理和划分
  • 将图像resize为448×448×3448\times 448 \times 3448×448×3的大小,resize的目的是为了满足全连接层的固定输入维度的要求
    • 代码:
      • import cv2
        img = cv2.resize(img, (448,448))
        
    • 将448的图像划分为7×77\times 77×7的格点,每个格点边长为64个像素点图解:
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7qmIDoI-1635915412647)(./1635745220607.png)]
将图像输入到YOLO v1网络前向传播:
  • 由于YOLO前边的网络主要作用是特征提取,因此使用resnet的全连接之前的部分代替,通过加载torchvision中的已经训练好的resnet34,并固定参数,后边跟YOLOv1后半部分卷积结构和全连接层变化结构
  • 每个网格要预测2个bounding box,每个bounding box除了要预测位置之外,还要附带预测一个confiden值,每个网格还要预测C个类别的分数
    • 网格有7×77\times 77×7每个网格2个边界框
    • 也就是每个bounding box 预测5个值,其中四个为位置参数(相对值,相对于整个图像大小来说),confidence值,这里有两个,就是2×(4+1)2 \times (4+1)2×(4+1)
    • VOC数据集有20个类别,则类别数为20
    • 所以一共有参数7×7×(20+2×(4+1))7 \times 7 \times(20+2\times (4+1))7×7×(20+2×(4+1))
      *在这里插入图片描述
from torchvision.models import resnet34
import torch.nn as nn
import torchclass YOLOv1_resnet(nn.Module):def __init__(self, num_box, class_num):super(YOLOv1_resnet,self).__init__()self.num_box = num_boxself.class_num = class_numresnet = resnest_model()# 调用torchvision里的resnet34预训练模型resnet_out_channel = resnet.fc.in_features # 记录resnet全连接层之前的网络输出通道数,方便连入后续卷积网络中self.resnet = nn.Sequential(*list(resnet.children())[:-2])  # 去除resnet的最后两层'''定义YOLO的最后的卷积层'''self.conv_layer = nn.Sequential(nn.Conv2d(resnet_out_channel,1024,3,padding=1),nn.BatchNorm2d(1024),nn.LeakyReLU(),nn.Conv2d(1024,1024,3,stride=2,padding=1),nn.BatchNorm2d(1024),nn.LeakyReLU(),nn.Conv2d(1024, 1024, 3, padding=1),nn.BatchNorm2d(1024),nn.LeakyReLU(),)'''全连接层,三维变量拉平进行处理'''self.dense_layers = nn.Sequential(nn.Linear(7*7*1024,4096),nn.LeakyReLU(),nn.Linear(4096,7*7*30))def forward(self, x):out = self.resnet(x)out = self.conv_layer(out)out = out.view(out.size()[0],-1)out = self.dense_layers(out)return out.reshape(-1, (5*self.num_box+self.class_num), 7, 7)def set_parameter_requires_grad(model):for param in model.parameters():param.requires_grad = False# ResNeSt模型
def resnest_model():model_ft = resnet34(pretrained=True)set_parameter_requires_grad(model_ft)num_ftrs = model_ft.fc.in_features  ### 问题:冻结参数是对已有的层,新定义的层不冻结?return model_ftif __name__ == '__main__':x = torch.randn((1,3,448,448))net = YOLOv1_resnet(2, 20)print(net)y = net(x)print(y.size())
3)损失函数的计算
  • 数据前向传播后,要计算损失才能后向传播,因此需要根据定义计算损失函数,在YOLO v1中损失函数由三个部分组成,均使用平方和计算:
  • 使用误差平方和计算
  • bounding box损失
    • Lloc(l,g)=∑i=0s2∑j=0B1i,jobj[(xi?x^i)2+(yi?y^i)2]+λcoord∑i=0s2∑j=0B1i,jobj[((wi)?w^i)2+((hi)?h^i)2]L_{loc}(l,g)=\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{obj}_{i,j}[(x_i-\hat x_i)^2+(y_i-\hat y_i)^2]+\lambda_{coord}\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{obj}_{i,j}[(\sqrt{(w_i)}-\sqrt{\hat w_i})^2+(\sqrt{(h_i)}-\sqrt{\hat h_i})^2]Lloc?(l,g)=i=0s2?j=0B?1i,jobj?[(xi??x^i?)2+(yi??y^?i?)2]+λcoord?i=0s2?j=0B?1i,jobj?[((wi?) ??w^i? ?)2+((hi?) ??h^i? ?)2]
  • confidence 损失
    • Lconf(o,c)=∑i=0s2∑j=0B1i,jobj[(Ci?C^i)2+λnoobj∑i=0s2∑j=0B1i,jnoobj[(Ci?C^i)2L_{conf}(o,c)=\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{obj}_{i,j}[(C_i-\hat C_i)^2+\lambda_{noobj}\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{noobj}_{i,j}[(C_i-\hat C_i)^2Lconf?(o,c)=i=0s2?j=0B?1i,jobj?[(Ci??C^i?)2+λnoobj?i=0s2?j=0B?1i,jnoobj?[(Ci??C^i?)2
  • classes 损失
    • Lcla(O,C)=∑i=0s21iobj∑c∈classes(pi(c)?pi^(c))L_{cla(O,C)}=\sum^{s^2}_{i=0}1^{obj}_{i}\sum_{c \in classes(p_i(c)-\hat {p_i}(c))}Lcla(O,C)?=i=0s2?1iobj?cclasses(pi?(c)?pi?^?(c))?
    • 总的损失为所有三种损失的损失相加
    • L(o,c,O,C,l,g)=λ1Lconf(o,c)+λ2Lcla(O,C)+λ3Lloc(l,g)L(o,c,O,C,l,g)=\lambda_1L_{conf}(o,c)+\lambda_2L_{cla(O,C)}+\lambda_3L_{loc}(l,g)L(o,c,O,C,l,g)=λ1?Lconf?(o,c)+λ2?Lcla(O,C)?+λ3?Lloc?(l,g)
      • 其中λ\lambdaλ为平衡系数
  • 在计算bounding box损失的时候涉及到一个问题:anchors的选取
    • anchors生成的过程如下:
      • 锚定过程:
        • 对于输入图像的每个对象,先找到其中心点,寻找中心点所在的划分好的网格(7*7),则该包含物体中心点的网格中confidence=1,其他48个网格中confidence为0,在YOLO中称为中心点所在的网格对预测该对象负责
        • bounding box坐标:
          • YOLO中并不会预设anchors坐标或者高宽比,而是锚定中心点之后,在结果中直接生成,组成输出结果的8个特征,不过每个网格cell生成两组boxes坐标,每组坐标有4个,分别为相对中心点坐标(x,y),相对高度和宽度为(w, h),其中相对中心坐标时相对于中心所在的子网格,w,h为相对于整幅图的宽度和高度比例,如下图:
            • 在这里插入图片描述
          • 由于有两个boxes,因此需要对boxes进行筛选,选取最优的boxes的坐标计算误差,筛选采用IoU 来进行:
      • IoU
      • IoU=intersection(A,B)union(A,B)IoU =\frac{intersection(A,B)}{union(A,B)}IoU=union(A,B)intersection(A,B)?
      • 物理意义为:IoU为交集部分面积与并集部分面积之比,当两个Box完全重合时IoU=1,不相交时IoU=0
  • 本部分代码采用沐神课程中的代码,在我之前的blog中有详细解析,链接https://blog.csdn.net/qq_34992900/article/details/120705041?spm=1001.2014.3001.5501:
def box_iou(boxes1, boxes2):'''args:boxes1: tensor[num_boxes, 4]boxes2: 同上'''box_area = lambda boxes:((boxes[:,2] - boxes[:,0]) * (boxes[:,3] - boxes[:,1]))areas1 = box_area(boxes1)areas2 = box_area(boxes2)inter_upperlefts = torch.max(boxes1[:, None, :2], boxes2[:, :2])inter_lowerrights = torch.min(boxes1[:, None, 2:], boxes2[: 2:])inters = (inter_lowerrights - inter_upperlefts).clamp(min=0)inter_areas = inters[:, :, 0] * inters[:, :, 1]union_areas = areas1[:, None] + areas2 - inter_areasreturn inter_areas / union_areas
  • 定义Loss函数
  • 为了方便计算Loss,将网络输出为7×7×307 \times 7 \times 307×7×30的数据格式与label计算,label转化为与网络输出一致的格式7×7×307 \times 7 \times 307×7×30得到;
  • 在计算中先计算格点预设两个边框与真实边框的IoU值,选择最大IoU边框,进一步计算边框坐标误差
  • 上述步骤如下:
    • 根据confidence判断网格中是否包含object的中心
    • 若存在中心,则将中心–高宽的形式,转为左上角和右下角坐标的形式
    • 通过上述IoU计算大赛分别计算两个bounding box与label的大小
    • 选取IoU最大的计算边框坐标误差,IoU小的bounding box则计入到网格没有object的误差,计算分类误差,概率值为IoU
    • 计算置信度误差
    • 计算分类误差
  • 代码如下:
class Loss_yolov1(nn.Module):def __init__(self):super(Loss_yolov1,self).__init__()def box_center_to_conner(self, boxes, n, m, grid_num):"""转换 YOLO v1的bounding box从中间,宽度,高度)转换(左上,右下)"""cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]x1 = (cx + n)/grid_num - w/2y1 = (cy + m)/grid_num - h/2x2 = (cx + n)/grid_num + w/2y2 = (cy + m)/grid_num + w/2boxes = torch.stack((x1, y1, x2, y2), axis=-1)return boxesdef forward(self, pred, labels):""":param pred: (batchsize,30,7,7)的网络输出数据:param labels: (batchsize,30,7,7)的样本标签数据:return: 当前批次样本的平均损失"""num_gridx, num_gridy = labels.size()[-2:]  # 划分网格数量num_b = 2  # 每个网格的bbox数量num_cls = 20  # 类别数量noobj_confi_loss_2 = 0.  # 不含目标的网格损失(只有置信度损失)coor_loss = 0.  # 含有目标的bbox的坐标损失obj_confi_loss = 0.  # 含有目标的bbox的置信度损失class_loss = 0.  # 含有目标的网格的类别损失n_batch = labels.size()[0]  # batchsize的大小iloss_1 = 0.for i in range(n_batch):  # batchsize循环for n in range(7):  # x方向网格循环for m in range(7):  # y方向网格循环if labels[i,4,m,n]==1:#如果包含物体bbox1_pred_xyxy = self.box_center_to_conner(pred[i,:4,m,n], n, m, num_gridx)bbox2_pred_xyxy = self.box_center_to_conner(pred[i,4:8,m,n], n, m, num_gridx) bbox_gt_xyxy =  self.box_center_to_conner(labels[i,:4,m,n], n, m, num_gridx)iou1 = box_iou(bbox1_pred_xyxy,bbox_gt_xyxy)iou2 = box_iou(bbox2_pred_xyxy,bbox_gt_xyxy)# 选择iou大的bbox作为负责物体box_pred = pred[i, :4, m, n] if iou1 >= iou2 else pred[i, 4:8, m, n]icoor_loss = (torch.sum((box_pred[0:2,:] - labels[i,0:2,m,n])**2) + torch.sum((pred[i,2:4,m,n].sqrt()-labels[i,2:4,m,n].sqrt())**2))iobj_confi_loss = (pred[i,4,m,n] - iou1)**2# iou比较小的bbox不负责预测物体,因此confidence loss算在noobj中,注意,对于标签的置信度应该是iou2inoobj_confi_loss_1 = ((pred[i,9,m,n]-iou2)**2)iclass_loss = torch.sum((pred[i,10:,m,n] - labels[i,10:,m,n])**2)iloss_1 += 5 * icoor_loss + iobj_confi_loss + iclass_loss + 0.5*inoobj_confi_loss_1else:  # 如果不包含物体noobj_confi_loss_2 += 0.5 * torch.sum(pred[i,[4,9],m,n]**2)loss = iloss_1 + 0.5 * noobj_confi_loss_2# 此处可以写代码验证一下loss的大致计算是否正确,这个要验证起来比较麻烦,比较简洁的办法是,将输入的pred置为全1矩阵,再进行误差检查,会直观很多。return loss/n_batch
###############
### 验证代码 ###
###############
loss_fn = Loss_yolov1()
pred = torch.randn((4,30,7,7))
label = torch.randn((4,30,7,7))
loss = loss_fn(pred, label)
print(loss)
参考:
  • 大量参考了该仓代码:https://github.com/lavendelion/YOLOv1-from-scratch
  • 部分参考了李沐深度学习课程代码