Loss function memberi tahu seberapa besar kesalahan model, tetapi belum memberi tahu bagaimana memperbaikinya. Backpropagation menjembatani keduanya: algoritma ini menghitung gradien loss terhadap setiap bobot dan bias di seluruh layer dengan menjalankan aturan rantai dari loss mundur ke input — nama lengkapnya memang backward propagation of errors. Hasilnya adalah arah dan besaran langkah perbaikan untuk tiap parameter, bahan baku yang dipakai gradient descent untuk memperbarui model. Pada chapter ini kita bedah algoritma tersebut pada satu layer dengan angka konkret yang bisa diikuti dengan hitungan tangan, lalu membuktikan kebenarannya lewat verifikasi numerik independen.
Dari Loss ke Bobot: Arah Mundur #
Neural network adalah fungsi bersarang: input mengalir lewat layer demi layer hingga loss terhitung. Aturan rantai yang sudah kita bahas pada chapter Kalkulus untuk Deep Learning menyatakan bahwa turunan fungsi bersarang adalah hasil kali turunan tiap tahap. Backpropagation menerapkan aturan tersebut secara sistematis dengan dua fase:
- Forward pass — hitung dan simpan semua nilai antara (skor
z, aktivasia) dari input sampai loss. - Backward pass — mulai dari loss, kalikan gradien yang datang dari kanan dengan turunan lokal setiap operasi, lalu kirim hasilnya ke operasi di kirinya.
Kunci efisiensi backpropagation ada pada fase pertama: nilai antara yang disimpan membuat setiap turunan lokal tinggal dikalikan, bukan dihitung ulang dari nol. Satu putaran forward dan satu putaran backward menghasilkan gradien untuk semua parameter sekaligus dengan biaya komputasi yang sebanding satu forward pass. Sebagai perbandingan, metode beda hingga membutuhkan dua forward pass penuh untuk setiap parameter — untuk model dengan jutaan bobot, selisih biayanya menjadi jutaan kali lipat.
Alur Backward pada Satu Layer #
Diagram berikut memetakan satu layer penuh: dua input x, bobot W, bias b, aktivasi sigmoid, dan loss. Garis penuh adalah forward pass, garis putus-putus adalah perjalanan gradien mundur:
Perhatikan bahwa gradien tidak pernah “bercabang dari loss ke setiap bobot” secara terpisah. Gradien mengalir lewat jalur yang sama dengan data, hanya arahnya dibalik: loss memberi gradien ke aktivasi, aktivasi memberi gradien ke skor, dan skor menyebarkan gradien ke semua parameter yang membentuknya.
Contoh Konkret: Satu Layer, Dua Input, Satu Output #
Kita pakai angka yang sengaja dipilih agar setiap tahap bisa dihitung dengan tangan. Input x = [1, 2], bobot W = [0.5, −0.25], bias b = 0, dan target y = 1. Forward pass menghasilkan:
z = W·x + b = 0.5×1 + (−0.25)×2 + 0 = 0a = sigmoid(0) = 0.5loss = (a − y)² = 0.25
Backward pass lalu berjalan mundur dengan aturan rantai:
dL/da = 2(a − y) = 2 × (0.5 − 1) = −1da/dz = a(1 − a) = 0.5 × 0.5 = 0.25dL/dz = dL/da × da/dz = −1 × 0.25 = −0.25dL/dW = dL/dz × x = [−0.25×1, −0.25×2] = [−0.25, −0.5]dL/db = dL/dz × 1 = −0.25
Tanda negatif pada kedua gradien bobot berarti menaikkan W akan menurunkan loss — sinyal yang tepat, karena target 1 lebih besar dari prediksi 0.5. Berkas backprop_manual.py memprogram seluruh proses tersebut beserta verifikasinya:
"""Backpropagation pada satu layer: dua input, satu output, aktivasi sigmoid.
Forward pass dihitung lebih dulu, lalu gradien dL/dW dan dL/db diturunkan
dengan aturan rantai langkah demi langkah, dan hasilnya diverifikasi
dengan beda hingga (finite difference).
"""
import numpy as np
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
def hitung_loss(W, b):
"""Loss penuh sebagai fungsi parameter, untuk verifikasi numerik."""
a = sigmoid(np.dot(W, x) + b)
return (a - target) ** 2
x = np.array([1.0, 2.0]) # dua input
W = np.array([0.5, -0.25]) # dua bobot yang akan dihitung gradiennya
b = 0.0 # bias
target = 1.0 # keluaran yang diinginkan
# ---------- FORWARD PASS ----------
z = np.dot(W, x) + b # weighted sum
a = sigmoid(z) # aktivasi
loss = (a - target) ** 2 # MSE satu sampel
print("=== FORWARD PASS ===")
print(f"z = W.x + b : {z:.4f}")
print(f"a = sigmoid(z) : {a:.4f}")
print(f"loss = (a-y)^2 : {loss:.4f}")
# ---------- BACKWARD PASS (aturan rantai) ----------
dL_da = 2.0 * (a - target) # turunan loss thd aktivasi
da_dz = a * (1.0 - a) # turunan sigmoid
dL_dz = dL_da * da_dz # aturan rantai: hasil kali dua segmen
dL_dW = dL_dz * x # turunan z thd W[i] adalah x[i]
dL_db = dL_dz # turunan z thd b adalah 1
print("\n=== BACKWARD PASS ===")
print(f"dL/da = 2(a-y) : {dL_da:.4f}")
print(f"da/dz = a(1-a) : {da_dz:.4f}")
print(f"dL/dz = dL/da * da/dz : {dL_dz:.4f}")
print(f"dL/dW = dL/dz * x : {dL_dW}")
print(f"dL/db = dL/dz : {dL_db:.4f}")
# ---------- VERIFIKASI BEDA HINGGA ----------
print("\n=== VERIFIKASI BEDA HINGGA ===")
h = 1e-5
for i in range(2):
W_plus = W.copy()
W_minus = W.copy()
W_plus[i] += h
W_minus[i] -= h
numerik = (hitung_loss(W_plus, b) - hitung_loss(W_minus, b)) / (2 * h)
selisih = abs(numerik - dL_dW[i])
print(f"dL/dW[{i}] analitik={dL_dW[i]:+.6f} "
f"numerik={numerik:+.6f} selisih={selisih:.2e}")
numerik_b = (hitung_loss(W, b + h) - hitung_loss(W, b - h)) / (2 * h)
selisih_b = abs(numerik_b - dL_db)
print(f"dL/db analitik={dL_db:+.6f} "
f"numerik={numerik_b:+.6f} selisih={selisih_b:.2e}")Output yang Dihasilkan:
=== FORWARD PASS ===
z = W.x + b : 0.0000
a = sigmoid(z) : 0.5000
loss = (a-y)^2 : 0.2500
=== BACKWARD PASS ===
dL/da = 2(a-y) : -1.0000
da/dz = a(1-a) : 0.2500
dL/dz = dL/da * da/dz : -0.2500
dL/dW = dL/dz * x : [-0.25 -0.5 ]
dL/db = dL/dz : -0.2500
=== VERIFIKASI BEDA HINGGA ===
dL/dW[0] analitik=-0.250000 numerik=-0.250000 selisih=9.46e-12
dL/dW[1] analitik=-0.500000 numerik=-0.500000 selisih=1.89e-11
dL/db analitik=-0.250000 numerik=-0.250000 selisih=9.46e-12Seluruh angka pada keluaran cocok persis dengan hitungan tangan di atas. Bagian verifikasi membandingkan dua metode yang sepenuhnya independen: gradien analitik hasil aturan rantai versus gradien numerik dari selisih loss. Selisih terbesar hanya 1.89e-11 — jauh di bawah ambang 1e-6 — membuktikan rantai turunan kita benar. Mari kita telaah kodenya baris per baris:
- Baris 11-12 (
sigmoid): fungsi aktivasi dan sekaligus sumber turunan lokala(1 − a)yang dipakai di backward pass. - Baris 15-18 (
hitung_loss): keseluruhan loss sebagai fungsi parameter — hanya dibutuhkan oleh verifikasi numerik, bukan oleh backward pass itu sendiri. - Baris 21-24: parameter contoh; bobot
0.5dan−0.25dipilih supaya weighted sum tepat nol dan sigmoid tepat 0.5. - Baris 27-29: forward pass tiga langkah — weighted sum, aktivasi, loss — persis seperti yang kita hitung dengan tangan.
- Baris 37-41: backward pass lima baris, satu baris untuk satu segmen aturan rantai — inilah seluruh isi backpropagation pada satu layer: perkalian beruntun yang pendek.
- Baris 53-66: verifikasi beda hingga dua sisi — tiap parameter digeser
+hdan−h, loss dihitung ulang, dan kemiringannya dibandingkan dengan gradien analitik.
Dari Satu Layer ke Banyak Layer #
Pada MLP berlapis, prosedur yang sama berulang per layer. Gradien terhadap skor sebuah layer — biasa disebut delta — dikirim mundur lewat bobot layer di depannya, dikalikan turunan lokal aktivasinya, lalu dipakai untuk menghitung gradien bobot layer itu sendiri. Secara ringkas untuk layer l:
delta_l = dL/dz_ldatang dari layerl+1dL/dW_l = a_{l−1}ᵀ · delta_l(aktivasi masukan dikali delta)delta_{l−1} = (delta_l · W_lᵀ) ⊙ f'(z_{l−1})(diteruskan mundur lewat bobot dan turunan aktivasi)
Tidak ada rumus baru — hanya aturan rantai yang diterapkan berlapis-lapis dengan bobot matriks menggantikan vektor.
Backpropagation telah memberi kita gradien setiap parameter; satu langkah terakhir belum dilakukan: memakai gradien tersebut untuk benar-benar menggeser parameter. Aturan pembaruannya singkat saja — w ← w − lr × gradien — tetapi pilihan learning rate dan pola pemakaiannya menentukan nasib training. Chapter berikutnya, Gradient Descent
, membahas aturan tersebut sampai ke varian batch, mini-batch, dan stochastic.