COE 592Lecture 02Full guide
Neural network fundamentals
The whole lecture on one page, taught concept by concept. Work through the parts in order, mark each concept once you understand it, and open the slide chips when you want the original slides.
- Parts
- 6
- Concepts
- 28
- Slides
- 45
- Reading
- 168 min
Part 01: Neurons, weights and the fully connected layer
The vocabulary of neural networks and the simplest layer, where every output neuron sees every input neuron.
5 concepts, slides 1-7
Why this part matters
Every TinyML decision you will make in this course, whether to prune a model, quantize it, or pick a smaller architecture for a Cortex-M board, starts from two counts that this part defines. Parameters are what a model stores. Activations are what it computes for each input. Until you can produce both numbers from a network diagram, no later result about efficiency will mean anything concrete.
The lecture opens by fixing vocabulary that the rest of the course, and most papers you will read, take for granted: neuron, synapse, weight, activation, feature, parameter, width, depth. It then introduces the simplest layer, the fully connected layer, and writes it as a matrix product with named tensor shapes. Exams ask for those shapes and for parameter counts. Your research project needs the same arithmetic to decide whether a model fits the flash and RAM of a device.
By the end you can
- Write a neuron as y = f(sum_i w_i x_i + b) and name what each symbol corresponds to in the biological picture.
- Use the two synonym families (synapses = weights = parameters; neurons = features = activations) and count layers without counting the input.
- State the shapes X (n, c_i), W (c_o, c_i), b (c_o,) and Y (n, c_o) of a fully connected layer and write Y = X W^T + b.
- Count the parameters and per-sample activations of any MLP, and say which count the batch size touches.
- Map parameters to flash storage and activations to runtime RAM on an embedded target.
Take a concrete board. The STM32F746, an ARM Cortex-M7 microcontroller used throughout the TinyML literature, has 320 kB of SRAM and 1 MB of flash (Lin et al., MCUNet, section 1). A ResNet-50 carries about 25.6 million parameters (torchvision), which is roughly 100 MB of weights in single precision. It does not fit, and no clever compiler will change that. Whether any network fits comes down to two numbers, and the whole point of this lecture is to teach you how to produce them from a diagram.
Two microcontrollers from the MCUNet paper
- STM32F746
- 320 kB SRAM, 1 MB flash
- STM32H743
- 512 kB SRAM, 2 MB flash
The two memories play different roles, and MCUNet states the split in one sentence: SRAM constrains the activation size, which is read and written at runtime, while flash constrains the model size, which is read only (Lin et al., MCUNet). So a model has a storage cost, the number of parameters it carries, and a runtime cost, the number of activations it must hold in memory while it computes. This part defines both words precisely and shows how to count them for the simplest layer. The parts that follow do the same for convolutions, pooling, normalization and attention.
The lecture's outline is a two-step plan. First fix the terminology: neuron, synapse, activation, feature, weight, parameter. Then learn the building blocks one at a time: fully connected, convolution, grouped and depthwise convolution, pooling, normalization, and the transformer block. This part covers the terminology and the first block. Everything later is a variation on the counting you learn here.
Start with a single unit and three numbers arriving at it: x0 = 1, x1 = 2 and x2 = 0.5. The unit does not treat them equally. It trusts the first a little, distrusts the second, and trusts the third a lot: w0 = 0.5, w1 = -1, w2 = 2. It also has a personal lean of b = 0.25. Multiply each input by its trust, add everything up, and the total is 0.5 - 2 + 1 + 0.25 = -0.25. One last step decides whether the unit speaks: a function f applied to that total.
Worked example
One neuron with three inputs
Scale each input by its weight
0.5 × 1 = 0.5, -1 × 2 = -2, 2 × 0.5 = 1.Sum and add the bias
0.5 - 2 + 1 + 0.25 = -0.25.Apply the activation function
With f the identity the output is -0.25. With f = ReLU, which returns max(0, z), the output is 0.The activation function decides whether the neuron fires
Same inputs, same weights, same bias: -0.25 or 0 depending only on f.
That is the whole artificial Neuron. Jurafsky and Martin put it in one sentence: a neural unit takes a weighted sum of its inputs with one additional term called the bias, then applies a function to the result. In symbols, with i ranging over the inputs of neuron j:
The slide draws this formula on top of a biological neuron, and the mapping is worth learning because the course reuses the biological words as technical terms. Sze, Chen, Yang and Emer, whose survey carries the same figure (adapted from Stanford CS231n), describe the key property of a Synapse: it scales the signal crossing it, and that scaling factor is the Weight. Each raw input x_i arrives along an input axon, is scaled to w_i x_i at the synapse, and travels down a dendrite into the cell body, which sums the products; the output axon carries the result out. They add that the input and output signals of a neuron are what the field calls activations.
| Structure | What it does biologically | What it is in the formula |
|---|---|---|
| Input axon | Carries the raw signal x_i from the previous neuron | An input feature, one entry of the vector x |
| Synapse | Scales the signal crossing it | The weight w_i multiplying x_i |
| Dendrite | Carries the scaled signal w_i x_i into the cell body | One product term of the sum |
| Cell body | Accumulates the incoming signals | The sum of all w_i x_i plus one bias b |
| Axon | Fires when the total is large enough | The activation function f and the output y_j |
Two details of the formula matter for counting parameters later. There is one weight per incoming edge, so a neuron with three inputs owns three weights. There is exactly one Bias per neuron, not one per edge, which is why the figure shows three w symbols and a single b. The activation function f is applied once, to the finished sum. Later in this lecture you will meet several choices for f (sigmoid, ReLU, ReLU6, swish); for now it is enough to know that it is usually non-linear and that it is what lets stacked neurons compute anything more interesting than a single straight-line function.
Recall
Write the formula for one neuron and name which symbol is the synapse, which arrives on the input axon, what the dendrite carries, and which is the output axon.
Now wire many neurons together. The network on the slide has a row of 5 inputs, then rows of 4, 3 and 2 neurons, with every neuron connected to every neuron in the row above. Count the edges band by band: 5 × 4 = 20, 4 × 3 = 12, 3 × 2 = 6, so 38 edges. Count the biases, one per computed neuron: 4 + 3 + 2 = 9. That is 47 numbers the network stores and 9 numbers it computes per input. Everything else in this concept is the vocabulary for those two counts.
Two synonym families
The slide attaches two labels to the diagram, and each is a family of three words the course uses interchangeably. The edges are synapses, weights or parameters. The nodes are neurons, features or activations. Sze et al. state the convention directly: the outputs of the neurons are often referred to as activations, and the synapses are often referred to as weights. Which word a paper picks depends on what it is emphasising. "Synapse" stresses the biology, "weight" the arithmetic, "parameter" the fact that it is learned and stored. "Feature" stresses what a node represents, "activation" the fact that it is a computed value that must be held in memory.
| Layer | Weights | Biases | Parameters | Neurons |
|---|---|---|---|---|
| Layer 0, hidden (5 to 4) | 5 × 4 = 20 | 4 | 24 | 4 |
| Layer 1, hidden (4 to 3) | 4 × 3 = 12 | 3 | 15 | 3 |
| Layer 2, output (3 to 2) | 3 × 2 = 6 | 2 | 8 | 2 |
| Total | 38 | 9 | 47 | 9 |
CS231n gives a second example you can use to check your method: a network with layers of sizes 3, 4, 4 and 1 has 4 + 4 + 1 = 9 neurons, 3 × 4 + 4 × 4 + 4 × 1 = 32 weights and 9 biases, for 41 learnable parameters. The recipe is always the same: multiply adjacent layer sizes for weights, add each non-input layer size for biases.
Counting layers, and why the input does not count
The slide calls this a 3-layer network with 2 hidden layers, and both numbers follow one convention: the input row is not a layer. CS231n states it plainly: when we say N-layer neural network, we do not count the input layer, so a single-layer network has no hidden layers. Jurafsky and Martin say the same in a figure caption. The reason is that the inputs compute nothing. They own no weights and no biases; they are simply the numbers handed in. The three computed rows are the layers, the two blue rows in the middle are the hidden layers, and the gray bottom row is the output layer, counted but not hidden.
Width and depth
The sentence at the bottom of the slide is quoted from Goodfellow, Bengio and Courville: the dimensionality of the hidden layers determines the width of the model. On the same pages they define the other axis: a network is a chain of functions, and the overall length of the chain gives its depth. So the slide network has width 4 at its widest hidden layer and depth 3. Making a network wider adds neurons to a row, which grows both parameters and activations. Making it deeper adds rows, which also grows both, but lets features be composed out of earlier features.
Quick check
How many learnable parameters does the 5-4-3-2 network on slide 4 contain in total?
Quick check
In the vocabulary of this lecture, what does the width of a model refer to?
Recall
In the phrase "3-layer network with 2 hidden layers", which layer is counted but not hidden, and which row is not counted at all?
Recall
What do width and depth of a model mean?
Take one band of the ladder and give it names: c_i = 5 inputs x0 to x4 and c_o = 3 outputs y0 to y2, every output connected to every input. Write out the first output by hand: y0 = w00 x0 + w01 x1 + w02 x2 + w03 x3 + w04 x4 + b0. Do the same for y1 and y2. That is 15 products and 3 biases, 18 parameters. Now stack the three rows of weights on top of each other and you have a 3 × 5 table. That table is W, and the three equations collapse into one matrix product.
This is the fully connected layer, also called a linear layer. Jurafsky and Martin define fully connected exactly this way: each unit in a layer takes as input the outputs of all the units in the previous layer, with a link between every pair of units in adjacent layers. The word Channel is the lecture's general name for a feature dimension, so c_i and c_o count input and output channels; for a fully connected layer a channel is simply one neuron.
Tensor shapes of a fully connected layer
- Input features X
- (1, c_i) for one sample, (n, c_i) for a batch
- Output features Y
- (1, c_o) for one sample, (n, c_o) for a batch
- Weights W
- (c_o, c_i)
- Bias b
- (c_o,)
The transpose in Y = X Wᵀ is not decoration. W is stored with one row per output neuron, shape (c_o, c_i), so that row i holds the weights of y_i. To multiply a row vector x of length c_i by it, the matrix has to be flipped to (c_i, c_o). PyTorch uses precisely this convention: nn.Linear computes y = x Aᵀ + b with a weight of shape (out_features, in_features) and a bias of shape (out_features). Jurafsky and Martin write the same matrix as W ∈ R^(n1 × n0), with element W_ji the weight from input i to hidden unit j. Output index first, input index second, in every source.
Batching: more rows, same weights
Nothing in the product cares whether X has one row. Put n samples in as n rows and the same multiplication produces n rows of outputs. That is all the batch size is: a leading dimension on X and Y. PyTorch describes the input as (*, H_in), where the star means any number of leading dimensions, and the output as (*, H_out); the weight stays (H_out, H_in) no matter what the star is. That is the fact to hold on to: W and b never change with n.
Worked example
The slide 5 layer with a batch of 32
Shapes
X (32, 5), W (3, 5), b (3,), Y (32, 3).Values held
X holds 32 × 5 = 160 numbers and Y holds 32 × 3 = 96. Both are activations.Parameters
Still 15 weights and 3 biases, 18 in total, identical to the single-sample case.Work
Each output is 5 multiply-accumulates, so 15 per sample and 32 × 15 = 480 for the batch.Batch size scales activations and work, never parameters
Activations went from 8 to 256 values; parameters stayed at 18.
Move a slider to see which counts respond.
- FC1 weights, W (3, 5)
- 3 × 515
- Biases, one per output neuron
- 33
- Parameters, stored in flash
- 15 + 318
- Activations per batch, live in RAM
- 1 × 88
- Multiply-accumulates per batch
- 1 × 1515
- Parameter storage
- 18 × 4 B72 B
- Activation memory
- 8 × 4 B32 B
The highlighted edges carry w₀₀ and, once the layer has more than one edge, the last weight, indexed (output, input) as in y_i = Σ_j w_ij x_j + b_i. Parameters count every edge plus one bias per output neuron; activations count every node value for every sample in the batch, inputs included.
What the two counts cost on a microcontroller
Scale the same arithmetic to a realistic first layer, one that takes a flattened 28 × 28 MNIST digit (784 values) to 128 hidden units. The table separates what the layer stores from what it computes, and shows which memory of the STM32F746 each column lands in.
| Quantity | Batch n = 1 | Batch n = 8 | Memory |
|---|---|---|---|
| Weights | 784 × 128 = 100,352 | same | flash |
| Biases | 128 | same | flash |
| Parameters | 100,480 | same | flash |
| Parameter bytes, fp32 | 401,920 B (about 392 KiB) | same | flash |
| Parameter bytes, int8 | 100,480 B | same | flash |
| Activations | 784 + 128 = 912 values | 8 × 912 = 7,296 values | SRAM |
The 401,920 bytes of fp32 weights would not fit the board's 320 kB of SRAM but sit comfortably in its 1 MB of flash, which is exactly why weights live in flash. Storing them as 8-bit integers cuts the same column by four, the first hint of why quantization matters later. Only the activation row moved when the batch grew, and it moved linearly.
Quick check
A fully connected layer maps 5 input features to 3 output features. In the course's convention, what is the shape of its weight tensor W?
Quick check
The batch size of an FC layer with 5 inputs and 3 outputs grows from 1 to 32. Which count changes?
Recall
Write the shapes of X, W, b and Y for a batched FC layer with n samples, c_i inputs and c_o outputs, and the formula that joins them.
Recall
Which quantity changes when the batch size doubles, and which microcontroller memory does each quantity map to?
Put a second fully connected layer after the first. The slide takes the 5 to 3 layer and feeds its three outputs into a 3 to 2 layer that produces z0 and z1. The new layer has W2 of shape (2, 3), six weights, and b2 with two biases: 8 more parameters, 26 in total. Per sample it computes two more activations, so 5 computed values, 10 if you include the inputs held in memory.
A chain of fully connected layers is a multilayer perceptron, or MLP. Goodfellow, Bengio and Courville write the chain as f(x) = f⁽³⁾(f⁽²⁾(f⁽¹⁾(x))), call f⁽¹⁾ the first layer and f⁽²⁾ the second, and define depth as the length of that chain. Jurafsky and Martin add a caution about the name: modern multilayer networks are called perceptrons for historical reasons only, since their units are not perceptrons in the strict sense. Use the name, but do not read anything into it.
The shapes must click together: the output channels of layer k are the input channels of layer k + 1. Here c_o = 3 of the first layer becomes c_i = 3 of the second, and the inner dimension of the second product matches. The batch dimension n rides through unchanged, just as it did in one layer.
| Layer | W shape | Weights | Biases | Parameters | Activations per sample |
|---|---|---|---|---|---|
| FC1 (5 to 3) | (3, 5) | 15 | 3 | 18 | 3 |
| FC2 (3 to 2) | (2, 3) | 6 | 2 | 8 | 2 |
| Total | 21 | 5 | 26 | 5 computed, 10 with inputs |
Once you see the MLP as a chain, counting any network becomes mechanical, and this is the skill the rest of the course leans on. For each fully connected layer the parameters are c_o × c_i + c_o; sum over the layers. The activations per sample are the sum of the layer output sizes, plus the input if you are budgeting memory for it. Multiply activations by n for a batch. Never multiply parameters by n.
Quick check
A 5 to 3 fully connected layer is followed by a 3 to 2 layer. How many learnable parameters does the pair contain?
Recall
How many parameters does the slide 7 MLP (5 to 3 to 2) have, and how many activations per sample does it compute?
Recap
If you remember nothing else
- A neuron computes a weighted sum of its inputs plus one bias, then applies an activation function f.
- Edges are synapses, weights or parameters; nodes are neurons, features or activations.
- Layers are counted without the input; 5-4-3-2 is a 3-layer network with 2 hidden layers and 47 parameters.
- Width is the size of the hidden layers, depth is the number of layers in the chain.
- An FC layer has W of shape (c_o, c_i) and b of shape (c_o,), and computes Y = X W^T + b.
- Batching adds rows to X and Y, giving (n, c_i) and (n, c_o); it never changes W or b.
- Parameters per FC layer = c_o x c_i + c_o; the slide 5 layer has 18, the slide 7 MLP has 26.
- Parameters determine flash (model size); activations determine SRAM (runtime memory).
Sources
- Deep Learning, chapter 6: Deep Feedforward NetworksBookMIT Press, Goodfellow, Bengio and Courville, 2016Depth as the length of the chain of layers, width as the dimensionality of the hidden layers (the sentence quoted on slide 4).(opens in a new tab)
- Speech and Language Processing, chapter 6: Neural NetworksBookJurafsky and Martin, 3rd edition draftThe neural unit z = w·x + b, W in R^(n1 × n0), input layer not counted, fully connected definition, MLP naming, XOR.(opens in a new tab)
- Neural Networks Part 1: Setting up the ArchitectureDocsStanford CS231nBiological motivation, the N-layer convention that excludes the input, and the 41-parameter sizing example.(opens in a new tab)
- torch.nn.LinearDocsPyTorch documentationy = x A^T + b with weight (out_features, in_features), bias (out_features), input (*, H_in) and output (*, H_out).(opens in a new tab)
- Efficient Processing of Deep Neural Networks: A Tutorial and SurveyPaperProceedings of the IEEE, Sze, Chen, Yang and Emer, 2017Reproduces the neuron and synapse figure (adapted from CS231n), synapse as a scaling factor, and fixes the activation and weight nomenclature.(opens in a new tab)
- MCUNet: Tiny Deep Learning on IoT DevicesPaperNeurIPS 2020, Lin, Chen, Lin, Cohn, Gan and HanSTM32F746 with 320 kB SRAM and 1 MB flash; SRAM constrains activations, flash constrains model size.(opens in a new tab)
- torchvision.models.resnet50DocsPyTorch documentationLists 25.6 million parameters for ResNet-50, used to size the model that does not fit a microcontroller.(opens in a new tab)
- MIT 6.5940 TinyML and Efficient Deep Learning Computing, Lecture 2: Basics of Deep LearningDocsMIT HAN Lab, Song Han, fall 2024The source deck that slides 2 to 7 reproduce, including the Layer 0 and w42 labels.(opens in a new tab)
Part 02: Convolution layers and their tensor shapes
Local receptive fields and weight sharing, from 1D to 2D convolution, the no-padding output size and a fully worked two-channel example.
4 concepts, slides 8-15
Why this part matters
Every efficiency technique in this course is an operation on the tensors of a convolution layer. Lecture 3 counts parameters and MACs from these shapes, pruning in lecture 4 removes entries of W, quantization shrinks the bits of W and Y, and the depthwise and grouped convolutions later in this lecture change the shape of W itself. Every model-size or MAC estimate for an embedded target starts from that shape and from the formula for the output height h_o.
Part 01 gave you the fully connected layer, whose weight matrix (c_o, c_i) connects every input to every output. This part crosses out that matrix and rebuilds it as a convolution: a small filter that sees only a local patch and is reused at every position. You will learn to read the four Conv2D tensors, to predict how much the feature map shrinks, to compute one output value by hand from a two-channel input, and to count parameters. These four skills are the most reliable exam material in the lecture, and they are the same arithmetic you will do when you size a model for a microcontroller or read a MobileNet table.
By the end you can
- Explain, with the receptive field and weight sharing, why one filter is reused at every position and why the parameter count does not depend on image size.
- Write the shapes of X, Y, W and b for any Conv2D from its c_i, c_o, k_h, k_w and input size, in PyTorch order.
- Compute the output size without padding at stride 1, and with the general stride formula that slide 15 uses ahead of slide 18.
- Compute one output value by hand from a multi-channel input, summing per-channel contributions and adding the bias.
- Count a Conv2D's parameters as c_o c_i k_h k_w + c_o and check it against the 1216 of the ConvNetJS demo.
Take a CIFAR-10 image: 32 x 32 pixels in three colour channels, so 3072 numbers. A single neuron of the Fully connected layer from Part 01 has one weight for each of those 3072 inputs, and CS231n uses exactly this count to show why the fully connected design does not scale to images. A neuron in a Convolution layer does something far more modest. It looks at a 5 x 5 patch of the image across all three channels, so 5 x 5 x 3 = 75 numbers, and ignores everything else. That patch is the neuron's Receptive field, and the slide states the whole idea in one sentence: the output neuron is connected to input neurons in the receptive field.
That sentence hides two separate decisions, and Goodfellow, Bengio and Courville name them in section 9.2 of Deep Learning. The first is sparse interactions. With m inputs and n outputs, a fully connected layer stores m x n weights. If each output is allowed to touch only k inputs, the layer stores k x n. On the CIFAR image that is 75 per output instead of 3072.
The second decision is Weight sharing. Sparse connectivity alone would still give every output position its own private set of 75 weights. Convolution goes further and uses the same 75 numbers at every position. Goodfellow and colleagues put it plainly: rather than learning a separate set of parameters for every location, we learn only one set, so the storage of one filter drops to k parameters. They also give the synonym you will meet in older papers, tied weights. Slide 8 labels its figure Weight Sharing, and slides 12 and 13 show it in motion: the same green, yellow and cyan filters are applied wherever the red window sits on the blue input.
What the saving looks like in a real first layer
CS231n works through AlexNet's first layer to show the size of the effect, and the numbers are worth holding in memory for the exam. The input is 227 x 227 x 3 (the AlexNet paper says 224 x 224; CS231n uses 227 so that (227 - 11)/4 + 1 = 55 comes out exact), the filters are 11 x 11 x 3, and the output is 55 x 55 x 96.
Worked example
AlexNet conv1 with and without weight sharing
Count the output neurons
55 x 55 x 96 = 290,400 outputs, one per position per output Channel.Weights per neuron with a local receptive field only
No sharing
290,400 x 364 = 105,705,600 parameters for one layer.With sharing
One filter per output channel: 96 x 363 = 34,848 weights plus 96 biases, so 34,944.Reduction
About 3025x fewer parameters, and 3025 = 55 x 55 is exactly the number of positions the filter is reused at.
Crossing out the fully connected shapes
The slide introduces the shapes by literally striking through the fully connected ones. In Part 01 the input was X (n, c_i): n samples in the batch (the Batch size), each a vector of c_i features. A 1D convolution adds a spatial axis of width w_i, so X becomes (n, c_i, w_i) and Y becomes (n, c_o, w_o). The weight matrix (c_o, c_i) gains a kernel width k_w and becomes (c_o, c_i, k_w). The bias does not change at all: still one number per output channel, (c_o,). The table already shows the 2D column, which adds a height axis in the same way; the next concept explains each of its axes.
| Tensor | Fully connected | 1D convolution | 2D convolution |
|---|---|---|---|
| Input X | (n, c_i) | (n, c_i, w_i) | (n, c_i, h_i, w_i) |
| Output Y | (n, c_o) | (n, c_o, w_o) | (n, c_o, h_o, w_o) |
| Weights W | (c_o, c_i) | (c_o, c_i, k_w) | (c_o, c_i, k_h, k_w) |
| Bias b | (c_o,) | (c_o,) | (c_o,) |
Recall
A 1D convolution has 8 input channels, 32 output channels and kernel width 5. Write the shapes of W and b, and say what happens to them if the input width doubles.
Quick check
Why does a convolution layer on a 32x32x3 image need far fewer parameters than a fully connected layer?
Start with a layer you could type into PyTorch today: nn.Conv2d(3, 16, kernel_size=5), applied to a batch of 8 RGB images of 32 x 32. Four tensors take part, and every axis of each one has a name.
nn.Conv2d(3, 16, kernel_size=5) on 8 images of 32x32, no padding
- Input X
- (8, 3, 32, 32): batch, input channels, height, width
- Weights W
- (16, 3, 5, 5): output channels, input channels, kernel height, kernel width
- Bias b
- (16,): one scalar per output channel
- Output Y
- (8, 16, 28, 28): batch, output channels, then 32 - 5 + 1 = 28 each way
The general rule is the 2D column that the previous concept left open. The input X is (n, c_i, h_i, w_i), the output Y is (n, c_o, h_o, w_o), the weights W are (c_o, c_i, k_h, k_w) and the bias b is (c_o,). The PyTorch documentation for torch.nn.Conv2d confirms this exact ordering: the weight is stored as (out_channels, in_channels / groups, kernel_size[0], kernel_size[1]) and the bias as (out_channels). The groups divisor is 1 by default; it becomes important in Part 03 when grouped and depthwise convolutions arrive.
A filter is a block, not a square
Slides 9 to 11 make one argument in three frames. The first frame draws a single filter as a green block with edges labelled k_h, k_w and c_i, sitting next to the red window on the input. The block is as deep as the input has channels. When it is applied at one position, it multiplies all c_i x k_h x k_w input values under the window by its own c_i x k_h x k_w weights, sums everything into a single number, and drops that number into one output channel. CS231n states the rule as a sentence to memorise: the extent of the connectivity along the depth axis is always equal to the depth of the input volume.
The second frame adds a yellow filter. It has the same depth c_i, sees the same window, and drops its number into a second output channel. The third frame shows the full stack of c_o filters with a bracket labelled c_o, and the output is now c_o channels deep. That bracket is literally the first axis of W. Choosing c_o is a design decision made by whoever builds the network; it is not derived from c_i.
Bias and batch, the two easy axes
The Bias is added once per output channel, after the whole c_i-deep dot product, which is why its shape is (c_o,) exactly as in the fully connected layer of Part 01. The Batch size n appears in X and Y but never in W or b: the same weights process every sample in the batch. If you are ever unsure of a shape, check that n is absent from the parameters and that c_o leads W.
Recall
Write the shapes of X, W, b and Y for nn.Conv2d(3, 16, kernel_size=5) applied to a batch of 8 images of 32x32 with no padding.
Recall
A Conv2D has 64 input channels and 128 filters of 3x3. How many output channels does it produce, and what is the shape of W?
Quick check
A Conv2D layer has c_i = 3, c_o = 16 and 5x5 kernels. Which is the shape of its weight tensor W?
Slide 14 gives the smallest example that shows the shrink. The input is 4 x 4, the Kernel (filter) is 3 x 3, there is no padding and the window moves one cell at a time. Along the top row the window can start at column 0 or column 1. Starting at column 2 would need columns 2, 3 and 4, and there is no column 4. Two starting positions per axis means the output is 2 x 2.
Worked example
Counting the valid positions on slide 14
Last valid start index
The window of height k_h = 3 starting at row r covers rows r to r + 2. It fits while r + 2 <= 3, so the last start is r = h_i - k_h = 1.Count the starts
Rows 0 and 1, which is h_i - k_h + 1 = 4 - 3 + 1 = 2 positions.Result
h_o = w_o = 2. The feature map is 2 x 2, smaller than the input by k - 1 = 2 along each axis (one cell lost at each border).
Slides 12 and 13 are two frames of the same motion, taken from the MIT 6.5940 lecture that this section of the deck reproduces. The red window moves one step at a time along a spatial dimension, and at every stop the same green, yellow and cyan filters are applied, which is why slide 12 carries the label Weight Sharing. Nothing about the filters changes as the window moves; this is where Weight sharing becomes visible rather than stated. Each stop adds one short stack of c_o values to the output, one value per filter, so the output has exactly one spatial cell per valid window position. The count of valid positions is the whole story of the output size.
The general formula, one part early
Slide 15 asks you to use a Stride of 2 before slide 18 has defined stride, so you need the general rule now. With padding p on every border and stride s, Dumoulin and Visin's relationship 6 gives the output size, and the PyTorch documentation states the same formula with a dilation term that is 1 throughout this course.
| h_i | k | s | p | Arithmetic | h_o |
|---|---|---|---|---|---|
| 4 | 3 | 1 | 0 | (4 - 3)/1 + 1 | 2 |
| 4 | 2 | 2 | 0 | (4 - 2)/2 + 1 | 2 |
| 32 | 5 | 1 | 0 | (32 - 5)/1 + 1 | 28 |
| 32 | 5 | 1 | 2 | (32 + 4 - 5)/1 + 1 | 32 |
| 224 | 7 | 2 | 3 | floor((224 + 6 - 7)/2) + 1 | 112 |
Two rows deserve a second look. The 32, 5, 1, 2 row is the ConvNetJS demo on slide 25, which uses pad 2 precisely so that its 5 x 5 convolution keeps the 32 x 32 size, giving the 32 x 32 x 16 output printed there. The 224, 7, 2, 3 row is the first layer of a ResNet, which halves the spatial size in one step. Both are the same formula.
Recall
A 32x32 input meets a 5x5 kernel at stride 1. What is the output size with no padding, and with padding 2?
Quick check
An input of 4x4 is convolved with a 3x3 kernel, stride 1, no padding. What is the output spatial size?
Slide 15 is the exam question in miniature: two input channels of 4 x 4, one output channel, a 2 x 2 filter, Stride 2, no padding and no Bias. Input channel 1 has every row equal to 1 2 3 4; input channel 2 has every row equal to 5 6 7 8. The filter's first slab is [[2, 1], [1, 2]] and its second slab is [[1, 2], [2, 1]]. In the shapes of the previous concepts, X is (1, 2, 4, 4) and W is (1, 2, 2, 2).
The formula says what the filter-is-a-block idea implied: the sum over c folds all input channels into one number. There is no separate output per input channel. With that in mind the four values of the slide are four applications of the same recipe. Strictly, this is cross-correlation: mathematical convolution flips the kernel first, and this formula does not. Deep learning libraries, PyTorch and this deck all call the unflipped version convolution (Goodfellow et al., section 9.1), and this course follows that convention.
Worked example
Slide 15, worked from the shapes down
Output size
Stride 2 with no padding: (4 - 2)/2 + 1 = 2, so the output is 2 x 2 and the window visits four positions, starting at rows and columns 0 and 2.Top-left value, window at rows 0 to 1, columns 0 to 1
Per-channel contributions at position (0, 0)
- Channel 1
- 2·1 + 1·2 + 1·1 + 2·2 = 9
- Channel 2
- 1·5 + 2·6 + 2·5 + 1·6 = 33
- Bias
- 0
- Sum
- 9 + 33 = 42
Top-right value, window at rows 0 to 1, columns 2 to 3
Per-channel contributions at position (0, 1)
- Channel 1
- 2·3 + 1·4 + 1·3 + 2·4 = 21
- Channel 2
- 1·7 + 2·8 + 2·7 + 1·8 = 45
- Bias
- 0
- Sum
- 21 + 45 = 66
Bottom row
Every row of both inputs is identical, so the windows at rows 2 to 3 see the same numbers and give 42 and 66 again.Output feature map
[[42, 66], [42, 66]], exactly as printed on the slide.
Now do it with your hands. The stepper below holds the slide 15 numbers, highlights the current window on both input channels, lists the products channel by channel and fills the output one cell at a time. Switch the stride to 1 to see the 3 x 3 output, then change a few cells and predict the result before you press Next.
- This position
- press Next to place the window
Every input and filter cell is editable, negative weights included; a blank cell counts as 0. Rows and columns are numbered from 0, as in the formula above. Each channel of the filter multiplies the matching input channel inside the highlighted window, the two channel sums are added into one value, and the bias is added last.Channel 1 and channel 2 keep their colours throughout.
| Stride 2 (the slide) | Stride 1 | |
|---|---|---|
| Output size | (4 - 2)/2 + 1 = 2, so 2x2 | (4 - 2)/1 + 1 = 3, so 3x3 |
| Channel 1 partials, one row | 9, 21 | 9, 15, 21 |
| Channel 2 partials, one row | 33, 45 | 33, 39, 45 |
| One output row | 42, 66 | 42, 54, 66 |
| Windows visited | 4 | 9 |
The stride-1 column shows what the middle window adds. At columns 1 to 2, channel 1 gives 2·2 + 1·3 + 1·2 + 2·3 = 15 and channel 2 gives 1·6 + 2·7 + 2·6 + 1·7 = 39, so 54 sits between 42 and 66. Stride 2 skips that window entirely, which is why the output is smaller and the compute drops from 9 windows to 4. On a large input the compute falls to about a quarter of the stride-1 cost, since both axes are halved.
Recall
In the slide 15 example, what are the per-channel contributions to the top-right output value?
Recall
Same data with stride 1: what is the output size, and what is the middle column?
Quick check
In the slide 15 example (two input channels, one filter with a 2x2 slab per input channel, stride 2, no bias), what is the top-left output value?
Counting parameters
The shapes of W and b give the parameter count directly, and that count is what decides whether a model fits in a microcontroller's flash. The weights number c_o x c_i x k_h x k_w, the size of W, and there is one bias per output channel.
Slide 25 later in this lecture shows the ConvNetJS CIFAR-10 demo, whose first convolution has 16 filters of 5 x 5 over the 3 colour channels. The demo prints 16 x 5 x 5 x 3 + 16 = 1216 parameters, and the formula reproduces it: 1200 weights and 16 biases. Use that number as a retrieval hook; if your formula does not give 1216 for that layer, you have forgotten the biases or swapped a factor.
| Layer | Arithmetic | Parameters | Depends on h_i, w_i? |
|---|---|---|---|
| Conv2D, 16 filters of 5x5, 3 to 16 channels | 16 x 3 x 5 x 5 + 16 | 1216 | No |
| Fully connected, 32x32x3 = 3072 inputs to 16 outputs | 3072 x 16 + 16 | 49,168 | Yes |
| AlexNet conv1, 96 filters of 11x11, 3 to 96 channels | 96 x 3 x 11 x 11 + 96 | 34,944 | No |
| Change | Arithmetic | Parameters |
|---|---|---|
| Input 64x64x3, conv | 16 x 3 x 5 x 5 + 16 | 1216 |
| Input 64x64x3, fully connected | 12288 x 16 + 16 | 196,624 |
| Kernels 3x3 | 16 x 3 x 3 x 3 + 16 | 448 |
| Filters 32 | 32 x 3 x 5 x 5 + 32 | 2432 |
Only the filter geometry and the channel counts move the number.
Recall
How many parameters does nn.Conv2d(3, 16, kernel_size=5) have, and what changes if the images become 64x64?
Quick check
A Conv2D with 16 filters of 5x5x3 plus biases (the ConvNetJS first layer) has how many parameters?
Recap
If you remember nothing else
- A conv output neuron is connected only to the input neurons in its receptive field, and the same filter is reused at every position (weight sharing).
- Shapes: X (n, c_i, h_i, w_i), Y (n, c_o, h_o, w_o), W (c_o, c_i, k_h, k_w), b (c_o,). The batch size n never appears in W or b.
- A filter is not 2D: it is c_i deep and produces exactly one output channel, so c_o filters give c_o channels regardless of c_i.
- Without padding at stride 1, h_o = h_i - k_h + 1: a 4x4 input with a 3x3 kernel gives 2x2; in general h_o = floor((h_i + 2p - k_h)/s) + 1.
- Slide 15: output size (4 - 2)/2 + 1 = 2; top-left 9 + 33 = 42, top-right 21 + 45 = 66; output [[42, 66], [42, 66]]; bias is ignored on the slide.
- Parameters = c_o c_i k_h k_w + c_o; 16 filters of 5x5x3 plus biases give 1216; the count is independent of h_i and w_i, unlike an FC layer's 3072 x 16 + 16 = 49,168.
Sources
- Deep Learning, chapter 9: Convolutional Networks, section 9.2 MotivationBookMIT Press (Goodfellow, Bengio and Courville)Sparse interactions (m x n against k x n), parameter sharing and tied weights, receptive field of one output unit(opens in a new tab)
- MIT 6.5940 TinyML and Efficient Deep Learning Computing, Lecture 02: Basics of Deep LearningDocsMIT HAN Lab (Song Han), Fall 2023Original slides that slides 8 to 14 reproduce: receptive field, tensor shapes, weight sharing, h_o = h_i - k_h + 1(opens in a new tab)
- torch.nn.Conv2dDocsPyTorch documentationWeight (out_channels, in_channels / groups, kH, kW), bias (out_channels), output size formula with padding, dilation and stride(opens in a new tab)
- CS231n Convolutional Neural Networks for Visual Recognition: Convolutional NetworksDocsStanford University3072 weights for one FC neuron on CIFAR-10, depth of connectivity equals input depth, AlexNet conv1 parameter counts with and without sharing(opens in a new tab)
- A guide to convolution arithmetic for deep learningPaperarXiv (Dumoulin and Visin, 2016)Relationship 1: o = i - k + 1; relationship 6: o = floor((i + 2p - k)/s) + 1(opens in a new tab)
- ConvNetJS CIFAR-10 demoDocsAndrej Karpathy, StanfordFirst conv layer: 16 filters of 5x5, stride 1, pad 2 on a 32x32x3 input, 1216 parameters as shown on slide 25(opens in a new tab)
- Gradient-based learning applied to document recognitionPaperProceedings of the IEEE (LeCun, Bottou, Bengio and Haffner, 1998)Local receptive fields and shared weights as the architectural ideas of LeNet-5; section II.A credits earlier work by Fukushima (1980) and LeCun et al. (1989)(opens in a new tab)
Part 03: Padding, stride, receptive field and grouped convolution
How padding and stride control the output size, how the receptive field grows with depth, and how grouped and depthwise convolution cut parameters.
4 concepts, slides 16-20
Why this part matters
Part 02 gave you a convolution that slides one filter over one input. Every embedded CNN you will profile or design in this course, from MobileNet to the TinyML backbones in your project, is built by turning four extra knobs on that layer: padding, stride, depth and grouping.
Those knobs decide the three numbers an embedded engineer lives by. The output size sets how much activation memory a layer needs. The receptive field sets how many layers it takes before one output can see the whole image. The parameter count sets how much flash the weights occupy and, with the output size, how many multiply-accumulates each inference costs. The exam asks for all three directly, and the storage and MAC budgets in your research project come out of the same formulas.
By the end you can
- Compute the output size of any Conv2D from h_i, k, p and s, including the floor, and pick p for a same-size output.
- Explain what each padding mode puts in the border and why none of them adds a parameter.
- Compute the receptive field after L layers, with and without stride, and explain why networks downsample.
- Count weights and multiply-accumulates for standard, grouped and depthwise convolutions, and check that g divides c_i and c_o.
- Argue when a depthwise separable block is the right trade for an embedded device and what it gives up.
Take the 5 x 5 input and 3 x 3 Kernel (filter) from Part 02. The kernel can only sit where it fits entirely inside the image, so it has three horizontal and three vertical positions and the output Feature map is 3 x 3. Stack a second such layer and you get 1 x 1. A third layer cannot run at all. Every layer eats two rows and two columns, and Goodfellow, Bengio and Courville put it plainly: without padding the representation shrinks by one pixel less than the kernel width at every layer.
Padding is the fix. Add a ring of p extra cells around the input before sliding the kernel. With p = 1 the 5 x 5 image becomes 7 x 7, the kernel now has five positions in each direction, and the output is 5 x 5 again. The same 27 weights per filter (3 x 3 over three input channels) do the work; the only thing that changed is where the kernel is allowed to stand.
Read the formula as a story. The padded input is h_i + 2p rows tall. A window of height k_h can start on any row except the last k_h - 1, which leaves h_i + 2p - k_h + 1 starting rows, one per output row. Set h_o = h_i and the equation solves to p = (k - 1)/2, which is a whole number only for odd kernels. That is why 3 x 3, 5 x 5 and 7 x 7 dominate practice: Zhang et al. note that an odd kernel lets the same amount of padding go on both sides and keeps the output pixel centred on its window. Dumoulin and Visin call this half padding, and the deep learning book calls it same convolution, after the MATLAB option of that name.
Worked example
Same, valid and a strided stem
CIFAR-10 input, no padding
32 x 32 image, k = 5, p = 0: 32 - 5 + 1 = 28. The map shrinks by four.Same padding for k = 5
p = (5 - 1)/2 = 2: 32 + 4 - 5 + 1 = 32. The map keeps its size.The slide 16 example
h_i = 5, k = 3, p = 1: 5 + 2 - 3 + 1 = 5.Parameters in every case
Identical. A 3 x 3 filter over three input channels has 27 weights whether p is 0, 1 or 10. Padding changes the output size and therefore the multiply-accumulates, and nothing else.
What goes in the ring
The formula does not care what the new cells contain, but the values do affect what the network learns near the border. The slide pads a 3 x 3 image holding 1 to 9 by p = 2 on every side, which is a good specimen because 2 is the largest reflection padding a 3 x 3 image can take. Zero padding writes zeros, and PyTorch uses it unless told otherwise. Reflection padding mirrors the image across its edge without repeating the edge pixel, so the row above 1 2 3 reads 4 5 6 and the row above that reads 7 8 9. Replication padding stretches each edge pixel outward, so the corner 1 fills the whole top-left 3 x 3 block. Constant padding is zero padding with a value of your choice.
| Mode | Rule | Row 0 | PyTorch |
|---|---|---|---|
| Zero | Fill the border with 0 | 0 0 0 0 0 0 0 | padding_mode='zeros' (default) |
| Reflection | Mirror across the edge; the edge value itself is not repeated | 9 8 7 8 9 8 7 | ReflectionPad2d, padding_mode='reflect' |
| Replication | Repeat the nearest edge value outward | 1 1 1 2 3 3 3 | ReplicationPad2d, padding_mode='replicate' |
| Constant | Fill the border with one chosen value v | v v v v v v v | ConstantPad2d(padding, value) |
The index rule behind the visual is short. For a padded coordinate j in 0 to 6, subtract p to get an offset into the original image. Replication clamps that offset into 0 to 2. Reflection folds it: -1 becomes 1, -2 becomes 2, 3 becomes 1 and 4 becomes 0. Fold both the row and the column offset and you have the source pixel. The PyTorch documentation for ReflectionPad2d walks through exactly this example with the values 0 to 8. PyTorch also offers a 'circular' mode that wraps around to the opposite edge; the slide leaves it out.
Recall
Input 5 x 5, kernel 3 x 3, stride 1. What padding keeps the output 5 x 5, and how many parameters does that padding add?
One output pixel of a 3 x 3 Convolution layer is a weighted sum of a 3 x 3 patch of its input. That patch is its Receptive field. Now feed that layer's output into a second 3 x 3 layer. One pixel of the second layer reads a 3 x 3 block of first-layer outputs, and each of those was itself computed from a 3 x 3 patch. The patches of neighbouring pixels overlap and slide by one cell, so the union of all nine is a 5 x 5 region of the original image. A third layer makes it 7 x 7.
Each stride-1 layer adds k - 1 to the side of the region, because the outermost cells of the new window reach (k - 1)/2 further in each direction. Start from a single pixel and add k - 1 once per layer.
With k = 3 this gives 3, 5, 7, 9, 11 for L = 1 to 5. The deep learning book's figure 9.4 makes the same point and adds the hint that matters next: the effect increases if the network includes strided convolution or pooling.
The problem with large images
A classifier must eventually let one output depend on the whole picture. For a 224 x 224 image and stride-1 3 x 3 layers, solve 2L + 1 >= 224: L = 112 layers before a single output pixel sees everything. Each of those layers works at full resolution, so the activation memory and the multiply-accumulates are enormous. The slide states the problem and the cure in one breath: downsample inside the network.
Stride is the cheapest way to do it. Instead of moving the kernel one cell at a time, move it s cells. The number of window positions drops by about a factor of s in each direction, and so does the output size. Goodfellow, Bengio and Courville describe stride s as mathematically equivalent to a stride-1 convolution followed by keeping every s-th output, only without wasting work on the outputs you would throw away.
The floor is not decoration. The padded input has h_i + 2p - k_h rows on which a window can start after the first, and the kernel visits every s-th of them. If that span is not a multiple of s, the last window would run past the edge and is dropped. Try h_i = 8, k = 3, p = 0, s = 2: the slide's version gives 3.5, which is not a size; the true answer is floor(5/2) + 1 = 3. Dumoulin and Visin state the relationship with the floor, and the PyTorch Conv2d documentation computes the output shape the same way.
Change p or s and watch the output size and the MACs move while the weight count stays still. Only c_i, c_o, k and g touch the parameter count.
How stride changes the receptive field rule
Stride does more than shrink maps. Once a layer has stride 2, two neighbouring outputs of that layer sit two input pixels apart, so every layer after it takes steps of two in input coordinates. Its 3 x 3 window no longer adds 2 pixels to the receptive field; it adds 2 x 2 = 4. The slide 18 top row shows the payoff: a stride-2 first layer followed by one stride-1 layer already reaches 7, where stride 1 needed three layers.
Araujo, Norris and Sim give the general closed form. Call the product of the strides before layer l the jump (some authors say effective stride). Each layer adds (k_l - 1) times its jump.
Worked example
Slide 18, top row, and one step further
Layer 1: k = 3, s = 2
Jump before it is 1, so RF = 1 + 2 x 1 = 3. Jump after it is 2.Layer 2: k = 3, s = 1
RF = 3 + 2 x 2 = 7. Two layers reach what three stride-1 layers reached.Add a third stride-1 layer
Jump is still 2: RF = 7 + 2 x 2 = 11.Or make layers 1 and 2 both stride 2
Jumps 1, 2, 4: RF = 1 + 2 + 4 + 8 = 15 after three 3 x 3 layers.The shortcut only holds for stride 1
L(k - 1) + 1 would predict 7 for all three-layer cases. The general formula gives 7, 11 or 15 depending on where the strides sit.
| Layers | All stride 1 | First layer stride 2 | Every layer stride 2 |
|---|---|---|---|
| 1 | 3 | 3 | 3 |
| 2 | 5 | 7 | 7 |
| 3 | 7 | 11 | 15 |
| 4 | 9 | 15 | 31 |
The jump column is the jump before each layer: the product of the strides of the layers above it. The layer adds (k - 1) times that jump. A stride of 2 in layer 1 doubles how much every later layer adds, which is why the general value pulls away from L(k - 1) + 1.
Real networks live at the strided end of that table. Araujo, Norris and Sim compute receptive fields of 195 for AlexNet, 212 for VGG-16, 483 for ResNet-50 and 1,311 for Inception-v3, all with a total downsampling of 32, and remark that the receptive field usually covers the entire input image. The stride in the ResNet stem you computed in the calculator is the first of those doublings: 224 to 112 in one layer.
Quick check
A 32 x 32 input passes through a 5 x 5 kernel with padding 2 and stride 2. What is the output height?
Quick check
Three 3 x 3 convolutions have strides 2, 1 and 1 in that order. What is the receptive field of one output pixel?
Recall
Write the output size formula with stride and say why the floor is needed.
Recall
Three 3 x 3 layers with strides 2, 2, 1: what is the receptive field?
Padding and stride change the shape of the computation. The last two concepts change its wiring. Take a layer with c_i = c_o = 64 channels and 3 x 3 kernels. Each of the 64 filters reads all 64 input channels, so the layer holds 64 x 64 x 9 = 36,864 weights. Now split the channels into two halves and run two separate convolutions, each mapping 32 inputs to 32 outputs. Each half holds 32 x 32 x 9 = 9,216 weights, so the pair holds 18,432: the same output shape at half the cost.
That is a Grouped convolution with g = 2, or in the slide's words a group of narrower convolutions. In general, split c_i into g blocks of c_i/g channels and c_o into g blocks of c_o/g. Output block j reads only input block j. The slide writes the new Weight shape as (g · c_o/g, c_i/g, k_h, k_w), and the first factor simplifies to c_o: there are still c_o filters, each just reads c_i/g channels instead of all c_i. The Bias stays one per output channel.
| Groups | c_o x (c_i/g) x k x k | Weights | Saving |
|---|---|---|---|
| g = 1 | 64 x 64 x 9 | 36,864 | 1x |
| g = 2 | 64 x 32 x 9 | 18,432 | 2x |
| g = 4 | 64 x 16 x 9 | 9,216 | 4x |
| g = 8 | 64 x 8 x 9 | 4,608 | 8x |
| g = 64 | 64 x 1 x 9 | 576 | 64x |
Each drawn lane stands for 8 real channels. 32 of 64 lane pairs stay connected.
The divisibility rule
The split only works if both channel counts divide evenly. PyTorch says so directly: in_channels and out_channels must both be divisible by groups. With c_i = 64 and c_o = 96, g = 4 is fine (16 inputs and 24 outputs per group) but g = 5 is not, because 64/5 is not an integer. Output channel j belongs to group floor(j / (c_o/g)) and reads only the input channels of that group. The calculator earlier in this part flags any pair that fails the rule.
The idea is older than its name. Krizhevsky, Sutskever and Hinton trained AlexNet on two GTX 580 GPUs with 3 GB each, so they put half of the kernels on each GPU and let the kernels of layer 4 take input only from the kernel maps of layer 3 that lived on the same GPU. That is g = 2, chosen for memory rather than elegance. ResNeXt (Xie et al., 2017) turned the group count into a design axis they named cardinality and found that raising it was more effective than going deeper or wider at the same parameter budget.
That last clause is the cost. Inside a grouped layer, no information crosses a group boundary; the faint grey lines in the visual are exactly the missing connections. Networks that use grouped layers alternate them with something that mixes channels again, most often a 1 x 1 convolution. The next concept pushes g to its limit and meets that need head on.
Quick check
A layer has c_i = c_o = 128, k = 3, g = 8 and no bias. How many weights does it hold?
Recall
c_i = c_o = 64, k = 3. How many weights for g = 1, g = 4 and depthwise (g = 64)?
Push the group count as far as it goes: g = c_i = c_o. Every group is now one Channel in and one channel out, so each channel gets its own k x k filter and nothing else. For 64 channels and 3 x 3 kernels that is 64 x 9 = 576 weights, 64 times fewer than the 36,864 of the standard layer. The slide 20 diagram says it in one picture: eight straight arrows from input strip to output strip, and not one of them crosses.
This is a Depthwise convolution. The slide writes its Weight tensor as (c, k_h, k_w), one k_h x k_w filter per channel. Frameworks keep the four-dimensional layout: PyTorch stores (out_channels, in_channels/groups, kH, kW), which with groups = in_channels is (c, 1, k_h, k_w), the same numbers with a singleton axis. PyTorch also allows out_channels = K · in_channels with groups = in_channels, a depthwise layer with depth multiplier K; the slide's g = c_i = c_o is the case K = 1.
Is this reduction in the number of weights really good?
The slide asks the question in red and leaves it open. The honest answer has two halves. For storage and arithmetic, yes: 576 weights fit in 2.3 KB of fp32 flash, and the multiply-accumulates fall by the same factor of 64. For what the layer can learn, no. A depthwise layer filters each channel in isolation. Sandler et al. describe it as lightweight filtering that applies a single convolutional filter per input channel, and Howard et al. point out that it does not combine input channels, so it cannot build a feature that depends on two channels at once. A standard layer does that at every position; a depthwise layer never does.
The repair is a 1 x 1 convolution, called a pointwise convolution, placed right after it. With k = 1 it looks at one position and mixes all c_i channels into c_o outputs: W of shape (c_o, c_i, 1, 1), c_o · c_i weights, and in Sandler et al.'s words it is responsible for building new features through computing linear combinations of the input channels. The depthwise layer handles space, the pointwise layer handles channels, and the pair is called a depthwise separable convolution. It is the building block of MobileNet and, wrapped in an expand and project pair of 1 x 1 layers, of MobileNetV2.
| Layer | Weights | Biases | Weights + biases | Fewer weights than standard |
|---|---|---|---|---|
| Standard 3 x 3 (g = 1) | 36,864 | 64 | 36,928 | 1x |
| Grouped, g = 2 | 18,432 | 64 | 18,496 | 2x |
| Grouped, g = 4 | 9,216 | 64 | 9,280 | 4x |
| Depthwise, g = 64 | 576 | 64 | 640 | 64x |
| Depthwise + 1 x 1 pointwise | 576 + 4,096 = 4,672 | 128 | 4,800 | 7.9x |
The last row is the one to remember. Howard et al. derive the cost ratio of a separable block to a standard layer as 1/N + 1/D_K², where N is the output channel count and D_K the kernel size. With N = 64 and D_K = 3 that is 1/64 + 1/9 ≈ 0.127, so the block is about 7.9 times cheaper, matching the table. For wide layers the 1/N term fades and the saving approaches D_K² = 9; Howard et al. report 8 to 9 times less computation for 3 x 3 kernels at a small accuracy cost.
Multiply-accumulates on a 56 x 56 map (h_o · w_o = 3,136)
- Standard 3 x 3, 64 to 64
- 36,864 x 3,136 = 115,605,504 (about 115.6 M)
- Depthwise 3 x 3 plus 1 x 1 pointwise
- 4,672 x 3,136 = 14,651,392 (about 14.7 M)
- Ratio
- about 7.9x
What this means on an embedded device
- Fewer weights means less flash and less weight traffic from memory, which on a microcontroller is often the whole budget.
- Fewer multiply-accumulates means lower latency and lower energy per inference on the same core.
- The depthwise layer does only k² multiply-accumulates per output value, against c_i · k² for a standard layer, yet still reads a k x k window per output, so it moves a lot of activation data for little arithmetic and on many accelerators is bound by memory bandwidth rather than compute. Treat the parameter saving as an upper bound on the speedup, not a promise.
- Accuracy drops a little relative to a standard layer of the same shape, which is the price of removing the cross-channel terms.
Quick check
Why does MobileNet follow each depthwise convolution with a 1 x 1 pointwise convolution?
Recall
Why is a depthwise convolution alone a weak layer on an embedded device, and what fixes it?
Recall
c_i = c_o = 64, k = 3. How many weights does a depthwise 3 x 3 plus 1 x 1 pointwise block hold, and how does it compare with the standard layer?
Recap
If you remember nothing else
- h_o = floor((h_i + 2p - k_h)/s) + 1. With s = 1 and p = (k - 1)/2 the output keeps the input size.
- Zero, reflection, replication and constant padding change only the border cells. W is unchanged.
- One 3 x 3 layer sees 3 x 3. Each stride-1 layer adds k - 1, so L layers give L(k - 1) + 1.
- With stride, RF_L = RF_(L-1) + (k - 1) times the product of the earlier strides. Stride 2 in layer 1 gives 7 after two 3 x 3 layers.
- Stride and padding change activations and MACs, never the parameter count.
- Grouped convolution: W is (c_o, c_i/g, k_h, k_w), g times fewer weights, and c_i and c_o must both be divisible by g.
- Depthwise convolution is g = c_i = c_o: one k x k filter per channel and no mixing across channels.
- 64 to 64 channels, 3 x 3: 36,864 weights standard, 576 depthwise, 4,672 depthwise plus 1 x 1, about 7.9x fewer.
- The 1 x 1 pointwise convolution restores cross-channel mixing. MobileNets are built on this pair.
Sources
- torch.nn.Conv2dDocsPyTorch documentationOutput shape with floor, groups divisibility, weight shape (out, in/groups, kH, kW), padding modes, padding='same' only for stride 1, depthwise as groups = in_channels.(opens in a new tab)
- torch.nn.ReflectionPad2dDocsPyTorch documentationWorked 3 x 3 to 7 x 7 reflection example.(opens in a new tab)
- torch.nn.ReplicationPad2dDocsPyTorch documentationEdge replication example.(opens in a new tab)
- torch.nn.ConstantPad2dDocsPyTorch documentationConstant padding with a chosen value.(opens in a new tab)
- A guide to convolution arithmetic for deep learningPaperDumoulin and Visin, arXiv, 2016 (revised 2018)Relationship 6: o = floor((i + 2p - k)/s) + 1; half padding p = floor(k/2).(opens in a new tab)
- Computing Receptive Fields of Convolutional Neural NetworksPaperAraujo, Norris and Sim, Distill, 2019Closed-form receptive field with strides; AlexNet 195, VGG-16 212, ResNet-50 483, Inception-v3 1311.(opens in a new tab)
- Deep Learning, chapter 9: Convolutional NetworksBookGoodfellow, Bengio and Courville, MIT Press, 2016Figure 9.4 receptive field growth; section 9.5 on zero padding (valid, same, full) and stride as convolution plus downsampling.(opens in a new tab)
- Dive into Deep Learning, section 7.3: Padding and StrideBookZhang, Lipton, Li and SmolaPadding and stride formulas with floor; odd kernels for symmetric padding.(opens in a new tab)
- MobileNets: Efficient Convolutional Neural Networks for Mobile Vision ApplicationsPaperHoward et al., arXiv, 2017Depthwise separable cost ratio 1/N + 1/D_K^2; 8 to 9 times less computation for 3 x 3; depthwise does not combine channels.(opens in a new tab)
- MobileNetV2: Inverted Residuals and Linear BottlenecksPaperSandler et al., CVPR 2018Section 3.1: single filter per input channel; pointwise builds features as linear combinations of channels; expand, depthwise, project block.(opens in a new tab)
- ImageNet Classification with Deep Convolutional Neural NetworksPaperKrizhevsky, Sutskever and Hinton, NeurIPS 2012Section 3.2: half the kernels on each GPU, layer 4 reads only same-GPU maps, the origin of grouped convolution.(opens in a new tab)
- Aggregated Residual Transformations for Deep Neural Networks (ResNeXt)PaperXie et al., CVPR 2017Cardinality via grouped convolutions; more effective than going deeper or wider at equal cost.(opens in a new tab)
- MIT 6.5940 TinyML and Efficient Deep Learning Computing, lecture 2DocsSong Han, MIT HAN LabThe deck these slides follow.(opens in a new tab)
Part 04: Pooling and what CNN filters learn
Parameter-free downsampling with max and average pooling, the feature hierarchy a CNN learns, and two interactive demos.
4 concepts, slides 21-26
Why this part matters
Pooling is the cheapest layer you will ever deploy: zero parameters, four times fewer activations, and it is therefore the first tool for fitting a CNN into microcontroller SRAM. The feature hierarchy that follows it explains why pruning, quantization and transfer learning treat early and late layers differently.
The two skills this part drills are pure counting: the output shape of a pooling layer and the parameter count of a convolution layer. That same counting is the first step of every embedded model budget, because parameters set flash and activation shapes set SRAM. The two browser demos at the end give you the numbers layer by layer so you can check yourself.
By the end you can
- Compute the output shape of any pooling layer from W1, H1, C, F and S, and state why its parameter count is zero.
- Evaluate max and average pooling by hand on a small slice and explain the small translation invariance max pooling brings.
- Describe the edges, parts, objects feature hierarchy and tie it to receptive-field growth through CONV-RELU-POOL blocks.
- Count the parameters of a conv layer from its filter shape, including biases, as in the ConvNetJS first layer.
- Chain shapes through a real small network (CNN Explainer) and separate the layers that cost parameters from those that cost only activations.
A VGG-style block hands the next layer a volume of 224 x 224 x 64, which is 3,211,264 activations. A 2 x 2 pool with stride 2 returns 112 x 112 x 64, which is 802,816. Nothing was learned, nothing was mixed across channels, and every one of the 64 maps was simply shrunk on its own. That is the whole job of a Pooling layer.
The rule behind the example: a pooling layer applies a fixed summary over a small window of each activation map, one map at a time. Two summaries are common. Max pooling keeps the largest value in the window, and Average pooling keeps the mean. Because the summary is computed inside one Channel and never across channels, the depth of the volume is preserved while the spatial size falls. Pooling is therefore the second Downsampling tool of this lecture, next to Stride, and its output is a smaller Feature map per channel.
Slide 21 pinned as shapes
- Input
- 224 x 224 x 64
- Spatial extent F, stride S
- 2, 2
- Output
- 112 x 112 x 64
- Channels
- Unchanged, 64 in and 64 out
- Activations
- 3,211,264 to 802,816 (75% discarded)
- Learned parameters
- 0
Why max pooling is the default, and why that is a heuristic
Pooling of any kind brings an approximate translation invariance, and max pooling shows it most sharply, which is why slide 21 attaches "Introduces spatial invariance" to max pooling. Goodfellow, Bengio and Courville describe pooling in all its forms as making the representation "approximately invariant to small translations of the input": if the input shifts by a pixel, the values of most pooled outputs do not change, because the maximum is still inside its window. Their example is face detection, where the network need not know the location of the eyes with pixel-perfect accuracy, only that there is an eye on each side. They also point out that pooling regions spaced k pixels apart give the next layer roughly k times fewer inputs to process, which is the memory saving your microcontroller will feel.
| Max pooling | Average pooling | |
|---|---|---|
| What it returns | The largest value in the window | The mean of all values in the window |
| What it is sensitive to | One strong activation anywhere in the window | Every value equally, so a strong spike is diluted |
| Effect of a one-cell shift | Usually none, if the maximum stays inside the window | Small change, the average moves slightly |
| Gradient in the backward pass | Routed to the argmax cell only | Spread equally, 1/F² to every cell |
| Typical place in a network | Inside the trunk after a conv block | Global average pooling as the classifier head |
Slide 21 goes further and says max pooling "performs a lot better" because it discards noisy activations. Treat that as the slide's heuristic, not a law. Boureau, Ponce and LeCun analysed the two in 2010 and found that which one wins depends on how sparse the features are and how large the pool is; they note that earlier comparisons had been "purely empirical". CS231n states more cautiously that max pooling "has been shown to work better in practice". And the field has partly moved on: Springenberg et al. showed in 2014 that max pooling "can simply be replaced by a convolutional layer with increased stride without loss in accuracy", and Lin, Chen and Yan's Network in Network replaced the fully connected head with global average pooling, which they found "less prone to overfitting". ResNet and MobileNet families follow that pattern: strided convolutions inside, a single global average pool at the end.
Recall
A 224x224x64 volume passes through max pooling with F = 2 and S = 2. What are the output shape and the number of learned parameters?
Quick check
A 56x56x128 volume passes through max pooling with a 2x2 window and stride 2. What does it output?
Take the single depth slice from slide 22, a 4 x 4 grid, and pool it with a 2 x 2 window and Stride 2. The window lands in four places and never overlaps itself, so the four coloured quadrants of the slide are the four windows. Each quadrant becomes one output cell.
| Window | Values | Max | Mean |
|---|---|---|---|
| Top left (pink on the slide) | {1, 1, 5, 6} | 6 | 13 / 4 = 3.25 |
| Top right (green) | {2, 4, 7, 8} | 8 | 21 / 4 = 5.25 |
| Bottom left (yellow) | {3, 2, 1, 2} | 3 | 8 / 4 = 2 |
| Bottom right (blue) | {1, 0, 3, 4} | 4 | 8 / 4 = 2 |
Read the two results as grids. Max pooling gives [[6, 8], [3, 4]] and Average pooling gives [[3.25, 5.25], [2, 2]]. Notice what each one threw away. Max kept only the argmax cell of each window (the 6, the 8, the 3, the 4) and forgot the other three values entirely. Mean kept a trace of all four but flattened the 8 down to 5.25.
The general rule
Let the input be W1 x H1 x C. A Pooling layer needs exactly two hyperparameters, the spatial extent F of the window and the stride S, and it produces W2 x H2 x C.
The shape formula is the convolution formula from part 03 with p = 0 and the kernel size renamed to F. PyTorch writes the same rule in its general form, H_out = floor((H_in + 2p - d(F - 1) - 1)/S + 1), with the stride defaulting to the kernel size, so MaxPool2d(2) means F = 2, S = 2 (PyTorch docs). The floor matters when the division is not exact, and the second worked example shows why.
The zero comes from what pooling does in the backward pass. CS231n notes that a pooling layer "introduces zero parameters since it computes a fixed function of the input". Implementations keep the index of the maximum, the "switch", so the gradient is routed to exactly one cell per window in max pooling; in average pooling it is spread as 1/F² to each cell. Either way the layer has no Weight and no Bias, so it adds nothing to the Parameter count.
Worked example
224 to 112, the slide 21 numbers
Substitute into the width formula
W2 = (224 - 2)/2 + 1 = 111 + 1 = 112. The height is identical.Carry the depth across
C2 = C1 = 64, because the window only ever looks inside one channel.Count the activations
Before: 224 x 224 x 64 = 3,211,264. After: 112 x 112 x 64 = 802,816. Exactly one quarter survives; CS231n phrases it as "discards exactly 75% of the activations in an input volume".Result
Output 112 x 112 x 64, 0 parameters, 75% of runtime memory for that tensor gone.
Worked example
Why F = 3, S = 2 needs care
Overlapping pool on 224
(224 - 3)/2 + 1 = 111.5. Not an integer: the last window would hang off the edge. PyTorch floors to 111 by default and rounds up to 112 only with ceil_mode=True.The AlexNet case that does divide
AlexNet pools 55 x 55 maps with F = 3, S = 2: (55 - 3)/2 + 1 = 27, a clean integer, which is why that configuration is famous.Rule of thumb
Check that (W1 - F) divides by S. If it does not, state the floor explicitly.
Changing F and S on the same 4 x 4 slice shows how strongly the two hyperparameters shape the result. The values below are max pooling; the simulator lets you check the means.
| F, S | W2 | Output | Values | What happened |
|---|---|---|---|---|
| F = 2, S = 2 | (4 - 2)/2 + 1 = 2 | 2 x 2 | [[6, 8], [3, 4]] | Windows tile the input, 75% of activations dropped |
| F = 2, S = 1 | (4 - 2)/1 + 1 = 3 | 3 x 3 | [[6, 7, 8], [6, 7, 8], [3, 3, 4]] | Windows overlap, the row 6, 7, 8 appears twice |
| F = 3, S = 1 | (4 - 3)/1 + 1 = 2 | 2 x 2 | [[7, 8], [7, 8]] | Nine cells per window, the small values vanish |
| F = 3, S = 2 | (4 - 3)/2 + 1 = 1.5 | 1 x 1 (floored) | [[7]] | Not an integer, PyTorch floors and drops the last row and column |
- This window
- hover an output cell
- Input W1 × H1 × C
- 4 × 4 × 1
- Output W2 × H2 × C
- 2 × 2 × 1
- Learned parameters
- 0
- Activations in, out
- 16 to 4 (75% discarded)
Recall
Compute max and mean pooling with F = 2, S = 2 on [[1,1,2,4],[5,6,7,8],[3,2,1,0],[1,2,3,4]].
Quick check
Why does a pooling layer add nothing to a network's parameter count?
The grid on slide 23 comes from Lee, Grosse, Ranganath and Ng (ICML 2009), who learned a first layer of edge detectors from natural images once, then trained the second and third layers of a convolutional deep belief network separately on unlabeled Caltech-101 images of faces, cars, elephants and chairs, and visualized what each layer responds to. Their own summary: the first, second and third layers "learn edge detectors, object parts, and objects respectively". Look at the bottom row of the slide. It is the same across all four columns because that first layer was learned once from natural images: small oriented edges like Gabor patches. The middle row diverges into eyes and noses, wheels and bumpers, tusks and ears, chair legs and backs. The top row shows entire faces, cars, elephants and chairs.
This ladder is the Feature hierarchy, and it is not designed by hand. Every Convolution layer runs the same operation with a small Kernel (filter). What changes with depth is the Receptive field: a first-layer unit sees only its k x k patch of pixels, so the most it can detect is an edge. A second-layer unit sees a k x k patch of first-layer responses, which covers a larger region of the image (L(k - 1) + 1 pixels after L stride-1 layers, faster with stride or pooling), so it can combine edges into a curve or an eye. Deep units cover most of the image and can match a whole face. Zeiler and Fergus confirmed in 2013 that the same ladder appears inside a supervised ImageNet CNN, using a deconvolutional visualization of intermediate layers.
| Tier | Layers | What the filters respond to | Receptive field, roughly |
|---|---|---|---|
| Low level | First convolution layers | Oriented edges, bars, blobs, colour opponents | 3 x 3 to 11 x 11 pixels (the first kernel size) |
| Mid level | Middle layers | Curves, corners, eyes, wheels, tusks, chair legs | tens of pixels |
| High level | Deepest layers | Whole faces, cars, elephants, chairs | most of the image |
The mechanism in action: slide 24
Slide 24 shows a network of the pattern [CONV, RELU, CONV, RELU, POOL] repeated three times and then a fully connected layer, run on a photo of a car. Each column is the activation maps of one layer. Read them left to right and the hierarchy appears live: the first columns look like edge-filtered copies of the car, the middle columns are sparse and blotchy, and the last columns are tiny grids that no longer resemble the photo at all.
Two convs with ReLU, then pool: edges and colour blobs.
Same pattern on the smaller maps: corners, wheels, parts.
Coarse maps whose units see most of the image.
car, truck, airplane, ship, horse; car wins.
The ReLU after each conv keeps only positive responses, which is why the columns after it are darker. Each POOL halves the Feature map and, because the next block's k x k window now covers twice as many original pixels, pushes the receptive field outward. Two pools have already halved the maps twice, so the third block works on maps one quarter the width of the input, and the third pool leaves the fully connected layer maps one eighth the width; that is why the last units can see a whole car. The Fully connected layer at the end reads every unit of the final maps at once and produces one score per class. On the slide, car scores highest, well above truck, then airplane, ship and horse.
Why this matters on an embedded device
The hierarchy explains a pattern you will meet throughout this course. Early layers have few parameters (a 5 x 5 x 3 filter is 75 weights) but large activation maps; late layers have many parameters but tiny maps. So SRAM pressure comes from the front of the network and flash pressure from the back, and pruning or quantizing them calls for different budgets. Transfer learning on the edge uses the hierarchy directly: the edge and part detectors are generic, so you freeze the low tiers and retrain only the top.
Recall
Name the three tiers of the feature hierarchy with one example each, and say what property of deep layers makes the top tier possible.
Quick check
In the feature hierarchy, what do the deepest convolution layers respond to?
Open the ConvNetJS CIFAR-10 demo and read its first conv block. It prints "conv (32x32x16), filter size 5x5x3, stride 1, parameters: 16x5x5x3+16 = 1216". That one line is a complete exam answer, and it is worth rebuilding by hand.
Worked example
The ConvNetJS first conv layer
Weights
16 filters, each 5 x 5 and 3 deep to match the RGB input: 16 x 5 x 5 x 3 = 1200 weights.Biases
One Bias per filter, so 16.Why the map stays 32 x 32
The demo's layer definition uses pad 2: (32 + 2·2 - 5)/1 + 1 = 32. With Padding equal to (F - 1)/2, a stride-1 conv keeps its size.Result
1200 + 16 = 1216 parameters, output 32 x 32 x 16, which is 16,384 activations for one image.
The ConvNetJS CIFAR-10 demo
- Dataset
- CIFAR-10: 60,000 colour images of 32 x 32, 10 classes, 50,000 train and 10,000 test (Krizhevsky)
- Classes
- airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck
- Accuracy quoted on the page
- state of the art "about 90%", humans "about 94%" (2014 figures; current models exceed 99%)
- Augmentation
- random flips and random shifts of up to 2 px
- Optimizer
- Adadelta, batch size 4, L2 decay 0.0001
The whole demo network is small enough to count in full. Two things to watch: every conv filter is as deep as the channels feeding it, and the three pooling layers contribute nothing to the total while cutting the activations by four each time.
| Layer | Output | Parameters | Note |
|---|---|---|---|
| input | 32 x 32 x 3 | 0 | Raw CIFAR-10 image |
| conv, 16 filters 5x5x3, pad 2 | 32 x 32 x 16 | 16 x 5 x 5 x 3 + 16 = 1216 | The count printed on slide 25 |
| max pool 2x2, stride 2 | 16 x 16 x 16 | 0 | Activations cut by 4x |
| conv, 20 filters 5x5x16, pad 2 | 16 x 16 x 20 | 20 x 5 x 5 x 16 + 20 = 8020 | Depth of the filter equals the input channels |
| max pool 2x2, stride 2 | 8 x 8 x 20 | 0 | |
| conv, 20 filters 5x5x20, pad 2 | 8 x 8 x 20 | 20 x 5 x 5 x 20 + 20 = 10020 | |
| max pool 2x2, stride 2 | 4 x 4 x 20 | 0 | |
| softmax, 10 classes | 10 | 320 x 10 + 10 = 3210 | The only layer whose count depends on image size |
| Total | 22,466 | Three pools contribute nothing |
The total is 22,466 parameters. The softmax layer is the only one whose count depends on the image size, because it reads the flattened 4 x 4 x 20 = 320 activations; every conv layer's count depends only on its filter shape, which is Weight sharing at work.
CNN Explainer as retrieval practice
CNN Explainer (Wang et al., IEEE VIS 2020) runs a network the authors call Tiny VGG on ten classes: lifeboat, ladybug, pizza, bell pepper, school bus, koala, espresso, red panda, orange and sport car. Its training code uses 3 x 3 convolutions with 10 filters and no padding ("valid"), and 2 x 2 max pools with stride 2. The input is 64 x 64 x 3. Before you look at the table, chain the shapes yourself: a valid conv subtracts 2, a pool halves.
Recall
Chain the CNN Explainer shapes from 64x64x3 to the second pool.
| Layer | Shape formula | Output | Parameters |
|---|---|---|---|
| input | 64 x 64 x 3 | 0 | |
| conv_1_1, 10 filters 3x3, valid | 64 - 3 + 1 | 62 x 62 x 10 | 10 x 3 x 3 x 3 + 10 = 280 |
| conv_1_2, 10 filters 3x3, valid | 62 - 3 + 1 | 60 x 60 x 10 | 10 x 3 x 3 x 10 + 10 = 910 |
| max_pool_1, 2x2, stride 2 | (60 - 2)/2 + 1 | 30 x 30 x 10 | 0 |
| conv_2_1, 10 filters 3x3, valid | 30 - 3 + 1 | 28 x 28 x 10 | 910 |
| conv_2_2, 10 filters 3x3, valid | 28 - 3 + 1 | 26 x 26 x 10 | 910 |
| max_pool_2, 2x2, stride 2 | (26 - 2)/2 + 1 | 13 x 13 x 10 | 0 |
| flatten, dense 10, softmax | 13 x 13 x 10 = 1690 | 10 | 1690 x 10 + 10 = 16,910 |
| Total | 19,920 |
The pattern repeats: 19,920 parameters, of which the two pools contribute 0 while each removes three quarters of the activations. The dense layer holds 16,910 of the total because it multiplies the image-dependent 1690 by the ten classes. Click any activation map in the live page and it animates the window that produced it, which is the pooling and convolution arithmetic of this lecture rendered one cell at a time.
Recall
How many parameters does the ConvNetJS first conv layer hold, and why does the map stay 32x32?
Quick check
In the ConvNetJS CIFAR-10 demo the first conv layer has 16 filters of 5x5x3. How many parameters does it hold?
Quick check
In CNN Explainer, conv_1_2 outputs 60x60x10. What does max_pool_1 with a 2x2 window and stride 2 output?
Recap
If you remember nothing else
- Pooling summarizes a window in each activation map independently: spatial size drops, channel count stays, 224x224x64 becomes 112x112x64.
- W2 = (W1 - F)/S + 1, H2 = (H1 - F)/S + 1, depth C unchanged, parameters 0.
- On the slide's 4x4 slice, F = 2 and S = 2 give max [[6,8],[3,4]] and mean [[3.25,5.25],[2,2]].
- Max pooling adds approximate invariance to small shifts; 'max is always better' is a heuristic, and many modern networks use strided convolutions or global average pooling instead.
- Filters form a hierarchy: edges in early layers, object parts in the middle, whole objects deep, because receptive fields grow with depth and pooling.
- CONV-RELU-CONV-RELU-POOL repeated three times then FC turns an image into class scores; car beats truck, airplane, ship, horse.
- ConvNetJS first layer: 16 x 5 x 5 x 3 + 16 = 1216 parameters; CNN Explainer shapes: 64, 62, 60, 30, 28, 26, 13.
Sources
- CS231n Convolutional Neural Networks for Visual Recognition, Pooling LayerDocsStanford UniversityPooling formulas, zero parameters, 75% discard, the argmax switch, and getting rid of pooling.(opens in a new tab)
- Deep Learning, chapter 9.3 PoolingBookGoodfellow, Bengio and Courville, MIT PressApproximate translation invariance and k times fewer inputs for the next layer, pp. 335 to 337.(opens in a new tab)
- Convolutional Deep Belief Networks for Scalable Unsupervised Learning of Hierarchical RepresentationsPaperLee, Grosse, Ranganath and Ng, ICML 2009Source of the faces, cars, elephants, chairs figure on slide 23: edges, object parts, objects.(opens in a new tab)
- Visualizing and Understanding Convolutional NetworksPaperZeiler and Fergus, 2013Deconvolutional visualization showing the same hierarchy in a supervised ImageNet CNN.(opens in a new tab)
- Striving for Simplicity: The All Convolutional NetPaperSpringenberg, Dosovitskiy, Brox and Riedmiller, 2014Max pooling replaced by strided convolution with no loss in accuracy.(opens in a new tab)
- Network In NetworkPaperLin, Chen and Yan, 2013Global average pooling as a classifier head, less prone to overfitting than fully connected layers.(opens in a new tab)
- A Theoretical Analysis of Feature Pooling in Visual RecognitionPaperBoureau, Ponce and LeCun, ICML 2010When max beats average depends on feature sparsity and pool size.(opens in a new tab)
- torch.nn.MaxPool2dDocsPyTorch documentationFloor formula, stride defaulting to kernel size, ceil_mode, and C unchanged from input to output.(opens in a new tab)
- ConvNetJS CIFAR-10 demoDocsAndrej Karpathy, StanfordAbout 90% state of the art, 94% human, 2 px shifts, Adadelta; layer parameters printed live.(opens in a new tab)
- ConvNetJS CIFAR-10 demo sourceDocsGitHub, karpathy/convnetjslayer_defs with pad 2 and 16, 20, 20 filters; trainer with batch size 4 and L2 decay 0.0001.(opens in a new tab)
- The CIFAR-10 datasetDocsAlex Krizhevsky, University of Toronto60,000 colour 32x32 images in 10 classes, 50,000 train and 10,000 test.(opens in a new tab)
- CNN ExplainerDocsPolo Club of Data Science, Georgia TechInteractive Tiny VGG with layer shapes 64, 62, 60, 30, 28, 26, 13.(opens in a new tab)
- Tiny VGG training scriptDocsGitHub, poloclub/cnn-explainer3x3 valid convolutions with 10 filters, 2x2 max pools, dense 10 with softmax.(opens in a new tab)
- CNN Explainer: Learning Convolutional Neural Networks with Interactive VisualizationPaperWang et al., IEEE TVCG (IEEE VIS 2020)The paper behind the demo on slide 26.(opens in a new tab)
- MIT 6.5940 TinyML and Efficient Deep Learning Computing, Lecture 2DocsSong Han, MITThe course whose Basics of Deep Learning lecture these slides follow.(opens in a new tab)
Part 05: Batch, layer, instance and group normalization
Why nicely scaled activations make optimization easier, how batch normalization computes, scales and shifts, how it changes at inference, and the axes each normalization variant averages over.
5 concepts, slides 27-38
Why this part matters
Every network you will compress, quantize or deploy in this course carries normalization layers. Batch normalization is a large part of why deep CNNs train at all, and it is also the first optimization you get for free on a microcontroller: at inference it folds into the layer before it and costs nothing.
This part builds the normalization family from one question: which numbers are averaged together to make one mean? Answer it for the batch axis and you have batch norm; answer it for the feature axis and you have layer norm, the block inside every transformer in part 06. Along the way you meet the exam staples of this lecture: tensor shapes, the train versus test difference, fusing BN into a linear layer, and computing BN on a tiny batch by hand.
By the end you can
- Explain why uncentered or unevenly scaled inputs make y = Wx hard to optimize.
- Write the batch norm transform with the shape of every tensor and say which parts are learned.
- State why BN differs between training and inference and derive the fused W' and b'.
- Name the axes each of BN, LN, IN and GN averages over and read off the resulting mu shape.
- Compute BN on a small mini-batch by hand and self-check with mean = beta and variance = gamma squared.
Feed a linear layer y = Wx two raw features: a pixel intensity somewhere in 0 to 255 and a binary flag in 0 to 1. For both features to have a similar influence on y, the two corresponding entries of W must differ by about two orders of magnitude. And because neither feature is centered at zero, the layer needs a large Bias just to bring its outputs near zero, where the next activation function does useful work.
Those are the two troubles every layer faces. Inputs that are not centered force a large bias, and inputs with different scales per element force the entries of W to vary a lot. Both make gradient descent slow, because one learning rate has to serve directions of very different steepness, so the update zig-zags along the steep direction while creeping along the shallow one. Goodfellow, Bengio and Courville motivate the same problem with a deep chain of layers: every update to an early layer changes the statistics that every later layer sees, so the layers keep re-adapting to each other.
The idea, due to Ioffe and Szegedy in 2015, is to stop hoping the inputs are nice and to force them to be. For each dimension k, subtract the mean and divide by the standard deviation, so that the dimension has zero mean and unit variance:
The part that makes this a layer rather than a preprocessing step is that the map is differentiable. It sits inside the network, at any hidden layer, and gradients flow through it exactly as through a convolution. The CS231n notes call it differentiable preprocessing built into the network itself. On slide 27 this is why Normalization appears as the fifth component of a CNN, beside convolution, pooling, fully connected layers and activations, and slide 39 later returns to the same map with the activation function circled instead.
Recall
Give the two properties of an input x that make y = Wx hard to optimize, and what each forces the layer to do.
Start with one column of numbers. Take the mini-batch from slide 38 after its ReLU, and look only at feature 1 across the four samples: (3, 1, 3, 2). Standardizing that column is the entire batch norm computation in miniature.
Worked example
One feature, four samples
Mean over the batch
mu = (3 + 1 + 3 + 2) / 4 = 2.25Variance over the batch
sigma squared = ((0.75) squared + (-1.25) squared + (0.75) squared + (-0.25) squared) / 4 = 0.6875Normalize each value
Divide each deviation by sqrt(0.6875 + eps), about 0.829: x_hat = (0.905, -1.508, 0.905, -0.302)Result
One mean and one variance for the feature, computed across the samples. Every feature gets its own pair.
Now the general rule. The input to a batch norm layer after a fully connected layer is a matrix x of shape N x D: N samples in the mini-batch (the Batch size) by D features. Every average runs down the N axis, and that is the whole story of the axes: one statistic per feature column, averaged over the sample rows.
Shape of every tensor in batch norm for a fully connected layer
- x
- N x D
- mu_j
- D
- sigma_j squared
- D
- x_hat
- N x D
- gamma, beta
- D
- y
- N x D
The epsilon under the root is a tiny constant, 1e-5 by default in PyTorch, that prevents division by zero when a feature happens to be constant across the batch. It changes nothing numerically otherwise, but it appears in every formula and in every fusion derivation, so keep it.
Why gamma and beta exist
Is forcing zero mean and unit variance always good? It is a hard constraint, and a network is not always best served by activations pinned to a standard distribution. So the layer gets two learnable vectors of shape D, gamma and beta, and outputs y = gamma x_hat + beta. Ioffe and Szegedy point out that setting gamma = sqrt(Var[x]) and beta = E[x] recovers the original activations, if that were the optimal thing to do. The network can therefore undo normalization entirely, which means normalization can never make the model less expressive; it can only change what is easy to learn.
Goodfellow, Bengio and Courville explain why the new parametrization learns more easily even though it can represent the same functions. In the original network the mean of a hidden unit is set by a long chain of interacting weights in all the layers below. After BN the mean is set by beta alone and the scale by gamma alone, two parameters with a direct, local effect on the quantity gradient descent needs to move.
The tanh plot on slide 31 makes the point concrete. The derivative 1 - tanh squared (x) peaks at 1 at the origin and falls below 0.1 once |x| exceeds about 1.8. If normalized values feed a tanh, then gamma decides how far they reach into the flat, saturated tails and beta decides where the center of the band sits. A small gamma keeps the layer almost linear; a large one lets it saturate on purpose. The model chooses the amount of saturation instead of inheriting it from whatever scale the previous layer produced.
Recall
For x of shape N x D, write the four BN equations and the shape of each of mu, sigma squared, x_hat, gamma, beta and y.
Quick check
For a fully connected layer with input of shape N x D, over which axis does batch normalization average to get one mean?
A keyword-spotting model on a microcontroller hears one audio clip at a time. A batch of one has zero variance, so the statistics of the "batch" are meaningless, and even a batch of ten would make the answer for one clip depend on which nine clips happened to arrive with it. Ioffe and Szegedy state the requirement plainly: at inference the output should depend only on the input, deterministically.
So the layer changes its source of statistics. During training it keeps a running average of the mini-batch means and variances it has seen. At inference it uses those stored values and ignores the batch entirely. PyTorch updates the buffers as running = (1 - momentum) running + momentum x_batch with momentum 0.1, normalizes with the biased variance during training, and stores the unbiased variance (divide by N - 1) in running_var, following the paper's m/(m - 1) correction. Goodfellow, Bengio and Courville note the payoff: with running averages the model can be evaluated on a single example.
Frozen statistics turn BN into an affine map
Once mu and sigma squared are constants, batch norm on feature j is nothing more than multiply by one number and add another. And if the layer before it is a linear layer z = Wx + b, two affine maps in a row are one affine map. Write it out:
Read the first formula per output row j: the whole row of W is multiplied by the scalar gamma_j / sqrt(sigma_j squared + eps), and the bias becomes that scalar times (b_j - mu_j), plus beta_j. The same holds for a convolution: each output filter is scaled by its channel's factor. Ioffe and Szegedy already say this in the paper: the normalization is a linear transform that may be composed with the scaling by gamma and shift by beta into a single linear transform. PyTorch ships it as torch.nn.utils.fusion.fuse_conv_bn_eval, which requires both modules in eval mode with their running buffers computed. This is Batch norm fusion.
Worked example
Fusing BN into the slide 38 linear layer
Take the linear layer and frozen statistics
W = [[1, 0, 1], [1, 1, 0], [0, 2, -1]], b = (0, -1, 0), gamma = (2, 3, -1), beta = (0, 0, 1). For frozen statistics use the batch statistics of z = Wx + b itself (BN sits directly on the linear output, no ReLU in between): mu = (2.25, 1.25, 1.25), sigma squared = (0.6875, 1.6875, 6.6875).Compute one scale factor per output row
s_j = gamma_j / sqrt(sigma_j squared + eps), so s = (2.412, 2.309, -0.387).Scale each row of W and rebuild the bias
Row j gamma_j / sqrt(var_j + eps) Row j of W' b'_j 1 2 / 0.829 = 2.412 (2.412, 0, 2.412) 2.412 (0 - 2.25) + 0 = -5.427 2 3 / 1.299 = 2.309 (2.309, 2.309, 0) 2.309 (-1 - 1.25) + 0 = -5.196 3 -1 / 2.586 = -0.387 (0, -0.773, 0.387) -0.387 (0 - 1.25) + 1 = 1.483 One linear layer replaces two
W'x + b' agrees with BN(Wx + b) to floating-point precision on all four slide 38 samples, which you will meet in full in the last concept. The deployed model holds only W' and b'; gamma, beta, the running mean and the running variance vanish from the binary.
For embedded deployment this is the reason the slide says zero overhead at test time. A fused network runs no extra multiply-accumulate operations, moves no extra activations through memory, launches one fewer kernel per layer, and stores four fewer vectors per layer in flash. In later lectures on pruning, the very gamma factors that get fused away are also what network slimming reads to decide which channels matter.
What BN buys during training, and the one way it bites
- Much easier training and higher learning rates: normalizing each layer's input stops small parameter changes from amplifying into large, suboptimal changes in activations and gradients as they pass through many layers (Ioffe and Szegedy, section 3.3). On ImageNet the paper reaches the same accuracy as its baseline in 14 times fewer training steps.
- Better gradient flow: the tanh argument from the previous concept applies at every layer. Activations are kept out of the saturated tails unless the model wants them there, so derivatives stay usable.
- Robustness to initialization: scaling the weights of a layer by a constant scales its output by the same constant, and normalization divides that constant back out. The scale of the initial weights largely stops mattering.
- Regularization during training, the "How?" on slide 33: each sample is normalized with statistics that depend on the other random samples in its mini-batch, so the network no longer produces deterministic values for a given training example (section 3.4). That injected noise acts like a mild regularizer, and the paper reports that dropout can be removed or reduced in strength. Dive into Deep Learning notes that batch sizes around 50 to 100 inject roughly the right amount of noise.
The red line on slide 33 is the hazard that follows from everything above. The layer computes something different in training mode (batch statistics) and evaluation mode (running statistics). Forget to call model.eval() and your deployed model's answer for one input depends on the other inputs in the batch and degrades silently; for a batch of one, x_hat is 0 by construction so every feature collapses to beta (PyTorch's BatchNorm1d refuses to run at all in that case). It is one of the most common bugs in deep learning code precisely because, for any larger batch, nothing crashes.
| Variance | Feature 1 | Feature 2 | Feature 3 |
|---|---|---|---|
| Biased (training) | 0.6875 | 1.6875 | 4.1875 |
| Unbiased (running_var) | 0.917 | 2.25 | 5.583 |
Recall
Why does BN behave differently at train and test time, and what does PyTorch store to make test time work?
Recall
Derive W' and b' when BN with frozen statistics directly follows y = Wx + b.
Quick check
During inference, which mean does a batch normalization layer subtract from its input?
Take one activation tensor from a convolution layer, N x C x H x W = 8 x 64 x 32 x 32. It holds about half a million numbers. Every normalization in this part does the same arithmetic, subtract a mean and divide by a standard deviation, then scale and shift. The only thing that distinguishes them is the answer to one question: which of those numbers are averaged together to make one mean?
| Normalization | Values per mean | Number of means |
|---|---|---|
| Batch norm for convolutions | 8 x 32 x 32 = 8192 | 64 |
| Layer norm | 64 x 32 x 32 = 65536 | 8 |
| Instance norm, per Feature map per sample | 32 x 32 = 1024 | 512 |
| Group norm | 2 x 1024 = 2048 | 256 |
Wu and He write all four as one formula. Each value x_i is normalized with a mean and standard deviation computed over a set S_i, and different normalizations simply use different definitions of that set:
| Normalization | Averages over | mu, sigma shape | gamma, beta shape | Same at train and test? |
|---|---|---|---|---|
| BN, fully connected | N | 1 x D | 1 x D | No |
| BN, convolution (BatchNorm2d) | N, H, W | 1 x C x 1 x 1 | 1 x C x 1 x 1 | No |
| Layer norm | D (or C, H, W) | N x 1 x 1 x 1 (N x 1 on slide 35) | 1 x D on slide 35; 1 x C x 1 x 1 in Wu and He | Yes |
| Instance norm | H, W | N x C x 1 x 1 | 1 x C x 1 x 1 | Yes |
| Group norm | H, W and C/G channels | N x G x 1 x 1 | 1 x C x 1 x 1 | Yes |
Two patterns in that table carry most of the marks. First, the shape of mu is the tensor shape with a 1 wherever you averaged and the full size everywhere else. Batch norm averages over N, H, W and leaves 1 x C x 1 x 1; instance norm averages over H, W only and leaves N x C x 1 x 1. Second, gamma and beta do not follow the mu shape: they are per channel for BN, IN and GN. Ioffe and Szegedy learn a pair per feature map rather than per activation, and Wu and He write that BN, LN, IN and GN all learn a per-channel linear transform, the convention the table and the explorer below use. A BatchNorm2d over 64 channels therefore holds 128 learnable values, whatever the image size.
C/G = 1 (G = C) is instance norm and C/G = C (G = 1) is layer norm
Only the three visible faces are drawn. Lit cells share one mean with the anchor cell (teal column). Sample index runs into the depth, channel runs along the front edge.
Why the batch-free variants exist
Layer norm was introduced by Ba, Kiros and Hinton for exactly the cases where a batch is awkward. Its statistics come from all of the summed inputs to the neurons in a layer on a single training case, so it performs exactly the same computation at training and test time and is straightforward to apply to recurrent networks by computing the statistics separately at each time step. Slide 35 lists both facts. Because it never looks at another sample, it also works at batch size one, and the Transformer block in part 06 uses it in every Add and Norm step: Vaswani and colleagues define each sub-layer output as LayerNorm(x + Sublayer(x)).
Instance norm came from style transfer. Ulyanov, Vedaldi and Lempitsky found that replacing batch norm with instance norm, applied both at training and testing, sharply improved stylization, because the per-image contrast statistics are exactly what a style transfer network needs to discard. PyTorch's InstanceNorm2d defaults to no affine parameters and no running statistics.
Group norm is the embedded and detection story. Wu and He show that batch norm's error increases rapidly when the batch size becomes smaller: a ResNet-50 on ImageNet at batch size 2 reaches 34.7 percent error with BN and 24.1 percent with GN, while GN's computation is independent of batch size. With G = 32 as the default, GN becomes LN when G = 1 and IN when G = C, which is what the explorer's two slider ends show: one channel per group is instance norm, all channels in one group is layer norm. When you fine-tune a model on a device with a memory budget that allows one or two samples per step, a batch-free normalization is the difference between a model that trains and one that does not. Wu and He note that dropping the batch size constraint can free considerably more memory, sixteen times or more.
Recall
A conv output is 8 x 64 x 32 x 32. Give the mu shape for BN, LN, IN and GN with 32 groups, and how many values each mean averages.
Quick check
A BatchNorm2d layer follows a convolution with 64 output channels of size 32 x 32. How many learnable parameters does it hold?
Quick check
Which normalization gives identical outputs at training and test time and never depends on other samples in the batch?
Tom Yeh's batch norm worksheet runs a mini-batch of four samples with three features each passes through a linear layer, a ReLU, then batch norm. Filling in every cell is the single best preparation for the computational question on this topic, so here is every number, recomputed rather than copied from the heavily rounded original.
The layout hides two conventions. The mini-batch is written as columns x1 = (1, 0, 2), x2 = (0, 3, 1), x3 = (3, 1, 0), x4 = (0, 1, 2) with a row of ones appended, so the red 3 x 4 "Linear Layer" block is [W | b] with W = [[1, 0, 1], [1, 1, 0], [0, 2, -1]] and b = (0, -1, 0). Likewise the "Scale and Shift" block is [diag(gamma) | beta] with gamma = (2, 3, -1) and beta = (0, 0, 1), applied to the normalized matrix with its own row of ones. The small two-column "Trainable Parameters" box (drawn as a 2 x 2 grid in the blank template) stands for the columns [gamma | beta]; for this three-feature layer it holds a 3 x 2 matrix.
Worked example
Batch norm on the slide 38 mini-batch
Linear layer z = Wx + b
Row 1 of W is (1, 0, 1) with bias 0, so for x1 it gives 1 + 2 = 3. Doing this for every row and sample: row 1 is (3, 1, 3, 2), row 2 is (0, 2, 3, 0), row 3 is (-2, 5, 2, 0).ReLU
Only one value is negative. The -2 in row 3 becomes 0, so the batch norm input is (3, 1, 3, 2), (0, 2, 3, 0), (0, 5, 2, 0).Batch statistics, one per feature row
Quantity Feature 1 Feature 2 Feature 3 After ReLU 3, 1, 3, 2 0, 2, 3, 0 0, 5, 2, 0 Sum 9 5 7 Mean (divide by N = 4) 2.25 1.25 1.75 Variance (biased, divide by N) 0.6875 1.6875 4.1875 Standard deviation about 0.829 about 1.299 about 2.046 Normalize
Subtract the row mean, divide by the row standard deviation (with eps = 1e-5 under the root). For feature 1: (3 - 2.25) / 0.829 = 0.905, (1 - 2.25) / 0.829 = -1.508, and so on.Scale and shift with gamma = (2, 3, -1), beta = (0, 0, 1)
Row Feature 1 Feature 2 Feature 3 x_hat 0.905, -1.508, 0.905, -0.302 -0.962, 0.577, 1.347, -0.962 -0.855, 1.588, 0.122, -0.855 gamma, beta 2, 0 3, 0 -1, 1 y = gamma x_hat + beta 1.81, -3.02, 1.81, -0.60 -2.89, 1.73, 4.04, -2.89 1.86, -0.59, 0.88, 1.86 Mean of y (should be beta) 0 0 1 Variance of y (should be gamma squared) 4 9 1 Self-check in ten seconds
Each output row has mean beta_j and variance gamma_j squared: means 0, 0, 1 and variances 4, 9, 1. If your rows do not satisfy this, the arithmetic slipped somewhere. Output values are rounded to two decimals, so treat them as approximate.
| x1 | x2 | x3 | x4 | |
|---|---|---|---|---|
| f1 | 1 | 0 | 3 | 0 |
| f2 | 0 | 3 | 1 | 1 |
| f3 | 2 | 1 | 0 | 2 |
| w1 | w2 | w3 | b | |
|---|---|---|---|---|
| f1 | 1 | 0 | 1 | 0 |
| f2 | 1 | 1 | 0 | -1 |
| f3 | 0 | 2 | -1 | 0 |
| x1 | x2 | x3 | x4 | |
|---|---|---|---|---|
| f1 | 3 | 1 | 3 | 2 |
| f2 | 0 | 2 | 3 | 0 |
| f3 | -2 | 5 | 2 | 0 |
| x1 | x2 | x3 | x4 | |
|---|---|---|---|---|
| f1 | 3 | 1 | 3 | 2 |
| f2 | 0 | 2 | 3 | 0 |
| f3 | 0 | 5 | 2 | 0 |
| f1 | f2 | f3 | |
|---|---|---|---|
| sum | 9 | 5 | 7 |
| mean | 2.2500 | 1.2500 | 1.7500 |
| var | 0.6875 | 1.6875 | 4.1875 |
| std | 0.829 | 1.299 | 2.046 |
| x1 | x2 | x3 | x4 | |
|---|---|---|---|---|
| f1 | 0.905 | -1.508 | 0.905 | -0.302 |
| f2 | -0.962 | 0.577 | 1.347 | -0.962 |
| f3 | -0.855 | 1.588 | 0.122 | -0.855 |
| x1 | x2 | x3 | x4 | |
|---|---|---|---|---|
| f1 | 1.809 | -3.015 | 1.809 | -0.603 |
| f2 | -2.887 | 1.732 | 4.041 | -2.887 |
| f3 | 1.855 | -0.588 | 0.878 | 1.855 |
| x1 | x2 | x3 | x4 | |
|---|---|---|---|---|
| train | 1.809 | -3.015 | 1.809 | -0.603 |
| eval | 4.000 | 0.000 | 4.000 | 2.000 |
Sample x1 gives 1.81 with batch statistics and 4.00 with the running ones. A model left in train mode at deployment produces the first number and its value depends on which other samples share the batch.
Switch the simulator to eval mode and the same four inputs produce different outputs with the simulator's placeholder running mean and variance of one (PyTorch itself starts running_mean at 0 and running_var at 1): sample 1, feature 1 gives 4.00 instead of 1.81. That gap is the slide 33 bug made visible. To reproduce the fusion table of the previous concept, switch the placement to the paper order, press "load this batch's statistics" so the running mean becomes 2.25, 1.25, 1.25 and the running variance 0.6875, 1.6875, 6.6875, then press fuse: W' and b' appear computed live, with the check that they reproduce BN(Wx + b). With the placeholder running values of one you get a different, equally valid fused layer with scales 2, 3, -1, because the fused weights depend on whatever statistics training froze.
Recall
Feature values after ReLU are (0, 2, 3, 0). Compute mu, sigma squared and x_hat with epsilon negligible.
Recall
Which of mu, sigma squared, gamma and beta are learned by backpropagation, and which are computed?
Recap
If you remember nothing else
- Normalization exists because y = Wx is hard to optimize when inputs are off-center (a large bias is needed) or differently scaled (W entries must span very different magnitudes).
- BN works per feature over the batch: mu and sigma squared have shape D, x_hat = (x - mu)/sqrt(sigma squared + eps), y = gamma x_hat + beta. gamma and beta are learned, mu and sigma are computed.
- gamma = sqrt(sigma squared + eps) and beta = mu recover the identity; before tanh they decide how far activations reach into the saturated tails.
- At inference mu and sigma squared are running averages, so BN is an affine map that folds into the previous FC or conv: W' = diag(gamma/sqrt(sigma squared + eps)) W, b' = gamma (b - mu)/sqrt(sigma squared + eps) + beta, at zero test-time cost.
- Benefits: easier training, better gradient flow, higher learning rates, robustness to initialization, regularization from batch noise. Hazard: train and eval mode behave differently.
- BN for conv averages over N, H, W (mu is 1 x C x 1 x 1); LN over features per sample (N x 1); IN over H, W per sample and channel (N x C x 1 x 1); GN over H, W and C/G channels. gamma and beta are per channel (1 x C x 1 x 1) for BN, IN and GN; LN on an N x D input has them 1 x D on slide 35, per channel in Wu and He.
- LN, IN and GN need no batch and behave the same at train and test, which is why LN runs in RNNs and transformers and GN wins at batch size 2.
- Slide 38 batch: means 2.25, 1.25, 1.75; variances 0.6875, 1.6875, 4.1875; every output row has mean beta and variance gamma squared.
Sources
- Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate ShiftPaperIoffe and Szegedy, ICML 2015Algorithm 1, identity recovery via gamma and beta, inference with population statistics, regularization (3.4), higher learning rates (3.3), per-feature-map parameters, 14x fewer steps.(opens in a new tab)
- Layer NormalizationPaperBa, Kiros and Hinton, 2016Statistics from a single training case, same computation at train and test, application to recurrent networks.(opens in a new tab)
- Instance Normalization: The Missing Ingredient for Fast StylizationPaperUlyanov, Vedaldi and Lempitsky, 2016Replacing batch norm with instance norm at training and test time for style transfer.(opens in a new tab)
- Improved Texture Networks: Maximizing Quality and Diversity in Feed-forward Stylization and Texture SynthesisPaperUlyanov, Vedaldi and Lempitsky, CVPR 2017The instance normalization reference cited on slide 36.(opens in a new tab)
- Group NormalizationPaperWu and He, ECCV 2018Unified S_i formulation, G = 32 default, LN at G = 1 and IN at G = C, per-channel gamma and beta, 24.1 vs 34.7 percent error at batch size 2, memory remark.(opens in a new tab)
- Attention Is All You NeedPaperVaswani et al., NeurIPS 2017Section 3.1: each sub-layer output is LayerNorm(x + Sublayer(x)).(opens in a new tab)
- How Does Batch Normalization Help Optimization?PaperSanturkar, Tsipras, Ilyas and Madry, NeurIPS 2018BN smooths the optimization landscape; internal covariate shift is not the cause of its success.(opens in a new tab)
- Deep Learning, chapter 8.7.1: Batch NormalizationBookGoodfellow, Bengio and Courville, MIT PressAdaptive reparametrization, why beta and gamma make the mean and scale easy to learn, running averages at test time.(opens in a new tab)
- MIT 6.5940 TinyML and Efficient Deep Learning Computing, Lecture 2DocsMIT HAN LabSlide 41: normalization makes optimization faster, unified S_i definition, per-channel linear transform.(opens in a new tab)
- torch.nn.BatchNorm2dDocsPyTorch documentationeps 1e-5, momentum 0.1, biased variance in training and unbiased running variance, running estimates in eval mode, gamma and beta of size C.(opens in a new tab)
- torch.nn.LayerNormDocsPyTorch documentationStatistics over the last D dimensions, identical in training and evaluation modes.(opens in a new tab)
- torch.nn.GroupNormDocsPyTorch documentationEach group holds num_channels / num_groups channels; same statistics at train and eval.(opens in a new tab)
- torch.nn.InstanceNorm2dDocsPyTorch documentationInstance statistics at train and eval, affine and track_running_stats off by default.(opens in a new tab)
- torch.nn.utils.fusion.fuse_conv_bn_evalDocsPyTorch documentationFuses a convolution and a batch norm in eval mode into a single convolution.(opens in a new tab)
- CS231n notes: Setting up the data and the modelDocsStanford UniversityBatch norm as differentiable preprocessing, inserted after FC or conv layers and before nonlinearities.(opens in a new tab)
- Dive into Deep Learning, section 8.5: Batch NormalizationBookZhang, Lipton, Li and SmolaTraining versus prediction mode, batch noise as regularization at batch sizes of 50 to 100, epsilon, per-channel BN for convolutions.(opens in a new tab)
- Batch Normalization by HandArticleTom Yeh, byhand.aiSource of the slide 38 worksheet. The full sheet is paywalled; values here were recomputed.(opens in a new tab)
Part 06: Activation functions and the transformer
The non-linearities used in embedded networks, then the transformer block: scaled dot-product attention worked by hand and the position-wise feed-forward network.
6 concepts, slides 39-45
Why this part matters
Every model you will compress, quantize or deploy on a microcontroller in this course has an activation function between its layers, and that one choice decides whether int8 inference is exact and cheap or approximate and slow. MobileNetV2 and V3 picked ReLU6 and hard swish for exactly this reason.
The second half of the part opens the transformer, now the model family you are most likely to be asked to shrink for the edge. Its attention block has a cost that grows with the square of the sequence length, which is the first number an embedded designer must reason about. The exam asks for the attention formula with shapes, the sqrt(d_k) argument, and a small attention computed by hand, so the part ends with two worked slides checked line by line, including two arithmetic slips on the slides themselves.
By the end you can
- Explain why activations must be non-linear, and write sigmoid, ReLU, ReLU6, leaky ReLU, swish and hard swish with their ranges and derivatives.
- Argue which activations suit quantized embedded models and why: a bounded range and no exponential.
- Draw the transformer block and place multi-head attention, Add and Norm, the feed-forward network and positional encoding.
- State Attention(Q, K, V) with every matrix shape, explain the query, key and value roles, and justify the sqrt(d_k) scaling.
- Compute a small self-attention and a position-wise FFN by hand, and explain why the N x N attention matrix is the embedded bottleneck.
Start with the Neuron from the beginning of the lecture: three inputs, three weights, a Bias, and then y = f(w0 x0 + w1 x1 + w2 x2 + b). Now ask what happens if f is the identity and you stack two such layers. The second layer computes W2 (W1 x + b1) + b2, which multiplies out to (W2 W1) x + (W2 b1 + b2): one matrix, one bias, one linear layer. Ten layers would collapse the same way. Depth buys nothing.
Worked example
Two linear layers collapse into one
Write the second layer in terms of the first
y = W2 h + b2 with h = W1 x + b1, so y = W2 W1 x + W2 b1 + b2.Name the products
Set W = W2 W1 and b = W2 b1 + b2. Both are constants once training stops.A single fully connected layer
y = W x + b. The stacked network can only draw straight decision boundaries, exactly like the one layer it collapsed into. Goodfellow, Bengio and Courville open their chapter on feedforward networks with XOR for this reason: no single line separates its four points, but a hidden layer of rectified units does.
That is why the slide says activation functions are typically non-linear. The Activation function is the bend that stops the collapse, and it sits after every weighted sum in every Hidden layer. Slide 39 places it on the CNN component map beside convolution, pooling, fully connected layers and the normalization block of the previous part: the only component with no parameters and no shape arithmetic, just a pointwise bend applied to every value. Which bend to use is a real design decision, and on embedded hardware it is decided by two questions the slide leaves implicit: does the function need an exponential, and is its output bounded?
The six on the slide, and what each costs a microcontroller
Sigmoid, 1 / (1 + e^-x), squashes any input into (0, 1). Its derivative is s(1 - s), at most 0.25 at zero and already 0.0066 at x = 5. A sigmoid unit therefore saturates across most of its domain, which is why Goodfellow and colleagues say its use as a hidden unit is now discouraged. It also needs an exponential per element. ReLU, max(0, x), has derivative 0 or 1, needs a single comparison, and is the same textbook's recommended default; AlexNet is the network that made it standard.
ReLU6, min(max(0, x), 6), is ReLU with a ceiling. Krizhevsky introduced the cap in 2010 to encourage sparse features; MobileNetV2 kept it for a different reason, robustness when used with low-precision computation. Leaky ReLU, max(alpha x, x), replaces the flat negative side with a small slope so a unit that is off still passes a gradient; Maas, Hannun and Ng used alpha = 0.01, which is still PyTorch's default. Swish, x / (1 + e^-x), is x times its own sigmoid (the general form is x sigmoid(beta x), PyTorch calls it SiLU). It dips to -0.278 at x = -1.28 before rising, and Ramachandran, Zoph and Le found it beats ReLU on deeper models. It costs an exponential, which is the whole problem on a Cortex-M.
Hard swish is MobileNetV3's answer: x ReLU6(x + 3) / 6. Expand it and you get the slide's three cases, 0 for x <= -3, x for x >= 3, and x(x + 3) / 6 between. Howard and colleagues give three reasons: optimized ReLU6 kernels exist on virtually every framework and chip, the piecewise form removes the precision loss that different approximate sigmoids introduce in quantized mode, and it can run with fewer memory accesses. They saw no discernible accuracy difference against real swish, and an optimized h-swish added about 1 ms over ReLU on a Pixel 1.
| x | swish | hard swish | gap |
|---|---|---|---|
| -3 | -0.142 | 0 | 0.142 |
| -1 | -0.269 | -0.333 | 0.064 |
| 0 | 0 | 0 | 0 |
| 1 | 0.731 | 0.667 | 0.064 |
| 3 | 2.858 | 3 | 0.142 |
| 4 | 3.928 | 4 | 0.072 |
The largest gap on [-6, 6] is 0.142, at the two knots x = -3 and x = 3. The derivative also lines up: swish has slope 0.5 at zero and hard swish has (2x + 3) / 6 = 0.5 there too.
| Function | Formula | Range | Derivative | Needs | Embedded note |
|---|---|---|---|---|---|
| Sigmoid | 1 / (1 + e^-x) | (0, 1) | s(1 - s), at most 0.25 | exp | Saturates both sides; output gates and probabilities only |
| ReLU | max(0, x) | [0, inf) | 0 or 1 | max only | Default hidden unit; unbounded, so the int8 range must be calibrated |
| ReLU6 | min(max(0, x), 6) | [0, 6] | 1 on (0, 6), else 0 | max and min | Bounded: fixed quantization grid; MobileNetV2's choice |
| Leaky ReLU | max(alpha x, x) | (-inf, inf) | alpha or 1 | max only | Keeps a small gradient for negative inputs, alpha = 0.01 by default |
| Swish | x / (1 + e^-x) | [-0.278, inf) | s + x s(1 - s) | exp | Smooth and non-monotonic; better than ReLU on deep models, costly on MCUs |
| Hard swish | x ReLU6(x + 3) / 6 | [-0.375, inf) | 0, (2x + 3)/6, or 1 | multiply and clamp | Swish traced with a ruler; MobileNetV3's replacement for swish |
| tanh | 2 s(2x) - 1 | (-1, 1) | 1 - tanh^2 | exp | Zero-centred sigmoid; still saturates |
| GELU | x Phi(x) | [-0.17, inf) | Phi(x) + x phi(x) | erf or tanh | Default in BERT and GPT style transformers |
| ELU | x if x > 0 else e^x - 1 | (-1, inf) | 1 or e^x | exp | Negative saturation at -1 pushes the mean toward zero |
| Mish | x tanh(softplus(x)) | [-0.31, inf) | numeric in practice | exp, log, tanh | Smooth like swish, even more expensive |
Why bounded is what int8 wants
Quantizing an Activation tensor to 8 bits means choosing a range and dividing it into 255 steps. With ReLU6 the range is known before you see a single input: [0, 6], so each step is 6 / 255 = 0.0235 and every layer shares the same grid. With plain ReLU the range depends on the data. If one activation channel happens to reach 60, the step becomes 60 / 255 = 0.235, ten times coarser, for every value in that tensor, including the many small ones. MobileNetV2's sentence about low-precision robustness is the citation; the arithmetic is the reason.
Worked example
uint8 grid for ReLU6 versus an unbounded ReLU
Bounded
Range [0, 6], 255 steps, step size 6 / 255 = 0.0235. Fixed at design time.Unbounded, calibrated on data
Observed range [0, 60], step size 60 / 255 = 0.235. One outlier channel sets the grid for everyone.Result
Same 8 bits, ten times the rounding error per value. A bounded activation removes the calibration risk entirely.
- cost on an MCU
- needs an exponential
- bounded
- no, range [-0.278, inf)
Quick check
Which activation removes the exponential so a quantized microcontroller can run it cheaply?
Recall
Which activation would you pick for an int8 MCU model and why, and what would you use instead of swish?
Feed a four-word sentence into the model of Vaswani and colleagues. Each word becomes an embedding of d_model = 512 numbers, a positional encoding of the same size is added to it, and the four vectors enter the first encoder layer. That layer does exactly two things: a Multi-head attention step that lets each word read the others, then a position-wise feed-forward network that processes each word on its own. Around each of the two sits an Add and Norm. Repeat the layer N = 6 times and you have the encoder of the original Transformer.
Token vector plus a sinusoidal position vector.
Every token reads every other token.
LayerNorm(x + Sublayer(x)).
Same two-layer MLP on each position.
Output feeds the next identical layer.
The Add and Norm box is the link back to the normalization part. Each sub-layer's output is added to its own input (a residual connection, the ResNet idea) and the sum goes through Layer normalization: LayerNorm(x + Sublayer(x)). LayerNorm rather than BatchNorm because it normalizes each token's 512 features on their own, so it behaves identically at training and inference and needs no batch statistics, which is what you want for variable-length sequences. Jurafsky and Martin describe the whole stack as a residual stream that each component reads from and adds back to.
Base model hyperparameters (Vaswani et al. 2017)
- Layers per stack N
- 6
- Model width d_model
- 512
- Heads h
- 8
- Head size d_k = d_v
- 512 / 8 = 64
- FFN hidden width d_ff
- 2048
Inside the two sub-layers
Multi-head attention runs h = 8 copies of Scaled dot-product attention in parallel. Each head first projects the queries, keys and values with its own small linear layers down to d_k = 64, computes attention, and the eight 64-wide results are concatenated back to 512 and passed through one more linear layer. Because each head works at one eighth of the width, the total cost is about that of a single head at full width, and each head is free to attend to a different relationship.
The feed-forward sub-layer is a two-layer MLP with a ReLU between, widening from 512 to 2048 and back. It is applied to each position separately and identically, which the paper notes is the same as two convolutions with kernel size 1. This is where the block's non-linearity lives: once the attention weights are fixed, attention itself is a linear mix of the values.
Position, the decoder, and the output
Attention has no notion of order: swap two tokens and the same weights come out swapped. So before the first layer, a positional encoding is added to each embedding. The paper uses sinusoids of different wavelengths, PE(pos, 2i) = sin(pos / 10000^(2i / d_model)) and cosine for odd indices, so that every position gets a distinct 512-vector and relative offsets are easy to express.
The decoder stack (right side of the slide) repeats the same layer with two changes. Its self-attention is masked so that position i cannot see positions after i, which together with the outputs being shifted right by one keeps training honest about what is known when. And a third sub-layer, encoder-decoder attention, takes queries from the decoder and keys and values from the encoder output. A final linear layer and softmax turn the last vector into next-token probabilities.
Recall
What does the position-wise FFN do that attention does not, and vice versa?
Type a phrase into YouTube's search bar. The phrase is your query. Every video carries a title and description, which is its key. The video itself is the value. Search scores your query against every key, ranks the matches, and hands you back videos. Attention does the same three things with vectors, with one twist: instead of returning the single best video it returns a blend of all the values, weighted by how well each key matched. That is the Query, key, value design of the slide.
In self-attention all three come from the same input matrix X of N tokens, each through its own learned projection: Q = X W_Q, K = X W_K, V = X W_V. Jurafsky and Martin give the roles precisely: the query is the current element being compared, a key is a preceding input being compared to it to produce a similarity weight, and a value is what gets weighted and summed. The comparison is a dot product, so the whole score table is one matrix multiply, and the paper names the result Scaled dot-product attention.
Shapes for N tokens with head size d_k and value size d_v
- Q, K
- N x d_k
- V
- N x d_v
- Q K^T (scores) and softmax output
- N x N
- Output
- N x d_v
Read the slide's diagram bottom to top with those shapes: Q and K of shape N x d multiply into an N x N table of scores, the optional mask sets forbidden entries to minus infinity, softmax turns each row into a probability distribution, and multiplying by V (N x d) gives one output row per token. The masked version is what the decoder uses so that a token cannot attend to what comes after it.
Why divide by the square root of d_k
If the components of a query and a key have mean zero and variance one, their dot product has variance d_k: it is a sum of d_k products. So raw scores grow with the head size, and Vaswani and colleagues observe that large dot products push the softmax into regions where it has extremely small gradients. Goodfellow and colleagues say the same about softmax in general: its outputs saturate when the differences between inputs become extreme. Dividing by sqrt(d_k) brings the variance back to one, whatever the head size.
| d_k | Unscaled scores | softmax unscaled | softmax after / sqrt(d_k) |
|---|---|---|---|
| 4 | (2, 0, -2) | (0.867, 0.117, 0.016) | (0.665, 0.245, 0.090) |
| 64 | (8, 0, -8) | (1.000, 0.000, 0.000) | (0.665, 0.245, 0.090) |
| 512 | (22.6, 0, -22.6) | (1.000, 0.000, 0.000) | (0.665, 0.245, 0.090) |
Without the division, a head of size 64 already turns a modest spread into a one-hot answer of (1.000, 0.000, 0.000), and the gradient through those zeros is essentially zero. With it, every head size sees the same healthy distribution. Softmax is also shift-invariant, softmax(z) = softmax(z + c), so only differences between scores matter, which is what implementations exploit for numerical stability.
The N x N matrix is the embedded constraint
Every query is scored against every key, so one head at one layer produces N^2 scores, whatever the feature size. Jurafsky and Martin state it plainly: attention is quadratic in the length of the input. Vaswani's complexity table writes the per-layer cost as O(n^2 d). Double the sequence and you quadruple the score matrix; multiply by heads and layers and it is the dominant activation memory of the model.
| Tokens N | Entries N^2 | Memory |
|---|---|---|
| 16 | 256 | 1 KB |
| 64 | 4,096 | 16 KB |
| 256 | 65,536 | 256 KB |
| 1,024 | 1,048,576 | 4 MB |
| 4,096 | 16,777,216 | 64 MB |
Quick check
What is the shape of the softmax output in single-head attention over N tokens with head size d_k?
Quick check
Why does Vaswani et al. divide Q K^T by the square root of d_k?
Recall
Write the attention formula and give the shape of every matrix for N tokens and head size d_k.
Recall
Where does the N^2 come from and why does it matter on embedded hardware?
Tom Yeh's drawing makes Scaled dot-product attention concrete with numbers you can check at a desk. Four tokens, each with six features, are the columns x1 = (2, 0, 0, 0, 2, 1), x2 = (0, 1, 2, 0, 0, 0), x3 = (0, 0, 1, 1, 0, 1) and x4 = (2, 0, 0, 1, 0, 1). Three 3 x 6 matrices W_Q, W_K and W_V project them to the queries, keys and values of head size d_k = 3. Those 54 numbers are the only learned parameters; everything else is computed from the input.
Two shortcuts keep the arithmetic in your head. Dividing by sqrt(3) = 1.732 is replaced by dividing by 2 and dropping the fraction (rounding toward zero, so -1 / 2 becomes 0), and e^x is replaced by 3^x, which has the same shape and integer values for non-negative exponents. The shortcuts preserve the ranking for three of the four queries; the comparison table below shows the one query where halving squashes the gap away.
Worked example
Column 1: what token 1 attends to
Project
q1 = W_Q x1 = (2, 0, 3). The keys are k1 = (0, 0, 1), k2 = (2, 1, 0), k3 = (1, 0, -1), k4 = (0, 0, 1), and the values v1 = (20, 0, 0), v2 = (0, 0, 10), v3 = (0, 10, 0), v4 = (20, 10, 0).Score
Dot products of q1 with each key: 3, 4, -1, 3. Key 2 is the best match.Scale
Divide by 2 and truncate: 1, 2, 0, 1. (Exact: divide by 1.732, giving 1.73, 2.31, -0.58, 1.73.)Softmax with powers of three
3^1, 3^2, 3^0, 3^1 = 3, 9, 1, 3, sum 16, weights 0.19, 0.56, 0.06, 0.19, drawn as .2, .6, 0, .2.Weighted sum of values
z1 = 0.2 v1 + 0.6 v2 + 0 v3 + 0.2 v4 = (4, 0, 0) + (0, 0, 6) + (4, 2, 0).z1 = (8, 2, 6)
Token 1's new representation is mostly value 2 (weight 0.6), with a fifth each of values 1 and 4. The exact computation gives (10.3, 2.8, 4.6) with the same ranking.
| d1 | d2 | d3 | |
|---|---|---|---|
| q1 | |||
| q2 | |||
| q3 | |||
| q4 |
| d1 | d2 | d3 | |
|---|---|---|---|
| k1 | |||
| k2 | |||
| k3 | |||
| k4 |
| d1 | d2 | d3 | |
|---|---|---|---|
| v1 | |||
| v2 | |||
| v3 | |||
| v4 |
| k1 | k2 | k3 | k4 | |
|---|---|---|---|---|
| q1 | 0.20 | 0.60 | 0.10 | 0.20 |
| q2 | 0.30 | 0.30 | 0.10 | 0.30 |
| q3 | 0.40 | 0.10 | 0.00 | 0.40 |
| q4 | 0.10 | 0.80 | 0.10 | 0.10 |
| q . k | trunc(s / 2) | 3^s | weight | |
|---|---|---|---|---|
| k1 | 3 | 1 | 3.00 | 0.200 |
| k2 | 4 | 2 | 9.00 | 0.600 |
| k3 | -1 | 0 | 1.00 | 0.100 |
| k4 | 3 | 1 | 3.00 | 0.200 |
Sum of the exponentials 16.00. Each weight is its power divided by this sum, rounded to one decimal as on the slide.
0.20 (20, 0, 0) + 0.60 (0, 0, 10) + 0.10 (0, 10, 0) + 0.20 (20, 10, 0)
(8.0, 3.0, 6.0)
Head size d_k = 3, N = 4 tokens, so the score matrix has 16 entries whatever the feature size. The highlighted row is the selected query; the teal entries are the keys that win (all of them when tied). Switch between the two arithmetics: the weights move and the winner rarely does. Try q2 to see the one default row where halving erases the gap.
| Query | Scores q . k | Slide weights | Exact weights | Slide z | Exact z |
|---|---|---|---|---|---|
| q1 = (2, 0, 3) | 3, 4, -1, 3 | .2, .6, 0, .2 | .258, .459, .026, .258 | (8, 2, 6) | (10.3, 2.8, 4.6) |
| q2 = (1, 1, 2) | 2, 3, -1, 2 | .3, .3, .1, .3 | .253, .450, .045, .253 | (12, 4, 3) | (10.1, 3.0, 4.5) |
| q3 = (0, 1, 2) | 2, 1, -2, 2 | .4, .2, 0, .4 | .376, .211, .037, .376 | (16, 4, 2) | (15.0, 4.1, 2.1) |
| q4 = (2, 1, 1) | 1, 5, 1, 1 | .1, .7, .1, .1 | .077, .770, .077, .077 | (4, 2, 7) | (3.1, 1.5, 7.7) |
For q1, q3 and q4 the same key wins under both arithmetics, and the second-best key is the same too: softmax cares about differences between scores, and a monotone squashing of those differences preserves the order as long as the differences survive. The numbers drift (a weight of 0.6 becomes 0.46), the decision does not. The corrected q2 is the exception that shows how coarse the shortcut is: its scores 2, 3, -1, 2 halve to 1, 1, 0, 1, so the by-hand softmax hands keys 1, 2 and 4 the same 0.3 while the exact softmax still separates key 2 at 0.45. Integer halving erased a difference of one, which is exactly the kind of information a real quantizer must be careful not to lose.
Quick check
On slide 43 with query q1 = (2, 0, 3), which key receives the largest attention weight?
Recall
With q = (2, 0, 3) and keys (0, 0, 1), (2, 1, 0), (1, 0, -1), (0, 0, 1), which key wins and what are the raw scores?
The second by-hand slide picks up where the first stopped. Five tokens with three features arrive from the previous block, x1 = (5, 0, 1), x2 = (6, 2, 0), x3 = (0, 4, 1), x4 = (7, 0, 1), x5 = (0, 3, 0). The attention weight matrix A is given rather than computed, and it is deliberately simple: each column has exactly two ones, so each token attends equally to itself and to one other token (the next neighbour for tokens 1 to 4, and token 1 for token 5). The attention-weighted feature of token 1 is then a plain sum, z1 = x1 + x2 = (11, 2, 1), and likewise z2 = x2 + x3, z3 = x3 + x4, z4 = x4 + x5 and z5 = x1 + x5.
Now the position-wise feed-forward network takes each z_j on its own. The first Fully connected layer has weights W1 of shape 4 x 3 with rows (1, -1, 0), (1, 1, 0), (0, 1, 1), (-1, 1, 1) and biases (1, 0, 1, 0). A ReLU follows. The second layer has W2 of shape 3 x 4 with rows (1, 0, 0, -1), (0, 1, 1, 0), (0, 0, 1, -1) and biases (0, 0, 1). Widen from three to four, bend, narrow back to three: the same expand-and-contract shape as the paper's 512 to 2048 to 512.
Worked example
Token 1 through the FFN
First layer
W1 z1 + b1 with z1 = (11, 2, 1): row 1 gives 11 - 2 + 0 + 1 = 10, row 2 11 + 2 + 0 = 13, row 3 0 + 2 + 1 + 1 = 4, row 4 -11 + 2 + 1 + 0 = -8. Hidden vector (10, 13, 4, -8).ReLU
max(0, h) = (10, 13, 4, 0). The fourth unit is off for this token.Second layer
W2 h + b2: row 1 10 - 0 + 0 = 10, row 2 13 + 4 + 0 = 17, row 3 4 - 0 + 1 = 5.out1 = (10, 17, 5)
This vector goes to the next block. Run the same three steps with the same W1, b1, W2, b2 on the other four tokens and you get the slide's five outputs.
| Token | z | W1 z + b1 | after ReLU | W2 h + b2 |
|---|---|---|---|---|
| z1 = x1 + x2 | (11, 2, 1) | (10, 13, 4, -8) | (10, 13, 4, 0) | (10, 17, 5) |
| z2 = x2 + x3 | (6, 6, 1) | (1, 12, 8, 1) | (1, 12, 8, 1) | (0, 20, 8) |
| z3 = x3 + x4 | (7, 4, 2) | (4, 11, 7, -1) | (4, 11, 7, 0) | (4, 18, 8) |
| z4 = x4 + x5 | (7, 3, 1) | (5, 10, 5, -3) | (5, 10, 5, 0) | (5, 15, 6) |
| z5 = x1 + x5 | (5, 3, 1) | (3, 8, 5, -1) | (3, 8, 5, 0) | (3, 13, 6) |
Where the parameters sit
The FFN is small on the slide and large in the real model. Derive it from the base hyperparameters: the four attention projections W_Q, W_K, W_V, W_O are each 512 x 512, about 1.05 M weights per layer. The FFN's two matrices are 512 x 2048 and 2048 x 512, about 2.1 M. Two thirds of a layer's weights live in the FFN, so pruning and quantization work in later lectures will spend most of its effort there, while the N^2 activation cost of the previous concept lives in attention.
Weights in one base encoder layer, derived from d_model = 512 and d_ff = 2048 (biases omitted)
- Attention projections (4 x 512 x 512)
- 1,048,576
- FFN (2 x 512 x 2048)
- 2,097,152
- FFN share of the layer
- about 67%
Quick check
In the position-wise feed-forward network, which statement is true?
Recall
Slide 44: compute the FFN output for token 4, whose attention-weighted feature is z4 = (7, 3, 1).
The closing slide lists the ten works the lecturer built this lecture from. Read them as a map of where each part came from: the convolution arithmetic animations and CS231n behind parts 2 to 4, the two normalization papers behind part 5, the four architecture papers whose design choices (ReLU in AlexNet, depth in VGG, residuals in ResNet, ReLU6 in MobileNetV2) run through this part, and the survey and course that frame the whole thing for embedded deployment.
Sources
- Convolution arithmetic (animations)DocsDumoulin and Visin, GitHubPadding and stride animations used in parts 2 and 3(opens in a new tab)
- CS231n Lecture 5: Image Classification with CNNsDocsStanford UniversityConvolution, pooling and the CNN components map(opens in a new tab)
- Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate ShiftPaperIoffe and Szegedy, ICML 2015Part 5(opens in a new tab)
- Group NormalizationPaperWu and He, ECCV 2018Part 5, the family of normalization axes(opens in a new tab)
- ImageNet Classification with Deep Convolutional Neural Networks (AlexNet)PaperKrizhevsky, Sutskever and Hinton, NeurIPS 2012ReLU as the standard hidden unit(opens in a new tab)
- Very Deep Convolutional Networks for Large-Scale Image Recognition (VGG)PaperSimonyan and Zisserman, ICLR 2015Depth from stacked 3 x 3 convolutions(opens in a new tab)
- Deep Residual Learning for Image Recognition (ResNet)PaperHe, Zhang, Ren and Sun, CVPR 2016The residual connection reused by Add and Norm(opens in a new tab)
- MobileNetV2: Inverted Residuals and Linear BottlenecksPaperSandler et al., CVPR 2018ReLU6 for low-precision robustness(opens in a new tab)
- Model Compression and Hardware Acceleration for Neural Networks: A Comprehensive SurveyPaperDeng, Li, Han, Shi and Xie, Proceedings of the IEEE 108(4), 2020The embedded framing of the whole lecture(opens in a new tab)
- MIT 6.5940: TinyML and Efficient Deep Learning Computing (Fall 2024)DocsSong Han, MIT HAN LabLecture 2, Basics of Deep Learning, which this lecture follows(opens in a new tab)
Recap
If you remember nothing else
- Without a non-linearity, stacked linear layers collapse into one linear layer, so depth buys nothing.
- Sigmoid saturates on both sides with a derivative of at most 0.25. ReLU, max(0, x), is the default hidden unit.
- ReLU6 = min(max(0, x), 6) is bounded, which hands int8 a fixed grid of 6/255 per step. MobileNetV2 chose it for low-precision robustness.
- Hard swish = x ReLU6(x + 3)/6 stays within 0.15 of swish everywhere and needs no exponential.
- A transformer block is multi-head attention, Add and Norm (residual plus LayerNorm), position-wise FFN, Add and Norm. Base model: N = 6, d_model = 512, h = 8, d_k = 64, d_ff = 2048.
- Attention(Q, K, V) = softmax(Q K^T / sqrt(d_k)) V. Q and K are N x d_k, the weights are N x N, the output is N x d_v.
- Dividing by sqrt(d_k) keeps the dot-product variance at 1 so softmax does not saturate and its gradients stay alive.
- Attention memory and compute grow with N^2. At N = 1024 one fp32 head already holds 4 MB of scores.
- Slide 43 by hand: q1 = (2, 0, 3) scores 3, 4, -1, 3 against the keys, weights about 0.2, 0.6, 0, 0.2, z1 = (8, 2, 6). The slide's q2 should be (1, 1, 2).
- The FFN applies the same linear, ReLU, linear to every token independently. Slide 44's two wrong hidden entries (-9 and -4 instead of -8 and -3) are zeroed by ReLU anyway.
Sources
- Attention Is All You NeedPaperVaswani et al., NeurIPS 2017Sections 3.1 to 3.5: block, multi-head attention, sqrt(d_k), FFN, positional encoding; Table 1 for O(n^2 d)(opens in a new tab)
- Speech and Language Processing, 3rd ed. draft, Chapter 7: Transformers and PretrainingBookJurafsky and Martin, StanfordQuery, key and value roles (7.1), quadratic cost, the residual stream and pre-norm (7.2)(opens in a new tab)
- Deep Learning, Chapter 6: Deep Feedforward NetworksBookGoodfellow, Bengio and Courville, MIT PressXOR motivation (6.1), ReLU as default and leaky ReLU (6.3), sigmoid saturation (6.3.2), softmax saturation (6.2.2.3)(opens in a new tab)
- Searching for MobileNetV3PaperHoward et al., ICCV 2019Section 5.2: h-swish definition and the three reasons; Table 5 latency(opens in a new tab)
- MobileNetV2: Inverted Residuals and Linear BottlenecksPaperSandler et al., CVPR 2018Section 4: ReLU6 chosen for robustness under low-precision computation(opens in a new tab)
- Searching for Activation FunctionsPaperRamachandran, Zoph and Le, 2017Swish, x sigmoid(beta x), and its gains over ReLU on deeper models(opens in a new tab)
- Convolutional Deep Belief Networks on CIFAR-10PaperKrizhevsky, 2010Origin of the rectified unit capped at 6 (section 4.1)(opens in a new tab)
- Rectifier Nonlinearities Improve Neural Network Acoustic ModelsPaperMaas, Hannun and Ng, ICML 2013The leaky rectifier with slope 0.01(opens in a new tab)
- Gaussian Error Linear Units (GELUs)PaperHendrycks and Gimpel, 2016x Phi(x)(opens in a new tab)
- Fast and Accurate Deep Network Learning by Exponential Linear Units (ELUs)PaperClevert, Unterthiner and Hochreiter, ICLR 2016(opens in a new tab)
- Mish: A Self Regularized Non-Monotonic Activation FunctionPaperMisra, BMVC 2020(opens in a new tab)
- torch.nn.HardswishDocsPyTorch 2.14 documentationThe three-case definition; see also ReLU6, LeakyReLU (negative_slope 0.01) and SiLU(opens in a new tab)
- Self Attention by HandArticleTom Yeh, AI by HandSource of slide 43; the 4 x 6 setup is in the free part(opens in a new tab)
- Transformer by HandArticleTom Yeh, AI by HandSource of slide 44(opens in a new tab)
- The Illustrated TransformerArticleJay AlammarLinked from slide 44(opens in a new tab)
- Attention in transformers, step-by-stepVideo3Blue1BrownThe attention pattern has the square of the context size in entries(opens in a new tab)
- Transformer Attention Explained By ExampleVideoYouTubeLinked from slide 42(opens in a new tab)
- How Attention Mechanism Works in Transformer ArchitectureVideoYouTubeLinked from slide 42(opens in a new tab)
- What exactly are keys, queries, and values in attention mechanisms?ArticleCross Validated (Stack Exchange)Content credit named on slide 42 for the search analogy(opens in a new tab)