Documentation

LeanMlir.Proofs.Nets.Small.CnnFold

PoC: the MNIST-CNN train step, proof-tied to the certified SGD step #

The CNN analogue of LinearFold / MlpFold. MainMnistCnnVerified trains on verified_mlir/cnn_train_step.mlir; this file makes the parameter updates of that module den-faithful — each emitted SGD op denotes the certified (fderiv/VJP-derived) softmax-CE loss-descent step.

The CNN has two kinds of parameter: the dense classifier head (W₃,W₄,W₅ + biases — structurally a 3-layer MLP over the flattened pool output) and the convolution kernels/biases (W₁,W₂ + biases). The dense head reuses the weightSgd/biasSgd SHlo ops added in LinearFold (its dens certified via the M2 weight_grad_bridge/bias_grad_bridge at the mlpCotOut-style chain cotangents — the head is a 3-layer MLP, so the IR mlpCotOut0/1 apply verbatim). The conv layers use the new core ops convWeightSgd/convBiasSgd (StableHLO.lean): their den is flatten(W − lr·conv2d_weight_grad…) / b − lr·conv2d_bias_grad…, proven = certified by the chain-pinned conv bridges cnn_render_conv{W,b}{1,2}_chain_certified (CnnChainClose.lean) at the cotangents the CNN backward chain actually delivers (cnnChainCotW1/cnnChainCotW2).

(Namespace/name lengths are kept short on purpose: tests/AuditAxioms.lean's three-axiom closure check greps #print axioms output per line, which Lean wraps past ~120 cols — long qualified names would split the benign triple across lines and false-fail the check.)

What is closed here (kernel, [propext, Classical.choice, Quot.sound]) #

Honest residual (the boundary shared with the forward SHlo den) #

Convolution layers — the new convWeightSgd/convBiasSgd ops denote certified #

den (convWeightSgd … (.operand _ c)) is by construction flatten W − lr·conv2d_weight_grad(b,x)·c (and likewise for the bias); pinning c to the cotangent the chain delivers and applying the chain-certified conv bridge gives θ − lr·(certified ∂conv/∂θ · the-chain-cotangent). (The den reduction is definitional — rfl — exactly as LinPoC.poc_weightSgd_den_eq.)

theorem Proofs.CnnPoC.cW2_den {c h w d1 nClasses kH kW : } (xN wN lrStr cotN : String) (b₂ : Vec c) (ac1 ac2 : Tensor3 c (2 * h) (2 * w)) (W₂ : Kernel4 c c kH kW) (W₃ : Mat (c * h * w) d1) (W₄ : Mat d1 d1) (W₅ : Mat d1 nClasses) (h3 h4 : Vec d1) (hc2 : Vec (c * (2 * h) * (2 * w))) (dy : Vec nClasses) (lr : ) (idx : Fin (c * c * kH * kW)) :
StableHLO.den (StableHLO.SHlo.convWeightSgd xN wN lrStr b₂ ac1 W₂ lr (StableHLO.SHlo.operand cotN (cnnChainCotW2 W₃ W₄ W₅ h3 h4 ac2 hc2 dy))) idx = W₂.flatten idx - lr * j : Fin (c * (2 * h) * (2 * w)), pdiv (fun (v' : Vec (c * c * kH * kW)) => (conv2d (Kernel4.unflatten v') b₂ ac1).flatten) W₂.flatten idx j * cnnChainCotW2 W₃ W₄ W₅ h3 h4 ac2 hc2 dy j

Conv-2 weight op = certified. The emitted convWeightSgd for W₂, fed the conv-2 chain cotangent, denotes W₂ − lr·(certified ∂conv2/∂W₂ · chain cot).

theorem Proofs.CnnPoC.cb2_den {c h w d1 nClasses kH kW : } (bN lrStr cotN : String) (ac1 ac2 : Tensor3 c (2 * h) (2 * w)) (W₂ : Kernel4 c c kH kW) (W₃ : Mat (c * h * w) d1) (W₄ : Mat d1 d1) (W₅ : Mat d1 nClasses) (b₂ : Vec c) (h3 h4 : Vec d1) (hc2 : Vec (c * (2 * h) * (2 * w))) (dy : Vec nClasses) (lr : ) (o : Fin c) :
StableHLO.den (StableHLO.SHlo.convBiasSgd bN lrStr W₂ ac1 b₂ lr (StableHLO.SHlo.operand cotN (cnnChainCotW2 W₃ W₄ W₅ h3 h4 ac2 hc2 dy))) o = b₂ o - lr * j : Fin (c * (2 * h) * (2 * w)), pdiv (fun (b' : Vec c) => (conv2d W₂ b' ac1).flatten) b₂ o j * cnnChainCotW2 W₃ W₄ W₅ h3 h4 ac2 hc2 dy j

Conv-2 bias op = certified.

theorem Proofs.CnnPoC.cW1_den {ic c h w kH kW : } (xN wN lrStr cotN : String) (b₁ : Vec c) (x : Tensor3 ic (2 * h) (2 * w)) (W₁ : Kernel4 c ic kH kW) (W₂ : Kernel4 c c kH kW) (hc1 cotW2 : Vec (c * (2 * h) * (2 * w))) (lr : ) (idx : Fin (c * ic * kH * kW)) :
StableHLO.den (StableHLO.SHlo.convWeightSgd xN wN lrStr b₁ x W₁ lr (StableHLO.SHlo.operand cotN (cnnChainCotW1 W₂ hc1 cotW2))) idx = W₁.flatten idx - lr * j : Fin (c * (2 * h) * (2 * w)), pdiv (fun (v' : Vec (c * ic * kH * kW)) => (conv2d (Kernel4.unflatten v') b₁ x).flatten) W₁.flatten idx j * cnnChainCotW1 W₂ hc1 cotW2 j

Conv-1 weight op = certified. The deepest conv layer, at the chain cotangent cnnChainCotW1 (which crosses one more conv-back than conv-2's).

theorem Proofs.CnnPoC.cb1_den {ic c h w kH kW : } (bN lrStr cotN : String) (W₁ : Kernel4 c ic kH kW) (x : Tensor3 ic (2 * h) (2 * w)) (b₁ : Vec c) (W₂ : Kernel4 c c kH kW) (hc1 cotW2 : Vec (c * (2 * h) * (2 * w))) (lr : ) (o : Fin c) :
StableHLO.den (StableHLO.SHlo.convBiasSgd bN lrStr W₁ x b₁ lr (StableHLO.SHlo.operand cotN (cnnChainCotW1 W₂ hc1 cotW2))) o = b₁ o - lr * j : Fin (c * (2 * h) * (2 * w)), pdiv (fun (b' : Vec c) => (conv2d W₁ b' x).flatten) b₁ o j * cnnChainCotW1 W₂ hc1 cotW2 j

Conv-1 bias op = certified.

Dense classifier head — reuse weightSgd/biasSgd (the head is a 3-layer MLP) #

The pool-output pool : Vec (c·h·w) flows through W₃→relu→W₄→relu→W₅; the per-layer cotangents are exactly the IR mlpCotOut0/1 (with (W₅,W₄,W₃) playing the MLP's (W₂,W₁,W₀)). Every head op's den = certified is Cifar8PoC.denseW_den / Cifar8PoC.denseB_den at that layer; only the output-layer weight op is stated here, as the tie reads it.

theorem Proofs.CnnPoC.dW5_den {c h w d1 nClasses : } (aN lrStr dyN : String) (W₃ : Mat (c * h * w) d1) (b₃ : Vec d1) (W₄ : Mat d1 d1) (b₄ : Vec d1) (W₅ : Mat d1 nClasses) (b₅ : Vec nClasses) (pool : Vec (c * h * w)) (dy : Vec nClasses) (lr : ) (i : Fin d1) (j : Fin nClasses) :
StableHLO.den (StableHLO.SHlo.weightSgd aN "%W5" lrStr (relu d1 (dense W₄ b₄ (relu d1 (dense W₃ b₃ pool)))) W₅ lr (StableHLO.SHlo.operand dyN dy)) (finProdFinEquiv (i, j)) = W₅ i j - lr * k : Fin nClasses, pdiv (fun (v : Vec (d1 * nClasses)) => dense (Mat.unflatten v) b₅ (relu d1 (dense W₄ b₄ (relu d1 (dense W₃ b₃ pool))))) W₅.flatten (finProdFinEquiv (i, j)) k * dy k

Output-layer weight op W₅ = certified step (cotangent = the loss cotangent dy).

Tie (dense head) — the top loss cotangent is the composed softmax-CE of the CONV forward #

The cnn *_den_certified above hold for a free top cotangent dy and a free pool output. The renderer feeds the cotangent the emitted loss graph sub(softmaxDiv(expe(logits)), onehot) produces, with logits the REAL conv-forward output mnistCnnNoBnForward … x. The lemma below pins that graph to the composed softmax-CE gradient of the conv forward (the cnn analogue of mlpLossCot_den), and the headline folds the dense output weight W₅ to the whole-loss gradient ∂CE/∂W₅ — so the output layer is tied forward(conv+dense)→softmax-CE→gradient. (The CONV layers W₁/W₂ need the conv backward chain composed — the hand-written selMask4/scatter/convBack rendered as SHlo + the conv cotangent-subgraph pins — which is the bigger conv-side work; see §1a of the planning doc.)

theorem Proofs.CnnPoC.cnnLossCot_den {ic c h w d1 nClasses kH kW : } (nlogN ohN : String) (W₁ : Kernel4 c ic kH kW) (b₁ : Vec c) (W₂ : Kernel4 c c kH kW) (b₂ : Vec c) (W₃ : Mat (c * h * w) d1) (b₃ : Vec d1) (W₄ : Mat d1 d1) (b₄ : Vec d1) (W₅ : Mat d1 nClasses) (b₅ : Vec nClasses) (x : Vec (ic * (2 * h) * (2 * w))) (label : Fin nClasses) :
StableHLO.den ((StableHLO.SHlo.operand nlogN (mnistCnnNoBnForward W₁ b₁ W₂ b₂ W₃ b₃ W₄ b₄ W₅ b₅ x)).expe.softmaxDiv.sub (StableHLO.SHlo.operand ohN (oneHot nClasses label))) = fun (j : Fin nClasses) => softmax nClasses (mnistCnnNoBnForward W₁ b₁ W₂ b₂ W₃ b₃ W₄ b₄ W₅ b₅ x) j - oneHot nClasses label j

The emitted loss-cotangent graph denotes the composed softmax-CE gradient of the CONV forward (= softmax(mnistCnnNoBnForward … x) − onehot = ∂CE/∂logits at the real conv-forward logits).

theorem Proofs.CnnPoC.cnn_W5_tied_totalloss {ic c h w d1 nClasses kH kW : } (aN lrStr dyN : String) (W₁ : Kernel4 c ic kH kW) (b₁ : Vec c) (W₂ : Kernel4 c c kH kW) (b₂ : Vec c) (W₃ : Mat (c * h * w) d1) (b₃ : Vec d1) (W₄ : Mat d1 d1) (b₄ : Vec d1) (W₅ : Mat d1 nClasses) (b₅ : Vec nClasses) (x : Vec (ic * (2 * h) * (2 * w))) (label : Fin nClasses) (lr : ) (i : Fin d1) (j : Fin nClasses) :
StableHLO.den (StableHLO.SHlo.weightSgd aN "%W5" lrStr (relu d1 (dense W₄ b₄ (relu d1 (dense W₃ b₃ (maxPoolFlat c h w (relu (c * (2 * h) * (2 * w)) (flatConv W₂ b₂ (relu (c * (2 * h) * (2 * w)) (flatConv W₁ b₁ x))))))))) W₅ lr (StableHLO.SHlo.operand dyN fun (k : Fin nClasses) => softmax nClasses (mnistCnnNoBnForward W₁ b₁ W₂ b₂ W₃ b₃ W₄ b₄ W₅ b₅ x) k - oneHot nClasses label k)) (finProdFinEquiv (i, j)) = W₅ i j - lr * pdiv (fun (v : Vec (d1 * nClasses)) (x_1 : Fin 1) => crossEntropy nClasses (dense (Mat.unflatten v) b₅ (relu d1 (dense W₄ b₄ (relu d1 (dense W₃ b₃ (maxPoolFlat c h w (relu (c * (2 * h) * (2 * w)) (flatConv W₂ b₂ (relu (c * (2 * h) * (2 * w)) (flatConv W₁ b₁ x)))))))))) label) W₅.flatten (finProdFinEquiv (i, j)) 0

Dense output weight op, tied to the WHOLE softmax-CE loss through the conv forward. With the pool output = the real conv forward (maxPoolFlat ∘ relu ∘ conv₂ ∘ relu ∘ conv₁) and the cotangent the emitted loss graph denotes (cnnLossCot_den), the weightSgd for W₅ denotes W₅ − lr·∂(crossEntropy ∘ forward)/∂W₅.

The CONV fold — the conv kernels/biases tied through the real conv forward #

The four conv *_den theorems above hold for FREE conv activations (ac1/ac2/hc2) and a free cotangent. The capstone below instantiates them at the real conv forward (ac1/hc1/hc2/ac2 = the actual conv₁/relu/conv₂/relu outputs, h3/h4 the dense pre-acts the head-backward reads) and the composed top cotangent g = softmax(mnistCnnNoBnForward x) − onehot (cnnLossCot_den). So all four conv param ops denote θ − lr·(certified ∂convₖ/∂θ · the conv backward-chain cotangent the real loss drives)cnnChainCotW2 for conv₂, cnnChainCotW1 W₂ hc1 cotW2 for conv₁ (it crosses one more conv-back). Together with the dense head (cnn_W5_tied_totalloss + the *_den at the composed cotangent) the WHOLE cnn train step is now den-composed forward→loss→backward — no free activations, no symbolic cotangent. (Residual: the conv backward is rendered hand-written, so the cotangent SSA ↔ cnnChainCot correspondence is the per-op trust, same kind the whole suite carries; making it a printed SHlo subgraph with a den pin — the cnn analogue of MlpPoC.cot{0,1}_den — is the polish.)

theorem Proofs.CnnPoC.cnn_conv_tied_certified {ic c h w d1 nClasses kH kW : } (xN wN bN lrStr cotN : String) (W₁ : Kernel4 c ic kH kW) (b₁ : Vec c) (W₂ : Kernel4 c c kH kW) (b₂ : Vec c) (W₃ : Mat (c * h * w) d1) (b₃ : Vec d1) (W₄ : Mat d1 d1) (b₄ : Vec d1) (W₅ : Mat d1 nClasses) (b₅ : Vec nClasses) (x : Tensor3 ic (2 * h) (2 * w)) (label : Fin nClasses) (lr : ) :
have xv := x.flatten; have hc1 := flatConv W₁ b₁ xv; have ac1v := relu (c * (2 * h) * (2 * w)) hc1; have ac1 := Tensor3.unflatten ac1v; have hc2 := flatConv W₂ b₂ ac1v; have ac2v := relu (c * (2 * h) * (2 * w)) hc2; have ac2 := Tensor3.unflatten ac2v; have pool := maxPoolFlat c h w ac2v; have h3 := dense W₃ b₃ pool; have h4 := dense W₄ b₄ (relu d1 h3); have g := fun (k : Fin nClasses) => softmax nClasses (mnistCnnNoBnForward W₁ b₁ W₂ b₂ W₃ b₃ W₄ b₄ W₅ b₅ xv) k - oneHot nClasses label k; have cotW2 := cnnChainCotW2 W₃ W₄ W₅ h3 h4 ac2 hc2 g; ConvWSgdTied xN wN lrStr cotN b₂ ac1 W₂ cotW2 lr ConvBSgdTied bN lrStr cotN W₂ ac1 b₂ cotW2 lr ConvWSgdTied xN wN lrStr cotN b₁ x W₁ (cnnChainCotW1 W₂ hc1 cotW2) lr ConvBSgdTied bN lrStr cotN W₁ x b₁ (cnnChainCotW1 W₂ hc1 cotW2) lr

Whole cnn conv tail, tied. All four conv kernel/bias ops, at the real conv forward and the composed softmax-CE cotangent, denote the certified loss-descent step.