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:
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:
"""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:
===== 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 parameterContoh di atas diuji nyata dengan PyTorch 2.14.1+cpu pada CPU. Mari kita telaah kode di atas baris per baris:
- Baris 17-20 (data): 400 sampel
make_moonsdipakai kedua optimizer dari kondisi awal yang sama. - Baris 26-33 (
buat_model): seed7per panggilan menjamin kedua optimizer menerima bobot awal identik. - Baris 36-45 (konfigurasi): kelas optimizer menjadi parameter fungsi — satu jalur kode melatih SGD dan Adam; learning rate keduanya sengaja berbeda (
0.1lawan0.01) karena Adam dengan pembagiansqrt(v)menghasilkan langkah efektif pada skala berbeda. - Baris 74-79 (membaca state): setelah pelatihan,
optimizer.statememuatstep(jumlah pembaruan,390 = 30 epoch × 13 batch),exp_avg(momen pertamam), danexp_avg_sq(momen keduav) per parameter — bentuk(16, 2)sesuai bobot layer pertama. - Baris 81-84 (menghitung langkah efektif): koreksi bias dan update dihitung ulang manual memakai rumus di atas, memverifikasi bahwa isi
optimizer.statememang komponen-komponen aturan Adam. - 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 #
| Situasi | Pilihan umum | Alasan |
|---|---|---|
| prototipe cepat, dataset kecil–menengah, arsitektur baru | Adam | butuh sedikit penyetelan learning rate |
| embedding / fitur jarang muncul (NLP, sistem rekomendasi) | Adam | koordinat langka mendapat langkah yang tetap bermakna |
| pelatihan panjang yang mengejar generalisasi maksimal (visi kompetisi) | SGD + momentum | di epoch sangat banyak, SGD kerap berakhir di solusi yang menggeneralisasi lebih baik |
| loss melonjak tidak stabil sejak awal | turunkan lr atau pakai Adam dengan lr kecil | pembagian sqrt(v) meredam koordinat bermasalah |
Varian Adam #
| Varian | Perubahan utama |
|---|---|
| AdamW | weight decay dipisahkan dari momen — pembahasan lengkap di chapter Weight Decay |
| RMSprop | hanya memakai momen kedua (pendahulu langsung Adam) |
| Adamax | memakai norma pangkat-tak-hingga untuk pembaruan skala |
| Nadam | momentum 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
.