Models / Dilated CNN
code
layersReceptive-Field Showcase · 02

Dilated CNN

A 98,730-parameter CIFAR-10 classifier that reaches a 65×6565\times65 receptive field, more than double the 32×3232\times32 input, without a single pooling or stride-2 layer, by stacking dilation rates 2,4,8,162, 4, 8, 16 on top of a depthwise-separable block.

analytics88.4% Acc2.4M paramsCIFAR-10
Concepts in play
play_circleC1 -> C2 (depthwise-separable) -> C3-C4 (dilation 2/4/8/16) -> GAP head
  1. The 32×32×332\times32\times3 input enters C1, a plain 3×33\times3 convolution. Nothing shrinks, every block in this network outputs the same spatial size it received.
  2. C2 is the depthwise-separable block: a 3×33\times3 spatial filter applied per channel, then a 1×11\times1 pointwise convolution that mixes channels. Watch the parameter counter barely move while the receptive-field counter keeps climbing.
  3. C3 and C4 run dilated convolutions at rates 2,4,8,162, 4, 8, 16: the kernel taps spread apart and the dashed footprint balloons, but the dot-count stays at 9 per kernel. The receptive-field counter jumps from 9 to 65 across these two blocks.
  4. Global average pooling collapses the final 32×3232\times32 feature map to raw logits, no softmax baked in. Takeaway: every block preserved resolution, and the receptive field still grew past the input size, all under 100K parameters.
scienceOpen Workspace

This model was built against a specific constraint: reach a receptive field greater than 44 on a 32×3232\times32 input, with no max-pooling and no stride-2 convolution anywhere. Pooling and stride are the usual way to grow the receptive field fast (the receptive-field page's jj, the jump between adjacent output positions, doubles with every stride-2 step), but both spend resolution to buy reach. Three stride-2 layers on a 32×3232\times32 input already shrinks it to 4×44\times4, leaving almost nothing for later layers to refine. The alternative this model bets on is dilation: widen what each kernel can see without touching the feature-map size or the weight count at all.

The result is a four-block stack, C1 through C4, that never changes spatial resolution, every feature map in the network is 32×3232\times32, yet the receptive field grows from 3 pixels after the first layer to 65 pixels by the last. That is more than double the input, with margin to spare.

Blockkdk_effr (in)r (out)
C1 - ConvBlock31313
C2 - Depthwise-separable31335
C3 - Dilated, d=232559
C3 - Dilated, d=4349917
C4 - Dilated, d=838171733
C4 - Dilated, d=16316333365
Real per-layer numbers from the Session-7 build. Every layer is stride 1, so j=1j=1 throughout and the entire receptive-field story is the keffk_{\text{eff}} column.

The mechanism behind every row is the same recurrence from the receptive-field page, with dilation folded in as a change to the effective kernel size rather than a new formula. A dilated kernel with rate dd spaces its kk taps dd pixels apart, so it spans the same area as an ordinary kernel of size keffk_{\text{eff}} while keeping the same k2k^2 weights:

keff=k+(k−1)(d−1),rout=rin+(keff−1) jin,jout=jink_{\text{eff}} = k + (k-1)(d-1), \qquad r_{\text{out}} = r_{\text{in}} + (k_{\text{eff}} - 1)\, j_{\text{in}}, \qquad j_{\text{out}} = j_{\text{in}}

With j=1j=1 everywhere in this network, rr simply accumulates (keff−1)(k_{\text{eff}}-1) at each layer. The C3/C4 dilation rates 2,4,8,162, 4, 8, 16 turn a 3×33\times3 kernel into effective footprints of 5,9,17,335, 9, 17, 33.

Run the numbers for the C4 d=16 layer: keff=3+(3−1)(16−1)=3+30=33k_{\text{eff}} = 3 + (3-1)(16-1) = 3 + 30 = 33. Plugging into the recurrence, rout=33+(33−1)⋅1=65r_{\text{out}} = 33 + (33-1)\cdot 1 = 65. A 3×33\times3 kernel, still only 9 weights, behaves, for receptive-field purposes, like a 33×3333\times33 kernel. That single layer alone takes the receptive field from 33 to 65, doubling it without adding a single parameter.

The escalating rates are deliberate, not arbitrary. Stack dilated layers at the same rate and their sampling gaps line up into a checkerboard that nothing ever reads, a failure mode called gridding. Doubling the rate at every stage (2→4→8→162 \to 4 \to 8 \to 16) means each layer's gaps fall on positions an earlier layer's taps already covered, so the union of everything sampled stays dense even though no single layer is.

view_in_arConcept refresherReceptive field 65, zero poolingThe full worked case study for this exact model: the recurrence, the gridding fix, and a chart comparing the dilated stack against a plain-equivalent one that only reaches RF 13.Revisitarrow_forward

The chart below is this table drawn as a ladder. Each rung is one block; the bar length is that block's receptive field relative to the 32×3232\times32 input (the dashed line). Notice the C1 and C2 rungs are short and the same color, both are plain 3×33\times3 (d=1d=1), while the C3/C4 rungs are dilated and cross the input line by the fifth block.

32×32 inputConvBlock 1d=1, k_eff=3 → r=3Depthwise blockd=1, k_eff=3 → r=5Dilated d=2d=2, k_eff=5 → r=9Dilated d=4d=4, k_eff=9 → r=17Dilated d=8d=8, k_eff=17 → r=33Dilated d=16d=16, k_eff=33 → r=65plain 3×3 (d=1)dilated (d>1)
Six layers, RF 3 -> 5 -> 9 -> 17 -> 33 -> 65. The receptive field overtakes the 32×3232\times32 input between the C3 d=4 and C4 d=8 rungs, and the d=16 rung alone adds 32 more pixels of reach.

C2 is where the parameter budget gets paid down. Splitting a 3×33\times3 convolution into a depthwise pass (one filter per channel) plus a 1×11\times1 pointwise pass (channel mixing only) cuts the weight count roughly k2k^2-fold for the same channel widths, the convolution page works the 64→6464{\to}64 case out fully: 576+4,096=4,672576 + 4{,}096 = 4{,}672 weights instead of 36,86436{,}864, about an 8x reduction. Crucially, this factorisation costs nothing in the receptive-field column: the depthwise pass contributes +2+2 to rr exactly like an ordinary 3×33\times3 kernel (keff=3k_{\text{eff}}=3), while the pointwise 1×11\times1 pass has keff=1k_{\text{eff}}=1 and adds zero. C2's row in the table above (r:3→5r: 3 \to 5) is identical to what a plain 3×33\times3 layer would produce, the parameter saving is free on the receptive-field side.

That saving is what keeps the whole six-layer stack (C1's plain conv, C2's depthwise-separable block, and four dilated convolutions in C3/C4) under the 200K-parameter assignment budget. The full network totals 98,730 parameters, under half that budget, while reaching a receptive field more than double the input. Spending the budget on dilation (which is free in parameters) and a factorised block (which is cheap in parameters) rather than on wider plain convolutions is the core design bet of this model.

filter_center_focusConcept refresherFactorising the convolution volumeThe 64→6464{\to}64 depthwise-separable worked example: 4,6724{,}672 weights instead of 36,86436{,}864, the same trick C2 in this model uses to stay under budget.Revisitarrow_forward
PropertyValueNote
Parameters98,730under the 200K budget
Receptive field65x65vs. 32×3232\times32 input
Pooling / stride-2 layers0resolution preserved end to end
Dilation rates used2, 4, 8, 16escalating to avoid gridding
Output headGlobal average pool -> raw logitsno softmax baked in
Test accuracy88.24%CIFAR-10 test set
The full scorecard. Every row trades against the others: dilation buys reach without spending parameters or resolution, and the depthwise-separable block buys back the parameters dilation's extra layers would otherwise cost.

Why no pooling at all? On a 32×3232\times32 input, every pooling or stride-2 layer halves the feature-map size, two of them and you are down to 8×88\times8, three and you are at 4×44\times4, with little spatial structure left for the dilated blocks to work on. This model's bet is that dilation alone can supply all the receptive-field growth a small image needs, so resolution is preserved for the entire forward pass and the final 32×32×C32\times32\times C feature map is reduced only at the very end, by global average pooling into the 10 CIFAR-10 class logits.

Training used Albumentations for augmentation: horizontal flips, shift-scale-rotate, and coarse dropout, which matters here specifically because a network with this much spatial coverage per neuron benefits from seeing the same object shifted, rotated, and partially occluded. Every one of those transformations falls well within a single neuron's 65×6565\times65 field of view by the final layer.

Sources & further reading