Code Architecture: SSD Notebook
Overview
The notebook builds an SSD-style single-shot detector from scratch in PyTorch and trains it on a synthetic multi-scale shapes dataset. Everything specific to SSD is implemented by hand: default box generation, the matching strategy, offset encoding and decoding, the MultiBox loss with hard negative mining, and the detection head layout. The only library pieces used are torchvision.ops.nms and box_iou. After training, it evaluates VOC-style mAP@0.5 and recall by object size. It then visualizes which feature map each detection came from, which is the central claim of the paper. Runtime is about 18 minutes on a Kaggle T4 (16.4 min of training); the validated run reaches mAP@0.5 = 0.959.
Section-by-Section Breakdown
1. Imports & Configuration
- Seeds, device, and all hyperparameters in one place.
FMAP_SIZES = [32, 16, 8, 4, 2, 1]are the 6 source maps, like SSD300’s 38/19/10/5/3/1.ASPECT_RATIOS = [[2], [2,3], [2,3], [2,3], [2], [2]]gives 4 or 6 boxes per location, the same pattern as SSD300.S_FIRST=0.1, S_MIN=0.2, S_MAX=0.9are the scales. The first map uses 0.1, as SSD300 does forconv4_3.- Training: 50 epochs, batch 32, AdamW at 1e-3 with OneCycle, IoU match threshold 0.5, and a 3:1 negative:positive ratio.
2. Synthetic Multi-Scale Shapes Dataset
make_sample(rng)draws a noisy background, random distractor lines, and 1–6 non-overlapping shapes. Classes are 1 = circle, 2 = rectangle (random aspect ratio) and 3 = triangle, with random colors that don’t depend on class.- Object size is log-uniform from 16 to 170 px, so each feature-map scale has work to do.
build_split(n, seed)returns auint8tensor of shape(n, 3, 256, 256)plus per-image lists of normalizedxyxyboxes and labels. 4,000 train and 500 val images are generated in memory.
3. Multi-Scale Default Boxes
compute_scales()implementss_k = s_min + (s_max − s_min)(k−1)/(m−2)over the maps after the first.make_default_boxes()loops over map, then row, then column. For each location it emits the square box ats_k, the extra square box at√(s_k s_{k+1}), and an(s√a, s/√a)/(s/√a, s√a)pair for each aspect ratio. It returns(6132, 4)boxes incxcywhformat and a(6132,)tensor recording each box’s source map.- An assertion checks that the total equals
Σ f²·A. The order matters because it must match how the heads are flattened. - A plot shows the default boxes at one central cell of each map.
4. Matching Strategy & Box Encoding
encode/decodeimplement the paper’s offset parameterization with variances (0.1, 0.2), and a round-trip test checks them.match(gt_boxes, gt_labels, ...)follows the paper:- Each ground truth takes its best-IoU default box. That box’s IoU is forced to 2.0 so it always counts as a positive.
- Every other default box with IoU > 0.5 to some ground truth becomes a positive for that ground truth. Everything else is background (label 0).
- It returns encoded loc targets
(N, 4)and class targets(N,). - Diagnostics show the average number of positives per object and the number of positives per feature map. A plot shows matched default boxes colored by feature map.
5. SSD Model
conv_bnis a Conv → BatchNorm → ReLU block. BatchNorm is there because training starts from scratch, with no pretrained VGG.L2Normis the paper’sconv4_3normalization with a learnable per-channel scale (initialized to 20).SSDis built from these parts:base: 8 conv layers and 3 max-pools, taking 256×256 down to(128, 32, 32). This map is L2-normalized and becomes source map 0.fc7: a max-pool and two convs, giving(256, 16, 16).extras: 4 blocks of 1×1 reduce plus 3×3 stride-2 conv, giving 8×8, 4×4, 2×2 and 1×1 maps.loc_heads/conf_heads: a 3×3 conv per source map withA·4andA·Coutput channels.
forwardpermutes each head output to(B, H, W, A·k)and reshapes to(B, H·W·A, k), then concatenates over maps toloc (B, 6132, 4)andconf (B, 6132, 4).- Assertions check the source map sizes and that the number of predictions equals the number of default boxes. The model has about 1.9M parameters.
6. MultiBox Loss with Hard Negative Mining
multibox_loss(loc_p, conf_p, loc_t, labels_t)computes:- Smooth L1 over positive boxes only.
- Per-box cross-entropy. Positive losses are zeroed in a detached copy, and negatives are ranked with a double
sort. Each image keeps its top3 × num_posnegatives. - Confidence loss summed over positives and the selected negatives.
- Both terms divided by the total number of positives
N(α = 1).
7. Training
- Images stay on the GPU as
uint8and are normalized per batch. - Random horizontal flips are applied to images and boxes together, then each image is matched against the default boxes on the GPU.
- Mixed precision (
autocast+GradScaler), with the loss computed in fp32. - Train and val loc/conf losses are logged per epoch and plotted afterwards.
8. Inference
detect(model, x)applies softmax, decodes all default boxes, and keeps boxes with score > 0.05. It runs per-class NMS at IoU 0.45 and keeps the top 100 per image.- It returns boxes, scores, labels and the source feature-map index of every detection.
9. Evaluation
average_precisionuses VOC-style greedy matching at IoU ≥ 0.5 and computes AP as the area under the precision-recall curve, using all points. Results are per-class AP, mAP, and precision-recall curves.- Recall at score ≥ 0.5 is reported for small (< 32 px), medium (32–96 px) and large (> 96 px) objects.
10. Detections Across Feature-Map Scales
- Validation images with detections colored by source map.
- A bar chart of detection counts per map, and a box plot of detected box size per map against the default box scale
s_k. - Per-map objectness heatmaps showing
max over anchors of (1 − P(background))at each cell.
11. Summary
- Prints the configuration, final losses, per-class AP, mAP and recall by size.
Key Functions/Classes
| Name | Role |
|---|---|
make_sample, build_split | Synthetic multi-scale shapes dataset |
compute_scales, make_default_boxes | Paper’s default box scales and aspect ratios |
encode, decode | Offset parameterization with variances |
match | Two-step matching strategy (best default box per ground truth, then IoU > 0.5) |
L2Norm | conv4_3-style normalization with learnable scale |
SSD | Backbone, extra layers, and 6 loc/conf heads |
multibox_loss | Smooth L1 + softmax CE with 3:1 hard negative mining |
detect | Decode + per-class NMS, tracking the source feature map |
average_precision | VOC-style AP@0.5 |
Data Flow
uint8 images (B,3,256,256) ──normalize──▶ SSD.base ─▶ (B,128,32,32) ─L2Norm─▶ source 0
└▶ fc7 ─▶ (B,256,16,16) ─▶ source 1
└▶ extras ─▶ 8x8, 4x4, 2x2, 1x1 ─▶ sources 2..5
each source ─3x3 conv─▶ loc (B,H,W,A*4) / conf (B,H,W,A*C) ─flatten+concat─▶ loc (B,6132,4), conf (B,6132,4)
GT boxes ──match()──▶ loc_t (B,6132,4), labels_t (B,6132) ──multibox_loss──▶ loss
inference: softmax + decode(loc, default_boxes) ─▶ threshold ─▶ per-class NMS ─▶ top-100 detections
Deliberate Simplifications vs Full Paper
- Data: synthetic shapes instead of PASCAL VOC / COCO, so training is fast, needs no download, and controls the object size distribution.
- Backbone: a small VGG-style CNN with BatchNorm trained from scratch, instead of ImageNet-pretrained VGG-16 with dilated
fc6. - Resolution: a 256×256 input with maps 32/16/8/4/2/1, instead of 300×300 with 38/19/10/5/3/1. There are 6,132 default boxes instead of 8,732.
- Optimizer: AdamW + OneCycle + AMP for 50 epochs, instead of SGD with momentum and step LR over about 120k iterations.
- Augmentation: horizontal flips only. The paper’s random IoU-constrained crops and zoom-out expansion are not implemented, even though the paper shows they matter a lot on real data.
- Inference: a confidence threshold of 0.05 and top-100 per image, instead of 0.01 and top-200.