Framework deep learning seperti PyTorch dan TensorFlow menyimpan tensornya dengan cara yang sama seperti ndarray, dan operasi inti jaringan saraf — perkalian matriks, penjumlahan bias, pemetaan per elemen — adalah operasi NumPy yang sudah kita kenal. Artinya, mekanisme di balik jaringan saraf bisa kita buka tanpa memasang framework apa pun (perbandingan antarmukanya dibahas pada Chapter NumPy dan PyTorch).

Kita akan membangun jaringan kecil untuk klasifikasi pembacaan sensor: empat fitur masukan, satu layer tersembunyi berisi lima neuron dengan aktivasi ReLU, dan tiga kelas keluaran yang dipetakan softmax menjadi peluang. Fokus bab ini adalah forward pass — mengalirkan data sampai menjadi peluang — plus satu langkah gradien backpropagation.

Merancang Bentuk Matriks Jaringan #

Jaringan didefinisikan sepenuhnya oleh bentuk bobotnya. Dengan batch dua sampel dan empat fitur, semua bentuk sudah tertentu sebelum satu angka pun dihitung:

ObjekPeranBentuk
Xbatch masukan (2 sampel, 4 fitur)(2, 4)
W1, b1bobot dan bias layer tersembunyi(4, 5), (5,)
W2, b2bobot dan bias layer keluaran(5, 3), (3,)
Ylabel kelas dalam bentuk one-hot(2, 3)

Aturan menghubungkan layer: jumlah kolom bobot harus sama dengan jumlah fitur masukannya, sehingga X @ W1 sah. Hasil sementaranya: z1 berbentuk (2, 5), dan setelah layer kedua peluang berbentuk (2, 3).

Forward Pass ke Layer Tersembunyi #

Langkah pertama: kalikan masukan dengan bobot, tambahkan bias, lalu terapkan aktivasi ReLU yang mengganti nilai negatif menjadi nol lewat np.maximum:

python
import numpy as np

rng = np.random.default_rng(0)
X = np.array([
    [ 0.5, -1.2,  0.8,  1.5],
    [-0.3,  0.4, -0.6, -1.1],
])
W1 = rng.normal(0.0, 0.5, size=(4, 5))
b1 = np.zeros(5)
z1 = X @ W1 + b1
a1 = np.maximum(z1, 0.0)
print("bentuk z1:", z1.shape)
print(z1.round(3))
print(a1.round(3))

Output yang Dihasilkan:

text
bentuk z1: (2, 5)
[[-0.984 -1.207 -1.575  0.67   0.909]
 [ 0.643  0.568  0.965 -0.317 -0.372]]
[[0.    0.    0.    0.67  0.909]
 [0.643 0.568 0.965 0.    0.   ]]

Mari kita bedah:

  1. Baris 3-9 (rng, X, dan bobot): bobot diambil dari default_rng ber-seed agar hasil bab ini dapat direproduksi (pola dari Chapter default_rng dan Generator); masukan kita anggap sudah dinormalisasi seperti latihan bab sebelumnya.
  2. Baris 10 (X @ W1 + b1): perkalian matriks menghasilkan (2, 5), lalu bias b1 berbentuk (5,) menyebar ke setiap baris batch berkat broadcasting.
  3. Baris 11 (np.maximum(z1, 0.0)): ReLU memotong semua nilai negatif menjadi 0.0; bandingkan baris pertama z1 dengan a1 — tiga neuron negatif mati, dua sisanya lolos.
  4. Baris 12 (z1.shape): bentuk (2, 5) mengonfirmasi setiap sampel kini direpresentasikan lima neuron.

Softmax yang Aman dari Overflow #

Layer keluaran klasifikasi mengubah skor mentah (logit) menjadi peluang dengan softmax. Rumusnya memakai np.exp, dan di situlah jebakannya: exp dari angka besar melampaui jangkauan float dan menghasilkan inf, sehingga inf / inf menjadi nan. Solusi standarnya mengurangkan nilai maksimum sebelum eksponensiasi — hasil matematisnya tidak berubah:

python
import numpy as np

z = np.array([1000.0, 1001.0, 1002.0])
with np.errstate(over="ignore", invalid="ignore"):
    naif = np.exp(z) / np.exp(z).sum()
print("softmax naif  :", naif)
z_aman = z - z.max()
stabil = np.exp(z_aman) / np.exp(z_aman).sum()
print("softmax stabil:", stabil.round(4))

Output yang Dihasilkan:

text
softmax naif  : [nan nan nan]
softmax stabil: [0.09   0.2447 0.6652]
  1. Baris 4-6: softmax naif menghitung exp(1000.0) yang meluap menjadi inf; pembagian inf / inf menghasilkan nan untuk semua elemen. np.errstate hanya meredam peringatannya.
  2. Baris 7 (z - z.max()): logit digeser ke bawah sehingga nilai terbesarnya nol; exp kini bekerja pada rentang [-2, 0] yang aman.
  3. Baris 8-9: hasil stabil berupa peluang yang berjumlah 1.0 — dan nilainya sama persis dengan softmax naif yang seharusnya, hanya saja dihitung tanpa meluap.

Forward Lengkap dan Gradien Backpropagation #

Kini kita rangkai semuanya: dua layer forward, softmax, lalu satu langkah backpropagation. Untuk loss cross-entropy, gradien pada logit keluaran berbentuk sederhana: probs - Y (sudah dibagi ukuran batch), dan gradien bobot diperoleh lewat aturan rantai:

python
import numpy as np

rng = np.random.default_rng(0)
X = np.array([[ 0.5, -1.2,  0.8,  1.5],
              [-0.3,  0.4, -0.6, -1.1]])
Y = np.eye(3)[np.array([0, 2])]
W1, b1 = rng.normal(0.0, 0.5, size=(4, 5)), np.zeros(5)
W2, b2 = rng.normal(0.0, 0.5, size=(5, 3)), np.zeros(3)
a1 = np.maximum(X @ W1 + b1, 0.0)
z2 = a1 @ W2 + b2
probs = np.exp(z2 - z2.max(axis=1, keepdims=True))
probs /= probs.sum(axis=1, keepdims=True)
print("probs :", probs.round(4))
print("kelas :", probs.argmax(axis=1))
dz2 = (probs - Y) / X.shape[0]
dW2 = a1.T @ dz2
db2 = dz2.sum(axis=0)
print("dW2   :", dW2.round(4))
print("db2   :", db2.round(4))

Output yang Dihasilkan:

text
probs : [[0.3405 0.31   0.3495]
 [0.2752 0.4777 0.2471]]
kelas : [2 1]
dW2   : [[ 0.0885  0.1536 -0.2421]
 [ 0.0781  0.1356 -0.2137]
 [ 0.1328  0.2304 -0.3632]
 [-0.2208  0.1038  0.117 ]
 [-0.2997  0.1409  0.1588]]
db2   : [-0.1922  0.3939 -0.2017]

Bedah baris demi baris:

  1. Baris 8 (Y = np.eye(3)[...]): label kelas 0 dan 2 diubah menjadi matriks one-hot (2, 3) dengan fancy indexing pada matriks identitas.
  2. Baris 9-14 (forward): baris-baris yang sama dengan dua bagian sebelumnya — ReLU, perkalian matriks, lalu softmax stabil dengan keepdims=True agar pemengurangan max tetap broadcast per baris.
  3. Baris 15-16 (prediksi): argmax(axis=1) memilih kelas berpeluang terbesar per sampel; model awal (bobot acak) menebak [2 1], yang belum tentu benar karena belum dilatih.
  4. Baris 17-19 (gradien): dz2 = (probs - Y) / X.shape[0] menakar seberapa jauh peluang meleset dari label (sudah dibagi ukuran batch); dW2 = a1.T @ dz2 meneruskan sinyal itu ke bobot, dan db2 menjumlahkan error per kelas.

Loop pelatihan yang lengkap hanya mengulang pola ini: forward, hitung gradien, lalu perbarui W2 -= lr * dW2 dan seterusnya, ribuan kali. Kelebihan framework seperti PyTorch adalah autograd — gradien seperti dW2 dihitung otomatis dari definisi forward, termasuk untuk arsitektur raksasa — dan eksekusi di GPU (dibandingkan pada Chapter NumPy dan TensorFlow). NumPy tidak menyediakan keduanya, tetapi versi manual kita menunjukkan bahwa tidak ada yang tersembunyi: semua komponennya adalah perkalian matriks dan penjumlahan bias.