Простая реализация YOLOV5 на основе Pytorch

Идентификация изображения
Простая реализация YOLOV5 на основе Pytorch

[Введение GiantPandaCV] Эта статья в основном направлена ​​на реализацию кода сетевой структуры версии YOLOV5-Pytorch, упрощение понимания кода и упрощение файла конфигурации, а также дальнейшее объединение некоторых из четырех сетевых структур YOLOV5, В этом процессе сеть V5 имеет более глубокое понимание понимания. Наконец, я надеюсь, что читатели, прочитавшие эту статью, смогут что-то почерпнуть, и я надеюсь пообщаться с вами по поводу оптимизации некоторых методов написания в коде.

1. Полный сетевой код

  1. Идеи реализации, общая структура кода в v5 была сохранена, потому что эта часть кода относительно проста для понимания, общий код выглядит относительно просто, в основном построение общей структуры сети путем разбора файла yaml для некоторых разработчиков не очень дружелюбный.

  2. некоторые переменные в сети

    c1:输入通道 c2:输出通道  k:卷积核大小  s:步长 p:padding g:分组  act;激活函数 e:扩展倍数
    gw:网络宽度因子  gd:网络深度因子  n:模块重复次数  nc:类别数
    
    
  3. код магистральной сетиCSPDarknet53

  4. 1

    import torch
    import torch.nn as nn
    
    
    def autopad(k, p=None):  # kernel, padding
        # Pad to 'same'
        if p is None:
            p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # auto-pad
        return p
    
    
    class CBL(nn.Module):
    
        def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, e=1.0):
            super(CBL, self).__init__()
            c1 = round(c1 * e)
            c2 = round(c2 * e)
            self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
            self.bn = nn.BatchNorm2d(c2)
            self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
    
        def forward(self, x):
            return self.act(self.bn(self.conv(x)))
    
    
    class Focus(nn.Module):
    
        def __init__(self, c1, c2, k=3, s=1, p=1, g=1, act=True, e=1.0):
            super(Focus, self).__init__()
            c2 = round(c2 * e)
            self.conv = CBL(c1 * 4, c2, k, s, p, g, act)
    
        def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2)
            flatten_channel = torch.cat([x[..., 0::2, 0::2],
                                         x[..., 1::2, 0::2],
                                         x[..., 0::2, 1::2],
                                         x[..., 1::2, 1::2]], dim=1)
            return self.conv(flatten_channel)
    
    
    class SPP(nn.Module):
    
        def __init__(self, c1, c2, k=(5, 9, 13), e=1.0):
            super(SPP, self).__init__()
            c1 = round(c1 * e)
            c2 = round(c2 * e)
            c_ = c1 // 2
            self.cbl_before = CBL(c1, c_, 1, 1)
            self.max_pool = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])
            self.cbl_after = CBL(c_ * 4, c2, 1, 1)
    
        def forward(self, x):  
            x = self.cbl_before(x)
            x_cat = torch.cat([x] + [m(x) for m in self.max_pool], 1)
            return self.cbl_after(x_cat)
    
    
    class ResUnit_n(nn.Module):
    
        def __init__(self, c1, c2, n):
            super(ResUnit_n, self).__init__()
            self.shortcut = c1 == c2
            res_unit = nn.Sequential(
                CBL(c1, c1, k=1, s=1, p=0),
                CBL(c1, c2, k=3, s=1, p=1)
            )
            self.res_unit_n = nn.Sequential(*[res_unit for _ in range(n)])
    
        def forward(self, x):
            return x + self.res_unit_n(x) if self.shortcut else self.res_unit_n(x)
    
    
    class CSP1_n(nn.Module):
    
        def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, n=1, e=None):
            super(CSP1_n, self).__init__()
    
            c1 = round(c1 * e[1])
            c2 = round(c2 * e[1])
            n = round(n * e[0])
            c_ = c2 // 2
            self.up = nn.Sequential(
                CBL(c1, c_, k, s, autopad(k, p), g, act),
                ResUnit_n(c_, c_, n),
                # nn.Conv2d(c_, c_, 1, 1, 0, bias=False) 这里最新yolov5结构中去掉了,与网上的结构图稍微有些区别
            )
            self.bottom = nn.Conv2d(c1, c_, 1, 1, 0)
            self.tie = nn.Sequential(
                nn.BatchNorm2d(c_ * 2),
                nn.LeakyReLU(),
                nn.Conv2d(c_ * 2, c2, 1, 1, 0, bias=False)
            )
        def forward(self, x):
            total = torch.cat([self.up(x), self.bottom(x)], dim=1)
            out = self.tie(total)
            return out
    
    
    class CSPDarkNet(nn.Module):
    
        def __init__(self, gd=0.33, gw=0.5):
            super(CSPDarkNet, self).__init__()
            self.truck_big = nn.Sequential(
                Focus(3, 64, e=gw),
                CBL(64, 128, k=3, s=2, p=1, e=gw),
                CSP1_n(128, 128, n=3, e=[gd, gw]),
                CBL(128, 256, k=3, s=2, p=1, e=gw),
                CSP1_n(256, 256, n=9, e=[gd, gw]),
    
            )
            self.truck_middle = nn.Sequential(
                CBL(256, 512, k=3, s=2, p=1, e=gw),
                CSP1_n(512, 512, n=9, e=[gd, gw]),
            )
            self.truck_small = nn.Sequential(
                CBL(512, 1024, k=3, s=2, p=1, e=gw),
                SPP(1024, 1024, e=gw)
            )
    
        def forward(self, x):
            h_big = self.truck_big(x)  # torch.Size([2, 128, 76, 76])
            h_middle = self.truck_middle(h_big)
            h_small = self.truck_small(h_middle)
            return h_big, h_middle, h_small
    
    
    def darknet53(gd, gw, pretrained, **kwargs):
        model = CSPDarkNet(gd, gw)
        if pretrained:
            if isinstance(pretrained, str):
                model.load_state_dict(torch.load(pretrained))
            else:
                raise Exception(f"darknet request a pretrained path. got[{pretrained}]")
        return model
    
    
  5. Построение общей сети

    import torch
    import torch.nn as nn
    from cspdarknet53v5 import darknet53
    
    
    def autopad(k, p=None):  # kernel, padding
        # Pad to 'same'
        if p is None:
            p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # auto-pad
        return p
    
    
    class UpSample(nn.Module):
    
        def __init__(self):
            super(UpSample, self).__init__()
            self.up_sample = nn.Upsample(scale_factor=2, mode='nearest')
    
        def forward(self, x):
            return self.up_sample(x)
    
    
    class CBL(nn.Module):
    
        def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, e=1.0):
            super(CBL, self).__init__()
            c1 = round(c1 * e)
            c2 = round(c2 * e)
            self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
            self.bn = nn.BatchNorm2d(c2)
            self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
    
        def forward(self, x):
            return self.act(self.bn(self.conv(x)))
    
    
    class ResUnit_n(nn.Module):
    
        def __init__(self, c1, c2, n):
            super(ResUnit_n, self).__init__()
            self.shortcut = c1 == c2
            res_unit = nn.Sequential(
                CBL(c1, c1, k=1, s=1, p=0),
                CBL(c1, c2, k=3, s=1, p=1)
            )
            self.res_unit_n = nn.Sequential(*[res_unit for _ in range(n)])
    
        def forward(self, x):
            return x + self.res_unit_n(x) if self.shortcut else self.res_unit_n(x)
    
    
    class CSP1_n(nn.Module):
    
        def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, n=1, e=None):
            super(CSP1_n, self).__init__()
    
            c1 = round(c1 * e[1])
            c2 = round(c2 * e[1])
            n = round(n * e[0])
            c_ = c2 // 2
            self.up = nn.Sequential(
                CBL(c1, c_, k, s, autopad(k, p), g, act),
                ResUnit_n(c_, c_, n),
                # nn.Conv2d(c_, c_, 1, 1, 0, bias=False) 这里最新yolov5结构中去掉了,与网上的结构图稍微有些区别
            )
            self.bottom = nn.Conv2d(c1, c_, 1, 1, 0)
            self.tie = nn.Sequential(
                nn.BatchNorm2d(c_ * 2),
                nn.LeakyReLU(),
                nn.Conv2d(c_ * 2, c2, 1, 1, 0, bias=False)
            )
    
        def forward(self, x):
            total = torch.cat([self.up(x), self.bottom(x)], dim=1)
            out = self.tie(total)
            return out
    
    
    class CSP2_n(nn.Module):
    
        def __init__(self, c1, c2, e=0.5, n=1):
            super(CSP2_n, self).__init__()
            c_ = int(c1 * e)
            cbl_2 = nn.Sequential(
                CBL(c1, c_, 1, 1, 0),
                CBL(c_, c_, 1, 1, 0),
            )
            self.cbl_2n = nn.Sequential(*[cbl_2 for _ in range(n)])
            self.conv_up = nn.Conv2d(c_, c_, 1, 1, 0)
            self.conv_bottom = nn.Conv2d(c1, c_, 1, 1, 0)
            self.tie = nn.Sequential(
                nn.BatchNorm2d(c_ * 2),
                nn.LeakyReLU(),
                nn.Conv2d(c_ * 2, c2, 1, 1, 0)
            )
    
        def forward(self, x):
            up = self.conv_up(self.cbl_2n(x))
            total = torch.cat([up, self.conv_bottom(x)], dim=1)
            out = self.tie(total)
            return out
    
    
    class yolov5(nn.Module):
    
        def __init__(self, nc=80, gd=0.33, gw=0.5):
            super(yolov5, self).__init__()
            # ------------------------------Backbone--------------------------------
            self.backbone = darknet53(gd, gw, None)
    
            # ------------------------------Neck------------------------------------
            self.neck_small = nn.Sequential(
                CSP1_n(1024, 1024, n=3, e=[gd, gw]),
                CBL(1024, 512, 1, 1, 0, e=gw)
            )
            self.up_middle = nn.Sequential(
                UpSample()
            )
            self.out_set_middle = nn.Sequential(
                CSP1_n(1024, 512, n=3, e=[gd, gw]),
                CBL(512, 256, 1, 1, 0, e=gw),
            )
            self.up_big = nn.Sequential(
                UpSample()
            )
            self.out_set_tie_big = nn.Sequential(
                CSP1_n(512, 256, n=3, e=[gd, gw])
            )
    
            self.pan_middle = nn.Sequential(
                CBL(256, 256, 3, 2, 1, e=gw)
            )
            self.out_set_tie_middle = nn.Sequential(
                CSP1_n(512, 512, n=3, e=[gd, gw])
            )
            self.pan_small = nn.Sequential(
                CBL(512, 512, 3, 2, 1, e=gw)
            )
            self.out_set_tie_small = nn.Sequential(
                CSP1_n(1024, 1024, n=3, e=[gd, gw])
            )
            # ------------------------------Prediction--------------------------------
            # prediction
            big_ = round(256 * gw)
            middle = round(512 * gw)
            small_ = round(1024 * gw)
            self.out_big = nn.Sequential(
                nn.Conv2d(big_, 3 * (5 + nc), 1, 1, 0)
            )
            self.out_middle = nn.Sequential(
                nn.Conv2d(middle, 3 * (5 + nc), 1, 1, 0)
            )
            self.out_small = nn.Sequential(
                nn.Conv2d(small_, 3 * (5 + nc), 1, 1, 0)
            )
    
        def forward(self, x):
            h_big, h_middle, h_small = self.backbone(x)
            neck_small = self.neck_small(h_small)  
            # ----------------------------up sample 38*38-------------------------------
            up_middle = self.up_middle(neck_small)
            middle_cat = torch.cat([up_middle, h_middle], dim=1)
            out_set_middle = self.out_set_middle(middle_cat)
    
            # ----------------------------up sample 76*76-------------------------------
            up_big = self.up_big(out_set_middle)  # torch.Size([2, 128, 76, 76])
            big_cat = torch.cat([up_big, h_big], dim=1)
            out_set_tie_big = self.out_set_tie_big(big_cat)
    
            # ----------------------------PAN 36*36-------------------------------------
            neck_tie_middle = torch.cat([self.pan_middle(out_set_tie_big), out_set_middle], dim=1)
            up_middle = self.out_set_tie_middle(neck_tie_middle)
    
            # ----------------------------PAN 18*18-------------------------------------
            neck_tie_small = torch.cat([self.pan_small(up_middle), neck_small], dim=1)
            out_set_small = self.out_set_tie_small(neck_tie_small)
    
            # ----------------------------prediction-------------------------------------
            out_small = self.out_small(out_set_small)
            out_middle = self.out_middle(up_middle)
            out_big = self.out_big(out_set_tie_big)
    
            return out_small, out_middle, out_big
    
    
    if __name__ == '__main__':
        # 配置文件的写法
        config = {
            #            gd    gw
            'yolov5s': [0.33, 0.50],
            'yolov5m': [0.67, 0.75],
            'yolov5l': [1.00, 1.00],
            'yolov5x': [1.33, 1.25]
        }
        # 修改一次文件名字
        net_size = config['yolov5x']
        net = yolov5(nc=80, gd=net_size[0], gw=net_size[1])
        print(net)
        a = torch.randn(2, 3, 416, 416)
        y = net(a)
        print(y[0].shape, y[1].shape, y[2].shape)
    
    
    

Во-вторых, анализ сетевой структуры

  1. Остаточный блок ResUnit_n

    class ResUnit_n(nn.Module):
    
        def __init__(self, c1, c2, n):
            super(ResUnit_n, self).__init__()
            self.shortcut = c1 == c2
            res_unit = nn.Sequential(
                CBL(c1, c1, k=1, s=1, p=0),
                CBL(c1, c2, k=3, s=1, p=1)
            )
            self.res_unit_n = nn.Sequential(*[res_unit for _ in range(n)])
    
        def forward(self, x):
            return x + self.res_unit_n(x) if self.shortcut else self.res_unit_n(x)
    
    
  2. Структура CSP1_x

    Идеи построения: код CSP1_n оптимизирован, и CSP рассматривается как лежащее животное, с головой слева и хвостом справа, вверху место ближе к небу, внизу ближе к земле, а галстук хвост животного

    class CSP1_n(nn.Module):
    
        def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, n=1, e=None):
            super(CSP1_n, self).__init__()
    
            c1 = round(c1 * e[1])
            c2 = round(c2 * e[1])
            n = round(n * e[0])
            c_ = c2 // 2
            self.up = nn.Sequential(
                CBL(c1, c_, k, s, autopad(k, p), g, act),
                ResUnit_n(c_, c_, n),
                # nn.Conv2d(c_, c_, 1, 1, 0, bias=False) 这里最新yolov5结构中去掉了,与网上的结构图稍微有些区别
            )
            self.bottom = nn.Conv2d(c1, c_, 1, 1, 0)
            self.tie = nn.Sequential(
                nn.BatchNorm2d(c_ * 2),
                nn.LeakyReLU(),
                nn.Conv2d(c_ * 2, c2, 1, 1, 0, bias=False)
            )
    
        def forward(self, x):
            total = torch.cat([self.up(x), self.bottom(x)], dim=1)
            out = self.tie(total)
            return out
    
    
  3. Строительство магистральной сети CSPDarknet

    class CSPDarkNet(nn.Module):
    
        def __init__(self, gd=0.33, gw=0.5):
            super(CSPDarkNet, self).__init__()
            self.truck_big = nn.Sequential(
                Focus(3, 64, e=gw),
                CBL(64, 128, k=3, s=2, p=1, e=gw),
                CSP1_n(128, 128, n=3, e=[gd, gw]),
                CBL(128, 256, k=3, s=2, p=1, e=gw),
                CSP1_n(256, 256, n=9, e=[gd, gw]),
    
            )
            self.truck_middle = nn.Sequential(
                CBL(256, 512, k=3, s=2, p=1, e=gw),
                CSP1_n(512, 512, n=9, e=[gd, gw]),
            )
            self.truck_small = nn.Sequential(
                CBL(512, 1024, k=3, s=2, p=1, e=gw),
                SPP(1024, 1024, e=gw)
            )
    
        def forward(self, x):
            h_big = self.truck_big(x)  
            h_middle = self.truck_middle(h_big)
            h_small = self.truck_small(h_middle)
            return h_big, h_middle, h_small
    
    
  4. Общая конструкция сети

    class yolov5(nn.Module):
    
        def __init__(self, nc=80, gd=0.33, gw=0.5):
            super(yolov5, self).__init__()
            # ------------------------------Backbone------------------------------------
            self.backbone = darknet53(gd, gw, None)
    
            # ------------------------------Neck------------------------------------
            self.neck_small = nn.Sequential(
                CSP1_n(1024, 1024, n=3, e=[gd, gw]),
                CBL(1024, 512, 1, 1, 0, e=gw)
            )
            # FPN:2次上采样 自顶而下 完成语义信息增强
            self.up_middle = nn.Sequential(
                UpSample()
            )
            self.out_set_middle = nn.Sequential(
                CSP1_n(1024, 512, n=3, e=[gd, gw]),
                CBL(512, 256, 1, 1, 0, e=gw),
            )
            self.up_big = nn.Sequential(
                UpSample()
            )
            self.out_set_tie_big = nn.Sequential(
                CSP1_n(512, 256, n=3, e=[gd, gw])
            )
    
            # PAN:2次下采样 自底而上 完成位置信息增强
            self.pan_middle = nn.Sequential(
                CBL(256, 256, 3, 2, 1, e=gw)
            )
            self.out_set_tie_middle = nn.Sequential(
                CSP1_n(512, 512, n=3, e=[gd, gw])
            )
            self.pan_small = nn.Sequential(
                CBL(512, 512, 3, 2, 1, e=gw)
            )
            self.out_set_tie_small = nn.Sequential(
                # CSP2_n(512, 512)
                CSP1_n(1024, 1024, n=3, e=[gd, gw])
            )
            # ------------------------------Prediction------------------------------------
            # prediction
            big_ = round(256 * gw)
            middle = round(512 * gw)
            small_ = round(1024 * gw)
            self.out_big = nn.Sequential(
                nn.Conv2d(big_, 3 * (5 + nc), 1, 1, 0)
            )
            self.out_middle = nn.Sequential(
                nn.Conv2d(middle, 3 * (5 + nc), 1, 1, 0)
            )
            self.out_small = nn.Sequential(
                nn.Conv2d(small_, 3 * (5 + nc), 1, 1, 0)
            )
    
        def forward(self, x):
            h_big, h_middle, h_small = self.backbone(x)
            neck_small = self.neck_small(h_small)  
            # ----------------------------up sample 38*38--------------------------------
            up_middle = self.up_middle(neck_small)
            middle_cat = torch.cat([up_middle, h_middle], dim=1)
            out_set_middle = self.out_set_middle(middle_cat)
    
            # ----------------------------up sample 76*76--------------------------------
            up_big = self.up_big(out_set_middle)  # torch.Size([2, 128, 76, 76])
            big_cat = torch.cat([up_big, h_big], dim=1)
            out_set_tie_big = self.out_set_tie_big(big_cat)
    
            # ----------------------------PAN 36*36-------------------------------------
            neck_tie_middle = torch.cat([self.pan_middle(out_set_tie_big), out_set_middle], dim=1)
            up_middle = self.out_set_tie_middle(neck_tie_middle)
    
            # ----------------------------PAN 18*18-------------------------------------
            neck_tie_small = torch.cat([self.pan_small(up_middle), neck_small], dim=1)
            out_set_small = self.out_set_tie_small(neck_tie_small)
    
            # ----------------------------prediction-------------------------------------
            out_small = self.out_small(out_set_small)
            out_middle = self.out_middle(up_middle)
            out_big = self.out_big(out_set_tie_big)
    
            return out_small, out_middle, out_big
    
    
  5. Четыре размера файлов конфигурации записываются в словарь конфигурации, которые являются параметрами конфигурации сетевой модели.Другие параметры не помещаются в файл конфигурации, и категория также может быть помещена в файл конфигурации. В приведенном выше сетевом коде параметр ширины является переменнойeа затем передается в каждую сеть.

    config = {
            #            gd    gw
            'yolov5s': [0.33, 0.50],
            'yolov5m': [0.67, 0.75],
            'yolov5l': [1.00, 1.00],
            'yolov5x': [1.33, 1.25]
        }
        # 修改一次文件名字
        net_size = config['yolov5x']
        net = yolov5(nc=80, gd=net_size[0], gw=net_size[1])
    
    

Исходный код v5 записывает часть Head в v3 в класс Detect.Основная причина в том, что в v5 используются некоторые приемы обучения.В Detect есть обучение и две части.Исходный код v5 представляет собой сравнение для начинающих.Сначала сложно прежде всего, метод записи сети требует относительно высокой способности кодирования. Тем не менее, этот метод конфигурационного файла yaml для настройки сети использовался во многих компаниях, это может быть способом написания кода в будущих проектах, и этот способ написания еще необходимо освоить.

3. Резюме

  1. Мое личное мнение состоит в том, что дизайн такого рода сети и способ написания кода должны быть творческими и образными, и код должен быть написан как элегантное чувство в романах о боевых искусствах. (В Интернете есть много диаграмм структуры сети. Я смоделировал диаграмму структуры Jiang Dabai и скорректировал ее на основе диаграммы структуры и последней версии кода v5).

  2. Появился в последней сетевой структуре v5TransformerСтруктура, есть своего рода ритм, который инженерия в области CV собирается изменить, вы можете узнать об этом.


Добро пожаловать в GiantPandaCV, где вы увидите эксклюзивный обмен глубокими знаниями, настаиваете на оригинальности и делитесь свежими знаниями, которые мы изучаем каждый день. ( • ̀ω•́ )✧

Если у вас есть вопросы по статье или вы хотите присоединиться к группе обмена, добавьте BBuf WeChat:

图片

QR код

Чтобы облегчить читателям получение информации и автору нашей официальной учетной записи, чтобы выпустить некоторые обновления проекта Github, мы создали группу QQ, QR-код выглядит следующим образом, вы можете присоединиться, если вы заинтересованы.

图片

Публичный аккаунт QQ exchange group

В этой статье используетсяПомощник по синхронизации статейСинхронизировать