Einführung
Dieser Beitrag setzt eine Reihe fort, in der wir die beliebtesten Konvolutionsneuronennetze von Grund auf in PyTorch aufbauen. Du kannst den vorherigen Beitrag hier lesen, in dem wir LeNet5 aufgebaut haben. In diesem Beitrag werden wir AlexNet bauen, eines der bedeutendsten Durchbrüche in der Computervision ist.
Wir werden beginnen, indem wir die Architektur von AlexNet untersuchen und verstehen. Dann werden wir direkt in den Code gehen, indem wir unsere Datenbank, CIFAR-10, laden, bevor wir die Daten vorverarbeiten. Anschließend werden wir AlexNet von Grund auf mit PyTorch
bauen und es auf unserer vorverarbeiteten Daten trainieren. Schließlich werden wir das trainierte Modell auf unbekannten (test) Daten zur Bewertung ziehen.
Voraussetzungen
Knowledge of neural networks will be helpful in understanding this article. This would encompass being familiar with the different layers of neural networks (input layer, hidden layers, output layer), activation functions, optimization algorithms (variants of gradient descent), loss functions, etc. Additionally, familiarity with Python syntax and the PyTorch library is essential for understanding the code snippets presented in this article.
Ein Verständnis von CNNs ist unerlässlich. Dies beinhaltet das Wissen über konvolutionale Schichten, Pooling-Schichten und ihre Rolle beim Extrahieren von Merkmalen aus Eingabedaten. Der Verständnis von Konzepten wie Stride, Padding und der Auswirkung der Kernel/Filtergröße ist ebenfalls hilfreich.
AlexNet
AlexNet ist eine tiefe konvolutionelle neuronale Netz, die ursprünglich 2012 von Alex Krizhevsky und seinen Kollegen entwickelt wurde. Es wurde entwickelt, um Bilder für den ImageNet LSVRC-2010 Wettbewerb zu klassifizieren, wo es die neueste Methoden erzielte. Du kannst detailliert Informationen über das Modell im Originalforschungsbericht hier lesen.
Lass uns die wichtigsten Schlussfolgerungen aus dem AlexNet-Papier anschauen. Erstens arbeitete AlexNet mit 3-Kanalbildern, die (224x224x3) groß waren. Es verwendete Max-Pooling zusammen mit ReLU-Aktivierungen beim Subsampling. Die Kernel für Konvolutionen waren entweder 11×11, 5×5 oder 3x3groß, während die Kernel für Max-Pooling 3x3groß waren. Es klassifizierte Bilder in 1000 Klassen. Es verwendete auch mehrere GPUs.
Datensatz
Wir beginnen mit der Laden und Vorverarbeitung der Daten. Für unsere Zwecke verwenden wir die CIFAR-10-Datenmenge. Die Datenmenge besteht aus 60000 32×32 Farbabbildern in 10 Klassen, je 6000 Abbilder pro Klasse. Es gibt 50000 Trainingsabbilder und 10000 Testabbilder.
Hier sind die Klassen in der Datenmenge sowie 10 zufällige Beispielabbilder von jedem:
Quelle: source
Die Klassen sind vollständig gegenseitig ausschließlich. Es gibt keine Überschneidung zwischen Automobilen und Lastwagen. „Automobil“ umfasst Limousinen, SUVs und solche Dinge. „Lastwagen“ umfasst nur große Lastwagen. Keiner umfasst den Pickup.
Bibliotheken importieren
Lassen Sie uns beginnen, indem wir die notwendigen Bibliotheken importieren und eine Variable device
definieren, sodass das Notizbuch weiß, einen GPU zum Trainieren des Modells zu verwenden, wenn er verfügbar ist.
import numpy as np
import torch
import torch.nn as nn
from torchvision import datasets
from torchvision import transforms
from torch.utils.data.sampler import SubsetRandomSampler
# Geräte-Konfiguration
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
Datenmenge laden
Mit torchvision
, einer Bibliothek zur Unterstützung von Computervision-Aufgaben, laden wir unser Datenset ein. Diese Methode beinhaltet einige Hilfsfunktionen, die die Vorverarbeitung recht einfach und direkt erleichtern. Definierten wir die Funktionen get_train_valid_loader
und get_test_loader
, und rufen sie dann auf, um unsere CIFAR-10-Daten zu laden und zu verarbeiten:
def get_train_valid_loader(data_dir,
batch_size,
augment,
random_seed,
valid_size=0.1,
shuffle=True):
normalize = transforms.Normalize(
mean=[0.4914, 0.4822, 0.4465],
std=[0.2023, 0.1994, 0.2010],
)
# definiere Transformationen
valid_transform = transforms.Compose([
transforms.Resize((227,227)),
transforms.ToTensor(),
normalize,
])
if augment:
train_transform = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
normalize,
])
else:
train_transform = transforms.Compose([
transforms.Resize((227,227)),
transforms.ToTensor(),
normalize,
])
# lade das Datenset
train_dataset = datasets.CIFAR10(
root=data_dir, train=True,
download=True, transform=train_transform,
)
valid_dataset = datasets.CIFAR10(
root=data_dir, train=True,
download=True, transform=valid_transform,
)
num_train = len(train_dataset)
indices = list(range(num_train))
split = int(np.floor(valid_size * num_train))
if shuffle:
np.random.seed(random_seed)
np.random.shuffle(indices)
train_idx, valid_idx = indices[split:], indices[:split]
train_sampler = SubsetRandomSampler(train_idx)
valid_sampler = SubsetRandomSampler(valid_idx)
train_loader = torch.utils.data.DataLoader(
train_dataset, batch_size=batch_size, sampler=train_sampler)
valid_loader = torch.utils.data.DataLoader(
valid_dataset, batch_size=batch_size, sampler=valid_sampler)
return (train_loader, valid_loader)
def get_test_loader(data_dir,
batch_size,
shuffle=True):
normalize = transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225],
)
# definiere Transformation
transform = transforms.Compose([
transforms.Resize((227,227)),
transforms.ToTensor(),
normalize,
])
dataset = datasets.CIFAR10(
root=data_dir, train=False,
download=True, transform=transform,
)
data_loader = torch.utils.data.DataLoader(
dataset, batch_size=batch_size, shuffle=shuffle
)
return data_loader
# CIFAR10-Datensatz
train_loader, valid_loader = get_train_valid_loader(data_dir = './data', batch_size = 64,
augment = False, random_seed = 1)
test_loader = get_test_loader(data_dir = './data',
batch_size = 64)
Lass uns den Code aufteilen:
- Wir definieren zwei Funktionen
get_train_valid_loader
undget_test_loader
, um die Trainings-/Validierungs- und Test-Sets zu laden - Wir beginnen mit der Definition der Variable
normalize
mit den Mittelwerten und Standardabweichungen für jede der Kanäle (rot, grün und blau) im Datensatz. Diese können manuell berechnet werden, sind aber wegen der Beliebtheit von CIFAR-10 auch online verfügbar - Für unsere Trainingsdatenbank haben wir die Option hinzugefügt, das Datenset auch zu vergrößern, indem wir es verändern, um eine robustere Trainings- und Erhöhung der Anzahl der Bilder zu erreichen. Anmerkung: Die Vergrößerung wird nur auf das Trainings-Subset angewendet und nicht auf das Validierungs- und Test-Subset, da sie nur für Evaluierungszwecke verwendet werden
- Wir teilen das Trainingsdatensatz in ein Trainings- und ein Validierungsdatensatz (90:10-Verhältnis) auf und nehmen ihn random aus dem gesamten Trainingsset
- Wir legen die Batchgröße fest und mischen das Datenset beim Laden, sodass jeder Batch eine Variation in den Typen der Labels aufweist. Dies wird die Effektivität unseres erhaltenen Modells erhöhen.
- Schließlich nutzen wir Datenloader. Dies kann die Leistung bei kleinen Datensätzen wie CIFAR-10 nicht beeinträchtigen, aber bei großen Datensätzen kann dies die Leistung wesentlich behindern und es wird allgemein als gute Praxis angesehen. Datenloader ermöglichen es uns, durch die Daten in Batches zu iterieren, und die Daten werden während der Iteration geladen und nicht alle gleichzeitig beim Start in Ihre RAM
AlexNet von Scratch
Beginnen wir mit dem Code:
class AlexNet(nn.Module):
def __init__(self, num_classes=10):
super(AlexNet, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=0),
nn.BatchNorm2d(96),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.layer2 = nn.Sequential(
nn.Conv2d(96, 256, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.layer3 = nn.Sequential(
nn.Conv2d(256, 384, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(384),
nn.ReLU())
self.layer4 = nn.Sequential(
nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(384),
nn.ReLU())
self.layer5 = nn.Sequential(
nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.fc = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(9216, 4096),
nn.ReLU())
self.fc1 = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU())
self.fc2= nn.Sequential(
nn.Linear(4096, num_classes))
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.layer5(out)
out = out.reshape(out.size(0), -1)
out = self.fc(out)
out = self.fc1(out)
out = self.fc2(out)
return out
Definieren des AlexNet-Modells
Lassen Sie uns anschauen, wie der obige Code funktioniert:
- Der erste Schritt zur Definition jeder neuronalen Netzwerks (egal ob es ein CNN ist oder nicht) in PyTorch besteht darin, eine Klasse zu definieren, die von
nn.Module
erbt, da es viele der Methoden enthält, die wir verwenden werden müssen - Es gibt zwei HauptSchritte danach. Der erste ist das Initialisieren der Schichten, die wir in unserem CNN verwenden werden, innerhalb des
__init__
-Methoden und der andere ist die Definition der Sequenz, in der diese Schichten das Bild verarbeiten werden. Dies wird innerhalb derforward
-Funktion definiert. - Für die Architektur selbst definieren wir zunächst die Konvolutionsschichten mithilfe der Funktion
nn.Conv2D
mit der passenden Kernelgröße und den Eingangs-/Ausgangskanälen. Wir setzen außerdem Max-Pooling mithilfe der Funktionnn.MaxPool2D
ein. Der Vorteil von PyTorch besteht darin, dass wir die Konvolutionsschicht, die Aktivierungsfunktion und das Max-Pooling in einer einzigen Schicht (sie werden separat angewendet, aber es hilft bei der Organisation) kombinieren können, indem wir dienn.Sequential
Funktion verwenden - . Dann definieren wir die vollständig verknüpften Schichten mit linearer (
nn.Linear
), Dropout (nn.Dropout
) und ReLU-Aktivierungsfunktion (nn.ReLU
) und kombinieren diese mit dernn.Sequential
Funktion - . Schließlich erzeugt unsere letzte Schicht 10 Neuronen, die unsere letzten Voraussagen für die 10 Klassen von Objekten sind
Hyperparameter Einstellungen
. Vor dem Training müssen wir einige Hyperparameter einstellen, wie z.B. die zu verwendende Verlustfunktion und der Optimizer zusammen mit Batchgröße, Lernrate und Anzahl der epochs.
num_classes = 10
num_epochs = 20
batch_size = 64
learning_rate = 0.005
model = AlexNet(num_classes).to(device)
# Verlustfunktion und Optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay = 0.005, momentum = 0.9)
# Trainiere das Modell
total_step = len(train_loader)
Wir beginnen mit der Definition einfacher Hyperparameter (Epochen, Batch-Größe und Lernrate) und initialisieren unser Modell mit der Anzahl der Klassen als Argument, die in diesem Fall 10 ist, und wechseln das Modell auf die passende Hardware (CPU oder GPU). Dann definieren wir unsere Kostenfunktion als Verteilungskostenverlust und den Optimizer als Adam. Es gibt viele Möglichkeiten für diese, aber diese tendieren dazu, gute Ergebnisse mit dem Modell und den gegebenen Daten zu liefern. Schließlich definieren wir total_step
, um bessere Schritte bei der Trainingszeit zu verfolgen
Training
Wir sind bereit, unser Modell zu trainieren:
total_step = len(train_loader)
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
# Tensoren auf die konfigurierte Hardware verschieben
images = images.to(device)
labels = labels.to(device)
# Forward-Durchlauf
outputs = model(images)
loss = criterion(outputs, labels)
# Rückwärtspass und Optimierung
optimizer.zero_grad()
loss.backward()
optimizer.step()
print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
.format(epoch+1, num_epochs, i+1, total_step, loss.item()))
# Validation
with torch.no_grad():
correct = 0
total = 0
for images, labels in valid_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
del images, labels, outputs
print('Accuracy of the network on the {} validation images: {} %'.format(5000, 100 * correct / total))
Lassen Sie uns sehen, was der Code tut:
- Wir beginnen, indem wir durch die Anzahl der Epochen iterieren und dann die Batchs in unseren Trainingsdaten
- Wir wandeln die Bilder und die Labels je nach der Hardware, die wir verwenden, also GPU oder CPU
- Im Forward-Durchlauf treffen wir Vorhersagen mit unserem Modell und berechnen den Verlust auf der Basis dieser Vorhersagen und unserer tatsächlichen Labeln
- Nächstes führen wir den Rückwärtspass durch, in dem wir tatsächlich die Gewichte unseres Modells verbessern
- Wir setzen dann die Gradienten auf Null vor jedem Update mit der Funktion
optimizer.zero_grad()
- Dann berechnen wir die neuen Gradienten mit der Funktion
loss.backward()
- Und schließlich aktualisieren wir die Gewichte mit der Funktion
optimizer.step()
- Außerdem berechnen wir am Ende jedes Epochens mit Hilfe des Validierungsdatensatzes die Genauigkeit des Modells. In diesem Fall brauchen wir keine Gradienten, daher verwenden wir
with torch.no_grad()
für eine schnellere Evaluierung
Wir können die Ausgabe wie folgt sehen:
Training Verlust und Validierungs Genauigkeit
Wie wir sehen können, nimmt der Verlust mit jeder Epoche ab, was anzeigt, dass unser Modell tatsächlich lernt. Zu beachten ist, dass dieser Verlust sich auf den Trainingsdatensatz bezieht und falls der Verlust zu klein ist, kann das Überfitten anzeigen. Deshalb verwenden wir auch den Validierungsdatensatz. Die Genauigkeit scheint im Validierungsdatensatz zu steigen, was anzeigt, dass ein starkes Überfitten unwahrscheinlich ist. Jetzt testen wir also unser Modell, um zu sehen, wie es performt.
Testen
Jetzt gucken wir, wie unser Modell auf unbekannten Daten performt:
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
del images, labels, outputs
print('Accuracy of the network on the {} test images: {} %'.format(10000, 100 * correct / total))
Beachten Sie, dass der Code exakt das gleiche für unsere Validierungszwecke ist.
Mit dem Modell und nur sechs Epochen Training erreichen wir etwa 78,8% Genauigkeit auf dem Validierungsdatensatz.
Testgenauigkeit
Fazit
Lassen Sie uns nun abschließend zusammenfassen, was wir in diesem Artikel getan haben:
- Wir begannen damit, die Architektur und verschiedene Schichten des AlexNet-Modells zu verstehen
- Als Nächstes luden und verarbeiteten wir das CIFAR-10-Dataset mit
torchvision
- Dann verwendeten wir
PyTorch
, um unser AlexNet-Modell von Grund auf zu erstellen - Schließlich trainierten und testeten wir unser Modell mit dem CIFAR-10-Dataset, und das Modell schien gut auf dem Test-Dataset abzuschneiden, selbst mit minimalem Training (6 Epochen)
Zukünftige Arbeiten
Dieser Artikel bietet eine solide Einführung und praktische Erfahrung, aber Sie werden noch mehr Wissen gewinnen, wenn Sie weiter erkunden und herausfinden, was Sie sonst noch erreichen können
- Sie können versuchen, verschiedene Datensätze zu verwenden. Einer dieser Datensätze ist CIFAR-100, eine Erweiterung des CIFAR-10-Datensatzes mit 100 Klassen
- Sie können mit verschiedenen Hyperparametern experimentieren und die beste Kombination für das Modell herausfinden
- Schließlich können Sie versuchen, Schichten zum Dataset hinzuzufügen oder zu entfernen, um deren Einfluss auf die Leistungsfähigkeit des Modells zu sehen
Source:
https://www.digitalocean.com/community/tutorials/alexnet-pytorch