Mục lục
- Mục tiêu bài học
- Vì sao init quyết định convergence
- Init bằng 0 — symmetry problem
- Naive random small — vẫn chưa đủ
- Mục tiêu của init tốt
- Xavier (Glorot) Initialization
- He (Kaiming) Initialization
- Khi nào dùng Xavier, khi nào He
- Bias initialization
- Default của PyTorch — không phải Xavier hay He
- nn.init module
- Custom init qua model.apply
- Orthogonal init cho RNN
- Init trong Transformer
- Pre-trained init (preview Transfer Learning)
- Visualization activation distribution
- Common bug
- Code Python tổng hợp
- Bài tập
- Tổng kết Module 2 — PyTorch
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.Conv2dkhông phải Xavier hay He thuần. - Dùng được
torch.nn.initvà patternmodel.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).
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) và 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.
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.
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.
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.
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 tanh và sigmoid (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.
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.
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ính —
tanh,sigmoid(mặc dù sigmoid không centered, vẫn dùng được vì derivative quanh 0 ổn). - He (Kaiming): cho activation kiểu rectifier —
ReLU,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.
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.LSTMmặ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.
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.Linear và nn.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ớia=sqrt(5). Đây là không chính là He init thông thường (vốn dùnga=0cho ReLU). Tham sốalà 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.
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 (xemnn.init.calculate_gain).a: negative slope cho leaky relu (chỉ dùng khinonlinearity="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.
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ọifn(m)cho từng submodule (kể cả nested) và cuối cùng cho chính model. Thứ tự: post-order traversal.isinstancecheck để 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.
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.
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.
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.
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.
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
NaNngay batch đầu. Symptom:nanhoặcinftrong 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 khiload_state_dict()sẽ ghi đè pretrained — bug hay gặp khi viết transfer learning pipeline. Thứ tự đúng: init trước, load sau.
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.
Bài tập
- Build MLP
784 → 256 → 128 → 64 → 10vớ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. - Cho
layer = nn.Linear(128, 64). Tính tayfan_in,fan_out, và std kỳ vọng nếu init bằng He normal modefan_in. Đối chiếu vớinn.init._calculate_fan_in_and_fan_out(layer.weight). - Áp orthogonal init cho
nn.RNN(64, 128): chỉ ma trậnweight_hhdùng orthogonal,weight_ihdùng Xavier, bias = 0. Inweight_hh @ weight_hh.Tđể kiểm tra xấp xỉ \( I \). - 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.
- Pitfall reproduce: viết hàm
init_weightschỉ override weight (quên bias), apply lênnn.Linear. Inbiastrước và sauapply— bias giữ default PyTorch (uniform), khác 0. - So sánh default PyTorch (
kaiming_uniform_vớia=sqrt(5)) và He thuần (kaiming_normal_vớinonlinearity="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. - 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à
nanhoặc số rất lớn. Giải thích.
Đáp án ngắn
- 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.
- \( n_{\text{in}} = 128 \), \( n_{\text{out}} = 64 \). Std = \( \sqrt{2/128} \approx 0.125 \). PyTorch trả
fan_in=128,fan_out=64. weight_hh @ weight_hh.Txấp xỉ \( I_{128} \) — diagonal ~1, off-diagonal ~0.- Naive: activation từ layer 2-3 trở đi collapse về 0. He: histogram tương tự nhau qua các layer.
- Bias mặc định của
nn.Linear(in, out)là uniform trong \( [-1/\sqrt{in}, +1/\sqrt{in}] \) — không phải 0. - 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. - \( \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/nansau vài layer.
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:
- B12 — PyTorch vs TensorFlow: chọn framework, lý do PyTorch dominant trong research.
- B13 — Tensor: data structure cốt lõi, shape / dtype / device.
- B14 — GPU và CUDA: di chuyển tensor giữa CPU / GPU, pinned memory.
- B15 — Autograd: reverse-mode autodiff, computation graph động.
- B16 — nn.Module: cấu trúc model,
forward,parameters. - B17 — Optimizer SGD và Adam:
step,zero_grad, momentum. - B18 — Training Loop: vòng lặp chuẩn, train/eval, checkpoint cơ bản.
- B19 — Dataset và DataLoader: batch dữ liệu,
num_workers, transform. - B20 — Weight Initialization (bài này): Xavier, He,
nn.init,model.apply.
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 overfitting và scale 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.
- Glorot & Bengio (2010) - Understanding the difficulty of training deep feedforward neural networks (AISTATS)
- He et al. (2015) - Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification
- Saxe et al. (2013) - Exact solutions to the nonlinear dynamics of learning in deep linear neural networks
- Jozefowicz et al. (2015) - An Empirical Exploration of Recurrent Network Architectures (LSTM forget bias)
- PyTorch Docs - torch.nn.init
- PyTorch Docs - nn.Module.apply
- PyTorch Docs - nn.Linear
- PyTorch Issue #15314 - Discussion on default init for nn.Linear
- Goodfellow, Bengio, Courville - Deep Learning Book Ch. 8.4 (Parameter Initialization Strategies)
- CS231n - Neural Networks Part 2: Initialization
