Detaillierte Erläuterung des GoogLenet-Netzwerks

GoogLenet

VGG wurde 2014 von der berühmten Forschungsgruppe vGG (Visual Geometry Group) der Universität Oxford vorgeschlagen und gewann in diesem Jahr den ersten Platz in der Lokalisierungsaufgabe (Positionierungsaufgabe) und den zweiten Platz in der Klassifizierungsaufgabe (Klassifizierungsaufgabe). . Der erste Platz in der Klassifizierungsaufgabe (Klassifizierungsaufgabe) ist GoogleNet. GoogleNet ist eine von Google entwickelte tiefe Netzwerkstruktur. Der Grund, warum es „GoogLeNet“ genannt wird, ist eine Hommage an „LeNet“.

Highlights des GoogLenet-Netzwerks

(
_
_
_ reduziert die Modellparameter erheblich)

Anfangsstruktur

Fügen Sie hier eine Bildbeschreibung ein
Die Grundstruktur des Inception-Moduls besteht aus vier Komponenten. 1x1-Faltung, 3x3-Faltung, 5x5-Faltung, 3x3 maximales Pooling. Schließlich ist die kanalweise Kombination der Ergebnisse der vier Komponentenoperationen die Kernidee von Naive Inception (Abbildung a oben): Verwenden Sie Faltungskerne unterschiedlicher Größe, um die Wahrnehmung unterschiedlicher Skalen zu erreichen, und führen Sie schließlich eine Fusion durch, um a zu erhalten bessere Darstellung des Bildes. Beachten Sie, dass die Höhe und Breite der von jedem Zweig erhaltenen Merkmalsmatrix gleich sein müssen. Naive Inception weist jedoch zwei sehr schwerwiegende Probleme auf: Erstens sind alle Faltungsschichten direkt mit den von der vorherigen Schicht eingegebenen Daten verbunden, sodass der Rechenaufwand in der Faltungsschicht groß sein wird, und zweitens ist das maximale Pooling, das in dieser Einheit verwendet wird, die Schicht Behält die Tiefe der Feature-Map der Eingabedaten bei, sodass beim Zusammenführen am Ende nur die Tiefe der gesamten Ausgabe-Feature-Map zunimmt, was den Berechnungsaufwand für die Netzwerkstruktur nach der Einheit erhöht. Der Hauptzweck der Verwendung des 1x1-Faltungskerns besteht hier also darin, die Dimensionalität zu komprimieren und zu reduzieren und die Anzahl der Parameter zu reduzieren, was in Abbildung b oben dargestellt ist, um das Netzwerk tiefer und breiter zu machen und Merkmale besser zu extrahieren. Diese Idee ist auch Pointwise Conv oder kurz PW genannt.

Führen Sie eine kleine Berechnung durch, nehmen Sie an, dass der Kanal des Eingabebildes 512 ist, verwenden Sie 64 5x5-Faltungskerne für die Faltung und die für die Dimensionsreduzierung erforderlichen Parameter ohne Verwendung von 1x1-Faltungskernen sind 512x64x5x5 = 819200. Wenn 24 1x1-Faltungskerne zur Dimensionsreduzierung verwendet werden, beträgt der Bildkanal 24, und wenn dann mit 65 Faltungskernen gefaltet wird, sind die erforderlichen Parameter 512x24x1x1+24x65x5x5=50688.

Hilfsklassifikator

Den experimentellen Daten zufolge hat die mittlere Schicht des neuronalen Netzwerks auch eine starke Erkennungsfähigkeit. Um die abstrakten Merkmale der mittleren Schicht zu nutzen, wird einigen mittleren Schichten ein Klassifikator mit mehreren Schichten hinzugefügt. Wie in der folgenden Abbildung dargestellt, stellt die Innenseite des roten Rahmens den hinzugefügten Hilfsklassifikator dar. GoogLeNet hat zwei zusätzliche Softmax-Zweige hinzugefügt, die zwei Funktionen haben: Die eine besteht darin, das Verschwinden des Gradienten zu verhindern und den Gradienten vorwärts zu leiten. Wenn eine Ableitungsschicht während der Rückausbreitung 0 ist, ist das Ergebnis der Kettenableitung 0. Die zweite besteht darin, die Ausgabe einer mittleren Schicht als Klassifizierung zu verwenden, um eine Rolle bei der Modellfusion zu spielen. Der endgültige Verlust = Verlust_2 + 0,3 * Verlust_1 + 0,3 * Verlust_0. Bei tatsächlichen Tests werden diese beiden zusätzlichen Softmax-Zweige entfernt.

Fügen Sie hier eine Bildbeschreibung ein

1. Durchschnittliche Pooling-Schicht

Bei einer Fenstergröße von 5×5 und einem Schritt von 3 ergibt sich eine Ausgabe von 4×4×512 für (4a) und 4×4×528 für Stufe (4d).

2. Faltungsschicht

Für die Faltung (Dimensionsreduzierung) werden 128 1×1-Faltungskerne unter Verwendung der ReLU-Aktivierungsfunktion verwendet.

3. Vollständig verbundene Schicht

Die vollständig verbundene Schicht von 1024 Knoten nutzt auch die ReLU-Aktivierungsfunktion.

4.Aussteiger

Dropout deaktiviert Neuronen zufällig mit einer Rate von 70 %.

5.softmax

Geben Sie 1000 Vorhersageergebnisse über Softmax aus.

GoogLenet-Netzwerkstruktur

Fügen Sie hier eine Bildbeschreibung ein
Die Netzwerkstruktur von GoogLeNet in Form einer Tabelle sieht wie folgt aus:
Fügen Sie hier eine Bildbeschreibung ein
Wie sehen Sie die Parameter der Inception-Struktur? Es ist im Bild unten markiert.
Fügen Sie hier eine Bildbeschreibung ein
Lassen Sie uns die Modellstruktur von GoogLeNet im Detail vorstellen.

1. Faltungsschicht

Fügen Sie hier eine Bildbeschreibung ein
Das Eingabebild ist 224x224x3, die Faltungskerngröße beträgt 7x7, die Schrittgröße beträgt 2, die Auffüllung beträgt 3, die Anzahl der Ausgabekanäle beträgt 64 und die Ausgabegröße beträgt (224-7+3x2)/2+1=112,5 (abgerundet) = 112, die Ausgabe ist 112 x 112 x 64 und die ReLU-Operation wird nach der Faltung ausgeführt.

2. Maximale Pooling-Schicht

Fügen Sie hier eine Bildbeschreibung ein

Die Fenstergröße beträgt 3x3, die Schrittgröße beträgt 2, die Ausgabegröße beträgt ((112 -3)/2)+1=55,5 (aufgerundet)=56 und die Ausgabe beträgt 56x56x64.

3. Zwei Faltungsschichten

Fügen Sie hier eine Bildbeschreibung ein

Die erste Ebene: Verwenden Sie 64 1x1-Faltungskerne (Dimensionsreduzierung vor dem 3x3-Faltungskern), um die Eingabe-Feature-Map (56x56x64) in 56x56x64 zu ändern, und führen Sie dann die ReLU-Operation aus.

Die zweite Ebene: Verwenden Sie die Faltungskerngröße 3x3, die Schrittgröße beträgt 1, die Auffüllung beträgt 1, die Anzahl der Ausgabekanäle beträgt 192 und die Faltungsoperation wird ausgeführt. Die Ausgabegröße beträgt (56-3 + 1x2)/1 +1 = 56 und die Ausgabe ist 56x56x192. Führen Sie dann die ReLU-Operation aus.

4. Max. Pooling-Schicht

Fügen Sie hier eine Bildbeschreibung ein

Die Fenstergröße beträgt 3x3, die Schrittgröße beträgt 2, die Anzahl der Ausgabekanäle beträgt 192, die Ausgabe beträgt ((56 - 3)/2)+1=27,5 (aufgerundet)=28 und die Ausgabe-Feature-Map-Dimension beträgt 28x28x192.

5. Gründung 3a

Fügen Sie hier eine Bildbeschreibung ein
1. Verwenden Sie 64 1x1-Faltungskerne, die Ausgabe nach der Faltung beträgt 28x28x64 und führen Sie dann eine RuLU-Operation durch.
2,96 1x1-Faltungskerne (Dimensionsreduzierung vor 3x3-Faltungskernen) werden gefaltet und die Ausgabe ist 28x28x96, und die ReLU-Berechnung wird durchgeführt, und dann werden 128 3x3-Faltungen durchgeführt, um 28x28x128 auszugeben.
3.16 1x1-Faltungskerne (Dimensionsreduzierung vor 5x5-Faltungskernen) werden gefaltet und die Ausgabe ist 28x28x16, und die ReLU-Berechnung wird durchgeführt, und dann werden 32 5x5-Faltungen durchgeführt, und die Ausgabe ist 28x28x32.
4. Die maximale Pooling-Schicht, die Fenstergröße beträgt 3x3, die Ausgabe beträgt 28x28x192, und dann werden 32 1x1-Faltungen durchgeführt, und die Ausgabe beträgt 28x28x32.

6. Gründung 3b

Fügen Sie hier eine Bildbeschreibung ein

7. Max. Pooling-Schicht

Fügen Sie hier eine Bildbeschreibung ein

8. Anfang 4a 4b 4c 4d 4e

Fügen Sie hier eine Bildbeschreibung ein

9. Max. Pooling-Schicht

Fügen Sie hier eine Bildbeschreibung ein

10.Inception 5a 5b

Fügen Sie hier eine Bildbeschreibung ein

11. Ausgabeebene

Fügen Sie hier eine Bildbeschreibung ein
GoogLeNet verwendet eine durchschnittliche Pooling-Schicht, um eine Faltungsschicht mit einer Höhe und Breite von 1 zu erhalten; dann deaktiviert Dropout zufällig Neuronen bei 40 %; die Aktivierungsfunktion der Ausgabeschicht verwendet Softmax.

GoogLenet-Implementierung

Umsetzung von Inception
class Inception(nn.Module):
    def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):
        super(Inception, self).__init__()

        self.branch1 = BasicConv2d(in_channels, ch1x1, kernel_size=1)

        self.branch2 = nn.Sequential(
            BasicConv2d(in_channels, ch3x3red, kernel_size=1),
            BasicConv2d(ch3x3red, ch3x3, kernel_size=3, padding=1)   # 保证输出大小等于输入大小
        )

        self.branch3 = nn.Sequential(
            BasicConv2d(in_channels, ch5x5red, kernel_size=1),
            # 在官方的实现中,其实是3x3的kernel并不是5x5,这里我也懒得改了,具体可以参考下面的issue
            # Please see https://github.com/pytorch/vision/issues/906 for details.
            BasicConv2d(ch5x5red, ch5x5, kernel_size=5, padding=2)   # 保证输出大小等于输入大小
        )

        self.branch4 = nn.Sequential(
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
            BasicConv2d(in_channels, pool_proj, kernel_size=1)
        )

    def forward(self, x):
        branch1 = self.branch1(x)
        branch2 = self.branch2(x)
        branch3 = self.branch3(x)
        branch4 = self.branch4(x)

        outputs = [branch1, branch2, branch3, branch4]
        return torch.cat(outputs, 1)
GoogLenet-Implementierung
import torch.nn as nn
import torch
import torch.nn.functional as F


class GoogLeNet(nn.Module):
    def __init__(self, num_classes=1000, aux_logits=True, init_weights=False):
        super(GoogLeNet, self).__init__()
        self.aux_logits = aux_logits

        self.conv1 = BasicConv2d(3, 64, kernel_size=7, stride=2, padding=3)
        self.maxpool1 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.conv2 = BasicConv2d(64, 64, kernel_size=1)
        self.conv3 = BasicConv2d(64, 192, kernel_size=3, padding=1)
        self.maxpool2 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.inception3a = Inception(192, 64, 96, 128, 16, 32, 32)
        self.inception3b = Inception(256, 128, 128, 192, 32, 96, 64)
        self.maxpool3 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.inception4a = Inception(480, 192, 96, 208, 16, 48, 64)
        self.inception4b = Inception(512, 160, 112, 224, 24, 64, 64)
        self.inception4c = Inception(512, 128, 128, 256, 24, 64, 64)
        self.inception4d = Inception(512, 112, 144, 288, 32, 64, 64)
        self.inception4e = Inception(528, 256, 160, 320, 32, 128, 128)
        self.maxpool4 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.inception5a = Inception(832, 256, 160, 320, 32, 128, 128)
        self.inception5b = Inception(832, 384, 192, 384, 48, 128, 128)

        if self.aux_logits:
            self.aux1 = InceptionAux(512, num_classes)
            self.aux2 = InceptionAux(528, num_classes)

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.dropout = nn.Dropout(0.4)
        self.fc = nn.Linear(1024, num_classes)
        if init_weights:
            self._initialize_weights()

    def forward(self, x):
        # N x 3 x 224 x 224
        x = self.conv1(x)
        # N x 64 x 112 x 112
        x = self.maxpool1(x)
        # N x 64 x 56 x 56
        x = self.conv2(x)
        # N x 64 x 56 x 56
        x = self.conv3(x)
        # N x 192 x 56 x 56
        x = self.maxpool2(x)

        # N x 192 x 28 x 28
        x = self.inception3a(x)
        # N x 256 x 28 x 28
        x = self.inception3b(x)
        # N x 480 x 28 x 28
        x = self.maxpool3(x)
        # N x 480 x 14 x 14
        x = self.inception4a(x)
        # N x 512 x 14 x 14
        if self.training and self.aux_logits:    # eval model lose this layer
            aux1 = self.aux1(x)

        x = self.inception4b(x)
        # N x 512 x 14 x 14
        x = self.inception4c(x)
        # N x 512 x 14 x 14
        x = self.inception4d(x)
        # N x 528 x 14 x 14
        if self.training and self.aux_logits:    # eval model lose this layer
            aux2 = self.aux2(x)

        x = self.inception4e(x)
        # N x 832 x 14 x 14
        x = self.maxpool4(x)
        # N x 832 x 7 x 7
        x = self.inception5a(x)
        # N x 832 x 7 x 7
        x = self.inception5b(x)
        # N x 1024 x 7 x 7

        x = self.avgpool(x)
        # N x 1024 x 1 x 1
        x = torch.flatten(x, 1)
        # N x 1024
        x = self.dropout(x)
        x = self.fc(x)
        # N x 1000 (num_classes)
        if self.training and self.aux_logits:   # eval model lose this layer
            return x, aux2, aux1
        return x

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)


class Inception(nn.Module):
    def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):
        super(Inception, self).__init__()

        self.branch1 = BasicConv2d(in_channels, ch1x1, kernel_size=1)

        self.branch2 = nn.Sequential(
            BasicConv2d(in_channels, ch3x3red, kernel_size=1),
            BasicConv2d(ch3x3red, ch3x3, kernel_size=3, padding=1)   # 保证输出大小等于输入大小
        )

        self.branch3 = nn.Sequential(
            BasicConv2d(in_channels, ch5x5red, kernel_size=1),
            # 在官方的实现中,其实是3x3的kernel并不是5x5,这里我也懒得改了,具体可以参考下面的issue
            # Please see https://github.com/pytorch/vision/issues/906 for details.
            BasicConv2d(ch5x5red, ch5x5, kernel_size=5, padding=2)   # 保证输出大小等于输入大小
        )

        self.branch4 = nn.Sequential(
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
            BasicConv2d(in_channels, pool_proj, kernel_size=1)
        )

    def forward(self, x):
        branch1 = self.branch1(x)
        branch2 = self.branch2(x)
        branch3 = self.branch3(x)
        branch4 = self.branch4(x)

        outputs = [branch1, branch2, branch3, branch4]
        return torch.cat(outputs, 1)


class InceptionAux(nn.Module):
    def __init__(self, in_channels, num_classes):
        super(InceptionAux, self).__init__()
        self.averagePool = nn.AvgPool2d(kernel_size=5, stride=3)
        self.conv = BasicConv2d(in_channels, 128, kernel_size=1)  # output[batch, 128, 4, 4]

        self.fc1 = nn.Linear(2048, 1024)
        self.fc2 = nn.Linear(1024, num_classes)

    def forward(self, x):
        # aux1: N x 512 x 14 x 14, aux2: N x 528 x 14 x 14
        x = self.averagePool(x)
        # aux1: N x 512 x 4 x 4, aux2: N x 528 x 4 x 4
        x = self.conv(x)
        # N x 128 x 4 x 4
        x = torch.flatten(x, 1)
        x = F.dropout(x, 0.5, training=self.training)
        # N x 2048
        x = F.relu(self.fc1(x), inplace=True)
        x = F.dropout(x, 0.5, training=self.training)
        # N x 1024
        x = self.fc2(x)
        # N x num_classes
        return x


class BasicConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, **kwargs):
        super(BasicConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        return x

Trainingsmodell
import os
import sys
import json

import torch
import torch.nn as nn
from torchvision import transforms, datasets
import torch.optim as optim
from tqdm import tqdm

from model import GoogLeNet


def main():
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("using {} device.".format(device))

    data_transform = {
    
    
        "train": transforms.Compose([transforms.RandomResizedCrop(224),
                                     transforms.RandomHorizontalFlip(),  # 随机左右翻转
                                     # transforms.RandomVerticalFlip(), # 随机上下翻转
                                     transforms.ColorJitter(brightness=0.5, contrast=0.5, saturation=0.5, hue=0.1),
                                     transforms.RandomRotation(degrees=5),
                                     transforms.ToTensor(),
                                     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
        "val": transforms.Compose([transforms.Resize((224, 224)),
                                   transforms.ToTensor(),
                                   transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}

    train_dataset = datasets.ImageFolder(root='./Training',
                                         transform=data_transform["train"])
    train_num = len(train_dataset)
    flower_list = train_dataset.class_to_idx
    cla_dict = dict((val, key) for key, val in flower_list.items())
    json_str = json.dumps(cla_dict, indent=4)
    with open(
            'class_indices.json', 'w') as json_file:
        json_file.write(json_str)

    batch_size = 32
    nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8])  # number of workers
    print('Using {} dataloader workers every process'.format(nw))

    train_loader = torch.utils.data.DataLoader(train_dataset,
                                               batch_size=batch_size, shuffle=True,
                                               num_workers=nw)

    validate_dataset = datasets.ImageFolder(root='./Test',
                                            transform=data_transform["val"])
    val_num = len(validate_dataset)
    validate_loader = torch.utils.data.DataLoader(validate_dataset,
                                                  batch_size=batch_size, shuffle=False,
                                                  num_workers=nw)

    print("using {} images for training, {} images for validation.".format(train_num, val_num))

    # test_data_iter = iter(validate_loader)
    # test_image, test_label = test_data_iter.next()

    net = GoogLeNet(num_classes=131, aux_logits=True, init_weights=True)  # num_classes根据分类的数量而定
    # 如果要使用官方的预训练权重,注意是将权重载入官方的模型,不是我们自己实现的模型
    # 官方的模型中使用了bn层以及改了一些参数,不能混用
    # import torchvision
    # net = torchvision.models.googlenet(num_classes=5)
    # model_dict = net.state_dict()
    # # 预训练权重下载地址: https://download.pytorch.org/models/googlenet-1378be20.pth
    # pretrain_model = torch.load("googlenet.pth")
    # del_list = ["aux1.fc2.weight", "aux1.fc2.bias",
    #             "aux2.fc2.weight", "aux2.fc2.bias",
    #             "fc.weight", "fc.bias"]
    # pretrain_dict = {
    
    k: v for k, v in pretrain_model.items() if k not in del_list}
    # model_dict.update(pretrain_dict)
    # net.load_state_dict(model_dict)
    net.to(device)
    loss_function = nn.CrossEntropyLoss()
    optimizer = optim.Adam(net.parameters(), lr=0.0003)

    epochs = 30
    best_acc = 0.0
    save_path = './googleNet.pth'
    train_steps = len(train_loader)
    for epoch in range(epochs):
        # train
        net.train()
        running_loss = 0.0
        train_bar = tqdm(train_loader, file=sys.stdout)
        for step, data in enumerate(train_bar):
            images, labels = data
            optimizer.zero_grad()
            logits, aux_logits2, aux_logits1 = net(images.to(device))
            loss0 = loss_function(logits, labels.to(device))
            loss1 = loss_function(aux_logits1, labels.to(device))
            loss2 = loss_function(aux_logits2, labels.to(device))
            loss = loss0 + loss1 * 0.3 + loss2 * 0.3
            loss.backward()
            optimizer.step()

            # print statistics
            running_loss += loss.item()

            train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,
                                                                     epochs,
                                                                     loss)

        # validate
        net.eval()
        acc = 0.0  # accumulate accurate number / epoch
        with torch.no_grad():
            val_bar = tqdm(validate_loader, file=sys.stdout)
            for val_data in val_bar:
                val_images, val_labels = val_data
                outputs = net(val_images.to(device))  # eval model only have last output layer
                predict_y = torch.max(outputs, dim=1)[1]
                acc += torch.eq(predict_y, val_labels.to(device)).sum().item()

        val_accurate = acc / val_num
        print('[epoch %d] train_loss: %.3f  val_accuracy: %.3f' %
              (epoch + 1, running_loss / train_steps, val_accurate))

        if val_accurate > best_acc:
            best_acc = val_accurate
            torch.save(net.state_dict(), save_path)

    print('Finished Training')


if __name__ == '__main__':
    main()


おすすめ

転載: blog.csdn.net/weixin_43912621/article/details/127910285