The MNIST CNN — every parameter gradient node IS the loss's derivative, at real MNIST #
cnn_train_step_tied_certified ties each of the ten SGD updates to the certified per-layer
Jacobian contracted with the cotangent the emitted chain threads to it, and leaves open whether
that cotangent is the loss gradient below the output. cnn_net_lossGrad closes it: the un-fused
*Grad node of each layer, at the chain cotangent, is the gradient of the loss in that parameter
(HasGradAt), for any loss L of the logits with gradient g there; cnn_net_lossGrad_CE
instantiates it at the softmax cross-entropy the render emits. The fused *Sgd ops are θ − lr·
these nodes (SmallParamGrad.convWeightSgd_eq_grad and its peers).
The pool's clause is stated for the parameters, not the image. On real MNIST almost every
image has a 2×2 window whose positive maximum sits at two cells, because the two cells read
identical input (a constant background patch): the net has no derivative in its input there, and
the pool none in its activation. But two such cells are the same function of the conv weights
(CnnPoolTwin, implied by identical two-layer receptive fields, cnnPoolTwin_of_convPatchEq2),
so along any parameter the pooled ReLU is the gather at a fixed selection
(SmallParamGrad.maxPool_relu_eventuallyEq_sel), and the loss IS differentiable in the
parameters. CnnLossSmoothAt allows exactly those ties. The probe
scripts/probes/mnist_pool_twin_probe.py checks this clause on the MNIST test set.
Which cotangent. At a tied window the pool's backward must pick one cell. The rendered
select_and_scatter (select = GE) does: it routes each window's cotangent to the window's first
maximal cell, the gather's adjoint. The capstone is stated at any selection σ naming a maximum of
every window (cnnChainCotW2Sel, SmallParamGrad.PoolSelDom). The step tie's cnnChainCotW2
reads the pool backward as maxPoolBackDenote, which routes to the first argmax
(maxPool2Argmax), so it IS cnnChainCotW2Sel at that selection, at every point
(cnnChainCotW2_eq_sel); SmallParamGrad.poolSelDom_argmax discharges the selection clause there.
How. The loss read at the logits is pulled back through the dense head
(SmallParamGrad.hasGradAt_dense, SmallParamGrad.hasGradAt_relu), through the pool as the
gather at σ (SmallParamGrad.hasGradAt_gatherRelu; at the point itself the pool IS that gather),
then through the convs (SmallParamGrad.hasGradAt_conv). Each conv node is the gather model's
parameter gradient, moved to the real net by the germ (HasGradAt.congr_of_eventuallyEq).
Hypotheses. Odd kernels (the rendered conv backward is the conv VJP there), and
CnnLossSmoothAt: every ReLU off its kink, and every pool window dead or tied only between twins.
Scope. One example (the emitted module batch-contracts; den is per-example).
The conv2 pre-activation, the input of the pool's ReLU.
Equations
- Proofs.CnnFold.cnnPoolPre W₁ b₁ W₂ b₂ x = Proofs.flatConv W₂ b₂ (Proofs.relu (c * (2 * h) * (2 * w)) (Proofs.flatConv W₁ b₁ x))
Instances For
Two pool-input cells are twins: they are equal at every conv weight, in every channel. At such a pair the pool can tie at a positive maximum, and the tie is the same at every weight, which is why the parameter gradient survives it.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Cells with identical two-layer receptive fields in the image are twins.
The conv2-output cotangent at a pool selection σ: cnnChainCotW2 with the pool backward
routing each window's cotangent to the ONE cell σ names (selScatter), as the rendered
select_and_scatter does, then the ReLU mask.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The step tie's conv₂ cotangent is the capstone's at the first argmax. The rendered pool
backward routes each window to its first maximum (maxPool2Argmax), so cnnChainCotW2 is
cnnChainCotW2Sel at that selection, at every point, ties included.
The smooth-point bundle the loss gradient needs. Every ReLU off its kink; every pool window
dead or tied only between twins (CnnPoolTwin); and σ names a maximum of every window.
- pool : SmallParamGrad.MaxPool2SmoothUpTo (CnnPoolTwin c kH kW x) (Tensor3.unflatten (cnnPoolPre W₁ b₁ W₂ b₂ x))
- sel : SmallParamGrad.PoolSelDom σ (relu (c * (2 * h) * (2 * w)) (cnnPoolPre W₁ b₁ W₂ b₂ x))
- z3 (k : Fin d1) : dense W₃ b₃ (maxPoolFlat c h w (relu (c * (2 * h) * (2 * w)) (cnnPoolPre W₁ b₁ W₂ b₂ x))) k ≠ 0
Instances For
Every MNIST-CNN parameter node is the gradient of L in that parameter: the ten un-fused
nodes, each at the cotangent the chain threads to its layer (the head's mlpCotOut1 /
mlpCotOut0, the pool routed at σ, the conv backward), stated against L of
mnistCnnNoBnForward with that one parameter varied.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Every MNIST-CNN parameter node is the gradient of L in that parameter, whenever g is
L's gradient at the logits.
Hypotheses: odd kernels, every ReLU off its kink, and every pool window dead or tied only
between cells that are the same function of the conv weights (CnnLossSmoothAt), with σ
naming a maximum of every window.
The artifact's loss: every node is the gradient of the softmax cross-entropy at label,
g the emitted loss cotangent.