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:

python
"""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:

text
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?  : False

Mari kita telaah kode di atas baris per baris:

  1. Baris 17 (masukan): randn * 3 + 5 menghasilkan 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.
  2. Baris 22-23 (sumbu statistik): dim=1 adalah inti LayerNorm — mean dan variansi dihitung per baris, sehingga mean berbentuk (4, 1) dan siap di-broadcast ke enam kolom saat pengurangan.
  3. Baris 25-26 (normalisasi + affine): x_hat memakai unbiased=False agar definisi variansinya sama dengan nn.LayerNorm, dan eps menjaga pembagi tetap aman saat suatu baris bernilai seragam; gamma/beta kemudian menerapkan transformasi per kolom — kolom kedua dikalikan dua (gamma=2), kolom keempat digeser −2 (beta).
  4. Baris 28-32 (verifikasi): nn.LayerNorm(6, eps=1e-5) diberi gamma/beta yang sama lewat copy_; hasil y_torch identik dengan hitungan manual — selisih maksimum 2,4 × 10⁻⁷ murni pembulatan aritmetika floating point.
  5. 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.
  6. 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.
  7. 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 #

AspekBatchNormLayerNorm
Sumbu statistikper channel, sepanjang dimensi batchper sampel, sepanjang dimensi fitur
Bergantung batchya — mode train memakai statistik batchtidak — sampel diproses mandiri
Mode train vs evalberbeda (statistik batch vs running stats)identik — tidak ada running stats
Batch kecil / batch 1tidak andal, mode train goyahaman — normalisasi tetap valid
Kebutuhan model.eval()wajib sebelum inferensitidak mengubah hasil
Habitat utamaCNN dengan batch besartransformer, 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.