
EE 641 - Unit 4A
Fall 2026
nn.RNNTranslating a Sentence · Reading the Source · Writing the Target · Choosing the Words · Training the Pair
[BPTT] P. J. Werbos, “Backpropagation through time: What it does and how to do it,” Proceedings of the IEEE, vol. 78, no. 10, pp. 1550–1560, 1990.
[RNN] I. Goodfellow, Y. Bengio, and A. Courville, “Deep Learning,” MIT Press, 2016, Chapter 10: Sequence Modeling: Recurrent and Recursive Nets.
[Gradients] Y. Bengio, P. Simard, and P. Frasconi, “Learning long-term dependencies with gradient descent is difficult,” IEEE Transactions on Neural Networks, vol. 5, no. 2, pp. 157–166, 1994.
[Clipping] R. Pascanu, T. Mikolov, and Y. Bengio, “On the difficulty of training recurrent neural networks,” in Proceedings of the 30th International Conference on Machine Learning, 2013, pp. 1310–1318.
[LSTM] S. Hochreiter and J. Schmidhuber, “Long short-term memory,” Neural Computation, vol. 9, no. 8, pp. 1735–1780, 1997.
[GRU] K. Cho, B. van Merriënboer, C. Gulcehre, D. Bahdanau, F. Bougares, H. Schwenk, and Y. Bengio, “Learning phrase representations using RNN encoder–decoder for statistical machine translation,” in Proceedings of the 2014 Conference on Empirical Methods in Natural Language Processing, 2014, pp. 1724–1734.
[Text] A. Karpathy, J. Johnson, and L. Fei-Fei, “Visualizing and understanding recurrent networks,” in International Conference on Learning Representations, Workshop Track, 2016.
One vector per step
Sources
| Source | Element \(\mathbf{x}_t\) | \(d\) | Rate |
|---|---|---|---|
| Text | one token, as an embedding vector | 100-1000 | one per word or subword |
| Audio | one frame of a mel spectrogram | 40-80 | 100 per second |
| Sensors | one reading of every channel | 3-50 | 50-1000 per second |


Lengths in three kinds of data
Requirements on the model
Examples
Relative position
Test

Language
Signals
Required of a model

First layer
Reshaping a sequence into \(D\) numbers

Parameter count
Separate weights per position
Length fixed at \(T_{\max}\)


Sum or mean over \(t\)
Permutation invariant, by construction
Task dependence

Across an image
Along time
\[\mathbf{y}_t = \sum_{j=0}^{k-1} \mathbf{W}_j\, \mathbf{x}_{t-j}\]
Properties
Receptive field
Set by the architecture
Benchmarks


What a window lacks
Keeping a value instead
Cost

Two familiar update rules
Same loop, different rule
Recurrence
\[\mathbf{h}_t = f(\mathbf{h}_{t-1}, \mathbf{x}_t), \qquad \mathbf{y}_t = g(\mathbf{h}_t)\]
From one number to a vector
Update rules
| Memory | \(f(\mathbf{h}_{t-1}, \mathbf{x}_t)\) | What survives |
|---|---|---|
| Running sum | \(h_{t-1} + x_t\) | the total |
| Running maximum | \(\max(h_{t-1}, x_t)\) | the largest value |
| Recurrent network | a network with weights fitted to a task | what the task’s loss rewards |

Parts
Feedback system

Forgetting is forced
Forgetting is chosen at every step
Two tasks
Two questions

What training needs
Why the loop is opened
Depth set by the data
Compared with a feedforward network
Training


Recursion
\[h_t = \alpha\, h_{t-1} + \beta\, x_t\]
Impulse response
Transfer function
\[H(z) = \frac{\beta}{1 - \alpha z^{-1}}\]
Three regimes
Memory horizon
DC gain

\[\mathbf{h}_t = \mathbf{A}\mathbf{h}_{t-1} + \mathbf{B}\mathbf{x}_t, \qquad \mathbf{h}_t \in \mathbb{R}^H,\ \mathbf{A} \in \mathbb{R}^{H \times H},\ \mathbf{B} \in \mathbb{R}^{H \times d}\]

Closed form
\[\mathbf{h}_t = \mathbf{A}^t \mathbf{h}_0 + \sum_{i=0}^{t-1} \mathbf{A}^i \mathbf{B}\mathbf{x}_{t-i}\]
Modes
Spectral radius

Complex pair
Three \(2 \times 2\) cases

FIR: a window with fixed taps
\[y_t = \sum_{j=0}^{k-1} w_j\, x_{t-j}\]
IIR: feedback through a state
\[h_t = \alpha\, h_{t-1} + \beta\, x_t\]
| FIR (window) | IIR (feedback) | |
|---|---|---|
| Reach | exactly \(k\) steps | unbounded, decaying as \(\alpha^t\) |
| Cost per step | \(k\) multiplications | one for the state, \(H^2\) for a vector state |
| Stability | any taps | \(|\alpha| < 1\) required |
| Sequence model | convolution over time | recurrent unit |
Setup
LMS for FIR taps
\[\mathbf{w}_{t+1} = \mathbf{w}_t + \mu\, e_t\, \mathbf{x}_t\]
Same procedure as training a network

Same setup, one pole to adapt
Gradient as a recursion
\[\frac{\partial y_t}{\partial \alpha} = y_{t-1} + \alpha\, \frac{\partial y_{t-1}}{\partial \alpha}\]
Two consequences
Carried forward


Superposition
What no choice of \(\alpha\), \(\beta\) gives
The recurrent unit’s \(f\) is nonlinear for this reason.
Recurrence and readout
\[\mathbf{z}_t = \mathbf{W}_{hh}\mathbf{h}_{t-1} + \mathbf{W}_{xh}\mathbf{x}_t + \mathbf{b}, \qquad \mathbf{h}_t = \tanh(\mathbf{z}_t)\]
\[\mathbf{y}_t = \mathbf{W}_{hy}\mathbf{h}_t\]
Against the linear recursion \(\mathbf{A}\mathbf{h}_{t-1} + \mathbf{B}\mathbf{x}_t\)
\(\tanh\) in the loop

| Object | Shape | Role |
|---|---|---|
| \(\mathbf{x}_t\) | \(\mathbb{R}^d\) | input at step \(t\) |
| \(\mathbf{h}_t\) | \(\mathbb{R}^H\) | state, the only carrier of the past |
| \(\mathbf{y}_t\) | \(\mathbb{R}^K\) | output at step \(t\) |
| \(\mathbf{W}_{xh}\) | \(\mathbb{R}^{H \times d}\) | projects the input into the state space |
| \(\mathbf{W}_{hh}\) | \(\mathbb{R}^{H \times H}\) | feedback: mixes the state with itself, once per step |
| \(\mathbf{b}\) | \(\mathbb{R}^H\) | sets where on \(\tanh\) the unit operates |
| \(\mathbf{W}_{hy}\) | \(\mathbb{R}^{K \times H}\) | reads the state out, one row per output |
Two of the three are outside the loop
\[N = \underbrace{H^2 + Hd}_{\text{recurrence}} + \underbrace{KH}_{\text{readout}} + H + K\]
One recurrence, \(d = 300\), \(H = 512\), two tasks
| Task | \(K\) | Recurrence \(H^2 + Hd\) | Readout \(KH\) | Total | Readout share |
|---|---|---|---|---|---|
| Tagging | \(50\) | \(415{,}744\) | \(25{,}600\) | \(441{,}906\) | \(5.8\%\) |
| Word prediction | \(10{,}000\) | \(415{,}744\) | \(5{,}120{,}000\) | \(5{,}546{,}256\) | \(92.3\%\) |
Memory is set by \(H\) alone
Readout is set by the task

Cost of one step
Parallel axes

What the backward pass reads
Memory, float32, batch \(B\)
| Tensor | Bytes |
|---|---|
| states | \(B \cdot T \cdot H \cdot 4\) |
| inputs | \(B \cdot T \cdot d \cdot 4\) |
| outputs | \(B \cdot T \cdot K \cdot 4\) |
Growth with \(T\)
def rnn_forward(x, h0, W_xh, W_hh, b, W_hy):
# x: (B, T, d) h0: (B, H)
B, T, d = x.shape
h = h0
states, outputs = [], []
for t in range(T):
z = x[:, t, :] @ W_xh.T + h @ W_hh.T + b # (B, H)
h = torch.tanh(z) # (B, H)
y = h @ W_hy.T # (B, K)
states.append(h)
outputs.append(y)
return torch.stack(outputs, 1), torch.stack(states, 1)
# outputs: (B, T, K) states: (B, T, H)
Line by line
x[:, t, :]: the batch’s step \(t\), one \(B \times d\) slice of the \((B, T, d)\) tensor above. Rows are examples and never interacth overwritten each step: the loop itself needs \(O(BH)\) memorystates kept for the backward passnn.RNN Takes \((B, T, d)\) and Returns Every Staternn = nn.RNN(input_size=300, # d
hidden_size=512, # H
num_layers=1,
nonlinearity='tanh',
batch_first=True,
bidirectional=False)
x = torch.randn(32, 100, 300) # (B, T, d)
h0 = torch.zeros(1, 32, 512) # (layers·dirs, B, H)
output, h_n = rnn(x, h0)
# output: (32, 100, 512) every h_t, top layer
# h_n: (1, 32, 512) last h_T, every layer
y = W_hy(output) # readout: a separate nn.LinearArguments
| Argument | Sets |
|---|---|
input_size |
\(d\) |
hidden_size |
\(H\) |
num_layers |
stacked units, each with its own weights |
nonlinearity |
tanh or relu in the loop |
batch_first |
\((B, T, d)\) in and out, or \((T, B, d)\) |
bidirectional |
a second unit reading \(T\) to \(1\) |
h_0 |
the initial state, zeros by default; a learned one adds \(H\) parameters and matters for the first steps only |
dropout |
between layers only, never on the state path |
Layouts
Not in the module
output is \(\mathbf{h}_t\), and \(\mathbf{W}_{hy}\) is a separate nn.LinearParameters as PyTorch counts them
weight_ih \((H, d)\), weight_hh \((H, H)\), bias_ih and bias_hh \((H)\): two bias vectors, \(H(H + d + 2)\)output Holds Every Step and h_n Holds the Last
One output for the sequence
h_n[-1], the top layer’s final state, or output[:, -1]: the same tensorOne output per step
output, all \(T\) states of the top layer, then apply the readout to every step at oncenn.RNN applied to output adds a layerLayer \(l\) takes layer \(l - 1\)’s state as its input
\[\mathbf{h}^{(l)}_t = \tanh\!\left(\mathbf{W}^{(l)}_{hh}\mathbf{h}^{(l)}_{t-1} + \mathbf{W}^{(l)}_{xh}\mathbf{h}^{(l-1)}_t + \mathbf{b}^{(l)}\right), \qquad \mathbf{h}^{(0)}_t = \mathbf{x}_t\]
Cost
Depth


Two units, two directions, one output
output is \((B, T, 2H)\), h_n is \((2L, B, H)\)Requirements
Where the loss sits
One node, two incoming gradients
\[\boldsymbol{\delta}_t \equiv \frac{\partial L}{\partial \mathbf{h}_t} = \underbrace{\mathbf{W}_{hy}^{\top} \frac{\partial L_t}{\partial \mathbf{y}_t}}_{\text{from this step's output}} + \underbrace{\mathbf{W}_{hh}^{\top}\left(\boldsymbol{\delta}_{t+1} \odot (1 - \mathbf{h}_{t+1}^2)\right)}_{\text{from the next state}}\]
Same recursion as the adaptive one-pole filter

One matrix, \(T\) contributions
\[\frac{\partial L}{\partial \mathbf{W}_{hh}} = \sum_{t=1}^{T} \left(\boldsymbol{\delta}_t \odot (1 - \mathbf{h}_t^2)\right) \mathbf{h}_{t-1}^{\top}\]
\[\frac{\partial L}{\partial \mathbf{W}_{xh}} = \sum_{t=1}^{T} \left(\boldsymbol{\delta}_t \odot (1 - \mathbf{h}_t^2)\right) \mathbf{x}_t^{\top}, \qquad \frac{\partial L}{\partial \mathbf{W}_{hy}} = \sum_{t=1}^{T} \frac{\partial L_t}{\partial \mathbf{y}_t}\, \mathbf{h}_t^{\top}\]
Magnitude - a sum of \(T\) terms, large whenever the \(\boldsymbol{\delta}_t\) are
Sharing - every step contributes to the same weights, so a pattern learned at step 3 is applied at step 40
def rnn_backward(x, states, dLdy, W_hh, W_hy):
# states: h_0 .. h_T dLdy[t]: dL_t/dy_t
T = len(dLdy)
dW_hh = torch.zeros_like(W_hh)
dW_xh = torch.zeros(H, d)
delta_next = torch.zeros(H)
for t in range(T, 0, -1):
delta = W_hy.T @ dLdy[t] + delta_next # two incoming terms
dz = delta * (1 - states[t] ** 2) # through tanh
dW_hh += torch.outer(dz, states[t - 1]) # one term per step
dW_xh += torch.outer(dz, x[t])
delta_next = W_hh.T @ dz # to the previous state
return dW_hh, dW_xhAutograd
loss.backward() runs through the \(T\) copies in reverse and accumulates into one .grad per parameter
Procedure
h = h.detach(), value kept, graph cutCost
Not trained
One step back
\[\frac{\partial \mathbf{h}_{t}}{\partial \mathbf{h}_{t-1}} = \mathbf{J}_t = \mathrm{diag}\!\left(1 - \mathbf{h}_t^2\right)\mathbf{W}_{hh}, \qquad \mathbf{J}_t \in \mathbb{R}^{H \times H}\]
\(T - k\) steps back
\[\frac{\partial \mathbf{h}_T}{\partial \mathbf{h}_k} = \mathbf{J}_T\, \mathbf{J}_{T-1} \cdots \mathbf{J}_{k+1} = \prod_{t=k+1}^{T} \mathbf{J}_t, \qquad \frac{\partial L}{\partial \mathbf{h}_k} = \frac{\partial L}{\partial \mathbf{h}_T}\prod_{t=k+1}^{T} \mathbf{J}_t\]
Bound on each factor
Linear filter, again

Two kinds of backward step
Paths from the last output to the first input
Consequence

Measured
Horizon
Task
Trained unit
Failure


The bound above one
Symptom in a training run
nan
Spikes
Two rules
Choosing \(c\)
Vanishing untouched

Three starting points for \(\mathbf{W}_{hh}\)
First updates only
Normalize the pre-activation, every step
\[\hat{\mathbf{z}}_t = \frac{\mathbf{z}_t - \mu(\mathbf{z}_t)}{\sigma(\mathbf{z}_t)}, \qquad \mathbf{h}_t = \tanh\!\left(\boldsymbol{\gamma} \odot \hat{\mathbf{z}}_t + \boldsymbol{\beta}\right)\]
Effect
Unchanged

For the gradient to survive \(T\) steps, every factor must have norm one
\[\left\|\prod_{t=k+1}^{T} \mathrm{diag}(1 - \mathbf{h}_t^2)\,\mathbf{W}_{hh}\right\| \approx 1 \quad\text{requires}\quad \mathrm{diag}(1 - \mathbf{h}_t^2)\,\mathbf{W}_{hh} \text{ close to orthogonal at every } t\]
No such matrix
Limits of the remedies
A state path that adds instead of multiplies has a Jacobian of one by construction.

Additive path
\[\mathbf{c}_t = \mathbf{c}_{t-1} + \boldsymbol{\Delta}_t \qquad \Rightarrow \qquad \frac{\partial \mathbf{c}_t}{\partial \mathbf{c}_{t-1}} = \mathbf{I}\]
Unconditional sum
Gates


Forget gate on the cell line
\[\mathbf{f}_t = \sigma\!\left(\mathbf{W}_f[\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_f\right), \qquad \mathbf{c}_t = \mathbf{f}_t \odot \mathbf{c}_{t-1} + \ldots\]
\[\frac{\partial \mathbf{c}_t}{\partial \mathbf{c}_{t-1}} = \mathrm{diag}(\mathbf{f}_t)\]
Three regimes of a gate value
Initialization

Two parts to a write
\[\mathbf{i}_t = \sigma\!\left(\mathbf{W}_i[\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_i\right), \qquad \tilde{\mathbf{c}}_t = \tanh\!\left(\mathbf{W}_c[\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_c\right)\]
\[\mathbf{c}_t = \mathbf{f}_t \odot \mathbf{c}_{t-1} + \mathbf{i}_t \odot \tilde{\mathbf{c}}_t\]
Against the plain unit

Read path
\[\mathbf{o}_t = \sigma\!\left(\mathbf{W}_o[\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_o\right), \qquad \mathbf{h}_t = \mathbf{o}_t \odot \tanh(\mathbf{c}_t)\]
Two states

\[\begin{aligned} \mathbf{f}_t &= \sigma(\mathbf{W}_f[\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_f) & \mathbf{i}_t &= \sigma(\mathbf{W}_i[\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_i) \\ \tilde{\mathbf{c}}_t &= \tanh(\mathbf{W}_c[\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_c) & \mathbf{o}_t &= \sigma(\mathbf{W}_o[\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_o) \\ \mathbf{c}_t &= \mathbf{f}_t \odot \mathbf{c}_{t-1} + \mathbf{i}_t \odot \tilde{\mathbf{c}}_t & \mathbf{h}_t &= \mathbf{o}_t \odot \tanh(\mathbf{c}_t) \end{aligned}\]
One step, in order
Given (\(H = 2\), gate values as computed by the four affine maps)
| value | |
|---|---|
| \(\mathbf{c}_{t-1}\) | \([1.5,\ -0.5]\) |
| \(\mathbf{f}_t\) | \([0.9,\ 0.1]\) |
| \(\mathbf{i}_t\) | \([0.2,\ 0.8]\) |
| \(\tilde{\mathbf{c}}_t\) | \([0.6,\ -0.4]\) |
| \(\mathbf{o}_t\) | \([0.7,\ 0.5]\) |
Gate values
Cell update
\[\mathbf{f}_t \odot \mathbf{c}_{t-1} = [1.35,\ -0.05], \qquad \mathbf{i}_t \odot \tilde{\mathbf{c}}_t = [0.12,\ -0.32]\]
\[\mathbf{c}_t = [1.47,\ -0.37]\]
Read
\[\tanh(\mathbf{c}_t) = [0.90,\ -0.35], \qquad \mathbf{h}_t = \mathbf{o}_t \odot \tanh(\mathbf{c}_t) = [0.63,\ -0.18]\]
Observations
| Plain unit | LSTM | GRU | |
|---|---|---|---|
| Affine maps of \([\mathbf{h}_{t-1}, \mathbf{x}_t]\) per step | 1 | 4 | 3 |
| Recurrence parameters | \(H(H + d + 1)\) | \(4H(H + d + 1)\) | \(3H(H + d + 1)\) |
| \(d = 300\), \(H = 512\) | \(416{,}256\) | \(1{,}665{,}024\) | \(1{,}248{,}768\) |
| Multiply-adds per step (recurrence) | \(H(H + d)\) | \(4H(H + d)\) | \(3H(H + d)\) |
| State carried per step | \(\mathbf{h}_t\) | \(\mathbf{h}_t\) and \(\mathbf{c}_t\) | \(\mathbf{h}_t\) |
| Activations kept for training, per step | \(\mathbf{h}_t\) | \(\mathbf{h}_t, \mathbf{c}_t\), four gate outputs | \(\mathbf{h}_t\), three gate outputs |
Cost
Unchanged
Along the cell line
\[\frac{\partial \mathbf{c}_T}{\partial \mathbf{c}_k} = \prod_{t=k+1}^{T} \mathrm{diag}(\mathbf{f}_t) + (\text{terms through } \mathbf{h}_t)\]
Other terms
Measured on the trained units


Same task, same budget
Measured
Failed seed
Gate below one
Gate near one
Changed and unchanged

\[\begin{aligned} \mathbf{z}_t &= \sigma(\mathbf{W}_z[\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_z) & \mathbf{r}_t &= \sigma(\mathbf{W}_r[\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_r) \\ \tilde{\mathbf{h}}_t &= \tanh(\mathbf{W}[\mathbf{r}_t \odot \mathbf{h}_{t-1},\ \mathbf{x}_t] + \mathbf{b}) & \mathbf{h}_t &= (1 - \mathbf{z}_t) \odot \mathbf{h}_{t-1} + \mathbf{z}_t \odot \tilde{\mathbf{h}}_t \end{aligned}\]
Three changes from the LSTM
Same additive path
nn.LSTM Returns the Cell State Beside h_nlstm = nn.LSTM(input_size=300, hidden_size=512,
num_layers=2, batch_first=True)
x = torch.randn(32, 100, 300) # (B, T, d)
h0 = torch.zeros(2, 32, 512) # (layers, B, H)
c0 = torch.zeros(2, 32, 512) # (layers, B, H)
output, (h_n, c_n) = lstm(x, (h0, c0))
# output: (32, 100, 512) h_t of the top layer, every step
# h_n, c_n: (2, 32, 512) last h and c of every layer
gru = nn.GRU(300, 512, num_layers=2, batch_first=True)
output, h_n = gru(x, h0) # no cell stateWeight layout, LSTM
weight_ih_l0: \((4H, d)\), weight_hh_l0: \((4H, H)\), rows in the order \(\mathbf{i}, \mathbf{f}, \mathbf{g}, \mathbf{o}\)bias_ih_l0 and bias_hh_l0: \((4H)\) each, both addedSame as nn.RNN
batch_first, num_layers, bidirectional, dropout between layersoutput is the top layer’s \(\mathbf{h}_t\), and the readout is a separate nn.LinearGRU layout
weight_ih_l0: \((3H, d)\), rows in the order \(\mathbf{r}, \mathbf{z}, \mathbf{n}\), with \(\mathbf{n}\) the candidateVariant
\[\mathbf{f}_t = \sigma\!\left(\mathbf{W}_f[\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{p}_f \odot \mathbf{c}_{t-1} + \mathbf{b}_f\right)\]
Origin
Comparison
Current use
nn.LSTM and absent from current reference implementations
Two readings of one unit
Both are set by the input
Tasks that do not

One vector for any length
Two consumers
Trained from where it is read

One added connection
Length set by the unit
What sets the first state

The composition
What it demands of one vector