CAS logoCASCenter for Advanced Studies · AI Engineering Program

DEEP DIVE TUTORIAL · RNN · NUMERICAL WALKTHROUGH

RNN Forward Pass & Backprop by Hand

A complete numerical example: the sentence "was not bad" enters as one-hot vectors, and we compute every single value — forward and backward — with real numbers you can verify on a calculator.

WHAT YOU WILL COMPUTE

We use a toy RNN with V=3 (vocab), H=2 (hidden), C=2 (classes). Every matrix multiplication, every tanh, every softmax probability, and every gradient — computed explicitly with actual values.

01

Forward pass: 3 timesteps, every at and ht with 4-decimal precision

02

Softmax output & cross-entropy loss with exact numbers

03

Backprop: δt at every timestep with intermediate products shown

04

Total gradients for all 5 parameter tensors, summed across time

05

Gradient descent update with η = 0.1 — the new weights

No black boxes. Every number below was computed by a Python script — you can reproduce every step.

01 THE EXAMPLE

Sentence: "was not bad"

Three words. Target: POSITIVE (because "not bad" is good). The model must read one word at a time and build a hidden memory.

VOCABULARY · 3 WORDS

IndexWordOne-hot
0was[1, 0, 0]
1not[0, 1, 0]
2bad[0, 0, 1]

Vocab size |V| = 3. Hidden dim H = 2. Classes C = 2 (negative, positive).

Initial Parameters (the weights we will learn from)

Wxh (2 × 3)

[[ 0.2, 0.1, -0.1],
[-0.1, 0.2, 0.1]]

Whh (2 × 2)

[[ 0.3, 0.2],
[-0.1, 0.3]]

Why (2 × 2)

[[ 0.2, -0.1],
[ 0.1, 0.3]]

Biases

bh = [ 0.1, -0.1]
by = [ 0.0, 0.0]

Parameters are set. Now we process each word — one timestep at a time, with every number shown.

02 FORWARD PASS

Every Number, Every Step

We initialize h−1 = [0, 0]. At each timestep: at = Wxh·xt + Whh·ht−1 + bh, then ht = tanh(at).

LIVE DEMO

Watch the network think

Step through the forward pass one timestep at a time. See values flow through weights, hidden states update, and the final prediction emerge. Every number is real — from the example above.

Step 1 of 4 — Initialization

FOLDED RNN CELL
INPUT xt
[0, 0, 0]
Wxh
PRE-ACTIVATION at
[0.0000, 0.0000]
tanh
HIDDEN ht
[0.0000, 0.0000]
Whh
+ bh
Why
LOGIT z
[0.0000, 0.0000]
softmax
OUTPUT ŷ
[0.0000, 0.0000]
Computation at this step

Press Next to begin the forward pass.

Current Weight Values
Wxh
0.200.10−0.10
−0.100.200.10
Whh
0.300.20
−0.100.30
bh
0.10−0.10
Why
0.20−0.10
0.100.30
by
0.000.00
TIMESTEP 0

Word: "was" · x₀ = [1, 0, 0]

Start with the zero hidden state. The one-hot x₀ selects column 0 of Wxh.

a₀ = Wxh·x₀ + Whh·h−1 + bh a₀ = [0.2, −0.1] + [0.0, 0.0] + [0.1, −0.1] a₀ = [0.3000, −0.2000]

Apply tanh to squash into (−1, 1):

h₀ = tanh(a₀) = [tanh(0.3000), tanh(−0.2000)] h₀ = [0.2913, −0.1974]
TIMESTEP 1

Word: "not" · x₁ = [0, 1, 0]

Now the recurrence kicks in. We multiply Whh by the previous hidden state h₀.

Whh·h₀ = [0.3·0.2913 + 0.2·(−0.1974), −0.1·0.2913 + 0.3·(−0.1974)]               = [0.0874 − 0.0395, −0.0291 − 0.0592]               = [0.0479, −0.0883]

Add the input contribution (column 1 of Wxh) and bias:

a₁ = [0.1, 0.2] + [0.0479, −0.0883] + [0.1, −0.1] a₁ = [0.2479, 0.0117]

Apply tanh:

h₁ = tanh(a₁) = [tanh(0.2479), tanh(0.0117)] h₁ = [0.2430, 0.0117]
TIMESTEP 2

Word: "bad" · x₂ = [0, 0, 1]

Same computation — Whh·h₁, plus column 2 of Wxh, plus bias.

Whh·h₁ = [0.3·0.2430 + 0.2·0.0117, −0.1·0.2430 + 0.3·0.0117]               = [0.0729 + 0.0023, −0.0243 + 0.0035]               = [0.0752, −0.0208]
a₂ = [−0.1, 0.1] + [0.0752, −0.0208] + [0.1, −0.1] a₂ = [0.0752, −0.0208]
h₂ = tanh(a₂) = [tanh(0.0752), tanh(−0.0208)] h₂ = [0.0751, −0.0208]
OUTPUT

Softmax Prediction

Use the final hidden state h₂ to compute logits z, then softmax.

z = Why·h₂ + by z = [0.2·0.0751 + (−0.1)·(−0.0208), 0.1·0.0751 + 0.3·(−0.0208)]    = [0.0150 + 0.0021, 0.0075 − 0.0062]    = [0.0171, 0.0013]

Softmax: exponentiate, sum, divide.

ez₀ = e0.0171 = 1.0172    ez₁ = e0.0013 = 1.0013 Σezᵢ = 1.0172 + 1.0013 = 2.0185 ŷ₀ = 1.0172 / 2.0185 = 0.5040 (negative) ŷ₁ = 1.0013 / 2.0185 = 0.4960 (positive)
Prediction: NEGATIVE (50.4% confidence) — the model is wrong! True label is POSITIVE.

The model predicted negative but the true label is positive. Time to compute the loss and blame the right weights.

03 LOSS

Cross-Entropy with Real Numbers

LOSS

How surprised is the model?

True label y = [0, 1] (positive). Model output ŷ = [0.5040, 0.4960].

L = − Σ yᵢ · log(ŷᵢ) L = − ( 0 · log(0.5040) + 1 · log(0.4960) ) L = − log(0.4960) = 0.7011
A perfect confident prediction (ŷ₁ = 0.9999) would give L ≈ 0.0001. Our model is uncertain and wrong — the loss is high.

Loss = 0.7011. Now we compute which weights are responsible — by propagating gradients backward through every timestep.

04 BACKPROPAGATION

Every Gradient, Every Product

We compute ∂L/∂Wxh, ∂L/∂Whh, ∂L/∂Why, and both biases. Starting from the output and walking back to t=0.

STEP A

Output Error Signal

For softmax + cross-entropy, the gradient simplifies to ŷ − y.

∂L/∂z = ŷ y ∂L/∂z = [0.5040, 0.4960] [0, 1] ∂L/∂z = [0.5040, −0.5040]
STEP B

Gradients for Why and by

∂L/∂Why = (ŷ−y) · h₂ᵀ               = [[0.5040·0.0751, 0.5040·(−0.0208)], [−0.5040·0.0751, −0.5040·(−0.0208)]]               = [[0.0378, −0.0105], [−0.0378, 0.0105]]
∂L/∂by = [0.5040, −0.5040]
STEP C

Initial Hidden Gradient δ₂

δ₂ = Why · (ŷ−y)     = [0.2·0.5040 + 0.1·(−0.5040), −0.1·0.5040 + 0.3·(−0.5040)]     = [0.1008 − 0.0504, −0.0504 − 0.1512]     = [0.0504, −0.2016]
STEP D

Gradients at t = 2 (word "bad")

First compute the tanh derivative at a₂: (1 − tanh²).

1 − tanh²(a₂) = [1 − tanh²(0.0752), 1 − tanh²(−0.0208)]                       = [0.9944, 0.9996]

Multiply δ₂ element-wise with the tanh derivative:

g₂ = δ₂ (1−tanh²) = [0.0504·0.9944, −0.2016·0.9996]     = [0.0501, −0.2015]

Now accumulate gradients. For Wxh, g₂ multiplies with x₂ᵀ = [0, 0, 1] — only column 2 survives:

∂L/∂Wxh at t=2 = [[0, 0, 0.0501], [0, 0, −0.2015]]
∂L/∂Whh at t=2 = g₂ · h₁ᵀ = [[0.0122, 0.0006], [−0.0490, −0.0023]]
∂L/∂bh at t=2 = [0.0501, −0.2015]
STEP E

Backprop to t = 1 (word "not")

Propagate the gradient backward through Whhᵀ:

δ₁ = Whh · g₂     = [0.3·0.0501 + (−0.1)·(−0.2015), 0.2·0.0501 + 0.3·(−0.2015)]     = [0.0150 + 0.0201, 0.0100 − 0.0604]     = [0.0352, −0.0504]

Apply tanh derivative at a₁:

1 − tanh²(a₁) = [0.9410, 0.9999] g₁ = δ₁ (1−tanh²) = [0.0331, −0.0504]
∂L/∂Wxh at t=1 = [[0, 0.0331, 0], [0, −0.0504, 0]]
∂L/∂Whh at t=1 = g₁ · h₀ᵀ = [[0.0096, −0.0065], [−0.0147, 0.0100]]
∂L/∂bh at t=1 = [0.0331, −0.0504]
STEP F

Backprop to t = 0 (word "was")

δ₀ = Whh · g₁     = [0.3·0.0331 + (−0.1)·(−0.0504), 0.2·0.0331 + 0.3·(−0.0504)]     = [0.0099 + 0.0050, 0.0066 − 0.0151]     = [0.0150, −0.0085]
1 − tanh²(a₀) = [0.9151, 0.9610] g₀ = δ₀ (1−tanh²) = [0.0137, −0.0082]
∂L/∂Wxh at t=0 = [[0.0137, 0, 0], [−0.0082, 0, 0]]
∂L/∂Whh at t=0 = [[0, 0], [0, 0]] (because h₋₁ = 0)
∂L/∂bh at t=0 = [0.0137, −0.0082]
TOTALS

Sum Gradients Across All Timesteps

Add the contributions from t=0, t=1, and t=2 for each parameter.

∂L/∂Wxh = [[0.0137+0+0, 0+0.0331+0, 0+0+0.0501],                     [−0.0082+0+0, 0−0.0504+0, 0+0−0.2015]]                     = [[0.0137, 0.0331, 0.0501], [−0.0082, −0.0504, −0.2015]]
∂L/∂Whh = [[0+0.0096+0.0122, 0−0.0065+0.0006],                     [0−0.0147−0.0490, 0+0.0100−0.0023]]                     = [[0.0218, −0.0060], [−0.0636, 0.0076]]
∂L/∂bh = [0.0137+0.0331+0.0501, −0.0082−0.0504−0.2015]           = [0.0969, −0.2601]
CHECKPOINT: You now have every gradient. The update rule is: W ← W − 0.1 · (∂L/∂W).

Gradients computed. One final step: apply gradient descent and see the new weights.

05 PARAMETER UPDATE

Gradient Descent: New Weights

Learning rate η = 0.1. We subtract 10% of each gradient from its parameter.

Wxh BEFORE

[[ 0.2, 0.1, −0.1],
[−0.1, 0.2, 0.1]]

Wxh AFTER (new)

[[0.1986, 0.0967, −0.1050],
[−0.0992, 0.2050, 0.1202]]

Whh BEFORE

[[ 0.3, 0.2],
[−0.1, 0.3]]

Whh AFTER (new)

[[0.2978, 0.2006],
[−0.0936, 0.2992]]

Why BEFORE

[[ 0.2, −0.1],
[ 0.1, 0.3]]

Why AFTER (new)

[[0.1962, −0.0989],
[0.1038, 0.2989]]

BIASES BEFORE

bh = [ 0.1, −0.1]
by = [ 0.0, 0.0]

BIASES AFTER (new)

bh = [0.0903, −0.0740]
by = [−0.0504, 0.0504]
OBSERVATION

Notice Why[1,0] increased from 0.1 → 0.1038, and Wxh[1,2] (the weight for "bad"→hidden[1]) increased from 0.1 → 0.1202. The model is learning to boost the positive signal. After many iterations, it would correctly predict POSITIVE.

The complete loop: forward → loss → backprop → update. Let us test your understanding with a quick quiz.

06 KNOWLEDGE CHECK

Verify Your Understanding

Instant feedback — no grades, just clarity. Every answer references the actual numbers above.

Q1

What is h₀ after reading "was"?

Q2

What was the model's prediction for "was not bad"?

Q3

What is the cross-entropy loss L?

Q4

Why is ∂L/∂Whh at t=0 equal to [[0,0],[0,0]]?

Q5

After the update, what happened to bh[1]?