Chapter Learning Rate
menunjukkan satu angka yang salah dapat membuat training merayap atau berosilasi. Namun angka terbaik itu sendiri berubah seiring usia training: awalnya kita butuh langkah besar untuk bergerak cepat, di tengah jalan langkah sedang untuk menjelajahi lereng, dan di akhir langkah kecil untuk berpijak presisi pada dasar minimum. Learning rate scheduling mengotomatiskan perubahan tersebut lewat objek scheduler PyTorch yang menimpa learning rate optimizer pada titik waktu yang kita tentukan. Chapter ini membahas tiga scheduler paling sering dipakai, posisi pemanggilan scheduler.step() yang benar, dan jebakan paling umum: lupa memanggilnya sama sekali.
Mengapa Learning Rate Perlu Diatur Ulang #
Tiga pola waktu menguasai praktik modern. Pertama, warm-up: memulai dari learning rate kecil lalu menaikkannya selama beberapa epoch awal, karena gradien pada bobot acak masih liar dan langkah besar di fase ini mudah melempar training ke wilayah yang sulit dipulihkan. Kedua, decay bertahap: menurunkan learning rate saat loss mulai mendatar agar parameter berpindah dalam langkah halus, tidak melompati minimum. Ketiga, cosine annealing: menurunkan learning rate mengikuti setengah kurva kosinus — mulai landai, curam di tengah, lalu melandai lagi mendekati nol — sehingga penurunan terasa mulus tanpa perlu memilih titik potong manual seperti pada step decay.
Tabel berikut memetakan scheduler yang tersedia di modul torch.optim.lr_scheduler:
| Jadwal | Pola lr | Kapan dipakai |
|---|---|---|
StepLR | turun dikali gamma tiap step_size epoch | baseline sederhana, mudah dibaca |
MultiStepLR | turun di daftar epoch tertentu | mengikuti jadwal manual (mis. ep 30/60/90) |
ExponentialLR | dikali gamma tiap epoch | decay agresif dan monoton |
CosineAnnealingLR | kurva kosinus menuju eta_min | pelatihan panjang tanpa titik potong manual |
OneCycleLR | naik ke max_lr lalu cosine turun | satu siklus penuh, sering tercepat konvergen |
| warm-up + cosine | naik singkat lalu cosine turun | transformer dan model besar |
Tiga Scheduler dalam Praktik #
Berkas jadwal_learning_rate.py tidak melatih model sungguhan; skrip ini memutar kerangka training 16 epoch sambil mencatat nilai learning rate yang benar-benar dipakai — cara tercepat memahami perilaku tiap scheduler tanpa derau loss. Bagian pertama membandingkan StepLR dan CosineAnnealingLR, bagian kedua membedah OneCycleLR per batch, dan bagian ketiga menyimpan skenario kelalaian:
"""Mengamati nilai learning rate yang dipakai tiap epoch untuk tiga scheduler.
Bagian 1: StepLR dan CosineAnnealingLR — lr dicatat di awal epoch
(lr yang benar-benar dipakai), lalu scheduler.step() dipanggil satu
kali per epoch SETELAH optimizer.step().
Bagian 2: OneCycleLR — scheduler.step() dipanggil per batch; lr naik
saat pemanasan, menyentuh puncak, lalu melandai menuju nilai akhir.
Bagian 3: demonstrasi lupa scheduler.step() — lr tidak pernah berubah.
"""
import torch
import torch.nn as nn
torch.manual_seed(42)
def buat_model():
torch.manual_seed(7)
return nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 3))
EPOCH = 16
# ---------- 1. Dua scheduler per-epoch ----------
model_step = buat_model()
opt_step = torch.optim.SGD(model_step.parameters(), lr=0.1)
sch_step = torch.optim.lr_scheduler.StepLR(opt_step, step_size=4, gamma=0.5)
model_cos = buat_model()
opt_cos = torch.optim.SGD(model_cos.parameters(), lr=0.1)
sch_cos = torch.optim.lr_scheduler.CosineAnnealingLR(
opt_cos, T_max=EPOCH, eta_min=0.001
)
lr_step, lr_cos = [], []
for epoch in range(EPOCH):
lr_step.append(opt_step.param_groups[0]["lr"]) # lr epoch ini
lr_cos.append(opt_cos.param_groups[0]["lr"])
# ... training loop sesungguhnya berjalan di sini ...
opt_step.step() # 1) optimizer memakai lr epoch ini
opt_cos.step()
sch_step.step() # 2) scheduler mengatur lr epoch berikutnya
sch_cos.step()
print(f"{'epoch':>5s} {'StepLR':>10s} {'Cosine':>10s}")
for e in range(EPOCH):
if e < 6 or e == EPOCH - 1:
print(f"{e + 1:5d} {lr_step[e]:10.4f} {lr_cos[e]:10.4f}")
print(f"setelah epoch {EPOCH}: lr StepLR = "
f"{opt_step.param_groups[0]['lr']:.4f} | lr Cosine = "
f"{opt_cos.param_groups[0]['lr']:.4f}")
# ---------- 2. OneCycleLR: scheduler per-batch ----------
BATCH_PER_EPOCH = 4
model_one = buat_model()
opt_one = torch.optim.SGD(model_one.parameters(), lr=0.002)
sch_one = torch.optim.lr_scheduler.OneCycleLR(
opt_one, max_lr=0.1, epochs=EPOCH,
steps_per_epoch=BATCH_PER_EPOCH,
)
lr_one = []
for epoch in range(EPOCH):
for batch in range(BATCH_PER_EPOCH):
lr_one.append(opt_one.param_groups[0]["lr"]) # lr langkah ini
# ... satu batch training berlangsung di sini ...
opt_one.step()
sch_one.step() # per batch, bukan per epoch
print()
print("OneCycleLR (64 langkah, 16 epoch x 4 batch):")
for langkah in [0, 15, 19, 31, 47]:
print(f" langkah {langkah + 1:2d}: lr = {lr_one[langkah]:.4f}")
print(f" langkah 64 (terakhir): lr = {lr_one[63]:.7f}")
# ---------- 3. Lupa memanggil scheduler.step() ----------
model_lupa = buat_model()
opt_lupa = torch.optim.SGD(model_lupa.parameters(), lr=0.1)
sch_lupa = torch.optim.lr_scheduler.StepLR(opt_lupa, step_size=4, gamma=0.5)
for epoch in range(EPOCH):
opt_lupa.step() # optimizer jalan ...
# ... tapi sch_lupa.step() tidak pernah dipanggil
print()
print(f"lr setelah {EPOCH} epoch TANPA scheduler.step(): "
f"{opt_lupa.param_groups[0]['lr']:.4f}")Output yang Dihasilkan:
epoch StepLR Cosine
1 0.1000 0.1000
2 0.1000 0.0990
3 0.1000 0.0962
4 0.1000 0.0917
5 0.0500 0.0855
6 0.0500 0.0780
16 0.0125 0.0020
setelah epoch 16: lr StepLR = 0.0063 | lr Cosine = 0.0010
OneCycleLR (64 langkah, 16 epoch x 4 batch):
langkah 1: lr = 0.0040
langkah 16: lr = 0.0929
langkah 20: lr = 0.0999
langkah 32: lr = 0.0812
langkah 48: lr = 0.0283
langkah 64 (terakhir): lr = 0.0000004
lr setelah 16 epoch TANPA scheduler.step(): 0.1000Mari kita telaah kode di atas baris per baris:
- Baris 27 (
StepLR):step_size=4, gamma=0.5berarti learning rate menjadi setengah setiap 4 epoch — terlihat pada keluaran: epoch 1-4 bernilai 0,1000, epoch 5-6 bernilai 0,0500, dan seterusnya hingga 0,0125 pada epoch 16. - Baris 31-33 (
CosineAnnealingLR):T_max=16menempatkan dasar kurva kosinus tepat di epoch ke-16 dengan lantaieta_min=0.001. Nilainya turun mulus 0,0990 → 0,0962 → 0,0917 — kontras dengan penurunan bertangga milik StepLR. - Baris 37-38 (pencatatan lr): nilai dicatat di awal epoch, sebelum
optimizer.step()— inilah learning rate yang benar-benar dipakai epoch tersebut, bukan nilai yang sudah ditimpa scheduler. - Baris 40-43 (urutan step): optimizer melangkah lebih dulu, scheduler menyusul. Scheduler yang dipanggil sebelum
optimizer.step()akan membuat optimizer memakai learning rate epoch berikutnya — meleset satu epoch sejak awal. - Baris 57-60 (
OneCycleLR): scheduler ini merencanakan seluruh perjalanan naik-turun dalam satu kurva; karenasteps_per_epoch=4, kurvanya terbagi 64 langkah, danscheduler.step()wajib dipanggil per batch (baris 68), bukan per epoch. - Baris 72-74 (bacaan kurva OneCycle): lr memanas dari 0,0040 menuju puncak 0,0999 di sekitar langkah 20, lalu menurun cosine hingga 0,0000004 di langkah terakhir — pola warm-up dan annealing lengkap dalam satu objek.
- Baris 80-82 (skenario kelalaian):
sch_lupadibuat tetapistep()-nya tidak pernah dipanggil. Hasilnya pada baris terakhir keluaran: learning rate diam di 0,1000 padahal 16 epoch telah berlalu — scheduler yang tidak dipanggil hanyalah objek mati.
Jebakan Posisi dan Kelalaian #
Dua aturan praktis merangkum seluruh bab ini. Aturan pertama: untuk scheduler per-epoch, panggil scheduler.step() satu kali setelah seluruh batch epoch itu selesai — posisinya di akhir blok epoch, setelah optimizer.step() terakhir. Aturan kedua: untuk OneCycleLR, panggil di akhir setiap batch. PyTorch juga akan menempelkan peringatan di terminal bila mendeteksi urutan scheduler.step() sebelum optimizer.step(), namun program tetap berjalan — kesalahan ini tidak menghentikan training, hanya diam-diam menggeser jadwal.
Rujukan resmi untuk seluruh scheduler dan aturan urutan pemanggilannya tersedia di docs.pytorch.org/docs/stable/optim.html . Scheduler mengatur besar langkah; bab berikutnya berjaga-jaga terhadap masalah yang datang dari arah berbeda — gradien yang tiba-tiba membengkak hingga satu langkah optimizer merusak bobot yang sudah terlatih. Alatnya bernama gradient clipping.