Chapter sebelumnya menutup satu persoalan: gradien backpropagation sudah terverifikasi benar hingga orde 1e-11. Tetapi gradien hanya menjawab “ke arah mana”; bab ini membahas “seberapa jauh dan seberapa sering” langkah harus diambil. Aturan pembaruan yang tampak sederhana — kurangi parameter sebanding gradiennya — menyimpan keputusan desain yang mengubah nasib pelatihan: berapa banyak data yang dipakai tiap pembaruan, seberapa besar laju belajar, dan bagaimana riwayat gradien dimanfaatkan untuk melicinkan langkah. Kita akan menyaksikan learning rate yang salah membuat optimasi merayap atau divergen, lalu melihat momentum dan metode adaptif seperti Adam yang menjadi standar praktik modern — semuanya tetap dengan NumPy murni dan angka yang bisa direproduksi.

Satu Langkah Gradient Descent #

Bentuk paling dasar dari pembelajaran jaringan adalah pembaruan berikut, diulang sampai loss berhenti turun:

text
theta_baru = theta - eta * gradien_loss(theta)

eta adalah learning rate (laju belajar) — panjang langkah di arah berlawanan gradien. Seluruh parameter jaringan diperbarui dengan aturan yang sama, memakai gradien yang dihitung backpropagation pada chapter sebelumnya. Pertanyaan pertama bukanlah rumusnya, melainkan data berapa banyak yang dipakai untuk menghitung gradien tiap kali pembaruan dilakukan.

SkemaData per PembaruanPembaruan per EpochKarakter
Batch GDseluruh data1gradien mulus, langkah jarang, mahal per pembaruan
Mini-batch SGD16–512 contohbanyakkompromi praktis, gradien sedikit bergejolak
Stochastic GD1 contohsebanyak jumlah datapembaruan sangat sering, gradien sangat bergejolak

Kata stochastic merujuk pada sumber keacakan ini: gradien satu batch kecil hanyalah perkiraan dari gradien sebenarnya. Kegejolakan tersebut bukan kekurangan — ia membantu pelatihan lolos dari titik datar dan membuat pembaruan jauh lebih murah, sehingga hampir seluruh deep learning modern berjalan mini-batch. Kegejolakan inilah yang membedakan “SGD” dari batch gradient descent penuh yang kita pakai pada dua chapter sebelumnya.

Efek Learning Rate: Merayap atau Divergen #

Learning rate terlalu kecil membuat pelatihan merayap; terlalu besar membuat parameter melompati lembah loss dan justru membesar tak terkendali. Mari kita ukur keduanya pada kuadratik cekung f(x, y) = x^2 + 10y^2 — permukaan mangkuk dengan lembah asimetris: arah y sepuluh kali lebih curam daripada arah x:

python
# Learning rate dan momentum pada kuadratik cekung f(x, y) = x^2 + 10y^2.
# Gradien = (2x, 20y): arah y sepuluh kali lebih curam daripada arah x,
# sehingga laju belajar terbesar yang stabil dibatasi oleh arah tercuram.

import numpy as np


def f(v):
    return float(v[0] ** 2 + 10.0 * v[1] ** 2)


def gradien(v):
    return np.array([2.0 * v[0], 20.0 * v[1]])


def gd_biasa(laju, mulai, iterasi_maks=3000, ambang=1e-8, batas=1e6):
    v = mulai.copy()
    for i in range(iterasi_maks):
        if np.linalg.norm(gradien(v)) < ambang:
            return i + 1, v, "konvergen"
        v = v - laju * gradien(v)
        if np.linalg.norm(v) > batas:
            return i + 1, v, "divergen"
    return iterasi_maks, v, "berhenti di batas iterasi"


def gd_momentum(laju, beta, mulai, iterasi_maks=3000, ambang=1e-8, batas=1e6):
    v = mulai.copy()
    kecepatan = np.zeros_like(v)
    for i in range(iterasi_maks):
        if np.linalg.norm(gradien(v)) < ambang:
            return i + 1, v, "konvergen"
        kecepatan = beta * kecepatan - laju * gradien(v)
        v = v + kecepatan
        if np.linalg.norm(v) > batas:
            return i + 1, v, "divergen"
    return iterasi_maks, v, "berhenti di batas iterasi"


mulai = np.array([8.0, 1.0])
print(f"titik mulai = {mulai.tolist()}, f awal = {f(mulai):.2f}")

print("\n== Gradient descent biasa pada tiga laju belajar ==")
for laju in (0.01, 0.05, 0.11):
    n, v, status = gd_biasa(laju, mulai)
    print(f"laju {laju:<5} -> {n:4d} iterasi, f akhir = {f(v):.3e} ({status})")

print("\n== GD biasa vs GD bermomentum ==")
n, v, status = gd_biasa(0.05, mulai)
print(f"biasa laju 0.050            : {n:4d} iterasi ({status})")
n, v, status = gd_momentum(0.05, 0.9, mulai)
print(f"momentum laju 0.050 b 0.90  : {n:4d} iterasi ({status})")
n, v, status = gd_momentum(0.115, 0.3, mulai)
print(f"momentum laju 0.115 b 0.30  : {n:4d} iterasi ({status})")

Output yang Dihasilkan:

text
titik mulai = [8.0, 1.0], f awal = 74.00

== Gradient descent biasa pada tiga laju belajar ==
laju 0.01  -> 1051 iterasi, f akhir = 2.404e-17 (konvergen)
laju 0.05  ->  203 iterasi, f akhir = 2.090e-17 (konvergen)
laju 0.11  ->   76 iterasi, f akhir = 1.085e+13 (divergen)

== GD biasa vs GD bermomentum ==
biasa laju 0.050            :  203 iterasi (konvergen)
momentum laju 0.050 b 0.90  :  387 iterasi (konvergen)
momentum laju 0.115 b 0.30  :   38 iterasi (konvergen)

Angka pada bagian pertama output memetakan tiga wilayah klasik learning rate:

  1. Baris 44–46 (tiga laju): laju 0,01 merayap — 1.051 iterasi karena arah datar x hanya menyusut 2% per langkah. Laju 0,05 selesai dalam 203 iterasi. Laju 0,11 divergen: pembaruan arah curam y berlawanan arah dan membesar setiap langkah, ditandai deteksi batas pada baris 22–23 dan nilai f yang membengkak ke 1,085e+13.
  2. Pelajaran asimetri: tidak ada satu laju yang pas untuk kedua arah. Laju besar yang dibutuhkan arah datar x dilarang oleh arah curam y. Masalah ill-conditioned seperti inilah bentuk awal dari lembah loss jaringan saraf sesungguhnya.

Momentum: Kecepatan yang Terakumulasi #

Momentum memperkenalkan variabel kecepatan yang mengingat arah langkah sebelumnya:

text
kecepatan_baru = beta * kecepatan - eta * gradien
theta_baru     = theta + kecepatan_baru

Komponen gradien yang konsisten antar langkah (sepanjang lembah) terakumulasi; komponen yang berganti tanda (melintasi lembah) saling meniadakan. Bola berat yang menggelinding menuruni lembah adalah analoginya — lajunya bertambah di arah lurus dan teredam saat memantul dinding. Bagian kedua output menunjukkan dua sisi fenomena ini:

  1. Momentum tanpa penyetelan (laju 0,050, beta 0,90) justru membutuhkan 387 iterasi — lebih lambat daripada GD biasa. Pada permukaan ini beta besar membuat ayunan arah y semakin lebar.
  2. Momentum dengan penyetelan (laju 0,115, beta 0,30) menyelesaikan masalah dalam 38 iterasi — sekitar 5 kali lebih cepat dari GD biasa terbaik. Nilai beta optimal bergantung pada bentuk permukaan; di praktik deep learning, beta = 0,9 adalah titik mulai lazim, dan laju belajar disetel ulang setiap kali momentum dinyalakan.

Untuk jaringan sesungguhnya, pengaruh momentum lebih mudah dibaca dari kurva loss. Demo berikut melatih MLP regresi 6-8-1 (65 parameter) dengan mini-batch SGD versus SGD bermomentum — urutan batch dibuat identik lewat generator berseed terpisah sehingga satu-satunya perbedaan adalah aturan pembaruannya:

python
# Mini-batch SGD vs SGD bermomentum pada MLP regresi 6-8-1 (tanh) murni
# NumPy. Urutan batch dibuat identik untuk kedua metode agar perbandingan
# adil: hanya aturan pembaruan bobot yang berbeda.

import copy

import numpy as np

rng = np.random.default_rng(42)

# Data sintetis: 120 contoh, 6 fitur, target nonlinear + derau.
n = 120
X = rng.uniform(-1.0, 1.0, size=(n, 6))
y = (1.5 * np.sin(1.2 * X[:, 0]) + 0.8 * X[:, 1] * X[:, 2]
     + 0.4 * X[:, 3] ** 2 - 0.3 * X[:, 4] * X[:, 5]
     + rng.normal(0.0, 0.05, n)).reshape(-1, 1)


def buat_bobot():
    W1 = rng.normal(0.0, 1.0 / np.sqrt(6), size=(6, 8))
    b1 = np.zeros(8)
    W2 = rng.normal(0.0, 1.0 / np.sqrt(8), size=(8, 1))
    b2 = np.zeros(1)
    return {"W1": W1, "b1": b1, "W2": W2, "b2": b2}


def maju(param, Xb):
    A1 = np.tanh(Xb @ param["W1"] + param["b1"])
    y_hat = A1 @ param["W2"] + param["b2"]
    return A1, y_hat


def loss_full(param):
    _, y_hat = maju(param, X)
    return float(np.mean((y_hat - y) ** 2))


def gradien_batch(param, Xb, yb):
    A1, y_hat = maju(param, Xb)
    m = len(yb)
    dZ2 = 2.0 * (y_hat - yb) / m
    dW2 = A1.T @ dZ2
    db2 = dZ2.sum(axis=0)
    dA1 = dZ2 @ param["W2"].T
    dZ1 = dA1 * (1.0 - A1 ** 2)
    dW1 = Xb.T @ dZ1
    db1 = dZ1.sum(axis=0)
    return {"W1": dW1, "b1": db1, "W2": dW2, "b2": db2}


def latih(mode, laju=0.05, beta=0.9, epoch=150, ukuran_batch=16):
    param = copy.deepcopy(bobot_awal)
    kecepatan = {nama: np.zeros_like(P) for nama, P in param.items()}
    acak = np.random.default_rng(123)   # urutan batch identik kedua metode
    riwayat = []
    for e in range(epoch):
        urutan = acak.permutation(n)
        for mula in range(0, n, ukuran_batch):
            pilih = urutan[mula:mula + ukuran_batch]
            G = gradien_batch(param, X[pilih], y[pilih])
            for nama in param:
                if mode == "sgd":
                    param[nama] -= laju * G[nama]
                else:  # momentum
                    kecepatan[nama] = beta * kecepatan[nama] - laju * G[nama]
                    param[nama] += kecepatan[nama]
        if e % 30 == 0 or e == epoch - 1:
            riwayat.append((e, loss_full(param)))
    return riwayat


bobot_awal = buat_bobot()
print(f"loss awal (bobot acak) = {loss_full(bobot_awal):.6f}")

print("\nepoch   loss_SGD   loss_momentum")
riwayat_sgd = latih("sgd")
riwayat_momentum = latih("momentum")
for (e, l_sgd), (_, l_mom) in zip(riwayat_sgd, riwayat_momentum):
    print(f"{e:5d}  {l_sgd:9.6f}  {l_mom:13.6f}")

akhir_sgd = riwayat_sgd[-1][1]
akhir_momentum = riwayat_momentum[-1][1]
print(f"\nloss akhir SGD      = {akhir_sgd:.6f}")
print(f"loss akhir momentum = {akhir_momentum:.6f}")
print(f"perbaikan           = {(1.0 - akhir_momentum / akhir_sgd) * 100:.1f}%")

Output yang Dihasilkan:

text
loss awal (bobot acak) = 0.981971

epoch   loss_SGD   loss_momentum
    0   0.622765       0.230538
   30   0.089795       0.018605
   60   0.064675       0.011250
   90   0.047600       0.008584
  120   0.039241       0.007007
  149   0.034592       0.005322

loss akhir SGD      = 0.034592
loss akhir momentum = 0.005322
perbaikan           = 84.6%

Hasilnya sepihak dan konsisten di setiap titik penyetopan:

  1. Baris 72 dan 52 (bobot_awal dan salinannya): fungsi latih memulai dari salinan bobot awal yang sama (copy.deepcopy pada baris 52), sehingga kedua metode berangkat dari titik identik.
  2. Baris 54 (acak berseed 123): generator terpisah untuk pengacakan batch dipakai oleh kedua pemanggilan — SGD dan momentum melihat urutan batch yang sama persis, memurnikan efek aturan pembaruan.
  3. Tabel loss: dengan laju dan batch yang sama, momentum mengakhiri di 0,005322 versus 0,034592 untuk SGD — perbaikan 84,6%. Derau target berdeviasi 0,05 (varians 0,0025), jadi momentum sudah mendekati lantai loss yang mungkin.

Jadwal Laju dan Metode Adaptif #

Dua penyempurnaan melengkapi gambarannya. Pertama, learning rate schedule: laju tidak harus tetap. Strategi umum menurunkannya bertahap — misalnya dibagi dua setiap beberapa epoch (step decay) atau mengikuti kurva kosinus menuju nol — karena langkah besar berguna di awal dan langkah kecil dibutuhkan saat mendekati dasar lembah. Kedua, laju adaptif per parameter: RMSProp membagi pembaruan tiap parameter dengan akar kuadrat rata-rata bergerak kuadrat gradiennya, sehingga parameter dengan gradien besar melangkah lebih kecil dan sebaliknya. Adam menggabungkan momentum dengan penskalaan RMSProp plus koreksi bias di awal pelatihan:

text
m      = beta1 * m + (1 - beta1) * g          (rata-rata bergerak gradien)
v      = beta2 * v + (1 - beta2) * g^2        (rata-rata bergerak kuadrat)
m_topi = m / (1 - beta1^t);  v_topi = v / (1 - beta2^t)
theta_baru = theta - eta * m_topi / (sqrt(v_topi) + eps)

Nilai bawaan beta1 = 0,9 dan beta2 = 0,999 bekerja baik untuk mayoritas masalah; itulah sebabnya MLPClassifier scikit-learn memakai solver adam secara default. Penyetelan tetap diperlukan pada kasus sulit — dan pemahaman Anda tentang GD biasa, momentum, serta efek laju dari chapter ini adalah bekal utamanya.

Satu karakter jaringan saraf yang belum kita sentuh kini menuntut perhatian: 65 parameter untuk 120 contoh data pada demo barusan adalah rasio yang boros. Jaringan yang lebih besar lagi menyimpan ribuan parameter, dan tanpa pengaman ia akan menghafal data latih dengan sempurna. Chapter Regularisasi Neural Network membangun pengaman tersebut: penalti bobot, dropout, dan early stopping.

Referensi Resmi #