OD--YOLOv5代码学习

最新的YOLOv5增加了wandb功能,要先注册一个wandb的账号
将申请的API配置到电脑上,即可实时看到损失、权重和偏执。

1.配置超参

YOLOv5的配置文件是.yaml格式
以yolov5l.yaml为例
OD--YOLOv5代码学习_第1张图片

# Parameters
nc: 80  # number of classes
depth_multiple: 1.0  # model depth multiple
width_multiple: 1.0  # layer channel multiple

nc:代表分类类别的数量
depth_multiple:模型深度,即控制模块的数量,模块的数量=depth_multipledepth;
width_multiple:模型宽度,控制的是卷积核的数量。卷积核的数量=num
width;
根据实际项目需求去调整,难以学习的或者数据规模较大的,用大一些的宽度和深度超参。小一些的就调小些。

anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

anchors:包含的是不同尺度的特征图中anchors的尺寸,10,13是一组,16,30是一组…每个尺度的特征图有三种尺寸的anchor。YOLOv5的anchor设计思想是在大特征图上检测小目标,在小特征图上检测大目标。P3/8…等等代表了输入的特征图经下采样后与原特征图的比例。

# YOLOv5 backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Focus, [64, 3]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 9, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 1, SPP, [1024, [5, 9, 13]]],
   [-1, 3, C3, [1024, False]],  # 9
  ]

YOLOv5的backbone中四个参数:
**from:**这一层的input从哪里来,-1代表从上一层
number:代表这一层的卷积核数量
model:这一层使用到的模块
args:这一层

Model介绍:

Focus

:对feature map进backbone前进行切片操作,具体操作就是每隔一个像素值取一个值,这样就拿到了四张图片,且没有信息丢失。
OD--YOLOv5代码学习_第2张图片
640×640×3的图像,经过Focus先切片,再通过concat从深度上连接四个切片后的feature map,得到320×320×12的feature map,再用卷积增加通道数至128,就得到了320×320×128的feature map。
Focus作用:和普通下采样比,保证信息不丢失。提高FLOPs速度

class Focus(nn.Module):
    # Focus wh information into c-space
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super(Focus, self).__init__()
        self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
        # self.contract = Contract(gain=2)

    def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2)
        return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))
        # return self.conv(self.contract(x))

Conv

class Conv(nn.module):
    # standard convolution
    # 这个类做了三件事 架构图中的标准卷积: conv+BN+hardswish
    # init初始化构造函数
    def __init__(self,c1,c2,k=1,s=1,p=None,g=1,act=True):  # ch_in ch_out kernel, stride, padding, groups 
        super(Conv, self).__init__()
        '''
        nn.conv2d函数的基本参数是:
        nn.Conv2d(self, in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, paddinng_mode='zeros')
        参数:nn.Conv 参考链接https://blog.csdn.net/qq_26369907/article/details/88366147
        in_channel:输入数据的通道数,例RGB图片的通道数为3
        out_channel:输出数据的通道数,这个根据模型调整
        kennel_size:卷积核大小,可以是int,或tuple,kennel_size=2,意味着卷积大小(2,2), kennel_size=(2,3),意味着卷积大小(2,3),即非正方形卷积
        stride: 步长,默认为1, 与kennel_size类似, stride=2, 意味着步长上下左右扫描皆为2, stride=(2,3),左右三秒步长为2,上下为3
        padding: 零填充
        groups: 从输入通道到输出通道阻塞连接数,通道分组的参数,输入通道数,输出通道数必须同时满足被groups整除
        groups:如果输出通道为6,输入通道也为6,假设groups为3,卷积核为1*1,;则卷积核的shape为2*1*1,即把输入通道分成了3份;那么卷积核的个数呢?之前是由输出通道
            决定的,这里也一样,输出通道为6,那么就有6个卷积核。这里实际是将卷积核也平分为groups份,在groups份特征图上计算,以输入输出都为6为例,每个2*h*w的特征图子层
            就有且仅有2个卷积核,最后相加恰好是6,这里可以起到的作用是不同通道分别计算特征
        bias:如果为True,则向输出添加可学习的偏置
        '''
 
        # conv调用nn.Conv2d函数,  p采用autopad, 不使用偏置bias=False 因为下面做融合时,这个卷积的bias会被消掉, 所以不用
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k,p), groups=g, bias=False)
        self.bn = nn.Batch2dNorm2d(c2)
        # 如果act是true则使用nn.SiLU 否则不使用激活函数
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
 
    def forward(self, x):   # 前向计算,网络执行的顺序是根据forward函数来决定的
        return self.act(self.bn(self.conv(x)))      # 先conv卷积然后在bn最后act激活
 
    def fuseforward(self,x):    # 前向融合计算
        return self.act(self.conv(x))  # 这里只有卷积和激活

标准Conv层包含一层卷积、一层BN和一层leakyrelu(CBL)

BottleneckCSP

class Bottleneck(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, shortcut, groups, expansion
        super(Bottleneck, self).__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, 3, 1, g=g)
        self.add = shortcut and c1 == c2
 
    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

paras:c1:bottleneck的输入维度
c2:bottleneck的输出维度
shortcut:是否加入shortcut连接
g:通道分组数,c1、c2必须都能整除group
e:expand_ratio,使用0.5即变为输入的1/2

SPP

OD--YOLOv5代码学习_第3张图片
SPP模块用来解决输入图像尺寸不统一的问题,不同大小特征的融合。有利于待检测图像中目标大小差异较大的情况。
2.数据读取与增强
YOLOv5的数据读取是在utils下面的datasets.py里完成的
(1)读取的参数设置
都是在LoadImagesAndLabels类下说明

    def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False,
                 cache_images=False, single_cls=False, stride=32, pad=0.0, prefix=''):
        self.img_size = img_size
        self.augment = augment
        self.hyp = hyp
        self.image_weights = image_weights
        self.rect = False if image_weights else rect
        self.mosaic = self.augment and not self.rect  # load 4 images at a time into a mosaic (only during training)
        self.mosaic_border = [-img_size // 2, -img_size // 2]
        self.stride = stride
        self.path = path
        self.albumentations = Albumentations() if augment else None

能指定img_size和batchsize以及augment,调参的时候调这些就够了
(2)读取数据集路径下的文件
用了一个异常处理机制来读取

        try:
            f = []  # image files
            for p in path if isinstance(path, list) else [path]:
                p = Path(p)  # os-agnostic
                if p.is_dir():  # dir
                    f += glob.glob(str(p / '**' / '*.*'), recursive=True)
                    # f = list(p.rglob('**/*.*'))  # pathlib
                elif p.is_file():  # file
                    with open(p, 'r') as t:
                        t = t.read().strip().splitlines()
                        parent = str(p.parent) + os.sep
                        f += [x.replace('./', parent) if x.startswith('./') else x for x in t]  # local to global path
                        # f += [p.parent / x.lstrip(os.sep) for x in t]  # local to global path (pathlib)
                else:
                    raise Exception(f'{prefix}{p} does not exist')
            self.img_files = sorted([x.replace('/', os.sep) for x in f if x.split('.')[-1].lower() in IMG_FORMATS])
            # self.img_files = sorted([x for x in f if x.suffix[1:].lower() in img_formats])  # pathlib
            assert self.img_files, f'{prefix}No images found'
        except Exception as e:
            raise Exception(f'{prefix}Error loading data from {path}: {e}\nSee {HELP_URL}')

其中os.path.isfile判断某一对象(需提供绝对路径)是否为文件
os.path.isdir()判断某一对象(需提供绝对路径)是否为目录
os.listdir():返回一个列表,其中包含有指定路径下的目录和文件的名称
获取数据集路径的两个判断说明有两种读取数据集的方式,一种是把图片路径放到txt文件中去别的地方读,第二种就是直接将folder放到path下

你可能感兴趣的:(深度学习,python)