Topik ini membahas jadwal learning rate, yaitu aturan yang mengubah nilai learning rate sepanjang pelatihan alih-alih membiarkannya tetap. Objek yang mengurus aturan itu disebut scheduler, dan tiga pola yang paling sering digunakan dibahas berurutan, yaitu penurunan bertangga dengan StepLR, penurunan melengkung dengan CosineAnnealingLR, dan warmup yang menaikkan learning rate di awal pelatihan. Tempat pemanggilan scheduler.step() yang benar, baik per epoch maupun per batch, juga ditetapkan. Semua contoh mencetak nilai learning rate per epoch, sehingga polanya dapat dibaca langsung dari keluaran.
Learning rate tetap dan trade-off-nya#
Learning rate menentukan seberapa jauh bobot bergerak setiap kali diperbarui. Nilai yang besar membuat pelatihan bergerak cepat pada awalnya tetapi mudah bergoyang di sekitar solusi, sedangkan nilai yang kecil berjalan stabil tetapi lambat dan mudah berhenti di tempat yang belum optimal. Pola yang sehat biasanya menggabungkan keduanya, yaitu learning rate besar di awal pelatihan dan mengecil menjelang akhir.
Untuk pelatihan pendek dengan model kecil, learning rate yang tetap sering kali sudah cukup. Keputusan mengaturnya menjadi penting ketika pelatihan berlangsung lama, kurva loss mulai mendatar lebih awal, atau langkah terakhir masih bergoyang. Pada PyTorch, aturan tersebut diwujudkan oleh objek scheduler dari modul torch.optim.lr_scheduler yang dibuat di atas sebuah optimizer. Scheduler menulis ulang nilai lr pada param_groups milik optimizer sesuai jadwalnya, sehingga optimizer tetap menggunakan nilai itu pada setiap langkah pembaruan, sedangkan besarannya ditentukan oleh jadwal.
StepLR#
StepLR menurunkan learning rate secara bertangga, yaitu melipatgandakan nilai saat ini dengan gamma setiap kali jumlah epoch mencapai kelipatan step_size. Jadwal ini paling mudah diprediksi, karena seluruh perjalanan learning rate dapat dihitung dari tiga angka saja, yaitu nilai awal, step_size, dan gamma. Skrip berikut menggunakan nilai awal 0.1 dengan step_size=3 dan gamma=0.1, sehingga learning rate terbelah sepuluh kali setiap tiga epoch:
import torch
model = torch.nn.Linear(4, 2)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
for epoch in range(10):
print(f"epoch {epoch + 1:2d}: lr {optimizer.param_groups[0]['lr']:.4f}")
optimizer.step()
scheduler.step()epoch 1: lr 0.1000
epoch 2: lr 0.1000
epoch 3: lr 0.1000
epoch 4: lr 0.0100
epoch 5: lr 0.0100
epoch 6: lr 0.0100
epoch 7: lr 0.0010
epoch 8: lr 0.0010
epoch 9: lr 0.0010
epoch 10: lr 0.0001Keluaran memperlihatkan tangga yang dimaksud, yaitu nilai 0.1 bertahan tiga epoch, turun menjadi 0.01 untuk tiga epoch berikutnya, dan terus mengecil. Perhatikan pula posisi baris cetak sebelum optimizer.step() dan scheduler.step(), sehingga angka yang tampil adalah learning rate yang berlaku untuk epoch tersebut. Rasio penurunan 0.1 tergolong agresif, dan nilai gamma 0.5 digunakan pula pada banyak pelatihan agar tangganya landai. Pemilihan step_size lazimnya mengikuti total epoch, misalnya sepertiga atau setengah durasi pelatihan, sehingga penurunan terbesar terjadi sebelum pelatihan berakhir.
CosineAnnealingLR#
CosineAnnealingLR menurunkan learning rate mengikuti separuh gelombang kosinus, dari nilai awal menuju nilai minimum eta_min, dengan kelengkungan yang melandai di kedua ujung. Bentuk ringkasnya adalah:
dengan sebagai panjang separuh gelombang dalam satuan epoch. Keunggulan jadwal ini terletak pada penurunannya yang mulus tanpa keputusan keras kapan harus turun, sehingga hanya perlu menetapkan total durasi pelatihan. Skrip berikut menggunakan nilai awal 0.1 dengan T_max=10 dan eta_min=0.001:
import torch
model = torch.nn.Linear(4, 2)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10, eta_min=0.001)
for epoch in range(10):
print(f"epoch {epoch + 1:2d}: lr {optimizer.param_groups[0]['lr']:.4f}")
optimizer.step()
scheduler.step()epoch 1: lr 0.1000
epoch 2: lr 0.0976
epoch 3: lr 0.0905
epoch 4: lr 0.0796
epoch 5: lr 0.0658
epoch 6: lr 0.0505
epoch 7: lr 0.0352
epoch 8: lr 0.0214
epoch 9: lr 0.0105
epoch 10: lr 0.0034Penurunannya lambat pada awal dan akhir, lalu paling curam di tengah, persis seperti bentuk gelombang kosinus. Nilai pada epoch kesepuluh sudah mendekati eta_min, yaitu 0.001. Perlu diketahui bahwa T_max sebaiknya dekat dengan jumlah epoch yang direncanakan, karena nilai di luar durasi tersebut membuat jadwal tidak mencapai bagian akhirnya. Jadwal kosinus menjadi pilihan yang lazim pada pelatihan modern karena tidak menuntut keputusan kapan harus menurunkan learning rate.
Warmup pada awal pelatihan#
Warmup menaikkan learning rate secara bertahap selama beberapa langkah pertama sebelum jadwal utama dimulai. Alasannya, pada awal pelatihan bobot masih acak sehingga arah gradien belum dapat dipercaya, dan learning rate penuh sejak langkah pertama berisiko membuat loss melompat liar. Implementasi manualnya mudah dengan LambdaLR, yaitu scheduler yang mengalikan learning rate dasar dengan nilai fungsi yang menerima nomor epoch. Skrip berikut menggabungkan warmup selama tiga epoch dengan peluruhan kosinus:
import math
import torch
model = torch.nn.Linear(4, 2)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
warmup = 3 # epoch pemanasan
total = 12 # total epoch pelatihan
def jadwal(epoch):
if epoch < warmup:
return (epoch + 1) / warmup
sisa = (epoch - warmup) / (total - warmup)
return 0.5 * (1.0 + math.cos(math.pi * sisa))
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, jadwal)
for epoch in range(total):
print(f"epoch {epoch + 1:2d}: lr {optimizer.param_groups[0]['lr']:.4f}")
optimizer.step()
scheduler.step()epoch 1: lr 0.0333
epoch 2: lr 0.0667
epoch 3: lr 0.1000
epoch 4: lr 0.1000
epoch 5: lr 0.0970
epoch 6: lr 0.0883
epoch 7: lr 0.0750
epoch 8: lr 0.0587
epoch 9: lr 0.0413
epoch 10: lr 0.0250
epoch 11: lr 0.0117
epoch 12: lr 0.0030Tiga epoch pertama menaikkan learning rate dari sepertiga nilai dasar menuju nilai penuh, kemudian jadwal kosinus mengambil alih hingga akhir pelatihan. Durasi warmup yang lazim berkisar beberapa epoch atau beberapa ratus langkah pertama, cukup untuk memberi waktu bobot menemukan arah yang stabil. Diagram berikut menyandingkan ketiga pola yang dibahas, yaitu tangga StepLR, kurva kosinus, dan kombinasi warmup.
Per epoch atau per batch#
scheduler.step() dipanggil satu kali pada setiap titik jadwal, dan satuan titik itu ditentukan penulis pelatihan. Pemanggilan per epoch merupakan kebiasaan yang paling umum untuk jadwal seperti pada contoh-contoh di atas, sedangkan pemanggilan per batch menghasilkan jadwal yang lebih halus dan menjadi pilihan bila warmup dilakukan dalam satuan langkah. Satuan juga mengubah makna angka pada scheduler, karena step_size pada StepLR yang dipanggil per batch berarti kelipatan batch, bukan kelipatan epoch. Apabila scheduler per epoch dipanggil di dalam perulangan batch, penurunannya berlangsung lebih cepat dari yang direncanakan, karena setiap batch dihitung sebagai satu titik jadwal. Posisi pemanggilan per batch ditunjukkan fragmen berikut:
# tidak-dijalankan: fragmen merujuk variabel dari skrip pelatihan
for x_batch, y_batch in loader:
optimizer.zero_grad()
loss = criterion(model(x_batch), y_batch)
loss.backward()
optimizer.step()
scheduler.step() # satu kali per batch, tepat setelah optimizer.step()Ketentuan yang berlaku pada kedua satuan adalah memanggil scheduler.step() setelah optimizer.step(). Apabila urutannya dibalik, PyTorch mengeluarkan peringatan dan jadwal bergeser satu langkah dari rencananya.
Kesalahan umum#
Tiga kesalahan berikut paling sering muncul saat scheduler digunakan pertama kali.
Yang pertama adalah memanggil scheduler.step() sebelum optimizer.step(). PyTorch menandai urutan ini dengan peringatan, dan titik-titik jadwal bergeser sehingga learning rate tidak sesuai rencana. Perbaikannya, letakkan pemanggilan scheduler tepat setelah pemanggilan optimizer.
Yang kedua adalah membuat objek scheduler baru di dalam perulangan epoch. Objek baru selalu memulai jadwalnya dari awal, sehingga learning rate terus berputar pada bagian pertama jadwal dan tidak pernah menurun. Perbaikannya, buat scheduler satu kali di luar perulangan, sebagaimana seluruh contoh pada topik ini.
Yang ketiga adalah memanggil scheduler.step() di dua tempat sekaligus, misalnya di dalam perulangan batch dan sekali lagi di akhir epoch. Jadwal berjalan dua kali lebih cepat dari yang direncanakan tanpa pesan error apa pun. Perbaikannya, tetapkan satu satuan titik jadwal dan panggil scheduler.step() hanya di tempat itu.
Ringkasan#
- Scheduler mengatur nilai
lrpadaparam_groupsmilik optimizer, danscheduler.step()dipanggil satu kali per titik jadwal, baik per epoch maupun per batch, selalu setelahoptimizer.step(). StepLRmelipatgandakan learning rate dengangammasetiap kelipatanstep_size, sedangkanCosineAnnealingLRmenurunkannya secara mulus dari nilai awal menujueta_minsepanjangT_max.- Warmup menaikkan learning rate bertahap pada awal pelatihan dan diimplementasikan dengan mudah melalui
LambdaLRyang mengalikan nilai dasar dengan pengali hasil fungsi.
Referensi#
- torch.optim — PyTorch — Dokumentasi optimizer dan bagian learning rate scheduling yang mencakup seluruh kelas scheduler.
- StepLR — PyTorch — Dokumentasi resmi jadwal bertangga yang dipakai pada contoh pertama.
- CosineAnnealingLR — PyTorch — Dokumentasi resmi jadwal kosinus beserta rumusnya.
- LambdaLR — PyTorch — Dokumentasi resmi jadwal berbasis fungsi yang dipakai untuk warmup.