datarekha

Activation functions

ReLU, GELU, SiLU, sigmoid, softmax — what each one does, what it's for, and why a network without them is just a linear regression in disguise.

5 min read Beginner Deep Learning Lesson 5 of 28

What you'll learn

  • Why nonlinearity is non-negotiable
  • When to use ReLU vs GELU vs SiLU in hidden layers
  • When to use sigmoid vs softmax in the output layer

Before you start

A neural network with no activation functions is just matrix multiplication — and stacked matrix multiplications collapse to a single matrix multiplication. So without nonlinearities, a 100-layer network has the same expressive power as a 1-layer network. Activations break that linearity and are what make depth useful.

TryActivations · function & gradient

It's the gradient that makes or breaks an activation

Each activation plots its value f(x) and its derivative f′(x) on the same axes. Drag the input x marker into the tails of Sigmoid/Tanh and watch the gradient collapse toward 0; on ReLU, slide it below 0 into the dead zone. That gradient story is why ReLU, GELU, and SiLU took over deep nets.

-1012-6-4-20246x
f(x)f′(x) · gradientdrag the x handle
Saturating — gradient vanishes in the tails
at x =2.4
f(x)0.917
f′(x) · gradient0.076
formulaσ(x) = 1 / (1 + e⁻ˣ)
range(0, 1)
saturates?yes
zero-centered?no
dead zone?no

Squashes to (0, 1). Both tails saturate, so f′ collapses toward 0 — gradients vanish and deep stacks barely learn. Lives on now as a binary-output activation, not a hidden one.

The collapse, demonstrated

import numpy as np

rng = np.random.default_rng(0)
W1 = rng.standard_normal((4, 8))
W2 = rng.standard_normal((8, 8))
W3 = rng.standard_normal((8, 2))
x = rng.standard_normal((1, 4))

# Stacked linear: x @ W1 @ W2 @ W3
deep_linear = x @ W1 @ W2 @ W3

# Equivalent single matrix
W_combined = W1 @ W2 @ W3
single_linear = x @ W_combined

print("3-layer linear:", deep_linear.round(4))
print("1-layer linear:", single_linear.round(4))
print("identical?    ", np.allclose(deep_linear, single_linear))
3-layer linear: [[0.678  0.3721]]
1-layer linear: [[0.678  0.3721]]
identical?     True

Three layers, one effective transformation. Add a nonlinearity between them and the network can learn curved decision boundaries, gating behaviors, and the rich function classes deep learning is famous for.

The hidden-layer activations

These go between layers. They’re the ones you’ll write nn.ReLU() or nn.GELU() for.

import numpy as np

x = np.linspace(-4, 4, 9)

def relu(x):        return np.maximum(0, x)
def leaky_relu(x):  return np.where(x > 0, x, 0.01 * x)
def gelu(x):
    # Approximate (tanh-based) GELU — what most implementations use
    return 0.5 * x * (1 + np.tanh(np.sqrt(2/np.pi) * (x + 0.044715 * x**3)))
def silu(x):        return x / (1 + np.exp(-x))   # also called Swish

print(f"{'x':>5} {'ReLU':>7} {'LeakyReLU':>10} {'GELU':>8} {'SiLU':>8}")
for xi in x:
    print(f"{xi:5.1f} {relu(xi):7.3f} {leaky_relu(xi):10.3f} {gelu(xi):8.3f} {silu(xi):8.3f}")
    x    ReLU  LeakyReLU     GELU     SiLU
 -4.0   0.000     -0.040   -0.000   -0.072
 -3.0   0.000     -0.030   -0.004   -0.142
 -2.0   0.000     -0.020   -0.045   -0.238
 -1.0   0.000     -0.010   -0.159   -0.269
  0.0   0.000      0.000    0.000    0.000
  1.0   1.000      1.000    0.841    0.731
  2.0   2.000      2.000    1.955    1.762
  3.0   3.000      3.000    2.996    2.858
  4.0   4.000      4.000    4.000    3.928

A quick tour of what each one’s for:

ActivationFormulaWhere it’s usedWhy
ReLUmax(0, x)CNNs, MLPs — the default for a decadeCheap, doesn’t saturate on the positive side; roughly half the neurons output exactly 0 (sparse), which is fast on hardware
Leaky ReLUx if x > 0 else 0.01xWhen ReLU’s “dead neurons” hurtSmall negative slope keeps gradient alive
GELUx · Φ(x) (smooth)Transformers (BERT, GPT-2/3 era)Smooth, slightly negative around 0, empirically helps language models
SiLU / Swishx · σ(x)Llama, Mistral, QwenLike GELU but cheaper to compute, currently dominant in LLMs

The table tells you what each one does; the real difference is in the gradient. Each activation is only as useful as the gradient it lets flow backward during training — that derivative f'(x) is the signal backprop multiplies through, and where it goes to zero, learning stalls.

ReLU’s key advantage: its derivative is exactly 1 for all positive inputs, so gradients pass through unchanged on the positive side. Sigmoid and tanh, by contrast, squash their entire input range into a small output range — for large |x| the derivative is nearly zero, shrinking every upstream gradient. Stack 20 layers of sigmoid and that gradient signal becomes too faint to update early layers (the vanishing gradient problem). ReLU avoids this for positive activations entirely.

One hazard: a ReLU neuron whose input is always negative will always output 0 and its gradient is always 0 — it can never recover. This is a dead neuron. Leaky ReLU fixes it with a small negative slope; GELU/SiLU fix it smoothly.

Hidden-layer activations: ReLU vs GELU vs SiLUx−44f(x)ReLUGELUSiLULeakyReLU ≈ ReLU but with a small negative slope (keeps the gradient alive)

The output activations

These go at the end of the network, shaping the output to match the task.

Sigmoid1 / (1 + exp(-x)) — squashes to (0, 1). Use for binary classification outputs (probability of class 1), or for any “this is a probability of one event” output. Used to be common in hidden layers — it isn’t anymore (gradients vanish at the tails).

Tanh(e^x - e^-x) / (e^x + e^-x) — squashes to (-1, 1). Like sigmoid but centered. Mostly historical now except in some RNN variants and specific output scaling.

Softmaxexp(x_i) / Σ exp(x_j) — turns a vector of logits into a probability distribution that sums to 1. Use for multi-class classification: the network outputs one logit per class, softmax turns them into probabilities.

import numpy as np

def sigmoid(x):    return 1 / (1 + np.exp(-x))
def softmax(x):
    # Numerically stable: subtract max before exp
    e = np.exp(x - x.max(axis=-1, keepdims=True))
    return e / e.sum(axis=-1, keepdims=True)

# Binary classification: one logit -> probability
binary_logit = np.array([2.0])
print("sigmoid(2.0) =", sigmoid(binary_logit).round(3))

# Multi-class (3 classes): a vector of logits -> probs that sum to 1
multi_logits = np.array([1.0, 3.0, 0.2])
probs = softmax(multi_logits)
print("softmax probs:", probs.round(3), "sum:", probs.sum())
sigmoid(2.0) = [0.881]
softmax probs: [0.113 0.836 0.051] sum: 0.9999999999999999

Picking one (the short version)

  • Hidden layers: SiLU or GELU. ReLU if you want cheap and don’t care.
  • Binary output: sigmoid.
  • Multi-class output: softmax (often fused with cross-entropy loss as nn.CrossEntropyLoss, which expects raw logits — don’t apply softmax yourself!).
  • Regression output: nothing. Output the raw values.

In one breath

  • Without nonlinear activations a deep network collapses to a single matrix multiply — depth only helps once you break the linearity.
  • Hidden layers: ReLU (cheap, sparse, derivative 1 on the positive side) gave way to GELU/SiLU (smooth, slightly negative near 0) in transformers; SiLU inside SwiGLU dominates modern LLMs.
  • An activation is only as good as the gradient it passes: ReLU avoids vanishing on the positive side but can leave dead neurons; smooth activations keep the gradient alive everywhere.
  • The output layer matches the task: sigmoid for binary, softmax for multi-class single-label, nothing for regression.
  • With nn.CrossEntropyLoss output raw logits — it applies log-softmax internally; applying softmax yourself is a bug.

Quick check

Quick check

0/2
Q1You're building a 10-class image classifier with `nn.CrossEntropyLoss`. What should the final layer output?
Q2Why did most LLMs move from ReLU to GELU/SiLU in their feed-forward layers?

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
What is the dying ReLU problem and how do you prevent it?

A ReLU neuron dies when its pre-activation is permanently negative for every training example, making its gradient exactly zero and freezing the neuron forever. Large learning rates or poorly initialized weights are the usual causes; leaky ReLU, parametric ReLU, or ELU provide sub-zero gradients that keep neurons recoverable.

What is GELU and why does it outperform ReLU in transformer models?

GELU (Gaussian Error Linear Unit) multiplies the input by the probability that a standard Gaussian random variable is smaller than it, producing a smooth, non-monotonic curve that approximates ReLU but with a stochastic regularization flavor. Transformers favor GELU because the smooth gradient near zero improves optimization in deep attention-based architectures.

Compare sigmoid, tanh, ReLU, leaky ReLU, and GELU — when would you pick each?

Sigmoid squashes to (0,1) and saturates at extremes, causing vanishing gradients. Tanh is zero-centered but still saturates. ReLU avoids saturation for positive inputs and trains fast but can produce dead neurons. Leaky ReLU fixes dying neurons. GELU is smooth and probabilistic, now the default in most transformer architectures.

What does the Universal Approximation Theorem guarantee — and what doesn't it guarantee?

The theorem proves that a single-hidden-layer network with enough neurons and a non-linear activation can approximate any continuous function on a compact domain to arbitrary precision. It guarantees existence, not learnability — it says nothing about how many neurons are needed, whether gradient descent will find the solution, or how the network will generalize.

Why does weight initialization matter and how do Xavier and He initialization work?

Poor initialization causes the variance of activations to either explode or collapse across layers, triggering vanishing or exploding gradients before training even begins. Xavier initialization targets variance preservation for saturating activations; He initialization corrects for the halved variance caused by ReLU zeroing negative inputs.

What does a single artificial neuron (perceptron) actually compute?

A neuron takes a weighted sum of its inputs, adds a bias, and passes the result through an activation function. The weights encode learned feature importance, the bias shifts the decision boundary, and the activation introduces the non-linearity needed for complex mappings.

Why do neural networks need activation functions at all?

Without a non-linear activation, any stack of linear layers collapses to a single linear transformation, giving a model no more expressive than logistic regression. Activation functions break linearity so the network can approximate arbitrarily complex functions.

Related lessons

Explore further

Skip to content