rumblr Work in progressWIP

● The AI Primer · Lesson 24 · Part 1: how the model works inside

CNNs and RNNs

how networks see images and read sequences

This lesson covers How convolutions see and recurrent nets remember, and why transformers won

Members · open during launch 35 min11 figures and diagrams
How it works builds the idea from scratch. Math & code adds the formulas and the Python.

At a glance

Key takeaways

  1. A convolution slides a small filter across an image, multiplying and adding at each position; the output map shows where the filter's pattern appears.
  2. Weight sharing (one filter everywhere) and local receptive fields make CNNs efficient; pooling downsamples; depth builds edges → parts → objects.
  3. An RNN reads one step at a time, carrying a hidden state; backpropagating through many steps multiplies gradients until they vanish or explode.
  4. LSTMs add a cell state edited by forget, input and output gates, so information and gradients survive many steps.
  5. Transformers won through parallel training and one-hop paths between any two tokens; Vision Transformers treat image patches as tokens.

Level 2

How it works, from scratch

Before transformers, two designs dominated deep learning, and both still matter. Convolutional networks (CNNs) see images by sliding small pattern detectors across them. Recurrent networks (RNNs) read sequences one item at a time, carrying a running memory. Each builds in an assumption about its data (patterns in images are local; sequences unfold in order), and each has a limit that the transformer removed. Knowing both stories explains why modern models look the way they do.

Part A: convolutional networks

Chapter 1

A1. A convolution is a flashlight looking for one pattern

Everyday picture You are in a dark room with a large photograph and a small flashlight. You're looking for one thing, say a place where dark turns to bright from left to right. You sweep the flashlight across the photo, one step at a time, and at every spot you jot down a score: high if the lit patch matches what you're looking for, near zero if it doesn't. When you finish, your notes form a new, smaller picture: a map of where the pattern appears. That map is called a feature map, and the pattern you were looking for, written as a small grid of numbers, is the filter (or kernel).

Tiny worked example A 5×5 image: two dark columns (0) then three bright columns (1), so there is a vertical edge between columns 2 and 3. The filter is a 3×3 "vertical edge" detector: −1 on the left column, 0 in the middle, +1 on the right. It rewards "bright on the right, dark on the left".

image X                 filter K
0 0 1 1 1               -1 0 1
0 0 1 1 1               -1 0 1
0 0 1 1 1               -1 0 1
0 0 1 1 1
0 0 1 1 1

Put the filter on the top-left 3×3 patch of the image. Multiply each image number by the filter number on top of it, and add all nine products:

patch      × filter    = products
0 0 1      -1 0 1        0 0 1
0 0 1      -1 0 1        0 0 1      sum = 1 + 1 + 1 = 3
0 0 1      -1 0 1        0 0 1

So the top-left cell of the feature map is 3. Slide one step right: the patch is 0 1 1 in every row; products 0 0 1 per row; sum again 3 (the edge is still under the flashlight). One more step: the patch is 1 1 1, products −1 0 1, sum 0 (flat bright area, no edge). Doing the same for every row gives the full 3×3 feature map:

3 3 0
3 3 0
3 3 0

The high numbers sit exactly where the edge is. Run a horizontal edge filter over the same image and every cell is 0: this image has no top-to-bottom change. Each filter answers one question.

Figure 1 · Diagram

Reading it: this loop is a convolution. The only arithmetic is multiply-and-add, repeated at every position. The stride is how far the flashlight jumps each time (1 pixel, or 2 to shrink the output faster). Padding adds a border of zeros so the filter can also centre on edge pixels and the output keeps the input's size.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here Shape / range
the input image; is channel c (red, green or blue) at row a, column b (channels, height, width)
the filter (kernel): a small grid of learned weights, one slice per channel (channels, k, k)
the feature-map value at output row i, column j one number
add up over every channel c and every filter row u and column v
filter size (3 for a 3×3 filter) small integer
stride: how many pixels the filter jumps between positions 1 or 2
input size along one side e.g. 5 or 224
padding: zeros added around each side 0, 1, 2…
floor: round down to a whole number

In words: each output number is the sum, over the filter's footprint and all colour channels, of filter weight times the pixel under it; the output has one number per place the filter can stand.

On the worked example: one channel, k = 3, s = 1, p = 0. Y[0, 0] = (−1)·0 + 0·0 + 1·1 for each of the 3 rows = 3. Output size ⌊(5 + 0 − 3)/1⌋ + 1 = 3. For a 224-pixel image, a 7×7 filter, stride 2 and padding 3: ⌊(224 + 6 − 7)/2⌋ + 1 = 112.

Level 3: in Python
# the 5×5 image: one channel, so Σ_c has one term
X = [[0, 0, 1, 1, 1] for _ in range(5)]
# the vertical-edge filter
K = [[-1, 0, 1] for _ in range(3)]
n, k, s, p = 5, 3, 1, 0
# ⌊(n + 2p - k) / s⌋ + 1
out = (n + 2 * p - k) // s + 1
out  # → 3
# Σ_u Σ_v K[u, v] X[i s + u, j s + v]
Y = [[sum(K[u][v] * X[i * s + u][j * s + v]
          for u in range(k) for v in range(k))
      for j in range(out)]
     for i in range(out)]
Y  # → [[3, 3, 0], [3, 3, 0], [3, 3, 0]]
# 224 pixels, 7×7 filter, stride 2, padding 3
(224 + 2 * 3 - 7) // 2 + 1  # → 112

In code: conv2d is the loop in the diagram, one multiply-and-add per position, and conv_output_size is the output-size formula.

Chapter 2

A2. Pooling: summarise each neighbourhood by its loudest voice

Everyday picture A manager asks each of four teams for one number: the strongest signal anyone on the team saw. The report is four times shorter and still says where something important happened.

Tiny worked example 2×2 max pooling on a 4×4 map keeps the largest value in each quarter:

1 3 | 0 0
2 4 | 0 1        ->   4 1
----+----             1 6
0 0 | 5 2
1 0 | 1 6

Figure 2 · Drawn from the lesson's code

input image (8×8) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 filter: vertical edge (3×3) -1 0 1 -1 0 1 -1 0 1 feature map (8×8, padding 1) 0 0 0 0 0 0 0 0 0 1 1 0 0 -1 -1 0 0 2 2 0 0 -2 -2 0 0 3 3 0 0 -3 -3 0 0 3 3 0 0 -3 -3 0 0 2 2 0 0 -2 -2 0 0 1 1 0 0 -1 -1 0 0 0 0 0 0 0 0 0 after 2×2 max pooling (4×4) 1 1 0 0 3 3 0 0 3 3 0 0 1 1 0 0 One CNN layer: sweep the filter, then pool

One CNN layer on an 8×8 bright square: the vertical-edge feature map is positive down the left side and negative down the right, and 2×2 max pooling keeps the left edge while the negative right edge becomes 0

Reading it: left to right, one pass of a CNN layer. The input is an 8×8 image of a bright square on a dark background. The filter is the vertical edge detector from the worked example. The feature map (with padding, so still 8×8) is bright red down the square's left side (dark to bright, the pattern it looks for) and blue down the right side (bright to dark, the opposite pattern); everywhere flat is zero. After 2×2 max pooling the map is 4×4: a quarter of the numbers, but the left edge is still clearly marked. The right edge's negative responses become 0, because max pooling keeps the largest value in each block. Real networks pass maps through ReLU (which zeroes negatives) before pooling anyway, and detect the bright-to-dark edge with a second, mirror-image filter. Numbers in the cells are the actual values.

In code: max_pool2d keeps the largest value in each non-overlapping block.

Chapter 3

A3. Weight sharing and the receptive field

Everyday picture A rubber stamp: you carve the pattern once and use it everywhere on the page. A CNN uses the same filter at every position, so a cat detector works in any corner of the photo and costs the same number of weights however large the photo is.

Tiny worked example 64 filters of size 3×3 over a colour image need 3·3·3·64 + 64 = 1,792 parameters, for any image size. A fully connected layer mapping a 224×224×3 image to an output of the same size as those 64 feature maps would need 150,528 × 3,211,264 ≈ 483 billion weights.

As layers stack, each neuron sees more of the original image: its receptive field. One 3×3 layer sees 3×3 pixels; two see 5×5; three see 7×7. Pooling between layers speeds this up: conv 3×3, pool 2×2, conv 3×3 already sees 8×8. That's why two stacked 3×3 filters (18 weights, and two nonlinearities) replaced single 5×5 filters (25 weights) in VGG.

Level 3: the formula and its symbols

Symbols

Symbol Meaning here Shape / range
layer number, counting from the input 1, 2, 3…
receptive field after layer ℓ: input pixels (along one side) one neuron sees ≥ 1
kernel (filter or pool window) size of layer ℓ
stride of layer ℓ
jump: input pixels between neighbouring neurons at layer ℓ ≥ 1

In words: every layer widens the view by (kernel − 1) steps, and a step at depth ℓ is as many input pixels as all earlier strides multiplied together.

On the worked example: conv 3 (r = 1 + 2·1 = 3, j = 1), pool 2 stride 2 (r = 3 + 1·1 = 4, j = 2), conv 3 (r = 4 + 2·2 = 8).

Level 3: in Python
# (k_ℓ, s_ℓ): conv 3, pool 2 with stride 2, conv 3
layers = [(3, 1), (2, 2), (3, 1)]
# r_0 = j_0 = 1
r, j = 1, 1
for k_l, s_l in layers:
    # r_ℓ = r_{ℓ-1} + (k_ℓ - 1) j_{ℓ-1}
    r = r + (k_l - 1) * j
    # j_ℓ = j_{ℓ-1} s_ℓ
    j = j * s_l
    print(r, j)  # → 3 1 4 2 8 2

Figure 3 · Drawn from the lesson's code

2 4 6 8 10 12 number of 3×3 convolution layers 0 50 100 150 200 250 300 receptive field (input pixels per side) How much of the image one neuron sees 3×3 convs only 3×3 convs, 2×2 pool after every second

Receptive field against depth: plain 3×3 layers widen the view by 2 pixels per layer, while pooling after every second layer makes the jumps double

Reading it: the x-axis is the number of 3×3 convolution layers; the y-axis is how many input pixels (along one side) one neuron at that depth can see. Without pooling (lower line) the view grows by 2 pixels per layer, slowly. With a 2×2 pool after every second layer (upper line) the jumps double each time, so a dozen layers already see most of a 224-pixel image. That growth is what lets deep layers recognise whole objects.

In code: conv_params and dense_params count the two layers' parameters, and receptive_field applies the recurrence to a stack of (kernel, stride) layers.

Chapter 4

A4. From edges to parts to objects

Everyday picture Reading starts with strokes, then letters, then words, then sentences. A CNN's first layer learns strokes (edges and colour blobs), the next combines them into textures and corners, deeper layers into parts (eyes, wheels), and the last into whole objects.

Figure 4 · Diagram

Reading it: each conv layer applies many filters to the feature maps below it, so its patterns are combinations of the patterns below. Each pool halves the resolution, which widens what the next layer sees (section A3). By the end, a small grid of very abstract features feeds an ordinary dense layer that outputs class probabilities. Nobody hand-designs these filters; training discovers them, and first-layer filters in trained networks look remarkably like the edge detectors in this lesson.

Figure 5 · Drawn from the lesson's code

layer 1: vertical edge response to the L layer 1: horizontal edge response to the L layer 1: diagonal response to the L layer 1: spot response to the L input: an L-shaped block layer 2: corner detector (vertical AND horizontal) Edges first, then combinations of edges

Four first-layer filters on an L-shaped block: the edge filters fire on their own sides, the diagonal filter fires on every side, and a second-layer product of the two edge maps lights up only at the L's corners

Reading it: the top row shows four 3×3 filters of the kind a first layer learns (red = positive weight, blue = negative): vertical edge, horizontal edge, diagonal, and a centre-surround "spot". The middle row shows each one's response to the same small image of an L-shaped block. The vertical-edge filter fires only on the L's left and right sides (red where dark turns bright, blue where bright turns dark) and the horizontal-edge filter only on its top and bottom. The diagonal filter is not so choosy: its weights lean both ways at once, so it answers about ±2 on every straight side of the L (two thirds of an edge filter's 3) and ±3 at some corners. The spot filter answers faintly (at most about 0.6) all around the outline. One first-layer filter is a weak witness on its own, which is why the next layer combines several. The last panel is a second-layer detector built from first-layer outputs: the vertical-edge strength times the horizontal-edge strength, which is large only where a vertical and a horizontal edge meet, at the L's corners. Combining simple detectors into more specific ones is the whole hierarchy in miniature.

In code: the figure runs each first-layer filter over the L with conv2d, and the corner detector multiplies two of those feature maps entry by entry.

Chapter 5

A5. Landmarks, and patches as tokens

  • AlexNet (2012) won the ImageNet competition by a wide margin by training a deep CNN on GPUs, which started the deep-learning boom.
  • VGG (2014) showed that deep stacks of small 3×3 filters work well.
  • ResNet (2015) added residual (skip) connections, letting gradients bypass layers, and made networks of 100+ layers trainable. The same idea sits inside every transformer (see primer.ml.deep_nets).
  • Vision Transformers (2020) cut an image into patches and treat each patch as a token, then apply ordinary attention.

Everyday picture for patches. Cut a photo into a grid of jigsaw pieces, lay them out in a row, and read them like the words of a sentence.

Tiny worked example A 224×224 colour image cut into 16×16 patches gives (224/16)² = 196 patches, each flattened to 16·16·3 = 768 numbers: 196 tokens of 768 values.

Figure 6 · Diagram

Reading it: after the cut-and-flatten step, an image is just a sequence of 196 tokens, and everything downstream is the same transformer used for text. Multimodal language models read images mostly this way.

In code: patchify does the cut-and-flatten step, turning an image into one row of numbers per patch.

Why it matters in practice. CNNs remain efficient and strong for small vision tasks, on-device models and limited data, because their built-in assumptions (locality, weight sharing) mean they learn from less. At large scale, Vision Transformers dominate.

Part B: recurrent networks

Chapter 6

B1. An RNN reads with a one-page summary

Everyday picture You read a book one word at a time, and you are allowed to keep only a single page of notes. After every word you rewrite the page: blend what the page said with the new word. At the end, the page is all you have. That page is the hidden state.

Tiny worked example The sentence "not very good", with each word turned into one number: not = −1, very = 0.5, good = 1. The summary is a single number h, starting at 0. The rule: new h = tanh(0.5 × old h + 1 × word). (tanh squashes any number into the range −1 to 1: tanh(0) = 0, tanh(1) = 0.76, tanh(−1) = −0.76.)

step word 0.5 × old h + word new h = tanh(…)
1 not (−1) 0.5 × 0 + (−1) = −1 −0.762
2 very (0.5) 0.5 × (−0.762) + 0.5 = 0.119 0.119
3 good (1) 0.5 × 0.119 + 1 = 1.059 0.785

The final summary is strongly positive: the "not" at the start has been almost washed out, because the page was rewritten twice since. This is the central weakness of RNNs, in three lines of arithmetic.

Figure 7 · Diagram

Reading it: this is one RNN "unrolled in time": the three cells are the same cell with the same weights, drawn once per step. Each step takes the previous summary (arrow from the left) and the next word (arrow from below) and produces the new summary. Everything the network knows about "not" must survive two more rewrites to reach the end.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here Shape / range
the time step (word position) 1, 2, 3…
the input at step t (a word's vector) (inputs,)
the hidden state (the summary page) after step t (hidden,), each entry in −1…1
the summary before this word (hidden,)
recurrent weights: how the old summary feeds the new one (hidden, hidden)
input weights: how the word feeds the new summary (hidden, inputs)
bias (hidden,)
hyperbolic tangent: squashes each number into −1…1

In words: the new summary is the squashed sum of the old summary times its weights, the new word times its weights, and a bias.

On the worked example: one-number summary, W_h = 0.5, W_x = 1, b = 0: h₁ = tanh(0.5·0 − 1) = −0.762; h₂ = tanh(0.5·(−0.762) + 0.5) = 0.119; h₃ = tanh(0.5·0.119 + 1) = 0.785.

Level 3: in Python
import math
W_h, W_x, b = 0.5, 1, 0
# not, very, good
x = [-1, 0.5, 1]
# h_0: a blank page
h = 0
for x_t in x:
    # h_t = tanh(W_h h_{t-1} + W_x x_t + b)
    h = math.tanh(W_h * h + W_x * x_t + b)
    print(round(h, 3))  # → -0.762 0.119 0.785

In code: RNNCell holds W_h, W_x and b; RNNCell.step is the formula once, RNNCell.run applies it along a sequence, and RNNCell.scalar builds the one-number cell of the worked example.

Chapter 7

B2. Why RNNs forget: the vanishing gradient

Everyday picture A photocopy of a photocopy of a photocopy. Each copy loses a little; after twenty copies the original is unreadable. Training an RNN sends a correction signal backwards through every step, and each step multiplies it by a factor. Factors below 1 fade the signal to nothing (vanishing gradient); factors above 1 blow it up (exploding gradient).

Tiny worked example With a recurrent weight of 0.5 and zero inputs, each step back multiplies the signal by exactly 0.5. Ten steps back: 0.5¹⁰ = 0.00098, about a thousandth. With a weight of 1.5 instead: 1.5¹⁰ = 57.7, and training diverges.

Level 3: the formula and its symbols

Symbols

Symbol Meaning here Shape / range
"how much does the final summary change if the starting summary changes a little?" (a derivative, one per pair of entries) (hidden, hidden)
multiply the factors for every step from 1 to T
the slope of tanh at step t: 1 near zero, near 0 when tanh saturates 0…1
a matrix with these values on the diagonal and zeros elsewhere (hidden, hidden)
the recurrent weights, as above (hidden, hidden)

In words: the influence of the start on the end is the product, over every step, of tanh's slope times the recurrent weights, so it shrinks or grows geometrically with the number of steps.

On the worked example: h stays 0, so every slope is 1, and the product is 0.5 × 0.5 × … (ten times) = 0.00098.

Level 3: in Python
W_h = 0.5
# zero inputs keep every h_t at 0
h = [0.0] * 10
influence = 1
for h_t in h:
    # Π over t of tanh's slope times W_h
    influence *= (1 - h_t ** 2) * W_h
round(influence, 5)  # → 0.00098
# the same product with a weight of 1.5
round(1.5 ** 10, 1)  # → 57.7

Figure 8 · Drawn from the lesson's code

0 10 20 30 40 50 steps between the start and now 1 0 − 1 8 1 0 − 1 5 1 0 − 1 2 1 0 − 9 1 0 − 6 1 0 − 3 1 0 0 gradient size (log scale) How much the start still matters vanilla RNN: ∂h_t / ∂h_0 LSTM: ∂c_t / ∂c_0 (forget bias 3)

Gradient reaching back through time on a log scale: the plain RNN's plunges to about 10⁻¹⁶ after 50 steps while the LSTM's stays near 1

Reading it: the x-axis is how many steps separate the start of the sequence from the current step; the y-axis (log scale) is how strongly the current memory still responds to a nudge in the starting memory. The plain RNN's line plunges: after 20 steps the start has almost no influence, and by 50 it's around 10⁻¹⁶, which means it cannot learn anything about the start. The LSTM's line stays near 1 across all 50 steps. That flat line is the reason LSTMs replaced plain RNNs.

In code: RNNCell.influence_of_start multiplies out the product above for one sequence, and gradient_through_time measures both lines of the figure.

Chapter 8

B3. LSTM: a notebook with an eraser, a pen and a highlighter

Everyday picture Instead of rewriting the whole page after every word, keep a notebook (the cell state) and three tools, each controlled by a dial from 0 to 1 that the network sets for itself at every step:

  • the eraser (forget gate f): how much of each line to keep;
  • the pen (input gate i): how much of the new note to write in;
  • the highlighter (output gate o): how much of the notebook to show the outside world right now.

Because the notebook is edited rather than rewritten, information can pass through many steps untouched: eraser off, pen off, and the line survives.

Tiny worked example One-line notebook holding 0.8.

eraser keeps f pen writes i new note g highlighter o new notebook c = f·0.8 + i·g shown h = o·tanh(c)
1 0 – 1 0.8 (kept, even after 100 steps) 0.664
0 0 – 1 0 (wiped) 0
0 1 0.5 1 0.5 (overwritten) tanh(0.5) = 0.462
1 0 – 0 0.8 (kept) 0 (hidden)

Figure 9 · Diagram

Reading it: follow the top line: the old notebook is multiplied by the eraser setting, the pen's contribution is added, and the result is the new notebook. There is no squashing on that line, only a multiply and an add, so when the eraser is near 1 the notebook (and the training signal flowing back along it) passes through almost unchanged. The dotted arrows show that all four dials are computed from the current input and the previous shown state, so the network decides at each step what to forget, write and show.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here Shape / range
forget (eraser), input (pen) and output (highlighter) gates (hidden,), each 0…1
the candidate note to write (hidden,), −1…1
the cell state: the notebook (hidden,)
the hidden state: what the cell shows (hidden,)
the input and previous shown state, stacked into one vector (inputs + hidden,)
learned weights for each gate (hidden, inputs + hidden)
sigmoid, 1/(1 + e⁻ᶻ): squashes to 0…1, so it works as a dial
multiply element by element (entry 1 with entry 1, and so on)

In words: three sigmoid dials and one candidate are computed from the input and the previous state; the notebook keeps f of itself and adds i of the candidate; the cell shows o of the squashed notebook.

On the worked example: third row: f = 0, i = 1, g = 0.5, o = 1, so c = 0·0.8 + 1·0.5 = 0.5 and h = 1·tanh(0.5) = 0.462.

Level 3: in Python
import math
def sigma(z):
    # σ: any score becomes a dial between 0 and 1
    return 1 / (1 + math.exp(-z))
[round(sigma(z), 3) for z in (-4, 0, 4)]  # → [0.018, 0.5, 0.982]
def lstm_step(f, i, g, o, c_prev=0.8):
    # c_t = f ⊙ c_{t-1} + i ⊙ g
    c = f * c_prev + i * g
    # h_t = o ⊙ tanh(c_t)
    h = o * math.tanh(c)
    return c, h
for f, i, g, o in [(1, 0, 0, 1), (0, 0, 0, 1), (0, 1, 0.5, 1), (1, 0, 0, 0)]:
    # the four rows of the table, dials pinned by hand
    c, h = lstm_step(f, i, g, o)
    print(c, round(h, 3))  # → 0.8 0.664 0.0 0.0 0.5 0.462 0.8 0.0

GRUs simplify this to two dials and no separate notebook: an update gate z chooses between keeping the old state (z = 1) and taking a new candidate (z = 0), and a reset gate decides how much old state feeds that candidate: h = (1 − z) ⊙ n + z ⊙ h_prev, where n is the candidate. Similar performance, fewer parameters.

In code: LSTMCell holds the four gates' stacked weights; LSTMCell.step computes the dials and updates the notebook, LSTMCell.run carries it along a sequence, and LSTMCell.fixed_gates pins the dials to replay the table above; GRUCell.step is the two-dial version.

Chapter 9

B4. Why transformers won

Everyday picture An RNN is a line of people passing a note: the last person hears about the first only through everyone in between, and nobody can start until the person before them finishes. A transformer is a meeting where everyone can speak to everyone directly, all at once.

Tiny worked example 1,000 tokens. An RNN needs 1,000 steps one after another, and information from token 1 reaches token 1,000 through 999 hand-offs. A transformer layer processes all 1,000 in 1 parallel step, and any token reaches any other in 1 hop of attention.

Figure 10 · Diagram

Reading it: in the RNN, information about "The" must survive three hand-offs to reach "down", and the four steps cannot run at the same time. In the transformer, all four positions go into attention together and every pair is compared directly. Parallel training is what made it practical to train on vastly more data, and that is what led to modern language models. The price is attention's cost growing with the square of the sequence length (see primer.ml.attention).

In code: sequential_steps and path_length return the two counts in the worked example for an RNN or a transformer.

State-space models such as Mamba revisit recurrence with a design that trains in parallel and runs in time linear in sequence length. They carry a compressed state like an RNN but avoid its training bottleneck, and some hybrid models mix them with attention.

Figure 11 · Drawn from the lesson's code

not very good word just read −1.00 −0.75 −0.50 −0.25 0.00 0.25 0.50 0.75 1.00 hidden state h (the summary) Reading "not very good": the "not" fades -0.762 0.119 0.785

The one-number summary after each word of "not very good": negative after "not", near zero after "very", strongly positive after "good", so the negation is lost

Reading it: the worked example as a picture. Each bar is the one-number summary after reading a word. "not" drives it negative; "very" pulls it back near zero; "good" pushes it strongly positive. The final summary would read as positive sentiment, which is wrong: the negation had to survive two rewrites and didn't. Gates (LSTM) and direct connections (attention) are the two historical fixes.

Test yourself

9 questions

Answer each one out loud or on paper before you open it. If you can explain it, you know it.

Question 1Q: What does one number in a feature map mean?Think it through, then reveal

A: How strongly the filter's pattern matches the image patch at that position: the sum of filter weights times the pixels under them.

Question 2Q: Why does a CNN need far fewer parameters than a dense layer on images?Think it through, then reveal

A: Weight sharing: one small filter is reused at every position, so the parameter count depends on filter size and number of filters, not on image size.

Question 3Q: What does pooling buy you?Think it through, then reveal

A: Smaller maps (less computation), a faster-growing receptive field, and tolerance to small shifts, since the strongest response in a neighbourhood survives wherever exactly it was.

Question 4Q: Why did VGG use stacks of 3×3 filters instead of larger ones?Think it through, then reveal

A: Two 3×3 layers see a 5×5 region with 18 weights instead of 25, and add an extra nonlinearity between them.

Question 5Q: How does a Vision Transformer turn an image into tokens?Think it through, then reveal

A: It cuts the image into fixed-size patches (e.g. 16×16), flattens each into a vector, projects it to the model width and adds a position embedding; the patches are then processed like words.

Question 6Q: Why do plain RNNs struggle with long-range dependencies?Think it through, then reveal

A: Backpropagation through time multiplies the gradient by the recurrent weights and tanh slopes at every step, so it shrinks geometrically (or explodes) and early inputs stop influencing learning.

Question 7Q: How do LSTM gates fix that?Think it through, then reveal

A: The cell state is updated additively, c = f ⊙ c_prev + i ⊙ g, so with the forget gate near 1 information and gradients flow through many steps almost unchanged.

Question 8Q: Why did transformers replace RNNs?Think it through, then reveal

A: RNNs process tokens sequentially, which prevents parallel training, and force all history through one fixed-size state. Attention connects any two tokens in one step and trains all positions in parallel.

Question 9Q: What did CNNs contribute that still matters?Think it through, then reveal

A: Residual connections (from ResNet), which make very deep networks trainable and are in every transformer block, plus the general lesson that built-in assumptions help when data is limited.

Primary sources

The papers behind this lesson

He, Zhang, Ren & Sun, Deep Residual Learning for Image Recognition (2015): Introduced residual connections, making networks of 100+ layers trainable.

Read the annotated companion →The paper ↗

Vaswani et al., Attention Is All You Need (2017): Replaced recurrence with attention, enabling parallel training and one-hop paths between tokens.

Read on rumblr →The paper ↗

Krizhevsky, Sutskever & Hinton, ImageNet Classification with Deep Convolutional Neural Networks (NeurIPS 2012). Trained a deep CNN on GPUs and won ImageNet by a wide margin, starting the deep-learning boom.

Simonyan & Zisserman, Very Deep Convolutional Networks for Large-Scale Image Recognition (VGG, 2014): Showed deep stacks of 3×3 filters work well.

The paper ↗

Dosovitskiy et al., An Image is Worth 16x16 Words (ViT, 2020): Applied a plain transformer to image patches as tokens.

The paper ↗

Hochreiter & Schmidhuber, Long Short-Term Memory (1997): Introduced the gated cell state that carries information across long sequences.

The paper ↗

Cho et al., Learning Phrase Representations using RNN Encoder-Decoder (2014): Introduced the GRU.

The paper ↗

Pascanu, Mikolov & Bengio, On the difficulty of training Recurrent Neural Networks (2012): Analysed vanishing and exploding gradients and proposed gradient clipping.

The paper ↗

Gu & Dao, Mamba: Linear-Time Sequence Modeling with Selective State Spaces (2023): A recurrent-style model that trains in parallel and scales linearly with length.

The paper ↗

Researcher's shelf

Further reading

  • Stanford CS231n, Convolutional Neural Networks: https://cs231n.github.io/convolutional-networks/
  • Christopher Olah, Understanding LSTM Networks: https://colah.github.io/posts/2015-08-Understanding-LSTMs/
  • Andrej Karpathy, The Unreasonable Effectiveness of Recurrent Neural Networks: http://karpathy.github.io/2015/05/21/rnn-effectiveness/
  • Olah, Mordvintsev & Schubert, Feature Visualization (Distill): https://distill.pub/2017/feature-visualization/

About this lesson. This is the illustrated edition of a lesson from the open-source AI Primer. Its text, figures and numbers are generated from the Primer's source at commit c8d5c21, so the two always agree: the explanation, the code that builds it and the tests that prove it.