Rozgrzewka szybkości uczenia się z rozpadem kosinusowym w Keras/TensorFlow PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.

Rozgrzewka szybkości uczenia się z rozkładem kosinusowym w Keras/TensorFlow

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)

Rozgrzewka szybkości uczenia się z rozpadem kosinusowym w Keras/TensorFlow PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.

Jeśli wykreślisz historię modelu wytrenowanego z rozgrzewką LR i bez niego – zobaczysz wyraźną różnicę w stabilności treningu:

Rozgrzewka szybkości uczenia się z rozpadem kosinusowym w Keras/TensorFlow PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.

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.Tensors. 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.

Znak czasu:

Więcej z Nadużycie stosu