Die Lernrate ist ein wichtiger Hyperparameter in Deep-Learning-Netzwerken – und bestimmt direkt die (...) an denen Aktualisierungen von Gewichten durchgeführt werden, die geschätzt werden, um eine gegebene Verlustfunktion zu minimieren. Im SGD:
$$
Gewicht_{t+1} = Gewicht_t – lr * frac{derror}{dweight_t}
$$
Mit einer Lernrate von 0
, das aktualisierte Gewicht ist gerade wieder auf sich selbst gestellt – Gewichtt. Die Lernrate ist praktisch ein Knopf, an dem wir drehen können, um das Lernen zu aktivieren oder zu deaktivieren, und sie hat einen großen Einfluss darauf, wie viel Lernen passiert, indem sie den Grad der Gewichtsaktualisierungen direkt steuert.
Verschiedene Optimierer verwenden Lernraten unterschiedlich – aber das zugrunde liegende Konzept bleibt gleich. Selbstverständlich sind Lernraten Gegenstand vieler Studien, Veröffentlichungen und Praxis-Benchmarks.
Im Allgemeinen sind sich so ziemlich alle einig, dass eine statische Lernrate nicht ausreicht, und bei den meisten Techniken, die die Lernrate während des Trainings anpassen, findet eine Art von Lernratenreduzierung statt – sei es monoton, kosinusförmig, dreieckig oder andere Arten von die Ermäßigung.
Eine Technik, die in den letzten Jahren Fuß gefasst hat Lernrate Aufwärmen, die praktisch mit jeder anderen Reduktionstechnik kombinierbar ist.
Lernrate Aufwärmen
Die Idee hinter dem Aufwärmen der Lernrate ist einfach. In den frühesten Stadien des Trainings – Gewichte sind weit von ihrem Idealzustand entfernt. Dies bedeutet große Aktualisierungen auf der ganzen Linie, die als „Überkorrekturen“ für jedes Gewicht angesehen werden können – wobei die drastische Aktualisierung eines anderen die Aktualisierung eines anderen Gewichts zunichte machen kann, wodurch die Anfangsphasen des Trainings instabiler werden.
Diese Änderungen bügeln aus, können aber vermieden werden, indem man zunächst eine kleine Lernrate hat, einen stabileren suboptimalen Zustand erreicht und dann eine größere Lernrate anwendet. Sie können das Netzwerk sozusagen in Updates einbinden, anstatt es mit ihnen zu treffen.
Das ist das Aufwärmen der Lernrate! Beginnen Sie mit einer niedrigen (oder 0) Lernrate und erhöhen Sie sich auf eine Startlernrate (womit Sie sowieso beginnen würden). Dieser Anstieg kann wirklich jeder Funktion folgen, ist aber üblicherweise linear.
Nach Erreichen der Anfangsrate können andere Zeitpläne wie Kosinusabfall, lineare Reduktion usw. angewendet werden, um die Rate schrittweise bis zum Ende des Trainings zu senken. Das Aufwärmen der Lernrate ist normalerweise Teil eines Zeitplans mit zwei Zeitplänen, bei dem das Aufwärmen von LR der erste ist, während ein anderer Zeitplan übernimmt, nachdem die Rate einen Startpunkt erreicht hat.
In diesem Leitfaden implementieren wir ein Aufwärmen der Lernrate in Keras/TensorFlow als keras.optimizers.schedules.LearningRateSchedule
Unterklasse und keras.callbacks.Callback
zurückrufen. Die Lernrate wird von erhöht 0
zu target_lr
und wenden Sie den Kosinusabfall an, da dies ein sehr häufiger sekundärer Zeitplan ist. Wie gewohnt macht es Keras einfach, flexible Lösungen auf verschiedene Arten zu implementieren und mit Ihrem Netzwerk auszuliefern.
Hinweis: Die Implementierung ist generisch und inspiriert von Tonys Keras-Implementierung der Tricks, die in „Trickkiste zur Bildklassifizierung mit Convolutional Neural Networks“.
Lernrate mit Keras Callbacks
Der einfachste Weg, einen beliebigen Lernratenplan zu implementieren, besteht darin, eine Funktion zu erstellen, die die übernimmt lr
Parameter (float32
), durchläuft es eine Transformation und gibt es zurück. Diese Funktion wird dann an die weitergegeben LearningRateScheduler
Callback, der die Funktion auf die Lernrate anwendet.
Nun wird die tf.keras.callbacks.LearningRateScheduler()
übergibt die Epochennummer an die Funktion, die zur Berechnung der Lernrate verwendet wird, was ziemlich grob ist. LR Warmup sollte bei jedem durchgeführt werden Step (Batch), nicht Epoche, also müssen wir a ableiten global_step
(über alle Epochen), um stattdessen die Lernrate zu berechnen und die zu unterteilen Callback
Klasse, um einen benutzerdefinierten Rückruf zu erstellen, anstatt nur die Funktion zu übergeben, da wir bei jedem Aufruf Argumente übergeben müssen, was unmöglich ist, wenn nur die Funktion übergeben wird:
def func():
return ...
keras.callbacks.LearningRateScheduler(func)
Dieser Ansatz ist günstig, wenn Sie kein hohes Maß an Anpassung wünschen und nicht in die Art und Weise eingreifen möchten, wie Keras die behandelt lr
, und vor allem, wenn Sie Rückrufe wie verwenden möchten ReduceLROnPlateau()
da es nur mit einem Float-basierten funktionieren kann lr
. Lassen Sie uns ein Aufwärmen der Lernrate mit einem Keras-Callback implementieren, beginnend mit einer praktischen Funktion:
def lr_warmup_cosine_decay(global_step,
warmup_steps,
hold = 0,
total_steps=0,
start_lr=0.0,
target_lr=1e-3):
learning_rate = 0.5 * target_lr * (1 + np.cos(np.pi * (global_step - warmup_steps - hold) / float(total_steps - warmup_steps - hold)))
warmup_lr = target_lr * (global_step / warmup_steps)
if hold > 0:
learning_rate = np.where(global_step > warmup_steps + hold,
learning_rate, target_lr)
learning_rate = np.where(global_step < warmup_steps, warmup_lr, learning_rate)
return learning_rate
Bei jedem Schritt berechnen wir die Lernrate und die Aufwärm-Lernrate (beide Elemente des Zeitplans) in Bezug auf die start_lr
und target_lr
. start_lr
beginnt in der Regel um 0.0
, Während die target_lr
hängt von Ihrem Netzwerk und Optimierer ab – 1e-3
Dies ist möglicherweise kein guter Standard. Stellen Sie daher sicher, dass Sie beim Aufrufen der Methode Ihr Ziel festlegen, das LR startet.
Besitzt das global_step
in der Ausbildung ist höher als die warmup_steps
Wir haben festgelegt – wir verwenden den Kosinus-Abklingplan LR. Wenn nicht, bedeutet dies, dass wir uns noch aufwärmen, also wird das Aufwärm-LR verwendet. Wenn die hold
Argument gesetzt ist, halten wir die target_lr
für diese Anzahl von Schritten nach dem Aufwärmen und vor dem Kosinusabfall. np.where()
bietet eine großartige Syntax dafür:
np.where(condition, value_if_true, value_if_false)
Sie können die Funktion visualisieren mit:
steps = np.arange(0, 1000, 1)
lrs = []
for step in steps:
lrs.append(lr_warmup_cosine_decay(step, total_steps=len(steps), warmup_steps=100, hold=10))
plt.plot(lrs)
Nun möchten wir diese Funktion als Teil eines Rückrufs verwenden und den Optimierungsschritt als übergeben global_step
anstelle eines Elements eines beliebigen Arrays – oder Sie können die Berechnung innerhalb der Klasse durchführen. Lassen Sie uns die subclass Callback
Klasse:
from keras import backend as K
class WarmupCosineDecay(keras.callbacks.Callback):
def __init__(self, total_steps=0, warmup_steps=0, start_lr=0.0, target_lr=1e-3, hold=0):
super(WarmupCosineDecay, self).__init__()
self.start_lr = start_lr
self.hold = hold
self.total_steps = total_steps
self.global_step = 0
self.target_lr = target_lr
self.warmup_steps = warmup_steps
self.lrs = []
def on_batch_end(self, batch, logs=None):
self.global_step = self.global_step + 1
lr = model.optimizer.lr.numpy()
self.lrs.append(lr)
def on_batch_begin(self, batch, logs=None):
lr = lr_warmup_cosine_decay(global_step=self.global_step,
total_steps=self.total_steps,
warmup_steps=self.warmup_steps,
start_lr=self.start_lr,
target_lr=self.target_lr,
hold=self.hold)
K.set_value(self.model.optimizer.lr, lr)
Sehen Sie sich unseren praxisnahen, praktischen Leitfaden zum Erlernen von Git an, mit Best Practices, branchenweit akzeptierten Standards und einem mitgelieferten Spickzettel. Hören Sie auf, Git-Befehle zu googeln und tatsächlich in Verbindung, um es!
Zuerst definieren wir den Konstruktor für die Klasse und verfolgen ihre Felder. Bei jedem beendeten Stapel erhöhen wir den globalen Schritt, notieren den aktuellen LR und fügen ihn der Liste der bisherigen LRs hinzu. Zu Beginn jeder Charge berechnen wir die LR anhand der lr_warmup_cosine_decay()
Funktion und setzen Sie diese LR als aktuelle LR des Optimierers. Dies geschieht mit dem Backend set_value()
.
Wenn das erledigt ist, berechnen Sie einfach die Gesamtschritte (Länge/Batch_Größe*Epochen) und nehmen Sie einen Teil dieser Zahl für sich warmup_steps
:
total_steps = len(train_set)*config['EPOCHS']
warmup_steps = int(0.05*total_steps)
callback = WarmupCosineDecay(total_steps=total_steps,
warmup_steps=warmup_steps,
hold=int(warmup_steps/2),
start_lr=0.0,
target_lr=1e-3)
Erstellen Sie schließlich Ihr Modell und stellen Sie den Rückruf in der bereit fit()
Anruf:
model = keras.applications.EfficientNetV2B0(weights=None,
classes=n_classes,
input_shape=[224, 224, 3])
model.compile(loss="sparse_categorical_crossentropy",
optimizer='adam',
jit_compile=True,
metrics=['accuracy'])
Am Ende des Trainings können Sie die geänderten LRs erhalten und visualisieren über:
lrs = callback.lrs
plt.plot(lrs)
Wenn Sie den Verlauf eines Modells darstellen, das mit und ohne LR-Aufwärmung trainiert wurde, sehen Sie einen deutlichen Unterschied in der Stabilität des Trainings:
Lernrate mit LearningRateSchedule-Unterklasse
Eine Alternative zum Erstellen eines Callbacks ist das Erstellen einer LearningRateSchedule
Unterklasse, die das LR nicht manipuliert – es ersetzt es. Dieser Ansatz ermöglicht es Ihnen, etwas mehr in das Backend von Keras/TensorFlow einzudringen, kann aber bei Verwendung nicht mit anderen LR-bezogenen Callbacks kombiniert werden, wie z ReduceLROnPlateau()
, die LRs als Gleitkommazahlen behandelt.
Darüber hinaus erfordert die Verwendung der Unterklasse, dass Sie sie serialisierbar machen (Überladung get_config()
), da es Teil des Modells wird, wenn Sie die Modellgewichte speichern möchten. Eine weitere zu beachtende Sache ist, dass die Klasse erwartet, ausschließlich mit zu arbeiten tf.Tensor
s. Glücklicherweise besteht der einzige Unterschied in unserer Arbeitsweise darin, anzurufen tf.func()
statt np.func()
da die TensorFlow- und NumPy-APIs erstaunlich ähnlich und kompatibel sind.
Lassen Sie uns Bequemlichkeit umschreiben lr_warmup_cosine_decay()
Funktion, um stattdessen TensorFlow-Operationen zu verwenden:
def lr_warmup_cosine_decay(global_step,
warmup_steps,
hold = 0,
total_steps=0,
start_lr=0.0,
target_lr=1e-3):
learning_rate = 0.5 * target_lr * (1 + tf.cos(tf.constant(np.pi) * (global_step - warmup_steps - hold) / float(total_steps - warmup_steps - hold)))
warmup_lr = target_lr * (global_step / warmup_steps)
if hold > 0:
learning_rate = tf.where(global_step > warmup_steps + hold,
learning_rate, target_lr)
learning_rate = tf.where(global_step < warmup_steps, warmup_lr, learning_rate)
return learning_rate
Mit der Convinience-Funktion können wir die ableiten LearningRateSchedule
Klasse. Auf jeder __call__()
(Batch) berechnen wir den LR mit der Funktion und geben ihn zurück. Sie können die Berechnung natürlich auch innerhalb der untergeordneten Klasse packen.
Die Syntax ist sauberer als die Callback
sublcass, vor allem, weil wir Zugriff auf die erhalten step
Feld, anstatt es selbst zu verfolgen, macht es aber auch etwas schwieriger, mit Klasseneigenschaften zu arbeiten – insbesondere macht es es schwierig, die zu extrahieren lr
von einem tf.Tensor()
in einen anderen Typ, um ihn in einer Liste zu verfolgen. Dies kann technisch umgangen werden, indem man im eifrigen Modus läuft, stellt aber ein Ärgernis dar, um den LR zu Debugging-Zwecken im Auge zu behalten, und wird am besten vermieden:
class WarmUpCosineDecay(keras.optimizers.schedules.LearningRateSchedule):
def __init__(self, start_lr, target_lr, warmup_steps, total_steps, hold):
super().__init__()
self.start_lr = start_lr
self.target_lr = target_lr
self.warmup_steps = warmup_steps
self.total_steps = total_steps
self.hold = hold
def __call__(self, step):
lr = lr_warmup_cosine_decay(global_step=step,
total_steps=self.total_steps,
warmup_steps=self.warmup_steps,
start_lr=self.start_lr,
target_lr=self.target_lr,
hold=self.hold)
return tf.where(
step > self.total_steps, 0.0, lr, name="learning_rate"
)
Die Parameter sind die gleichen und können auf die gleiche Weise wie zuvor berechnet werden:
total_steps = len(train_set)*config['EPOCHS']
warmup_steps = int(0.05*total_steps)
schedule = WarmUpCosineDecay(start_lr=0.0, target_lr=1e-3, warmup_steps=warmup_steps, total_steps=total_steps, hold=warmup_steps)
Und die Trainingspipeline unterscheidet sich nur darin, dass wir das LR des Optimierers auf setzen schedule
:
model = keras.applications.EfficientNetV2B0(weights=None,
classes=n_classes,
input_shape=[224, 224, 3])
model.compile(loss="sparse_categorical_crossentropy",
optimizer=tf.keras.optimizers.Adam(learning_rate=schedule),
jit_compile=True,
metrics=['accuracy'])
history3 = model.fit(train_set,
epochs = config['EPOCHS'],
validation_data=valid_set)
Wenn Sie das Modell speichern möchten, wird die WarmupCosineDecay
Zeitplan muss die überschreiben get_config()
Verfahren:
def get_config(self):
config = {
'start_lr': self.start_lr,
'target_lr': self.target_lr,
'warmup_steps': self.warmup_steps,
'total_steps': self.total_steps,
'hold': self.hold
}
return config
Schließlich müssen Sie beim Laden des Modells a passieren WarmupCosineDecay
als benutzerdefiniertes Objekt:
model = keras.models.load_model('weights.h5',
custom_objects={'WarmupCosineDecay', WarmupCosineDecay})
Zusammenfassung
In diesem Leitfaden haben wir einen Blick auf die Intuition hinter Learning Rate Warmup geworfen – eine gängige Technik zur Manipulation der Lernrate beim Training neuronaler Netze.
Wir haben eine Lernratenaufwärmung mit Kosinusabfall implementiert, die häufigste Art der LR-Reduktion gepaart mit Aufwärmphase. Sie können jede andere Funktion zur Reduzierung implementieren oder die Lernrate überhaupt nicht reduzieren – und es anderen Callbacks überlassen, wie z ReduceLROnPlateau()
. Wir haben das Aufwärmen der Lernrate als Keras Callback sowie als Keras Optimizer Schedule implementiert und die Lernrate über die Epochen aufgetragen.