March 31, 2026
Last lecture we hit a wall: the VQ-VAE produces a grid of codebook indices, but generating those indices from scratch requires modeling their sequential dependencies.
\[P(s_1, s_2, \ldots, s_{16}) = \prod_i P(s_i \,|\, s_1, \ldots, s_{i-1})\]
But sequences go far beyond image tokens.
Language is a sequence of words. Speech is a sequence of audio frames. Video is a sequence of images. Music, DNA, stock prices — any data where order matters.
The tools we build over the next few lectures apply to all of them.
Thus far, we have only seen deep learning architectures where information flows upward through the model
Feedforward in the sense that the only direct influence of a layer is to its child
For tabular data and image analysis:
Not necessarily equal number of inputs and outputs
Equal number of outputs and inputs
A feedforward network maps one fixed-size input to one fixed-size output. No memory. No sense of order.
We could concatenate the entire sequence into one long vector and feed it through an MLP. But sequences have variable length, and the model would need position-specific weights for every possible length.
No weight sharing across time. No generalization to new sequence lengths.
If the order of the input matters, we need an architecture that preserves it.
Recurrent Neural Networks (RNNs) are the backbone of sequence modeling
Adds knowledge to the NN architecture
If the order of the input matters, then we should preserve that information!
We can process a sequence of vectors, \(\mathbf{x}\), by applying a recurrence formula at every time step:
\[\mathbf{h}_t = f_w(\mathbf{h}_{t-1}, \mathbf{x}_t)\]
The hidden state vector contains latent information about the current state of the model
This information should persist throughout the sequence of predictions!
The hidden state is a latent vector — a compressed representation of the sequence, just like \(\mathbf{z}\) in the VAE is a compressed representation of an image.
Start with an initial state, \(\mathbf{h}_0\)
\[\underset{(m \times 1)}{\mathbf{h}_t} = \text{tanh}\left(\underset{(m \times m)}{\mathbf{W}_{hh}} \underset{(m \times 1)}{\mathbf{h}_{t-1}} + \underset{(m \times P)}{\mathbf{W}_{xh}} \underset{(P \times 1)}{\mathbf{x}_t} + \underset{(m \times 1)}{\mathbf{b}_h}\right)\]
\[\underset{(m \times 1)}{\mathbf{h}_t} = \text{tanh}\left(\underset{(m \times m)}{\mathbf{W}_{hh}} \underset{(m \times 1)}{\mathbf{h}_{t-1}} + \underset{(m \times P)}{\mathbf{W}_{xh}} \underset{(P \times 1)}{\mathbf{x}_t} + \underset{(m \times 1)}{\mathbf{b}_h}\right)\]
Drawing some parallels to what we’ve seen before:
A FCNN with a twist!
Note that \(\mathbf{x}_1\) only relates to \(\mathbf{y}_1\) through \(\mathbf{h}_1\)!
Given a hidden state, we say that \(\mathbf{y}_t\) is determined as:
\[\underset{(G \times 1)}{\mathbf{y}_t} = g\left(\underset{(G \times m)}{\mathbf{W}_{hy}} \underset{(m \times 1)}{\mathbf{h}_t} + \underset{(G \times 1)}{\mathbf{b}_y}\right)\]
The part that is so simple that it’s kinda dumb
Use the same \(\mathbf{W}_{hh}\), \(\mathbf{W}_{xh}\), and \(\mathbf{W}_{hy}\) at all time points
Let’s think about how \(\mathbf{y}_2\) is computed.
Let \(\xi()\) be the \(\text{tanh}\) activation. Dropping bias terms:
\[\mathbf{y}_2 = \mathbf{W}_{hy} \xi(\mathbf{W}_{hh} \xi(\mathbf{W}_{hh} \mathbf{h}_0 + \mathbf{W}_{xh} \mathbf{x}_1) + \mathbf{W}_{xh} \mathbf{x}_2)\]
Every output sees the full history through the chain of hidden states.
Let’s look at an aligned many-to-many model!
Goal: Given input characters, build a model to predict the next character!
For test-time, generate new text
This is autoregressive generation — the same process we need for VQ-VAE codebook indices. Predict the next token, feed it back, predict the next.
Right now, a basic one-hot encoded character vector.
What if we want to see something that was not necessarily used to train the model?
Text embeddings are used to pre-process text (an inherently discrete variable) into a lower dimensional continuous space
Same idea as the categorical embeddings we used for breed and color in the VQ-VAE!
self.embed = nn.Embedding(vocab_size, embed_dim)
# "h" → learned 32-dim vector
# "e" → different learned 32-dim vectorMany-to-One:
One-to-Many:
Pre-GPT, the seq2seq model was a main approach for question/answer and translation models
This is another example of an encoder/decoder architecture:
“The cat sat on the mat” \(\to\) encoder \(\to\) \(\mathbf{h}_6\) \(\to\) decoder \(\to\) “That durn cat is sittin on the rug”
The entire input sentence — every word, every grammatical relationship — must be compressed into a single vector \(\mathbf{h}_T\).
Sound familiar? This is the same information bottleneck as the VAE’s flat vector.
Tanh is used here over ReLU for a number of reasons:
But tanh has a problem we’re about to see.
Given \(\mathbf{X}\) and \(\mathbf{Y}\), we need to learn \(\mathbf{W}_{hh}\), \(\mathbf{W}_{xh}\), and \(\mathbf{W}_{hy}\)
The loss sums over all time steps:
\[\mathcal{L}(\hat{\mathbf{Y}}, \mathbf{Y}) = \sum_{t=1}^{T} \mathcal{L}(\hat{\mathbf{y}}_t, \mathbf{y}_t)\]
The gradient for \(\mathbf{W}_{hh}\):
\[\frac{\partial \mathcal{L}}{\partial \mathbf{W}_{hh}} = \frac{\partial \mathcal{L}}{\partial \mathbf{h}_T}\frac{\partial \mathbf{h}_T}{\partial \mathbf{h}_{T-1}}\frac{\partial \mathbf{h}_{T-1}}{\partial \mathbf{h}_{T-2}} \cdots \frac{\partial \mathbf{h}_1}{\partial \mathbf{W}_{hh}}\]
Assuming tanh activation:
\[\frac{\partial \mathbf{h}_t}{\partial \mathbf{h}_{t-1}} = \text{tanh}'(\mathbf{W}_{hh}\mathbf{h}_{t-1} + \mathbf{W}_{xh}\mathbf{x}_t) \cdot \mathbf{W}_{hh}\]
A product of \(T\) terms, each involving \(\text{tanh}'\) and \(\mathbf{W}_{hh}\).
The derivative of tanh w.r.t its input is always between 0 and 1!
\[\frac{\partial \mathcal{L}}{\partial \mathbf{W}_{hh}} = \frac{\partial \mathcal{L}}{\partial \mathbf{h}_T} \frac{\partial \mathbf{h}_1}{\partial \mathbf{W}_{hh}} \mathbf{W}_{hh}^{T-1} \prod_{t=2}^{T} \text{tanh}'(\mathbf{W}_{hh}\mathbf{h}_{t-1} + \mathbf{W}_{xh}\mathbf{x}_t)\]
Double trouble:
There’s no escape…
Assuming linear activation functions:
\[\mathbf{y}_2 = \mathbf{W}_{hy} \mathbf{W}_{hh} \mathbf{W}_{hh} \mathbf{h}_0 + \mathbf{W}_{hy} \mathbf{W}_{hh} \mathbf{W}_{xh} \mathbf{x}_1 + \mathbf{W}_{hy} \mathbf{W}_{xh} \mathbf{x}_2\]
\[\mathbf{y}_3 = \mathbf{W}_{hy} \mathbf{W}_{xh} \mathbf{x}_3 + \mathbf{W}_{hy} \mathbf{W}_{hh} \mathbf{W}_{xh} \mathbf{x}_2 + \mathbf{W}_{hy} \mathbf{W}_{hh}^2 \mathbf{W}_{xh} \mathbf{x}_1 + \mathbf{W}_{hy} \mathbf{W}_{hh}^3 \mathbf{h}_0\]
The influence of \(\mathbf{x}_1\) on \(\mathbf{y}_3\) goes through \(\mathbf{W}_{hh}^2\). On \(\mathbf{y}_{100}\) it goes through \(\mathbf{W}_{hh}^{99}\).
Small times small equals smaller!
This is also why Seq2Seq struggles with long sequences.
The entire input sentence — every word, every grammatical relationship — must be compressed into a single vector \(\mathbf{h}_T\).
For a 50-word paragraph, \(\mathbf{h}_T\) is the same size as for a 5-word sentence. Information loss is inevitable.
This is the same information bottleneck as the VAE’s flat vector. And the fix will be analogous — instead of compressing everything into one vector, let the decoder access the full sequence of encoder states.
That fix is called attention — next lecture.
A clever solution to the vanishing gradient problem — The Long Short-Term Memory model (LSTM)
There’s a lot going on here. Let’s take it step-by-step.
LSTMs alter the RNN model by having two concurrently running hidden units — the hidden state and the cell state
The cell state runs straight down the chain with only some minor linear interactions
The hidden state can add or remove information from the cell state through gates
A sigmoid layer (which maps a value between 0 and 1) determines when and how much the hidden state should influence the next one!
LSTMs have 3 of these gates to protect and control the cell state.
The forget gate controls how we lose information from the previous memory
The sigmoid activation is applied element-wise — some parts of the vector might be more useful to remember than others.
Two weight matrices — the input weights and the candidate gate
The new cell state is a linear combination of the old cell state and the candidate, weighted by the forget gate and the input gate:
\[\mathbf{C}_t = \mathbf{f}_t \odot \mathbf{C}_{t-1} + \mathbf{i}_t \odot \tilde{\mathbf{C}}_t\]
The gradient:
\[\frac{\partial \mathbf{C}_t}{\partial \mathbf{C}_{t-1}} = \mathbf{f}_t\]
No matrix multiplication. No tanh derivative. Just the forget gate — a sigmoid between 0 and 1. A gradient highway through time.
The output gate creates a filtered version of the cell state
What does this accomplish for gradients?
This is the same principle as ResNets: create a highway for gradients that bypasses the nonlinear transformations.
LSTMs are the default — not a special case. Most RNN architectures use LSTM cells as they are much easier to train.
The Gated Recurrent Unit (Cho et al., 2014) achieves similar performance with fewer parameters.
Two gates instead of three:
\[\tilde{\mathbf{h}}_t = \tanh(\mathbf{W}_{xh}\mathbf{x}_t + \mathbf{W}_{hh}(\mathbf{r}_t \odot \mathbf{h}_{t-1}))\] \[\mathbf{h}_t = \mathbf{z}_t \odot \mathbf{h}_{t-1} + (1 - \mathbf{z}_t) \odot \tilde{\mathbf{h}}_t\]
No separate cell state. The update gate interpolates between old and new directly. Simpler, faster, often works just as well.
LSTMs and GRUs mitigate vanishing gradients but don’t solve the fundamental problem:
The hidden state is still a fixed-size vector. At step 1000, the entire sequence history must fit in the same 256 or 512 dimensions that step 2 used for just one input.
Information gets overwritten. The Seq2Seq bottleneck persists — long sequences lose early information regardless of gating.
And training is sequential. \(\mathbf{h}_t\) depends on \(\mathbf{h}_{t-1}\) — you can’t parallelize across time steps. Long sequences are slow.
Two problems to solve:
Both problems are solved by attention — a mechanism that lets any position directly attend to any other position, bypassing the sequential hidden state chain entirely.
Next lecture: how attention works, and why it replaces recurrence.