Learn ML

Chapter 15 of 25

Activation Functions

The spark that lets networks learn anything non-linear

25 min read

Motivation

You already proved something important in the last chapter: stack as many linear layers as you like, and you still only ever get a straight line (or flat plane) at the end. The thing that breaks that curse — the single ingredient that lets depth actually buy you something — is the small nonlinear function squeezed into every neuron, right after the weighted sum. That function is called the activation function, and this chapter is a complete tour of the ones actually used in practice, including why some of them fell out of favor and why others became the default almost everywhere.

A Real-Life Scenario

Think about how a thermostat works versus how a smart climate system works. A dumb thermostat is essentially a step function: below the target temperature, heater fully on; above it, fully off. That's simple, but abrupt and uninformative — it gives you no sense of how close you are to the target, and a control system built entirely out of such switches would behave jerkily and be almost impossible to fine-tune.

A smart system instead responds smoothly: it ramps the heater up gradually as the room gets colder, and eases off gradually as it warms. That smooth, graded response is what most modern activation functions try to achieve inside a neural network — a way of responding to input that isn't just "fully on" or "fully off," but carries a continuous, informative gradient the network can learn from.

Building the Intuition

  • Every activation function takes a raw weighted sum (zz) and squashes, reshapes, or filters it in some nonlinear way before passing it to the next layer.
  • A good activation function should be nonlinear (or depth adds nothing, as proven last chapter), (almost everywhere) differentiable (so backpropagation has a gradient to work with), cheap to compute (since it runs millions of times per training step), and ideally avoid saturating — having a near-zero derivative over large stretches of input.
  • That last property, saturation, turns out to be the central plot of this entire chapter. It's the reason sigmoid and tanh, once the default choice, were largely replaced by ReLU and its descendants.

The Vanishing and Exploding Gradient Problem

During backpropagation, the error signal δ[]\boldsymbol{\delta}^{[\ell]} gets multiplied by φ(z[])\varphi'(\mathbf{z}^{[\ell]}) at every single layer as it propagates backward. If φ\varphi' is consistently much smaller than 1 (as with sigmoid and tanh once they saturate), these repeated multiplications shrink the gradient exponentially the deeper it travels — the vanishing gradient problem. Early layers of a deep network then receive almost no learning signal at all and effectively stop improving. The mirror-image failure, where φ\varphi' is consistently larger than 1, causes gradients to blow up exponentially instead — the exploding gradient problem.

This one issue shaped decades of deep learning history

Vanishing gradients were the primary practical obstacle to training genuinely deep networks for years. Understanding this mechanism is essential to understanding why ReLU-family activations became the default choice for hidden layers.

The Math Behind It: A Catalog of Activation Functions

Binary Step (Historical)

φ(z)={1z00z<0,φ(z)=0 (almost everywhere)\varphi(z) = \begin{cases}1 & z \geq 0\\0 & z<0\end{cases}, \qquad \varphi'(z) = 0 \text{ (almost everywhere)}

This is what the original perceptron used. Its gradient is zero almost everywhere, which makes it completely unusable with gradient-based learning — it survives only as a historical reference point.

Sigmoid (Logistic)

Sigmoid
σ(z)=11+ez,σ(z)=σ(z)(1σ(z))\sigma(z) = \frac{1}{1+e^{-z}}, \qquad \sigma'(z) = \sigma(z)\big(1-\sigma(z)\big)

Derivation of the derivative: let u=1+ezu = 1+e^{-z}, so σ=u1\sigma = u^{-1}. By the chain rule, σ=u2(ez)=ez(1+ez)2\sigma' = -u^{-2}\cdot(-e^{-z}) = \frac{e^{-z}}{(1+e^{-z})^2}. Noting ez=1σ1=1σσe^{-z} = \frac{1}{\sigma}-1 = \frac{1-\sigma}{\sigma}, substituting and simplifying gives σ=σ(1σ)\sigma' = \sigma(1-\sigma).

Range: (0,1)(0,1). Advantages: smooth, directly interpretable as a probability, historically important. Disadvantages: saturates for large z|z| — its maximum derivative is only 0.250.25, at z=0z=0 — causing severe vanishing gradients in deep networks; its output is also not zero-centered, which can slow convergence. Use case: the output layer for binary classification (interpreting the output as P(y=1x)P(y=1|\mathbf{x})); rarely used in hidden layers of modern deep networks.

Hyperbolic Tangent (Tanh)

Tanh
tanh(z)=ezezez+ez=2σ(2z)1,tanh(z)=1tanh2(z)\tanh(z) = \frac{e^{z}-e^{-z}}{e^{z}+e^{-z}} = 2\sigma(2z)-1, \qquad \tanh'(z) = 1-\tanh^2(z)

Range: (1,1)(-1,1). Advantages: zero-centered (unlike sigmoid), generally improving convergence speed, with a steeper gradient near zero. Disadvantages: still saturates for large z|z|, so it still suffers vanishing gradients in deep networks. Use case: historically common in hidden layers before ReLU became standard; still seen in some recurrent architectures.

ReLU (Rectified Linear Unit)

ReLU
ReLU(z)=max(0,z),ReLU(z)={1z>00z<0undefinedz=0\text{ReLU}(z) = \max(0, z), \qquad \text{ReLU}'(z) = \begin{cases}1 & z>0\\0 & z<0\\\text{undefined} & z=0\end{cases}

Range: [0,)[0,\infty). Advantages: trivially cheap to compute; its derivative is exactly 1 for every positive input, so it doesn't saturate on the positive side, largely fixing vanishing gradients there; empirically converges much faster than sigmoid or tanh. Disadvantages: the dying ReLU problem — a neuron whose pre-activation stays persistently negative during training receives zero gradient forever and effectively stops learning, permanently; also not zero-centered. Use case: the default choice for hidden layers in most modern feedforward and convolutional networks.

A very common interview question

"What is the dying ReLU problem, and how can it be mitigated?" Explain the mechanism — a persistently negative pre-activation yields zero gradient forever — then mention mitigations: Leaky ReLU, ELU, or SELU (below), careful weight initialization, and lower learning rates.

Leaky ReLU

LReLU(z)={zz0αzz<0,LReLU(z)={1z0αz<0\text{LReLU}(z) = \begin{cases}z & z\geq0\\\alpha z & z<0\end{cases}, \qquad \text{LReLU}'(z) = \begin{cases}1 & z\geq0\\\alpha & z<0\end{cases}

where α\alpha is a small constant, typically 0.010.01. This fixes the dying ReLU problem by allowing a small, nonzero gradient when z<0z<0, at the cost of one extra hyperparameter to choose. It's a common drop-in replacement for ReLU when dying neurons are observed.

ELU (Exponential Linear Unit)

ELU(z)={zz0α(ez1)z<0,ELU(z)={1z0ELU(z)+αz<0\text{ELU}(z) = \begin{cases}z & z\geq0\\\alpha(e^{z}-1) & z<0\end{cases}, \qquad \text{ELU}'(z) = \begin{cases}1 & z\geq0\\\text{ELU}(z)+\alpha & z<0\end{cases}

ELU is smooth (unlike Leaky ReLU's kink at zero), and its negative values push the mean activation closer to zero, which can speed up learning — a motivation similar to batch normalization. The trade-off is extra computational cost from the exponential.

SELU (Scaled ELU)

SELU(z)=λ{zz0α(ez1)z<0\text{SELU}(z) = \lambda\begin{cases}z & z\geq0\\\alpha(e^z-1) & z<0\end{cases}

with fixed constants λ1.0507\lambda \approx 1.0507 and α1.6733\alpha \approx 1.6733 (not hyperparameters to tune). Under specific conditions — LeCun normal weight initialization and a plain feedforward architecture without other normalization layers — SELU causes each layer's outputs to automatically converge toward zero mean and unit variance, a property called self-normalization. This benefit is fragile: it can break down in the presence of skip connections, standard dropout, or batch normalization.

Softplus

softplus(z)=ln(1+ez),softplus(z)=σ(z)\text{softplus}(z) = \ln(1+e^{z}), \qquad \text{softplus}'(z) = \sigma(z)

A smooth approximation of ReLU with no sharp kink at zero, always differentiable, but more expensive to compute and still capable of producing small gradients for very negative inputs.

Swish

swish(z)=zσ(βz)\text{swish}(z) = z\cdot\sigma(\beta z)

where β\beta is a constant or learnable parameter, often simply fixed at 1. Swish is smooth and non-monotonic — it dips slightly below zero for small negative zz before returning to zero — and has been shown empirically to outperform ReLU on some deep architectures, at extra computational cost.

GELU (Gaussian Error Linear Unit)

GELU(z)=zΦ(z)\text{GELU}(z) = z\cdot\Phi(z)

where Φ(z)\Phi(z) is the standard normal cumulative distribution function, often approximated as 0.5z(1+tanh[2/π(z+0.044715z3)])0.5z\big(1+\tanh[\sqrt{2/\pi}(z + 0.044715z^3)]\big) for efficiency. GELU can be interpreted as weighting the input by the probability that a standard normal variable is less than zz, and has become the standard activation in Transformer-based architectures.

Comparing Modern Activation Functions
ReLU, Leaky ReLU, Swish, and GELU compared. The smooth variants (Swish, GELU) dip slightly below zero before rising, unlike ReLU and Leaky ReLU's sharp corner at the origin.

Interactive: Explore Each Function and Its Derivative

Activation Function Explorer

Pick sigmoid, tanh, ReLU, or Leaky ReLU and see the function plotted alongside its derivative — watch where each one saturates.

f(x)f'(x)

Softmax (Multi-Class Output Activation)

Softmax
softmax(z)k=ezkj=1Kezj\text{softmax}(\mathbf{z})_k = \frac{e^{z_k}}{\sum_{j=1}^K e^{z_j}}

Why it's needed: softmax converts a vector of arbitrary real numbers ("logits") into a valid probability distribution over KK classes — every output lies in (0,1)(0,1) and all outputs sum to exactly 1.

Numerical stability note: in practice, every entry of z\mathbf{z} is first shifted by subtracting maxjzj\max_j z_j before exponentiating. This doesn't change the mathematical result (it cancels in the ratio) but prevents overflow when computing ezke^{z_k} for large zkz_k.

Worked example: let z=(2.0,1.0,0.1)\mathbf{z} = (2.0, 1.0, 0.1). Then e2.07.389e^{2.0}\approx7.389, e1.02.718e^{1.0}\approx2.718, e0.11.105e^{0.1}\approx1.105, summing to about 11.21211.212, giving softmax(z)(0.659, 0.242, 0.099)\text{softmax}(\mathbf{z}) \approx (0.659,\ 0.242,\ 0.099). The class with the largest logit (2.0) receives the highest probability, and all three sum to exactly 1. Softmax is used exclusively in the output layer for multi-class, single-label classification, always paired with categorical cross-entropy loss.

Summary Comparison

All activation functions covered in this chapter, at a glance
FunctionRangeSaturates?Typical Use
Binary Step{0,1}Yes (fully)Historical (perceptron) only
Sigmoid(0,1)Yes (both sides)Binary classification output
Tanh(-1,1)Yes (both sides)Legacy hidden layers, RNNs
ReLU[0,∞)Only for z<0Default for hidden layers
Leaky ReLU(-∞,∞)NoHidden layers, dying-ReLU fix
ELU(-α,∞)Only asymptoticallyHidden layers, faster convergence
SELU(-λα,∞)Only asymptoticallySelf-normalizing deep MLPs
Softplus(0,∞)Only for very negative zSmooth ReLU alternative
Swish(≈-0.28,∞)NoModern deep networks
GELU(≈-0.17,∞)NoTransformer architectures
Softmax(0,1)^K, sums to 1Yes (relatively)Multi-class output layer
Default recommendation

For hidden layers: start with ReLU — it's cheap, effective, and well understood. If dying neurons or instability show up, try Leaky ReLU or ELU; for Transformer-style architectures, GELU is conventional. For output layers: sigmoid for binary classification, softmax for multi-class classification, linear (identity) for regression.

Implementing It

import numpy as np
 
def sigmoid(z): return 1/(1+np.exp(-z))
def tanh(z): return np.tanh(z)
def relu(z): return np.maximum(0, z)
def leaky_relu(z, alpha=0.01): return np.where(z >= 0, z, alpha*z)
def elu(z, alpha=1.0): return np.where(z >= 0, z, alpha*(np.exp(z)-1))
def softmax(z):
    z = z - np.max(z)             # numerical stability
    e = np.exp(z)
    return e / np.sum(e)
 
z = np.array([-2.0, -0.5, 0.0, 0.5, 2.0])
print("sigmoid:   ", sigmoid(z))
print("tanh:      ", tanh(z))
print("relu:      ", relu(z))
print("leaky_relu:", leaky_relu(z))
print("elu:       ", elu(z))
print("softmax([2.0, 1.0, 0.1]):", softmax(np.array([2.0, 1.0, 0.1])))

Each function is implemented with vectorized NumPy operations so it applies element-wise across an entire array at once — exactly how it's used inside a real network, applied to every neuron in a layer simultaneously. Note the subtraction of the max value inside softmax before exponentiating; this is the numerical stability trick described above.

Key Takeaways

Key Takeaways
  • Activation functions must be nonlinear, (almost everywhere) differentiable, and ideally avoid saturating. - Sigmoid and tanh saturate and cause vanishing gradients in deep networks; both are largely replaced by ReLU-family functions in hidden layers. - ReLU is cheap and mitigates vanishing gradients on the positive side, but can suffer from the dying ReLU problem. - Leaky ReLU, ELU, and SELU address dying ReLU or add self-normalizing properties. - Swish and GELU are smooth, modern alternatives, with GELU standard in Transformers. - Softmax converts logits to a proper probability distribution for multi-class classification and is always paired with categorical cross-entropy loss.

Common Mistakes

Using sigmoid or tanh in deep hidden layers by default

Both saturate heavily, and stacking many such layers compounds vanishing gradients. Reach for ReLU or a modern variant for hidden layers unless there's a specific reason not to.

Plugging the pre-activation z into a derivative that expects the activation a

Several derivatives (like σ(z)=σ(z)(1σ(z))\sigma'(z) = \sigma(z)(1-\sigma(z))) are most conveniently written in terms of the already-computed activation aa, not the raw zz. Mixing these up is a common off-by-one-step bug during backpropagation.

Interview Corner

Quick Check
1. What causes the dying ReLU problem?
2. Why must softmax always be paired with categorical cross-entropy rather than mean squared error?

Where This Shows Up in Practice

Every modern architecture is defined partly by its choice of activation function: convolutional networks and most feedforward MLPs default to ReLU or a variant, Transformers default to GELU, and every classification network's output layer uses sigmoid or softmax depending on whether it's binary or multi-class. The vanishing gradient problem introduced here also motivates architectural techniques covered later in this course, like batch normalization and careful weight initialization — both are, at their core, strategies for keeping activations away from the saturating regions described in this chapter.