Pyramids Detect Every Scale Once
A feature pyramid reuses the backbone once and fuses strong low-resolution semantics into high-resolution maps, so one detector reads tiny and huge objects together.
Why Does This Exist?
A backbone built for classification outputs one low-resolution summary, which answers what is present but not where each size lives. A 12-pixel bird vanishes in a stride-32 map while a 300-pixel bus overflows a stride-8 map, so no single level serves both. The old fix ran the whole backbone on three resized copies of the image, paying roughly three times the compute.
A feature pyramid (Lin et al., 2017) runs the backbone once and builds the scales inside the network. A top-down path upsamples semantically strong maps and adds them into high-resolution lateral maps, giving every level both detail and meaning. The same Faster R-CNN or RetinaNet heads then read small objects off fine levels and large objects off coarse levels in that single pass.
Think of It Like This
An architect's drawing set
An architect never draws a door handle on the site plan or the whole building on the hinge detail sheet. The site plan carries context at coarse scale and the detail sheet carries edges at fine scale, and red markup notes flow from the site plan down onto each detail sheet so every page stays oriented.
The bottom-up backbone draws the sheets, the top-down path carries the markup downward, and the lateral 1x1 connections are the registration marks aligning them. Where it stops: markup cannot invent a door nobody measured, just as upsampling cannot invent a 12-pixel bird the fine map never resolved.
How It Actually Works
The bottom-up backbone yields , , at strides 8, 16, and 32. Each lateral 1x1 convolution projects its map to 256 channels, the top map sets , and each lower level adds its lateral map to the 2x upsampled parent: , then a 3x3 convolution smooths aliasing. Heads attach per level, so anchors on stay small and anchors on grow large, and each object is assigned to the level whose resolution fits it.
Worked example
Take an image. Grids are for , for , and for . A 12-pixel bird spans cells on but only cells on , so it is readable on and a smudge on . A 300-pixel bus spans cells on , a comfortable window. The lateral maps with weights, and one backbone pass replaces three image-pyramid passes.
Code
The point of this snippet is the grid arithmetic, not a network: pure Python recomputes the level sizes, the cell spans, and the lateral weight count.
size = 800for stride in (8, 16, 32): print(size // stride)# -> 100# -> 50# -> 25
print(round(12 / 8, 2), round(12 / 32, 2), round(300 / 32, 2))# -> 1.5 0.38 9.38
print(2048 * 256)# -> 524288offers a 100-wide grid where the bird covers 1.5 cells, offers a 25-wide grid where the bus covers 9.38 cells, and the lateral projection costs 524288 weights.
Watch Out For
Attaching heavy heads to every level for free
The symptom is inference that slows sharply after adding the pyramid, because five heads each score thousands of anchors. Fix it by sharing lightweight heads across levels and tuning per-level anchor counts before blaming the backbone.
Expecting upsampling to rescue invisible objects
The symptom is small-object AP that barely moves after adding the pyramid on a low-resolution input. Fix it by checking that actually resolves the object: a 12-pixel target on stride 8 is marginal, and no top-down semantics can invent detail the fine map never captured.
The Quick Version
- One backbone pass yields strides 8, 16, and 32 with grids 100, 50, and 25 on an 800-pixel image.
- Lateral 1x1 maps project each level to 256 channels, costing weights at .
- Top-down 2x upsampling plus addition gives every level both detail and semantics.
- The 12-pixel bird reads on at 1.5 cells, the 300-pixel bus reads on at 9.38 cells.
- Heads attach per level, replacing three backbone passes with one.