GenAI Course · Module 3 · Session 12

Activation Functions & Gradient Problems

⏱ 13 min read🎯 Intermediate🖼 1 diagram🧪 2 live demos🐍 Python code

Without activation functions, a 100-layer network would collapse into one straight line, because stacking linear steps is still linear. Activations add the bends that let networks learn complex patterns. But a poor choice can make gradients vanish or explode, and training stops working. Let us see both, with real measurements.

In this session

  • Why we need non-linear activations
  • Sigmoid, tanh, ReLU, Leaky ReLU, ELU and softmax
  • Vanishing gradients: measured in a 20-layer network
  • Exploding gradients and gradient clipping
  • Which activation to use where

01Why activations?

A layer computes x @ W + b, a straight-line (linear) operation. Two linear layers in a row equal one linear layer, so depth would be useless. An activation function applied after each layer adds a non-linear bend, and stacks of bends can fit almost any shape.

z1−1sigmoid → 1tanh → 1ReLU keeps growingtanh → −1sigmoid → 0
Sigmoid and tanh flatten out at both ends (gradient ≈ 0 there); ReLU stays a straight line for positive inputs.
activations.py
import numpy as np

z = np.array([-3.0, -1.0, 0.0, 1.0, 3.0])
acts = {
    "sigmoid":    1 / (1 + np.exp(-z)),
    "tanh":       np.tanh(z),
    "relu":       np.maximum(0, z),
    "leaky_relu": np.where(z > 0, z, 0.01 * z),
    "elu":        np.where(z > 0, z, np.exp(z) - 1),
}
print("z          ", z)
for name, a in acts.items():
    print(f"{name:11s}", a.round(3))

scores = np.array([2.0, 1.0, 0.1])                 # raw scores for 3 classes
soft = np.exp(scores) / np.exp(scores).sum()
print("\nsoftmax", scores, "->", soft.round(3), "sum =", soft.sum().round(3))
z [-3. -1. 0. 1. 3.] sigmoid [0.047 0.269 0.5 0.731 0.953] tanh [-0.995 -0.762 0. 0.762 0.995] relu [0. 0. 0. 1. 3.] leaky_relu [-0.03 -0.01 0. 1. 3. ] elu [-0.95 -0.632 0. 1. 3. ] softmax [2. 1. 0.1] -> [0.659 0.242 0.099] sum = 1.0
Demo · Compare activations at z = −3, −1, 0, 1, 3

Softmax is special: it works on a whole vector, turning raw scores into probabilities that add up to 1. It is used on the output layer for multi-class problems, and it is how an LLM turns scores into next-token probabilities.

02Vanishing and exploding gradients

Backprop multiplies one local derivative per layer (Session 11). The derivative of sigmoid is at most 0.25. Multiply many numbers below 1 and the result shrinks towards zero: early layers get almost no gradient and stop learning. That is the vanishing gradient problem.

vanishing.py
import numpy as np

# The derivative of sigmoid is at most 0.25 (at z = 0)
z = np.linspace(-6, 6, 1201); s = 1 / (1 + np.exp(-z))
print("max sigmoid'(z) =", (s * (1 - s)).max().round(3))

# Backprop multiplies one derivative per layer
for layers in [2, 5, 10, 20]:
    print(f"{layers:2d} layers: sigmoid gradient <= {0.25 ** layers:.1e}   relu gradient = {1.0 ** layers:.0f}")

# Exploding: weights a bit above 1 multiply up
print("\n1.5 multiplied 30 times =", f"{1.5 ** 30:,.0f}")
max sigmoid'(z) = 0.25 2 layers: sigmoid gradient <= 6.2e-02 relu gradient = 1 5 layers: sigmoid gradient <= 9.8e-04 relu gradient = 1 10 layers: sigmoid gradient <= 9.5e-07 relu gradient = 1 20 layers: sigmoid gradient <= 9.1e-13 relu gradient = 1 1.5 multiplied 30 times = 191,751

Now a real 20-layer PyTorch network. We measure the average gradient that reaches the first layer:

vanish_torch.py
import torch, torch.nn as nn
torch.set_num_threads(1)                    # same results on every run

def first_layer_grad(act, he_init=False, depth=20):
    torch.manual_seed(0)
    layers = []
    for _ in range(depth):
        lin = nn.Linear(32, 32)
        if he_init: nn.init.kaiming_normal_(lin.weight, nonlinearity="relu"); nn.init.zeros_(lin.bias)
        layers += [lin, act()]
    net = nn.Sequential(*layers, nn.Linear(32, 1))
    net(torch.randn(64, 32)).pow(2).mean().backward()
    return net[0].weight.grad.abs().mean().item()

for name, act, he in [("Sigmoid", nn.Sigmoid, False), ("Tanh", nn.Tanh, False),
                      ("ReLU", nn.ReLU, False), ("ReLU + He init", nn.ReLU, True)]:
    print(f"{name:15s} 20 layers -> average gradient at layer 1: {first_layer_grad(act, he):.1e}")
Sigmoid 20 layers -> average gradient at layer 1: 4.8e-20 Tanh 20 layers -> average gradient at layer 1: 2.0e-08 ReLU 20 layers -> average gradient at layer 1: 1.3e-11 ReLU + He init 20 layers -> average gradient at layer 1: 3.8e-03

Sigmoid’s gradient at layer 1 is around 10⁻²⁰: effectively zero, so the first layer never learns. Tanh and ReLU with default PyTorch initialisation also fade badly at this depth. Only ReLU with He initialisation keeps a healthy gradient (around 10⁻³).

Lesson: activation and weight initialisation work together. ReLU only keeps gradients alive with a matching initialisation (He/Kaiming, 2015). Modern networks also add residual connections and normalisation (Module 6).

The opposite problem, exploding gradients, happens when factors are above 1 (1.5³⁰ ≈ 191,751): the loss jumps to infinity or NaN. Common fixes are gradient clipping (torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)), a smaller learning rate and careful initialisation. RNNs suffer from both problems badly (Module 4).

03Choosing an activation

LayerUseWhy
Hidden layers (default)ReLUfast, no vanishing for positive values
Hidden layers, many dead ReLUsLeaky ReLU or ELUsmall slope for negatives keeps neurons alive
Transformers (BERT, GPT)GELU (a smooth ReLU)works slightly better in very deep models
Output: yes/noSigmoidgives a probability
Output: one of many classesSoftmaxprobabilities that sum to 1
Output: a number (price)None (linear)any value allowed
RNN / LSTM insideTanh and sigmoidbounded values keep the state stable
Demo · Pick the output activation
Dying ReLU: if a neuron’s input is always negative, ReLU outputs 0 and its gradient is 0, so it never recovers. A too-high learning rate often causes this.

04Summary

05Quick quiz

1. Why do we need activation functions?

2. The maximum derivative of sigmoid is…

3. Best default activation for hidden layers?

4. Output activation for “choose one of 10 classes”?

5. Gradient clipping helps with…

06References

Created by Ajeetkumar