Inisialisasi

Mengapa inisialisasi buruk mematikan pelatihan dan cara Xavier serta He menyelesaikannya.

Daftar isi

Inisialisasi adalah penetapan nilai awal bobot sebelum pelatihan dimulai, dan pilihan yang buruk pada urusan ini mampu mematikan pelatihan sebelum satu langkah pun selesai. Topik ini menjelaskan dua kegagalan klasiknya, menurunkan gagasan inisialisasi Xavier dan He, serta menunjukkan cara PyTorch menginisialisasi modulnya secara bawaan. Setelah selesai, inisialisasi berhenti menjadi ritual kosong yang disalin dari contoh dan menjadi keputusan yang berdasar.

Dua kegagalan klasik: simetri dan skala#

Kegagalan pertama adalah inisialisasi seragam, misalnya seluruh bobot bernilai nol. Neuron-neuron pada layer yang sama menerima gradien yang persis sama karena masukan dan bobotnya identik, sehingga seluruh baris matriks bobot tetap identik sepanjang pelatihan, dan layer yang berisi ratusan neuron berperilaku seperti satu neuron tunggal. Kegagalan ini disebut pemecahan simetri yang gagal, dan obatnya sederhana, yaitu mengacak bobot agar setiap neuron berangkat dari arah yang berbeda.

Kegagalan kedua adalah skala keacakan yang keliru, dan efeknya muncul seiring kedalaman. Bobot acak yang terlalu besar membuat aktivasi mengembang eksponensial antar layer hingga meluber menjadi inf, sedangkan yang terlalu kecil membuatnya menyusut hingga nyaris nol. Kode berikut menunjukkan kedua rezim itu pada tumpukan dua puluh layer tanpa aktivasi, yang gradien dan aktivasi berpindah melalui perkalian matriks berulang.

python Contoh: aktivasi meledak atau menghilang seiring kedalaman
import torch
import torch.nn as nn

torch.manual_seed(0)

def aktivasi_akhir(skala):
    layer = [nn.Linear(64, 64) for _ in range(20)]
    for l in layer:
        torch.nn.init.normal_(l.weight, mean=0.0, std=skala)
        torch.nn.init.zeros_(l.bias)
    with torch.no_grad():
        x = torch.randn(64)
        for l in layer:
            x = l(x)
        return float(x.abs().mean())

print("std 0.01 ->", f"{aktivasi_akhir(0.01):.3e}")
print("std 1.00 ->", f"{aktivasi_akhir(1.00):.3e}")
print("std 0.18 ->", f"{aktivasi_akhir(0.18):.3e}")
text Keluaran
std 0.01 -> 1.679e-22
std 1.00 -> 9.576e+17
std 0.18 -> 2.077e+03

Dengan simpangan baku 0,01 aktivasi akhir menyusut hingga orde 10−22 10^{-22} , dan dengan 1,0 ia meledak hingga orde 1017 10^{17} , padahal keduanya memakai arsitektur yang sama. Nilai 0,18 yang terlihat kecil masih berada di atas titik keseimbangan dan tetap tumbuh hingga ribuan, sedangkan titik keseimbangan untuk layer 64 unit terletak di sekitar 0,125, yaitu akar dari 1/64 1/64 . Di situlah intuisi inisialisasi terkendali berdiri, yaitu skala keacakan yang dihitung dari ukuran layer.

Xavier dan He: keseimbangan variansi#

Inisialisasi Xavier, yang juga disebut Glorot, memilih simpangan baku bobot sehingga variansi aktivasi tetap terjaga melewati layer dengan masukan berdimensi nin n_{\text{in}} dan keluaran nout n_{\text{out}} :

Var(w)=2nin+nout \text{Var}(w) = \frac{2}{n_{\text{in}} + n_{\text{out}}}

Inisialisasi He menurunkan varian yang sesuai untuk ReLU dengan mempertimbangkan bahwa separuh masukan terpotong menjadi nol:

Var(w)=2nin \text{Var}(w) = \frac{2}{n_{\text{in}}}

Prinsip keduanya sama, yaitu skala keacakan yang dihitung dari ukuran layer, bukan ditetapkan sepihak. Pemilihan mengikuti aktivasi, yaitu Xavier untuk tanh dan sigmoid, sedangkan He untuk ReLU dan keluarganya, dan jaringan modern yang memakai ReLU atau GELU hampir selalu berada di rezim He. Pada contoh di atas, 0,125 persis merupakan akar dari 1/64 1/64 , dan He memberikan 2/64≈0,177 \sqrt{2/64} \approx 0{,}177 karena ReLU memotong separuh sinyalnya.

Kode berikut membandingkan tiga inisialisasi pada tumpukan dua puluh layer dengan ReLU, dan hasilnya memperlihatkan betapa pentingnya pilihan ini bagi sinyal yang sampai ke layer akhir.

python Contoh: tiga inisialisasi pada tumpukan ber-ReLU
import torch
import torch.nn as nn

torch.manual_seed(0)

def bangun(skema):
    layer = [nn.Linear(64, 64) for _ in range(20)]
    for l in layer:
        if skema == "nol":
            torch.nn.init.zeros_(l.weight)
        elif skema == "normal":
            torch.nn.init.normal_(l.weight, std=1.0)
        else:
            torch.nn.init.kaiming_normal_(l.weight, nonlinearity="relu")
        torch.nn.init.zeros_(l.bias)
    return layer

for skema in ("nol", "normal", "kaiming"):
    layer = bangun(skema)
    x = torch.randn(32, 64)
    with torch.no_grad():
        for l in layer:
            x = torch.relu(l(x))
    print(f"{skema:8s}: |aktivasi akhir| = {float(x.abs().mean()):.5g}")
text Keluaran
nol     : |aktivasi akhir| = 0
normal  : |aktivasi akhir| = 1.5543e+14
kaiming : |aktivasi akhir| = 0.3806

Inisialisasi nol meninggalkan sinyal tepat nol karena seluruh neuron identik, normal berukuran satu meledak hingga orde 1014 10^{14} , dan kaiming mempertahankan sinyal pada skala yang tetap sehat sampai layer kedua puluh. Angka kaiming tidak tepat satu karena ReLU memotong separuh sinyal, tetapi urutan besarnya yang penting, dan inilah kondisi tempat gradien dapat mengalir mundur tanpa menghilang sejak langkah pertama.

Cara PyTorch menginisialisasi secara bawaan#

PyTorch tidak meninggalkan inisialisasi kepada keberuntungan, karena setiap modul memiliki reset_parameters() yang dijalankan saat pembangunannya. nn.Linear dan nn.Conv2d memakai varian kaiming uniform dengan perhitungan gain sesuai aktivasi, sedangkan nn.Embedding memakai distribusi normal berukuran 0,01. Pada praktiknya, bawaan ini sudah cukup untuk sebagian besar jaringan, dan menulis ulang inisialisasi secara manual hanya perlu pada kasus khusus.

Apabila skema khusus memang diperlukan, misalnya layer tertentu yang lebih lebar atau aktivasi yang berbeda, pola yang lazim adalah fungsi yang menjelajah model.modules() dan menerapkan init sesuai jenisnya, lalu dipanggil sekali sebelum pelatihan. Ritual overfit satu batch menjadi pemeriksaan alami bagi perubahan inisialisasi seperti ini, karena skala yang keliru akan menampakkan dirinya dalam ratusan langkah pertama.

Ringkasan#

  • Bobot seragam mematikan keberagaman neuron, sedangkan skala keacakan yang keliru membuat aktivasi meledak atau menghilang seiring kedalaman.
  • Xavier menyeimbangkan variansi dengan 2/(nin+nout) 2/(n_{\text{in}} + n_{\text{out}}) untuk tanh dan sigmoid, sedangkan He memakai 2/nin 2/n_{\text{in}} untuk ReLU.
  • Tumpukan ber-ReLU dengan inisialisasi nol atau normal berukuran satu mati sebelum pelatihan, sedangkan kaiming menjaga skala sinyalnya.
  • Bawaan PyTorch sudah memakai varian kaiming, sehingga inisialisasi manual hanya perlu pada kasus khusus dan diverifikasi dengan ritual satu batch.

Referensi#