Weight Initialization & Layer Normalization

Derive Xavier/Glorot and He/Kaiming variance preservation proofs from first principles, understand LayerNorm mechanics in modern Transformers, and inspect zero-dependency implementations in Python and Rust.

03. Deep Learning, CNNs & Transformer Architectures8 min read

Deep neural networks consist of stacked non-linear matrix transformations. Without careful parameter initialization, activations and backpropagated gradients either explode exponentially towards numerical infinity or collapse towards zero as depth increases.

Modern deep architecture stability rests on two foundational mathematical developments: Variance-Preserving Weight Initialization (Glorot and He) and Activation Normalization (LayerNorm and BatchNorm).

He VarianceOptimization
2 / n_in

Compensates for the 50% activation sparsity induced by the non-linear ReLU operator.

1. Mathematical Proof: Variance Collapse in Naive Networks#

Consider a linear layer computing pre-activations yy:

y=∑i=1ninwixiy = \sum_{i=1}^{n_{\text{in}}} w_i x_i

Assume input features xix_i and weights wiw_i are independent random variables with zero mean (E[xi]=0\mathbb{E}[x_i] = 0, E[wi]=0\mathbb{E}[w_i] = 0) and identical variances Var(x)\text{Var}(x) and Var(w)\text{Var}(w).

Architecture Diagram
Rendering diagram...
1

Step 1: Compute the Variance of a Single Product

Using the variance identity for independent zero-mean variables:

Var(wixi)=E[wi2xi2]−(E[wixi])2=E[wi2]E[xi2]−0=Var(w)Var(x)\text{Var}(w_i x_i) = \mathbb{E}[w_i^2 x_i^2] - (\mathbb{E}[w_i x_i])^2 = \mathbb{E}[w_i^2] \mathbb{E}[x_i^2] - 0 = \text{Var}(w) \text{Var}(x)
2

Step 2: Sum Across All $n_{\text{in}}$ Inputs

Because inputs are independent, the variance of the sum is the sum of variances:

Var(y)=Var(∑i=1ninwixi)=∑i=1ninVar(wixi)=ninVar(w)Var(x)\text{Var}(y) = \text{Var}\left( \sum_{i=1}^{n_{\text{in}}} w_i x_i \right) = \sum_{i=1}^{n_{\text{in}}} \text{Var}(w_i x_i) = n_{\text{in}} \text{Var}(w) \text{Var}(x)
3

Step 3: Compound Across $L$ Layers

In an LL-layer deep network, the variance at layer LL compounds multiplicatively:

Var(y(L))=(ninVar(w))LVar(x(0))\text{Var}(y^{(L)}) = \Big( n_{\text{in}} \text{Var}(w) \Big)^L \text{Var}(x^{(0)})
  • If ninVar(w)>1n_{\text{in}} \text{Var}(w) > 1: Activations explode exponentially towards ±∞\pm \infty.
  • If ninVar(w)<1n_{\text{in}} \text{Var}(w) < 1: Activations collapse exponentially towards 00.

To preserve activation scale across the forward pass, we must enforce ninVar(w)=1n_{\text{in}} \text{Var}(w) = 1, which requires Var(w)=1nin\text{Var}(w) = \frac{1}{n_{\text{in}}}.

2. Xavier/Glorot vs He/Kaiming Derivations#

Xavier/Glorot Initialization (Symmetric Activations: Tanh / Linear)#

Glorot and Bengio (2010) analyzed both the forward pass and backward gradient flow:

  • Forward pass stability requires: Var(w)=1nin\text{Var}(w) = \frac{1}{n_{\text{in}}}
  • Backward pass stability requires: Var(w)=1nout\text{Var}(w) = \frac{1}{n_{\text{out}}}

Taking the harmonic mean between forward and backward constraints:

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

For a uniform distribution U[−a,a]U[-a, a], where Var=a23\text{Var} = \frac{a^2}{3}:

a23=2nin+nout  ⟹  a=6nin+nout\frac{a^2}{3} = \frac{2}{n_{\text{in}} + n_{\text{out}}} \implies a = \sqrt{\frac{6}{n_{\text{in}} + n_{\text{out}}}}

He/Kaiming Initialization (Rectified Linear Unit: ReLU)#

Xavier initialization assumes activations are linear or zero-mean symmetric. Under the ReLU activation f(z)=max⁡(0,z)f(z) = \max(0, z), all negative inputs are set to zero:

E[x2]=12Var(zprev)\mathbb{E}[x^2] = \frac{1}{2} \text{Var}(z_{\text{prev}})

Because ReLU halves the active variance of incoming signals, He et al. (2015) proved that weights must be scaled by an additional factor of 2:

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

For a Gaussian normal distribution:

w∼N(0,2nin)w \sim \mathcal{N}\left( 0, \frac{2}{n_{\text{in}}} \right)

3. Layer Normalization (LayerNorm)#

While Batch Normalization computes statistics across the mini-batch dimension, it degrades when batch sizes are small and fails on variable-length autoregressive sequential tokens.

Layer Normalization (Ba, Kiros, Hinton, 2016) computes the mean and variance across the feature (channel) dimension for each individual sample independently:

Architecture Diagram
Rendering diagram...

Given feature vector x∈Rdx \in \mathbb{R}^d:

μ=1d∑j=1dxj,σ2=1d∑j=1d(xj−μ)2\mu = \frac{1}{d} \sum_{j=1}^d x_j, \qquad \sigma^2 = \frac{1}{d} \sum_{j=1}^d (x_j - \mu)^2
x^j=xj−μσ2+ϵ\hat{x}_j = \frac{x_j - \mu}{\sqrt{\sigma^2 + \epsilon}}
yj=γjx^j+βjy_j = \gamma_j \hat{x}_j + \beta_j

Where γ∈Rd\gamma \in \mathbb{R}^d and β∈Rd\beta \in \mathbb{R}^d are learnable affine parameters initialized to γ=1\gamma = 1 and β=0\beta = 0.

Because LayerNorm has zero cross-sample dependencies, it performs identically during training and evaluation, serving as the universal normalization layer across all modern Transformer architectures (GPT, LLaMA, BERT).

4. From-Scratch Implementation: Zero Dependencies#

The following implementations construct He/Kaiming Initialization and a complete LayerNorm Forward Pass from scratch in pure Python and pure Rust.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import math import random from typing import List class DeepLearningPrimitives: @staticmethod def he_normal_init(fan_in: int, fan_out: int) -> List[List[float]]: # Variance = 2 / fan_in => StdDev = sqrt(2 / fan_in) std = math.sqrt(2.0 / fan_in) random.seed(42) weights = [] for _ in range(fan_out): row = [random.gauss(0.0, std) for _ in range(fan_in)] weights.append(row) return weights @staticmethod def layer_norm( x: List[List[float]], gamma: List[float], beta: List[float], eps: float = 1e-5 ) -> List[List[float]]: num_samples = len(x) d = len(x[0]) output = [] for i in range(num_samples): # Compute mean across feature dimension d mean = sum(x[i][j] for j in range(d)) / d # Compute variance across feature dimension d variance = sum((x[i][j] - mean) ** 2 for j in range(d)) / d std_inv = 1.0 / math.sqrt(variance + eps) # Normalize, scale, and shift normed_row = [ gamma[j] * ((x[i][j] - mean) * std_inv) + beta[j] for j in range(d) ] output.append(normed_row) return output if __name__ == "__main__": # Test He initialization W = DeepLearningPrimitives.he_normal_init(fan_in=4, fan_out=2) print("He Initialized Weight Matrix (2x4):") for row in W: print(" ", [round(v, 4) for v in row]) # Test LayerNorm tokens = [ [1.0, 2.0, 3.0, 4.0], [10.0, 20.0, 30.0, 40.0] ] gamma = [1.0] * 4 beta = [0.0] * 4 normed = DeepLearningPrimitives.layer_norm(tokens, gamma, beta) print("\nLayerNorm Normalized Tokens:") for row in normed: print(" ", [round(v, 4) for v in row])

5. Active Knowledge Checks#

Concept check

Why does He/Kaiming initialization scale weight variance by 2 / fan_in rather than 1 / fan_in as in Xavier initialization?

Concept check

What architectural constraint makes Layer Normalization superior to Batch Normalization for sequence processing in Transformers?