SGD memakai satu learning rate untuk seluruh parameter, padahal parameter yang satu menerima gradien besar dan konsisten sementara yang lain menerima gradien kecil dan jarang. Dengan learning rate yang cukup besar bagi parameter kedua, parameter pertama justru melonjak tidak stabil — dilema yang diselesaikan Adam dengan menghitung ukuran langkah berbeda untuk setiap koordinat parameter. Adam kini menjadi optimizer default di banyak proyek karena perlu lebih sedikit penyetelan. Chapter ini membedah dua momen statistiknya beserta koreksi bias, mengadunya melawan SGD pada kondisi yang disamakan penuh, lalu membuktikan sifat adaptifnya dengan membaca isi optimizer.state.

Learning Rate Adaptif per Parameter #

Ide intinya satu pembagian: setiap gradien dibagi dengan akar rata-rata kuadrat gradien koordinat itu sendiri. Koordinat yang gradiennya selalu besar mendapat penyebut besar sehingga langkahnya dikecilkan; koordinat yang gradiennya kecil mendapat penyebut kecil sehingga langkahnya tetap bermakna. Rata-rata kuadrat juga dipadukan dengan rata-rata biasa (momen pertama) supaya arah langkah dihaluskan seperti momentum pada chapter Optimizer SGD .

Dua Momen dan Koreksi Bias #

Adam menyimpan dua statistik berjalan per parameter — keduanya rata-rata eksponensial yang bobot lamanya dikuasai beta:

python
m = beta1 * m + (1 - beta1) * grad      # momen pertama: rata-rata gradien
v = beta2 * v + (1 - beta2) * grad**2   # momen kedua : rata-rata kuadrat gradien
m_topi = m / (1 - beta1**t)             # koreksi bias m (m mulai dari nol)
v_topi = v / (1 - beta2**t)             # koreksi bias v
w = w - lr * m_topi / (v_topi.sqrt() + eps)

Koreksi bias menjawab masalah awal pelatihan: m dan v diinisialisasi nol sehingga pada langkah awal keduanya bias ke bawah — m seolah gradien memang kecil. Pembagi 1 - beta**t (mendekati nol saat t=1, menuju satu) mengembalikan skala statistik ke besaran sebenarnya; tanpa koreksi, langkah-langkah pertama Adam terlalu pendek. Nilai bawaan PyTorch: beta1=0.9, beta2=0.999, eps=1e-8 — nyaris selalu dibiarkan.

Contoh: Adam Melawan SGD #

Berkas adam_vs_sgd.py mengadukan SGD lr=0.1 melawan Adam lr=0.01 pada kondisi identik: make_moons 400 sampel, MLP [2, 16, 2], batch 32, 30 epoch, bobot awal sama. Bagian kedua membuka optimizer.state Adam untuk menghitung ukuran langkah efektif per koordinat:

python
"""Adam vs SGD pada kondisi yang sama, plus isi state optimizer Adam.

Bagian 1: perlombaan SGD lr=0.1 melawan Adam lr=0.01 pada make_moons
dengan batch, epoch, dan inisialisasi bobot yang identik. Bagian 2:
mengintip exp_avg, exp_avg_sq, dan step di optimizer.state, lalu
menghitung ukuran langkah efektif per koordinat parameter. 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 = 30


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


def latih(kelas_optimizer, lr):
    model = buat_model()
    loss_fn = nn.CrossEntropyLoss()
    optimizer = kelas_optimizer(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 (1, 2, 5, 10, 20, 30):
            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}")
    return model, optimizer


print("===== PERLOMBANAN: SGD lr=0.1 vs ADAM lr=0.01 =====")
print("data: make_moons 400 sampel | MLP [2, 16, 2] | batch 32 | 30 epoch")
print()
print("--- SGD (lr=0.1) ---")
model_sgd, opt_sgd = latih(torch.optim.SGD, 0.1)
print()
print("--- Adam (lr=0.01) ---")
model_adam, opt_adam = latih(torch.optim.Adam, 0.01)

# ---------- 2. Isi state Adam: exp_avg, exp_avg_sq, step ----------
print()
print("===== ISI optimizer.state PADA ADAM =====")
param_pertama = list(opt_adam.state.keys())[0]
state = opt_adam.state[param_pertama]
t = int(state["step"])
m = state["exp_avg"]          # rata-rata gradien (moment pertama)
v = state["exp_avg_sq"]       # rata-rata kuadrat gradien (moment kedua)
print(f"step t = {t} (30 epoch x 13 batch) | bentuk m dan v: {tuple(m.shape)}")

beta1, beta2, eps, lr = 0.9, 0.999, 1e-8, 0.01
m_topi = m / (1 - beta1**t)   # koreksi bias
v_topi = v / (1 - beta2**t)
langkah = lr * m_topi / (v_topi.sqrt() + eps)

print("3 koordinat pertama parameter (bobot layer pertama):")
for i in range(3):
    print(f"  m={m.flatten()[i]:+.6f} | v={v.flatten()[i]:.6e} "
          f"-> langkah efektif {langkah.flatten()[i]:+.6f}")
print("langkah efektif berbeda tiap koordinat -> lr adaptif per parameter")

Output yang Dihasilkan:

text
===== PERLOMBANAN: SGD lr=0.1 vs ADAM lr=0.01 =====
data: make_moons 400 sampel | MLP [2, 16, 2] | batch 32 | 30 epoch

--- SGD (lr=0.1) ---
  epoch  1 | loss = 0.5710
  epoch  2 | loss = 0.4788
  epoch  5 | loss = 0.3723
  epoch 10 | loss = 0.3178
  epoch 20 | loss = 0.2900
  epoch 30 | loss = 0.2844
  akurasi akhir: 0.860

--- Adam (lr=0.01) ---
  epoch  1 | loss = 0.5406
  epoch  2 | loss = 0.4289
  epoch  5 | loss = 0.3113
  epoch 10 | loss = 0.2512
  epoch 20 | loss = 0.1453
  epoch 30 | loss = 0.1131
  akurasi akhir: 0.962

===== ISI optimizer.state PADA ADAM =====
step t = 390 (30 epoch x 13 batch) | bentuk m dan v: (16, 2)
3 koordinat pertama parameter (bobot layer pertama):
  m=+0.000600 | v=2.443829e-04 -> langkah efektif +0.000218
  m=+0.013782 | v=1.147523e-04 -> langkah efektif +0.007313
  m=+0.001094 | v=3.693776e-05 -> langkah efektif +0.001023
langkah efektif berbeda tiap koordinat -> lr adaptif per parameter

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-20 (data): 400 sampel make_moons dipakai kedua optimizer dari kondisi awal yang sama.
  2. Baris 26-33 (buat_model): seed 7 per panggilan menjamin kedua optimizer menerima bobot awal identik.
  3. Baris 36-45 (konfigurasi): kelas optimizer menjadi parameter fungsi — satu jalur kode melatih SGD dan Adam; learning rate keduanya sengaja berbeda (0.1 lawan 0.01) karena Adam dengan pembagian sqrt(v) menghasilkan langkah efektif pada skala berbeda.
  4. Baris 74-79 (membaca state): setelah pelatihan, optimizer.state memuat step (jumlah pembaruan, 390 = 30 epoch × 13 batch), exp_avg (momen pertama m), dan exp_avg_sq (momen kedua v) per parameter — bentuk (16, 2) sesuai bobot layer pertama.
  5. Baris 81-84 (menghitung langkah efektif): koreksi bias dan update dihitung ulang manual memakai rumus di atas, memverifikasi bahwa isi optimizer.state memang komponen-komponen aturan Adam.
  6. Baris 86-90 (koordinat per koordinat): tiga koordinat pertama bobot dicetak lengkap dengan m, v, dan langkah efektifnya.

Pada perlombaan, Adam menyelesaikan loss sampai 0.1131 dengan akurasi 0.962, sedangkan SGD berhenti di 0.2844 dengan akurasi 0.860 pada epoch yang sama — pada dataset mini ini Adam jelas lebih cepat menuju solusi. Bagian kedua memperlihatkan alasannya: ketiga koordinat menerima langkah efektif yang berbeda (+0.000218, +0.007313, +0.001023) meskipun learning rate tunggal 0.01 — koordinat dengan gradien kecil dan konsisten (misalnya m=+0.000600, v=2.44e-04) tetap melangkah bermakna, sementara koordinat dengan v besar otomatis diredam. Inilah makna harfiah adaptive learning rate.

Kapan Adam, Kapan SGD #

SituasiPilihan umumAlasan
prototipe cepat, dataset kecil–menengah, arsitektur baruAdambutuh sedikit penyetelan learning rate
embedding / fitur jarang muncul (NLP, sistem rekomendasi)Adamkoordinat langka mendapat langkah yang tetap bermakna
pelatihan panjang yang mengejar generalisasi maksimal (visi kompetisi)SGD + momentumdi epoch sangat banyak, SGD kerap berakhir di solusi yang menggeneralisasi lebih baik
loss melonjak tidak stabil sejak awalturunkan lr atau pakai Adam dengan lr kecilpembagian sqrt(v) meredam koordinat bermasalah

Varian Adam #

VarianPerubahan utama
AdamWweight decay dipisahkan dari momen — pembahasan lengkap di chapter Weight Decay
RMSprophanya memakai momen kedua (pendahulu langsung Adam)
Adamaxmemakai norma pangkat-tak-hingga untuk pembaruan skala
Nadammomentum gaya Nesterov diterapkan pada aturan Adam

Dokumentasi resmi torch.optim.Adam: docs.pytorch.org/docs/stable/generated/torch.optim.Adam.html . Keluarga ini masih punya satu tingkat pendalaman — momen adaptif secara rinci dan perbandingan antarvarian — di chapter Momentum dan Adaptive Optimizer .