yolo verbessert und ersetzt das VanillaNet-Backbone

Huawei Noah schlägt VanillaNet vor – ein neues visuelles Backbone, minimalistisch und leistungsstark

论文地址:https://arxiv.org/pdf/2305.12972.pdf

Code-Adresse:GitHub – huawei-noah/VanillaNet

Einführung in VanillaNet

Im Mittelpunkt des zugrunde liegenden Modells steht die Philosophie „mehr Differenz“, die durch die atemberaubenden Erfolge von Computer Vision und natürlicher Sprachverarbeitung veranschaulicht wird. Optimierungsherausforderungen und die inhärente Komplexität von Transformer-Modellen erfordern jedoch einen Paradigmenwechsel hin zur Einfachheit. In dieser Studie stellen wir VanillaNet vor, eine neuronale Netzwerkarchitektur, deren Design Eleganz vereint. Durch den Verzicht auf große Tiefe, Abkürzungen und komplexe Vorgänge wie Selbstaufmerksamkeit ist VanillaNet erfrischend einfach und dennoch äußerst leistungsstark. Jede Schicht wird sorgfältig in eine kompakte und einfache Struktur umgewandelt, und nichtlineare Aktivierungsfunktionen werden nach dem Training beschnitten, um die ursprüngliche Architektur wiederherzustellen. VanillaNet überwindet die Herausforderungen der inhärenten Komplexität und ist daher ideal für Umgebungen mit begrenzten Ressourcen. Seine leicht verständliche und stark vereinfachte Architektur eröffnet neue Möglichkeiten für eine effiziente Bereitstellung. Umfangreiche Experimente zeigen, dass VanillaNet eine vergleichbare Leistung wie bekannte tiefe neuronale Netze und visuelle Transformer bietet und die Leistungsfähigkeit des Minimalismus beim Deep Learning demonstriert. Diese visionäre Reise von VanillaNet hat erhebliches Potenzial, den Status quo der zugrunde liegenden Modelle neu zu definieren und in Frage zu stellen und so einen neuen Weg für elegantes und effektives Modelldesign zu schaffen.

        In den letzten Jahrzehnten haben Forscher einen gewissen Konsens über den grundlegenden Aufbau neuronaler Netze erzielt. Die meisten hochmodernen Bildklassifizierungsnetzwerkarchitekturen sollten aus drei Teilen bestehen:

  1. Der Backbone-Block wird verwendet, um das Eingabebild von 3 Kanälen in mehrere Kanäle umzuwandeln und ein Downsampling durchzuführen, ein nützliches Informationsthema zum Lernen.
  2. Der Körper besteht normalerweise aus vier Stufen, wobei jede Stufe durch Stapeln identischer Blöcke entsteht. Nach jeder Stufe werden die Kanäle des Features erweitert, während Höhe und Breite abnehmen. Verschiedene Netzwerke nutzen und stapeln unterschiedliche Arten von Blöcken, um tiefe Modelle zu erstellen.
  3. Vollständig verbundene Layer-Klassifizierungsausgabe.

        ​​​​​Trotz des Erfolgs bestehender tiefer Netzwerke nutzen sie eine große Anzahl komplexer Schichten, um High-Level-Features für die folgenden Aufgaben zu extrahieren. Beispielsweise erfordert das berühmte ResNet 34 oder 50 Schichten mit Shortcat, um eine Top-1-Genauigkeit von mehr als 70 % auf ImageNet zu erreichen. Die Basisversion von Vit besteht aus 62 Schichten, da für die Berechnung von K, Q und V in der Selbstaufmerksamkeit mehrere Schichten erforderlich sind. Da es immer mehr KI-Chips gibt, liegt der Engpass bei der Inferenzgeschwindigkeit neuronaler Netze nicht mehr in FLOPs oder Parametern, da moderne GPUs problemlos parallele Berechnungen durchführen können. Im Gegensatz dazu behindern ihr komplexer Aufbau und ihre große Tiefe ihre Geschwindigkeit. Zu diesem Zweck schlagen wir ein Vanilla-Netzwerk vor, nämlich VanillaNet, dessen Rahmendiagramm in Abbildung 1 dargestellt ist. Wir folgen dem beliebten neuronalen Netzwerkdesign, einschließlich Backbone, Body und vollständig verbundenen Schichten. Im Gegensatz zu bestehenden tiefen Netzwerken verwenden wir in jeder Phase nur eine Schicht, um ein äußerst einfaches Netzwerk mit möglichst wenigen Schichten aufzubauen. Das Merkmal dieses Netzwerks besteht darin, dass es keine Verknüpfung verwendet (eine Verknüpfung erhöht die Speicherzugriffszeit) und keine komplexen Module wie Selbstaufmerksamkeit enthält.

Beim Deep Learning ist es üblich, die Leistung eines Modells durch die Einführung höherer Kapazitäten während der Trainingsphase zu steigern. Zu diesem Zweck schlagen wir vor, tiefgreifende Trainingstechniken zu nutzen, um die Fähigkeiten des vorgeschlagenen VanillaNet während des Trainings zu verbessern.

Optimierungsstrategie 1: Tiefes Training, flaches Denken Um die Nichtlinearität der VanillaNet-Architektur zu verbessern, haben wir zunächst eine Strategie für tiefes Training (Deep Training) vorgeschlagen. Während des Trainingsprozesses Split Eine Faltungsschicht in zwei Faltungsschichten aufteilen und dazwischen die folgenden nichtlinearen Operationen einfügen:

        Von diesen ist A eine traditionelle nichtlineare Aktivierungsfunktion, die einfachste ist ReLU. λ wird mit der Optimierung des Modells allmählich 1, und die beiden Faltungsschichten können zu einer Schicht zusammengeführt werden, ohne die Struktur von VanillaNet zu ändern .

Optimierungsstrategie 2: Aktivierungsfunktion ändern Da wir die Nichtlinearität von VanillaNet verbessern möchten, besteht eine direktere Lösung darin, eine Aktivierungsfunktion mit stärkerer Nichtlinearität und diese Aktivierungsfunktion zu haben Gute Parallelität und hohe Geschwindigkeit? Um diese sowohl notwendige als auch notwendige Konstitution zu erreichen, schlagen wir eine auf Serieninspiration basierende Aktivierungsfunktion vor, die mehrere ReLU-Gewichte und -Bias stapelt:

        ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​?

Verbesserungen in YOLO

Nehmen Sie als Beispiel das Backbone von yolov7-tiny. Wenn Sie v5v8 verwenden möchten, können Sie das Backbone einfach in die v5v8-Konfiguration kopieren.

Konfigurieren Sie zunächst die Datei yolov7-tiny-vanilla.yaml:

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

activation: nn.ReLU()
# anchors
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

# yolov7-tiny backbone
backbone:
  # [from, number, module, args] c2, k=1, s=1, p=None, g=1, act=True
  [[-1, 1, VanillaStem, [64, 4, 4, None, 1]],  # 0-P1/4
   [-1, 1, VanillaBlock, [256, 1, 2, None, 1]],  # 1-P2/8
   [-1, 1, VanillaBlock, [512, 1, 2, None, 1]],  # 2-P3/16
   [-1, 1, VanillaBlock, [1024, 1, 2, None, 1]],  # 3-P4/32
  ]

# yolov7-tiny head
head:
  [[1, 1, Conv, [128, 1, 1, None, 1]],  # 4
   [2, 1, Conv, [256, 1, 1, None, 1]],  # 5
   [3, 1, Conv, [512, 1, 1, None, 1]],  # 6

   [-1, 1, SPPCSPCSIM, [256]], # 7

   [-1, 1, Conv, [128, 1, 1, None, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [5, 1, Conv, [128, 1, 1, None, 1]], # route backbone P3
   [[-1, -2], 1, Concat, [1]], # 11

   [-1, 1, ELAN, [128, 1, 1, None, 1]],  # 12

   [-1, 1, Conv, [64, 1, 1, None, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [4, 1, Conv, [64, 1, 1, None, 1]], # route backbone P2
   [[-1, -2], 1, Concat, [1]],  # 16

   [-1, 1, ELAN, [64, 1, 1, None, 1]],  # 17

   [-1, 1, Conv, [128, 3, 2, None, 1]],
   [[-1, 12], 1, Concat, [1]],

   [-1, 1, ELAN, [128, 1, 1, None, 1]],  # 20

   [-1, 1, Conv, [256, 3, 2, None, 1]],
   [[-1, 7], 1, Concat, [1]],

   [-1, 1, ELAN, [256, 1, 1, None, 1]],  # 23

   [17, 1, Conv, [128, 3, 1, None, 1]],
   [20, 1, Conv, [256, 3, 1, None, 1]],
   [23, 1, Conv, [512, 3, 1, None, 1]],

   [[24,25,26], 1, Detect, [nc, anchors]],   # Detect(P3, P4, P5)
  ]

Das ELAN-Modul ist eine vereinfachte Version des entsprechenden Moduls in der ursprünglichen Konfigurationsdatei.

Vereinfachte Methode:yolov7 vereinfacht die Netzwerk-Yaml-Konfiguration file_athrunsunnys Blog-CSDN-Blog

Um sicherzustellen, dass sich die Gesamtparameter des Modells zu sehr von denen von v5s und v7tiny unterscheiden, ist die Anzahl der Kanäle in VanillaBlock kleiner eingestellt als im Originalartikel.

Fügen Sie common.py hinzu

class activation(nn.ReLU):
    def __init__(self, dim, act_num=3, deploy=False):
        super(activation, self).__init__()
        self.act_num = act_num
        self.deploy = deploy
        self.dim = dim
        self.weight = torch.nn.Parameter(torch.randn(dim, 1, act_num * 2 + 1, act_num * 2 + 1))
        if deploy:
            self.bias = torch.nn.Parameter(torch.zeros(dim))
        else:
            self.bias = None
            self.bn = nn.BatchNorm2d(dim, eps=1e-6)
        nn.init.trunc_normal_(self.weight, std=.02)

    def forward(self, x):
        if self.deploy:
            return torch.nn.functional.conv2d(
                super(activation, self).forward(x),
                self.weight, self.bias, padding=self.act_num, groups=self.dim)
        else:
            return self.bn(torch.nn.functional.conv2d(
                super(activation, self).forward(x),
                self.weight, padding=self.act_num, groups=self.dim))

    def _fuse_bn_tensor(self, weight, bn):
        kernel = weight
        running_mean = bn.running_mean
        running_var = bn.running_var
        gamma = bn.weight
        beta = bn.bias
        eps = bn.eps
        std = (running_var + eps).sqrt()
        t = (gamma / std).reshape(-1, 1, 1, 1)
        return kernel * t, beta + (0 - running_mean) * gamma / std

    def switch_to_deploy(self):
        kernel, bias = self._fuse_bn_tensor(self.weight, self.bn)
        self.weight.data = kernel
        self.bias = torch.nn.Parameter(torch.zeros(self.dim))
        self.bias.data = bias
        self.__delattr__('bn')
        self.deploy = True


class VanillaStem(nn.Module):
    def __init__(self, in_chans=3, dims=96,
                 k=0, s=0, p=None,g=0, act_num=3, deploy=False, ada_pool=None, **kwargs):
        super().__init__()
        self.deploy = deploy
        stride, padding = (4, 0) if not ada_pool else (3, 1)
        if self.deploy:
            self.stem = nn.Sequential(
                nn.Conv2d(in_chans, dims, kernel_size=k, stride=stride, padding=padding),
                activation(dims, act_num, deploy=self.deploy)
            )
        else:
            self.stem1 = nn.Sequential(
                nn.Conv2d(in_chans, dims, kernel_size=k, stride=stride, padding=padding),
                nn.BatchNorm2d(dims, eps=1e-6),
            )
            self.stem2 = nn.Sequential(
                nn.Conv2d(dims, dims, kernel_size=1, stride=1),
                nn.BatchNorm2d(dims, eps=1e-6),
                activation(dims, act_num)
            )
        self.act_learn = 1
        self.apply(self._init_weights)

    def _init_weights(self, m):
        if isinstance(m, (nn.Conv2d, nn.Linear)):
            nn.init.trunc_normal_(m.weight, std=.02)
            nn.init.constant_(m.bias, 0)

    def forward(self, x):
        if self.deploy:
            x = self.stem(x)
        else:
            x = self.stem1(x)
            x = torch.nn.functional.leaky_relu(x, self.act_learn)
            x = self.stem2(x)

        return x

    def _fuse_bn_tensor(self, conv, bn):
        kernel = conv.weight
        bias = conv.bias
        running_mean = bn.running_mean
        running_var = bn.running_var
        gamma = bn.weight
        beta = bn.bias
        eps = bn.eps
        std = (running_var + eps).sqrt()
        t = (gamma / std).reshape(-1, 1, 1, 1)
        return kernel * t, beta + (bias - running_mean) * gamma / std

    def switch_to_deploy(self):
        self.stem2[2].switch_to_deploy()
        kernel, bias = self._fuse_bn_tensor(self.stem1[0], self.stem1[1])
        self.stem1[0].weight.data = kernel
        self.stem1[0].bias.data = bias
        kernel, bias = self._fuse_bn_tensor(self.stem2[0], self.stem2[1])
        self.stem1[0].weight.data = torch.einsum('oi,icjk->ocjk', kernel.squeeze(3).squeeze(2),
                                                 self.stem1[0].weight.data)
        self.stem1[0].bias.data = bias + (self.stem1[0].bias.data.view(1, -1, 1, 1) * kernel).sum(3).sum(2).sum(1)
        self.stem = torch.nn.Sequential(*[self.stem1[0], self.stem2[2]])
        self.__delattr__('stem1')
        self.__delattr__('stem2')
        self.deploy = True


class VanillaBlock(nn.Module):
    def __init__(self, dim, dim_out,k=0 , stride=2,p=None,g=0, ada_pool=None,act_num=3 ,deploy=False):
        super().__init__()
        self.act_learn = 1
        self.deploy = deploy
        if self.deploy:
            self.conv = nn.Conv2d(dim, dim_out, kernel_size=1)
        else:
            self.conv1 = nn.Sequential(
                nn.Conv2d(dim, dim, kernel_size=1),
                nn.BatchNorm2d(dim, eps=1e-6),
            )
            self.conv2 = nn.Sequential(
                nn.Conv2d(dim, dim_out, kernel_size=1),
                nn.BatchNorm2d(dim_out, eps=1e-6)
            )

        if not ada_pool:
            self.pool = nn.Identity() if stride == 1 else nn.MaxPool2d(stride)
        else:
            self.pool = nn.Identity() if stride == 1 else nn.AdaptiveMaxPool2d((ada_pool, ada_pool))

        self.act = activation(dim_out, act_num, deploy=self.deploy)

    def forward(self, x):
        if self.deploy:
            x = self.conv(x)
        else:
            x = self.conv1(x)
            # We use leakyrelu to implement the deep training technique.
            x = torch.nn.functional.leaky_relu(x, self.act_learn)
            x = self.conv2(x)

        x = self.pool(x)
        x = self.act(x)
        return x

    def _fuse_bn_tensor(self, conv, bn):
        kernel = conv.weight
        bias = conv.bias
        running_mean = bn.running_mean
        running_var = bn.running_var
        gamma = bn.weight
        beta = bn.bias
        eps = bn.eps
        std = (running_var + eps).sqrt()
        t = (gamma / std).reshape(-1, 1, 1, 1)
        return kernel * t, beta + (bias - running_mean) * gamma / std

    def switch_to_deploy(self):
        kernel, bias = self._fuse_bn_tensor(self.conv1[0], self.conv1[1])
        self.conv1[0].weight.data = kernel
        self.conv1[0].bias.data = bias
        # kernel, bias = self.conv2[0].weight.data, self.conv2[0].bias.data
        kernel, bias = self._fuse_bn_tensor(self.conv2[0], self.conv2[1])
        self.conv = self.conv2[0]
        self.conv.weight.data = torch.matmul(kernel.transpose(1, 3),
                                             self.conv1[0].weight.data.squeeze(3).squeeze(2)).transpose(1, 3)
        self.conv.bias.data = bias + (self.conv1[0].bias.data.view(1, -1, 1, 1) * kernel).sum(3).sum(2).sum(1)
        self.__delattr__('conv1')
        self.__delattr__('conv2')
        self.act.switch_to_deploy()
        self.deploy = True

Nehmen Sie gleichzeitig die folgenden Änderungen in yolo.py vor

1. In der parse_model-Funktion

if m in (Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
                 BottleneckCSP, C3, C3TR, C3SPP, C3Ghost, nn.ConvTranspose2d, DWConvTranspose2d, C3x, SPPCSPC, RepConv,
                 RFEM, ELAN, SPPCSPCSIM,VanillaBlock,VanillaStem):
            c1, c2 = ch[f], args[0]
            if c2 != no:  # if not output
                c2 = make_divisible(c2 * gw, 8)

            args = [c1, c2, *args[1:]]
            if m in [BottleneckCSP, C3, C3TR, C3Ghost, C3x]:
                args.insert(2, n)  # number of repeats
                n = 1

2. Die Sicherungsfunktion von BaseModel

if isinstance(m, (VanillaStem, VanillaBlock)):
                # print(m)
                m.deploy = True
                m.switch_to_deploy()

Testen Sie yolov7-tiny-vanilla.yaml in yolo.py

Supongo que te gusta

Origin blog.csdn.net/athrunsunny/article/details/134451582
Recomendado
Clasificación