In diesem Artikel werde ich die grundlegende Implementierung mit PyTorch (auch als Memorandum) zusammenfassen. Nehmen Sie als Beispiel die Klassifizierung von CIFAR10 (Farbbildklassifizierungssatz).
Copyright 2020 shun310
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Die gesamte Ansicht des Programms ist wie folgt.
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
ave = 0.5 #Normalisierter Durchschnitt
std = 0.5 #Normalisierte Standardabweichung
batch_size_train = 256 #Stapelgröße lernen
batch_size_test = 16 #Chargengröße testen
val_ratio = 0.2 #Verhältnis von Validierungsdaten zu Gesamtdaten
epoch_num = 30 #Anzahl der Lernepochen
class Net(nn.Module):
#Definition der Netzwerkstruktur
def __init__(self):
super(Net, self).__init__()
self.init_conv = nn.Conv2d(3,16,3,padding=1)
self.conv1 = nn.ModuleList([nn.Conv2d(16,16,3,padding=1) for _ in range(3)])
self.bn1 = nn.ModuleList([nn.BatchNorm2d(16) for _ in range(3)])
self.pool = nn.MaxPool2d(2, stride=2)
self.fc1 = nn.ModuleList([nn.Linear(16*16*16, 128), nn.Linear(128, 32)])
self.output_fc = nn.Linear(32, 10)
#Vorwärtsberechnung
def forward(self, x):
x = F.relu(self.init_conv(x))
for l,bn in zip(self.conv1, self.bn1):
x = F.relu(bn(l(x)))
x = self.pool(x)
x = x.view(-1,16*16*16) # flatten
for l in self.fc1:
x = F.relu(l(x))
x = self.output_fc(x)
return x
def set_GPU():
#GPU-Einstellungen
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
return device
def load_data():
#Lade Daten
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((ave,),(std,))])
train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
#Aufteilung der Validierungsdaten
n_samples = len(train_set)
val_size = int(n_samples * val_ratio)
train_set, val_set = torch.utils.data.random_split(train_set, [(n_samples-val_size), val_size])
#DataLoader-Definition
train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size_train, shuffle=True, num_workers=2)
val_loader = torch.utils.data.DataLoader(val_set, batch_size=batch_size_train, shuffle=False, num_workers=2)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size_test, shuffle=False, num_workers=2)
return train_loader, test_loader, val_loader
def train():
device = set_GPU()
train_loader, test_loader, val_loader = load_data()
model = Net()
model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=5, verbose=True)
min_loss = 999999999
print("training start")
for epoch in range(epoch_num):
train_loss = 0.0
val_loss = 0.0
train_batches = 0
val_batches = 0
model.train() #Trainingsmodus
for i, data in enumerate(train_loader): #Batch gelesen
inputs, labels = data[0].to(device), data[1].to(device) #Daten sind[inputs, labels]Liste von
#Gradient Reset
optimizer.zero_grad()
outputs = model(inputs) #Vorwärtsberechnung
loss = criterion(outputs, labels) #Verlustberechnung
loss.backward() #Umgekehrte Berechnung(Gradientenberechnung)
optimizer.step() #Parameteraktualisierung
#Kumulative Geschichte
train_loss += loss.item()
train_batches += 1
# validation_Berechnung des Verlustes
model.eval() #Inferenzmodus
with torch.no_grad():
for i, data in enumerate(val_loader): #Batch gelesen
inputs, labels = data[0].to(device), data[1].to(device) #Daten sind[inputs, labels]Liste von
outputs = model(inputs) #Vorwärtsberechnung
loss = criterion(outputs, labels) #Verlustberechnung
#Kumulative Geschichte
val_loss += loss.item()
val_batches += 1
#Verlaufsausgabe
print('epoch %d train_loss: %.10f' %
(epoch + 1, train_loss/train_batches))
print('epoch %d val_loss: %.10f' %
(epoch + 1, val_loss/val_batches))
with open("history.csv",'a') as f:
print(str(epoch+1) + ',' + str(train_loss/train_batches) + ',' + str(val_loss/val_batches),file=f)
#Speichern Sie das beste Modell
if min_loss > val_loss/val_batches:
min_loss = val_loss/val_batches
PATH = "best.pth"
torch.save(model.state_dict(), PATH)
#Dynamische Änderung der Lernrate
scheduler.step(val_loss/val_batches)
#Modell der letzten Epoche speichern
print("training finished")
PATH = "lastepoch.pth"
torch.save(model.state_dict(), PATH)
if __name__ == "__main__":
train()
Die in diesem Artikel verwendeten Bibliotheken lauten wie folgt.
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
Definieren Sie die Konstanten, die später verwendet werden sollen.
ave = 0.5 #Normalisierter Durchschnitt
std = 0.5 #Normalisierte Standardabweichung
batch_size_train = 256 #Stapelgröße lernen
batch_size_test = 16 #Chargengröße testen
val_ratio = 0.2 #Verhältnis von Validierungsdaten zu Gesamtdaten
epoch_num = 30 #Anzahl der Lernepochen
Wenn Sie eine GPU verwenden, müssen Sie das Gerät vor verschiedenen Einstellungen angeben.
def set_GPU():
#GPU-Einstellungen
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
return device
Die GPU wird über dieses Objekt namens Gerät verwendet. Zum Beispiel
data.to(device)
model.to(device)
Auf diese Weise können Daten- und neuronale Netzwerkmodelle auf die GPU geladen werden.
PyTorch verfügt bereits über mehrere Datensätze. Mit CIFAR10 können Sie beispielsweise Folgendes vorbereiten.
def load_data():
#Lade Daten
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((ave,),(std,))])
train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
#Aufteilung der Validierungsdaten
n_samples = len(train_set)
val_size = int(n_samples * val_ratio)
train_set, val_set = torch.utils.data.random_split(train_set, [(n_samples-val_size), val_size])
#DataLoader-Definition
train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size_train, shuffle=True, num_workers=2)
val_loader = torch.utils.data.DataLoader(val_set, batch_size=batch_size_train, shuffle=False, num_workers=2)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size_test, shuffle=False, num_workers=2)
return train_loader, test_loader, val_loader
Ich werde in der Reihenfolge erklären. Erstens stellt die Transformation eine Reihe von Prozessen dar, die die Funktion haben, Daten zu transformieren.
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((ave,),(std,))])
Im obigen Beispiel wird transforms.ToTensor () verwendet, um die Daten in Tensor (PyTorch-Datentyp) zu ändern, und dann wird transforms.Normalize ((ave,), (std,)) verwendet, um den Mittelwert ave und die Standardabweichung std zu normalisieren. Geht. Komponieren spielt die Rolle der Organisation einer Reihe von Prozessen. Es gibt verschiedene andere Arten der Datenkonvertierung. Siehe Dokumentation.
Lesen Sie als nächstes die Daten von CIFAR10.
train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
Geben Sie das Download-Ziel für root an. Es scheint, dass der vorhandene Datensatz von PyTorch in Zug und Test unterteilt ist. Geben Sie mit einer Option namens Zug an. Stellen Sie hier die Datenkonvertierung ein, indem Sie die zuvor als Argument definierte Transformation übergeben.
Ich möchte die Verifizierungsdaten von den gelesenen Daten trennen. Dafür verwenden wir torch.utils.data.random_split.
train_set, val_set = torch.utils.data.random_split(train_set, [(n_samples-val_size), val_size])
Die Daten werden zufällig in die durch das zweite Argument angegebene Zahl unterteilt.
Beim Lernen mit PyTorch ist die Verwendung von DataLoader beim Lernen sehr praktisch. Machen Sie es wie folgt.
train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size_train, shuffle=True, num_workers=2)
val_loader = torch.utils.data.DataLoader(val_set, batch_size=batch_size_train, shuffle=False, num_workers=2)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size_test, shuffle=False, num_workers=2)
Übergeben Sie den Datensatz als erstes Argument. batch_size gibt die Stapelgröße an, shuffle gibt das Vorhandensein oder Fehlen von Datenmischungen an und num_workers gibt die Anzahl der Unterprozesse (Anzahl der Parallelen) zum Zeitpunkt des Lesens an. Die Identität von DataLoader ist übrigens ein Iterator. Daher wird zum Zeitpunkt des Lernens die for-Anweisung verwendet, um die Daten für jeden Stapel abzurufen.
Es gibt viele Situationen, in denen Sie Ihre eigenen Daten verwenden möchten. In diesem Fall sollten Sie Ihre eigene Dataset-Klasse wie folgt definieren. (Referenz: https://qiita.com/mathlive/items/2a512831878b8018db02)
class MyDataset(torch.utils.data.Dataset):
def __init__(self, data, label, transform=None):
self.transform = transform
self.data = data
self.data_num = len(data)
self.label = label
def __len__(self):
return self.data_num
def __getitem__(self, idx):
out_data = self.data[idx]
out_label = self.label[idx]
if self.transform:
out_data = self.transform(out_data)
return out_data, out_label
Definieren Sie mindestens len (eine Funktion, die die Datengröße zurückgibt) und getitem (eine Funktion, die Daten abruft) in der Klasse. Mit diesem,
dataset = MyDataset(Eingabedaten,Lehreretikett, transform=Erforderliche Datenkonvertierung)
Sie können einen solchen Datensatz erstellen. Abgesehen davon, wenn Sie sich den Inhalt von MyDataset ansehen, können Sie irgendwie verstehen, was PyTorch im Inneren tut. Mit anderen Worten, wenn idx angegeben wird, werden die diesem Index entsprechenden Daten zurückgegeben. Wenn eine Transformation angegeben wird, wird sie außerdem auf die Daten angewendet. Es ist möglich, verschiedene Operationen durchzuführen, indem Sie mit getitem spielen (z. B. mit zwei Transformationen), dies wird jedoch weggelassen.
Mit PyTorch ist es einfach, ein neuronales Netzwerk mithilfe von Klassen zu definieren. Für den CIFAR10-Klassifikator zum Beispiel:
class Net(nn.Module):
#Definition der Netzwerkstruktur
def __init__(self):
super(Net, self).__init__()
self.init_conv = nn.Conv2d(3,16,3,padding=1)
self.conv1 = nn.ModuleList([nn.Conv2d(16,16,3,padding=1) for _ in range(3)])
self.bn1 = nn.ModuleList([nn.BatchNorm2d(16) for _ in range(3)])
self.pool = nn.MaxPool2d(2, stride=2)
self.fc1 = nn.ModuleList([nn.Linear(16*16*16, 128), nn.Linear(128, 32)])
self.output_fc = nn.Linear(32, 10)
#Vorwärtsberechnung
def forward(self, x):
x = F.relu(self.init_conv(x))
for l,bn in zip(self.conv1, self.bn1):
x = F.relu(bn(l(x)))
x = self.pool(x)
x = x.view(-1,16*16*16) # flatten
for l in self.fc1:
x = F.relu(l(x))
x = self.output_fc(x)
return x
Ich werde verschiedene Dinge erklären.
Das Netzwerk wird durch Erben von nn.Module erstellt. Definieren Sie jede Ebene als eigenes Mitglied in init.
def __init__(self):
super(Net, self).__init__()
self.init_conv = nn.Conv2d(3,16,3,padding=1)
self.conv1 = nn.ModuleList([nn.Conv2d(16,16,3,padding=1) for _ in range(3)])
self.bn1 = nn.ModuleList([nn.BatchNorm2d(16) for _ in range(3)])
self.pool = nn.MaxPool2d(2, stride=2)
self.fc1 = nn.ModuleList([nn.Linear(16*16*16, 128), nn.Linear(128, 32)])
self.output_fc = nn.Linear(32, 10)
Beispielsweise übergibt nn.Conv2d die folgenden Argumente.
nn.Conv2d(Anzahl der Eingangskanäle, Anzahl der Ausgangskanäle, Kernelgröße, Auffüllung=Polstergröße, Schritt=Bewegungsumfang)
Einzelheiten finden Sie im offiziellen Dokument. (https://pytorch.org/docs/stable/nn.html)
Netzwerkschichten können mit nn.ModuleList auch als Arrays definiert werden.
self.conv1 = nn.ModuleList([nn.Conv2d(16,16,3,padding=1) for _ in range(3)])
Besonders nützlich beim Definieren eines großen Netzwerks mit einer sich wiederholenden Struktur. Es scheint, dass die Parameter nicht aktualisiert werden können, wenn das Array ohne Verwendung von nn.ModuleList in ein normales Array geändert wird. (Referenz: https://qiita.com/perrying/items/857df46bb6cdc3047bd8) Stellen Sie sicher, dass Sie nn.ModuleList ordnungsgemäß verwenden.
Die Vorwärtsberechnung wird als Vorwärtsberechnung definiert.
def forward(self, x):
x = F.relu(self.init_conv(x))
for l,bn in zip(self.conv1, self.bn1):
x = F.relu(bn(l(x)))
x = self.pool(x)
x = x.view(-1,16*16*16) # flatten
for l in self.fc1:
x = F.relu(l(x))
x = self.output_fc(x)
return x
Die durch nn.ModuleList angeordneten Ebenen werden durch die for-Anweisung extrahiert. Diese Art von Ort ist auch bequem. Auch unterwegs
x = x.view(-1,16*16*16) # flatten
ein. Hier werden die bildähnlichen Daten in einen eindimensionalen Vektor umgewandelt (übergeben Sie die Anzahl der Kanäle x die vertikale Breite des Bildes x die horizontale Breite des Bildes als zweites Argument). Der Grund, warum das erste Argument -1 ist, ist, dass die Konvertierung automatisch entsprechend der Stapelgröße durchgeführt wird.
Die Verlustfunktion ist in torch.nn definiert und die Aktualisierungsmethode ist in torch.optim definiert, die aufgerufen und verwendet werden. Dieses Mal verwenden wir CrossEntropyLoss als Verlustfunktion für die Klassifizierung. Adam wird als Aktualisierungsmethode verwendet.
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
Um effizient zu lernen, möchten wir möglicherweise die Lernrate dynamisch einstellen (oder reduzieren). Verwenden Sie in diesem Fall etwas namens lr_scheduler. Zum Beispiel
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=5, verbose=True)
Definieren Sie den Scheduler wie folgt. Damit wird nach Berechnung des Verlustes an den Validierungsdaten
scheduler.step(val_loss)
Wenn sich während der (Geduld-) Epoche keine Verbesserung ergibt, wird die Lernrate beim Schreiben automatisch reduziert. Dies kann eine Stagnation des Lernens verhindern.
Wie beim Datensatz möchten Sie die Verlustfunktion möglicherweise selbst erstellen. Es scheint, dass dies durch Erben der PyTorch-Klasse erstellt oder als einfache Funktion definiert werden kann. (Referenz: https://kento1109.hatenablog.com/entry/2018/08/13/092939) Für einfache Regressions- / Klassifizierungsaufgaben funktioniert MSELoss / CrossEntropyLoss häufig gut, aber für Papiere zum maschinellen Lernen wurde die Verlustfunktion entwickelt, um die Leistung zu verbessern, sodass es sich um eine ziemlich wichtige Implementierung handelt.
Nachdem wir uns bis zu diesem Punkt vorbereitet haben, werden wir endlich anfangen zu lernen. Wenden Sie zunächst jede bisher definierte Einstellung an.
device = set_GPU()
train_loader, test_loader, val_loader = load_data()
model = Net()
model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=5, verbose=True)
Das Lernen ist wie folgt. Grundsätzlich wie auskommentiert.
min_loss = 999999999
print("training start")
for epoch in range(epoch_num):
train_loss = 0.0
val_loss = 0.0
train_batches = 0
val_batches = 0
model.train() #Trainingsmodus
for i, data in enumerate(train_loader): #Batch gelesen
inputs, labels = data[0].to(device), data[1].to(device) #Daten sind[inputs, labels]Liste von
#Gradient Reset
optimizer.zero_grad()
outputs = model(inputs) #Vorwärtsberechnung
loss = criterion(outputs, labels) #Verlustberechnung
loss.backward() #Umgekehrte Berechnung(Gradientenberechnung)
optimizer.step() #Parameteraktualisierung
#Kumulative Geschichte
train_loss += loss.item()
train_batches += 1
# validation_Berechnung des Verlustes
model.eval() #Inferenzmodus
with torch.no_grad():
for i, data in enumerate(val_loader): #Batch gelesen
inputs, labels = data[0].to(device), data[1].to(device) #Daten sind[inputs, labels]Liste von
outputs = model(inputs) #Vorwärtsberechnung
loss = criterion(outputs, labels) #Verlustberechnung
#Kumulative Geschichte
val_loss += loss.item()
val_batches += 1
#Verlaufsausgabe
print('epoch %d train_loss: %.10f' %
(epoch + 1, train_loss/train_batches))
print('epoch %d val_loss: %.10f' %
(epoch + 1, val_loss/val_batches))
with open("history.csv",'a') as f:
print(str(epoch+1) + ',' + str(train_loss/train_batches) + ',' + str(val_loss/val_batches),file=f)
#Speichern Sie das beste Modell
if min_loss > val_loss/val_batches:
min_loss = val_loss/val_batches
PATH = "best.pth"
torch.save(model.state_dict(), PATH)
#Dynamische Änderung der Lernrate
scheduler.step(val_loss/val_batches)
#Modell der letzten Epoche speichern
print("training finished")
PATH = "lastepoch.pth"
torch.save(model.state_dict(), PATH)
In PyTorch muss die Berechnung der Verlustfunktion und die Rückausbreitung des Fehlers explizit beschrieben werden (es gibt auch eine Bibliothek, die dies abschließt).
Offizielles Tutorial (https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html) zum Testen des erlernten Modells usw. ). Bei diesem Modell beträgt die Genauigkeit jeder Klasse etwa 40 bis 80% (die Unschärfe ist ziemlich groß ...).
Für Batch-Normalisierung und Drop-Out ist es erforderlich, das Verhalten zwischen Lernen und Inferenz umzuschalten. Daher während des Lernens und der Schlussfolgerung
model.train()
model.eval()
Es scheint notwendig zu sein, jede Beschreibung hinzuzufügen. Darüber hinaus gibt es einen Modus namens torch.no_grad (). Dies ist ein Modus, in dem keine Verlaufsinformationen gespeichert werden. Da zum Zeitpunkt der Überprüfung keine Rückwärtsberechnung durchgeführt wird, sind keine Gradienteninformationen erforderlich. Wenn Sie diese nicht angeben, wird die Berechnungsgeschwindigkeit erhöht und Speicherplatz gespart.
Ich fühle mich auf dem Weg erschöpft, also kann ich es bald hinzufügen. Ich bin froh, wenn Sie es als Referenz verwenden können.
Mr. Fukuit: https://qiita.com/fukuit/items/215ef75113d97560e599 Mr. Perry: https://qiita.com/perrying/items/857df46bb6cdc3047bd8
Offizielles Tutorial: https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html transform Offizielle Dokumentation: https://pytorch.org/docs/stable/torchvision/transforms.html
Offizielle Dokumentation: https://pytorch.org/docs/stable/torchvision/datasets.html
Offizielle Dokumentation: https://pytorch.org/docs/stable/data.html
mathlive: https://qiita.com/mathlive/items/2a512831878b8018db02
Herr kento1109: https://kento1109.hatenablog.com/entry/2018/08/13/092939
Offizielle Dokumentation: https://pytorch.org/docs/stable/generated/torch.nn.ModuleList.html
Offizielle Dokumentation: https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate
Offizielles Tutorial: https://pytorch.org/tutorials/beginner/saving_loading_models.html jyori112: https://qiita.com/jyori112/items/aad5703c1537c0139edb
Offizielles Tutorial: https://pytorch.org/tutorials/beginner/saving_loading_models.html PyTorch-Forum: https://discuss.pytorch.org/t/model-eval-vs-with-torch-no-grad/19615 Herr s0sem0y: https://www.hellocybernetics.tech/entry/2018/02/20/182906