MultiScaleResolution
MultiScaleResolution(sizes: tuple[int, ...] = tuple(range(320, 609, 32)), period: int = 10, stride: int = 32)The input-size schedule of YOLOv2's multi-scale training (§2).
The paper does not train at one resolution: "every 10 batches our network randomly chooses a new image dimension size ... from the multiples of 32: {320, 352, ..., 608}". The same weights then have to work from 320x320 to 608x608, which is what lets one trained model trade accuracy for speed at test time by simply being fed a smaller image.
Nothing about the network needs to change to support this — it is fully convolutional, and the anchors are expressed relative to a 32-pixel cell — so all that was missing is the schedule itself. This class is it: a training loop asks for the size before each batch and resizes its own images.
Args:
sizes: Candidate side lengths. Defaults to the paper's
range(320, 609, 32). Every entry must be a positive
multiple of stride, or the stride-32 feature map — and the
passthrough layer that halves it again — would not come out
integral.
period: How many batches to hold a size before redrawing (paper: 10).
stride: The network's total downsampling factor, used to validate
sizes.
Notes
The YOLOv2 loss used to make this schedule unusable on its own: the
objectness term is evaluated at every cell and was summed raw, so the
total tracked the cell count almost exactly — 69.9 at 320x320 against
239.3 at 608x608 on a fixed batch, a 3.4x swing that no single learning
rate can serve. _compute_loss now divides the objectness pair by
the cell count and the localisation terms by the positive count, which
leaves the obj/noobj ratio lambda_noobj sets untouched and takes the
grid out of the total. Measured as loss / cells, the spread across
the schedule went from 1.05x — near-constant, which is what proportional
means — to 5.9x.
Examples
>>> import lucid
>>> import lucid.nn.functional as F
>>> from lucid.models.vision.yolo import MultiScaleResolution
>>> schedule = MultiScaleResolution()
>>> images = lucid.randn(2, 3, 416, 416)
>>> for batch in range(3):
... side = schedule.size_for(batch)
... batch_images = F.interpolate(
... images, size=(side, side), mode="bilinear", align_corners=False
... )
... out = model(batch_images, targets=targets)
>>> schedule.size_for(0) == schedule.size_for(9) # same 10-batch window
True