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
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.
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
Die Netzwerkstruktur von GoogLeNet in Form einer Tabelle sieht wie folgt aus:
Wie sehen Sie die Parameter der Inception-Struktur? Es ist im Bild unten markiert.
Lassen Sie uns die Modellstruktur von GoogLeNet im Detail vorstellen.
1. Faltungsschicht
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
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
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
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
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
7. Max. Pooling-Schicht
8. Anfang 4a 4b 4c 4d 4e
9. Max. Pooling-Schicht
10.Inception 5a 5b
11. Ausgabeebene
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()