Chapter sebelumnya menutup dengan dua luka BatchNorm: statistik batch goyah saat ukuran batch kecil, dan keluaran train bergantung pada siapa teman satu batch. Layer Normalization (Ba, Kiros, dan Hinton, 2016) menjahit kedua luka itu dengan satu perubahan sederhana pada sumbu: statistik dihitung dari dimensi fitur pada satu sampel, bukan dari dimensi batch. Konsekuensinya menjulur jauh — normalisasi bekerja identik baik pada batch 512, batch 1, bahkan saat panjang urutan berubah-ubah — dan itulah alasan LayerNorm menjadi komponen baku setiap transformer modern seperti yang akan kita bangun di section Attention dan Transformer
. Chapter ini menghitung LayerNorm secara manual sampai cocok dengan nn.LayerNorm, membuktikan kemandiriannya dari batch, lalu menutup dengan tabel perbandingan melawan BatchNorm.
Normalisasi Per Sampel, Per Layer #
Untuk tensor aktivasi (N, D), LayerNorm menghitung mean dan variance sepanjang dimensi fitur (dim=1) untuk setiap baris sampel secara terpisah. Setiap baris dinormalkan agar rata-ratanya 0 dan variansinya 1, lalu dikembalikan ke bentuk yang paling berguna bagi jaringan lewat transformasi affine dengan parameter learnable gamma (pengali, ln.weight) dan beta (penambah, ln.bias) — sama seperti BatchNorm, hanya sumbu statistiknya yang berbeda. Karena tidak ada satu pun nilai dari sampel lain yang ikut dihitung, ukuran dan isi batch menjadi tidak relevan.
Berkas layernorm_manual.py menuntun kita dari rumus mentah sampai verifikasi: bagian pertama menghitung normalisasi manual dan membandingkannya dengan nn.LayerNorm yang diberi gamma/beta yang sama; bagian kedua memproses satu sampel dalam dua susunan batch berbeda dan memanggil BatchNorm sebagai pembanding yang justru bergantung batch:
"""LayerNorm manual vs nn.LayerNorm, plus bukti bebas ketergantungan batch.
Bagian 1: hitung normalisasi per baris (per sampel) secara manual
dengan mean dan variansi sepanjang dimensi fitur, lalu bandingkan
dengan nn.LayerNorm yang diberi gamma dan beta yang sama.
Bagian 2: satu sampel menghasilkan output identik apa pun susunan
batch yang menemaninya — perilaku yang berlawanan dengan BatchNorm
mode train pada chapter sebelumnya.
"""
import torch
import torch.nn as nn
torch.manual_seed(42)
# ---------- 1. Perhitungan manual ----------
x = torch.randn(4, 6) * 3.0 + 5.0 # 4 sampel, 6 fitur
gamma = torch.tensor([1.0, 2.0, 0.5, 1.0, 1.0, 1.0])
beta = torch.tensor([0.0, 0.0, 0.0, -2.0, 0.0, 0.0])
mean = x.mean(dim=1, keepdim=True) # statistik per baris
var = x.var(dim=1, unbiased=False, keepdim=True)
eps = 1e-5
x_hat = (x - mean) / torch.sqrt(var + eps) # normalisasi per baris
y_manual = x_hat * gamma + beta # transformasi affine
ln = nn.LayerNorm(6, eps=eps)
with torch.no_grad():
ln.weight.copy_(gamma)
ln.bias.copy_(beta)
y_torch = ln(x)
print("x (4 sampel x 6 fitur):")
for baris in x.tolist():
print(" ", [round(v, 2) for v in baris])
print()
print("output manual (gamma * x_hat + beta):")
for baris in y_manual.tolist():
print(" ", [round(v, 3) for v in baris])
print()
print("statistik x_hat (sebelum affine), per baris:")
print(" mean :", [round(v + 0.0, 4) for v in x_hat.mean(dim=1).tolist()])
print(" std :", [round(v, 4)
for v in x_hat.std(dim=1, unbiased=False).tolist()])
print(" mean kolom x_hat (boleh apa pun):",
[round(v, 3) for v in x_hat.mean(dim=0).tolist()])
print("manual vs nn.LayerNorm identik?",
torch.allclose(y_manual, y_torch, atol=1e-6))
print("selisih maksimum:",
(y_manual - y_torch).abs().max().item())
# ---------- 2. Bebas ketergantungan batch ----------
s = x[2:3] # sampel ketiga, diambil utuh
teman_acak = torch.randn(3, 6) * 10.0
batch_lain = torch.cat([s, teman_acak])
out_sendiri = ln(s) # sendirian
out_batch = ln(batch_lain) # ditemani 3 sampel acak
print()
print("sampel ketiga diproses sendiri vs di dalam batch:")
print(" sendirian :", [round(v, 3) for v in out_sendiri[0].tolist()])
print(" di batch :", [round(v, 3) for v in out_batch[0].tolist()])
print(" identik? :", torch.allclose(out_sendiri, out_batch[0:1]))
# Pembanding: BatchNorm1d mode train justru bergantung batch
bn = nn.BatchNorm1d(6)
bn_out_a = bn(torch.cat([s, torch.zeros(1, 6)]))[0] # 1 teman nol
bn_out_b = bn(batch_lain)[0] # 3 teman bernilai besar
print("BatchNorm train — sampel sama, dua susunan batch:")
print(" teman nol :", [round(v, 3) for v in bn_out_a.tolist()])
print(" teman acak:", [round(v, 3) for v in bn_out_b.tolist()])
print(" identik? :", torch.allclose(bn_out_a, bn_out_b))Output yang Dihasilkan:
x (4 sampel x 6 fitur):
[10.78, 9.46, 7.7, -1.32, 7.04, 1.3]
[4.87, 0.19, 6.07, 2.94, 3.52, 5.72]
[1.67, 5.27, -1.95, 4.35, 4.07, 3.81]
[7.41, 3.14, 3.22, 4.81, 2.51, 5.99]
output manual (gamma * x_hat + beta):
[1.135, 1.666, 0.215, -3.637, 0.277, -1.038]
[0.495, -3.712, 0.548, -2.474, -0.183, 0.923]
[-0.498, 1.991, -0.998, -1.388, 0.497, 0.39]
[1.661, -1.583, -0.37, -1.83, -1.148, 0.848]
statistik x_hat (sebelum affine), per baris:
mean : [-0.0, -0.0, -0.0, -0.0]
std : [1.0, 1.0, 1.0, 1.0]
mean kolom x_hat (boleh apa pun): [0.698, -0.205, -0.303, -0.332, -0.139, 0.281]
manual vs nn.LayerNorm identik? True
selisih maksimum: 2.384185791015625e-07
sampel ketiga diproses sendiri vs di dalam batch:
sendirian : [-0.498, 1.991, -0.998, -1.388, 0.497, 0.39]
di batch : [-0.498, 1.991, -0.998, -1.388, 0.497, 0.39]
identik? : True
BatchNorm train — sampel sama, dua susunan batch:
teman nol : [1.0, 1.0, -1.0, 1.0, 1.0, 1.0]
teman acak: [-0.879, 0.704, -0.857, 0.443, 0.878, -0.157]
identik? : FalseMari kita telaah kode di atas baris per baris:
- Baris 17 (masukan):
randn * 3 + 5menghasilkan empat baris dengan rata-rata sekitar 5 dan sebaran lebar — kondisi yang layak dinormalkan; tanpa normalisasi, baris seperti ini masuk ke fungsi aktivasi di wilayah jenuh. - Baris 22-23 (sumbu statistik):
dim=1adalah inti LayerNorm — mean dan variansi dihitung per baris, sehinggameanberbentuk(4, 1)dan siap di-broadcast ke enam kolom saat pengurangan. - Baris 25-26 (normalisasi + affine):
x_hatmemakaiunbiased=Falseagar definisi variansinya sama dengannn.LayerNorm, danepsmenjaga pembagi tetap aman saat suatu baris bernilai seragam;gamma/betakemudian menerapkan transformasi per kolom — kolom kedua dikalikan dua (gamma=2), kolom keempat digeser −2 (beta). - Baris 28-32 (verifikasi):
nn.LayerNorm(6, eps=1e-5)diberigamma/betayang sama lewatcopy_; hasily_torchidentik dengan hitungan manual — selisih maksimum 2,4 × 10⁻⁷ murni pembulatan aritmetika floating point. - Baris 42-47 (arah normalisasi): per baris, mean 0 dan std 1 — tetapi rata-rata per kolom boleh berapa pun (
[0.698, −0.205, ...]). LayerNorm tidak menjamin keseimbangan antar channel, hanya kesehatan tiap sampel. - Baris 54-58 (uji kemandirian batch): sampel ketiga diproses sendirian dan bersama tiga sampel acak bernilai besar; kedua hasil identik — tidak ada nilai dari teman batch yang menyentuh perhitungan.
- Baris 67-73 (pembanding BatchNorm): sampel yang sama diproses BatchNorm mode train dengan dua susunan teman; hasilnya berbeda total (
[1.0, 1.0, ...]vs[−0.879, 0.704, ...]) — kebalikan persis dari LayerNorm.
BatchNorm vs LayerNorm #
| Aspek | BatchNorm | LayerNorm |
|---|---|---|
| Sumbu statistik | per channel, sepanjang dimensi batch | per sampel, sepanjang dimensi fitur |
| Bergantung batch | ya — mode train memakai statistik batch | tidak — sampel diproses mandiri |
| Mode train vs eval | berbeda (statistik batch vs running stats) | identik — tidak ada running stats |
| Batch kecil / batch 1 | tidak andal, mode train goyah | aman — normalisasi tetap valid |
Kebutuhan model.eval() | wajib sebelum inferensi | tidak mengubah hasil |
| Habitat utama | CNN dengan batch besar | transformer, RNN, arsitektur sekuens |
Pilihan antar keduanya kini punya dasar yang jelas. Convolutional network dengan batch puluhan-ratusan sampel menikmati korelasi statistik antar sampel yang BatchNorm manfaatkan. Arsitektur sekuens — transformer dengan panjang kalimat berbeda-beda, RNN yang memproses langkah demi langkah — membutuhkan normalisasi yang setia pada satu sampel; di sanalah LayerNorm berkuasa, dan hampir seluruh model bahasa modern memasangnya setelah tiap sublayer attention dan feed-forward.
Dokumentasi resmi nn.LayerNorm — termasuk aturan bentuk normalized_shape dan cara gamma/beta diinisialisasi — tersedia di docs.pytorch.org/docs/stable/generated/torch.nn.LayerNorm.html
. Sampai di sini rantai optimasi kita sudah menjangkau arah langkah (optimizer), besar langkah (scheduler), keamanan langkah (clipping), titik berangkat (inisialisasi), dan kesehatan sinyal (normalisasi). Satu dimensi praktis tersisa: bagaimana melipatgandakan kecepatan dan memangkas konsumsi memori dengan menukar — secara terkendali — sedikit presisi angka. Chapter penutup section ini membahas mixed precision training.