Memahami Backpropagation di Neural Network
Pendahuluan
Pernahkah kamu bertanya-tanya bagaimana sebuah neural network bisa “belajar” dari data? Di balik kemampuan itu ada satu algoritma krusial yang bekerja keras setiap kali training berjalan: backpropagation.
Backpropagation adalah jantung dari proses pembelajaran supervised pada neural network. Tanpa algoritma ini, network hanya akan menebak secara acak tanpa pernah membaik. Jika kamu sudah familiar dengan konsep Apa itu Machine Learning? Penjelasan Lengkap untuk Pemula, artikel ini akan membawamu selangkah lebih dalam ke mekanisme yang membuat model neural network bisa dioptimasi.
Di artikel ini kita akan membedah backpropagation dari intuisi, matematika, sampai implementasi kode yang bisa langsung kamu jalankan.
Konsep Dasar Neural Network dan Gradient Descent
Sebelum masuk ke backpropagation, kita perlu paham dua fondasi utamanya.
Neural Network terdiri dari layer-layer neuron. Setiap neuron menerima input, mengalikannya dengan bobot (weight), menambahkan bias, lalu melewatkan hasilnya melalui fungsi aktivasi.
Gradient Descent adalah teknik optimasi yang digunakan untuk meminimalkan loss function — ukuran seberapa jauh prediksi model dari nilai sebenarnya. Caranya dengan menggerakkan weight ke arah yang menurunkan loss.
import numpy as np
# Contoh sederhana: satu neuron dengan satu input
def forward(x, w, b):
return w * x + b
def loss_mse(y_pred, y_true):
return 0.5 * (y_pred - y_true) ** 2
# Misal: prediksi = 3, target = 5
y_pred = 3.0
y_true = 5.0
print(f"Loss: {loss_mse(y_pred, y_true)}") # Loss: 2.0
Gradient descent membutuhkan turunan (gradient) dari loss terhadap setiap weight. Di sinilah backpropagation berperan.
Intuisi dan Mekanisme Backpropagation
Bayangkan kamu sedang bermain telepon berantai. Pesan kesalahan dikirim dari orang terakhir (output) mundur ke orang pertama (input), dan setiap orang menyesuaikan cara mereka berbicara berdasarkan seberapa besar kesalahan yang mereka sebabkan.
Itulah inti backpropagation:
- Forward pass — input mengalir maju, menghasilkan prediksi
- Hitung loss — bandingkan prediksi dengan label sebenarnya
- Backward pass — error mengalir mundur, setiap weight mendapat gradiennya
- Update weight — kurangi weight proporsional dengan gradiennya (gradient descent)
Proses ini berulang ribuan hingga jutaan kali hingga loss konvergen ke nilai minimum.
Matematika di Balik Backpropagation (Secara Bertahap)
Kunci matematika backpropagation adalah chain rule dari kalkulus:
$$\frac{\partial L}{\partial w} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z} \cdot \frac{\partial z}{\partial w}$$
Di mana:
L= lossŷ= output setelah aktivasiz= pre-activation (w·x + b)w= weight yang ingin diupdate
Mari kita implementasikan dari nol dengan Python murni (tanpa library ML):
import numpy as np
# Fungsi aktivasi sigmoid dan turunannya
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def sigmoid_derivative(z):
s = sigmoid(z)
return s * (1 - s)
# Loss: Binary Cross-Entropy
def bce_loss(y_pred, y_true):
eps = 1e-9 # hindari log(0)
return -np.mean(y_true * np.log(y_pred + eps) + (1 - y_true) * np.log(1 - y_pred + eps))
# ========================
# Neural Network 2-layer sederhana: Input(2) -> Hidden(3) -> Output(1)
# ========================
np.random.seed(42)
# Inisialisasi weight
W1 = np.random.randn(2, 3) * 0.1 # input→hidden
b1 = np.zeros((1, 3))
W2 = np.random.randn(3, 1) * 0.1 # hidden→output
b2 = np.zeros((1, 1))
# Data contoh: XOR problem
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([[0],[1],[1],[0]])
learning_rate = 0.5
for epoch in range(10000):
# ---- FORWARD PASS ----
z1 = X @ W1 + b1 # (4,3)
a1 = sigmoid(z1) # aktivasi hidden
z2 = a1 @ W2 + b2 # (4,1)
a2 = sigmoid(z2) # output (prediksi)
# ---- HITUNG LOSS ----
loss = bce_loss(a2, y)
# ---- BACKWARD PASS ----
# Gradient loss terhadap a2
dL_da2 = (a2 - y) / len(y)
# Gradient melalui sigmoid di layer output
dL_dz2 = dL_da2 * sigmoid_derivative(z2) # chain rule
# Gradient W2 dan b2
dW2 = a1.T @ dL_dz2
db2 = np.sum(dL_dz2, axis=0, keepdims=True)
# Propagasi ke layer 1
dL_da1 = dL_dz2 @ W2.T
dL_dz1 = dL_da1 * sigmoid_derivative(z1)
# Gradient W1 dan b1
dW1 = X.T @ dL_dz1
db1 = np.sum(dL_dz1, axis=0, keepdims=True)
# ---- UPDATE WEIGHT (Gradient Descent) ----
W2 -= learning_rate * dW2
b2 -= learning_rate * db2
W1 -= learning_rate * dW1
b1 -= learning_rate * db1
if epoch % 2000 == 0:
print(f"Epoch {epoch:5d} | Loss: {loss:.4f}")
print("\nPrediksi akhir (mendekati 0/1):")
print(np.round(a2, 3))
Output yang diharapkan:
Epoch 0 | Loss: 0.6931
Epoch 2000 | Loss: 0.1823
Epoch 4000 | Loss: 0.0521
Epoch 6000 | Loss: 0.0302
Epoch 8000 | Loss: 0.0213
Prediksi akhir (mendekati 0/1):
[[0.028]
[0.972]
[0.972]
[0.028]]
Perhatikan bagaimana setiap gradient dihitung dari layer output mundur ke layer pertama — inilah esensi “backward propagation of errors”.
Contoh Kasus Nyata
Sekarang kita lihat implementasi yang lebih praktis menggunakan PyTorch, framework yang umum dipakai di industri:
import torch
import torch.nn as nn
import torch.optim as optim
# Data XOR dengan PyTorch tensor
X = torch.tensor([[0,0],[0,1],[1,0],[1,1]], dtype=torch.float32)
y = torch.tensor([[0],[1],[1],[0]], dtype=torch.float32)
# Definisi model
class SimpleNet(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(2, 4)
self.fc2 = nn.Linear(4, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = torch.relu(self.fc1(x)) # ReLU di hidden layer
x = self.sigmoid(self.fc2(x)) # Sigmoid di output
return x
model = SimpleNet()
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)
# Training loop — PyTorch menangani backprop otomatis
for epoch in range(5000):
optimizer.zero_grad() # reset gradient sebelumnya
y_pred = model(X)
loss = criterion(y_pred, y)
loss.backward() # ← backpropagation terjadi di sini!
optimizer.step() # update weight
if epoch % 1000 == 0:
print(f"Epoch {epoch:5d} | Loss: {loss.item():.4f}")
print("\nPrediksi:")
with torch.no_grad():
print(torch.round(model(X), decimals=2))
Perlu dicatat bahwa kompleksitas komputasi backpropagation bergantung pada kedalaman dan lebar network. Memahami konsep ini berkaitan erat dengan Mengenal Big O Notation: Cara Mengukur Efisiensi Algoritma — semakin dalam network, semakin banyak operasi matrix yang harus dihitung.
Bayangkan kamu ingin membangun sistem rekomendasi seperti yang digunakan platform e-commerce besar — sistem itu menggunakan network jauh lebih besar, tapi prinsip backpropagation-nya sama persis dengan yang kita implementasikan di atas.
Troubleshooting: Error yang Sering Muncul
Gradient Vanishing: Loss Tidak Berubah di Epoch Awal
Penyebab: Fungsi aktivasi sigmoid/tanh menekan gradient mendekati nol saat nilai sangat besar atau sangat kecil. Di network dalam, gradient yang sudah kecil semakin mengecil saat dipropagasi ke belakang.
Solusi:
import torch
import torch.nn as nn
# Ganti sigmoid dengan ReLU di hidden layer
class BetterNet(nn.Module):
def __init__(self):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(128, 256),
nn.ReLU(), # ← ReLU tidak squash gradient
nn.Linear(256, 256),
nn.ReLU(),
nn.Linear(256, 10),
)
def forward(self, x):
return self.layers(x)
# Gunakan weight initialization yang tepat (He initialization)
def init_weights(m):
if isinstance(m, nn.Linear):
nn.init.kaiming_uniform_(m.weight, nonlinearity='relu')
nn.init.zeros_(m.bias)
model = BetterNet()
model.apply(init_weights)
print("Model siap digunakan dengan He initialization.")
Loss Menjadi NaN Setelah Beberapa Epoch
Penyebab: Learning rate terlalu besar menyebabkan gradient explode — nilai weight melompat ekstrem hingga overflow menjadi NaN.
Solusi:
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.utils as utils
# Buat model dan data dummy untuk demonstrasi
model = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
criterion = nn.MSELoss()
X = torch.randn(32, 4)
y = torch.randn(32, 1)
optimizer = optim.Adam(model.parameters(), lr=0.001) # turunkan lr
epochs = 100
for epoch in range(epochs):
optimizer.zero_grad()
output = model(X)
loss = criterion(output, y)
loss.backward()
# Gradient clipping: batasi maksimum norm gradient
utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
# Deteksi NaN lebih awal
if torch.isnan(loss):
print(f"NaN terdeteksi di epoch {epoch}, hentikan training!")
break
if epoch % 20 == 0:
print(f"Epoch {epoch:3d} | Loss: {loss.item():.4f}")
RuntimeError: Trying to backward through the graph a second time
Penyebab: PyTorch secara default menghapus computational graph setelah satu kali .backward(). Memanggil .backward() dua kali tanpa retain_graph=True akan menyebabkan error ini.
Solusi:
import torch
import torch.nn as nn
import torch.optim as optim
model = nn.Linear(3, 1)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
X = torch.randn(10, 3)
y = torch.randn(10, 1)
epochs = 5
for epoch in range(epochs):
optimizer.zero_grad() # ← WAJIB ada di setiap iterasi
output = model(X)
loss = criterion(output, y)
loss.backward() # hanya dipanggil sekali per iterasi
optimizer.step()
print(f"Epoch {epoch} | Loss: {loss.item():.4f}")
# Jika memang perlu backward dua kali (jarang terjadi):
# loss_utama.backward(retain_graph=True) # pertama, graph dipertahankan
# loss_aux.backward() # kedua, graph dihapus
Pertanyaan yang Sering Diajukan
Apa perbedaan backpropagation dengan gradient descent?
Keduanya berbeda tapi saling melengkapi. Gradient descent adalah algoritma optimasi yang menentukan cara mengupdate weight menggunakan gradient. Backpropagation adalah metode menghitung gradient itu sendiri secara efisien menggunakan chain rule. Gradient descent tidak bisa berjalan tanpa gradient, dan backpropagation menyediakan gradient tersebut.
Mengapa kita perlu fungsi aktivasi non-linear?
Tanpa aktivasi non-linear, tumpukan layer linear berapa pun dalamnya tetap setara dengan satu layer linear. Fungsi aktivasi seperti ReLU, sigmoid, atau tanh memungkinkan network mempelajari pola non-linear yang kompleks — seperti membedakan gambar kucing dari anjing atau mendeteksi sentimen dari teks.
Bagaimana cara kerja loss.backward() di PyTorch?
PyTorch menggunakan autograd — setiap operasi tensor direkam dalam computational graph. Saat .backward() dipanggil, PyTorch menelusuri graph tersebut dari node loss mundur ke setiap leaf tensor (weight), menghitung gradient dengan chain rule secara otomatis. Kamu tidak perlu menghitung turunan secara manual seperti di implementasi from-scratch kita sebelumnya.
Apakah backpropagation selalu konvergen ke solusi optimal?
Tidak selalu. Backpropagation dengan gradient descent bisa terjebak di local minimum atau saddle point, terutama untuk loss landscape yang kompleks. Teknik seperti momentum, Adam optimizer, learning rate scheduling, dan batch normalization membantu mengatasi ini. Dalam praktiknya, untuk model besar, bahkan local minimum yang “cukup baik” sudah memberikan performa luar biasa.
Berapa banyak epoch yang dibutuhkan untuk training?
Tidak ada angka pasti — tergantung kompleksitas data, arsitektur model, dan learning rate. Gunakan teknik early stopping: hentikan training saat validation loss tidak membaik selama N epoch berturut-turut, bukan setelah jumlah epoch tetap.
import torch
import torch.nn as nn
import torch.optim as optim
# Simulasi early stopping
model = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
optimizer = optim.Adam(model.parameters(), lr=0.01)
criterion = nn.MSELoss()
X_train = torch.randn(64, 4)
y_train = torch.randn(64, 1)
X_val = torch.randn(16, 4)
y_val = torch.randn(16, 1)
best_loss = float('inf')
patience = 10
patience_counter = 0
max_epochs = 200
for epoch in range(max_epochs):
# Training step
model.train()
optimizer.zero_grad()
loss = criterion(model(X_train), y_train)
loss.backward()
optimizer.step()
# Validasi
model.eval()
with torch.no_grad():
val_loss = criterion(model(X_val), y_val).item()
if val_loss < best_loss:
best_loss = val_loss
patience_counter = 0
torch.save(model.state_dict(), 'best_model.pt')
else:
patience_counter += 1
if patience_counter >= patience:
print(f"Early stopping di epoch {epoch} | Best val loss: {best_loss:.4f}")
break
if epoch % 50 == 0:
print(f"Epoch {epoch:3d} | Train Loss: {loss.item():.4f} | Val Loss: {val_loss:.4f}")
Kesimpulan
Backpropagation adalah fondasi dari hampir semua model deep learning modern. Dengan memahami alur forward pass → hitung loss → backward pass → update weight, kamu kini punya gambaran jelas tentang bagaimana neural network benar-benar “belajar”. Kita juga sudah melihat implementasi dari nol dengan NumPy untuk memahami matematika di baliknya, serta versi praktis dengan PyTorch yang digunakan di industri.
Langkah selanjutnya, coba eksplorasi variasi optimizer seperti Adam dan RMSprop, serta teknik regularisasi seperti Dropout dan Batch Normalization untuk membuat training lebih stabil.
Selamat belajar dan terus bereksperimen — semakin banyak model yang kamu latih, semakin dalam intuisimu terhadap backpropagation! Jika ada pertanyaan atau kamu ingin menggali lebih jauh, KamusNgoding masih banyak artikel seru yang menanti untuk dijelajahi.