AI Research Library

Convolution, Invariance and Equivariance

Rajesh Ranjan Mahapatra

A convolutional layer earns its value by moving a learned detector with the evidence; whether the final answer should move, stay fixed or recover absolute position is a separate architectural decision.

© 2026 Rajesh Ranjan Mahapatra. 34 min read

Convolution, Invariance and Equivariance

A convolutional layer earns its value by moving a learned detector with the evidence; whether the final answer should move, stay fixed or recover absolute position is a separate architectural decision.

TLDR

  1. Convolution does not make a classifier translation-invariant. It makes a local feature map translation-equivariant: move the input, and the detector response should move with it.
  2. The mechanism is shared local weights. One learned detector is applied at every grid position, which lowers parameter count and makes translated evidence look like the same feature at a different address.
  3. Invariance appears only when a later operation deliberately discards address, such as global pooling. Dense prediction, pose estimation and coordinate tasks usually need equivariant or location-aware outputs instead.
  4. Padding, cropping, stride, downsampling, nonlinearities and sensor sampling can break the ideal symmetry. Standard convolution also says nothing about rotation, scale, deformation, lighting or occlusion.
  5. Choose an architecture by writing a transformation contract first: name the input motion, state how the output should change, then test the whole orbit rather than one benchmark split.

The crack that moved six centimetres

A camera inspects circuit boards on a conveyor. One board carries a short crack near its left edge. The next board carries the same crack six centimetres to the right. Nothing about the crack's shape, texture or severity has changed. Only its address in the image has changed.

The inspection system produces two outputs. The first is a board-level disposition: accept, review or reject. For that output, the crack should have the same effect wherever it appears. The second is a pixel mask that guides a human examiner to the damaged region. For that output, moving the crack must move the answer. A board-level score that follows the crack across the image is wrong. A mask that stays fixed while the crack moves is also wrong.

This small case exposes the central confusion. Convolution does not decide that location is irrelevant. It supplies a detector whose response changes location in step with the input. A later readout may collapse those responses into a location-free decision, preserve their coordinates for dense prediction, or combine them with explicit position when location is causal. Calling all three behaviours “invariance” hides the mechanism that makes each one correct.

Figure 1. The detector travels with the crack, while the readout chooses what to keep A local detector is shown scanning two grids containing the same crack at different positions. The activation peak moves. A global readout stays constant, while a spatial mask moves. Same local evidence, different address translate input activation peak activation peak moves global decision: reject global decision: reject
Figure 1. The detector travels with the evidence; the readout chooses what survives. Authored conceptual figure. The grids and crack positions are illustrative. Translation equivariance is represented by the moving activation peak; invariant classification is represented by the unchanged board-level disposition.

A dense layer connected to fixed image coordinates can learn a different detector at every position. A convolutional layer makes a stronger bet: the local relation that signals a crack near the left edge is useful near the centre and near the right edge. The bet saves parameters and, more importantly, creates a particular response to translation. The feature does not vanish when the input moves. Its address changes.

The distinction matters because “recognise it anywhere” contains two operations. First, detect the same thing at many places. Second, decide whether those places matter to the requested output. Convolution mainly addresses the first. Pooling, spatial readout, coordinates, attention or a task-specific head address the second.

Part I

A detector that travels

Start with a small grey-scale image. At each grid location, place the same 3 by 3 stencil over the pixels, multiply corresponding entries and add the results. If the stencil resembles a short diagonal edge, locations containing that edge receive a large response. Deep-learning libraries usually implement cross-correlation rather than the mathematically flipped version of convolution, but learned kernels make that naming difference operationally minor. The essential feature is repeated use of one local operator.[1][2]

z(u) = (K ⋆ x)(u) = Σv∈Ω K(v) x(u + v)
Here x is the input field, u is an output location, Ω is the kernel's finite support, v indexes an offset inside that support, K is the shared kernel and z is the resulting feature map. Changing K changes what local relation is detected; changing u changes only where the same detector is applied.

Now define a translation by a displacement δ. The translated image is (Tδx)(u) = x(u - δ). Under the ideal conditions of an infinite or periodic grid, unit stride and a consistently defined boundary, applying the detector after moving the image equals moving the detector response after applying it:

CK(Tδx) = Tδ(CKx)
CK is correlation with the shared kernel. Increasing δ does not change which feature is sought. It changes where the response appears. This commuting relation is translation equivariance.

The equation is useful because it says exactly what the architecture promises. It does not say that the final prediction stays constant. It says that the representation transforms in a known way. If a later operator P takes the maximum or average over all spatial locations, then P(Tδz) = P(z) for translations that preserve the feature field. The composed classifier P ∘ CK can therefore be invariant even though the feature map is equivariant. Global average pooling was introduced as a classification readout in Network in Network, partly to replace position-specific fully connected layers.[3][4]

Figure 2. Equivariance is a commuting path; invariance is a collapsed path A commutative diagram shows an image and its translated version passing through the same convolution, producing translated feature maps. Global pooling maps both feature maps to the same scalar. Two paths, one required equality translate Tδ same kernel CK same kernel CK feature at u feature moves Tδ pool P pool P 7.4 7.4 invariant scalar
Figure 2. Equivariance is a commuting path; invariance is a collapsed path. Authored conceptual diagram. The value 7.4 is illustrative. The upper and lower paths should agree for the declared translation; pooling then removes spatial address.

Thought experiment: change only the requested output

Give two teams the same images, labels, training budget and convolutional trunk. Team A must answer “is a crack present?” Team B must return the crack's centre coordinate. Nothing about the visual evidence changes. Only the required output transformation changes.

Team A can aggregate across position because its target is unchanged by translation. Team B cannot. If Team B globally pools every feature map before predicting a coordinate, the architecture has destroyed the variable its loss needs. It may recover weak location cues from padding or background regularities, but those cues are accidents, not the intended mechanism. Adding coordinates or retaining a spatial map is not a concession that convolution failed. It is a deliberate change in the output contract.

f(ρin(g)x) = ρout(g)f(x)
g is a transformation such as a translation or rotation. ρin states how it acts on inputs; ρout states how the required output should act. Invariance is the special case where ρout(g) is the identity. Dense prediction uses a non-trivial output action because masks, keypoints or vector fields must move.[5]

This group-action form is more than notation. It prevents a common architecture error: asking whether a model is “invariant” without naming the transformation or the output. A system can be translation-invariant for class identity, translation-equivariant for segmentation, rotation-equivariant for an orientation field and explicitly sensitive to absolute position for a safety zone, all in one product.[6]

Part II

The argument in one passChart · flow
flowchart TD
  A["The crack that moved six<br/>centimetres"] --> B["A detector that travels"]
  B --> C["The assumptions inside a<br/>sliding kernel"]
  C --> D["Where the symmetry leaks"]
  D -. "re-test" .-> A
  classDef first fill:#0a1424,stroke:#D95545,stroke-width:2px,color:#e6f3ff
  classDef step fill:#0a1424,stroke:#3f7da0,stroke-width:1px,color:#e6f3ff
  class A first
  class B,C,D step
A structural route through Convolution, Invariance and Equivariance, from The crack that moved six centimetres to Choose the motion before the model. Reading use: follow the relation, then test it against the surrounding evidence and limits.

The assumptions inside a sliding kernel

Convolution is often introduced as a calculation, then justified after the fact by its success. The more useful view is to treat it as a bundle of assumptions. Those assumptions explain both the data efficiency of CNNs and the tasks on which their bias becomes awkward.

Locality: nearby values form reusable evidence

A small kernel says that useful low-level relations can be recognised from a bounded neighbourhood. Edges, corners, texture elements and short temporal motifs fit this assumption. A fully connected layer must separately learn how every distant input relates to every output. A local layer initially refuses most of those interactions.

Locality does not mean the network can never use global context. Stacking layers grows the receptive field. For L unit-stride layers with an odd kernel width k, the theoretical receptive-field width is RL = 1 + L(k - 1). Here RL is the side length of the input region that can affect one output unit. Increasing depth or kernel width enlarges that region. Stride and dilation enlarge it faster, but they also change sampling and resolution.

Theoretical reach is not the same as effective use. Gradients and learned weights may concentrate influence near the centre. A large receptive field also allows boundary effects to reach deep into an image. Kayhan and van Gemert showed that modern convolutional networks can exploit absolute position through padding-induced boundary cues, even far from the border.[7]

Weight sharing: the same relation matters at many addresses

The decisive constraint is not local connectivity alone. A locally connected network can use a separate 3 by 3 filter at every position. Convolution ties those parameters. One vertical-edge detector serves the entire field. This creates the translation symmetry of the parameters, which formal work connects directly to equivariance.[8]

Weight sharing converts examples across positions into evidence for the same parameters. A crack observed near the centre can improve the detector used near an edge. This is the main source of sample efficiency in the running case. It is also a restriction: the network cannot freely learn that the identical local patch means one thing in the sky and another on the road unless later context, boundary cues or explicit coordinates break the symmetry.

Stationarity: the sensor and task are sufficiently uniform

Applying the same kernel everywhere assumes that local statistics are comparable across the grid. Camera vignetting, lens distortion, spatially varying resolution, medical scanner geometry and map projections violate that assumption to different degrees. Sometimes calibration removes the variation. Sometimes the model needs coordinates, spatially varying filters or a geometry-aware operator. “Use convolution for images” is therefore too broad. The actual question is whether the relevant relation repeats under the transformation represented by parameter sharing.

Composition: larger structures can be assembled from local relations

Early convolutional systems, from Fukushima's neocognitron to LeNet-style document recognition, combined local receptive fields, shared feature extraction and spatial aggregation to tolerate changes in position.[9][10] The historical lesson is not that biological vision has been reproduced. It is that repeated local structure provides a powerful computational economy. Later layers can combine edges into parts and parts into objects without relearning each low-level relation at every address.

Figure 3. Shared locality expands into context without abandoning the travelling detector Nested receptive fields show a 3 by 3 local detector, then 5 by 5 and 7 by 7 effective receptive fields through stacked layers. Parallel coloured traces show the same kernels reused across positions. Local reuse grows into hierarchical context 3 × 3, layer 1 5 × 5, layer 2 7 × 7, layer 3 same parameters reused at each position then composed across depth
Figure 3. Shared locality expands into context without abandoning the travelling detector. Authored illustrative geometry. Receptive-field sizes assume three unit-stride 3 by 3 layers. The coloured trajectories represent tied parameters, not measured activations.

Published evidence

Group-equivariant CNNs generalise the weight-sharing idea beyond translations by sharing filters over rotations and reflections, and reported reduced sample complexity on tasks aligned with those symmetries.[11] Later work connected equivariance to broader parameter symmetries and extended convolution-like operators to Lie groups and non-image data.[8][12] These results support an inductive-bias claim, not a universal claim that stronger symmetry always wins.

The strongest negative control: remove the need for translation

Suppose a trustworthy upstream system registers every board to sub-pixel accuracy, crops the same physical region and rejects any failed registration. The crack can no longer appear at arbitrary addresses. A dense model may then match or beat a convolutional model, especially when absolute layout is informative and the dataset is large. Translation sharing has less work to do because the environment has already canonicalised the nuisance variable.

This is a serious counterexample to the claim that convolution is intrinsically superior for pixels. Its advantage depends on repeated structure and transformations that remain in the data. The correct comparison is not “CNN versus MLP on images”. It is “which architecture encodes the symmetries, locality and output behaviour that the task still contains, at the available data and compute budget?”

Practitioner synthesis

Convolution is a data-allocation rule. It decides that evidence collected at one location should update a detector used elsewhere. That rule is valuable when the local relation is stationary and translations are under-sampled. It is restrictive when location changes meaning or when the sensor geometry varies across the field.

Part III

Where the symmetry leaks

The clean commuting equation lives on an ideal domain. Real CNNs operate on finite arrays, change resolution, apply nonlinearities and receive images produced by sampled sensors. Each step can narrow or break the promised equivariance. The right response is not to abandon the concept. It is to state the exact boundary of the claim.

Finite images do not form an unrestricted translation group

Move a finite image far enough and content leaves the frame. New pixels must enter from somewhere. Zero padding, reflection, replication, circular padding and learned padding define different worlds outside the observed array. A patch near an edge therefore has a different neighbourhood from the same patch in the centre. The operation can remain well defined, but “the same image translated” is no longer a closed transformation unless the boundary rule is part of the specification.

This is not a minor implementation detail. Padding can become a coordinate system. If zero values occur only beyond the image boundary, deep filters can infer distance from that boundary. The network then gains absolute position sensitivity through a path that ordinary diagrams omit.[7] That sensitivity may help a dataset in which sky is usually above road, while hurting a deployment in which crops and camera framing change.

Stride changes the sampling phase

Take every second value in a feature map. A one-pixel input shift now changes which phase of the signal survives. Formally, downsampling by factor s generally satisfies translation equivariance only for shifts that are multiples of s. For other shifts, DsTδ ≠ Tδ/sDs because δ/s is not an integer grid displacement.

High-frequency content makes the mismatch worse. The classical remedy is low-pass filtering before decimation, which reduces aliasing. Azulay and Weiss traced surprising sensitivity to small image transformations partly to violations of sampling assumptions.[13] Zhang showed that anti-aliased downsampling could improve shift stability and, in the studied architectures, also improve ImageNet accuracy and corruption robustness.[14] Later alias-free ConvNets extended the analysis to nonlinearities, arguing that downsampling fixes alone do not guarantee fractional-shift invariance.[15]

Figure 4. A one-pixel shift changes the phase seen by stride two Two sampled wave traces differ by one input pixel. Selecting every second sample captures peaks in one trace and valleys in the shifted trace. A smoothed trace reduces the difference before sampling. Downsampling turns translation into a phase choice original signal, retain even samples shift by one pixel, retain the same grid phase low-pass before decimation reduces, but does not redefine, the task symmetry
Figure 4. A one-pixel shift changes the phase seen by stride two. Authored signal illustration, not measured network data. Coral points mark retained samples. The lower teal curve represents prefiltering before decimation.

Pointwise operations preserve one symmetry; other layers may not

A pointwise ReLU commutes with integer translations because it applies the same scalar function at every address. Batch operations, normalisation across selected dimensions, interpolation, attention with positional terms and stochastic cropping require separate analysis. Recent work has derived conditions under which normalisation layers remain equivariant to discrete or continuous translations, illustrating that the property belongs to the complete computation, not the presence of a convolution somewhere inside it.[16]

Translation is only one transformation

Rotate a cat by ninety degrees and a standard 3 by 3 kernel does not rotate itself. Scale a lesion and the same kernel covers a different physical extent. Bend a road, change illumination or occlude a corner and no translation equation applies. Data augmentation can teach approximate robustness to these changes, but it does not convert ordinary translation sharing into an exact architectural symmetry.

Thought experiment: rotate only the camera mount

Two factories inspect identical circular parts. In Factory A, a keyed fixture fixes orientation. In Factory B, the part can arrive at any multiple of ninety degrees. Keep the defect distribution, camera, labels and training count unchanged. A standard CNN receives a useful translation prior in both factories, but Factory B also contains a rotation symmetry. It must learn rotated copies from data or use an architecture that shares across the rotation group.

Now alter one detail: a stamped serial number must remain upright in the final report. Full rotation invariance becomes wrong because orientation carries information for that subtask. The system needs rotation-equivariant features, followed by an invariant defect head and an orientation-sensitive text head. The transformation contract has become multi-output rather than globally invariant.

Figure 5. Each output has its own transformation trajectory A central image orbit branches into three trajectories: a class score stays fixed, a segmentation mask rotates and translates with the input, and a coordinate report changes numerically. A red region indicates broken sampling and boundary assumptions. One input orbit, three correct output behaviours translations and rotations reject class stays fixed mask follows input (12, 7) coordinate changes boundary, sampling and cropping can break the orbit
Figure 5. Each output has its own transformation trajectory. Authored conceptual orbit map. No empirical rates are implied. The red lower boundary marks transformations for which cropping, padding or sampling no longer preserve the declared input relation.

Explicit failure boundary

The convolutional argument stops where the symmetry stops. It weakens when absolute position is causal, local statistics vary across the field, the relevant transformation is rotation, scale or deformation rather than translation, aggressive downsampling aliases the signal, boundaries change the scene, or the task depends primarily on long-range relations. Equivariance also provides no guarantee against lighting shift, occlusion, adversarial perturbation or label error.

Open hypothesis

For many real systems, the strongest design will be neither globally equivariant nor fully unconstrained. It will preserve a known symmetry through early evidence extraction, then introduce controlled symmetry breaking at the point where pose, coordinates or environment-specific reference frames become causally relevant. Recent controlled studies of equivariant and symmetry-breaking models report a similar task-dependent pattern, but the general rule still needs broader replication across sensors and domains.[17]

Exactness has a budget

Architecture discussions often treat equivariance as a badge that a network either owns or lacks. A deployed imaging chain is less tidy. The physical object moves continuously; the lens projects it; a sensor samples it onto a finite grid; registration estimates a frame; preprocessing crops and resizes; the network changes resolution; and the readout converts a field into a decision. An exact algebraic property at one layer can therefore coexist with a visibly unstable end-to-end system. Conversely, a network without a formal guarantee may be consistent enough inside the operating envelope.

Separate three claims: a layer-level guarantee, measured orbit consistency and useful task performance. The first follows from mathematics under declared boundary and sampling assumptions. The second is an empirical property of the complete pipeline. The third asks whether remaining variation changes a decision that matters. None implies the next. A classifier can be exactly invariant and consistently wrong. A segmenter can score highly on average while moving its mask by two pixels whenever the acquisition phase changes. A system can also violate exact equivariance by a tiny amount that stays below the physical tolerance of the process.

Return to the board inspector. Suppose the defect rule tolerates a one-millimetre localisation error, while one output pixel represents a tenth of a millimetre. A one-pixel mask wobble is then operationally small; an eleven-pixel wobble crosses the rule boundary. Those figures are illustrative, not measurements. Their purpose is to force the tolerance into physical units. The architecture review should allocate an error budget across registration, interpolation, network response and post-processing, then test whether their combined effect can cross a business threshold.

This view also answers a serious objection. Given enough varied data, a sufficiently expressive unconstrained model may learn the desired symmetry without specialised layers. That is a valid negative control, not a reason to ignore structure. The comparison should ask how much data, computation and failure coverage each route needs, and whether the learned behaviour remains stable near sparsely observed parts of the orbit. Architectural sharing earns its cost when the symmetry is credible, examples are scarce or consistency matters beyond average accuracy. Augmentation may be preferable when the transformation is only approximate, exceptions are common or a specialised operator would impose the wrong relation.

The practical target is therefore not maximum symmetry. It is the smallest, most testable transformation contract that removes nuisance variation while preserving causal variables. Measure exact smoke tests where the implementation promises exactness. Measure orbit error across the complete pipeline where exactness is impossible. Finally, connect that error to task thresholds. This three-level ledger prevents a formal guarantee from becoming a proxy for system quality, and prevents a good benchmark average from concealing unstable behaviour under motion.

Part IV

A matched experiment that separates detection from address

The accompanying artefact creates 16 by 16 synthetic images with sparse noise and one 3 by 3 pattern. The first task classifies a plus sign versus an L shape. Training examples place the pattern only in a central 4 by 4 region. The shifted test places it in unseen edge regions. The second task keeps the plus sign fixed as the only shape and asks whether it lies on the left or right. This location task is the negative control for invariance.

Four small models receive matched training counts and optimisation. A dense MLP has no weight sharing. A spatial CNN uses two convolutional layers but flattens the feature map into a position-specific head. A pooled CNN uses the same style of convolutional trunk followed by global max pooling. A coordinate-pooled CNN adds explicit x and y channels before the convolutions, allowing the pooled representation to break translation symmetry.

The experiment changes one causal feature at a time. The spatial CNN tests whether convolution alone creates an invariant classifier. The pooled CNN adds only an address-discarding readout. The coordinate model then restores access to address while retaining shared local detection. Five seeds are reported. The tenth and ninetieth percentiles describe variation across those seeds; they are not confidence intervals for a real deployment.

Shape task: median accuracy across five seeds
ModelParametersSeen central positionsUnseen shifted positionsMechanism isolated
Dense MLP16,57895.4% [94.2, 96.9]51.6% [49.0, 53.6]Position-specific weights
Spatial CNN4,762100.0% [99.7, 100.0]47.7% [42.9, 54.2]Shared detector, position-specific head
Pooled CNN682100.0% [100.0, 100.0]100.0% [98.3, 100.0]Shared detector, address discarded
Coordinate-pooled CNN826100.0% [100.0, 100.0]91.4% [83.6, 99.8]Shared detector with explicit address
Figure 6. Convolution alone did not transfer the classifier; pooling did A horizontal bar chart shows shifted shape accuracy for four models. Dense MLP is 51.6 percent, spatial CNN 47.7, pooled CNN 100, and coordinate-pooled CNN 91.4. Whiskers show tenth to ninetieth percentiles across five seeds. Unseen-position shape accuracy median with 10th to 90th percentile across five seeds 0255075100% Dense MLPSpatial CNNPooled CNNCoordinate-pooled 51.6%47.7%100.0%91.4%
Figure 6. Convolution alone did not transfer the classifier; pooling did. Measured synthetic result: five seeds, 1,200 training images and 700 shifted test images per seed. Bars are medians; whiskers show seed-level 10th to 90th percentiles. These values diagnose the constructed mechanism and do not estimate real-world vision accuracy.

The spatial CNN is the decisive result. It contains shared convolutional filters and uses fewer parameters than the MLP, yet its shifted accuracy is at chance. Its detector can travel, but the final linear head learns which positions mattered during training. A translation-equivariant trunk does not rescue a position-specific readout.

Global max pooling changes the result. The pooled CNN reaches a median 100 per cent on shifted positions because the head asks whether a strong feature exists, not where it exists. The coordinate-pooled model also learns the shape, but its explicit address channels make the invariance softer and its shifted result more variable. That is expected: a model allowed to use position can exploit training-position regularities.

The counterexample: when successful invariance is the bug

On the left-versus-right task, the globally pooled CNN falls to 50.1 per cent, essentially chance. The architecture has done exactly what it was designed to do: discard where the feature occurred. The spatial CNN, dense MLP and coordinate-pooled CNN retain or reconstruct position and reach about 100 per cent.

Location task: median accuracy across five seeds
ModelAccuracyInterpretation
Dense MLP99.9% [99.7, 99.9]Absolute pixel weights make location easy
Spatial CNN100.0% [100.0, 100.0]Spatial head preserves the feature address
Pooled CNN50.1% [48.9, 50.4]Global pooling removes the target variable
Coordinate-pooled CNN100.0% [100.0, 100.0]Coordinate channels permit controlled symmetry breaking
Figure 7. The invariant readout fails when location is the label Four vertical columns show location accuracy. Dense MLP, spatial CNN and coordinate-pooled CNN are near 100 percent. Pooled CNN is near 50 percent. A horizontal line marks chance. Left-versus-right accuracy 50% chance 99.9%100%50.1%100% DenseSpatialPooledCoordinate MLPCNNCNNpooled
Figure 7. The invariant readout fails when location is the label. Measured synthetic result: five seeds, 1,200 training images and 700 test images per seed. The patterns were kept away from the boundary so the pooled model could not use padding as an accidental coordinate signal.

This negative control prevents a shallow conclusion that more invariance is always better. The preferred architecture changes when the requested output changes, even though the input type and local evidence stay the same. CoordConv research made the same general point through coordinate-transform tasks: ordinary convolution can struggle when a task needs explicit access to position, and coordinate channels provide a direct symmetry-breaking path.[18]

Worked example

In the minimal crack example, the convolutional map answers “where does crack-like evidence occur?” Global max pooling answers “does it occur anywhere?” A spatial mask head answers “which pixels should be marked?” Coordinate channels answer “in which calibrated zone did it occur?” These are four different questions, not four competing definitions of one invariant network.

How the claim changes under pressureChart · state
stateDiagram-v2
  [*] --> Claim
  state "Thought experiment: change<br/>only the requested output" as Claim
  state "Locality: nearby values form<br/>reusable evidence" as Test
  state "Weight sharing: the same<br/>relation matters at many<br/>addresses" as Revision
  Claim --> Test: expose mechanism
  Test --> Decision: evidence survives
  Test --> Revision: rival survives
  Revision --> Claim: narrow or rebuild
  Decision --> [*]
The revision loop begins with Thought experiment, change only the… and keeps counterexamples inside the reasoning path. Reading use: follow the relation, then test it against the surrounding evidence and limits.

A production-shaped decision: inspect the board, not the benchmark

Worked scenario

This is a synthetic composite intended to expose the architecture decision. It is not a claim about a live factory or client deployment.

A line-scan inspection system receives 4,096 by 4,096 images of assembled boards. Fiducial registration removes most displacement, but residual translation remains. Boards may enter at one of four right-angle orientations. The system must produce a defect mask, a board-level severity score and a zone-specific rule outcome because damage near a high-voltage connector has different consequences from the same damage near an unused edge.

A single demand for “invariance” would be incoherent. The defect mask should translate and rotate with the board. The severity score should remain stable under those same nuisance transformations, provided no content leaves the frame. The zone rule should be evaluated in board coordinates after orientation and registration are resolved. Absolute camera coordinates are irrelevant; calibrated board coordinates are causal.

A plausible design begins with registration quality checks, not a larger network. If registration confidence is high, canonicalising the four orientations may be cheaper and easier to validate than building a rotation-equivariant model. If orientation is ambiguous or the same detector must serve unregistered images, a group-equivariant or steerable trunk becomes a credible option. Cohen and Welling's group convolutions show how discrete rotations and reflections can be built into feature transformations; later Lie-group methods extend the idea to broader continuous symmetries.[11][12]

The trunk should preserve sufficient spatial resolution for the mask. Anti-aliased downsampling can be tested where stride is necessary. A dense decoder returns the mask in board coordinates. A global aggregation of defect features feeds the board-level severity head. The zone rule receives the mask plus calibrated coordinates, rather than hoping that zero padding teaches the network a stable map of the board.

The evaluation should follow the declared transformations. For each held-out board, construct an orbit of permitted translations and right-angle rotations. Transform the expected mask with the image; keep the expected severity fixed; recompute the zone result in board coordinates. Record where cropping makes the transformed case invalid. This produces three separate errors rather than one accuracy number.

EG(f) = Ex,g[ d(f(ρin(g)x), ρout(g)f(x)) ]
G is the tested transformation set, x is a case, g is a sampled transformation, d is a task-appropriate distance and the two ρ terms define input and output actions. A smaller value means the measured model better respects the declared transformation. Classification can use probability distance; masks can use pixel disagreement after alignment; coordinates can use physical error in millimetres.

This metric is not a substitute for ordinary quality evaluation. A consistently wrong model can be perfectly equivariant. Orbit consistency must be paired with accuracy, calibration, boundary cases and task utility. It answers one question only: does the system transform its answer in the way the task declares?

Figure 8. The transformation contract chooses the readout A branching decision map starts with an input transformation and asks whether the output should stay fixed, move with the input or depend on absolute coordinates. Each branch points to an architecture and a test. Transformation contract: ask what the output must do input orbit G stay fixed? move with input? need reference frame? invariant head global pool or set aggregate equivariant head dense map, keypoints, vector field symmetry breaking coordinates or pose canonical frame test: constancy test: alignment test: calibrated change reject transformations that crop evidence or violate the sensor model
Figure 8. The transformation contract chooses the readout. Authored decision instrument. The three branches can coexist in a multi-head system. The red boundary reminds the evaluator to exclude or separately label transformations that change content rather than merely move it.

The transformation contract

  1. Name the transformation. Translation by how many pixels or millimetres? Rotation around which centre? Reflection, scale, permutation or deformation?
  2. State the output action. Should each output stay fixed, move, rotate, rescale, permute, or change according to calibrated coordinates?
  3. Define the valid orbit. Which transformations preserve content, sensor support and label meaning? What happens at crops and boundaries?
  4. Place the symmetry. Use shared kernels, group-equivariant layers, augmentation, canonicalisation or none. Introduce pooling only where location can be discarded.
  5. Place symmetry breaking. Add coordinates, pose, reference frames or spatial heads only where the task requires them.
  6. Measure the contract. Evaluate ordinary task quality and orbit consistency separately, including shifts smaller than the total stride.
  7. Run the negative control. Include a task or slice where the discarded variable is causal. Verify that the architecture can recover it when required.

The instrument changes the architecture review. Instead of asking whether CNNs are still competitive with another model family, it asks which transformation economy the system needs. A transformer with relative position, a CNN, a steerable network and a registered template pipeline can all be good answers to different contracts. Benchmark rank alone cannot decide among them.

The executable artefact

The Python program reproduces the two-task benchmark and runs three algebraic smoke tests. A unit-stride convolution with circular padding must commute with an integer translation. Global max pooling over that response must remain unchanged. A one-pixel shift followed by stride-two sampling must expose a phase error. The training experiment then supplies the positive case and the negative control.

Run python paper45_convolution_contract.py with Python 3.10 or later, NumPy and PyTorch. The fixed configuration uses five seeds, 1,200 training cases per task and CPU execution. The program asserts broad mechanism-level expectations rather than exact percentages, then prints median and seed-range results. All data are generated locally.

Open the complete runnable Python artefact
#!/usr/bin/env python3
"""Paper 45 artefact: convolution, equivariance, invariance and symmetry breaking.

The script contains two experiments on synthetic 16 x 16 images.

1. Shape task: learn '+' versus 'L' from central placements, then evaluate at
   unseen edge placements. This tests whether a detector travels with the input
   and whether the readout discards location.
2. Location task: every image contains the same '+'; predict left versus right.
   This is the negative control. A deliberately invariant readout should fail,
   while a spatial head or coordinate channels should succeed.

It also runs exact smoke tests showing that unit-stride circular convolution is
translation equivariant, global max pooling is invariant to that translation,
and stride-2 sampling breaks one-pixel equivariance by changing sampling phase.

Dependencies: Python 3.10+, NumPy and PyTorch. CPU execution is sufficient.
Expected runtime on a modern laptop CPU: tens of seconds, not minutes.
"""

from __future__ import annotations

import argparse
import json
import random
from dataclasses import dataclass
from statistics import median
from typing import Callable

import numpy as np
import torch
from torch import Tensor, nn
from torch.nn import functional as F
from torch.utils.data import DataLoader, TensorDataset

HEIGHT = WIDTH = 16
BASE_SEED = 45_045
REPLICATES = 5
EPOCHS = 8
TRAIN_CASES = 1_200
SEEN_CASES = 500
SHIFTED_CASES = 700
LOCATION_CASES = 700

PLUS = np.array(
    [[0.0, 1.0, 0.0],
     [1.0, 1.0, 1.0],
     [0.0, 1.0, 0.0]],
    dtype=np.float32,
)
ELL = np.array(
    [[1.0, 0.0, 0.0],
     [1.0, 0.0, 0.0],
     [1.0, 1.0, 1.0]],
    dtype=np.float32,
)
PATTERNS = (PLUS, ELL)

CENTRAL_POSITIONS = [(y, x) for y in range(5, 9) for x in range(5, 9)]
EDGE_POSITIONS = [
    (y, x)
    for y in (0, 1, 2, 11, 12, 13)
    for x in (0, 1, 2, 11, 12, 13)
]
# Keep the location task away from padding so the invariant model cannot infer
# coordinates from the image boundary. x >= 7 is the right-hand class.
LOCATION_POSITIONS = [(y, x) for y in range(3, 11) for x in range(3, 11)]


@dataclass(frozen=True)
class Interval:
    median: float
    p10: float
    p90: float

    @classmethod
    def from_values(cls, values: list[float]) -> "Interval":
        return cls(
            median=100.0 * median(values),
            p10=100.0 * float(np.quantile(values, 0.10)),
            p90=100.0 * float(np.quantile(values, 0.90)),
        )


class DenseMLP(nn.Module):
    """No spatial sharing: every pixel receives position-specific weights."""

    def __init__(self) -> None:
        super().__init__()
        self.network = nn.Sequential(
            nn.Flatten(),
            nn.Linear(HEIGHT * WIDTH, 64),
            nn.ReLU(),
            nn.Linear(64, 2),
        )

    def forward(self, x: Tensor) -> Tensor:
        return self.network(x)


class SpatialCNN(nn.Module):
    """Shared local detector followed by a position-specific linear readout."""

    def __init__(self) -> None:
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 8, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Conv2d(8, 8, kernel_size=3, padding=1),
            nn.ReLU(),
        )
        self.head = nn.Sequential(
            nn.Flatten(),
            nn.Linear(8 * HEIGHT * WIDTH, 2),
        )

    def forward(self, x: Tensor) -> Tensor:
        return self.head(self.features(x))


class PooledCNN(nn.Module):
    """Shared detector plus global max pooling: deliberately location invariant."""

    def __init__(self) -> None:
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 8, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Conv2d(8, 8, kernel_size=3, padding=1),
            nn.ReLU(),
        )
        self.head = nn.Sequential(
            nn.AdaptiveMaxPool2d(1),
            nn.Flatten(),
            nn.Linear(8, 2),
        )

    def forward(self, x: Tensor) -> Tensor:
        return self.head(self.features(x))


class CoordPooledCNN(nn.Module):
    """Global pooling with explicit x/y channels that permit symmetry breaking."""

    def __init__(self) -> None:
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 8, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Conv2d(8, 8, kernel_size=3, padding=1),
            nn.ReLU(),
        )
        self.head = nn.Sequential(
            nn.AdaptiveMaxPool2d(1),
            nn.Flatten(),
            nn.Linear(8, 2),
        )
        yy, xx = torch.meshgrid(
            torch.linspace(-1.0, 1.0, HEIGHT),
            torch.linspace(-1.0, 1.0, WIDTH),
            indexing="ij",
        )
        self.register_buffer("coordinates", torch.stack((xx, yy)).unsqueeze(0))

    def forward(self, x: Tensor) -> Tensor:
        coords = self.coordinates.expand(x.shape[0], -1, -1, -1)
        return self.head(self.features(torch.cat((x, coords), dim=1)))


MODEL_FACTORIES: dict[str, Callable[[], nn.Module]] = {
    "Dense MLP": DenseMLP,
    "Spatial CNN": SpatialCNN,
    "Pooled CNN": PooledCNN,
    "Coord-pooled CNN": CoordPooledCNN,
}


def make_dataset(
    count: int,
    positions: list[tuple[int, int]],
    seed: int,
    task: str,
) -> tuple[Tensor, Tensor]:
    """Create noisy synthetic images for either the shape or location task."""

    rng = np.random.default_rng(seed)
    images = np.zeros((count, 1, HEIGHT, WIDTH), dtype=np.float32)
    labels = np.zeros(count, dtype=np.int64)

    for index in range(count):
        y, x = positions[int(rng.integers(0, len(positions)))]
        image = rng.normal(0.0, 0.12, (HEIGHT, WIDTH)).astype(np.float32)

        if task == "shape":
            label = int(rng.integers(0, 2))
            image[y : y + 3, x : x + 3] += PATTERNS[label]
        elif task == "location":
            label = int(x >= 7)
            image[y : y + 3, x : x + 3] += PLUS
        else:
            raise ValueError(f"Unknown task: {task}")

        # Sparse non-causal distractors prevent a trivial clean-template lookup.
        for _ in range(3):
            yy = int(rng.integers(0, HEIGHT))
            xx = int(rng.integers(0, WIDTH))
            image[yy, xx] += float(rng.uniform(0.2, 0.5))

        images[index, 0] = image
        labels[index] = label

    return torch.tensor(images), torch.tensor(labels)


def reset_and_train(
    model: nn.Module,
    dataset: tuple[Tensor, Tensor],
    seed: int,
) -> nn.Module:
    """Train one small classifier deterministically on CPU."""

    torch.manual_seed(seed)

    def reset(module: nn.Module) -> None:
        if hasattr(module, "reset_parameters"):
            module.reset_parameters()  # type: ignore[attr-defined]

    model.apply(reset)
    loader = DataLoader(
        TensorDataset(*dataset),
        batch_size=64,
        shuffle=True,
        generator=torch.Generator().manual_seed(seed),
    )
    optimiser = torch.optim.Adam(model.parameters(), lr=0.003)
    criterion = nn.CrossEntropyLoss()

    model.train()
    for _ in range(EPOCHS):
        for inputs, targets in loader:
            optimiser.zero_grad()
            loss = criterion(model(inputs), targets)
            loss.backward()
            optimiser.step()
    return model


@torch.no_grad()
def accuracy(model: nn.Module, dataset: tuple[Tensor, Tensor]) -> float:
    model.eval()
    inputs, targets = dataset
    predictions = model(inputs).argmax(dim=1)
    return float((predictions == targets).float().mean())


def parameter_count(model: nn.Module) -> int:
    return sum(parameter.numel() for parameter in model.parameters())


def exact_mechanism_tests() -> dict[str, float]:
    """Positive and negative tests for the algebra described in the article."""

    torch.manual_seed(BASE_SEED)
    signal = torch.randn(1, 1, 16, 16)
    kernel = torch.tensor(
        [[[[0.0, 1.0, 0.0],
           [1.0, -4.0, 1.0],
           [0.0, 1.0, 0.0]]]],
        dtype=torch.float32,
    )

    def circular_correlation(x: Tensor) -> Tensor:
        return F.conv2d(F.pad(x, (1, 1, 1, 1), mode="circular"), kernel)

    shift = (2, -3)
    moved_signal = torch.roll(signal, shifts=shift, dims=(-2, -1))
    moved_response = circular_correlation(moved_signal)
    response_moved_afterwards = torch.roll(
        circular_correlation(signal), shifts=shift, dims=(-2, -1)
    )
    equivariance_error = float(
        (moved_response - response_moved_afterwards).abs().max()
    )

    invariant_error = float(
        (moved_response.amax(dim=(-2, -1))
         - circular_correlation(signal).amax(dim=(-2, -1)))
        .abs()
        .max()
    )

    # A one-pixel shift changes which samples survive stride-2 decimation.
    impulse = torch.zeros(1, 1, 8, 8)
    impulse[0, 0, 2, 2] = 1.0
    shifted_impulse = torch.roll(impulse, shifts=1, dims=-1)
    stride_phase_error = float(
        (impulse[:, :, ::2, ::2] - shifted_impulse[:, :, ::2, ::2])
        .abs()
        .sum()
    )

    assert equivariance_error < 1e-6, equivariance_error
    assert invariant_error < 1e-6, invariant_error
    assert stride_phase_error > 0.9, stride_phase_error

    return {
        "unit_stride_circular_equivariance_max_error": equivariance_error,
        "global_max_invariance_max_error": invariant_error,
        "one_pixel_stride2_phase_error": stride_phase_error,
    }


def run_benchmark() -> dict[str, object]:
    torch.set_num_threads(1)
    random.seed(BASE_SEED)
    np.random.seed(BASE_SEED)
    torch.manual_seed(BASE_SEED)

    raw: dict[str, dict[str, dict[str, list[float]]]] = {
        "shape": {
            name: {"seen": [], "shifted": []} for name in MODEL_FACTORIES
        },
        "location": {
            name: {"accuracy": []} for name in MODEL_FACTORIES
        },
    }

    for replicate in range(REPLICATES):
        seed = BASE_SEED + 100 * replicate
        shape_train = make_dataset(TRAIN_CASES, CENTRAL_POSITIONS, seed + 1, "shape")
        shape_seen = make_dataset(SEEN_CASES, CENTRAL_POSITIONS, seed + 2, "shape")
        shape_shifted = make_dataset(
            SHIFTED_CASES, EDGE_POSITIONS, seed + 3, "shape"
        )
        location_train = make_dataset(
            TRAIN_CASES, LOCATION_POSITIONS, seed + 4, "location"
        )
        location_test = make_dataset(
            LOCATION_CASES, LOCATION_POSITIONS, seed + 5, "location"
        )

        for model_index, (name, factory) in enumerate(MODEL_FACTORIES.items()):
            shape_model = reset_and_train(factory(), shape_train, seed + 10 + model_index)
            raw["shape"][name]["seen"].append(accuracy(shape_model, shape_seen))
            raw["shape"][name]["shifted"].append(
                accuracy(shape_model, shape_shifted)
            )

            location_model = reset_and_train(
                factory(), location_train, seed + 20 + model_index
            )
            raw["location"][name]["accuracy"].append(
                accuracy(location_model, location_test)
            )

    summary: dict[str, object] = {
        "configuration": {
            "replicates": REPLICATES,
            "epochs": EPOCHS,
            "train_cases_per_replicate": TRAIN_CASES,
            "seen_shape_cases_per_replicate": SEEN_CASES,
            "shifted_shape_cases_per_replicate": SHIFTED_CASES,
            "location_cases_per_replicate": LOCATION_CASES,
            "base_seed": BASE_SEED,
        },
        "mechanism_tests": exact_mechanism_tests(),
        "shape_task": {},
        "location_task": {},
    }

    for name, factory in MODEL_FACTORIES.items():
        summary["shape_task"][name] = {  # type: ignore[index]
            "parameters": parameter_count(factory()),
            "seen_accuracy_percent": Interval.from_values(
                raw["shape"][name]["seen"]
            ).__dict__,
            "shifted_accuracy_percent": Interval.from_values(
                raw["shape"][name]["shifted"]
            ).__dict__,
        }
        summary["location_task"][name] = {  # type: ignore[index]
            "parameters": parameter_count(factory()),
            "accuracy_percent": Interval.from_values(
                raw["location"][name]["accuracy"]
            ).__dict__,
        }

    # Discriminating assertions. These are intentionally broad enough to avoid
    # treating a tiny synthetic benchmark as a precision contest.
    pooled_shifted = summary["shape_task"]["Pooled CNN"][  # type: ignore[index]
        "shifted_accuracy_percent"
    ]["median"]
    spatial_shifted = summary["shape_task"]["Spatial CNN"][  # type: ignore[index]
        "shifted_accuracy_percent"
    ]["median"]
    pooled_location = summary["location_task"]["Pooled CNN"][  # type: ignore[index]
        "accuracy_percent"
    ]["median"]
    coord_location = summary["location_task"]["Coord-pooled CNN"][  # type: ignore[index]
        "accuracy_percent"
    ]["median"]

    assert pooled_shifted > 95.0, pooled_shifted
    assert spatial_shifted < 65.0, spatial_shifted
    assert 45.0 <= pooled_location <= 55.0, pooled_location
    assert coord_location > 95.0, coord_location
    return summary


def print_table(summary: dict[str, object]) -> None:
    print("\nShape task: '+' versus 'L'\n")
    print(f"{'Model':<19} {'Params':>8} {'Seen median':>13} {'Shifted median':>16}")
    print("-" * 61)
    for name, row in summary["shape_task"].items():  # type: ignore[union-attr]
        seen = row["seen_accuracy_percent"]
        shifted = row["shifted_accuracy_percent"]
        print(
            f"{name:<19} {row['parameters']:>8,d} "
            f"{seen['median']:>6.1f}% [{seen['p10']:.1f}, {seen['p90']:.1f}] "
            f"{shifted['median']:>6.1f}% [{shifted['p10']:.1f}, {shifted['p90']:.1f}]"
        )

    print("\nLocation task: left versus right\n")
    print(f"{'Model':<19} {'Params':>8} {'Accuracy median':>28}")
    print("-" * 59)
    for name, row in summary["location_task"].items():  # type: ignore[union-attr]
        result = row["accuracy_percent"]
        print(
            f"{name:<19} {row['parameters']:>8,d} "
            f"{result['median']:>6.1f}% [{result['p10']:.1f}, {result['p90']:.1f}]"
        )

    print("\nMechanism tests")
    for name, value in summary["mechanism_tests"].items():  # type: ignore[union-attr]
        print(f"  {name}: {value:.8f}")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--json",
        action="store_true",
        help="Print the complete result as JSON instead of compact tables.",
    )
    args = parser.parse_args()

    summary = run_benchmark()
    if args.json:
        print(json.dumps(summary, indent=2))
    else:
        print_table(summary)


if __name__ == "__main__":
    main()
Method notes and limits

The benchmark is intentionally small. It does not compare state-of-the-art vision architectures, augmentations, optimisers or large-scale pretraining. The edge test changes position more sharply than many natural datasets, which makes the causal mechanism visible. Global max pooling is selected because the synthetic shape is sparse; average pooling would create a different signal-to-noise trade.

The result would weaken if a matched spatial CNN transferred well to unseen positions, if the pooled CNN failed the shape task despite adequate optimisation, or if the pooled CNN solved the interior location task without coordinates or boundary cues. None occurred under the declared seeds. A larger replication should vary noise, pattern size, training region, padding, pooling type and data volume.

Compact glossary

Equivariance: transforming the input causes a prescribed transformation of the output. Invariance: the prescribed output transformation is identity. Kernel: a shared local parameter array. Stride: the spacing between evaluated positions. Aliasing: ambiguity introduced when sampling is too coarse for the signal. Orbit: the set of examples reached by applying a transformation group to one example. Symmetry breaking: introducing information that distinguishes states otherwise treated as equivalent.

The evidence conversationChart · sequence
sequenceDiagram
  participant Q as Question
  participant M as Mechanism
  participant E as Evidence
  participant D as Decision
  Q->>M: The crack that moved six centimetres
  M->>E: Thought experiment: change only the requested output
  E-->>M: compare rival explanations
  E->>D: Choose the motion before the model
  D-->>Q: revise scope or act
Question, mechanism, evidence and decision exchange the burdens developed in Convolution, Invariance and Equivariance. Reading use: follow the relation, then test it against the surrounding evidence and limits.

Choose the motion before the model

The durable intuition is simple. A convolutional kernel is a detector on rails. Move the evidence, and the detector response should move with it. That is translation equivariance. The detector has not yet decided whether location matters. The readout makes that decision.

This distinction turns a vague architecture preference into a testable design. Use global aggregation when the requested answer should stay fixed. Preserve a spatial field when the answer should move. Add calibrated coordinates or pose when a reference frame is causal. Extend parameter sharing to rotations or other groups only when those transformations belong to the task. Treat padding, stride and sampling as part of the claim, not as plumbing.

The changed decision is to stop selecting a CNN because the input is an image. Select or reject convolution after writing the transformation contract: which local relations repeat, which motions preserve meaning, how each output should transform, where symmetry must break and how the complete system will be tested across its valid orbit. That contract is the real inductive bias. The architecture is its implementation.

References

  1. PyTorch, torch.nn.Conv2d documentation.
  2. Ian Goodfellow, Yoshua Bengio and Aaron Courville, “Convolutional Networks”, Chapter 9 of Deep Learning, MIT Press.
  3. Min Lin, Qiang Chen and Shuicheng Yan, “Network In Network”, 2013.
  4. PyTorch, torch.nn.AdaptiveMaxPool2d documentation.
  5. Jonathan Long, Evan Shelhamer and Trevor Darrell, “Fully Convolutional Networks for Semantic Segmentation”, CVPR, 2015.
  6. Stéphane Mallat, “Group Invariant Scattering”, Communications on Pure and Applied Mathematics, 2012.
  7. Osman Semih Kayhan and Jan C. van Gemert, “On Translation Invariance in CNNs: Convolutional Layers Can Exploit Absolute Spatial Location”, CVPR, 2020.
  8. Siamak Ravanbakhsh, Jeff Schneider and Barnabás Póczos, “Equivariance Through Parameter-Sharing”, ICML, 2017.
  9. Kunihiko Fukushima, “Neocognitron: a self-organizing neural network model for a mechanism of pattern recognition unaffected by shift in position”, Biological Cybernetics, 1980.
  10. Yann LeCun, Léon Bottou, Yoshua Bengio and Patrick Haffner, “Gradient-Based Learning Applied to Document Recognition”, Proceedings of the IEEE, 1998.
  11. Taco Cohen and Max Welling, “Group Equivariant Convolutional Networks”, ICML, 2016.
  12. Marc Finzi, Samuel Stanton, Pavel Izmailov and Andrew Gordon Wilson, “Generalizing Convolutional Neural Networks for Equivariance to Lie Groups on Arbitrary Continuous Data”, ICML, 2020.
  13. Aharon Azulay and Yair Weiss, “Why do deep convolutional networks generalize so poorly to small image transformations?”, Journal of Machine Learning Research, 2019.
  14. Richard Zhang, “Making Convolutional Networks Shift-Invariant Again”, ICML, 2019.
  15. Hagay Michaeli, Tomer Michaeli and Daniel Soudry, “Alias-Free Convnets: Fractional Shift Invariance via Polynomial Activations”, CVPR, 2023.
  16. Jérémy Scanvic, Quentin Barthélemy and Julián Tachella, “Translation-Equivariance of Normalization Layers and Aliasing in Convolutional Neural Networks”, workshop paper, 2025.
  17. Sharvaree Vadgama et al., “Probing Equivariance and Symmetry Breaking in Convolutional Networks”, preprint, 2025.
  18. Rosanne Liu et al., “An Intriguing Failing of Convolutional Neural Networks and the CoordConv Solution”, NeurIPS, 2018.