Danh sách bài viết

Bài 20: Weight Initialization — Xavier và He initialization

Giá trị khởi tạo của weight quyết định liệu training có converge hay không. Init = 0 phá đối xứng (symmetry problem); naive random vẫn vanish/explode signal qua nhiều layer. Xavier (Glorot 2010) giữ variance ổn định cho tanh/sigmoid; He (Kaiming 2015) bù lại việc ReLU tắt 50% neuron. Bài này đi qua lý do, công thức, default PyTorch, API nn.init, model.apply, orthogonal cho RNN, và init cho transformer.

24/05/2026
13 phút đọc
2 lượt xem
1

Mục tiêu bài học

Sau bài học, bạn sẽ:

  • Giải thích được vì sao init = 0 làm mạng không học được (symmetry problem).
  • Hiểu mục tiêu của init tốt: giữ variance activation và gradient ổn định qua các layer.
  • Biết công thức Xavier (Glorot, 2010) và He (Kaiming, 2015), khi nào dùng cái nào.
  • Nhận diện default của nn.Linear / nn.Conv2d không phải Xavier hay He thuần.
  • Dùng được torch.nn.init và pattern model.apply(init_fn) để override init.
  • Biết orthogonal init cho RNN và sơ lược init kiểu Transformer / pre-trained.

Bài này khép lại Module 2 (PyTorch), nối Bài 19 — Dataset và DataLoader với Bài 21 — Overfitting trong Deep Learning mở đầu Module 3 (Regularization).

2

Vì sao init quyết định convergence

Loss surface của neural network không lồi. Optimizer đi từ điểm khởi tạo theo gradient, nên điểm xuất phát quyết định quỹ đạo và đích đến:

  • Init xấu → activation và gradient vanish/explode ngay từ epoch đầu → loss đứng yên hoặc NaN.
  • Init tốt → gradient có scale hợp lý → SGD/Adam đi mượt → converge nhanh, ổn định.

Vấn đề càng nặng khi mạng càng sâu. Với 2-3 layer, init "tạm được" vẫn chạy. Với 50+ layer (ResNet) hoặc 96 layer (Transformer GPT-2 large), một hằng số nhân sai 1.1 lần ở mỗi layer cũng đủ làm signal blow up theo cấp số nhân.

Lịch sử: pre-2010, deep learning bị nghẽn nhiều năm một phần vì init kém. Glorot & Bengio (2010)He et al. (2015) là hai paper giải phóng được thực hành train mạng sâu mà không cần pre-training từng layer kiểu Hinton 2006.

3

Init bằng 0 — symmetry problem

Đây là sai lầm kinh điển: không bao giờ init weight bằng 0. Lý do gọi là symmetry problem.

Xét một layer có hai neuron cùng nhận input \( \mathbf{x} \). Nếu \( W = \mathbf{0} \), output của hai neuron giống nhau (đều bằng 0 sau activation phù hợp). Khi backward:

\[ \frac{\partial L}{\partial w_{1j}} = \frac{\partial L}{\partial w_{2j}} \]

nên hai neuron nhận update giống hệt nhau. Sau bao nhiêu step thì hai neuron vẫn bằng nhau — về bản chất mạng chỉ học được như có một neuron mỗi layer. Đối xứng giữa neuron không bao giờ bị phá vỡ.

Cũng đúng với bất kỳ hằng số chung nào (init tất cả bằng 0.1, 1.0, ...): symmetry không bị phá. Phải dùng random để mỗi neuron khởi tạo khác nhau, từ đó nhận gradient khác nhau, từ đó học feature khác nhau.

Ngoại lệ: bias init bằng 0 hoàn toàn OK. Vì bias không nhân với input — mỗi neuron đã có \( W \) random đủ để phá đối xứng, bias = 0 chỉ giúp loss surface đối xứng quanh gốc, dễ optimize.

4

Naive random small — vẫn chưa đủ

Pre-2010 nhiều code khởi tạo weight kiểu:

\[ w \sim \mathcal{N}(0, \sigma^2), \quad \sigma = 0.01 \]

Trông có vẻ ổn (nhỏ, random, đối xứng quanh 0). Nhưng:

  • Layer \( l \) có \( n_l \) input. Pre-activation \( z = \sum_{i=1}^{n_l} w_i x_i \). Với weight i.i.d. variance \( \sigma^2 \) và input variance \( v \), pre-activation có variance \( n_l \sigma^2 v \).
  • Nếu \( n_l \sigma^2 < 1 \): variance giảm ở mỗi layer → activation tiến về 0 → vanishing (sigmoid/tanh derivative ~0 tại 0... ngược lại, derivative max — nhưng activation chính nó vanish).
  • Nếu \( n_l \sigma^2 > 1 \): variance tăng → activation explode → sigmoid/tanh saturate → derivative ~0 → gradient vanish ở backward.

Cùng vấn đề trên backward: gradient ngược qua layer có cùng phép nhân ma trận, scale theo \( n_l \sigma^2 \). Sau \( L \) layer, gradient bị scale \( (n_l \sigma^2)^L \) — số mũ này phá mọi thứ.

Kết luận: \( \sigma = 0.01 \) cố định không scale theo số input của layer. Đó là lỗ hổng mà Xavier và He vá lại.

5

Mục tiêu của init tốt

Yêu cầu: chọn variance \( \sigma^2 \) sao cho variance của activation không thay đổi đáng kể từ layer \( l \) sang layer \( l+1 \), và đồng thời variance của gradient cũng giữ ổn định khi lan ngược.

Hai điều kiện:

\[ \mathrm{Var}(a^{(l+1)}) = \mathrm{Var}(a^{(l)}), \quad \mathrm{Var}\!\left(\frac{\partial L}{\partial a^{(l)}}\right) = \mathrm{Var}\!\left(\frac{\partial L}{\partial a^{(l+1)}}\right) \]

Để forward giữ variance: cần \( n_{\text{in}} \cdot \mathrm{Var}(w) = 1 \), tức \( \mathrm{Var}(w) = 1 / n_{\text{in}} \). Đây là "LeCun init" (LeCun et al., 1998).

Để backward giữ variance: cần \( n_{\text{out}} \cdot \mathrm{Var}(w) = 1 \), tức \( \mathrm{Var}(w) = 1 / n_{\text{out}} \).

Hai điều kiện mâu thuẫn khi \( n_{\text{in}} \neq n_{\text{out}} \). Glorot & Bengio thoả hiệp bằng trung bình điều hoà — đó là Xavier.

6

Xavier (Glorot) Initialization

Glorot & Bengio (2010) đề xuất compromise giữa hai điều kiện forward / backward bằng cách lấy trung bình của \( n_{\text{in}} \) và \( n_{\text{out}} \):

\[ \mathrm{Var}(w) = \frac{2}{n_{\text{in}} + n_{\text{out}}} \]

Hai dạng tương đương cùng variance:

Normal:

\[ w \sim \mathcal{N}\!\left(0, \frac{2}{n_{\text{in}} + n_{\text{out}}}\right) \]

Uniform:

\[ w \sim \mathcal{U}\!\left(-\sqrt{\frac{6}{n_{\text{in}} + n_{\text{out}}}}, \sqrt{\frac{6}{n_{\text{in}} + n_{\text{out}}}}\right) \]

(Variance của \( \mathcal{U}(-a, a) \) là \( a^2/3 \); thay \( a = \sqrt{6/(n_{\text{in}}+n_{\text{out}})} \) ra đúng \( 2/(n_{\text{in}}+n_{\text{out}}) \).)

Trong đó \( n_{\text{in}} \) là fan-in (số input của neuron, = số cột của ma trận \( W \)), \( n_{\text{out}} \) là fan-out (số neuron của layer, = số hàng).

Assumption khi dẫn: activation tuyến tính hoặc gần tuyến tính quanh 0 — đúng với tanhsigmoid (sigmoid quanh 0 có slope ~0.25, dẫn được biến thể tương ứng).

Paper gốc: "Understanding the difficulty of training deep feedforward neural networks" — AISTATS 2010.

7

He (Kaiming) Initialization

Xavier giả định activation gần tuyến tính. ReLU không phải vậy: nó tắt mọi giá trị âm. Trung bình một nửa neuron có output = 0, kéo variance activation xuống một nửa.

He et al. (2015) phân tích lại với assumption ReLU và đề xuất tăng variance lên gấp đôi để bù:

\[ \mathrm{Var}(w) = \frac{2}{n_{\text{in}}} \]

Normal:

\[ w \sim \mathcal{N}\!\left(0, \frac{2}{n_{\text{in}}}\right) \]

Uniform tương đương:

\[ w \sim \mathcal{U}\!\left(-\sqrt{\frac{6}{n_{\text{in}}}}, \sqrt{\frac{6}{n_{\text{in}}}}\right) \]

Hệ số \( 2 \) trên tử là điểm khác biệt: Xavier có 2 trên \( n_{\text{in}}+n_{\text{out}} \), He có 2 trên chỉ \( n_{\text{in}} \). Với layer vuông \( n_{\text{in}} = n_{\text{out}} \), variance của He gấp đôi Xavier.

Paper gốc: "Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification" — ICCV 2015. Cùng paper giới thiệu PReLU.

He cũng có biến thể fan-out mode (\( 2/n_{\text{out}} \)) — PyTorch cho chọn qua tham số mode. Mặc định fan_in tốt cho forward stability; fan_out ưu tiên backward stability.

8

Khi nào dùng Xavier, khi nào He

  • Xavier (Glorot): cho activation đối xứng quanh 0 và gần tuyến tínhtanh, sigmoid (mặc dù sigmoid không centered, vẫn dùng được vì derivative quanh 0 ổn).
  • He (Kaiming): cho activation kiểu rectifierReLU, LeakyReLU, PReLU, ELU, GELU.
  • Mặc định hiện nay: hầu hết kiến trúc dùng ReLU hoặc biến thể → He là default thực hành. Xavier vẫn xuất hiện trong RNN gate (sigmoid/tanh) và một số module có activation cụ thể.

Quy tắc kiểm tra nhanh: nếu layer theo sau là ReLU-like, dùng He. Nếu là tanh/sigmoid, dùng Xavier. Khi không chắc — He là lựa chọn an toàn cho phần lớn case.

9

Bias initialization

Bias đơn giản hơn weight:

  • Default: bias = 0. Đúng cho phần lớn layer dense, conv, attention output projection.
  • LSTM forget gate bias = 1: Jozefowicz et al. (2015) chỉ ra rằng init forget bias = 1 (thay vì 0) giữ memory mặc định ở thời điểm đầu train, giúp gradient lan tốt hơn qua chuỗi dài. PyTorch nn.LSTM mặc định bias = 0 cho cả 4 gate — cần override thủ công nếu muốn theo paper.
  • Bias cho ReLU đôi khi init bằng số dương nhỏ (0.01) để giảm "dying ReLU" — nhưng thực hành hiện đại không cần khi đã dùng He init.
10

Default của PyTorch — không phải Xavier hay He

Một điểm dễ nhầm lẫn: PyTorch không default Xavier hay He cho nn.Linearnn.Conv2d. Source code thực tế:

# torch/nn/modules/linear.py (rút gọn)
def reset_parameters(self):
    nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5))
    if self.bias is not None:
        fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight)
        bound = 1 / math.sqrt(fan_in) if fan_in > 0 else 0
        nn.init.uniform_(self.bias, -bound, bound)

Phân tích:

  • Weight: kaiming_uniform_ với a=sqrt(5). Đây là không chính là He init thông thường (vốn dùng a=0 cho ReLU). Tham số a là negative slope của leaky ReLU; a=sqrt(5) tương đương leaky ReLU rất xuôi, kết quả gain ≈ 1/3 — gần với LeCun init hơn He.
  • Bias: uniform trong \( [-1/\sqrt{n_{\text{in}}}, +1/\sqrt{n_{\text{in}}}] \) chứ không phải 0.
  • Lý do là legacy: copy từ Torch7 (Lua) từ 2014, giữ lại để không phá backward-compatible.

Hệ quả thực tế: PyTorch default thường chạy được nhưng không tối ưu. Với mạng sâu (ResNet, Transformer), nên explicitly override bằng He hoặc init phù hợp paper gốc.

Tham khảo: PyTorch Issue #15314 thảo luận về default này — kết luận là giữ legacy nhưng khuyến cáo override khi cần.

11

nn.init module

torch.nn.init cung cấp các hàm in-place (kết thúc bằng _) để override init:

import torch.nn as nn

layer = nn.Linear(784, 128)

# Xavier
nn.init.xavier_uniform_(layer.weight)
nn.init.xavier_normal_(layer.weight)

# He / Kaiming
nn.init.kaiming_uniform_(layer.weight, mode="fan_in", nonlinearity="relu")
nn.init.kaiming_normal_(layer.weight, mode="fan_in", nonlinearity="relu")

# Bias
nn.init.zeros_(layer.bias)
nn.init.constant_(layer.bias, 0.01)

# Cơ bản
nn.init.normal_(layer.weight, mean=0.0, std=0.02)
nn.init.uniform_(layer.weight, a=-0.1, b=0.1)

Tham số quan trọng:

  • mode: "fan_in" (mặc định, ổn forward) hoặc "fan_out" (ổn backward).
  • nonlinearity: "relu", "leaky_relu", "tanh", "sigmoid", "linear"... Quyết định gain nhân vào variance (xem nn.init.calculate_gain).
  • a: negative slope cho leaky relu (chỉ dùng khi nonlinearity="leaky_relu").

Quy ước: hàm có hậu tố _ = in-place (ghi vào tensor sẵn). Không trả về tensor mới, không cần gán.

12

Custom init qua model.apply

Pattern chuẩn để init toàn bộ model một cách nhất quán: định nghĩa hàm xử lý từng nn.Module rồi gọi model.apply(fn) — PyTorch recurse qua tất cả submodule.

import torch.nn as nn

def init_weights(m):
    if isinstance(m, nn.Linear):
        nn.init.kaiming_normal_(m.weight, nonlinearity="relu")
        if m.bias is not None:
            nn.init.zeros_(m.bias)
    elif isinstance(m, nn.Conv2d):
        nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu")
        if m.bias is not None:
            nn.init.zeros_(m.bias)
    elif isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d)):
        nn.init.ones_(m.weight)
        nn.init.zeros_(m.bias)

model = nn.Sequential(
    nn.Linear(784, 256), nn.ReLU(),
    nn.Linear(256, 128), nn.ReLU(),
    nn.Linear(128, 10),
)
model.apply(init_weights)

Quy tắc:

  • apply(fn) gọi fn(m) cho từng submodule (kể cả nested) và cuối cùng cho chính model. Thứ tự: post-order traversal.
  • isinstance check để chỉ apply init đúng kiểu layer. Bỏ qua container như nn.Sequential (không có weight để init).
  • BatchNorm: weight (scale \( \gamma \)) init = 1, bias (shift \( \beta \)) init = 0 — đây là default đúng của BN, nhưng cần khai báo lại nếu hàm bao quát.
  • Conv2d thường dùng mode="fan_out" vì paper He original (cho VGG / ResNet) chứng minh tốt hơn cho conv.
13

Orthogonal init cho RNN

RNN lan trạng thái ẩn qua nhiều bước thời gian, lặp nhân với cùng ma trận \( W_{hh} \). Nếu \( \|W_{hh}\| > 1 \) → explode; \( \|W_{hh}\| < 1 \) → vanish (vấn đề cốt lõi sẽ học ở Module 5).

Orthogonal init giúp: dùng ma trận trực giao \( W \) với \( W^\top W = I \), mọi singular value bằng 1. Phép nhân lặp giữ norm vector không đổi → giảm vanishing/exploding cho RNN sâu.

import torch.nn as nn

rnn = nn.RNN(input_size=64, hidden_size=128)
for name, param in rnn.named_parameters():
    if "weight_hh" in name:          # ma trận hidden-to-hidden
        nn.init.orthogonal_(param)
    elif "weight_ih" in name:        # input-to-hidden
        nn.init.xavier_uniform_(param)
    elif "bias" in name:
        nn.init.zeros_(param)

Saxe et al. (2013) — "Exact solutions to the nonlinear dynamics of learning in deep linear neural networks" — là paper nền cho orthogonal init.

Thực hành: PyTorch nn.LSTM / nn.GRU không default orthogonal — phải override qua named_parameters() hoặc apply.

14

Init trong Transformer

Transformer (Vaswani et al., 2017) và LLM hiện đại có scheme init riêng vì kiến trúc đặc biệt (attention, residual, layer norm). Tóm tắt:

  • GPT-style: weight \( \mathcal{N}(0, 0.02^2) \) cho linear/embedding, thêm scale theo độ sâu cho output projection của block: chia thêm cho \( \sqrt{2 L} \) với \( L \) là số layer (giúp activation không blow up theo residual).
  • Post-norm vs Pre-norm: pre-norm (layer norm trước attention/MLP, như GPT-2) ổn định hơn về gradient — relax yêu cầu init careful. Post-norm cần warmup learning rate dài.
  • Embedding: init từ normal nhỏ (std 0.02 cho GPT, hoặc fan-in scale cho T5).
  • LayerNorm: \( \gamma = 1 \), \( \beta = 0 \) (default đúng).

Bài học cho người dùng: với LLM sẵn có (LLaMA, GPT, Mistral), không bao giờ init from scratch — luôn load pretrained weight. Init scheme nội bộ chỉ cần biết khi training from scratch hoặc thiết kế kiến trúc mới.

15

Pre-trained init (preview Transfer Learning)

Init tốt nhất không phải Xavier hay He — mà là weight đã train sẵn trên dataset lớn. Đây là transfer learning: copy weight từ ResNet đã train trên ImageNet, BERT đã train trên Wikipedia, làm điểm xuất phát cho task mới.

  • Khởi điểm tốt hơn random nhiều: model đã học feature low-level (cạnh, texture cho ảnh; ngữ pháp, từ vựng cho text).
  • Fine-tune nhanh hơn train from scratch — vài epoch là đủ thay vì hàng trăm.
  • Default cho computer vision (ImageNet pretrained), NLP (BERT/GPT pretrained), và phần lớn pipeline production hiện đại.

Bài Bài 32 — Transfer Learning trong Module 4 sẽ đi sâu vào cơ chế và code. Ở Module 2 này, chỉ cần ghi nhận: với torchvision.models.resnet18(weights="IMAGENET1K_V1"), weight đã được init tối ưu sẵn — không cần model.apply(init_weights) nữa.

16

Visualization activation distribution

Cách trực quan kiểm tra init: forward một batch dữ liệu qua model chưa train, plot histogram activation của mỗi layer:

  • Init tốt: histogram của các layer trông tương tự nhau, variance giữ ổn định, không tập trung tại 0 hay tại bound saturate.
  • Init quá nhỏ: activation collapse về 0 từ layer thứ 3-4. Histogram là một spike tại 0.
  • Init quá lớn: activation saturate (tanh đẩy về \( \pm 1 \), sigmoid về 0 hoặc 1). Histogram là hai cụm ở rìa.
import torch
import matplotlib.pyplot as plt

def collect_activations(model, x):
    acts = []
    h = x
    for layer in model:
        h = layer(h)
        if isinstance(layer, torch.nn.Linear):
            acts.append(h.detach().flatten().cpu().numpy())
    return acts

x = torch.randn(256, 784)
acts = collect_activations(model, x)
fig, axes = plt.subplots(1, len(acts), figsize=(3 * len(acts), 3))
for i, a in enumerate(acts):
    axes[i].hist(a, bins=50)
    axes[i].set_title(f"Layer {i+1}")
plt.show()

Đây là kỹ thuật chẩn đoán nhanh nhất khi loss đứng yên hoặc NaN ở epoch đầu.

17

Common bug

  • Init weight bằng 0: như mục 3 — mạng không học gì, mọi neuron đối xứng. Symptom: loss đứng yên hoặc giảm cực chậm, accuracy ngang random.
  • Quên override default PyTorch: code chạy nhưng converge chậm hơn cần thiết. Với mạng <5 layer thường không thấy rõ; với ResNet/Transformer sâu, khác biệt rõ rệt — nên explicit init.
  • Init quá lớn: \( \sigma = 1.0 \) cho weight → activation explode → loss NaN ngay batch đầu. Symptom: nan hoặc inf trong loss / gradient.
  • Quên init bias khi viết custom init function: bias giữ giá trị PyTorch default (uniform thay vì 0). Thường không gây lỗi to nhưng làm khó reproduce.
  • Dùng Xavier với ReLU: chạy được nhưng converge chậm hơn He, đặc biệt với mạng sâu. Ngược lại dùng He với tanh cũng OK nhưng activation hơi to.
  • Init layer đã load pretrained: model.apply(init_weights) sau khi load_state_dict() sẽ ghi đè pretrained — bug hay gặp khi viết transfer learning pipeline. Thứ tự đúng: init trước, load sau.
18

Code Python tổng hợp

So sánh 3 init scheme trên cùng MLP với dữ liệu giả lập (binary classification):

import torch
import torch.nn as nn
import torch.nn.functional as F

torch.manual_seed(42)

# Dữ liệu giả: 2 class trên 50 chiều
X = torch.randn(1024, 50)
y = (X[:, 0] + X[:, 1] - X[:, 2] > 0).long()

def make_model():
    return nn.Sequential(
        nn.Linear(50, 128), nn.ReLU(),
        nn.Linear(128, 128), nn.ReLU(),
        nn.Linear(128, 128), nn.ReLU(),
        nn.Linear(128, 2),
    )

def init_zero(m):
    if isinstance(m, nn.Linear):
        nn.init.zeros_(m.weight)
        nn.init.zeros_(m.bias)

def init_naive(m):
    if isinstance(m, nn.Linear):
        nn.init.normal_(m.weight, mean=0.0, std=0.01)
        nn.init.zeros_(m.bias)

def init_he(m):
    if isinstance(m, nn.Linear):
        nn.init.kaiming_normal_(m.weight, nonlinearity="relu")
        nn.init.zeros_(m.bias)

def train(init_fn, name, epochs=50):
    model = make_model()
    model.apply(init_fn)
    opt = torch.optim.SGD(model.parameters(), lr=0.05)
    losses = []
    for _ in range(epochs):
        logits = model(X)
        loss = F.cross_entropy(logits, y)
        opt.zero_grad()
        loss.backward()
        opt.step()
        losses.append(loss.item())
    acc = (model(X).argmax(dim=1) == y).float().mean().item()
    print(f"{name:<10}  loss {losses[0]:.3f} -> {losses[-1]:.3f}  acc={acc:.3f}")
    return losses

l_zero  = train(init_zero,  "zero")
l_naive = train(init_naive, "naive")
l_he    = train(init_he,    "he")

Output điển hình:

zero       loss 0.693 -> 0.693  acc=0.512   # mạng không học
naive      loss 0.693 -> 0.690  acc=0.530   # học rất chậm
he         loss 0.693 -> 0.123  acc=0.962   # converge tốt

Rõ ràng:

  • Zero init: loss đứng yên ở \( \ln 2 \approx 0.693 \) — accuracy = 50% là random guess.
  • Naive small: loss giảm rất chậm vì signal vanish qua 3 hidden layer.
  • He init: converge nhanh, accuracy cao.

Apply He init qua model.apply cho MLP thực tế:

def init_weights(m):
    if isinstance(m, nn.Linear):
        nn.init.kaiming_normal_(m.weight, mode="fan_in", nonlinearity="relu")
        if m.bias is not None:
            nn.init.zeros_(m.bias)

model = nn.Sequential(
    nn.Linear(784, 256), nn.ReLU(),
    nn.Linear(256, 128), nn.ReLU(),
    nn.Linear(128, 10),
)
model.apply(init_weights)

# Verify variance hợp lý
for name, p in model.named_parameters():
    if "weight" in name:
        print(f"{name}  shape={tuple(p.shape)}  std={p.std().item():.4f}")

Standard deviation kỳ vọng cho He: \( \sqrt{2/n_{\text{in}}} \). Ví dụ Linear(784, 256) có \( n_{\text{in}} = 784 \), \( \sqrt{2/784} \approx 0.0505 \) — std in ra phải xấp xỉ giá trị này.

19

Bài tập

  1. Build MLP 784 → 256 → 128 → 64 → 10 với ReLU. Train 30 epoch trên MNIST với ba init scheme: zero, Xavier, He. So sánh loss curve và accuracy. Verify rằng zero không học được.
  2. Cho layer = nn.Linear(128, 64). Tính tay fan_in, fan_out, và std kỳ vọng nếu init bằng He normal mode fan_in. Đối chiếu với nn.init._calculate_fan_in_and_fan_out(layer.weight).
  3. Áp orthogonal init cho nn.RNN(64, 128): chỉ ma trận weight_hh dùng orthogonal, weight_ih dùng Xavier, bias = 0. In weight_hh @ weight_hh.T để kiểm tra xấp xỉ \( I \).
  4. Forward một batch random qua MLP 5 layer với hai init (naive \( \sigma = 0.01 \) và He). Plot histogram activation mỗi layer. Quan sát layer nào collapse và layer nào giữ variance.
  5. Pitfall reproduce: viết hàm init_weights chỉ override weight (quên bias), apply lên nn.Linear. In bias trước và sau apply — bias giữ default PyTorch (uniform), khác 0.
  6. So sánh default PyTorch (kaiming_uniform_ với a=sqrt(5)) và He thuần (kaiming_normal_ với nonlinearity="relu") trên MLP 10 layer + ReLU. Xác nhận default chạy chậm hơn He thuần khi mạng đủ sâu.
  7. Thử init weight với \( \sigma = 1.0 \) trên MLP 5 layer. Forward một batch và quan sát loss đầu tiên — thường là nan hoặc số rất lớn. Giải thích.
Đáp án ngắn
  1. Zero: accuracy ~10% (random với 10 class). Xavier: chạy được nhưng converge chậm hơn He khi mạng dùng ReLU. He: converge nhanh, accuracy thường >97% sau 30 epoch.
  2. \( n_{\text{in}} = 128 \), \( n_{\text{out}} = 64 \). Std = \( \sqrt{2/128} \approx 0.125 \). PyTorch trả fan_in=128, fan_out=64.
  3. weight_hh @ weight_hh.T xấp xỉ \( I_{128} \) — diagonal ~1, off-diagonal ~0.
  4. Naive: activation từ layer 2-3 trở đi collapse về 0. He: histogram tương tự nhau qua các layer.
  5. Bias mặc định của nn.Linear(in, out) là uniform trong \( [-1/\sqrt{in}, +1/\sqrt{in}] \) — không phải 0.
  6. Mạng sâu với a=sqrt(5) có gain ~1/3 → activation shrink nhanh → train chậm. He thuần với gain \( \sqrt{2} \) giữ variance tốt hơn.
  7. \( \sigma = 1.0 \) → variance pre-activation \( = n_{\text{in}} \cdot 1 = 128 \) cho layer đầu → activation cực lớn → ReLU truyền tiếp → loss bùng nổ thành inf/nan sau vài layer.
20

Tổng kết Module 2 — PyTorch

Hết Module 2. Chín bài trong module này phủ đủ skill để build và train một neural network end-to-end:

Tới đây bạn đã có công cụ để train neural network thật trên dữ liệu thật. Cái còn thiếu là kỹ thuật kiểm soát overfittingscale training — đó là nội dung Module 3 (B21–B26): overfitting, dropout, batch normalization, early stopping, learning rate scheduling, data augmentation.

Bài kế tiếp: Bài 21 — Overfitting trong Deep Learning — dấu hiệu nhận biết train/val loss divergence, vì sao mạng sâu dễ overfit, và đặt nền cho các kỹ thuật regularization sẽ học sau.