Skip to content

Weight Initialization ​

Starting right — why proper initialization is critical for training deep networks


Why Initialization Matters ​

Weight initialization determines the starting point for gradient descent. Poor initialization can cause:

  • Training failure (no learning at all)
  • Vanishing/exploding activations
  • Extremely slow convergence

Symmetry Breaking ​

If all weights are initialized identically, all neurons in a layer compute the same output, receive the same gradient, and update identically. They remain symmetric forever.

Result: A layer with 100 neurons behaves like a layer with 1 neuron.

Solution: Random initialization breaks symmetry — each neuron learns different features.


Bad Initializations ​

All Zeros ​

python
W = np.zeros((input_dim, output_dim))

Problem: All neurons output zero, all gradients are zero, nothing ever changes.

All Same Value ​

python
W = np.ones((input_dim, output_dim)) * 0.1

Problem: Symmetry is never broken — neurons stay identical.

Too Large ​

python
W = np.random.randn(input_dim, output_dim) * 10

Problem: Activations explode through layers, gradients explode, NaN losses.

Too Small ​

python
W = np.random.randn(input_dim, output_dim) * 0.001

Problem: Activations shrink to zero, gradients vanish, no learning.

Bad Initializations


The Variance Problem ​

Consider a layer: z=∑i=1ninwixi

If weights and inputs are independent with zero mean:

Var(z)=nin⋅Var(w)⋅Var(x)

Through Multiple Layers ​

For a network with L layers:

Var(a[L])=(∏l=1Lnin[l]⋅Var(w[l]))⋅Var(x)

If nin⋅Var(w)>1: activations explode exponentially

If nin⋅Var(w)<1: activations vanish exponentially

Goal: Keep nin⋅Var(w)≈1 so variance stays stable.

Variance Through Layers


Xavier/Glorot Initialization ​

For sigmoid/tanh activations (Glorot & Bengio, 2010)

Derivation ​

To maintain variance through both forward and backward passes:

  • Forward: Var(w)=1/nin
  • Backward: Var(w)=1/nout

Compromise: Use the harmonic mean:

Var(w)=2nin+nout

Implementation ​

python
# Xavier uniform
limit = np.sqrt(6 / (n_in + n_out))
W = np.random.uniform(-limit, limit, (n_in, n_out))

# Xavier normal
std = np.sqrt(2 / (n_in + n_out))
W = np.random.randn(n_in, n_out) * std

When to Use ​

  • Sigmoid activations
  • Tanh activations
  • Linear activations

He/Kaiming Initialization ​

For ReLU activations (He et al., 2015)

Why Xavier Fails for ReLU ​

ReLU zeros out ~50% of activations on average. If input variance is Var(x):

Var(ReLU(z))=12⋅Var(z)

This extra factor of 1/2 causes variance to shrink through layers with Xavier init.

Derivation ​

To compensate for ReLU killing half the values:

Var(w)=2nin

The factor of 2 accounts for ReLU's halving effect.

Implementation ​

python
# He uniform
limit = np.sqrt(6 / n_in)
W = np.random.uniform(-limit, limit, (n_in, n_out))

# He normal (most common)
std = np.sqrt(2 / n_in)
W = np.random.randn(n_in, n_out) * std

When to Use ​

  • ReLU activations
  • Leaky ReLU
  • ELU
  • GELU (usually works well)

He vs Xavier


Other Initialization Schemes ​

LeCun Initialization ​

For SELU (self-normalizing) activations:

Var(w)=1nin

Orthogonal Initialization ​

Initialize weight matrices as orthogonal matrices:

python
W, _ = np.linalg.qr(np.random.randn(n_in, n_out))

Benefits: Preserves gradient norm during backprop, useful for RNNs.

LSUV (Layer-Sequential Unit-Variance) ​

  1. Initialize with orthogonal weights
  2. Forward pass on a batch
  3. Rescale each layer to have unit variance

Benefit: Data-dependent initialization, works for any architecture.


Framework Defaults ​

Modern frameworks have sensible defaults:

FrameworkDefault for Linear/Dense
PyTorchKaiming uniform (for Linear), Xavier for others
TensorFlow/KerasGlorot uniform
JAXLecun normal

Usually you don't need to change these — but knowing why they work helps debugging.


Diagnosing Initialization Problems ​

Signs of Bad Initialization ​

SymptomLikely Cause
Loss is NaNExploding activations (init too large)
Loss doesn't decreaseVanishing gradients (init too small) or symmetry
All outputs are sameSymmetry not broken
Some neurons never activateDead ReLUs (init caused negative region)

Debugging Steps ​

  1. Print activation statistics at each layer:
python
for name, layer in model.named_modules():
    print(f"{name}: mean={activations.mean():.4f}, std={activations.std():.4f}")
  1. Check gradient norms:
python
for name, param in model.named_parameters():
    if param.grad is not None:
        print(f"{name}: grad_norm={param.grad.norm():.6f}")
  1. Visualize activation distributions:

Activation Distributions


Practical Guidance ​

Rules of Thumb ​

ActivationInitializationFormula
Sigmoid/TanhXavierVar=2nin+nout
ReLU/variantsHeVar=2nin
SELULeCunVar=1nin
LinearXavierVar=2nin+nout

Special Cases ​

  • ResNets: Scale final layer of each residual block by 1/2 or 1/L
  • Transformers: Often use 1/dmodel scaling
  • Batch Norm: Initialization matters less because BN normalizes

Interview Questions ​

Q1: "Why can't we initialize all weights to zero?" ​

All neurons would compute the same output, because they have identical weights. During backpropagation, they all receive the same gradient and update identically. This symmetry is never broken — the neurons remain identical forever.

Result: A layer with 100 neurons effectively acts like a single neuron. The network cannot learn diverse features.

Solution: Random initialization breaks symmetry. Each neuron starts with different weights, computes different outputs, receives different gradients, and learns different features.

Note: Biases can be initialized to zero because they don't break symmetry (each neuron still has unique weights).

Q2: "Derive He initialization for ReLU." ​

Goal: Keep activation variance stable through layers.

For a layer z=∑i=1nwixi with independent inputs:

Var(z)=n⋅Var(w)⋅Var(x)

ReLU's effect: Var(ReLU(z))=12Var(z) (zeros half the values)

For stable variance through layers:

n⋅Var(w)⋅12=1Var(w)=2n

He initialization: Sample from N(0,2/nin) or Uniform(−6/nin,6/nin)

The factor of 2 compensates for ReLU killing half the activations.

Q3: "How would you debug a network that isn't learning?" ​

Step 1: Check activation distributions at each layer

  • Near zero everywhere → init too small, vanishing activations
  • Very large/NaN → init too large, exploding activations
  • All same value → symmetry not broken

Step 2: Check gradient norms

  • Very small (< 1e-7) → vanishing gradients
  • Very large or NaN → exploding gradients

Step 3: Verify initialization matches activation

  • ReLU needs He init (Var=2/n)
  • Sigmoid/Tanh needs Xavier (Var=2/(nin+nout))

Step 4: Other checks

  • Learning rate too high/low?
  • Loss function appropriate for task?
  • Data preprocessing correct?
  • Bug in model architecture?

Quick fix: Add BatchNorm — it's more forgiving of initialization.


Key Takeaways ​

  1. Random initialization breaks symmetry — essential for neurons to learn different features.

  2. Variance must be preserved through layers to prevent vanishing/exploding activations.

  3. Use He for ReLU (Var=2/nin) — the factor of 2 compensates for ReLU killing half the values.

  4. Use Xavier for sigmoid/tanh (Var=2/(nin+nout)) — balances forward and backward variance.

  5. Diagnosis: Print activation means/stds and gradient norms layer by layer to identify problems.