Ich lese ein Meisterwerk, ** "Deep Learning from Zero" **. Diesmal ist ein Memo von Kapitel 6. Um den Code auszuführen, laden Sie den gesamten Code von Github herunter und verwenden Sie das Jupiter-Notizbuch in Kapitel 06.
Um die Optimierungsmethode tatsächlich auszuprobieren, verwenden wir ch06 / optimizer_compare_mnist.py
mit einigen Modifikationen / Ergänzungen. Das Netzwerk ist eine 100x4-Schicht, die MNIST klassifiziert. Mit der "Optimierungsschlüsseleinstellung" im folgenden Code können Sie nur den Optimierer auskommentieren und ausführen, den Sie nicht verwenden.
import os
import sys
sys.path.append(os.pardir) #Einstellungen zum Importieren von Dateien in das übergeordnete Verzeichnis
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.util import smooth_curve # smooth_curve (Eine Funktion, die den Übergang des Verlustwerts glättet)importieren
from common.multi_layer_net import MultiLayerNet #MultiLayerNet-Import
from common.optimizer import * #Optimiererimport
#MNIST-Daten lesen
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
#Grundeinstellung
train_size = x_train.shape[0]
batch_size = 128
max_iterations = 2001
#Optimierungstasteneinstellung
optimizers = {}
optimizers['SGD'] = SGD()
optimizers['Momentum'] = Momentum()
optimizers['Nesterov'] = Nesterov()
optimizers['AdaGrad'] = AdaGrad()
optimizers['RMSprop'] = RMSprop()
optimizers['Adam'] = Adam()
#Netzwerk und Zug_Stellen Sie den Verlust für jeden Optimierungsschlüssel ein
networks = {}
train_loss = {}
for key in optimizers.keys():
networks[key] = MultiLayerNet(
input_size=784, hidden_size_list=[100, 100, 100, 100],
output_size=10)
train_loss[key] = []
#Lernschleife
for i in range(max_iterations):
#Extrahieren Sie Mini-Batch-Daten
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
#Aktualisieren Sie den Farbverlauf und zeichnen Sie den Verlust für jeden Optimierungsschlüssel auf
for key in optimizers.keys():
grads = networks[key].gradient(x_batch, t_batch)
optimizers[key].update(networks[key].params, grads)
loss = networks[key].loss(x_batch, t_batch)
train_loss[key].append(loss)
#Verlustanzeige(Alle 500 Iter)
if i % 500 == 0:
print( "===========" + "iteration:" + str(i) + "===========")
for key in optimizers.keys():
loss = networks[key].loss(x_batch, t_batch)
print(key + ":" + str(loss))
#Zeichnen eines Diagramms
fig = plt.figure(figsize=(8,6)) #Spezifikation der Diagrammgröße
markers = {"SGD": "o", "Momentum": "x", "Nesterov": "^", "AdaGrad": "s", "RMSprop":"*", "Adam": "D", }
x = np.arange(max_iterations)
for key in optimizers.keys():
plt.plot(x, smooth_curve(train_loss[key]), marker=markers[key], markevery=100, label=key)
plt.xlabel("iterations")
plt.ylabel("loss")
plt.ylim(0, 1)
plt.legend()
plt.show()
3.SGD Das Basismodell der Optimierungsmethode ist "SGD", das bis zu Kapitel 5 verwendet wurde.
Betrachtet man die Implementierung von SGD
in common / optimizer.py
,
class SGD:
def __init__(self, lr=0.01):
self.lr = lr
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr * grads[key]
4.Momentum
SGD
braucht Zeit, um zu optimieren, insbesondere in den frühen Stadien, da Gradientenaktualisierungen immer konstant sind. Hier kommt "Momentum" ins Spiel.
"Momentum" ist eine Methode, um den Grad der Gradientenaktualisierung schrittweise zu erhöhen, während sich die Richtung des Gradienten nicht ändert. Es ist nur ein Bild des Balls, der entsprechend der Neigung des Bodens rollt, und $ \ alpha = 0,9 $ kann als Reibung und Luftwiderstand des Bodens angesehen werden.
Um das Bild etwas konkreter auszudrücken, beispielsweise unter der Annahme, dass die Ergebnisse der vier Gradientenberechnungen für $ \ frac {\ partielles L} {\ partielles W} $ gleich sind, ist v
Sie können sehen, dass der Grad der Aktualisierung des Verlaufs allmählich auf -1,0, -1,9, -2,71, -3,439 ansteigt. Betrachtet man die Implementierung von Momentum
in common / optimizer.py
,
class Momentum:
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] = self.momentum*self.v[key] - self.lr*grads[key]
params[key] += self.v[key]
5.Nesterov Das Momentum neigt zu Überschwingen, wenn die Richtung des Gradienten nach Erhöhen des Gradienten der Gradientenaktualisierung umgekehrt wird. Hier wird der "Nesterov" (auch bekannt als Nestrovs Impuls) eingeführt, der eine teilweise Modifikation des Impulses darstellt.
Nesterov
ändert die Position, an der der Gradient berechnet wird, auf die Position nach der Gradientenaktualisierung, einen Schritt voraus, anstelle der aktuellen Position. Natürlich kenne ich die genaue Position nach dem Aktualisieren des Verlaufs nicht, aber ich werde sie ersetzen, indem ich das ungefähre v unter Verwendung des aktuellen Verlaufs finde. Es ist zu erwarten, dass dies ein Überschwingen unterdrückt.
Betrachtet man die Implementierung in common / optimizer.py
,
class Nesterov:
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] *= self.momentum
self.v[key] -= self.lr * grads[key]
params[key] += self.momentum * self.momentum * self.v[key]
params[key] -= (1 + self.momentum) * self.lr * grads[key]
Vergleichen wir nun "SGD", "Momentum", "Nesterov".
Im Vergleich zu "SGD" haben "Momentum" und "Nesterov" die Geschwindigkeit der anfänglichen Verlustreduzierung und die endgültige Verlustrate überwältigend verbessert. "Nesterov" ist einen Schritt besser als "Momentum", und es scheint, dass die Variation des Verlusts etwas geringer ist.
6.AdaGrad AdaGrad stellt zwei wichtige Ideen vor.
Das erste ist die Idee der "adaptiven Lernrate" (Adaptive), die besagt, dass eine große Anzahl von Parametern gemäß den Parametern optimiert werden sollte, anstatt auf einmal optimiert zu werden.
Die zweite ist die Idee, den Lernkoeffizienten zu verringern, um die Lernrate zu Beginn des Lernens zu erhöhen und die Lernrate im Verlauf des Lernens zu verringern, um das Lernen effizient zu fördern.
Die Lernrate wird korrigiert, indem die Summe der Quadrate des Gradienten auf h akkumuliert und beim Aktualisieren des Gradienten mit $ \ frac {1} {\ sqrt {h} + \ epsilon} $ multipliziert wird. Mit anderen Worten wird die Lernrate der stark aktualisierten Parameter allmählich verringert.
$ \ Epsilon $ ist übrigens eine sehr kleine Zahl (um eine Nullteilung zu verhindern). Betrachtet man die Implementierung von common / optimizer.py
,
class AdaGrad:
def __init__(self, lr=0.01):
self.lr = lr
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] += grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
7.RMSprop
RMSprop
ist eine verbesserte Version von AdaGrad
, die den ** exponentiellen gleitenden Durchschnitt ** des Quadrats des Gradienten in h speichert (vergangene Berechnungsergebnisse nach und nach vergessen und neue Berechnungsergebnisse einbeziehen) und den Gradienten aktualisiert. Die Lernrate wird korrigiert, indem dabei $ \ frac {1} {\ sqrt {h} + \ epsilon} $ multipliziert wird.
Betrachtet man die Implementierung von common / optimizer.py
,
class RMSprop:
def __init__(self, lr=0.01, decay_rate = 0.99):
self.lr = lr
self.decay_rate = decay_rate
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] *= self.decay_rate
self.h[key] += (1 - self.decay_rate) * grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
8.Adam "Adam" ist das Ergebnis der Idee, das Beste aus "Momentum" und "AdaGrad" herauszuholen.
m
ist wie der exponentielle gleitende Durchschnitt von Momentum
und v
ist AdaGrad
selbst. Danach werden "m" und "v" in großem Umfang verwendet, während der Iter klein ist, und der Nutzungsgrad wird mit zunehmendem Iter geschwächt. Die Implementierung erfolgt durch geringfügiges Ändern der Formel wie unten gezeigt.
Betrachtet man die Implementierung von common / optimizer.py
,
class Adam:
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.iter = 0
self.m = None
self.v = None
def update(self, params, grads):
if self.m is None:
self.m, self.v = {}, {}
for key, val in params.items():
self.m[key] = np.zeros_like(val)
self.v[key] = np.zeros_like(val)
self.iter += 1
lr_t = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)
for key in params.keys():
self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])
params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
Vergleichen wir nun "AdaGrad", "RMSprop", "Adam".
![Screenshot 2020-05-05 19.06.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/4ba770be-3cfe-15a1-16bb- 030899994296.png)
Es gibt keine Optimierungsmethode, die für jede Aufgabe die besten Ergebnisse liefert. Diesmal lieferte "AdaGrad" die besten Ergebnisse.
Die Lernratenkorrektur durch den exponentiellen gleitenden Durchschnitt des Quadrats des Gradienten von "RMSprop" war für diese Aufgabe übertrieben, und die Verlustamplitude wurde sehr groß, und die endgültige Verlustrate wurde ebenfalls hoch.
Es wird gesagt, dass "Adam" eine Optimierungsmethode ist, die vorerst zuerst verwendet werden sollte und immer eine stabile Leistung zeigt. Diese Aufgabe ist nicht die beste, zeigt aber einen Übergang zur Reduzierung des Verlusts von Ehrenschülern.
Wenn der Anfangswert des Gewichts Null ist, werden alle Gewichtswerte einheitlich aktualisiert und die Ausdruckskraft geht verloren. Welche Art der Initialisierung gut ist, hängt von der Art der Aktivierungsfunktion ab.
Wenn die Sigmoidfunktion, die Tanh-Funktion usw. symmetrisch sind und der mittlere Bereich als lineare Funktion betrachtet werden kann, wird die Gaußsche Verteilung mit $ \ sqrt {\ frac {1} {n}} $ als Standardabweichung, die als "Xavier-Initialisierung" bezeichnet wird, als optimal bezeichnet. Es ist
Bei Verwendung von ReUL wird gesagt, dass die Gaußsche Verteilung mit $ \ sqrt {\ frac {2} {n}} $ als Standardabweichung, die als "He-Initialisierung" bezeichnet wird, optimal ist.
10.Dropout "Dropout" ist eine Methode, um das Überlernen selbst in einem sehr ausdrucksstarken Netzwerk zu unterdrücken, indem die für jeden Iter zufällig ausgewählten Neuronen während des Lernens getrennt werden. Mit Blick auf den Implementierungscode,
class Dropout:
def __init__(self, dropout_ratio=0.5):
self.dropout_ratio = dropout_ratio
self.mask = None
#Vorwärtsausbreitung
def forward(self, x, train_flg=True):
#Erstellen Sie zum Zeitpunkt des Lernens eine Maske, die bestimmt, ob eine Verbindung hergestellt werden soll, und multiplizieren Sie das sich vorwärts ausbreitende Signal mit der Maske.
if train_flg:
self.mask = np.random.rand(*x.shape) > self.dropout_ratio
return x * self.mask
#Wenden Sie beim Ableiten keine Maske an und decken Sie das gesamte Signal ab(1 - dropout_ratio)Multiplizieren
else:
return x * (1.0 - self.dropout_ratio)
#Multiplizieren Sie das Backpropagating-Signal mit der Maske
def backward(self, dout):
return dout * self.mask
Bei jeder Lernsitzung werden eine einheitliche Zufallszahl und ein Schwellenwert (dropout_ratio) verwendet, um eine Maske (True für verbindbar, False für nicht verbindbar) zu erstellen, die bestimmt, ob eine Verbindung hergestellt werden soll oder nicht. Maskieren Sie dann das sich vorwärts ausbreitende Signal (x * self.mask). In ähnlicher Weise maskieren Sie das Signal während der Rückausbreitung.
In einem konkreten Bild sieht es so aus:
Zum Zeitpunkt der Inferenz wird das gesamte Signal ohne Maskierung mit (1 --dropout_ratio) multipliziert, und nur die Größe des gesamten Signals wird angepasst.
11.Batch Normalization
Batch Normalization
ist eine 2015 angekündigte Methode, die die Lernkonvergenzgeschwindigkeit verbessert, den Bedarf an Dropout
verringert und anfänglich gewichtet, indem jeder Mini-Batch so normalisiert wird, dass er einen Durchschnitt von 0 und eine Verteilung von 1 hat. Sie können Effekte wie die Reduzierung des Konvertierungsbedarfs erzielen (robust für die Gewichtsinitialisierung). Unten ist der Algorithmus.
12.Weight decay Der Gewichtsabfall ist eine Methode zur Unterdrückung des Überlernens, indem eine Strafe für ein großes Gewicht im Lernprozess verhängt wird.
Wenn das Gewicht W ist, ist es durch Hinzufügen von $ \ frac {1} {2} \ lambda W ^ 2 $ zur Verlustfunktion möglich, die Zunahme des Gewichts W zu unterdrücken, und diese Methode wird ** L2 reguliert ** Wird genannt. Wenn Sie hier $ \ lambda $ erhöhen, können Sie die Strafe erhöhen. ![Screenshot 2020-05-06 14.31.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/ddbf678e-4b99-e2f8-a182- 76a1329147c5.png)
Übrigens in der Verlustfunktion
Recommended Posts