Deep Sequence Modelling (RNN)
Lesson 3RNN internal mechanics
The hidden state, recurrence equations, and formal structure
Now that we have an intuitive foundation for recurrent neural networks (RNNs), let us formalize their internal structure and operational semantics. This section builds upon our understanding of the perceptron, the idea of recurrence, and the motivation behind modeling sequences through memory-aware architectures.
Recurrence relations in RNNs
At the heart of an RNN lies the internal state ht, which is updated as the network processes the input sequence step by step. The update to this state is defined by a recurrence relation, which incorporates both the current input xt and the prior hidden state ht−1. This recurrence relation enables the model to retain and evolve a memory of the sequence it has observed so far.
RNN cell & recurrence relation

The state update can be formally described as:
| Symbol | Role |
|---|---|
| Wxh | weight matrix mapping input to hidden state |
| Whh | recurrent weight matrix mapping previous state to current state |
| Why | output weight matrix mapping hidden state to predicted output |
| σ | element-wise nonlinearity (e.g., tanh or sigmoid), tanh outputs values between −1 and 1, while sigmoid outputs between 0 and 1. |
| bh, by | bias terms |
Weight sharing
These same weight matrices are shared across all time steps, which enforces temporal consistency and greatly reduces the number of trainable parameters.
RNN logic through pseudocode
To further solidify this idea, consider a simple pseudocode example that illustrates how an RNN operates over a sequence:
# Initialize RNN hidden state
h = 0
# Input sequence of words
sentence = ["I", "love", "recurrent", "neural"]
# RNN loop over time
for word in sentence:
h = update_hidden_state(x=word, h_prev=h)
y_hat = predict_output(h)In this code snippet, each word in the input sequence is processed one at a time. The RNN uses the current word and the previously computed hidden state to compute a new hidden state. This new state is then used to produce an output prediction. The process is iterative and maintains a memory of the sequence through the hidden state updates.
Unrolled representation and weight sharing
We can visualize the RNN in two complementary ways:
Cyclic form
A looped computation graph where the hidden state is fed back into itself.
Unrolled form
A sequence of computations unwrapped across time, the same operations applied at each time step.
In the unrolled representation:
| Aspect | What happens |
|---|---|
| Inputs | The sequence x₁, x₂, …, xT flows across time steps. |
| Hidden state | ht at each time is passed forward. |
| Outputs | yt is generated at each step. |
| Parameters | The same Wxh, Whh, Why at every time step, weight sharing for variable length and fewer parameters. |
Walk through the full forward pass below, stage by stage, timestep by timestep. Every matrix multiply is computed live; every value shown is the real arithmetic.
Inside the RNN cell · stage by stage
Five operations make up one RNN timestep: read input → project via W_xh → add the recurrent term via W_hh → squash with tanh → emit output via W_hy. Watch each stage with real arithmetic.
Stage 1 · Read x_t
The sequence feeds in one timestep at a time
x_1
x_1 ∈ ℝ^3
Defining loss over time
Training an RNN requires defining a loss function that quantifies the error between predicted and actual outputs. Unlike static models, where a single loss is computed, RNNs compute a loss at each time step. The total loss across a sequence is then the sum of the losses at individual steps:
This loss drives learning through Backpropagation Through Time (BPTT), where gradients are computed not only over network layers but also across time steps.
Implementation in code
Let us now translate this conceptual understanding into a more explicit code-level implementation.
Consider defining an RNN from scratch using an object-oriented approach:
class SimpleRNN:
def __init__(self, input_dim, hidden_dim, output_dim):
self.W_xh = initialize_weights(input_dim, hidden_dim)
self.W_hh = initialize_weights(hidden_dim, hidden_dim)
self.W_hy = initialize_weights(hidden_dim, output_dim)
self.b_h = initialize_bias(hidden_dim)
self.b_y = initialize_bias(output_dim)
def forward(self, input_sequence):
h = np.zeros(self.hidden_dim)
outputs = []
for x_t in input_sequence:
h = activation(np.dot(x_t, self.W_xh) + np.dot(h, self.W_hh) + self.b_h)
y_hat = np.dot(h, self.W_hy) + self.b_y
outputs.append(y_hat)
return outputsThis class encapsulates:
Initialization of weights and biases
Forward pass through the sequence, updating hidden states
Prediction generation at each step
This procedural design closely mirrors the theoretical recurrence structure discussed earlier. It also forms the basis for understanding more complex implementations in TensorFlow or PyTorch.
Run it yourself
Press Run to execute a real SimpleRNN forward pass over a 3-step sequence. The same W_xh, W_hh, W_hy are reused at every step, that's the recurrence:
From scratch to frameworks
Once you understand the fundamentals of how an RNN functions internally, you can transition to using high-level abstractions in frameworks such as TensorFlow and PyTorch. Both offer RNN modules and layers that encapsulate the forward pass, weight sharing, and even support for batching and sequence padding.
You will practice this in the upcoming lab, where you will implement RNNs for next-word prediction, character generation, and many-to-many sequence classification tasks.
Applications and broader impact
Understanding and building RNNs from scratch equips you to solve sequence-modeling problems across the many-to-one, one-to-many, and many-to-many settings (Lesson 1).
This last setting, many-to-many modeling, is foundational to how large language models function today. Although modern architectures such as Transformers have largely replaced vanilla RNNs due to their scalability and parallelism, the core concepts of sequence modeling, memory, and recurrence remain essential.
Check your understanding
1 / 8The vanilla RNN hidden-state update is h_t = σ(W_xh · x_t + W_hh · h_{t-1} + b_h). What is the role of W_hh?
Practice it yourself
Build the RNN's machinery — the readout, the loss over time, and the full forward pass. Real Python, hidden tests, no setup.