Representation learning · E29 · Implementation

Train one convolutional block at a time

Greedy local contrastive learning freezes earlier blocks and gives the current block its own projection head. That is different from splitting an ordinary backward pass.

PyTorch blocksCustom local loopRidge probe
A local objective updates the current block; an earlier frozen prefix supplies features without receiving that parameter update.
Figure 1. Stop the gradient here. A local objective updates the current block; an earlier frozen prefix supplies features without receiving that parameter update. Local-learning schematic. Original vector illustration.

Follow the information

From input to outcome

The frozen prefix supplies activations but owns no parameter update. The current block and local projection own the local objective. Advancing to a new block is a training-stage change, not another layer after the loss.

The frozen prefix supplies activations but owns no parameter update. The current block and local projection own the local objective. Advancing to a new block is a training-stage change, not another layer after the loss.
Figure 2. Information flow. Solid arrows carry observations, tensors or artifacts; other routes are explicitly labelled. Signal shapes, matrices and network icons are schematic, not measured samples or literal neuron counts. Open full-size SVG ↗ On narrow screens, scroll the diagram horizontally.

Read this alongside Figure 1: A local objective updates the current block; an earlier frozen prefix supplies features without receiving that parameter update. The module map and layer-level figures below expand the operations in this route.

Train one convolutional block at a time: architectureTwo augmented images: Shared sample identities → Frozen prefix: Earlier convolutional blocks → Current block: Only this block learns → Local projection head: Pooled contrastive embedding → NT-Xent loss: Batch positives / negatives → Next block: Freeze prefix and advance. A high-level module map; comparison branches and training details are explained in the article.REPRESENTATION LEARNING / E29 / MODULE MAP01 INPUTTwo augmented imagesShared sample identities02 MODULEFrozen prefixEarlier convolutional blocks03 MODULECurrent blockOnly this block learns04 MODULELocal projection headPooled contrastive embedding05 MODULENT-Xent lossBatch positives / negatives06 OUTPUTNext blockFreeze prefix and advance
Source-grounded module map. Boxes summarize operations, not individual neurons; comparison arms and training paths are detailed below. On a small screen, scroll the diagram horizontally.
Two augmented images — Shared sample identities

The architecture in context

The system we are building

The experiment builds a hierarchy without propagating a single end-to-end supervised loss through every layer. For each block, it applies the already trained prefix without gradients, learns the current block through a local contrastive head, then freezes that block before continuing. The projection head serves the local training objective rather than becoming the final classifier.

Who does what in the stack

PyTorch blocks
Convolution, batch normalization, pooling and activation.
Custom local loop
Freezes the prefix and trains the current projection objective.
Ridge probe
Evaluates the final representation without fine-tuning.

The custom training loop controls which modules are in evaluation mode, which parameters enter the optimizer and where gradients stop. PyTorch supplies convolution, normalization and differentiation. Locality here describes the optimization boundary, not an absence of gradients.

Framework responsibility map. Each row maps a library or custom component to its job; rows are not a sequential inference graph.
Framework responsibility map. Each row maps a library or custom component to its job; rows are not a sequential inference graph. Open full-size SVG ↗

From module map to executable structure

Inside Greedy local contrastive CNN

CIFAR-10, three blocks with channels 32,64,128; two 3×3 convolutions per block.

Layer-level implementation. B denotes batch size; parameter and shape conventions are expanded in the table.
Layer-level implementation. B denotes batch size; parameter and shape conventions are expanded in the table. Open full-size SVG ↗
Layer / tensor / operation ledger
Layer or branchOutput shapeImplementation detail
Block 1: 3→32→32B × 32 × 16 × 16Each convolution → BatchNorm → ReLU; finish with average pool 2.
Block 2: 32→64→64B × 64 × 8 × 8Train after block 1 is frozen and in eval mode.
Block 3: 64→128→128B × 128 × 4 × 4Train after both preceding blocks are frozen.
Temporary local headB × 128At the current block: pool → Linear(c,256) → ReLU → Linear(256,128).
Frozen feature probeClass scoresLocal projection head is not the final ridge classifier.

This is a different learning rule from a blockwise reverse sweep. A local loss updates only the current block and its projection head. Previously trained blocks receive neither an input gradient nor an optimizer step. The NT-Xent matrix contains 2B views, excludes self-similarity and identifies the matching view by a half-batch offset.

The equation and the update

θb←θb−η∇θbLb(gb(fb(stopgrad⁡(hb−1))))\theta_b\leftarrow\theta_b-\eta\nabla_{\theta_b}\mathcal L_b(g_b(f_b(\operatorname{stopgrad}(h_{b-1}))))

Configured 80 epochs per block, batch 512, Adam 1e-3, temperature .2; subset 20,000. The saved comparison is negative versus random convolutional features. Do not reinterpret the training budget as evidence of a benefit.

Learning or solution path. A parameter-update path is different from the forward inference path; see text for target-network, frozen-feature and local-loss boundaries.
Learning or solution path. A parameter-update path is different from the forward inference path; see text for target-network, frozen-feature and local-loss boundaries. Open full-size SVG ↗

Implementation card / no invented benchmarks

Capacity, budget and execution evidence

Parameters / retained state
287,904 backbone scalars. Each temporary projection head adds 256(c+1)+128×257; heads are not all simultaneously trained.
Duration and hardware evidence
E30 records 326.8 seconds for its saved experiment on MPS; this is not a per-block or per-epoch timing and does not identify the current workstation.
Source coordinates
E29 lines 24–25 and 50–84
Current reproduction context
Current workstation, supplied by the author: Apple M4, 128 GB unified RAM, 40 GPU cores and 16 CPU cores. This is context for prospective reproduction, not attribution of every archived run. Python and framework versions are not fully locked for these historical sources; declarations, when available, are identified separately.

Counts above are calculated from the stated layer shapes unless identified as saved measurements. They exclude optimizer state and nontrainable buffers. No archived training was rerun for this revision.

What these design choices change

Greedy freezing can reduce simultaneously trainable state but may discard information that later blocks need. A useful local contrastive objective is not automatically a useful end-to-end representation. The supervised and random-feature controls are necessary to interpret the negative result.

Reproduction and measurement protocol

After one optimizer step, compare every earlier block’s parameters and BatchNorm buffers byte-for-byte. Check that only the current block and projection head move. A zero parameter gradient is insufficient if running statistics continue changing.

For a new run, save the resolved Python/framework versions, backend, dtype, seed, input shapes, batch size and exact source revision. Start with one batch and one update. Log training steps separately from epochs or environment steps. Do not equate the configured maximum with a completed budget or convergence.

Measure initialization/compilation, data preparation, warmed forward pass, training updates and evaluation separately. Synchronize accelerator work around timed regions using the chosen framework’s supported mechanism. Report peak process memory and framework allocation separately; parameter bytes exclude activations, gradients, optimizer state and input buffers. On a shared machine, begin with a single CPU worker and a small batch rather than claiming all available resources.

A closer look at the implementation

The code that carries the idea

The excerpt evaluates prior blocks under no_grad and trains only the current block/head pair. Evaluation mode is important as well as no_grad: otherwise running normalization statistics could change in a supposedly frozen prefix. Later layers inherit whatever information earlier local objectives preserve.

Python · file · lines 67–85
def train_unsup_local(Xtr):
    """Greedy: train each block on a LOCAL contrastive loss; input = frozen prev blocks; NO global backward."""
    blocks = []; N = len(Xtr)
    for bi, cout in enumerate(CH):
        cin = 3 if bi == 0 else CH[bi - 1]
        blk = block(cin, cout).to(DEV); head = proj_head(cout).to(DEV)
        opt = torch.optim.Adam(list(blk.parameters()) + list(head.parameters()), 1e-3)
        for ep in range(EP_BLOCK):
            pm = torch.randperm(N, device=DEV)
            for i in range(0, N, BS):
                idx = pm[i:i + BS]; xb = Xtr[idx]
                v1, v2 = augment(xb), augment(xb)
                with torch.no_grad():
                    for pblk in blocks: v1 = pblk(v1); v2 = pblk(v2)            # frozen previous blocks (detached)
                opt.zero_grad(); loss = nt_xent(head(blk(v1)), head(blk(v2))); loss.backward(); opt.step()
        for p in blk.parameters(): p.requires_grad_(False)
        blk.eval(); blocks.append(blk)
        print(f"  [unsup-local] block {bi} (ch {cout}) trained, last loss={loss.item():.3f}", flush=True)
    return blocks

Verbatim archive excerpt from closed_form_neat_biolocal.py. Context-dependent historical code, not a standalone runnable program. Comments retain their original wording; the article distinguishes implemented behavior from stale or overbroad comments.

The boundary that matters

Local objectives need not align with the final task. A low contrastive loss at each layer does not guarantee useful class features after composition. The saved CIFAR-10 result is discussed separately rather than being implied by the architecture.

Keep building

Other posts of interest