.. _sec_scheduler:
Lập kế hoạch tỷ lệ học tập
==========================
Cho đến nay chúng tôi chủ yếu tập trung vào tối ưu hóa các thuật toán \*
để làm thế nào để cập nhật các vectơ trọng lượng thay vì trên \* tỷ lệ\*
tại đó chúng đang được cập nhật. Tuy nhiên, việc điều chỉnh tốc độ học
tập thường cũng quan trọng như thuật toán thực tế. Có một số khía cạnh
cần xem xét:
- Rõ ràng nhất là *độ lượng* của tỷ lệ học tập quan trọng. Nếu nó quá
lớn, tối ưu hóa phân kỳ, nếu nó quá nhỏ, phải mất quá nhiều thời gian
để đào tạo hoặc chúng ta kết thúc với một kết quả tối ưu. Trước đây
chúng tôi đã thấy rằng số điều kiện của vấn đề quan trọng (xem ví dụ,
:numref:`sec_momentum` để biết chi tiết). Trực giác đó là tỷ lệ
giữa số lượng thay đổi theo hướng ít nhạy cảm nhất so với một nhạy
cảm nhất.
- Thứ hai, tỷ lệ phân rã cũng quan trọng như vậy. Nếu tỷ lệ học tập vẫn
lớn, chúng tôi có thể chỉ đơn giản là kết thúc nảy xung quanh mức tối
thiểu và do đó không đạt được sự tối ưu.
:numref:`sec_minibatch_sgd` đã thảo luận chi tiết về điều này và
chúng tôi đã phân tích đảm bảo hiệu suất trong :numref:`sec_sgd`.
Nói tóm lại, chúng tôi muốn tỷ lệ phân rã, nhưng có lẽ chậm hơn
:math:`\mathcal{O}(t^{-\frac{1}{2}})`, đây sẽ là một lựa chọn tốt cho
các vấn đề lồi.
- Một khía cạnh khác quan trọng không kém là \* khởi hóa\ *. Điều này
liên quan đến cả cách các tham số được đặt ban đầu (xem lại
:numref:`sec_numerical_stability` để biết chi tiết) và cũng như
cách chúng phát triển ban đầu. Điều này đi theo moniker của *
warmup\*, tức là, nhanh chóng như thế nào chúng ta bắt đầu di chuyển
về phía giải pháp ban đầu. Các bước lớn ngay từ đầu có thể không có
lợi, đặc biệt là vì tập hợp các tham số ban đầu là ngẫu nhiên. Các
hướng cập nhật ban đầu cũng có thể khá vô nghĩa.
- Cuối cùng, có một số biến thể tối ưu hóa thực hiện điều chỉnh tỷ lệ
học tập theo chu kỳ. Điều này nằm ngoài phạm vi của chương hiện tại.
Chúng tôi khuyên người đọc xem lại chi tiết trong
:cite:`Izmailov.Podoprikhin.Garipov.ea.2018`, ví dụ, làm thế nào để
có được các giải pháp tốt hơn bằng cách trung bình trên toàn bộ một
\* path\* của các tham số.
Với thực tế là có rất nhiều chi tiết cần thiết để quản lý tỷ lệ học tập,
hầu hết các khuôn khổ học sâu đều có các công cụ để xử lý điều này một
cách tự động. Trong chương hiện tại, chúng tôi sẽ xem xét các hiệu ứng
mà các lịch trình khác nhau có về độ chính xác và cũng cho thấy cách
điều này có thể được quản lý hiệu quả thông qua một *học tỷ lệ lịch
học*.
Vấn đề Toy
----------
Chúng tôi bắt đầu với một vấn đề đồ chơi đủ rẻ để tính toán dễ dàng,
nhưng đủ không tầm thường để minh họa một số khía cạnh chính. Đối với
điều đó, chúng tôi chọn một phiên bản hơi hiện đại hóa của LeNet
(``relu`` thay vì kích hoạt ``sigmoid``, MaxPooling thay vì
AveragePooling), như áp dụng cho Fashion-MNIST. Hơn nữa, chúng tôi lai
mạng để thực hiện. Vì hầu hết các mã là tiêu chuẩn, chúng tôi chỉ giới
thiệu những điều cơ bản mà không cần thảo luận chi tiết thêm. Xin xem
:numref:`chap_cnn` để được làm mới khi cần thiết.
.. raw:: html
.. raw:: html
.. code:: python
%matplotlib inline
from mxnet import autograd, gluon, init, lr_scheduler, np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l
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)
# The code is almost identical to `d2l.train_ch6` defined in the
# lenet section of chapter convolutional neural networks
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
.. 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)
# The code is almost identical to `d2l.train_ch6` defined in the
# lenet section of chapter convolutional neural networks
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
.. code:: python
%matplotlib inline
import math
import tensorflow as tf
from tensorflow.keras.callbacks import LearningRateScheduler
from d2l import tensorflow as d2l
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)
# The code is almost identical to `d2l.train_ch6` defined in the
# lenet section of chapter convolutional neural networks
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
Chúng ta hãy xem những gì sẽ xảy ra nếu chúng ta gọi thuật toán này với
các cài đặt mặc định, chẳng hạn như tốc độ học tập :math:`0.3` và đào
tạo cho :math:`30` lặp lại. Lưu ý độ chính xác của đào tạo tiếp tục tăng
lên trong khi tiến bộ về các quầy hàng chính xác thử nghiệm vượt ra
ngoài một điểm. Khoảng cách giữa cả hai đường cong cho thấy quá mức.
.. raw:: html
.. raw:: html
.. 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)
.. parsed-literal::
:class: output
train loss 0.130, train acc 0.950, test acc 0.897
.. figure:: output_lr-scheduler_1dfeb6_15_1.svg
.. raw:: html
.. raw:: html
.. 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)
.. parsed-literal::
:class: output
train loss 0.156, train acc 0.940, test acc 0.869
.. figure:: output_lr-scheduler_1dfeb6_18_1.svg
.. raw:: html
.. raw:: html
.. code:: python
lr, num_epochs = 0.3, 30
train(net, train_iter, test_iter, num_epochs, lr)
.. parsed-literal::
:class: output
loss 0.212, train acc 0.920, test acc 0.895
51676.5 examples/sec on /GPU:0
.. parsed-literal::
:class: output
.. figure:: output_lr-scheduler_1dfeb6_21_2.svg
.. raw:: html
.. raw:: html
Người lập lịch
--------------
Một cách để điều chỉnh tốc độ học tập là thiết lập nó một cách rõ ràng ở
mỗi bước. Điều này đạt được thuận tiện bằng phương pháp
``set_learning_rate``. Chúng ta có thể điều chỉnh nó xuống sau mỗi kỷ
nguyên (hoặc thậm chí sau mỗi minibatch), ví dụ, một cách năng động để
đáp ứng cách tối ưu hóa đang tiến triển.
.. raw:: html
.. raw:: html
.. code:: python
trainer.set_learning_rate(0.1)
print(f'learning rate is now {trainer.learning_rate:.2f}')
.. parsed-literal::
:class: output
learning rate is now 0.10
.. raw:: html
.. raw:: html
.. code:: python
lr = 0.1
trainer.param_groups[0]["lr"] = lr
print(f'learning rate is now {trainer.param_groups[0]["lr"]:.2f}')
.. parsed-literal::
:class: output
learning rate is now 0.10
.. raw:: html
.. raw:: html
.. 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())
.. parsed-literal::
:class: output
learning rate is now , 0.1
.. raw:: html
.. raw:: html
Nói chung hơn chúng tôi muốn xác định một trình lập lịch. Khi được gọi
với số lượng cập nhật nó trả về giá trị thích hợp của tốc độ học tập.
Hãy để chúng tôi xác định một cái đơn giản đặt tỷ lệ học tập thành
:math:`\eta = \eta_0 (t + 1)^{-\frac{1}{2}}`.
.. 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)
Hãy để chúng tôi vẽ hành vi của nó trên một loạt các giá trị.
.. raw:: html
.. raw:: html
.. 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
.. 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
.. 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
Bây giờ chúng ta hãy xem cách điều này diễn ra để đào tạo về thời
trang-MNIST. Chúng tôi chỉ cần cung cấp trình lập lịch như một đối số bổ
sung cho thuật toán đào tạo.
.. raw:: html
.. raw:: html
.. code:: python
trainer = gluon.Trainer(net.collect_params(), 'sgd',
{'lr_scheduler': scheduler})
train(net, train_iter, test_iter, num_epochs, loss, trainer, device)
.. parsed-literal::
:class: output
train loss 0.523, train acc 0.810, test acc 0.797
.. figure:: output_lr-scheduler_1dfeb6_53_1.svg
.. raw:: html
.. raw:: html
.. code:: python
net = net_fn()
trainer = torch.optim.SGD(net.parameters(), lr)
train(net, train_iter, test_iter, num_epochs, loss, trainer, device,
scheduler)
.. parsed-literal::
:class: output
train loss 0.275, train acc 0.898, test acc 0.873
.. figure:: output_lr-scheduler_1dfeb6_56_1.svg
.. raw:: html
.. raw:: html
.. code:: python
train(net, train_iter, test_iter, num_epochs, lr,
custom_callback=LearningRateScheduler(scheduler))
.. parsed-literal::
:class: output
loss 0.381, train acc 0.861, test acc 0.846
54892.0 examples/sec on /GPU:0
.. parsed-literal::
:class: output
.. figure:: output_lr-scheduler_1dfeb6_59_2.svg
.. raw:: html
.. raw:: html
Điều này làm việc khá tốt hơn một chút so với trước đây. Hai điều nổi
bật: đường cong khá trơn tru hơn trước đây. Thứ hai, có ít quá nhiều
hơn. Thật không may, nó không phải là một câu hỏi được giải quyết tốt về
lý do tại sao một số chiến lược nhất định dẫn đến ít quá mức trong \* lý
đề\*. Có một số lập luận rằng kích thước bước nhỏ hơn sẽ dẫn đến các
tham số gần bằng 0 và do đó đơn giản hơn. Tuy nhiên, điều này không giải
thích hoàn toàn hiện tượng vì chúng ta không thực sự dừng lại sớm mà chỉ
đơn giản là giảm tốc độ học tập nhẹ nhàng.
Chính sách
----------
Mặc dù chúng tôi không thể bao gồm toàn bộ các lập lịch trình tỷ lệ học
tập, chúng tôi cố gắng đưa ra một cái nhìn tổng quan ngắn gọn về các
chính sách phổ biến dưới đây. Các lựa chọn phổ biến là phân rã đa thức
và từng mảnh lịch trình liên tục. Ngoài ra, lịch trình học cosine đã
được tìm thấy để làm việc tốt theo kinh nghiệm về một số vấn đề. Cuối
cùng, về một số vấn đề, có lợi khi làm nóng trình tối ưu hóa trước khi
sử dụng tỷ lệ học tập lớn.
Bộ lập lịch yếu tố
~~~~~~~~~~~~~~~~~~
Một thay thế cho phân rã đa thức sẽ là một phân rã nhân, đó là
:math:`\eta_{t+1} \leftarrow \eta_t \cdot \alpha` cho
:math:`\alpha \in (0, 1)`. Để ngăn chặn tỷ lệ học tập phân rã vượt quá
giới hạn thấp hơn hợp lý, phương trình cập nhật thường được sửa đổi
thành
:math:`\eta_{t+1} \leftarrow \mathop{\mathrm{max}}(\eta_{\mathrm{min}}, \eta_t \cdot \alpha)`.
.. raw:: html
.. raw:: html
.. 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
.. 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
.. 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
Điều này cũng có thể được thực hiện bằng trình lập lịch tích hợp trong
MXNet thông qua đối tượng ``lr_scheduler.FactorScheduler``. Phải mất
thêm một vài tham số, chẳng hạn như thời gian khởi động, chế độ khởi
động (tuyến tính hoặc không đổi), số lượng cập nhật mong muốn tối đa,
v.v.; Về phía trước, chúng tôi sẽ sử dụng các bộ lập lịch tích hợp phù
hợp và chỉ giải thích chức năng của chúng ở đây. Như minh họa, nó là khá
đơn giản để xây dựng lịch trình của riêng bạn nếu cần thiết.
Bộ lập lịch đa yếu tố
~~~~~~~~~~~~~~~~~~~~~
Một chiến lược phổ biến để đào tạo các mạng sâu là giữ cho tỷ lệ học tập
không đổi và giảm nó bằng một số tiền nhất định thường xuyên. Đó là, đưa
ra một tập hợp thời gian khi giảm tỷ lệ, chẳng hạn như
:math:`s = \{5, 10, 20\}` giảm
:math:`\eta_{t+1} \leftarrow \eta_t \cdot \alpha` bất cứ khi nào
:math:`t \in s`. Giả sử rằng các giá trị giảm một nửa ở mỗi bước chúng
ta có thể thực hiện điều này như sau.
.. raw:: html
.. raw:: html
.. 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
.. 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
.. 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
Trực giác đằng sau lịch trình tỷ lệ học tập liên tục piecewise này là
một cho phép tối ưu hóa tiến hành cho đến khi đạt được một điểm cố định
về sự phân bố của vectơ trọng lượng. Sau đó (và chỉ sau đó) chúng ta có
giảm tỷ lệ như để có được một proxy chất lượng cao hơn đến mức tối thiểu
địa phương tốt. Ví dụ dưới đây cho thấy làm thế nào điều này có thể tạo
ra các giải pháp tốt hơn bao giờ hết.
.. raw:: html
.. raw:: html
.. code:: python
trainer = gluon.Trainer(net.collect_params(), 'sgd',
{'lr_scheduler': scheduler})
train(net, train_iter, test_iter, num_epochs, loss, trainer, device)
.. parsed-literal::
:class: output
train loss 0.200, train acc 0.924, test acc 0.885
.. figure:: output_lr-scheduler_1dfeb6_89_1.svg
.. raw:: html
.. raw:: html
.. code:: python
train(net, train_iter, test_iter, num_epochs, loss, trainer, device,
scheduler)
.. parsed-literal::
:class: output
train loss 0.180, train acc 0.932, test acc 0.891
.. figure:: output_lr-scheduler_1dfeb6_92_1.svg
.. raw:: html
.. raw:: html
.. code:: python
train(net, train_iter, test_iter, num_epochs, lr,
custom_callback=LearningRateScheduler(scheduler))
.. parsed-literal::
:class: output
loss 0.234, train acc 0.913, test acc 0.882
55093.9 examples/sec on /GPU:0
.. parsed-literal::
:class: output
.. figure:: output_lr-scheduler_1dfeb6_95_2.svg
.. raw:: html
.. raw:: html
Bộ lập lịch cosine
~~~~~~~~~~~~~~~~~~
Một heuristic khá bối rối đã được đề xuất bởi
:cite:`Loshchilov.Hutter.2016`. Nó dựa vào quan sát rằng chúng ta có
thể không muốn giảm tốc độ học tập quá đáng kể ngay từ đầu và hơn nữa,
rằng chúng ta có thể muốn “tinh chỉnh” giải pháp cuối cùng bằng cách sử
dụng một tốc độ học tập rất nhỏ. Điều này dẫn đến một lịch trình giống
như cosin với dạng chức năng sau đây cho tỷ lệ học tập trong khoảng
:math:`t \in [0, T]`.
.. math:: \eta_t = \eta_T + \frac{\eta_0 - \eta_T}{2} \left(1 + \cos(\pi t/T)\right)
Ở đây :math:`\eta_0` là tỷ lệ học tập ban đầu, :math:`\eta_T` là tỷ lệ
mục tiêu tại thời điểm :math:`T`. Hơn nữa, đối với :math:`t > T`, chúng
tôi chỉ cần ghim giá trị lên :math:`\eta_T` mà không tăng lại. Trong ví
dụ sau, chúng tôi đặt bước cập nhật tối đa :math:`T = 20`.
.. raw:: html
.. raw:: html
.. 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
.. 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
.. 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
Trong bối cảnh tầm nhìn máy tính, lịch trình này \* có thể\* dẫn đến kết
quả được cải thiện. Tuy nhiên, lưu ý rằng những cải tiến như vậy không
được đảm bảo (như có thể thấy dưới đây).
.. raw:: html
.. raw:: html
.. code:: python
trainer = gluon.Trainer(net.collect_params(), 'sgd',
{'lr_scheduler': scheduler})
train(net, train_iter, test_iter, num_epochs, loss, trainer, device)
.. parsed-literal::
:class: output
train loss 0.346, train acc 0.877, test acc 0.876
.. figure:: output_lr-scheduler_1dfeb6_113_1.svg
.. raw:: html
.. raw:: html
.. 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)
.. parsed-literal::
:class: output
train loss 0.190, train acc 0.929, test acc 0.898
.. figure:: output_lr-scheduler_1dfeb6_116_1.svg
.. raw:: html
.. raw:: html
.. code:: python
train(net, train_iter, test_iter, num_epochs, lr,
custom_callback=LearningRateScheduler(scheduler))
.. parsed-literal::
:class: output
loss 0.257, train acc 0.907, test acc 0.886
55621.9 examples/sec on /GPU:0
.. parsed-literal::
:class: output
.. figure:: output_lr-scheduler_1dfeb6_119_2.svg
.. raw:: html
.. raw:: html
Khởi động
~~~~~~~~~
Trong một số trường hợp khởi tạo các tham số là không đủ để đảm bảo một
giải pháp tốt. Điều này đặc biệt là một vấn đề đối với một số thiết kế
mạng tiên tiến có thể dẫn đến các vấn đề tối ưu hóa không ổn định. Chúng
tôi có thể giải quyết điều này bằng cách chọn một tốc độ học tập đủ nhỏ
để ngăn chặn sự phân kỳ ngay từ đầu. Thật không may, điều này có nghĩa
là tiến bộ chậm. Ngược lại, một tỷ lệ học tập lớn ban đầu dẫn đến sự
phân kỳ.
Một khắc phục khá đơn giản cho tình huống khó xử này là sử dụng thời
gian khởi động trong đó tốc độ học tập \* tăng\* lên mức tối đa ban đầu
và để hạ nhiệt tỷ lệ cho đến khi kết thúc quá trình tối ưu hóa. Để đơn
giản, người ta thường sử dụng sự gia tăng tuyến tính cho mục đích này.
Điều này dẫn đến một lịch trình của biểu mẫu được chỉ định dưới đây.
.. raw:: html
.. raw:: html
.. 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
.. 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
.. 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
Lưu ý rằng mạng hội tụ tốt hơn ban đầu (đặc biệt quan sát hiệu suất
trong 5 thời đại đầu tiên).
.. raw:: html
.. raw:: html
.. code:: python
trainer = gluon.Trainer(net.collect_params(), 'sgd',
{'lr_scheduler': scheduler})
train(net, train_iter, test_iter, num_epochs, loss, trainer, device)
.. parsed-literal::
:class: output
train loss 0.350, train acc 0.873, test acc 0.860
.. figure:: output_lr-scheduler_1dfeb6_137_1.svg
.. raw:: html
.. raw:: html
.. 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)
.. parsed-literal::
:class: output
train loss 0.208, train acc 0.924, test acc 0.895
.. figure:: output_lr-scheduler_1dfeb6_140_1.svg
.. raw:: html
.. raw:: html
.. code:: python
train(net, train_iter, test_iter, num_epochs, lr,
custom_callback=LearningRateScheduler(scheduler))
.. parsed-literal::
:class: output
loss 0.268, train acc 0.903, test acc 0.883
54638.2 examples/sec on /GPU:0
.. parsed-literal::
:class: output
.. figure:: output_lr-scheduler_1dfeb6_143_2.svg
.. raw:: html
.. raw:: html
Khởi động có thể được áp dụng cho bất kỳ bộ lập lịch nào (không chỉ
cosine). Để có một cuộc thảo luận chi tiết hơn về lịch trình học tập và
nhiều thí nghiệm khác xem thêm :cite:`Gotmare.Keskar.Xiong.ea.2018`.
Đặc biệt, họ thấy rằng một giai đoạn khởi động giới hạn số lượng phân kỳ
của các tham số trong các mạng rất sâu. Điều này có ý nghĩa trực giác vì
chúng ta sẽ mong đợi sự phân kỳ đáng kể do khởi tạo ngẫu nhiên trong
những phần của mạng mất nhiều thời gian nhất để đạt được tiến bộ ngay từ
đầu.
Tóm tắt
-------
- Giảm tỷ lệ học tập trong quá trình đào tạo có thể dẫn đến cải thiện
độ chính xác và (khó chịu nhất) giảm quá mức của mô hình.
- Giảm từng phần tỷ lệ học tập bất cứ khi nào tiến bộ đã đạt được hiệu
quả trong thực tế. Về cơ bản, điều này đảm bảo rằng chúng tôi hội tụ
hiệu quả với một giải pháp phù hợp và chỉ sau đó giảm phương sai vốn
có của các tham số bằng cách giảm tỷ lệ học tập.
- Cosine scheulers là phổ biến cho một số vấn đề tầm nhìn máy tính. Xem
ví dụ, `GluonCV `__ để biết chi tiết về
trình lập lịch như vậy.
- Một thời gian khởi động trước khi tối ưu hóa có thể ngăn chặn sự phân
kỳ.
- Tối ưu hóa phục vụ nhiều mục đích trong học sâu. Bên cạnh việc giảm
thiểu mục tiêu đào tạo, các lựa chọn khác nhau về thuật toán tối ưu
hóa và lập kế hoạch tỷ lệ học tập có thể dẫn đến số lượng tổng quát
hóa và quá mức khác nhau trên bộ thử nghiệm (đối với cùng một lượng
lỗi đào tạo).
Bài tập
-------
1. Thử nghiệm với hành vi tối ưu hóa cho một tỷ lệ học tập cố định nhất
định. Mô hình tốt nhất bạn có thể có được theo cách này là gì?
2. Làm thế nào để hội tụ thay đổi nếu bạn thay đổi số mũ của sự giảm tỷ
lệ học tập? Sử dụng ``PolyScheduler`` để thuận tiện cho bạn trong các
thí nghiệm.
3. Áp dụng bộ lập lịch cosine cho các vấn đề tầm nhìn máy tính lớn, ví
dụ: đào tạo ImageNet. Làm thế nào để nó ảnh hưởng đến hiệu suất so
với các lập lịch khác?
4. Thời gian khởi động nên kéo dài bao lâu?
5. Bạn có thể kết nối tối ưu hóa và lấy mẫu? Bắt đầu bằng cách sử dụng
kết quả từ :cite:`Welling.Teh.2011` trên Stochastic Gradient
Langevin Dynamics.
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html