Fungsi step yang kita pakai sejauh ini berhasil memutuskan aktif atau tidaknya sebuah neuron, tetapi ia punya cacat fatal untuk pelatihan: turunannya bernilai nol di semua titik sehingga tidak memberi arah perbaikan apa pun saat bobot salah. Deep learning modern menggantinya dengan keluarga fungsi aktivasi yang mulus dan kaya perilaku — dari sigmoid yang melengkung lembut hingga ReLU yang sederhana namun menjadi standar industri. Pada chapter ini kita implementasikan enam fungsi aktivasi dengan NumPy murni, membandingkan keluarannya pada vektor yang sama, menggambar kurvanya, lalu menyusun panduan memilih fungsi yang tepat untuk hidden layer maupun output layer.

Peran Fungsi Aktivasi #

Fungsi aktivasi membawa dua sifat yang tidak dimiliki transformasi affine z = W·x + b. Pertama, non-linearitas. Tanpa fungsi aktivasi, dua layer berurutan hanya menghasilkan transformasi linear baru: karena perkalian matriks asosiatif, (X @ W1) @ W2 = X @ (W1 @ W2). Menumpuk seribu layer affine setara dengan satu layer affine — jaringan kehilangan seluruh kemampuannya membentuk batas keputusan yang melengkung, termasuk kemampuan menyelesaikan XOR pada chapter sebelumnya.

Kedua, kemulusan untuk pembelajaran. Algoritma pelatihan seperti backpropagation memakai turunan untuk mengukur pengaruh tiap bobot terhadap loss — materi chapter Kalkulus untuk Deep Learning . Fungsi step memiliki turunan nol di semua titik dan tak terdefinisi tepat di titik lompatan, sehingga sinyal perbaikan tidak pernah mengalir. Fungsi mulus seperti sigmoid dan ReLU menyediakan turunan yang bermakna hampir di semua wilayah input.

Enam Fungsi Aktivasi #

Tabel berikut merangkum keenam fungsi yang akan kita implementasikan:

FungsiRumusRentangKegunaan umum
Step1 jika z >= 0, selainnya 0{0, 1}Sejarah; model McCulloch-Pitts dan perceptron
Sigmoid1 / (1 + e^-z)(0, 1)Output binary classification; komponen gerbang pada arsitektur berulang
Tanhtanh(z)(-1, 1)Hidden layer; keluaran berpusat di nol
ReLUmax(0, z)[0, ∞)Hidden layer; standar default jaringan modern
Leaky ReLUmax(α·z, z)(-∞, ∞)Alternatif ReLU untuk menghindari dying ReLU
Softmaxe^zj / Σk e^zk(0, 1), jumlah = 1Output multi-class classification

Lima fungsi pertama bekerja elemen per elemen: satu nilai z masuk, satu nilai keluar. Softmax berbeda — ia memproses seluruh vektor sekaligus dan mengubahnya menjadi distribusi probabilitas yang jumlahnya tepat satu, sehingga hanya relevan di output layer pengklasifikasi.

Implementasi dengan NumPy #

Berkas fungsi_aktivasi.py mendefinisikan keenam fungsi sebagai fungsi Python murni di atas operasi NumPy, mengujinya pada vektor z = [-4, -1, 0, 2, 5], lalu menyimpan kurva masing-masing ke berkas aktivasi.png:

python
"""Implementasi enam fungsi aktivasi dengan NumPy murni.

Step, sigmoid, tanh, ReLU, dan Leaky ReLU diuji elemen per elemen pada
satu vektor z. Softmax mengubah seluruh vektor menjadi distribusi
probabilitas. Skrip juga menyimpan kurva tiap fungsi ke aktivasi.png.
"""

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt
import numpy as np


def step(z):
    """Step: 1 bila z >= 0, selain itu 0."""
    return (z >= 0.0).astype(float)


def sigmoid(z):
    """Sigmoid: memetakan nilai z ke rentang (0, 1)."""
    return 1.0 / (1.0 + np.exp(-z))


def tanh(z):
    """Tanh: memetakan nilai z ke rentang (-1, 1)."""
    return np.tanh(z)


def relu(z):
    """ReLU: nilai negatif dipotong menjadi nol."""
    return np.maximum(0.0, z)


def leaky_relu(z, alpha=0.1):
    """Leaky ReLU: nilai negatif dikalikan alpha agar tetap ada sinyal."""
    return np.where(z >= 0.0, z, alpha * z)


def softmax(z):
    """Softmax: seluruh vektor menjadi distribusi probabilitas."""
    e = np.exp(z - np.max(z))
    return e / e.sum()


z = np.array([-4.0, -1.0, 0.0, 2.0, 5.0])

print("Perbandingan fungsi aktivasi pada vektor z =", z)
print()
print(f"{'z':>6} {'step':>6} {'sigmoid':>8} {'tanh':>8} {'relu':>6} {'leaky':>7}")
for i in range(len(z)):
    print(f"{z[i]:6.1f} {step(z[i]):6.0f} {sigmoid(z[i]):8.4f}"
          f" {tanh(z[i]):8.4f} {relu(z[i]):6.1f} {leaky_relu(z[i]):7.2f}")

p = softmax(z)
print()
print("softmax(z) :", " ".join(f"{v:7.4f}" for v in p))
print("jumlah     :", round(p.sum(), 6))

# Bagian grafik: kurva tiap fungsi + softmax sebagai diagram batang
xs = np.linspace(-6.0, 6.0, 601)
fig, axes = plt.subplots(2, 3, figsize=(12, 6), constrained_layout=True)

axes[0, 0].plot(xs, step(xs))
axes[0, 0].set_title("Step")
axes[0, 1].plot(xs, sigmoid(xs))
axes[0, 1].set_title("Sigmoid")
axes[0, 2].plot(xs, tanh(xs))
axes[0, 2].set_title("Tanh")
axes[1, 0].plot(xs, relu(xs))
axes[1, 0].set_title("ReLU")
axes[1, 1].plot(xs, leaky_relu(xs))
axes[1, 1].set_title("Leaky ReLU (alpha=0.1)")

logits = np.array([2.0, 1.0, 0.1, -1.0])
axes[1, 2].bar(range(len(logits)), softmax(logits))
axes[1, 2].set_title("Softmax (4 kelas)")
axes[1, 2].set_xticks(range(len(logits)))

for ax in axes.flat:
    ax.axhline(0.0, color="gray", linewidth=0.6)
    ax.axvline(0.0, color="gray", linewidth=0.6)
    ax.set_xlabel("z")
    ax.set_ylabel("f(z)")

axes[1, 2].set_xlabel("indeks kelas")
axes[1, 2].set_ylabel("probabilitas")

fig.savefig("aktivasi.png", dpi=110)
print("Grafik disimpan ke aktivasi.png")

Output yang Dihasilkan:

text
Perbandingan fungsi aktivasi pada vektor z = [-4. -1.  0.  2.  5.]

     z   step  sigmoid     tanh   relu   leaky
  -4.0      0   0.0180  -0.9993    0.0   -0.40
  -1.0      0   0.2689  -0.7616    0.0   -0.10
   0.0      1   0.5000   0.0000    0.0    0.00
   2.0      1   0.8808   0.9640    2.0    2.00
   5.0      1   0.9933   0.9999    5.0    5.00

softmax(z) :  0.0001  0.0023  0.0064  0.0470  0.9442
jumlah     : 1.0
Grafik disimpan ke aktivasi.png

Kurva enam fungsi aktivasi: step, sigmoid, tanh, ReLU, Leaky ReLU, dan softmax

Beberapa perilaku menonjol dari tabel dan kurva di atas. Sigmoid dan tanh sama-sama menjepit nilai ekstrem: pada z = -4 keduanya sudah menempel di batas bawah rentangnya (0,0180 dan −0,9993), sementara di z = 5 keduanya menempel di batas atas. ReLU membiarkan nilai positif lewat tanpa diubah dan memotong seluruh nilai negatif menjadi nol; Leaky ReLU melakukan hal yang sama tetapi menyisakan bocoran kecil -0.4 dan -0.1 di wilayah negatif. Softmax mengubah kelima nilai menjadi probabilitas dengan z = 5 menguasai 94,42 persen — nilai terbesar ditonjolkan, nilai kecil ditekan, dan jumlahnya tepat satu. Mari kita telaah kode di atas baris per baris:

  1. Baris 8-13 (impor): matplotlib.use("Agg") dipanggil sebelum pyplot agar grafik tersimpan ke berkas tanpa perlu tampilan layar; NumPy menyusul setelahnya.
  2. Baris 16-18 (step): perbandingan z >= 0.0 menghasilkan array boolean yang dikonversi astype(float) menjadi 0.0/1.0.
  3. Baris 21-23 (sigmoid): terjemahan langsung rumus 1 / (1 + e^-z) dengan numpy.exp yang bekerja elemen per elemen.
  4. Baris 26-33 (tanh, relu): np.tanh adalah implementasi bawaan; ReLU cukup satu panggilan np.maximum(0.0, z) yang memilih nilai terbesar antara nol dan z.
  5. Baris 36-38 (leaky_relu): numpy.where memilih cabang per elemen — z untuk wilayah non-negatif, alpha * z untuk wilayah negatif.
  6. Baris 41-44 (softmax): pengurangan np.max(z) sebelum np.exp menjaga angka tetap kecil secara numerik — hasilnya tidak berubah karena pembilang dan penyebut terbagi konstanta yang sama.
  7. Baris 47-59 (tabel keluaran): satu loop mencetak kelima fungsi elementwise dalam kolom yang sejajar, lalu softmax dicetak terpisah bersama jumlahnya sebagai bukti distribusi.
  8. Baris 61-91 (grafik): np.linspace menyiapkan 601 titik dari −6 sampai 6, plt.subplots(2, 3) menata enam panel, dan fig.savefig("aktivasi.png", dpi=110) menuliskan hasilnya ke berkas di dalam bundle chapter ini.

Masalah Dying ReLU #

ReLU punya kelemahan yang khas: neuron yang menerima z negatif terus-menerus akan selalu mengeluarkan nol. Karena turunan ReLU di wilayah negatif juga nol, gradient yang mengalir balik ke bobot neuron tersebut selalu nol — bobotnya berhenti diperbarui selamanya meski training berlanjut. Kondisi ini disebut dying ReLU. Jaringan yang besar persentasenya neuron berhenti bereaksi kehilangan kapasitas representasi secara diam-diam; loss berhenti turun tanpa pesan galat. Penyebabnya biasanya inisialisasi bobot yang buruk atau learning rate yang terlalu besar. Leaky ReLU mengatasinya dengan menyisakan gradien kecil α di wilayah negatif — bocoran -0.4 dan -0.1 pada tabel kita itulah jalur hidupnya.

Memilih Fungsi Aktivasi #

Pilihan fungsi berbeda antara hidden layer dan output layer. Untuk hidden layer, ReLU adalah titik awal terbaik: murah dihitung, tidak menjepit nilai positif, dan terbukti stabil pada jaringan dalam. Tanh layak dipertimbangkan bila keluaran berpusat di nol membantu (misalnya arsitektur berulang), dan Leaky ReLU bila gejala dying ReLU muncul. Untuk output layer, pilihan ditentukan tugasnya:

Tugas outputFungsiAlasan
Regresi (nilai bebas)Linear (tanpa aktivasi)Nilai target tidak terikat rentang
Binary classificationSigmoidKeluaran probabilitas satu kelas
Multi-class classificationSoftmaxDistribusi probabilitas antar kelas

Kita kini punya seluruh komponen individual: neuron, bobot, bias, dan fungsi aktivasi. Chapter berikutnya merakitnya menjadi alur lengkap forward propagation — data batch mengalir dari layer ke layer hingga menjadi prediksi — dengan implementasi dua layer NumPy yang bentuk matriksnya kita periksa di setiap tahap.