Setiap iterasi training menutup dengan keputusan yang sama: seberapa jauh parameter digeser melawan arah gradien? Gradien hanya menunjukkan arah kemiringan; learning rate menentukan panjang langkah kita menuruni kemiringan itu. Nilainya kecil secara angka tetapi konsekuensinya besar — terlalu kecil membuat training merangkak berhari-hari, terlalu besar membuat loss berayun atau meroket hingga nan. Chapter ini membahas peran learning rate pada aturan pembaruan, lalu menjalankan sweep tiga nilai (0.001, 0.1, 10) di mana seluruh kondisi lain disamakan, sehingga perbedaan lintasan loss murni menjadi akibat learning rate.

Peran Learning Rate pada Pembaruan Parameter #

Aturan pembaruan gradient descent yang kita turunkan manual di chapter Gradient Descent berbunyi:

python
w = w - lr * grad

Gradien grad memiliki satuan dan skala sendiri — bisa 0.01 pada model tenang, bisa 500 pada input tak diskalakan — sehingga panjang langkah efektifnya adalah hasil kali lr × ‖grad‖. Learning rate yang sehat untuk satu masalah bisa berbahaya untuk masalah lain; itulah alasan nilai ini tidak pernah bisa disalin mentah-mentah antar proyek.

Terlalu Besar atau Terlalu Kecil #

Gejala pada lintasan lossDiagnosis
turun lalu naik tajam, berayun naik-turun, atau menuju inf/nanlearning rate terlalu besar — langkah melampaui dasar lembah
hampir tidak turun setelah puluhan epochlearning rate terlalu kecil — langkah terlalu pendek untuk kemajuan terukur
turun stabil, lalu melandai perlahanwilayah sehat; penurunan bertahap (scheduler) dapat memoles bagian akhir

Baris pertama tabel perlu dijelaskan secara geometri: kalau langkah selalu lebih panjang daripada lebar lembah, parameter melompat dari satu dinding ke dinding seberang dan justru menjauhi dasar — loss naik meski gradien selalu menunjuk ke bawah. Baris kedua adalah pemborosan diam-diam: tidak ada yang salah secara teknis, hanya waktu training membengkak tanpa perlu.

Contoh: Sweep Tiga Learning Rate #

Berkas sweep_lr.py melatih tiga model dari inisialisasi bobot yang identik pada make_moons 400 sampel — MLP [2, 16, 2], batch 32, SGD, 20 epoch. Bagian pertama mengukur panjang langkah pembaruan pertama (lr × norma gradien); bagian kedua melihat akibatnya pada lintasan loss:

python
"""Eksperimen sweep learning rate: 0.001, 0.1, dan 10.

Dataset make_moons 400 sampel, MLP [2, 16, 2], batch 32, SGD, 20 epoch.
Hanya learning rate yang berubah. Bagian pertama mengukur panjang langkah
pembaruan pertama (lr x norma gradien); bagian kedua melihat akibatnya
pada lintasan loss. Semua contoh diuji pada CPU (torch 2.14.1+cpu).
"""

import torch
import torch.nn as nn
from sklearn.datasets import make_moons
from torch.utils.data import DataLoader, TensorDataset

torch.manual_seed(42)

# ---------- 1. Dataset dan komponen yang disamakan ----------
X, y = make_moons(n_samples=400, noise=0.20, random_state=42)
X_t = torch.tensor(X, dtype=torch.float32)
y_t = torch.tensor(y, dtype=torch.long)
dataset = TensorDataset(X_t, y_t)

JUMLAH_EPOCH = 20
EPOCH_CETAK = (1, 2, 5, 10, 15, 20)


def buat_model():
    """Model dengan inisialisasi bobot identik untuk tiap learning rate."""
    torch.manual_seed(7)
    return nn.Sequential(
        nn.Linear(2, 16),
        nn.ReLU(),
        nn.Linear(16, 2),
    )


def batch_pertama():
    """Ambil batch pertama dengan urutan shuffle yang sama."""
    loader = DataLoader(
        dataset,
        batch_size=32,
        shuffle=True,
        generator=torch.Generator().manual_seed(42),
    )
    return next(iter(loader))


# ---------- 2. Ukuran langkah pembaruan pertama: lr x norma gradien ----------
print("===== UKURAN LANGKAH PEMBARUAN PERTAMA =====")
X_batch, y_batch = batch_pertama()
for lr in (0.001, 0.1, 10.0):
    model = buat_model()
    loss = nn.CrossEntropyLoss()(model(X_batch), y_batch)
    loss.backward()
    norma_grad = sum(
        (p.grad**2).sum().item() for p in model.parameters()
    ) ** 0.5
    print(f"lr = {lr:<6} | norma gradien {norma_grad:.4f} "
          f"| panjang langkah = lr x gradien = {lr * norma_grad:.4f}")

# ---------- 3. Lintasan loss untuk tiap learning rate ----------
print()
print("===== LINTASAN LOSS (20 epoch) =====")


def latih(lr):
    model = buat_model()
    loss_fn = nn.CrossEntropyLoss()
    optimizer = torch.optim.SGD(model.parameters(), lr=lr)
    loader = DataLoader(
        dataset,
        batch_size=32,
        shuffle=True,
        generator=torch.Generator().manual_seed(42),
    )
    for epoch in range(1, JUMLAH_EPOCH + 1):
        total_loss = 0.0
        for X_batch, y_batch in loader:
            optimizer.zero_grad()
            loss = loss_fn(model(X_batch), y_batch)
            loss.backward()
            optimizer.step()
            total_loss += loss.item()
        if epoch in EPOCH_CETAK:
            print(f"  epoch {epoch:2d} | loss = {total_loss / len(loader):.4f}")
    with torch.no_grad():
        akurasi = (model(X_t).argmax(dim=1) == y_t).float().mean().item()
    print(f"  akurasi akhir: {akurasi:.3f}")


for lr in (0.001, 0.1, 10.0):
    print(f"--- lr = {lr} ---")
    latih(lr)
    print()

Output yang Dihasilkan:

text
===== UKURAN LANGKAH PEMBARUAN PERTAMA =====
lr = 0.001  | norma gradien 0.4567 | panjang langkah = lr x gradien = 0.0005
lr = 0.1    | norma gradien 0.4567 | panjang langkah = lr x gradien = 0.0457
lr = 10.0   | norma gradien 0.4567 | panjang langkah = lr x gradien = 4.5673

===== LINTASAN LOSS (20 epoch) =====
--- lr = 0.001 ---
  epoch  1 | loss = 0.6445
  epoch  2 | loss = 0.6406
  epoch  5 | loss = 0.6353
  epoch 10 | loss = 0.6243
  epoch 15 | loss = 0.6152
  epoch 20 | loss = 0.6054
  akurasi akhir: 0.695

--- lr = 0.1 ---
  epoch  1 | loss = 0.5710
  epoch  2 | loss = 0.4788
  epoch  5 | loss = 0.3723
  epoch 10 | loss = 0.3178
  epoch 15 | loss = 0.3019
  epoch 20 | loss = 0.2900
  akurasi akhir: 0.858

--- lr = 10.0 ---
  epoch  1 | loss = 5.3115
  epoch  2 | loss = 2.7853
  epoch  5 | loss = 0.9954
  epoch 10 | loss = 1.5444
  epoch 15 | loss = 1.7597
  epoch 20 | loss = 1.5952
  akurasi akhir: 0.627

Contoh di atas diuji nyata dengan PyTorch 2.14.1+cpu pada CPU. Mari kita telaah kode di atas baris per baris:

  1. Baris 17-23 (data dan konstanta): 400 sampel make_moons, batch 32; EPOCH_CETAK memilih epoch yang dilaporkan supaya keluaran tetap ringkas.
  2. Baris 26-33 (buat_model): seed 7 dipanggil ulang tiap kali fungsi berjalan — ketiga learning rate berangkat dari bobot yang persis sama.
  3. Baris 36-44 (batch_pertama): loader dengan generator ber-seed dibuat ulang agar ketiga pengukuran memakai batch pertama yang identik — norma gradien pun pasti sama, dan memang keluaran menunjukkan 0.4567 tiga kali.
  4. Baris 49-58 (panjang langkah): gradien dihitung sekali per learning rate tanpa optimizer.step(); hasil 0.0005, 0.0457, dan 4.5673 memperlihatkan hubungan linear — learning rate 10 menghasilkan langkah 10.000 kali lebih panjang daripada learning rate 0.001.
  5. Baris 65-93 (pelatihan): fungsi latih standar; loop terakhir menjalankan ketiga learning rate pada kondisi yang sepenuhnya disamakan.

Tiga lintasan pada keluaran mewakili tiga nasib yang berbeda. Learning rate 0.001 turun terlalu lambat: 0.6445 → 0.6054 dalam 20 epoch — model nyaris tidak bergerak, akurasi akhir 0.695 masih selevel tebakan acak yang baik. Learning rate 0.1 berada di wilayah sehat: loss turun cepat ke 0.2900 dengan akurasi 0.858. Learning rate 10 justru berayun: loss jatuh ke 0.9954 pada epoch 5 lalu naik-turun (1.5444, 1.7597, 1.5952) tanpa pernah stabil — parameter memantul antar dinding lembah, dan akurasi akhirnya (0.627) bahkan di bawah kasus learning rate kecil. Perhatikan pula bahwa loss epoch 1 pada lr 10 jauh lebih tinggi (5.3115) daripada dua lainnya — langkah pertama yang terlalu panjang melempar parameter ke wilayah loss yang tinggi sejak awal.

Menemukan Nilai Awal: LR Finder #

Karena rentang sehat bisa mencakup beberapa orde besaran, teknik lr finder membantu menemukannya secara sistematis: latih sambil menaikkan learning rate secara eksponensial dari nilai sangat kecil, catat loss pada setiap nilainya, lalu pilih learning rate sedikit di bawah titik tempat loss mulai menanjak — titik sebelum langkah menjadi terlalu panjang. Kurva loss-versus-learning rate yang dihasilkannya menggantikan tebakan dengan pengukuran; perangkat serupa juga tersedia sebagai utilitas scheduler di torch.optim, dibahas lebih jauh di chapter Learning Rate Scheduling . Rujukan resmi hyperparameter optimizer, termasuk lr, tersedia di docs.pytorch.org/docs/stable/optim.html .

Learning rate mengatur panjang langkah, tetapi arah dan ritme langkah masih bisa diperbaiki. Optimizer SGD yang selama ini kita pakai dapat diberi momentum — memori arah yang membuat gradien konsisten mengayun lebih jauh dan gradien goyah saling meniadakan. Chapter berikutnya membedah optimizer pertama keluarga itu.