AIAny. ← Back to AIAny

Lesson notes · One paper · two authors · one kernel that grows

Multi-Scale Context Aggregation by Dilated Convolutions

By the end of this page you'll understand — with real numbers — how putting gaps between a filter's taps lets a tiny 3×3 kernel see an ever-wider patch of an image, without shrinking the picture or adding a single new weight. It is one of the quietest, most reused ideas in computer vision.

Subject Semantic segmentation Venue ICLR 2016 arXiv 1511.07122 Lab Princeton · Intel Labs Key win +5pt mIoU, no resolution lost

Fisher Yu · Vladlen Koltun — Princeton University & Intel Labs. Submitted 23 Nov 2015; presented at ICLR 2016. Evaluated on Pascal VOC 2012 semantic segmentation. The mechanism — not a whole architecture — that quietly became standard equipment.

The core idea

Widen what a filter sees by spacing out its taps — not by shrinking the picture.

For dense prediction — labelling every pixel, as in semantic segmentation — a network faces two demands that pull in opposite directions. It needs wide context (to know a brown blob is a dog, you must see the whole dog) and full resolution (so the label snaps to the dog's exact outline). The old toolkit bought context by pooling and downsampling — and paid for it by destroying resolution, then awkwardly upsampling it back.

The authors' bet: that trade was never necessary. A dilated convolution spreads a filter's taps apart by a dilation factor, so each neuron reaches farther across the image while the kernel keeps its same few weights and the feature map keeps its full size. Stack layers with a doubling dilation and the receptive field — how much of the input one output cell depends on — grows exponentially while the parameter count grows only linearly. Wide context and sharp detail, at the same time, for free.

“Dilated convolutions support exponential expansion of the receptive field without loss of resolution or coverage.” — Yu & Koltun, 2016

PART A

The problem from orbit

What is dense prediction, why does pooling hurt it, and what does the fix look like?

Step 1 · The task & the black box

Not one label for a photo — one label for every single pixel.

Here is the whole story in one picture. A photo goes in; a map of labels comes out — one class per pixel, snapped to the object's outline. That task is semantic segmentation, a kind of dense prediction (a prediction at every location, not one summary answer). This is the job our mechanism has to serve all the way through:

H × W × 3 pixels DILATED-CONV NET full resolution kept how? ↓ "dog" per pixel
Our running task. Output is a per-pixel label map at the input's full resolution — every mechanism on this page exists to make that map both wide-aware and sharp. (Drawn in SVG — no images are loaded.)

Why is this hard? Classification nets answer one question — “what is this photo?” — and to do it they happily throw away location, pooling a big image down to a tiny grid. Dense prediction cannot afford that: a label map blurred down to a 7×7 grid and blown back up has fuzzy, wandering boundaries. Yet you still need wide context — a single brown patch could be dog, horse, or sofa until you see enough around it.

So the field was stuck between two needs that the usual tools force you to trade against each other. This paper's quiet claim is that the trade is avoidable. The rest of the page builds the one operator that removes it — starting with exactly why pooling was the villain.

From the abstract: “Dilated convolutions support exponential expansion of the receptive field without loss of resolution or coverage.” The paper evaluates on Pascal VOC 2012 semantic segmentation.

Step 2 · Why pooling is the villain

Pool to gather context, and you destroy the resolution dense prediction was built to keep.

You've seen the task: a sharp label for every pixel. Now pin down exactly why the standard recipe — borrowed from classification nets — fights that goal.

How does a classifier earn wide context? With pooling and striding — both downsampling moves that summarize a window into one number and halve the grid. Stack a few and a neuron deep in the net effectively “sees” a huge swath of the input. Great for answering “dog or not”.

But each halving throws away where. By the time you've pooled an image down enough for global context, a crisp 1024-wide picture can be a coarse grid. To label pixels you must blow it back up, and upsampling can only guess the detail it already discarded — so boundaries come back fuzzy and wandering. Compare the two routes:

The old route · pool → upsample sharp · 64² pooled · 8² blurry label · 64² boundaries lost ✗ This paper · keep resolution throughout sharp the whole way ✓ same 64² grid at every layer
The dilemma in one frame. Top: pool for context, lose resolution, upsample, get blur. Bottom: the goal — never lose resolution, yet still widen what each neuron sees. The next step is the trick that makes the bottom row possible.
Key tension — hold onto this

Dense prediction needs both wide context and full resolution. Pooling buys the first by spending the second. The whole paper is one move that buys context without spending resolution.

Step 3 · The shape of the system

Two pieces: a front-end that keeps resolution, and a context module — all dilation, no pooling.

You know the tension: context versus resolution. Here's the map the rest of the page zooms into — the whole system as one left-to-right pipeline, before we magnify the dilation trick at its heart.

The system has two halves. First a front-end: the authors take VGG-16 (a standard 16-layer classification net) and surgically remove its last two pooling/striding layers, so the feature map stops shrinking and stays at a roomy 64×64 grid with C = 21 channels (one per VOC class). Then a context module: a short stack of dilated convolutions — no pooling at all — that widens context while every layer keeps that same 64×64 size.

image FRONT-END VGG-16, pooling layers removed → 64² × 21 CONTEXT MODULE 1 1 2 4 8 16 1 1×1 every layer stays 64² — dilation grows, size doesn't labels dilation tag below each layer
image → front-end (VGG-16 with its last two downsamples removed) → context module (a dilated stack) → dense labels. Reading left to right, the grid never shrinks: it is 64×64 from the front-end onward. The amber number under each context layer is its dilation.
Key property — remember this one

Unlike a classifier — where the grid shrinks layer by layer — here the feature map is the same size all the way through. Context grows not by shrinking the image, but by spacing out where each filter looks. That spacing is the one idea this whole page is about.

Front-end: adapted VGG-16, last two pooling/striding layers removed, intermediate convolutions dilated by 2 then 4 to compensate · output 64×64×21 · the context module's 8 layers (next, Step 7) all operate at that 64×64 resolution.

PART B

The dilation trick, up close

A dilated conv by hand · receptive field doubling · the context module · identity init

Step 4 · A dilated convolution, by hand

Same three weights — just spaced apart. The taps step over gaps, and the reach grows.

You have the floor plan; now we zoom all the way into the one operator that makes it work. The trick is almost embarrassingly small, so do one by hand before any formula.

An ordinary convolution takes a tiny set of learned numbers — a kernel (or filter) — and at each position multiplies it against the pixels directly under it, summing the products into one output number. A dilated convolution (also called atrous, French for “with holes”) changes one thing: it spaces the kernel's taps apart by a dilation factor l. With l = 1 it is an ordinary conv; with l = 2 each tap skips one pixel; with l = 4, three pixels — but the kernel still has the same number of weights.

Our toy: a row of pixels with a bright/dark step (value 9 on the left, 0 on the right) and a 3-tap edge kernel [+1, 0, −1]. Drag the dilation and watch the taps spread — and watch how wide a span the one output cell now depends on.

Drag the dilation l — watch the three taps step over gaps

1
9 one output cell

Three weights, ever-wider reach. At l = 1 the taps touch 3 adjacent pixels — span 3. At l = 2 they touch pixels 1 apart — span 5. At l = 4 — span 9. The output value barely changes (the edge is still an edge), but the cell now sees a much wider context. No pooling, no lost resolution, no extra weights.

The one-line definition, and the dot product at l = 2
(F l k)(p)  =  Σ s + l·t = p   F(s) k(t)

The taps of k are read at stride l in the input F. Set l = 1 and it is the ordinary convolution.

centre on a pixel astride the edge, l = 2 → taps land on values [9, 9, 0]
= (+1)·9 + 0·9 + (−1)·0
= 9 + 0 − 0 = 9 · still positive → “edge here”.

Whatever the dilation, this output cell fires on a bright→dark step and stays near zero deep inside a flat region — the detector is unchanged. All that grew is the distance between the pixels it consults.

Key property — remember this one

A dilated kernel has the same parameter count as a normal one — here, three weights — no matter how large l is. Only its reach changes. That is what makes widening context essentially free.

“Atrous” / “à trous” (with holes) is the older signal-processing name — the same operator appears in the algorithme à trous for wavelet decomposition. Next: what happens when you stack these and double the dilation each layer.

Step 5 · Stack them — and the reach doubles

Double the dilation each layer, and the receptive field jumps 3 → 7 → 15 → 31 → 63.

You saw one dilated layer widen its reach. The real power shows up when you stack them and let the dilation double at every step. This is the number you should be able to recompute from memory.

Start with feature map F₀. Apply a 3×3 dilated conv with dilation 1 to get F₁; then dilation 2 for F₂; then 4, 8, 16… The receptive field — the square of input pixels one output cell can depend on — follows a clean rule: after layer i+1 it is (2i+2−1) on a side. Each new layer doubles the previous width and adds one. Two layers cover 7, four layers cover 31, six cover 127 — all while the kernel adds just 9 weights per layer.

F₄ · 31×31 F₃ · 15 F₂ · 7 3 layer · dilation · receptive field · params F₁ · l=1 · 3×3 · 9 F₂ · l=2 · 7×7 · 18 F₃ · l=4 · 15×15 · 27 F₄ · l=8 · 31×31 · 36 F₅ · l=16 · 63×63 · 45 field: ×2 each layer (exponential) params: +9 each layer (linear)
Section 2 of the paper, drawn to scale. The squares share one centre pixel and roughly double in side: 3, 7, 15, 31, 63. The field grows exponentially; the parameter count grows only linearly (+9 a layer). That gap is the entire point.
Key property — the formula as relief

After i+1 dilated layers, the receptive field is (2i+2−1) wide. You just computed it by hand — 3, 7, 15, 31, 63 — so the formula is only a summary: exponential reach for linear cost.

To cover a whole image region you need only a logarithmic number of layers — six dilated layers already reach 127×127 — and every one of those layers runs at full input resolution. No pooling appears anywhere in this stack.

Step 6 · Why not just stack normal convs?

A plain 3×3 stack grows its reach by 2 a layer; the dilated stack doubles it.

You might object: a stack of ordinary 3×3 convs also grows its receptive field. True — but only by addition, not doubling. That difference is the whole reason dilation exists.

Each ordinary 3×3 conv widens the field by exactly 2 (one pixel on each side): 3, 5, 7, 9, 11… To reach a 63-wide field you would need 31 plain layers — and 31 layers of weights and compute. The dilated stack reaches the same 63 in just 5 layers, because each one doubles the reach. Same kernels, a fraction of the depth:

field layers → 63-wide receptive field dilated · 5 layers plain 3×3 · 31 layers same 63-wide field — about 6× fewer layers with dilation
Plain 3×3 convs add 2 to the field per layer (linear, amber); dilated convs double it (exponential, indigo). Reaching a 63-wide field costs 31 plain layers versus 5 dilated ones — and, critically, the plain route is what you'd be forced into once you ban pooling.
Why this matters for dense prediction

Banning pooling (to keep resolution) would normally force a very deep, slow stack to recover wide context. Dilation gets the same context in a logarithmic number of layers — that is what makes full-resolution context affordable.

Step 7 · The context module, layer by layer

Eight layers, dilations 1·1·2·4·8·16·1·1 — a 67×67 view, all at one resolution.

You can now grow a receptive field exponentially. The paper packages that into a small, drop-in block — the context module — and here is its exact recipe from Table 1.

The module is 8 convolution layers. The first seven are 3×3; the last is a 1×1 that just mixes channels. Their dilations run 1, 1, 2, 4, 8, 16, 1, 1 — ramping up to gather wide context, then settling back down. Read the receptive-field column: it climbs to 67×67, and the feature map is the same size at every single layer. In the Basic form every layer keeps C channels (here 21); a Large form widens the middle layers (up to 32C) for more capacity.

layerconvdilationreceptive fieldBasic ch.Large ch.
13×313×3C2C
23×315×5C2C
33×329×9C4C
43×3417×17C8C
53×3833×33C16C
63×31665×65C32C
73×3167×67C32C
81×1167×67CC
Table 1, reproduced. C = number of classes (21 for VOC). The dilation ramps 1·1·2·4·8·16 then drops back to 1·1; the receptive field reaches 67×67 at full resolution. The 1×1 final layer just recombines channels into a per-pixel class score.
Drop-in by design

The context module takes a feature map and returns one of the same size and channel count — so it plugs onto the end of an existing segmentation front-end without retraining the whole pipeline. It is a module, not a new architecture.

The two extra dilation-1 layers (1, 2 and 7) and the 1×1 head let the module blend the wide-context signal back with local detail before emitting per-pixel scores. Next: why this stack refused to train until the authors found the right way to initialize it.

Step 8 · Identity init — the trick that made it train

Start every layer as a “do nothing” copier, then let it learn what to add.

The module looks elegant on paper. But the authors hit a wall: with standard random weights, the context module simply would not learn. The fix is a small, clever choice of starting weights.

Their solution: identity initialization. Set each layer's kernel so that, at the start of training, it copies its input straight through — formally k(t, a) = 1 if t = 0 and a = b, else 0 (the centre tap is an identity, everything else zero). So a freshly-built module is a transparent pass-through, and gradient descent only has to learn the useful context to add on top — never to reconstruct the signal from noise.

at init · identity 0 0 0 0 1 0 0 0 0 input copied through, untouched — train —→ after training · learned context .1 −.1 .0 −.1 .9 .1 .0 .1 −.1 centre near 1, neighbours nudged
Identity initialization (Section 3). Left: at init the centre tap is 1 and all others 0, so the layer is a pass-through. Right: training only has to learn the small off-centre weights — the context to add — rather than rebuild the whole signal. (Right-side values are illustrative.)
Why it was necessary

With standard random init the module failed to improve accuracy at all. Starting from the identity guarantees the front-end's signal survives layer one, so backprop can discover contextual corrections instead of fighting through random noise — a recurring idea you'll also recognize in residual connections.

The paper notes standard initializations “did not lead to accuracy improvements”; the identity scheme is what let the context module learn. With variable channel counts the same idea is generalized so each output channel starts as a copy of one input channel.

PART C

Did it work, and what it left behind

The VOC numbers · the simpler front-end · the lasting idea

Step 9 · Did it work?

Bolt the context module on, and segmentation accuracy climbs — across every class.

You've built the operator, the module, and the trick that trains it. Now the only question that matters: on real images, does adding dilated context actually help? It does.

The score is mean IoU — mean intersection-over-union, the overlap between predicted and true regions averaged over the 20 Pascal VOC classes, where higher is better. Starting from the front-end alone, simply appending the context module lifts accuracy, and the Large form lifts it more. The gains hold on every category, not just on average.

Report card · Pascal VOC 2012 mean IoU (longer = better)

Front-end + Context (Large)

72.1%

Front-end + Context (Basic)

71.3%

Front-end alone (no context)

69.8%

DeepLab · prior work

62.1%

FCN-8s · prior work

62.2%

50%mean IoU →~80%

69.8 → 72.1 the context module (Large) adds +2.3 mIoU on the VOC validation set, on top of the front-end.
67.6% the simplified front-end alone (test set) already beats FCN-8s (62.2%) and DeepLab (62.1%).
74.7% / 75.3% the full system with a CRF / CRF-RNN on the VOC 2012 test set.
every class the context module raised accuracy on each of the 20 categories, not just the mean.

Validation-set mIoU from Table 3 (front-end 69.8, +Basic 71.3, +Large 72.1); test-set comparisons from the paper's tables (FCN-8s 62.2, DeepLab 62.1, front-end 67.6; full system 74.7 / 75.3 with CRF / CRF-RNN). Bars longer = better.

Step 10 · A bonus finding — strip the baggage

A net built for classification carries dead weight; remove it and dense prediction improves.

The context module was the headline. But the paper has a second, quieter result that has aged just as well — about the front-end you graft it onto.

Everyone was adapting classification nets (like VGG-16) for segmentation by bolting decoders onto them. The authors noticed that those nets carry vestigial structure — pooling and padding choices that exist only to serve whole-image classification, and that actively hurt dense prediction. So they simplified: drop the last two downsamples (Step 3), dilate to compensate, and remove the leftover machinery.

The payoff is striking. Their stripped-down front-end, with no context module and no CRF post-processing, already scores 67.6% mean IoU on the VOC test set — beating the more complicated FCN-8s (62.2%) and DeepLab (62.1%) systems it replaced. Simpler and better.

Before

Classification net, adapted

VGG-16 with all its pooling, plus decoders / upsampling bolted on. Carries structure meant for one-label-per-image.

Strip

Remove the baggage

Drop the last two pooling/striding layers; dilate the convolutions that followed by 2 then 4 to keep their coverage. No upsampling needed.

After

67.6% mean IoU

The simpler front-end alone beats the prior state of the art — before the context module even attaches.

The deeper lesson

Don't just inherit a classification backbone and patch over its quirks — question what dense prediction actually needs. Often the right move is to remove, not add. This mindset shaped a generation of segmentation architectures.

The simplified front-end is trained on Pascal VOC 2012, augmented with Microsoft COCO images (any COCO image containing a VOC class, non-VOC objects treated as background). It is the base everything else in the paper builds on.

Step 11 · Now you can explain it

Five questions. If each comes easily, you've genuinely got this paper.

You've followed one tiny operator from definition (Step 4) to a doubling reach (Steps 5–6), into a drop-in module (Step 7), trained by identity init (Step 8), validated on real images (Steps 9–10). Time to test yourself.

Now you can explain it. Answer each out loud before opening it. If all five come easily, you can re-teach dilated convolutions to someone else — which was the whole point.

What exactly does a dilated convolution change?

It spaces the kernel's taps apart by a dilation factor l: the same few weights are read at stride l in the input. l = 1 is an ordinary conv; l = 2 skips one pixel per tap; l = 4 skips three. The weight count never changes — only the reach.

Why does the receptive field grow exponentially?

Stack 3×3 dilated layers with doubling dilation (1, 2, 4, 8, 16). Each layer doubles the previous width and adds one, so the field goes 3, 7, 15, 31, 63 — formally (2^(i+2)−1) wide after i+1 layers. Parameters grow only +9 a layer (linear). Exponential reach, linear cost.

How is this different from pooling?

Pooling buys context by downsampling — it shrinks the feature map and discards location, so you must upsample later and lose sharp boundaries. Dilation buys the same context while every layer keeps the input's full resolution. No size is ever lost.

What is the context module, and why identity init?

It's 8 dilated conv layers (dilations 1·1·2·4·8·16·1·1) reaching a 67×67 field at one resolution — a drop-in block. With random init it failed to learn, so each layer is initialized as the identity (centre tap 1, rest 0), a pass-through that backprop only has to add useful context to.

Did it actually improve accuracy?

Yes. On Pascal VOC 2012 the context module lifts mean IoU from 69.8 to 72.1 (Large), and on every one of the 20 classes. Even the simplified front-end alone (67.6%) beat FCN-8s and DeepLab; the full system reaches 74.7–75.3% with a CRF.

What happened next — the dilation idea spread far beyond this paper. It became standard in semantic segmentation (DeepLab's “atrous” convolutions and atrous spatial pyramid pooling), powered raw-audio generation (WaveNet's dilated causal convolutions), and remains a go-to tool whenever a model must see wide context without throwing away resolution.

“Dilated convolutions support exponential expansion of the receptive field without loss of resolution or coverage.” — the abstract, Yu & Koltun, ICLR 2016
The profound impact

A mechanism, not an architecture — which is why it kept showing up.

Dilated convolution wasn't a model you'd download; it was an operator you could drop into almost anything. Because it widens context without spending resolution, it quietly became standard equipment wherever a network must reason across a wide field yet output fine detail.

segmentation · DeepLab

Atrous & ASPP

The DeepLab family built “atrous convolutions” and atrous spatial pyramid pooling on this idea, dominating semantic segmentation benchmarks for years.

audio · WaveNet

Dilated causal convs

WaveNet stacked dilated causal convolutions to span thousands of audio samples, generating raw, natural-sounding speech and music.

everywhere · default part

Wide context, kept sharp

From depth estimation to detection, dilation became a routine knob for enlarging receptive fields cheaply — a quiet primitive in the modern toolkit.

One small change to where a filter looks — reused across vision and audio ever since.