Szybkość uczenia się jest ważnym hiperparametrem w sieciach głębokiego uczenia – i bezpośrednio dyktuje stopień do których wykonywane są aktualizacje wag, które są szacowane w celu zminimalizowania określonej funkcji straty. W SGD:
$$
waga_{t+1} = waga_t – lr * frac{derror}{dweight_t}
$$
Z szybkością uczenia się 0
, zaktualizowana waga wróciła do siebie – wagat. Szybkość uczenia się jest w rzeczywistości pokrętłem, które możemy przekręcić, aby włączyć lub wyłączyć uczenie się i ma duży wpływ na to, jak intensywnie się uczymy, bezpośrednio kontrolując stopień aktualizacji wagi.
Różne optymalizatory w różny sposób wykorzystują tempo uczenia się – ale podstawowa koncepcja pozostaje taka sama. Nie trzeba dodawać, że wskaźniki uczenia się były przedmiotem wielu badań, artykułów i punktów odniesienia praktyków.
Ogólnie rzecz biorąc, prawie wszyscy zgadzają się, że statyczny współczynnik uczenia się go nie zmniejszy, a pewien rodzaj zmniejszenia tempa uczenia się ma miejsce w większości technik, które dostrajają tempo uczenia się podczas treningu – niezależnie od tego, czy jest to monotoniczne, cosinusowe, trójkątne czy inne rodzaje zmniejszenie.
Technika, która w ostatnich latach zyskała przyczółek, to: rozgrzewka tempa nauki, który można łączyć z praktycznie każdą inną techniką redukcji.
Rozgrzewka szybkości uczenia się
Idea rozgrzewki szybkości uczenia się jest prosta. Na najwcześniejszych etapach treningu – ciężary są dalekie od swoich idealnych stanów. Oznacza to duże aktualizacje na całej planszy, które mogą być postrzegane jako „nadmierne korekty” dla każdej wagi – gdzie drastyczna aktualizacja innej wagi może zanegować aktualizację innej wagi, czyniąc początkowe etapy treningu bardziej niestabilnymi.
Zmiany te eliminują się, ale można ich uniknąć, stosując na początku małą szybkość uczenia się, osiągając bardziej stabilny stan suboptymalny, a następnie stosując większą szybkość uczenia się. Możesz w pewnym sensie ułatwić sieć w aktualizacjach, zamiast uderzać nimi.
To jest rozgrzewka szybkości uczenia się! Zaczynając od niskiego (lub 0) tempa uczenia się i zwiększając do początkowego tempa uczenia się (od czego i tak zacząłbyś). Ten wzrost może podążać za każdą funkcją, ale zwykle jest liniowy.
Po osiągnięciu początkowego tempa można zastosować inne schematy, takie jak zanik cosinusa, redukcja liniowa itp., aby stopniowo obniżać tempo aż do końca treningu. Rozgrzewka tempa uczenia się jest zwykle częścią dwuharmonogramowego harmonogramu, gdzie rozgrzewka LR jest pierwszym, a inny harmonogram przejmuje kontrolę, gdy tempo osiągnie punkt początkowy.
W tym przewodniku będziemy wdrażać rozgrzewkę szybkości uczenia się w Keras/TensorFlow jako keras.optimizers.schedules.LearningRateSchedule
podklasa i keras.callbacks.Callback
oddzwonić. Szybkość uczenia się zostanie zwiększona z 0
do target_lr
i zastosuj rozpad kosinusowy, ponieważ jest to bardzo powszechny rozkład wtórny. Jak zwykle Keras ułatwia wdrażanie elastycznych rozwiązań na różne sposoby i dostarczanie ich wraz z siecią.
Uwaga: Implementacja jest ogólna i inspirowana Implementacja Tony'ego Keras sztuczek opisanych w „Torba sztuczek do klasyfikacji obrazów za pomocą splotowych sieci neuronowych”.
Szybkość nauki z Keras Callbacks
Najprostszym sposobem na zaimplementowanie dowolnego harmonogramu nauki jest utworzenie funkcji, która przyjmuje lr
parametr (float32
), przechodzi przez jakąś transformację i zwraca. Ta funkcja jest następnie przekazywana do LearningRateScheduler
callback, który stosuje funkcję do szybkości uczenia się.
Teraz tf.keras.callbacks.LearningRateScheduler()
przekazuje numer epoki do funkcji, której używa do obliczania szybkości uczenia się, która jest dość prosta. Rozgrzewka LR powinna odbywać się na każdym krok (partia), a nie epoka, więc będziemy musieli wyprowadzić a global_step
(we wszystkich epokach), aby zamiast tego obliczyć wskaźnik uczenia się i podklasy Callback
klasy, aby utworzyć niestandardowe wywołanie zwrotne, a nie tylko przekazywać funkcję, ponieważ będziemy musieli przekazać argumenty przy każdym wywołaniu, co jest niemożliwe przy przekazywaniu funkcji:
def func():
return ...
keras.callbacks.LearningRateScheduler(func)
Takie podejście jest korzystne, gdy nie chcesz wysokiego poziomu dostosowywania i nie chcesz ingerować w sposób, w jaki Keras traktuje lr
, a zwłaszcza jeśli chcesz korzystać z wywołań zwrotnych, takich jak ReduceLROnPlateau()
ponieważ może działać tylko z pływakiem lr
. Zaimplementujmy rozgrzewkę szybkości uczenia się za pomocą wywołania zwrotnego Keras, zaczynając od wygodnej funkcji:
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
Na każdym kroku obliczamy tempo uczenia się i tempo uczenia się rozgrzewki (oba elementy harmonogramu), z uwzględnieniem start_lr
i target_lr
. start_lr
zwykle zaczyna się o 0.0
, podczas target_lr
zależy od Twojej sieci i optymalizatora – 1e-3
może nie być dobrym ustawieniem domyślnym, więc upewnij się, że podczas wywoływania metody ustawiłeś swój docelowy LR.
Jeśli global_step
w szkoleniu jest wyższy niż warmup_steps
ustaliliśmy – korzystamy z rozkładu kosinusowego LR. Jeśli nie, to znaczy, że wciąż się rozgrzewamy, więc używana jest rozgrzewka LR. Jeśli hold
argument jest ustawiony, będziemy trzymać target_lr
dla tej liczby kroków po rozgrzewce i przed rozpadem cosinusa. np.where()
zapewnia do tego świetną składnię:
np.where(condition, value_if_true, value_if_false)
Możesz wizualizować funkcję za pomocą:
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)
Teraz będziemy chcieli użyć tej funkcji jako części wywołania zwrotnego i przekazać krok optymalizatora jako global_step
zamiast elementu arbitralnej tablicy — lub możesz wykonać obliczenia w obrębie klasy. Przejdźmy do podklasy Callback
klasa:
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)
Zapoznaj się z naszym praktycznym, praktycznym przewodnikiem dotyczącym nauki Git, zawierającym najlepsze praktyki, standardy przyjęte w branży i dołączoną ściągawkę. Zatrzymaj polecenia Google Git, a właściwie uczyć się to!
Najpierw definiujemy konstruktor klasy i śledzimy jej pola. Na każdej zakończonej partii zwiększymy globalny krok, zanotujemy aktualny LR i dodamy go do dotychczasowej listy LR. Na początku każdej partii – obliczymy LR za pomocą lr_warmup_cosine_decay()
funkcji i ustaw ten LR jako bieżący LR optymalizatora. Odbywa się to za pomocą backendu set_value()
.
Gdy to zrobisz – po prostu oblicz łączną liczbę kroków (długość/rozmiar_wsadu*epok) i weź część tej liczby dla swojego 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)
Na koniec skonstruuj swój model i podaj wywołanie zwrotne w fit()
połączenie:
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'])
Pod koniec szkolenia możesz uzyskać i zwizualizować zmienione LR poprzez:
lrs = callback.lrs
plt.plot(lrs)
Jeśli wykreślisz historię modelu wytrenowanego z rozgrzewką LR i bez niego – zobaczysz wyraźną różnicę w stabilności treningu:
Szybkość uczenia się z podklasą LearningRateSchedule
Alternatywą do tworzenia wywołania zwrotnego jest utworzenie LearningRateSchedule
podklasa, która nie manipuluje LR – zastępuje ją. Takie podejście pozwala ci nieco bardziej zagłębić się w backend Keras/TensorFlow, ale gdy jest używane, nie można go łączyć z innymi wywołaniami zwrotnymi związanymi z LR, takimi jak ReduceLROnPlateau()
, który zajmuje się LR jako liczbami zmiennoprzecinkowymi.
Dodatkowo, użycie podklasy będzie wymagało od Ciebie uczynienia jej serializowalną (przeciążenie get_config()
), ponieważ staje się częścią modelu, jeśli chcesz zapisać wagi modelu. Inną rzeczą, na którą należy zwrócić uwagę, jest to, że klasa będzie oczekiwać, że będzie pracować wyłącznie z tf.Tensor
s. Na szczęście jedyną różnicą w sposobie, w jaki pracujemy, będzie dzwonienie tf.func()
zamiast np.func()
ponieważ interfejsy API TensorFlow i NumPy są zadziwiająco podobne i kompatybilne.
Przepiszmy wygodę lr_warmup_cosine_decay()
funkcja, aby zamiast tego używać operacji TensorFlow:
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
Dzięki funkcji convinience możemy podklasy LearningRateSchedule
klasa. Na każdym __call__()
(partia), obliczymy LR za pomocą funkcji i zwrócimy ją. Oczywiście możesz również spakować obliczenia w ramach podklasy.
Składnia jest czystsza niż Callback
sublcass, przede wszystkim dlatego, że uzyskujemy dostęp do step
pola, zamiast śledzić je na własną rękę, ale także utrudnia nieco pracę z właściwościami klasy – w szczególności utrudnia wyodrębnienie lr
z tf.Tensor()
do dowolnego innego typu, aby śledzić na liście. Można to technicznie obejść, uruchamiając w trybie przyspieszonym, ale stanowi to irytujące śledzenie LR w celach debugowania i najlepiej tego unikać:
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"
)
Parametry są takie same i można je obliczyć w podobny sposób jak poprzednio:
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)
Potok uczenia różni się tylko tym, że ustawiamy LR optymalizatora na 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)
Jeśli chcesz zapisać model, WarmupCosineDecay
harmonogram będzie musiał zastąpić get_config()
metoda:
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
Wreszcie, podczas ładowania modelu, będziesz musiał zdać WarmupCosineDecay
jako obiekt niestandardowy:
model = keras.models.load_model('weights.h5',
custom_objects={'WarmupCosineDecay', WarmupCosineDecay})
Wnioski
W tym przewodniku przyjrzeliśmy się intuicji stojącej za rozgrzewaniem tempa uczenia się — powszechną techniką manipulowania tempem uczenia się podczas uczenia sieci neuronowych.
Wdrożyliśmy rozgrzewkę szybkości uczenia się z rozpadem cosinusowym, najczęstszym rodzajem redukcji LR w połączeniu z rozgrzewką. Możesz zaimplementować dowolną inną funkcję w celu redukcji lub wcale nie zmniejszać tempa uczenia się – pozostawiając to innym wywołaniom zwrotnym, takim jak ReduceLROnPlateau()
. Wdrożyliśmy rozgrzewkę szybkości uczenia się jako wywołanie zwrotne Keras, a także harmonogram Keras Optimizer i wykreśliliśmy szybkość uczenia się w poszczególnych epokach.