Weight Initialization: The Importance of Initialization

Weight initialization sets the initial values of a neural network's parameters. Proper initialization is critical to prevent gradients from vanishing or exploding, ensuring stable optimization from the very first epoch.


The Role of Initialization

Initialization defines the starting coordinates of optimization on the high-dimensional loss surface.

The Starting State of Optimization

Training a neural network is an optimization process that adjusts weights to minimize a loss function. The initialization step sets the starting coordinates on this complex, high-dimensional loss landscape.

If the starting point is in a flat plateau or a region of saturated gradients, optimization will stall. If the starting point is in a chaotic region, parameter updates will diverge, highlighting the importance of setting the starting values correctly.

Zero and Constant Initialization Problems

A common error is initializing all weights to zero or a constant value. In a fully connected layer, if all weights are identical, every hidden unit will compute the exact same output in the forward pass:

$$a_1 = a_2 = \dots = a_H$$

During backpropagation, all weights will receive identical gradient updates, meaning the units will remain identical throughout training. This is the symmetry problem, which prevents the network from learning diverse features, reducing it to a single-neuron model.

Signal Propagation Dynamics

Proper initialization ensures that signal variances remain stable as inputs propagate through deep layers.

Variance Propagation

As signals propagate forward through hidden layers, the variance of the activations should ideally remain constant. If the variance decreases at each layer, the signal will shrink, eventually dying out in deep networks.

Conversely, if the variance increases, the signals will grow exponentially, leading to numerical overflow (NaNs) and unstable training dynamics. Maintaining constant variance is the primary goal of modern initialization schemes.

Mathematical Proof of Variance Collapse

For a linear layer $z = \sum_{i=1}^{d} w_i x_i$ with independent inputs $x_i$ and weights $w_i$ of zero mean, the variance of the output $z$ is:

$$\text{Var}(z) = d \times \text{Var}(w_i) \times \text{Var}(x_i)$$

If we initialize weights such that $\text{Var}(w_i) < \frac{1}{d}$, the output variance $\text{Var}(z)$ will be smaller than the input variance $\text{Var}(x_i)$. In a network with 100 layers, the variance will scale by $(\text{Var}(w_i) \times d)^{100}$, collapsing to zero rapidly.

PyTorch Implementation

We can apply different weight initializations in PyTorch using the torch.nn.init module.

Native PyTorch Initializations

Here is how to apply native initialization functions to the weights of a PyTorch layer:

<pre><code class="language-python">import torch import torch.nn as nn import torch.nn.init as init # Linear layer layer = nn.Linear(10, 5) # Initialize weights with standard normal distribution init.normal_(layer.weight, mean=0.0, std=0.01) # Initialize biases to exactly zero init.constant_(layer.bias, 0.0) print("Layer weights initialized:\n", layer.weight)</pre>

In this code, we use in-place methods (indicated by the trailing underscore) to overwrite the layer's parameters. Setting biases to zero is standard practice, as weights break the symmetry.

Coding Manual Weight Override

We can manually override weights by wrapping parameter updates in a torch.no_grad() context, ensuring that our manual initializations do not interfere with the autograd engine:

<pre><code class="language-python">with torch.no_grad(): # Fill weights manually with a custom constant layer.weight.fill_(0.05) print("Manual fill complete.")</pre>

This manual override is useful when testing custom initialization schemes or setting weights to specific template values, providing fine control over parameter states.