Einführung in ReseNet

ResNet ist weit verbreitet. Um zukünftiges Lernen zu erleichtern, haben wir ein besseres Verständnis der ResNet-Netzwerkstruktur. Es gibt verschiedene Arten von ResNet. Hier finden Sie eine kurze Einführung in den Code von ResNet-18.

Papieradresse:

1512.03385.pdf (arxiv.org) https://arxiv.org/pdf/1512.03385.pdf

ResNet

ResNet (Residual Neural Network) wurde von Kaiming He und anderen vier Chinesen von Microsoft Research vorgeschlagen. Durch die Verwendung von ResNet Unit wurde ein 152-schichtiges neuronales Netzwerk erfolgreich trainiert und gewann die Meisterschaft im ILSVRC2015-Wettbewerb mit einer Fehlerrate von 3,57 % top5. Gleichzeitig ist die Anzahl der Parameter geringer als bei VGGNet und der Effekt ist sehr hervorragend. Die Struktur von ResNet kann das Training neuronaler Netze extrem schnell beschleunigen und auch die Genauigkeit des Modells wurde erheblich verbessert. Gleichzeitig erfreut sich ResNet großer Beliebtheit und kann sogar direkt im InceptionNet-Netzwerk verwendet werden.

    Die Hauptidee von ResNet besteht darin, dem Netzwerk einen direkten Verbindungskanal hinzuzufügen, also die Idee des Highway Network. Die bisherige Netzwerkstruktur war eine nichtlineare Transformation des Leistungsinputs, während das Highway Network die Beibehaltung eines bestimmten Anteils des Outputs der vorherigen Netzwerkschicht ermöglichte. Die Idee von ResNet ist der von Highway Network sehr ähnlich und ermöglicht die direkte Weitergabe der ursprünglichen Eingabeinformationen an nachfolgende Schichten, wie in der folgenden Abbildung dargestellt:

In diesem Fall muss das neuronale Netzwerk dieser Schicht nicht die gesamte Ausgabe lernen, sondern den Rest der vorherigen Netzwerkausgabe, daher wird ResNet auch als Restnetzwerk bezeichnet.

Die Innovation liegt in:

Die Idee des Restlernens wird vorgeschlagen. Herkömmliche Faltungsnetzwerke oder vollständig verbundene Netzwerke haben mehr oder weniger Probleme wie Informationsverlust und Abnutzung bei der Übertragung von Informationen. Sie können auch zum Verschwinden oder Explodieren von Gradienten führen, was das Training sehr tiefer Netzwerke unmöglich macht. ResNet löst dieses Problem bis zu einem gewissen Grad. Durch die direkte Übertragung der Eingabeinformationen an die Ausgabe wird die Integrität der Informationen geschützt. Das gesamte Netzwerk muss nur den Unterschied zwischen Eingabe und Ausgabe lernen, wodurch die Lernziele und -schwierigkeiten vereinfacht werden. Der Vergleich zwischen VGGNet und ResNet ist in der folgenden Abbildung dargestellt. Der größte Unterschied zwischen ResNet besteht darin, dass es viele Seitenkanäle gibt, die den Eingang direkt mit nachfolgenden Schichten verbinden. Diese Struktur wird auch als Shortcut- oder Skip-Verbindung bezeichnet.

In der ResNet-Netzwerkstruktur werden zwei Arten von Restmodulen verwendet: Eine besteht darin, zwei 3 * 3-Faltungsnetzwerke als Restmodul miteinander zu verbinden, und die andere besteht aus 1 * 1, 3 * 3, 1 * 1. Die drei Faltungsnetzwerke sind als Restmodul miteinander verbunden, wie in der Abbildung unten dargestellt.

ResNet verfügt über verschiedene Netzwerkschichten. Die am häufigsten verwendeten sind 50 Schichten, 101 Schichten und 152 Schichten. Sie werden alle durch Stapeln der oben genannten Restmodule implementiert.

 Der Pytorch-Code von ResNet-18 lautet wie folgt:

Die Funktion ist wie folgt definiert:

def resnet18(pretrained=False, **kwargs):
    """构建一个ResNet-18模型

    参数:
        pretrained (bool): 若为True则返回在ImageNet上预训练的模型
    """
    model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
    if pretrained:
        model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))
    return model

Wenn Sie andere ResNet-Modelle verwenden, verwenden Sie einfach die Funktion mit dem entsprechenden Namen. Die Funktion gibt eine ResNetInstanz eines Typs zurück. Diese Klasse definiert die Struktur des Resnet-Netzwerks. ResNetDie Struktur der Klasse ist wie folgt:

class ResNet(nn.Module):
    def __init__(self, block, layers, num_classes=1000, zero_init_residual=False):
        """定义ResNet网络的结构
        
        参数:
            block (BasicBlock / Bottleneck): 残差块类型
            layers (list): 每一个stage的残差块的数目,长度为4
            num_classes (int): 类别数目
            zero_init_residual (bool): 若为True则将每个残差块的最后一个BN层初始化为零,
                这样残差分支从零开始每一个残差分支,每一个残差块表现的就像一个恒等映射,根据
                https://arxiv.org/abs/1706.02677这可以将模型的性能提升0.2~0.3%
        """
        super(ResNet, self).__init__()
        # __init__
    def _make_layer(self, block, planes, blocks, stride=1):
        # _make_layer function
    def forward(self, x):
        # forward function

Beachten Sie die Parameter im Code der ResNet-Klasse oben block. Dieser Parameter definiert die Struktur des Restblocks, der in zwei Typen unterteilt ist:

  1. BasicBlock: Restblockstruktur von Resnet-18 und Resnet-34;
  2. Bottleneck: Restblockstruktur von Resnet-50, Resnet-101 und Resnet-152;

 Wir konzentrieren uns hier nur auf Resnet-18 , daher konzentrieren wir uns nur auf die BasicBlock- Klasse. Die Struktur dieser Klasse ist wie folgt:

def conv3x3(in_planes, out_planes, stride=1):
    """3x3 convolution with padding"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=1, bias=False)


def conv1x1(in_planes, out_planes, stride=1):
    """1x1 convolution"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)


class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        """定义BasicBlock残差块类
        
        参数:
            inplanes (int): 输入的Feature Map的通道数
            planes (int): 第一个卷积层输出的Feature Map的通道数
            stride (int, optional): 第一个卷积层的步长
            downsample (nn.Sequential, optional): 旁路下采样的操作
        注意:
            残差块输出的Feature Map的通道数是planes*expansion
        """
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = nn.BatchNorm2d(planes)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        identity = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)

        return out

BasicBlockDie definierte Netzwerkstruktur ist wie folgt: 

 ResNet-Klasse:

class ResNet(nn.Module):

    def __init__(self, block, layers, num_classes=1000, zero_init_residual=False):
         """定义ResNet网络的结构
        
        参数:
            block (BasicBlock / Bottleneck): 残差块类型
            layers (list): 每一个stage的残差块的数目,长度为4
            num_classes (int): 类别数目
            zero_init_residual (bool): 若为True则将每个残差块的最后一个BN层初始化为零,
                这样残差分支从零开始每一个残差分支,每一个残差块表现的就像一个恒等映射,根据
                https://arxiv.org/abs/1706.02677这可以将模型的性能提升0.2~0.3%
        """
        super(ResNet, self).__init__()
        self.inplanes = 64  # 第一个残差块的输入通道数
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        
        # Stage1 ~ Stage4
        self.layer1 = self._make_layer(block, 64, layers[0])
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
        
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))  # GAP
        self.fc = nn.Linear(512 * block.expansion, num_classes)

        # 网络参数初始化
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
        if zero_init_residual:
            for m in self.modules():
                if isinstance(m, Bottleneck):
                    nn.init.constant_(m.bn3.weight, 0)
                elif isinstance(m, BasicBlock):
                    nn.init.constant_(m.bn2.weight, 0)
    
    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)

        return x

 ResNetDie von der Klasse definierte Netzwerkstruktur ist in der folgenden Abbildung dargestellt:

 _make_layer-Methode

ResNetEs gibt eine weitere Methode in der Klasse. _make_layerIn dieser Methode wird die Struktur einer Stufe des ResNet-Netzwerks definiert. Der Code lautet wie folgt:

def _make_layer(self, block, planes, blocks, stride=1):
    """定义ResNet的一个Stage的结构
    
    参数:
        block (BasicBlock / Bottleneck): 残差块结构
        plane (int): 残差块中第一个卷积层的输出通道数
        bloacks (int): 当前Stage中的残差块的数目
        stride (int): 残差块中第一个卷积层的步长
    """
    downsample = None
    if stride != 1 or self.inplanes != planes * block.expansion:
        downsample = nn.Sequential(
            conv1x1(self.inplanes, planes * block.expansion, stride),
            nn.BatchNorm2d(planes * block.expansion),
        )

    layers = []
    layers.append(block(self.inplanes, planes, stride, downsample))
    self.inplanes = planes * block.expansion
    for _ in range(1, blocks):
        layers.append(block(self.inplanes, planes))

    return nn.Sequential(*layers)

Der erste Schritt besteht darin, die Downsampling-Struktur für den ersten Restblock zu definieren. Wenn die Eingabe- und Ausgabegrößen des Restblocks inkonsistent sind oder die Anzahl der Kanäle inkonsistent ist, ist eine Downsampling-Struktur erforderlich. Die Downsampling-Struktur besteht aus einer 1x1-Faltungsschicht und eine BatchNorm-Ebene. Zusammensetzung. Anschließend werden Blöcke, Blöcke, Restblöcke definiert (jede Stufe in ResNet-18 hat zwei Restblöcke), und nur der erste Restblock erfordert eine Downsampling-Schicht.

In ResNet ist die Anzahl der Eingangs- und Ausgangskanäle in Stufe 1 gleich und es wird eine Faltung mit einem Schritt von 1 verwendet, sodass in Stufe 1 keine Downsampling-Schicht erforderlich ist und in den verbleibenden Stufen Downsampling-Schichten erforderlich sind.

Die Struktur von ResNet-50 ist etwas anders, aber das allgemeine Heimweh ist nicht viel anders. Wenn Sie interessiert sind, können Sie den Quellcode in Pytorch selbst überprüfen.

Supongo que te gusta

Origin blog.csdn.net/Mr___WQ/article/details/127415489
Recomendado
Clasificación