Inlärningshastighetsuppvärmning med cosinusförfall i Keras/TensorFlow PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

Learning Rate Warmup med Cosinus Decay i Keras/TensorFlow

Inlärningshastigheten är en viktig hyperparameter i nätverk för djupinlärning – och den dikterar direkt grad till vilka uppdateringar av vikter utförs, vilka beräknas minimera en viss förlustfunktion. I SGD:

$$
vikt_{t+1} = vikt_t – lr * frac{fel}{dweight_t}
$$

Med en inlärningshastighet på 0, den uppdaterade vikten är bara tillbaka till sig själv – viktt. Inlärningshastigheten är faktiskt en ratt vi kan vrida på för att aktivera eller inaktivera inlärning, och den har stort inflytande över hur mycket inlärning som sker, genom att direkt styra graden av viktuppdateringar.

Olika optimerare använder inlärningshastigheter olika – men det underliggande konceptet förblir detsamma. Naturligtvis har inlärningshastigheter varit föremål för många studier, artiklar och praktiserande riktmärken.

Generellt sett är i stort sett alla överens om att en statisk inlärningshastighet inte kommer att minska den, och någon typ av minskning av inlärningshastigheten sker i de flesta tekniker som ställer in inlärningshastigheten under träning – oavsett om detta är en monoton, cosinus, triangulär eller andra typer av minskning.

En teknik som de senaste åren har fått fäste är uppvärmning av inlärningshastigheten, som kan paras ihop med praktiskt taget vilken annan reduktionsteknik som helst.

Uppvärmning av inlärningshastighet

Tanken bakom uppvärmning av inlärningshastigheten är enkel. I de tidigaste stadierna av träning – vikter är långt ifrån deras idealtillstånd. Detta innebär stora uppdateringar över hela linjen, vilket kan ses som "överkorrigeringar" för varje vikt – där drastiska uppdateringar av en annan kan förneka uppdateringen av någon annan vikt, vilket gör de inledande stadierna av träningen mer instabila.

Dessa förändringar stryks ut, men kan undvikas genom att ha en liten inlärningshastighet till att börja med, nå ett mer stabilt suboptimalt tillstånd och sedan tillämpa en högre inlärningshastighet. Du kan liksom förenkla nätverket till uppdateringar, snarare än att slå det med dem.

Det är uppvärmning av inlärningshastigheten! Börja med en låg (eller 0) inlärningshastighet och öka till en inlärningshastighet (vad du skulle börja med i alla fall). Denna ökning kan följa vilken funktion som helst, men är vanligtvis linjär.

Efter att ha uppnått den initiala frekvensen kan andra scheman såsom cosinusavklingning, linjär minskning, etc. tillämpas för att gradvis sänka frekvensen ner till slutet av träningen. Inlärningstaktsuppvärmning är vanligtvis en del av ett två-schemat schema, där LR-uppvärmning är den första, medan ett annat schema tar vid efter att takten har nått en startpunkt.

I den här guiden kommer vi att implementera en uppvärmning av inlärningshastigheten i Keras/TensorFlow som en keras.optimizers.schedules.LearningRateSchedule underklass och keras.callbacks.Callback ring tillbaka. Inlärningstakten höjs fr.o.m 0 till target_lr och tillämpa cosinussönderfall, eftersom detta är ett mycket vanligt sekundärt schema. Som vanligt gör Keras det enkelt att implementera flexibla lösningar på olika sätt och skicka dem med ditt nätverk.

Notera: Implementeringen är generisk och inspirerad av Tonys Keras implementering av de knep som beskrivs i "Påse med tricks för bildklassificering med konvolutionella neurala nätverk”.

Inlärningshastighet med Keras-återuppringningar

Det enklaste sättet att implementera alla inlärningshastighetsscheman är genom att skapa en funktion som tar lr parameter (float32), passerar den genom någon transformation och returnerar den. Denna funktion skickas sedan vidare till LearningRateScheduler callback, som tillämpar funktionen på inlärningshastigheten.

Nu, den tf.keras.callbacks.LearningRateScheduler() skickar epoknumret till funktionen den använder för att beräkna inlärningshastigheten, vilket är ganska grovt. LR Warmup bör göras på varje steg (batch), inte epok, så vi måste härleda en global_step (över alla epoker) för att istället beräkna inlärningshastigheten, och underklass Callback klass för att skapa en anpassad återuppringning istället för att bara skicka funktionen, eftersom vi måste skicka in argument för varje anrop, vilket är omöjligt när man bara skickar funktionen:

def func():
    return ...
    
keras.callbacks.LearningRateScheduler(func)

Detta tillvägagångssätt är fördelaktigt när du inte vill ha en hög nivå av anpassning och du inte vill störa hur Keras behandlar lr, och speciellt om du vill använda återuppringningar som ReduceLROnPlateau() eftersom det bara kan fungera med en float-baserad lr. Låt oss implementera en uppvärmning av inlärningshastigheten med en Keras-återuppringning, som börjar med en bekvämlighetsfunktion:

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

För varje steg beräknar vi inlärningshastigheten och uppvärmningshastigheten (båda delarna av schemat), med avseende på start_lr och target_lr. start_lr brukar börja kl 0.0, medan target_lr beror på ditt nätverk och optimerare – 1e-3 kanske inte är en bra standard, så se till att du ställer in ditt mål som startar LR när du anropar metoden.

Om global_step i utbildningen är högre än warmup_steps vi har ställt in – vi använder cosinusförfallsschemat LR. Om inte betyder det att vi fortfarande värmer upp, så uppvärmningen LR används. Om hold argumentet är inställt, kommer vi att hålla target_lr för det antalet steg efter uppvärmning och före cosinusförfallet. np.where() ger en bra syntax för detta:

np.where(condition, value_if_true, value_if_false)

Du kan visualisera funktionen med:

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)

Nu vill vi använda den här funktionen som en del av en återuppringning och skicka optimeringssteget som global_step snarare än ett element i en godtycklig array – eller så kan du utföra beräkningen inom klassen. Låt oss subclss Callback klass:

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)

Kolla in vår praktiska, praktiska guide för att lära dig Git, med bästa praxis, branschaccepterade standarder och medföljande fuskblad. Sluta googla Git-kommandon och faktiskt lära Det!

Först definierar vi konstruktorn för klassen och håller reda på dess fält. För varje batch som är avslutad kommer vi att öka det globala steget, ta del av den nuvarande LR och lägga till den i listan över LRs hittills. I början av varje batch – vi beräknar LR med hjälp av lr_warmup_cosine_decay() funktion och ställ in den LR som optimerarens nuvarande LR. Detta görs med backend's set_value().

När det är gjort – beräkna bara det totala antalet steg (längd/batch_size*epoker) och ta en del av det numret för din 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)

Slutligen, konstruera din modell och ge återuppringningen i fit() ringa upp:

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'])

I slutet av utbildningen kan du erhålla och visualisera de ändrade LR:erna via:

lrs = callback.lrs 
plt.plot(lrs)

Inlärningshastighetsuppvärmning med cosinusförfall i Keras/TensorFlow PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

Om du plottar historien för en modell tränad med och utan LR-uppvärmning – kommer du att se en tydlig skillnad i träningens stabilitet:

Inlärningshastighetsuppvärmning med cosinusförfall i Keras/TensorFlow PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

Learning Rate med LearningRateSchedule Subclass

Ett alternativ till att skapa en återuppringning är att skapa en LearningRateSchedule subklass, som inte manipulerar LR – den ersätter den. Detta tillvägagångssätt gör att du kan prodda lite mer i backend av Keras/TensorFlow, men när den används kan den inte kombineras med andra LR-relaterade återuppringningar, som t.ex. ReduceLROnPlateau(), som behandlar LR som flyttal.

Dessutom kommer användning av underklassen att kräva att du gör den serialiserbar (överbelastning get_config()) eftersom det blir en del av modellen, om du vill spara modellvikterna. En annan sak att notera är att klassen förväntar sig att arbeta uteslutande med tf.Tensors. Tack och lov är den enda skillnaden i hur vi arbetar att ringa tf.func() istället för np.func() eftersom API:erna TensorFlow och NumPy är otroligt lika och kompatibla.

Låt oss skriva om bekvämligheten lr_warmup_cosine_decay() funktion för att använda TensorFlow-operationer istället:

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

Med convinience-funktionen kan vi underklassa LearningRateSchedule klass. På varje __call__() (batch), beräknar vi LR med funktionen och returnerar den. Du kan naturligtvis också paketera beräkningen inom den underklassade klassen.

Syntaxen är renare än Callback sublcass, främst för att vi får tillgång till step fältet, snarare än att hålla reda på det på egen hand, men gör det också något svårare att arbeta med klassegenskaper – särskilt gör det det svårt att extrahera lr från en tf.Tensor() till någon annan typ för att hålla reda på i en lista. Detta kan tekniskt kringgås genom att köra i ivrigt läge, men utgör ett irritationsmoment för att hålla reda på LR för felsökningsändamål och är bäst att undvika:

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"
        )

Parametrarna är desamma och kan beräknas på ungefär samma sätt som tidigare:


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)

Och träningspipelinen skiljer sig bara genom att vi ställer in optimerarens LR till 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)

Om du vill spara modellen, WarmupCosineDecay schemat måste åsidosätta get_config() metod:

    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

Slutligen, när du laddar modellen måste du passera en WarmupCosineDecay som ett anpassat objekt:

model = keras.models.load_model('weights.h5', 
                                custom_objects={'WarmupCosineDecay', WarmupCosineDecay})

Slutsats

I den här guiden har vi tagit en titt på intuitionen bakom Learning Rate Warmup – en vanlig teknik för att manipulera inlärningshastigheten medan du tränar neurala nätverk.

Vi har implementerat en inlärningshastighetsuppvärmning med cosinusförfall, den vanligaste typen av LR-reduktion i kombination med uppvärmning. Du kan implementera vilken annan funktion som helst för att minska, eller inte minska inlärningshastigheten alls – överlåta den till andra återuppringningar som t.ex. ReduceLROnPlateau(). Vi har implementerat uppvärmning av inlärningshastigheten som en Keras Callback, såväl som ett Keras Optimizer-schema och plottat inlärningshastigheten genom epokerna.

Tidsstämpel:

Mer från Stackabuse