yolov3(ultralytics cfg旧版) 代码详解

YOLOv3.cfg文件解析

[net]

#Testing
#batch=1
#subdivisions=1
# 在测试的时候,设置 batch=1,subdivisions=1

#Training
batch=16
subdivisions=4
# 这里的 batch 与普遍意义上的 batch 不是一致的。
# 训练的过程中将一次性加载 16 张图片进内存,然后分 4 次完成前向传播,每次 4 张。
# 经过 16 张图片的前向传播以后,进行一次反向传播。

width=416
height=416
channels=3
# 设置图片进入网络的宽、高和通道个数。
# 由于 YOLOv3 的下采样一般是 32 倍,所以宽高必须能被 32 整除。
# 多尺度训练选择为 32 的倍数最小 320*320,最大 608*608。
# 长和宽越大,对小目标越好,但是占用显存也会高,需要权衡。

momentum=0.9
# 动量参数影响着梯度下降到最优值得速度。
decay=0.0005
# 权重衰减正则项,防止过拟合。
angle=0
# 数据增强,设置旋转角度。
saturation = 1.5
# 饱和度
exposure = 1.5
# 曝光量
hue=.1
# 色调

learning_rate=0.001
# 学习率: 刚开始训练时可以将学习率设置的高一点,而一定轮数之后,将其减小。
# 在训练过程中,一般根据训练轮数设置动态变化的学习率。

burn_in=1000
max_batches = 500200
# 最大 batch

policy=steps
# 学习率调整的策略,有以下 policy:constant, steps, exp, poly, step, sig, RANDOM,constant 等方式
#steps# 比较好理解,按照 steps 来改变学习率。

steps=400000,450000
scales=.1,.1
# 在达到 40000、45000 的时候将学习率乘以对应的 scale
--------------------- 卷积层 ----------------------
[convolutional]
batch_normalize=1
# 是否做 BN 操作
filters=32
# 输出特征图的数量
size=3
# 卷积核的尺寸
stride=1
# 做卷积运算的步长
pad=1
# 如果 pad 为 0,padding 由 padding 参数指定。
# 如果 pad 为 1,padding 大小为 size/2,padding 应该是对输入图像左边缘拓展的像素数量
activation=leaky
# 激活函数的类型:logistic,loggy,relu,elu,relie,plse,hardtan,lhtan,linear,ramp,leaky,tanh,stair
# alexeyAB 版添加了 mish, swish, nrom_chan 等新的激活函数
------------------------ 下采样 ------------------------
[convolutional]
batch_normalize=1
filters=128
size=3
stride=2
pad=1
activation=leaky
可以通过带入以上公式,可以得到 OutFeature 是 InFeature 的一半。

也可以使用 maxpooling 进行下采样:
[maxpool]
size=2
stride=2
------------------------ 上采样 -------------------------
[upsample]
stride=2
上采样是通过线性插值实现的。
----------------- Shortcut 和 Route 层 ------------------
[shortcut]
from=-3
activation=linear
#shortcut 操作是类似 ResNet 的跨层连接,参数 from 是 −3,
# 意思是 shortcut 的输出是当前层(记为0)与先前的倒数第三层(倒数-1,-2,-3)相加而得到。
# 通俗来讲就是 add 操作

[route]
layers = -1, 36
# 当属性有两个值,就是将上一层和第 36 层进行 concate
# 即沿深度的维度连接,这也要求 feature map 大小是一致的。

[route]
layers = -4
# 当属性只有一个值时,它会输出由该值索引的网络层的特征图。
# 本例子中就是提取从当前倒数第四个层输出
------------------------------ YOLO 层 ---------------------------
[convolutional]
size=1
stride=1
pad=1
filters=18
# 每一个 [region/yolo] 层前的最后一个卷积层中的
#filters=num(yolo 层个数)*(classes+5) ,5 的意义是 5 个坐标,
# 代表论文中的 tx,ty,tw,th,po
# 这里类别个数为 1,(1+5)*3=18
activation=linear

[yolo]
mask = 0,1,2
# 训练框 mask 的值是 0,1,2,
# 这意味着使用第一,第二和第三个 anchor

anchors = 10,13, 16,30, 33,23, 30,61, 62,45,59,119, 116,90, 156,198, 373,326
# 总共有三个检测层,共计 9 个 anchor
# 这里的 anchor 是由 kmeans 聚类算法得到的。

classes=1
# 类别个数
num=9
# 每个 grid 预测的 BoundingBox num/yolo 层个数

jitter=.3
# 利用数据抖动产生更多数据,
# 属于 TTA(Test Time Augmentation)

ignore_thresh = .5
# ignore_thresh 指得是参与计算的 IOU 阈值大小。
# 当预测的检测框与 ground true 的 IOU 大于 ignore_thresh 的时候,
# 不会参与 loss 的计算,否则,检测框将会参与损失计算。
# 目的是控制参与 loss 计算的检测框的规模,当 ignore_thresh 过于大,
# 接近于 1 的时候,那么参与检测框回归 loss 的个数就会比较少,同时也容易造成过拟合;
# 而如果 ignore_thresh 设置的过于小,那么参与计算的会数量规模就会很大。
# 同时也容易在进行检测框回归的时候造成欠拟合。
#ignore_thresh 一般选取 0.5-0.7 之间的一个值
# 小尺度(13*13)用的是 0.7,
# 大尺度(26*26)用的是 0.5。

数据组织、加载

参考https://blog.csdn.net/DD_PP_JJ/article/details/104709299

在pytorch中,数据集加载主要是重构datasets类,然后再使用dataloader中加载dataset,就构建好了数据部分。

下面是一个简单的使用模板(pytorch中的数据加载机制):

import os
from torch.utils.data import Dataset
from torch.utils.data import DataLoader

# 根据自己的数据集格式进行重构
class MyDataset(Dataset):
    def __init__(self):
        #下载数据、初始化数据,都可以在这里完成
        xy = np.loadtxt('label.txt', delimiter=',', dtype=np.float32) 
        # 使用numpy读取数据
        self.x_data = torch.from_numpy(xy[:, 0:-1])
        self.y_data = torch.from_numpy(xy[:, [-1]])
        self.len = xy.shape[0]
    
    def __getitem__(self, index):
        # dataloader中使用该方法,通过index进行访问
        return self.x_data[index], self.y_data[index]

    def __len__(self):
        # 查询数据集中数量,可以通过len(mydataset)得到
        return self.len

# 实例化这个类,然后我们就得到了Dataset类型的数据,记下来就将这个类传给DataLoader,就可以了。 
myDataset = MyDataset()

# 构建dataloader
train_loader = DataLoader(dataset=myDataset,
                          batch_size=32,
                          shuffle=True)

for epoch in range(2):
    for i, data in enumerate(train_loader):
        # 将数据从 train_loader 中读出来,一次读取的样本数是32个
        inputs, labels = data
        # 将这些数据转换成Variable类型
        inputs, labels = Variable(inputs), Variable(labels)
		# 模型训练...

建议阅读:

矩形训练相关:https://blog.csdn.net/songwsx/article/details/102639770
仿射变换:https://zhuanlan.zhihu.com/p/93822508
Rectangle Trainning:https://github.com/ultralytics/yolov3/issues/232
数据自由读取:https://zhuanlan.zhihu.com/p/30385675

超参数搜索与进化

参考https://blog.csdn.net/DD_PP_JJ/article/details/104709330

YOLOv3代码中也提供了参数搜索,可以为对应的数据集进化一套合适的超参数。

在train.py,其中包含了一些数据增强参数设置:

# Hyperparameters
hyp = {'giou': 3.54,  # giou loss gain
       'cls': 37.4,  # cls loss gain
       'cls_pw': 1.0,  # cls BCELoss positive_weight
       'obj': 64.3,  # obj loss gain (*=img_size/320 if img_size != 320)
       'obj_pw': 1.0,  # obj BCELoss positive_weight
       'iou_t': 0.20,  # iou training threshold
       'lr0': 0.01,  # initial learning rate (SGD=5E-3, Adam=5E-4)
       'lrf': 0.0005,  # final learning rate (with cos scheduler)
       'momentum': 0.937,  # SGD momentum
       'weight_decay': 0.0005,  # optimizer weight decay
       'fl_gamma': 0.0,  # focal loss gamma (efficientDet default is gamma=1.5)
       'hsv_h': 0.0138,  # image HSV-Hue augmentation (fraction)
       'hsv_s': 0.678,  # image HSV-Saturation augmentation (fraction)
       'hsv_v': 0.36,  # image HSV-Value augmentation (fraction)
       'degrees': 1.98 * 0,  # image rotation (+/- deg)
       'translate': 0.05 * 0,  # image translation (+/- fraction)
       'scale': 0.05 * 0,  # image scale (+/- gain)
       'shear': 0.641 * 0}  # image shear (+/- deg)

在训练的时候,train.py提供了一个可选参数--evolve, 决定了是否进行超参数搜索与进化(默认是不开启超参数搜索的)。

python train.py --data data/voc.data --cfg cfg/yolov3-tiny.cfg --img-size 416 --epochs 273 --evolve

实际使用的时候,需要进行修改,train.py中的约444行:

for _ in range(1):  # generations to evolve

将其中的1修改为你想设置的迭代数,比如200代,如果不设置,结果将会如下图所示,实际上就是只有一代。

网络模型的构建

参考:https://blog.csdn.net/DD_PP_JJ/article/details/104709403

1、如何从cfg文件构造模型。本文涉及到一个比较有用的部分就是bias的设置,可以提升mAP、F1、P、R等指标,还能让训练过程更加平滑。
2、修改网络结构很容易,只需要修改cfg文件即可。目前,cfg文件支持convolutional, maxpool, unsample, route, shortcut, yolo这几个层。
3、如果想要添加自定义的模块也很方便,比如说注意力机制模块、空洞卷积等,都可以简单地得到添加或者修改。
4、为了更加方便的理解cfg文件网络是如何构建的,在这里推荐一个Github上的网络结构可视化软件:Netron

train.py:

#Initialize model
model = Darknet(cfg, arc=opt.arc).to(device)

然后沿着Darknet实现进行讲解:

class Darknet(nn.Module):
    # YOLOv3 object detection model
    def __init__(self, cfg, img_size=(416, 416), arc='default'):
        super(Darknet, self).__init__()
        self.module_defs = parse_model_cfg(cfg)
        self.module_list, self.routs = create_modules(self.module_defs, img_size, arc)
        self.yolo_layers = get_yolo_layers(self)

        # Darknet Header
        self.version = np.array([0, 2, 5], dtype=np.int32)  
        # (int32) version info: major, minor, revision
        self.seen = np.array([0], dtype=np.int64)  
        # (int64) number of images seen during training

比较关键的就是成员函变量module_defs、module_list、routs、yolo_layers四个成员函数

YOLOLayer解析和推理过程

模型构建中最重要的YOLOLayer还没有梳理,本文将从代码的角度理解YOLOLayer的构建与实现

1、 Grid创建
需要注意的是这是针对某一层YOLOLayer,而不是所有的YOLOLayer

def create_grids(self,
                 img_size=416,
                 ng=(13, 13),
                 device='cpu',
                 type=torch.float32):
    nx, ny = ng  # 网格尺寸
    self.img_size = max(img_size)
    #下采样倍数为32
    self.stride = self.img_size / max(ng)

    # 划分网格,构建相对左上角的偏移量
    yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)])
    # 通过以上例子很容易理解
    self.grid_xy = torch.stack((xv, yv), 2).to(device).type(type).view(
        (1, 1, ny, nx, 2))

    # 处理anchor,将其除以下采样倍数
    self.anchor_vec = self.anchors.to(device) / self.stride
    self.anchor_wh = self.anchor_vec.view(1, self.na, 1, 1,
                                          2).to(device).type(type)
    self.ng = torch.Tensor(ng).to(device)
    self.nx = nx
    self.ny = ny

2、 YOLOLayer
训练过程:
YOLOLayer的作用就是对上一个卷积层得到的张量进行处理,具体可以看training过程涉及的代码(暂时不关心ONNX部分的代码):

class YOLOLayer(nn.Module):
    def __init__(self, anchors, nc, img_size, yolo_index, arc):
        super(YOLOLayer, self).__init__()

        self.anchors = torch.Tensor(anchors)
        self.na = len(anchors)  # 该YOLOLayer分配给每个grid的anchor的个数
        self.nc = nc  # 类别个数
        self.no = nc + 5  # 每个格子对应输出的维度 class + 5 中5代表x,y,w,h,conf
        self.nx = 0  # 初始化x方向上的格子数量
        self.ny = 0  # 初始化y方向上的格子数量
        self.arc = arc

        if ONNX_EXPORT:  # grids must be computed in __init__
            stride = [32, 16, 8][yolo_index]  # stride of this layer
            nx = int(img_size[1] / stride)  # number x grid points
            ny = int(img_size[0] / stride)  # number y grid points
            create_grids(self, img_size, (nx, ny))

    def forward(self, p, img_size, var=None):
        '''
        onnx代表开放式神经网络交换
        pytorch中的模型都可以导出或转换为标准ONNX格式
        在模型采用ONNX格式后,即可在各种平台和设备上运行
        在这里ONNX代表规范化的推理过程
        '''
        if ONNX_EXPORT:
            bs = 1  # batch size
        else:
            bs, _, ny, nx = p.shape  # bs, 255, 13, 13
            if (self.nx, self.ny) != (nx, ny):
                create_grids(self, img_size, (nx, ny), p.device, p.dtype)

        # p.view(bs, 255, 13, 13) -- > (bs, 3, 13, 13, 85)
        # (bs, anchors, grid, grid, classes + xywh)
        p = p.view(bs, self.na, self.no, self.ny,
                   self.nx).permute(0, 1, 3, 4, 2).contiguous()  

        if self.training:
            return p

在理解以上代码的时候,需要理解每一个通道所代表的意义,原先的P是由上一层卷积得到的feature map, 形状为(以80个类别、输入416、下采样32倍为例):【batch size, anchor×(80+5), 13, 13】,在训练的过程中,将feature map通过张量操作转化的形状为:【batch size, anchor, 13, 13, 85】。
测试过程:

# p的形状目前为:【bs, anchor_num, gridx,gridy,xywhc+class】
else:  # 测试推理过程
   # s = 1.5  # scale_xy  (pxy = pxy * s - (s - 1) / 2)
   io = p.clone()  # 测试过程输出就是io
   io[..., :2] = torch.sigmoid(io[..., :2]) + self.grid_xy  # xy
   # grid_xy是左上角再加上偏移量io[...:2]代表xy偏移
   io[..., 2:4] = torch.exp(
       io[..., 2:4]) * self.anchor_wh  # wh yolo method
   # io[..., 2:4] = ((torch.sigmoid(io[..., 2:4]) * 2) ** 3) * self.anchor_wh  
   # wh power method
   io[..., :4] *= self.stride

   if 'default' in self.arc:  # seperate obj and cls
       torch.sigmoid_(io[..., 4])
   elif 'BCE' in self.arc:  # unified BCE (80 classes)
       torch.sigmoid_(io[..., 5:])
       io[..., 4] = 1
   elif 'CE' in self.arc:  # unified CE (1 background + 80 classes)
       io[..., 4:] = F.softmax(io[..., 4:], dim=4)
       io[..., 4] = 1

   if self.nc == 1:
       io[..., 5] = 1
       # single-class model https://github.com/ultralytics/yolov3/issues/235

   # reshape from [1, 3, 13, 13, 85] to [1, 507, 85]
   return io.view(bs, -1, self.no), p

理解以上内容是需要对应以下公式:
在这里插入图片描述

io[..., :2] = torch.sigmoid(io[..., :2]) + self.grid_xy  # xy
# grid_xy是左上角再加上偏移量io[...:2]代表xy偏移

在这里插入图片描述

# wh yolo method
io[..., 2:4] = torch.exp(io[..., 2:4]) * self.anchor_wh  

class部分:
在类别部分,提供了几种方法,根据arc参数来进行不同模式的选择。以CE(crossEntropy)为例:

#io: (bs, anchors, grid, grid, xywh+classes)
io[..., 4:] = F.softmax(io[..., 4:], dim=4)# 使用softmax
io[..., 4] = 1 

Loss部分计算

YOLOv1是一个anchor-free的,从YOLOv2开始引入了Anchor,在VOC2007数据集上将mAP提升了10个百分点。YOLOv3也继续使用了Anchor,本文主要讲ultralytics版YOLOv3的Loss部分的计算, 实际上这部分loss和原版差距非常大,并且可以通过arc指定loss的构建方式, 如果想看原版的loss可以在release的v6中下载源码。

详见:https://blog.csdn.net/DD_PP_JJ/article/details/105173151

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值