.. _sec_scheduler:
Öğrenme Oranını Zamanlama
=========================
Şimdiye kadar öncelikle ağırlık vektörlerinin güncellendikleri *oran*
yerine ağırlık vektörlerinin nasıl güncelleneceğine dair optimizasyon
*algoritmalarına* odaklandık. Bununla birlikte, öğrenme oranının
ayarlanması genellikle gerçek algoritma kadar önemlidir. Göz önünde
bulundurulması gereken bir dizi husus vardır:
- En çok açıkçası öğrenme oranının *büyüklüğü* önemlidir. Çok büyükse,
optimizasyon ıraksar, eğer çok küçükse, eğitilmesi çok uzun sürer
veya yarı-optimal bir sonuç elde ederiz. Daha önce problemin
sağlamlık sayısının önemli olduğunu gördük (ayrıntılar için bkz.
:numref:`sec_momentum`). Sezgisel olarak, en hassas yöndeki değişim
miktarının en az hassas olanına oranıdır.
- İkincisi, sönme oranı da aynı derecede önemlidir. Öğrenme oranı büyük
kalırsa, en küçük seviyeyi atlayabilir ve böylece eniyiye
ulaşamayabiliriz. :numref:`sec_minibatch_sgd` içinde bunu ayrıntılı
olarak tartıştık ve :numref:`sec_sgd` içinde performans
garantilerini analiz ettik. Kısacası, sönme oranını istiyoruz, ama
muhtemelen dışbükey problemler için
:math:`\mathcal{O}(t^{-\frac{1}{2}})` daha yavaş olanlar iyi bir
seçim olurdu.
- Eşit derecede önemli olan bir diğer yön ise *ilklemedir*. Bu, hem
parametrelerin başlangıçta nasıl ayarlandığı (ayrıntılar için
bkz.:numref:\ ``sec_numerical_stability``) hem de başlangıçta nasıl
evrimleştikleri de ilgilidir. Bu, *ısınma* diye tabir edilir, yani
başlangıçta çözüme doğru ne kadar hızlı ilerlemeye başladığımızla
ilgilidir. Başlangıçta büyük adımlar yararlı olmayabilir, özellikle
de ilk parametre kümesi rastgele olduğundan. İlk güncelleme
yönergeleri de oldukça anlamsız olabilir.
- Son olarak, döngüsel öğrenme hızı ayarlaması gerçekleştiren bir dizi
optimizasyon türü vardır. Bu, bu bölümün kapsamı dışındadır.
Okuyucuya :cite:`Izmailov.Podoprikhin.Garipov.ea.2018`
çalışmasındaki ayrıntıları gözden geçirmesini tavsiye ederiz,
örneğin, parametrelerin izlediği *yolun* üzerinden ortalama alarak
daha iyi çözümler elde etmek gibi.
Öğrenme oranlarını yönetmek için gereken çok fazla ayrıntı olduğu göz
önüne alındığında, çoğu derin öğrenme çerçevesinin bununla otomatik
olarak başa çıkabilmesi için araçlar vardır. Bu bölümde, farklı
zamanlamaların doğruluk üzerindeki etkilerini gözden geçireceğiz ve
ayrıca bunun bir *öğrenme oranı zamanlayıcısı* aracılığıyla nasıl
verimli bir şekilde yönetilebileceğini göstereceğiz.
Basit Örnek Problem
-------------------
Kolayca hesaplanabilecek kadar ucuz, ancak bazı önemli hususları
göstermek için yeterince açık olmayan bir basit örnek problem ile
başlıyoruz. Bunun için Fashion-MNIST'e uygulandığı gibi LeNet'in biraz
modern bir sürümünü seçin (``relu`` yerine ``sigmoid`` aktivasyon,
MaxPooling (Maksimum Havuzlama) yerine AveragePooling (Ortalama
Havuzlama)). Dahası, ağı performans için melezleştiriyoruz. Kodun çoğu
standart olduğundan, daha fazla ayrıntılı tartışma yapmadan sadece temel
bilgileri sunuyoruz. Gerektiğinde anımsamak için bkz.
:numref:`chap_cnn`.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%matplotlib inline
from d2l import mxnet as d2l
from mxnet import autograd, gluon, init, lr_scheduler, np, npx
from mxnet.gluon import nn
npx.set_np()
net = nn.HybridSequential()
net.add(nn.Conv2D(channels=6, kernel_size=5, padding=2, activation='relu'),
nn.MaxPool2D(pool_size=2, strides=2),
nn.Conv2D(channels=16, kernel_size=5, activation='relu'),
nn.MaxPool2D(pool_size=2, strides=2),
nn.Dense(120, activation='relu'),
nn.Dense(84, activation='relu'),
nn.Dense(10))
net.hybridize()
loss = gluon.loss.SoftmaxCrossEntropyLoss()
device = d2l.try_gpu()
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
# Kod, evrişimli sinir ağları bölümünün lenet kısmında tanımlanan
# `d2l.train_ch6` ile neredeyse aynıdır.
def train(net, train_iter, test_iter, num_epochs, loss, trainer, device):
net.initialize(force_reinit=True, ctx=device, init=init.Xavier())
animator = d2l.Animator(xlabel='epoch', xlim=[0, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
metric = d2l.Accumulator(3) # train_loss, train_acc, num_examples
for i, (X, y) in enumerate(train_iter):
X, y = X.as_in_ctx(device), y.as_in_ctx(device)
with autograd.record():
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
trainer.step(X.shape[0])
metric.add(l.sum(), d2l.accuracy(y_hat, y), X.shape[0])
train_loss = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
if (i + 1) % 50 == 0:
animator.add(epoch + i / len(train_iter),
(train_loss, train_acc, None))
test_acc = d2l.evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
print(f'train loss {train_loss:.3f}, train acc {train_acc:.3f}, '
f'test acc {test_acc:.3f}')
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%matplotlib inline
import math
import torch
from torch import nn
from torch.optim import lr_scheduler
from d2l import torch as d2l
def net_fn():
model = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), nn.ReLU(),
nn.Linear(120, 84), nn.ReLU(),
nn.Linear(84, 10))
return model
loss = nn.CrossEntropyLoss()
device = d2l.try_gpu()
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
# Kod, evrişimli sinir ağları bölümünün lenet kısmında tanımlanan
# `d2l.train_ch6` ile neredeyse aynıdır.
def train(net, train_iter, test_iter, num_epochs, loss, trainer, device,
scheduler=None):
net.to(device)
animator = d2l.Animator(xlabel='epoch', xlim=[0, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
metric = d2l.Accumulator(3) # train_loss, train_acc, num_examples
for i, (X, y) in enumerate(train_iter):
net.train()
trainer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
trainer.step()
with torch.no_grad():
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
train_loss = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
if (i + 1) % 50 == 0:
animator.add(epoch + i / len(train_iter),
(train_loss, train_acc, None))
test_acc = d2l.evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch+1, (None, None, test_acc))
if scheduler:
if scheduler.__module__ == lr_scheduler.__name__:
# Using PyTorch In-Built scheduler
scheduler.step()
else:
# Using custom defined scheduler
for param_group in trainer.param_groups:
param_group['lr'] = scheduler(epoch)
print(f'train loss {train_loss:.3f}, train acc {train_acc:.3f}, '
f'test acc {test_acc:.3f}')
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%matplotlib inline
import math
import tensorflow as tf
from d2l import tensorflow as d2l
from tensorflow.keras.callbacks import LearningRateScheduler
def net():
return tf.keras.models.Sequential([
tf.keras.layers.Conv2D(filters=6, kernel_size=5, activation='relu',
padding='same'),
tf.keras.layers.AvgPool2D(pool_size=2, strides=2),
tf.keras.layers.Conv2D(filters=16, kernel_size=5,
activation='relu'),
tf.keras.layers.AvgPool2D(pool_size=2, strides=2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(120, activation='relu'),
tf.keras.layers.Dense(84, activation='sigmoid'),
tf.keras.layers.Dense(10)])
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
# Kod, evrişimli sinir ağları bölümünün lenet kısmında tanımlanan
# `d2l.train_ch6` ile neredeyse aynıdır.
def train(net_fn, train_iter, test_iter, num_epochs, lr,
device=d2l.try_gpu(), custom_callback = False):
device_name = device._device_name
strategy = tf.distribute.OneDeviceStrategy(device_name)
with strategy.scope():
optimizer = tf.keras.optimizers.SGD(learning_rate=lr)
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
net = net_fn()
net.compile(optimizer=optimizer, loss=loss, metrics=['accuracy'])
callback = d2l.TrainCallback(net, train_iter, test_iter, num_epochs,
device_name)
if custom_callback is False:
net.fit(train_iter, epochs=num_epochs, verbose=0,
callbacks=[callback])
else:
net.fit(train_iter, epochs=num_epochs, verbose=0,
callbacks=[callback, custom_callback])
return net
.. raw:: html
.. raw:: html
Bu algoritmayı :math:`0.3` öğrenme oranı ve :math:`30` yinelemeyle
eğitmek için varsayılan ayarlarla çalıştırırsak ne olacağına bir göz
atalım. Test doğruluğu açısından ilerleme bir noktanın ötesinde dururken
eğitim doğruluğunun nasıl artmaya devam ettiğine dikkat edin. Her iki
eğri arasındaki boşluk aşırı öğrenmeyi işaret eder.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
lr, num_epochs = 0.3, 30
net.initialize(force_reinit=True, ctx=device, init=init.Xavier())
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': lr})
train(net, train_iter, test_iter, num_epochs, loss, trainer, device)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
train loss 0.171, train acc 0.935, test acc 0.874
.. figure:: output_lr-scheduler_1dfeb6_15_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
lr, num_epochs = 0.3, 30
net = net_fn()
trainer = torch.optim.SGD(net.parameters(), lr=lr)
train(net, train_iter, test_iter, num_epochs, loss, trainer, device)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
train loss 0.181, train acc 0.930, test acc 0.892
.. figure:: output_lr-scheduler_1dfeb6_18_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
lr, num_epochs = 0.3, 30
train(net, train_iter, test_iter, num_epochs, lr)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss 0.223, train acc 0.917, test acc 0.886
61325.8 examples/sec on /GPU:0
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. figure:: output_lr-scheduler_1dfeb6_21_2.svg
.. raw:: html
.. raw:: html
Zamanlayıcılar
--------------
Öğrenme oranını ayarlamanın bir yolu, her adımda açıkça ayarlamaktır.
Bu, ``set_learning_rate`` yöntemi ile elverişli bir şekilde elde edilir.
Her dönemden sonra (hatta her minigrup işleminden sonra), örneğin,
optimizasyonun nasıl ilerlediğine yanıt olarak dinamik bir şekilde,
aşağı doğru ayarlayabiliriz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
trainer.set_learning_rate(0.1)
print(f'learning rate is now {trainer.learning_rate:.2f}')
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
learning rate is now 0.10
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
lr = 0.1
trainer.param_groups[0]["lr"] = lr
print(f'learning rate is now {trainer.param_groups[0]["lr"]:.2f}')
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
learning rate is now 0.10
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
lr = 0.1
dummy_model = tf.keras.models.Sequential([tf.keras.layers.Dense(10)])
dummy_model.compile(tf.keras.optimizers.SGD(learning_rate=lr), loss='mse')
print(f'learning rate is now ,', dummy_model.optimizer.lr.numpy())
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
learning rate is now , 0.1
.. raw:: html
.. raw:: html
Daha genel olarak bir zamanlayıcı tanımlamak istiyoruz. Güncelleme
sayısı ile çağrıldığında, öğrenme oranının uygun değerini döndürür.
Öğrenme oranını :math:`\eta = \eta_0 (t + 1)^{-\frac{1}{2}}`'ye
ayarlayan basit bir tane tanımlayalım.
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class SquareRootScheduler:
def __init__(self, lr=0.1):
self.lr = lr
def __call__(self, num_update):
return self.lr * pow(num_update + 1.0, -0.5)
Davranışını bir dizi değer üzerinde çizelim.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
scheduler = SquareRootScheduler(lr=0.1)
d2l.plot(np.arange(num_epochs), [scheduler(t) for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_41_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
scheduler = SquareRootScheduler(lr=0.1)
d2l.plot(torch.arange(num_epochs), [scheduler(t) for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_44_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
scheduler = SquareRootScheduler(lr=0.1)
d2l.plot(tf.range(num_epochs), [scheduler(t) for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_47_0.svg
.. raw:: html
.. raw:: html
Şimdi bunun Fashion-MNIST eğitimi için nasıl bittiğini görelim.
Zamanlayıcıyı eğitim algoritmasına ek bir argüman olarak sağlıyoruz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
trainer = gluon.Trainer(net.collect_params(), 'sgd',
{'lr_scheduler': scheduler})
train(net, train_iter, test_iter, num_epochs, loss, trainer, device)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
train loss 0.522, train acc 0.812, test acc 0.815
.. figure:: output_lr-scheduler_1dfeb6_53_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = net_fn()
trainer = torch.optim.SGD(net.parameters(), lr)
train(net, train_iter, test_iter, num_epochs, loss, trainer, device,
scheduler)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
train loss 0.283, train acc 0.897, test acc 0.873
.. figure:: output_lr-scheduler_1dfeb6_56_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
train(net, train_iter, test_iter, num_epochs, lr,
custom_callback=LearningRateScheduler(scheduler))
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss 0.379, train acc 0.863, test acc 0.843
61240.3 examples/sec on /GPU:0
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. figure:: output_lr-scheduler_1dfeb6_59_2.svg
.. raw:: html
.. raw:: html
Bu eskisinden biraz daha iyi çalıştı. İki şey öne çıkıyor: Eğri daha
öncekinden daha pürüzsüzdü. İkincisi, daha az aşırı öğrenme vardı. Ne
yazık ki, belirli stratejilerin neden *teoride* daha az aşırı öğrenmeye
yol açtığı tam olarak çözülmüş bir soru değil. Daha küçük bir adım
boyutunun sıfıra yakın ve dolayısıyla daha basit olan parametrelere yol
açacağına dair argümanlar vardır. Bununla birlikte, bu fenomeni tam
olarak açıklamaz, çünkü gerçekten erken durmayız, sadece öğrenme oranını
nazikçe azaltırız.
Politikalar
-----------
Tüm öğrenme oranı zamanlayıcılarını kapsayamasak da, aşağıda popüler
politikalara kısa bir genel bakış vermeye çalışıyoruz. Yaygın seçenekler
polinom sönme ve parçalı sabit zamanlamalardır. Bunun ötesinde, kosinüs
öğrenme oranı zamanlamalarının bazı problemler üzerinde deneysel olarak
iyi çalıştığı tespit edilmiştir. Son olarak, bazı problemlerde, büyük
öğrenme oranları kullanmadan önce optimize ediciyi ısıtmak faydalıdır.
Çarpan Zamanlayıcı
~~~~~~~~~~~~~~~~~~
Bir polinom sönmesine bir alternatif,
:math:`\eta_{t+1} \leftarrow \eta_t \cdot \alpha` için
:math:`\alpha \in (0, 1)` olan çarpımsal bir çözüm olabilir. Öğrenme
oranının makul bir alt sınırın ötesinde sönmesini önlemek için
güncelleme denklemi genellikle
:math:`\eta_{t+1} \leftarrow \mathop{\mathrm{max}}(\eta_{\mathrm{min}}, \eta_t \cdot \alpha)`
olarak değiştirilir.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class FactorScheduler:
def __init__(self, factor=1, stop_factor_lr=1e-7, base_lr=0.1):
self.factor = factor
self.stop_factor_lr = stop_factor_lr
self.base_lr = base_lr
def __call__(self, num_update):
self.base_lr = max(self.stop_factor_lr, self.base_lr * self.factor)
return self.base_lr
scheduler = FactorScheduler(factor=0.9, stop_factor_lr=1e-2, base_lr=2.0)
d2l.plot(np.arange(50), [scheduler(t) for t in range(50)])
.. figure:: output_lr-scheduler_1dfeb6_65_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class FactorScheduler:
def __init__(self, factor=1, stop_factor_lr=1e-7, base_lr=0.1):
self.factor = factor
self.stop_factor_lr = stop_factor_lr
self.base_lr = base_lr
def __call__(self, num_update):
self.base_lr = max(self.stop_factor_lr, self.base_lr * self.factor)
return self.base_lr
scheduler = FactorScheduler(factor=0.9, stop_factor_lr=1e-2, base_lr=2.0)
d2l.plot(torch.arange(50), [scheduler(t) for t in range(50)])
.. figure:: output_lr-scheduler_1dfeb6_68_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class FactorScheduler:
def __init__(self, factor=1, stop_factor_lr=1e-7, base_lr=0.1):
self.factor = factor
self.stop_factor_lr = stop_factor_lr
self.base_lr = base_lr
def __call__(self, num_update):
self.base_lr = max(self.stop_factor_lr, self.base_lr * self.factor)
return self.base_lr
scheduler = FactorScheduler(factor=0.9, stop_factor_lr=1e-2, base_lr=2.0)
d2l.plot(tf.range(50), [scheduler(t) for t in range(50)])
.. figure:: output_lr-scheduler_1dfeb6_71_0.svg
.. raw:: html
.. raw:: html
Bu, ``lr_scheduler.FactorScheduler`` nesnesi aracılığıyla MXNet'te
yerleşik bir zamanlayıcı tarafından da gerçekleştirilebilir. Isınma
süresi, ısınma modu (doğrusal veya sabit), istenen güncelleme sayısı,
vb. gibi birkaç parametre daha alır; ileriye gidersek yerleşik
zamanlayıcıları uygun olarak kullanacağız ve yalnızca işlevselliklerini
burada açıklayacağız. Gösterildiği gibi, gerekirse kendi zamanlayıcınızı
oluşturmak oldukça basittir.
Çok Çarpanlı Zamanlayıcı
~~~~~~~~~~~~~~~~~~~~~~~~
Derin ağları eğitmek için yaygın bir strateji, öğrenme oranını parçalı
sabit tutmak ve sık sık belirli bir miktarda azaltmaktır. Diğer bir
deyişle, bir dizi zaman verildiğinde hızın ne zaman düşürüleceğidir,
örneğin :math:`s = \{5, 10, 20\}` ve :math:`t \in s` için
:math:`\eta_{t+1} \leftarrow \eta_t \cdot \alpha`'nın azalması.
Değerlerin her adımda yarıya indirildiğini varsayarsak, bunu aşağıdaki
gibi uygulayabiliriz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
scheduler = lr_scheduler.MultiFactorScheduler(step=[15, 30], factor=0.5,
base_lr=0.5)
d2l.plot(np.arange(num_epochs), [scheduler(t) for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_77_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = net_fn()
trainer = torch.optim.SGD(net.parameters(), lr=0.5)
scheduler = lr_scheduler.MultiStepLR(trainer, milestones=[15, 30], gamma=0.5)
def get_lr(trainer, scheduler):
lr = scheduler.get_last_lr()[0]
trainer.step()
scheduler.step()
return lr
d2l.plot(torch.arange(num_epochs), [get_lr(trainer, scheduler)
for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_80_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class MultiFactorScheduler:
def __init__(self, step, factor, base_lr):
self.step = step
self.factor = factor
self.base_lr = base_lr
def __call__(self, epoch):
if epoch in self.step:
self.base_lr = self.base_lr * self.factor
return self.base_lr
else:
return self.base_lr
scheduler = MultiFactorScheduler(step=[15, 30], factor=0.5, base_lr=0.5)
d2l.plot(tf.range(num_epochs), [scheduler(t) for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_83_0.svg
.. raw:: html
.. raw:: html
Bu parçalı sabit öğrenme oranı zamanlamasının arkasındaki sezgi, ağırlık
vektörlerinin dağılımı açısından sabit bir noktaya ulaşılana kadar
optimizasyonun ilerlemesine izin vermesidir. O zaman (ve ancak o zaman)
daha yüksek kaliteli bir vekil elde etmek için oranı iyi bir yerel
minimuma düşürürüz. Aşağıdaki örnek, bunun nasıl biraz daha iyi çözümler
üretebileceğini göstermektedir.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
trainer = gluon.Trainer(net.collect_params(), 'sgd',
{'lr_scheduler': scheduler})
train(net, train_iter, test_iter, num_epochs, loss, trainer, device)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
train loss 0.177, train acc 0.933, test acc 0.901
.. figure:: output_lr-scheduler_1dfeb6_89_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
train(net, train_iter, test_iter, num_epochs, loss, trainer, device,
scheduler)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
train loss 0.204, train acc 0.922, test acc 0.888
.. figure:: output_lr-scheduler_1dfeb6_92_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
train(net, train_iter, test_iter, num_epochs, lr,
custom_callback=LearningRateScheduler(scheduler))
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss 0.228, train acc 0.915, test acc 0.890
60173.8 examples/sec on /GPU:0
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. figure:: output_lr-scheduler_1dfeb6_95_2.svg
.. raw:: html
.. raw:: html
Kosinüs Zamanlayıcı
~~~~~~~~~~~~~~~~~~~
:cite:`Loshchilov.Hutter.2016` tarafından oldukça şaşırtıcı bir
sezgisel yöntem önerildi. Başlangıçta öğrenme oranını çok büyük ölçüde
düşürmek istemeyebileceğimiz ve dahası, çözümü sonunda çok küçük bir
öğrenme oranı kullanarak “ince ayarlamak” isteyebileceğimiz gözlemine
dayanıyor. Bu, :math:`t \in [0, T]` aralığındaki öğrenme oranları için
aşağıdaki işlevsel formla kosinüs benzeri bir zamanlama ile sonuçlanır.
.. math:: \eta_t = \eta_T + \frac{\eta_0 - \eta_T}{2} \left(1 + \cos(\pi t/T)\right)
Burada :math:`\eta_0` ilk öğrenme oranı, :math:`\eta_T` ise :math:`T`
zamanındaki hedef orandır. Ayrıca, :math:`t > T` için değeri tekrar
artırmadan :math:`\eta_T`'ya sabitleriz. Aşağıdaki örnekte, maksimum
güncelleme adımını :math:`T = 20`'ye ayarladık.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
scheduler = lr_scheduler.CosineScheduler(max_update=20, base_lr=0.3,
final_lr=0.01)
d2l.plot(np.arange(num_epochs), [scheduler(t) for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_101_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class CosineScheduler:
def __init__(self, max_update, base_lr=0.01, final_lr=0,
warmup_steps=0, warmup_begin_lr=0):
self.base_lr_orig = base_lr
self.max_update = max_update
self.final_lr = final_lr
self.warmup_steps = warmup_steps
self.warmup_begin_lr = warmup_begin_lr
self.max_steps = self.max_update - self.warmup_steps
def get_warmup_lr(self, epoch):
increase = (self.base_lr_orig - self.warmup_begin_lr) \
* float(epoch) / float(self.warmup_steps)
return self.warmup_begin_lr + increase
def __call__(self, epoch):
if epoch < self.warmup_steps:
return self.get_warmup_lr(epoch)
if epoch <= self.max_update:
self.base_lr = self.final_lr + (
self.base_lr_orig - self.final_lr) * (1 + math.cos(
math.pi * (epoch - self.warmup_steps) / self.max_steps)) / 2
return self.base_lr
scheduler = CosineScheduler(max_update=20, base_lr=0.3, final_lr=0.01)
d2l.plot(torch.arange(num_epochs), [scheduler(t) for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_104_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class CosineScheduler:
def __init__(self, max_update, base_lr=0.01, final_lr=0,
warmup_steps=0, warmup_begin_lr=0):
self.base_lr_orig = base_lr
self.max_update = max_update
self.final_lr = final_lr
self.warmup_steps = warmup_steps
self.warmup_begin_lr = warmup_begin_lr
self.max_steps = self.max_update - self.warmup_steps
def get_warmup_lr(self, epoch):
increase = (self.base_lr_orig - self.warmup_begin_lr) \
* float(epoch) / float(self.warmup_steps)
return self.warmup_begin_lr + increase
def __call__(self, epoch):
if epoch < self.warmup_steps:
return self.get_warmup_lr(epoch)
if epoch <= self.max_update:
self.base_lr = self.final_lr + (
self.base_lr_orig - self.final_lr) * (1 + math.cos(
math.pi * (epoch - self.warmup_steps) / self.max_steps)) / 2
return self.base_lr
scheduler = CosineScheduler(max_update=20, base_lr=0.3, final_lr=0.01)
d2l.plot(tf.range(num_epochs), [scheduler(t) for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_107_0.svg
.. raw:: html
.. raw:: html
Bilgisayarla görme bağlamında bu zamanlama gelişmiş sonuçlara yol
aça\ *bilir*. Yine de, bu tür iyileştirmelerin garanti edilmediğini
unutmayın (aşağıda görülebileceği gibi).
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
trainer = gluon.Trainer(net.collect_params(), 'sgd',
{'lr_scheduler': scheduler})
train(net, train_iter, test_iter, num_epochs, loss, trainer, device)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
train loss 0.345, train acc 0.878, test acc 0.873
.. figure:: output_lr-scheduler_1dfeb6_113_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = net_fn()
trainer = torch.optim.SGD(net.parameters(), lr=0.3)
train(net, train_iter, test_iter, num_epochs, loss, trainer, device,
scheduler)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
train loss 0.178, train acc 0.934, test acc 0.899
.. figure:: output_lr-scheduler_1dfeb6_116_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
train(net, train_iter, test_iter, num_epochs, lr,
custom_callback=LearningRateScheduler(scheduler))
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss 0.261, train acc 0.904, test acc 0.880
60457.0 examples/sec on /GPU:0
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. figure:: output_lr-scheduler_1dfeb6_119_2.svg
.. raw:: html
.. raw:: html
Isınma
~~~~~~
Bazı durumlarda parametrelerin ilklenmesi, iyi bir çözümü garanti etmek
için yeterli değildir. Bu, özellikle kararsız optimizasyon sorunlarına
yol açabilecek bazı gelişmiş ağ tasarımları için bir sorundur.
Başlangıçta ıraksamayı önlemek için yeterince küçük bir öğrenme oranı
seçerek bunu ele alabiliriz. Ne yazık ki bu ilerlemenin yavaş olduğu
anlamına gelir. Tersine, büyük bir öğrenme oranı başlangıçta ıraksamaya
yol açar.
Bu ikilem için oldukça basit bir çözüm, öğrenme hızının başlangıçtaki
maksimum değerine *arttığı* bir ısınma periyodu kullanmak ve hızı
optimizasyon sürecinin sonuna kadar soğutmaktır. Basitlik için
genellikle bu amaçla doğrusal bir artış kullanır. Bu, aşağıda belirtilen
formda bir zamanlamaya yol açar.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
scheduler = lr_scheduler.CosineScheduler(20, warmup_steps=5, base_lr=0.3,
final_lr=0.01)
d2l.plot(np.arange(num_epochs), [scheduler(t) for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_125_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
scheduler = CosineScheduler(20, warmup_steps=5, base_lr=0.3, final_lr=0.01)
d2l.plot(torch.arange(num_epochs), [scheduler(t) for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_128_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
scheduler = CosineScheduler(20, warmup_steps=5, base_lr=0.3, final_lr=0.01)
d2l.plot(tf.range(num_epochs), [scheduler(t) for t in range(num_epochs)])
.. figure:: output_lr-scheduler_1dfeb6_131_0.svg
.. raw:: html
.. raw:: html
Ağın ilkin daha iyi yakınsadığına dikkat edin (özellikle ilk 5 dönemde
performansı gözlemleyin).
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
trainer = gluon.Trainer(net.collect_params(), 'sgd',
{'lr_scheduler': scheduler})
train(net, train_iter, test_iter, num_epochs, loss, trainer, device)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
train loss 0.347, train acc 0.874, test acc 0.873
.. figure:: output_lr-scheduler_1dfeb6_137_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = net_fn()
trainer = torch.optim.SGD(net.parameters(), lr=0.3)
train(net, train_iter, test_iter, num_epochs, loss, trainer, device,
scheduler)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
train loss 0.177, train acc 0.934, test acc 0.907
.. figure:: output_lr-scheduler_1dfeb6_140_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
train(net, train_iter, test_iter, num_epochs, lr,
custom_callback=LearningRateScheduler(scheduler))
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss 0.272, train acc 0.901, test acc 0.881
62052.2 examples/sec on /GPU:0
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. figure:: output_lr-scheduler_1dfeb6_143_2.svg
.. raw:: html
.. raw:: html
Isınma herhangi bir zamanlayıcıya uygulanabilir (sadece kosinüs değil).
Öğrenme oranı programları ve daha birçok deney hakkında daha ayrıntılı
bir tartışma için ayrıca bkz. :cite:`Gotmare.Keskar.Xiong.ea.2018`.
Özellikle, ısınma fazının çok derin ağlardaki parametrelerin sapma
miktarını sınırladığını buldular. Bu sezgisel olarak mantıklıdır, çünkü
ağın başlangıçta ilerleme kaydetmesi en çok zaman alan bölümlerinde
rastgele ilkleme nedeniyle önemli farklılıklar bekleyebiliriz.
Özet
----
- Eğitim sırasında öğrenme oranının azaltılması, doğruluğun
iyileştirilmesine ve (en şaşırtıcı şekilde) modelde azaltılmış aşırı
öğrenmeye neden olabilir.
- İlerlemenin düzleştiği her durumda öğrenme hızının parçalı azalması
pratikte etkilidir. Esasen bu, uygun bir çözüme verimli bir şekilde
yakınlaşmamızı ve ancak daha sonra öğrenme oranını azaltarak
parametrelerin doğal varyansını azaltmamızı sağlar.
- Kosinüs zamanlayıcıları bazı bilgisayarla görme problemleri için
popülerdir. Bu tür bir zamanlayıcı ile ilgili ayrıntılar için bkz.
`GluonCV `__.
- Optimizasyondan önceki bir ısınma periyodu ıraksamayı önleyebilir.
- Optimizasyon, derin öğrenmede birden çok amaca hizmet eder. Eğitim
hedefini en aza indirmenin yanı sıra, farklı optimizasyon
algoritmaları ve öğrenme oranı zamanlama seçimleri, test kümesinde
oldukça farklı miktarlarda genellemeye ve aşırı öğrenmeye (aynı
miktarda eğitim hatası için) yol açabilir.
Alıştırmalar
------------
1. Belirli bir sabit öğrenme hızı için optimizasyon davranışıyla
deneyler yapın. Bu şekilde elde edebileceğiniz en iyi model nedir?
2. Öğrenme oranındaki düşüşün üssünü değiştirirseniz yakınsama nasıl
değişir? Deneylerde kolaylık sağlamak için ``PolyScheduler``'i
kullanın.
3. Kosinüs zamanlayıcısını büyük bilgisayarla görme problemlerine
uygulayın, örn. ImageNet'i eğitin. Diğer zamanlayıcılara göre
performansı nasıl etkiler?
4. Isınma ne kadar sürer?
5. Optimizasyon ve örneklemeyi ilişkilendirebilir misiniz?
:cite:`Welling.Teh.2011`'ten Rasgele Gradyan Langevin Dinamik
sonuçlarını kullanarak başlayın.
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html