Quant Memo
Core

U-Net: Encoder-Decoder With Skip Connections

A U-Net compresses an input down to its coarse shape and then rebuilds it at full resolution, but rebuilding from the compressed version alone loses fine detail. Skip connections smuggle that detail across directly, from each downsampling step to its matching upsampling step.

Prerequisites: Convolutional Neural Networks, ResNet and Identity Mappings

Some tasks need output at the same resolution as the input, but built from global understanding: label every pixel of a medical scan as tumor or not, denoise every point of a volatility surface while respecting the overall shape of the smile, forecast every minute of an order-book snapshot while conditioning on the whole session's regime. A plain encoder that compresses the input down to a small bottleneck and a decoder that expands it back up struggles here — every time you downsample, you throw away exactly the fine-grained detail (the sharp edge, the single spiky quote, the exact strike) that the final output needs to reproduce. The bottleneck captures the big picture but is a lossy funnel for detail, and no amount of decoder cleverness can invent detail that was thrown away three layers earlier.

The analogy: tracing paper over a rough sketch

An architect blocks out a building's overall shape on a small napkin sketch — just the rough massing, nothing precise. To turn that into a construction drawing, she doesn't work from the napkin alone; she lays tracing paper over her original, detailed site survey and uses the napkin's shape as a guide for where the big structures go, while copying exact boundary lines straight from the survey underneath. The final drawing combines coarse guidance (from the compressed sketch) with fine detail (copied directly from the original, at the same points). A U-Net does exactly this: its decoder gets both the compressed "what's the overall shape" signal from the bottleneck and a direct copy of the fine detail from the matching-resolution layer on the way down, spliced in via a skip connection.

The maths

A U-Net has two halves. The encoder (contracting path) repeatedly applies convolutions and downsampling (usually max-pooling), halving spatial resolution at each stage while doubling the number of channels:

h=Pool(Conv(h1))h_\ell = \text{Pool}(\text{Conv}(h_{\ell-1}))

In words: at stage \ell, run a convolution over the previous stage's output h1h_{\ell-1}, then shrink it — this builds a stack of feature maps at successively coarser resolutions, ending in a small, information-dense bottleneck.

The decoder (expanding path) reverses this, upsampling back toward the original resolution:

u=Conv(concat[Upsample(u+1), h])u_\ell = \text{Conv}\big(\text{concat}[\text{Upsample}(u_{\ell+1}),\ h_\ell]\big)

In words: take the decoder's previous (coarser) output u+1u_{\ell+1}, upsample it back to stage \ell's resolution, then concatenate it — stack it channel-wise, side by side, not add it — with hh_\ell, the encoder's output at that exact same resolution, saved earlier via the skip connection. Run a convolution over the combined stack. The key symbol is hh_\ell: it is a value computed early in the forward pass and carried, unchanged, all the way across the network to be used late in the forward pass. Nothing about it passes through the bottleneck.

bottleneck skip connections (concatenated, not added) encoder decoder
The encoder (left) compresses toward a bottleneck; the decoder (right) expands back out. Dashed lines are skip connections carrying fine detail directly across, bypassing the bottleneck entirely.

Worked example 1: why concatenation beats "decoder alone"

Suppose a 1-D toy input has 8 points, and one of them is a sharp spike: x=(1,1,1,9,1,1,1,1)x = (1, 1, 1, 9, 1, 1, 1, 1). Downsample by average-pooling in pairs:

pool=(1.0, 5.0, 1.0, 1.0)\text{pool} = (1.0,\ 5.0,\ 1.0,\ 1.0)

Already, the spike's exact location and height are smeared into the value 5.0 shared with its neighbor. Pool again to 2 points, and the spike is barely a bump in an average. A decoder trying to reconstruct the original 8-point series from that heavily pooled bottleneck alone has to guess where the spike belongs and how tall it truly was — it can only recover a blurry approximation.

Now suppose the encoder saved h1=(1.0,5.0,1.0,1.0)h_1 = (1.0, 5.0, 1.0, 1.0) — the first pooling stage's output — as a skip connection. When the decoder upsamples the bottleneck back to 4 points and concatenates h1h_1, it has direct access to the actual pooled value 5.0 at the right position, not a doubly-blurred guess. Each additional skip level recovers one more level of the detail that a bottleneck-only path destroyed. That's the entire mechanism, just repeated at every resolution.

Worked example 2: counting channels through a skip

Say encoder stage 2 outputs h2h_2 with 128 channels at a 32×3232\times32 resolution. The decoder's matching stage upsamples the previous decoder output (256 channels at 16×1616\times16) to 32×3232\times32, keeping 256 channels. Concatenating gives:

256+128=384 channels at 32×32256 + 128 = 384 \text{ channels at } 32\times32

A convolution at that decoder stage must therefore be built to accept 384 input channels, not just the 256 the upsampling alone produced — this is the single most common shape bug when implementing a U-Net from scratch: forgetting that concatenation adds channel counts, so every decoder convolution's input-channel dimension must be sized for "upsampled channels + skip channels," not just one or the other.

Matrix explorer
dashed = before · solid = after
det 1.25trace 2.50λ 1.81, 0.69area scales by 1.25×

A convolution's learned filters are just structured linear maps; watching how a transform stretches, rotates, and can lose information along a collapsed axis is a useful mental model for what repeated pooling does to spatial detail — and why the skip connection's job is to hand back exactly the axis the bottleneck flattened away.

A U-Net's bottleneck captures what is in the input at a coarse, global level. Its skip connections separately carry where exactly and how precisely, at every resolution, straight from encoder to decoder. Remove the skips and you still have a working encoder-decoder — you've just thrown away every output's ability to be sharp.

What this means in practice

U-Net-style architectures show up anywhere the required output is dense and full-resolution while still needing global context: denoising or completing a volatility surface, segmenting anomalous regions in a heatmap of order-flow imbalance, or reconstructing a corrupted price panel. The architecture is a strict superset of a plain autoencoder — same bottleneck idea, plus the skip highway — so anywhere you'd reach for an autoencoder but the output needs to preserve fine local structure that the input had, a U-Net is very likely the better default.

The trap: treating the skip connection as if it were a ResNet-style addition. It isn't — U-Net skips are concatenations, which change the channel count the next layer must accept, whereas ResNet skips are additions, which require matching shapes and leave channel count untouched. Confusing the two either crashes with a shape-mismatch error (trying to add tensors of different channel depth) or silently drops information (concatenating when you meant to add, doubling parameters for no benefit). A second confusion: skip connections carry raw encoder features, which were computed before the corresponding decoder stage's learned processing — passing them through an activation or normalization inconsistent with the decoder's expectations is a frequent, hard-to-spot bug.

Related concepts

Practice in interviews

Further reading

  • Ronneberger, Fischer & Brox, U-Net: Convolutional Networks for Biomedical Image Segmentation (2015)
  • Goodfellow, Bengio & Courville, Deep Learning, ch. 9
ShareTwitterLinkedIn