Index Ai Academy
Phase 4 · Deep learning · Neural networks

Activation functions

ReLU, sigmoid, GELU, and why nonlinearity matters.

About 25 minutes · A free account opens the whole lesson

Create a free account Try part of it here

Already have an account? Sign in to open this lesson

Why it matters

An activation function is a small bend a neural network applies after each weighted sum. Without it, a hundred layers can do no more than one. The bend you pick also decides how easily the network learns. The switch from sigmoid to ReLU was one of the changes that made deep learning work. The GELU and SwiGLU used inside today's LLMs are the latest step in the same story.

What you'll be able to do

  • Show that stacked layers without activations collapse into one linear layer
  • Compare sigmoid, tanh, ReLU, leaky ReLU and GELU, and their slopes
  • Explain saturation and dead ReLUs, and why they slow or stop learning
  • Choose the right activation for hidden layers and for outputs

What it covers

  1. Without a bend, layers collapse
  2. The common activations
  3. Worked example: three inputs, five activations
  4. What the slopes do to learning
  5. Choosing

Without a bend, layers collapse

Say you change dollars into euros, then euros into yen. You could have used one dollars-to-yen rate and got the same answer. Two straight-line rules in a row always make one straight-line rule.

A neural network layer works the same way, unless something bends it. In the last lesson, a neuron took a weighted sum and then passed it through an activation function. That function is the bend. Leave it out, and every layer is a plain "multiply, then add" rule.

Here's a tiny example with one number per layer:

  • Layer 1 takes xx, the input, and gives h=2x+1h = 2x + 1.
  • Layer 2 takes hh and gives y=3h−4y = 3h - 4.

Put the first rule inside the second: y=3(2x+1)−4=6x−1y = 3(2x + 1) - 4 = 6x - 1. One layer with weight 6 and bias −1 does the work of both. At x=2x = 2, layer 1 gives 5 and layer 2 gives 11. The merged rule gives 6×2−1=116 \times 2 - 1 = 11 too.

Now put ReLU between the layers. ReLU keeps positive numbers and turns negative ones into 0. At x=−1x = -1, layer 1 gives −1. ReLU turns that into 0, so layer 2 gives −4. The merged straight line would have said −7. The bend broke the shortcut.

for x in [-1.0, 2.0]:
    h = 2 * x + 1                  # layer 1
    y = 3 * h - 4                  # layer 2
    merged = 6 * x - 1             # one layer doing both
    bent = 3 * max(0.0, h) - 4     # ReLU between the layers
    print(x, y, merged, bent)
It runs right here in your browser, with nothing to install.

The y and merged columns always agree. Only the last column, with ReLU, can differ. It does at x=−1x = -1.

With many numbers at once

A real layer handles a whole list of numbers. Its weights sit in a table called a matrix, which you met in Phase 2. Call layer 1's weight table W1W_1. Its list of biases is b1\mathbf{b}_1. The input list is x\mathbf{x}, so layer 1 gives h=W1x+b1\mathbf{h} = W_1 \mathbf{x} + \mathbf{b}_1. Layer 2 has its own W2W_2 and b2\mathbf{b}_2. Feed one into the other and you get:

W2(W1x+b1)+b2=(W2W1) x+(W2b1+b2)W_2 (W_1 \mathbf{x} + \mathbf{b}_1) + \mathbf{b}_2 = (W_2 W_1)\, \mathbf{x} + (W_2 \mathbf{b}_1 + \mathbf{b}_2)

In plain words: two layers in a row equal one layer. Its weights are W2W1W_2 W_1 and its biases are W2b1+b2W_2 \mathbf{b}_1 + \mathbf{b}_2. It's the dollars-to-yen shortcut, with tables.

However many of these layers you stack, the result can only draw straight-line boundaries. XOR, from the last lesson, stays out of reach. NumPy confirms it with random weights:

import numpy as np

rng = np.random.default_rng(0)
W1, b1 = rng.normal(size=(4, 3)), rng.normal(size=4)
W2, b2 = rng.normal(size=(2, 4)), rng.normal(size=2)
x = np.array([1.0, -2.0, 0.5])

two_layers = W2 @ (W1 @ x + b1) + b2
one_layer = (W2 @ W1) @ x + (W2 @ b1 + b2)
print("two linear layers:", np.round(two_layers, 4))
print("one merged layer: ", np.round(one_layer, 4))

with_relu = W2 @ np.maximum(0, W1 @ x + b1) + b2
print("with ReLU between:", np.round(with_relu, 4))

The first two lines match exactly: [-0.2398 3.0556]. With ReLU in between, the answer changes to [0.5434 1.6494], and no single merged layer can copy it.

Put a bend between the layers and the merge no longer works. Each layer can now bend what it receives. Stacks of bends can trace almost any shape.

Go deeper Why the merge works

Multiplying out brackets works for matrices the same way it works for numbers: W2(W1x+b1)=W2W1x+W2b1W_2 (W_1 \mathbf{x} + \mathbf{b}_1) = W_2 W_1 \mathbf{x} + W_2 \mathbf{b}_1. Add b2\mathbf{b}_2 and group the terms. The part that multiplies x\mathbf{x} is the new weight table. The part that doesn't is the new bias. Put ReLU around the inner bracket and this fails. ReLU treats positive and negative numbers differently, so the bracket can't be multiplied out any more.

Quick check
A 10-layer network uses no activation functions. What can it represent?
  1. Anything a 10-layer ReLU network can, because depth is what adds the power
  2. Only XOR and other problems that a single straight line can't separate
  3. Nothing at all, because a network with no activations can't be trained
  4. Only linear functions, the same as a single layer
Show the answer

Only linear functions, the same as a single layer

Linear layers compose into one linear layer: W₁₀ ⋯ W₁ x + (combined bias).

Try it now

Drag along ReLU and watch its slope: exactly 1 for positive inputs and 0 for negative ones. Sigmoid and tanh flatten out at both ends.

The common activations

Each activation takes one number in and gives one number out. Call the number going in zz. It's the neuron's weighted sum plus bias, from the last lesson.

  • ReLU keeps positive numbers and turns negative ones into 0. Think of a bank account that can't go below zero.
  • Leaky ReLU keeps positive numbers too. Negative ones shrink to 1% of their size instead of vanishing, so −4 becomes −0.04.
  • Sigmoid squeezes any number into the range 0 to 1. You used it in logistic regression.
  • tanh (short for "hyperbolic tangent") has the same S-shape as sigmoid, but its range is −1 to 1.
  • GELU is a smooth version of ReLU. Big positive numbers pass almost unchanged. Big negative ones fade to nearly 0. In between it curves gently, and dips a little below 0.

Here they are side by side. The letter ee is the number about 2.718 from the exponentials lesson. The symbol Φ\Phi in GELU's row is explained just below the table.

ActivationFormulaOutput rangeWhere it's used
Sigmoid1/(1+e−z)1/(1+e^{-z})0 to 1output layer for yes/no probabilities
tanhtanh⁡z\tanh z−1 to 1older networks, RNNs
ReLUmax⁡(0,z)\max(0, z)0 to ∞default for hidden layers in MLPs and CNNs
Leaky ReLUzz if z>0z>0, else 0.01z0.01z−∞ to ∞a ReLU that never fully switches off
GELUz⋅Φ(z)z \cdot \Phi(z)about −0.17 to ∞transformers (BERT, GPT-2 and many since)

In plain words: ReLU is "the number or 0, whichever is bigger". Leaky ReLU is "the number, or 1% of it if it's negative". Sigmoid and tanh are S-shaped squashers. GELU is "the number, times a share between 0 and 1 that grows with the number".

In GELU, Φ\Phi (capital phi, said "fie") is the normal distribution's cumulative probability. It's the chance that a value from the standard bell curve (average 0, spread 1) lands below zz, so it's always between 0 and 1. Many recent LLMs use SwiGLU instead. It's a gated cousin of these: it multiplies two linear projections, one of them passed through a smooth activation.

A hidden layer is any layer before the last one. An MLP is a plain stack of layers (the next lesson). CNNs are networks for images, and RNNs are older networks for sequences. You'll meet both later.

Worked example

Worked example: three inputs, five activations

Push z = −1, 0 and 2 through each activation by hand first. ReLU gives 0, 0, 2. Leaky ReLU gives −0.01, 0, 2. Then run the code to see the curved ones.

import math

print("z, ReLU, leaky ReLU, sigmoid, tanh, GELU")
for z in [-1.0, 0.0, 2.0]:
    relu = max(0.0, z)
    leaky = z if z > 0 else 0.01 * z
    sigmoid = 1 / (1 + math.exp(-z))
    tanh = math.tanh(z)
    gelu = z * 0.5 * (1 + math.erf(z / math.sqrt(2)))   # z times Phi(z)
    print(z, relu, leaky, round(sigmoid, 3), round(tanh, 3), round(gelu, 3))
In the lesson you can change this and run it.

At z = 0, sigmoid gives exactly 0.5 and the others give 0. At z = 2, sigmoid gives 0.881 and tanh gives 0.964: both are already close to their ceilings. GELU gives 1.954, a bit under ReLU's

  1. At z = −1, GELU gives −0.159, a small dip below 0 that ReLU never makes. The math.erf line

is the standard library's way to get Φ\Phi. You don't need its details.

Quick check
What does this print?
z = -4.0
relu = max(0.0, z)
leaky = z if z > 0 else 0.01 * z
print(relu, leaky)
  1. -4.0 -0.04
  2. 0.0 -0.04
  3. 0.0 0.0
  4. 4.0 0.04
Show the answer

0.0 -0.04

ReLU turns any negative into 0. Leaky ReLU keeps 1% of a negative: 0.01 × −4 = −0.04.

What the slopes do to learning

An activation's slope says how much its output moves when its input moves a little. You met slopes in the calculus lessons.

Slopes matter because of how training works. Training sends an error signal backward through the network. At each layer, the chain rule multiplies that signal by the activation's slope. A slope of 1 lets the whole signal through. A slope of 0.1 lets a tenth through. A slope of 0 blocks it. Think of a row of taps on a pipe: each one can open fully, open a little, or shut.

So the slope decides how much learning signal gets through. For sigmoid, written σ\sigma (the Greek letter sigma), the slope has a neat form. The mark σ′\sigma' means "the slope of sigma":

σ′(z)=σ(z) (1−σ(z))\sigma'(z) = \sigma(z)\,\bigl(1 - \sigma(z)\bigr)

In plain words: the sigmoid's slope is its output times "one minus its output".

Try it with small numbers. At z=0z = 0, the sigmoid gives 0.5, so the slope is 0.5×0.5=0.250.5 \times 0.5 = 0.25. That's the biggest it ever gets. At z=2z = 2, the sigmoid gives about 0.881, so the slope is about 0.881×0.119≈0.1050.881 \times 0.119 \approx 0.105. The other slopes are simpler. ReLU's is 1 for positive inputs and 0 otherwise. Leaky ReLU's is 1 or 0.01. The code below works out all four at five points (tanh's slope is 1 - np.tanh(z) ** 2):

import numpy as np

z = np.array([-6.0, -2.0, 0.0, 2.0, 6.0])
sig = 1 / (1 + np.exp(-z))
slopes = {
    "sigmoid": sig * (1 - sig),
    "tanh": 1 - np.tanh(z) ** 2,
    "ReLU": (z > 0).astype(float),
    "leaky ReLU": np.where(z > 0, 1.0, 0.01),
}
print("z:          ", z)
for name, s in slopes.items():
    print(f"{name:<12}", np.round(s, 4))

Sigmoid's slope peaks at 0.25 and falls to 0.0025 at ±6. Tanh's peaks at 1 but is about 0 at ±6. ReLU's is 0 or 1, nothing in between.

  • Saturation: sigmoid and tanh go flat at both ends. Picture a dimmer switch already turned all the way up: turning it further changes nothing. A neuron pushed to a large positive or negative input passes almost no signal back. Even at best, the sigmoid's slope is only 0.25. Multiply ten of those together and you get less than one millionth. Through deep layers the signal vanishes. A later lesson covers this.
  • ReLU passes the signal through unchanged for any positive input. That's why deep networks became trainable once it caught on, around 2010–2012. It's also cheap to compute.
  • Dead ReLUs: some ReLU neurons get a negative input for every example. Their slope is then 0 every time, so their weights never change again: no gradient reaches those weights, so they stop changing. Leaky ReLU and GELU keep a small slope to avoid this.
Go deeper Where the sigmoid's slope comes from

Write s=σ(z)=1/(1+e−z)s = \sigma(z) = 1/(1+e^{-z}). Taking the slope with the chain rule gives e−z/(1+e−z)2e^{-z}/(1+e^{-z})^2. Split that into two fractions: 1/(1+e−z)1/(1+e^{-z}), which is ss, times e−z/(1+e−z)e^{-z}/(1+e^{-z}), which is 1−s1 - s. So the slope is s(1−s)s(1-s). The product of two numbers that add up to 1 is largest when both are 0.5. That's why the slope never beats 0.5×0.5=0.250.5 \times 0.5 = 0.25.

Quick check
A sigmoid neuron receives z = 6. Roughly how much of the learning signal passes back through it?
  1. About 0.0025 of it, almost nothing
  2. A quarter: the slope is 0.25
  3. Six times the signal
  4. All of it: the slope is 1
Show the answer

About 0.0025 of it, almost nothing

At z = 6 the sigmoid is saturated. Its output is about 0.9975, so its slope is about 0.9975 × 0.0025 ≈ 0.0025.

Choosing

Pick hidden-layer activations and output activations separately. They answer different questions.

  • Hidden layers: ReLU for MLPs and CNNs. GELU or SwiGLU in transformers, the design behind LLMs. Don't use sigmoid there: it saturates.
  • Output layer: this depends on the task, not on the hidden layers. Use sigmoid for one probability, such as "will this email get opened?". Use softmax for one-of-many classes (Phase 2). Use none at all (a linear output) to predict a number, such as next month's sales.
Quick check
Your network predicts tomorrow's temperature in °F, which can be below zero. What should the output layer use?
  1. ReLU
  2. Sigmoid
  3. No activation: a plain linear output
  4. Softmax
Show the answer

No activation: a plain linear output

Predicting a number calls for a linear output. Sigmoid would cap it between 0 and 1, softmax is for picking a class, and ReLU would block every temperature below zero.

Practice what you just read

  • 4 code examples you can edit and run
  • a practice exercise that checks your code
  • a 5-question quiz
  • 5 review cards for spaced repetition

Create a free account

Try a quiz question

Which activation is the usual default for hidden layers in a plain feed-forward network?

  1. ReLU
  2. Sigmoid
  3. Softmax
  4. None

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson