EasyNeural Networks

RNN Step Forward (Tanh Cell)

Neural Networks · NLP

Easy

Problem

Implement one step of a tanh recurrent neural network:

\mathbf{a}_t=\mathbf{x}_tW_x+\mathbf{h}_{t-1}W_h+\mathbf{b}

\mathbf{h}_t=\tanh(\mathbf{a}_t)

Here, \mathbf{x}_t\in\mathbb{R}^{D} is the current input, \mathbf{h}_{t-1}\in\mathbb{R}^{H} is the previous hidden state, W_x\in\mathbb{R}^{D\times H} maps inputs, W_h\in\mathbb{R}^{H\times H} maps the previous state, and \mathbf{b}\in\mathbb{R}^{H} is the bias. Return \mathbf{h}_t as a one-dimensional NumPy array.

Theory

Standard neural networks take a fixed-size input and produce a fixed-size output. They have no concept of order. Feed them the words "dog bites man" or "man bites dog" and they see the same bag of words.

But many problems are inherently sequential:

In all of these, earlier elements influence later ones, and the sequence length can vary. Recurrent Neural Networks (RNNs) handle this by maintaining a hidden state that gets updated at every time step, acting as a running memory of what the network has seen so far.


The Hidden State

The hidden state h_t is a vector of size H that summarizes everything the network has processed up to time t. Think of it as the network's "working memory."

Each hidden state carries forward a compressed representation of the entire history. The network never looks at raw past inputs directly. It only sees the current input and the previous hidden state.

The initial hidden state h_{-1} is typically set to all zeros.


The Tanh RNN Cell

The update rule for the simplest RNN cell is:

h_t = \tanh(x_t \, W_x + h_{t-1} \, W_h + b)

This has three parts that get added together before \tanh is applied:

1. Input contribution: x_t W_x

2. Recurrent contribution: h_{t-1} W_h

3. Bias: b

After summing all three, the \tanh function squashes each element into [-1, 1]. The result is the new hidden state h_t.


Why Tanh?

The saturation at the extremes (output barely changes for very large or very small inputs) is both a feature (bounding) and a weakness (vanishing gradients), which we discuss below.


Unrolling Through Time

To process a full sequence x_0, x_1, x_2, \ldots, the same cell is applied repeatedly:

h_0 = \tanh(x_0 W_x + h_{-1} W_h + b)

h_1 = \tanh(x_1 W_x + h_0 W_h + b)

h_2 = \tanh(x_2 W_x + h_1 W_h + b)

Two important things to notice:


The Vanishing Gradient Problem

During BPTT, the gradient at each step gets multiplied by:

After many steps, these repeated multiplications cause the gradient to shrink exponentially. The result:

This is why the plain tanh RNN has been largely replaced by gated architectures.


LSTM and GRU: Solving the Problem

LSTM (Long Short-Term Memory) adds a separate cell state c_t that flows through time with minimal modification, controlled by three gates:

The key insight: the cell state can carry information unchanged across many steps when the forget gate stays close to 1, letting gradients flow without shrinking.

GRU (Gated Recurrent Unit) is a simpler variant with two gates:

Both architectures build on the same core idea as the tanh RNN (combine input + previous state, apply nonlinearity) but add gating mechanisms that let gradients survive over long sequences.


Where This Shows Up

Examples

Example 1

Input
x_t = [1.0, 0.0], h_prev = [0.0, 0.0], Wx = [[1.0, 0.0], [0.0, 1.0]], Wh = [[0.0, 0.0], [0.0, 0.0]], b = [0.0, 0.0]
Output
[0.761594, 0.0]
Explanation
Only the first input coordinate reaches the pre-activation, so tanh is applied to [1, 0].

Example 2

Input
x_t = [0.0, 0.0], h_prev = [1.0, -1.0], Wx = [[0.0, 0.0], [0.0, 0.0]], Wh = [[1.0, 0.0], [0.0, 1.0]], b = [0.0, 0.0]
Output
[0.761594, -0.761594]

Hints

  1. Compute the pre-activation with x_t @ Wx + h_prev @ Wh + b.
  2. Pass the complete pre-activation array to np.tanh.

Requirements

Constraints

Starter Code

import numpy as np

def rnn_step_forward(x_t: list, h_prev: list, Wx: list, Wh: list, b: list) -> np.ndarray:
    """
    Returns a NumPy array with shape (H,).
    """
    # Write code here
    pass

Test Cases

CaseMatches
Basic with identity Wxpublic
Identity recurrencepublic