Læringshastigheden er en vigtig hyperparameter i dybe læringsnetværk - og den dikterer direkte grad hvortil der udføres opdateringer af vægte, som skønnes at minimere en given tabsfunktion. I SGD:
$$
vægt_{t+1} = vægt_t – lr * frac{fejl}{dvægt_t}
$$
Med en indlæringshastighed på 0
, den opdaterede vægt er bare tilbage til sig selv – vægtt. Indlæringshastigheden er faktisk en knap, vi kan dreje for at aktivere eller deaktivere læring, og den har stor indflydelse på, hvor meget læring der sker, ved direkte at styre graden af vægtopdateringer.
Forskellige optimeringsværktøjer udnytter læringshastigheder forskelligt - men det underliggende koncept forbliver det samme. Det er overflødigt at sige, at læringshastigheder har været genstand for mange undersøgelser, artikler og praktiserende benchmarks.
Generelt er stort set alle enige om, at en statisk indlæringshastighed ikke vil reducere den, og en eller anden form for reduktion af indlæringshastigheden sker i de fleste teknikker, der justerer indlæringshastigheden under træning - uanset om det er en monotonisk, cosinus, trekantet eller andre typer af reduktion.
En teknik der i de senere år har vundet indpas er opvarmning af indlæringshastighed, som kan parres med praktisk talt enhver anden reduktionsteknik.
Læringshastighedsopvarmning
Ideen bag opvarmning af læringshastighed er enkel. I de tidligste stadier af træning - vægte er langt fra deres ideelle tilstande. Dette betyder store opdateringer over hele linjen, hvilket kan ses som "overkorrektioner" for hver vægt - hvor den drastiske opdatering af en anden kan ophæve opdateringen af en anden vægt, hvilket gør de indledende stadier af træningen mere ustabile.
Disse ændringer udlignes, men kan undgås ved at have en lille indlæringshastighed til at begynde med, nå en mere stabil suboptimal tilstand og derefter anvende en større indlæringshastighed. Du kan på en måde lette netværket til opdateringer i stedet for at ramme det med dem.
Det er opvarmning af læringshastighed! Starter med en lav (eller 0) indlæringshastighed og stiger til en begyndende indlæringsrate (hvad du alligevel ville starte med). Denne stigning kan følge enhver funktion, men er almindeligvis lineær.
Efter at have nået den indledende hastighed, kan andre skemaer såsom cosinus-henfald, lineær reduktion osv. anvendes for gradvist at sænke hastigheden indtil slutningen af træningen. Læringshastighedsopvarmning er normalt en del af et skema med to skemaer, hvor LR-opvarmning er den første, mens et andet skema tager over, efter at satsen har nået et udgangspunkt.
I denne guide implementerer vi en opvarmning af læringshastigheden i Keras/TensorFlow som en keras.optimizers.schedules.LearningRateSchedule
underklasse og keras.callbacks.Callback
ring tilbage. Læringshastigheden øges fra 0
til target_lr
og anvende cosinus-henfald, da dette er et meget almindeligt sekundært skema. Som sædvanlig gør Keras det enkelt at implementere fleksible løsninger på forskellige måder og sende dem med dit netværk.
Bemærk: Implementeringen er generisk og inspireret af Tony's Keras implementering af de tricks, der er beskrevet i "Pose med tricks til billedklassificering med konvolutionelle neurale netværk”.
Læringshastighed med Keras-tilbagekald
Den enkleste måde at implementere enhver læringshastighedsplan er ved at oprette en funktion, der tager lr
parameter (float32
), sender den gennem en transformation og returnerer den. Denne funktion videregives derefter til LearningRateScheduler
callback, som anvender funktionen på indlæringshastigheden.
Nu tf.keras.callbacks.LearningRateScheduler()
overfører epoketallet til den funktion, det bruger til at beregne indlæringshastigheden, hvilket er ret groft. LR Warmup bør udføres på hver trin (batch), ikke epoke, så vi bliver nødt til at udlede en global_step
(på tværs af alle epoker) for at beregne indlæringshastigheden i stedet, og underklasser Callback
klasse for at oprette et tilpasset tilbagekald i stedet for blot at videregive funktionen, da vi bliver nødt til at sende argumenter ind på hvert opkald, hvilket er umuligt, når du bare sender funktionen:
def func():
return ...
keras.callbacks.LearningRateScheduler(func)
Denne tilgang er gunstig, når du ikke ønsker en høj grad af tilpasning, og du ikke ønsker at forstyrre den måde, Keras behandler lr
, og især hvis du vil bruge tilbagekald som ReduceLROnPlateau()
da det kun kan fungere med en float-baseret lr
. Lad os implementere en læringshastighedsopvarmning ved hjælp af et Keras-tilbagekald, startende med en bekvemmelighedsfunktion:
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
På hvert trin beregner vi indlæringshastigheden og opvarmningsindlæringshastigheden (begge elementer af skemaet) med hensyn til start_lr
, target_lr
. start_lr
vil normalt starte kl 0.0
, Mens target_lr
afhænger af dit netværk og optimizer – 1e-3
er muligvis ikke en god standard, så sørg for at indstille dit mål, der starter LR, når du kalder metoden.
Hvis global_step
i uddannelsen er højere end warmup_steps
vi har indstillet – vi bruger cosinus-decay-skemaet LR. Hvis ikke, betyder det, at vi stadig varmer op, så opvarmningen LR bruges. Hvis hold
argumentet er indstillet, holder vi target_lr
for det antal skridt efter opvarmning og før cosinus-henfaldet. np.where()
giver en fantastisk syntaks til dette:
np.where(condition, value_if_true, value_if_false)
Du kan visualisere 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 vil vi gerne bruge denne funktion som en del af et tilbagekald og videregive optimeringstrinnet som global_step
i stedet for et element i et vilkårligt array - eller du kan udføre beregningen inden for klassen. Lad os subclss 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)
Tjek vores praktiske, praktiske guide til at lære Git, med bedste praksis, brancheaccepterede standarder og inkluderet snydeark. Stop med at google Git-kommandoer og faktisk lærer det!
Først definerer vi konstruktøren for klassen og holder styr på dens felter. På hver batch, der er afsluttet, øger vi det globale trin, noterer os den nuværende LR og tilføjer den til listen over LR'er indtil videre. Ved hver batchs begyndelse - beregner vi LR ved hjælp af lr_warmup_cosine_decay()
funktion og indstil den LR som optimizerens aktuelle LR. Dette gøres med backend's set_value()
.
Når det er gjort – beregn bare de samlede trin (længde/batch_size*epoker) og tag en del af dette tal for 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)
Til sidst skal du konstruere din model og give tilbagekaldet i fit()
opkald:
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'])
Ved afslutningen af træningen kan du få og visualisere de ændrede LR'er via:
lrs = callback.lrs
plt.plot(lrs)
Hvis du plotter historien om en model trænet med og uden LR-opvarmning – vil du se en tydelig forskel i træningens stabilitet:
Learning Rate med LearningRateSchedule underklasse
Et alternativ til at oprette et tilbagekald er at oprette en LearningRateSchedule
underklasse, som ikke manipulerer LR – den erstatter den. Denne tilgang giver dig mulighed for at proppe lidt mere ind i backend af Keras/TensorFlow, men når den bruges, kan den ikke kombineres med andre LR-relaterede tilbagekald, som f.eks. ReduceLROnPlateau()
, som omhandler LR'er som flydende kommatal.
Derudover vil brug af underklassen kræve, at du gør den serialiserbar (overload get_config()
), da det bliver en del af modellen, hvis du vil gemme modelvægtene. En anden ting at bemærke er, at klassen vil forvente udelukkende at arbejde med tf.Tensor
s. Heldigvis vil den eneste forskel i den måde, vi arbejder på, være at ringe tf.func()
i stedet for np.func()
da TensorFlow og NumPy API'erne er utroligt ens og kompatible.
Lad os omskrive bekvemmeligheden lr_warmup_cosine_decay()
funktion for at bruge TensorFlow-operationer i stedet:
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 underklassificere LearningRateSchedule
klasse. På hver __call__()
(batch), beregner vi LR ved hjælp af funktionen og returnerer den. Du kan naturligvis også pakke beregningen inden for den underklassede klasse.
Syntaksen er renere end Callback
sublcass, primært fordi vi får adgang til step
felt, snarere end at holde styr på det på egen hånd, men gør det også noget sværere at arbejde med klasseejendomme – især gør det det svært at udvinde lr
fra en tf.Tensor()
til enhver anden type for at holde styr på en liste. Dette kan teknisk omgås ved at køre i ivrig tilstand, men giver en irritation for at holde styr på LR til fejlfindingsformål og undgås bedst:
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"
)
Parametrene er de samme og kan beregnes på nogenlunde samme måde som før:
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)
Og træningspipelinen adskiller sig kun ved, at vi indstiller optimizerens LR til 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)
Hvis du ønsker at gemme modellen, WarmupCosineDecay
tidsplanen skal tilsidesætte get_config()
metode:
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
Til sidst, når du indlæser modellen, skal du bestå en WarmupCosineDecay
som et brugerdefineret objekt:
model = keras.models.load_model('weights.h5',
custom_objects={'WarmupCosineDecay', WarmupCosineDecay})
Konklusion
I denne guide har vi taget et kig på intuitionen bag Learning Rate Warmup – en almindelig teknik til at manipulere indlæringshastigheden, mens du træner neurale netværk.
Vi har implementeret en læringshastighedsopvarmning med cosinus-decay, den mest almindelige type LR-reduktion parret med opvarmning. Du kan implementere en hvilken som helst anden funktion til reduktion eller slet ikke reducere indlæringshastigheden – overlade det til andre tilbagekald som f.eks. ReduceLROnPlateau()
. Vi har implementeret opvarmning af indlæringshastighed som et Keras-tilbagekald samt et Keras Optimizer-skema og plottet indlæringshastigheden gennem epokerne.