Feed-Forward Networks › A Complete Worked Example
Language AI HandbookAll chapters Feed-Forward Networks › A Complete Worked ExampleOne section · Sections in this chapter8 / 13 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
The formula is compact, but its compactness can obscure what happens at each step. To understand the FFN, let's trace through a concrete computation with actual numbers. We'll use intentionally small dimensions ( and ) so you can follow every multiplication and addition by hand if you like, verifying each step against the formulas.
The goal of this example is threefold: to see exactly how the input vector is transformed at each stage, to observe how ReLU zeros out negative activations to introduce nonlinearity, and to verify that the output dimension matches the input dimension, ready for the residual connection. We'll use simple, hand-readable weight values to keep the arithmetic transparent.
# Small example for hand-traceable computation
d_model_tiny = 3
d_ff_tiny = 4
# Initialize weights with simple values
W1_tiny = np.array(
[
[0.5, -0.3, 0.8, 0.2],
[-0.2, 0.6, 0.1, -0.4],
[0.3, 0.1, -0.5, 0.7],
]
) # Shape: (3, 4)
b1_tiny = np.array([0.1, -0.1, 0.2, 0.0]) # Shape: (4,)
W2_tiny = np.array(
[
[0.4, -0.2, 0.3],
[0.1, 0.5, -0.1],
[-0.3, 0.2, 0.4],
[0.2, -0.4, 0.1],
]
) # Shape: (4, 3)
b2_tiny = np.array([0.05, -0.05, 0.1]) # Shape: (3,)
# Input vector
x_tiny = np.array([1.0, -0.5, 0.8])We have chosen an input with mixed positive and negative values (), which is representative of what you'd see in practice after layer normalization. The weight matrices use a mix of positive and negative values. This ensures some hidden dimensions will have positive pre-activations (and survive ReLU) while others will be zeroed out.
Stage 1: Expansion via the First Linear Layer
The first operation computes . Our 3-dimensional input vector gets multiplied by a weight matrix, producing a 4-dimensional hidden representation. Each element of this output is a weighted sum of the input elements plus a bias term. Specifically, the -th element of the pre-activation is : a dot product between the input and the -th column of .
Step 1: First linear transformation (x @ W1 + b1) Input x: [ 1. -0.5 0.8] W1: [[ 0.5 -0.3 0.8 0.2] [-0.2 0.6 0.1 -0.4] [ 0.3 0.1 -0.5 0.7]] b1: [ 0.1 -0.1 0.2 0. ] x @ W1 + b1 = [ 0.94 -0.62 0.55 0.96]
Notice the dimension change: a 3-dimensional vector goes in, and a 4-dimensional vector comes out. This expansion is the "higher-dimensional working space" we discussed earlier, where the network has more room to manipulate the representation before applying nonlinearity. The result is called the pre-activation because we haven't applied the nonlinearity yet. Some of these pre-activation values are positive and some are negative, which sets up the selective filtering that ReLU will perform in the next step.
Stage 2: Nonlinearity via ReLU
Here is where the FFN gains its expressive power. The ReLU activation function applies a simple rule: keep positive values unchanged, but set negative values to zero. Mathematically, . This is one of the simplest possible nonlinear functions, yet it is sufficient to make the FFN a universal approximator when the hidden dimension is large enough.
Step 2: Apply ReLU activation Pre-activation: [ 0.94 -0.62 0.55 0.96] After ReLU: [0.94 0. 0.55 0.96] Negative values become zero, positive values pass through unchanged.
By zeroing out some dimensions, ReLU creates sparse hidden representations: only a subset of hidden dimensions are active for any given input. Different inputs activate different subsets, allowing the network to learn piece-wise linear functions that approximate arbitrary curves. Each hidden dimension can be thought of as a detector for a particular pattern in the input. When the detector fires (positive pre-activation), it contributes to the output. When it doesn't fire (negative pre-activation, zeroed by ReLU), it contributes nothing. The network learns during training which patterns each detector should respond to, and what contribution to make when it does respond.
The key insight here is that ReLU's behavior changes based on which side of zero the pre-activation falls. For pre-activations far above zero, the gradient of ReLU is 1 and learning proceeds normally. For pre-activations below zero, the gradient is 0 and the network cannot update that hidden dimension for that particular input. This creates a form of implicit regularization: hidden dimensions that consistently fail to fire for a category of inputs effectively "opt out" of processing that category, allowing specialization.


Stage 3: Contraction via the Second Linear Layer
Finally, we project back to the original dimension. The 4-dimensional hidden vector is multiplied by a weight matrix, and a 3-dimensional bias is added. This contraction reduces the dimension and combines the contributions of all active hidden dimensions into an output vector. Each output dimension receives a weighted sum of the active hidden dimensions, where the weights are the corresponding elements of .
Think of the second linear layer as the "summarization" step. The first layer asked "which features of this input are relevant?" The nonlinearity filtered out irrelevant dimensions. The second layer asks "given these relevant features, what transformation should we apply to the representation?"
Step 3: Second linear transformation (hidden @ W2 + b2) Hidden: [0.94 0. 0.55 0.96] W2: [[ 0.4 -0.2 0.3] [ 0.1 0.5 -0.1] [-0.3 0.2 0.4] [ 0.2 -0.4 0.1]] b2: [ 0.05 -0.05 0.1 ] hidden @ W2 + b2 = [ 0.453 -0.512 0.698]
The output has the same dimension as the input (3 elements), which is essential. In the full transformer, this output will be added to the original input via a residual connection, and both must have the same shape. The FFN has transformed the representation while preserving its dimensionality, enabling the residual addition that stabilizes training in deep networks.
Verification and Summary
Let's verify that our step-by-step calculation matches the complete FFN function applied directly:
Verification using ffn() function: Output: [ 0.453 -0.512 0.698] Match: True Summary: Input: [ 1. -0.5 0.8] (dimension 3) Output: [ 0.453 -0.512 0.698] (dimension 3)
The step-by-step and function-based computations match exactly. This worked example demonstrates the complete journey: a 3-dimensional input expands to 4 dimensions, passes through nonlinearity (with some dimensions zeroed out), and contracts back to 3 dimensions. The transformation is nonlinear (different inputs will activate different subsets of hidden dimensions, producing qualitatively different transformations), position-independent (the same computation would apply to any token's representation), and dimension-preserving (the output shape matches the input shape for the residual connection).