Siamese Neural Networks
Architecture, variants and theory — from Bell Labs signature verification (1993) to modern xBD pre/post damage assessment.
What is a Siamese network? Two identical copies of the same neural network running side by side. Both copies share the exact same weights — that is what "Siamese" means here (like identical twins). You feed one copy the "before" image and the other copy the "after" image, then compare what each one saw.
Why two copies? For disaster damage we don't really care what a building looks like in isolation — we care about what changed between the pre-event and post-event photo. The cleanest way to ask "what changed?" is to run both photos through the same eyes (same weights), then subtract the answers. That subtraction is the difference block: D = | F_post − F_pre |.
The four ideas you need before reading on:
- Shared weights — both branches use the same parameters θ. If one branch learns "this is a roof", the other branch automatically learns the same. Without sharing, the two branches would drift apart and the comparison would be meaningless.
- Feature map — what comes out of an encoder. Not a picture, but a stack of numerical maps where each map highlights a different pattern (edges, textures, roofs, debris…).
- Difference block — the absolute difference of the two feature maps. Large values mean "this patch changed a lot" — exactly the change-detection signal we want.
- Skip + attention — the difference is fed sideways into a U-Net decoder via skip connections, while attention gates suppress noisy areas (clouds, shadows) so the model focuses on real structural change.
Analogy: a Siamese network is like asking two identical inspectors to walk the same street before and after a storm, then computing the difference in their notes. Because the inspectors are clones, any disagreement is evidence of real change — not personal style.

- ① Pre-event image (tpre) — a satellite photo of a neighbourhood before the disaster. Top (teal) branch of the network.
- ② Post-event image (tpost) — the same geographic location after the disaster. Bottom (orange) branch. Both images must be co-registered (aligned pixel-for-pixel).
- ③ Shared Encoder (ResNet / ViT) — weights θ — a CNN or ViT backbone that turns each image into a compact feature map. The dashed "SHARED (weights tied)" arrow means both branches are literally the same network, just run twice. Updating θ updates both branches at once → pre and post are described in the same feature space, which is what makes subtraction meaningful.
- ④ Fpre and Fpost — the two feature tensors that come out of the encoder. Each one summarises "what is in this image" at a coarse spatial resolution (e.g. 32×32×512).
- ⑤ Difference Block |Fpre − Fpost| — element-wise absolute difference. Pixels that did not change cancel out (→ 0); pixels that changed (collapsed roofs, debris) light up. This is the change signal the decoder will turn into a damage map.
- ⑥ Skip connections (dashed arrows into the decoder) — U-Net style. They route fine-grained features from each encoder stage directly to the matching decoder stage, restoring the spatial detail that pooling discarded → sharp building outlines instead of blobs.
- ⑦ Attention Gates (the @ boxes) — small learned gates on each skip connection that ask "which skip pixels are actually relevant for this output region?" and suppress the rest. Cuts false positives on background (roads, trees).
- ⑧ U-Net Decoder — progressively upsamples the difference features back to full image resolution, fusing them with the gated skip features at every stage.
- ⑨ Damage Map (5 classes) — the final per-pixel prediction, colour-coded: green = no damage, yellow = minor, orange = major, red = destroyed, purple = un-classified. Argmax over the 5 channels gives each pixel its label.
- Arrow legend — solid teal/orange = the two image branches, solid black = feature flow, dashed blue = U-Net skip connection, @ = attention gate. The down-arrow inside the decoder is downsampling, the up-arrow is upsampling.
What is a Siamese Network?
Origins, intuition, and the core idea.
A Siamese network is a neural architecture containing two (or more) identical sub-networks that share the same weights. Each branch processes a different input; their outputs are compared to produce a similarity score or difference map.
- Bromley et al. (1993) — Bell Labs, signature verification. The original "Siamese" twin network.
- Koch et al. (2015) — one-shot image classification via contrastive loss.
- Modern uses — face verification (FaceID-style), satellite change detection (xBD), medical image comparison, self-supervised learning (SimSiam, BYOL).
Core Architecture for xBD Damage Assessment
Pre-event (t₁) ──▶ [Shared Encoder θ] ──▶ F_pre ┐
├─▶ Difference Block |F_pre − F_post| ─▶ Decoder ─▶ Damage map (5 classes)
Post-event (t₂) ──▶ [Shared Encoder θ] ──▶ F_post ┘ │
▼
Combined Loss = Focal + Dice
│
◀──── Backprop (updates ONE shared encoder θ) ────- Pre/post inputs — 512×512×3 satellite images of the same geographic location at two times.
- Shared encoder — ResNet-50/101 or ViT, parameters θ₁ = θ₂. Output: feature maps [C × H/8 × W/8].
- Difference block — |Fpre − Fpost| (or learned fusion). Highlights what changed.
- Decoder — upsamples H/8 → H/4 → H/2 → H with skip connections from encoder stages.
- Output — [5 × H × W] tensor; argmax gives per-pixel damage class.
Inside the Shared Encoder
Conv → BN → ReLU → pool, four times.
Input 512×512×3
→ Conv Block 1 → 256×256×64 (stride 2, 64 filters)
→ Conv Block 2 → 128×128×128
→ Conv Block 3 → 64×64×256
→ Conv Block 4 → 32×32×512 ← Feature map for comparisonDecoder — Skip Connections & Attention Gates
U-Net-style skip connections route encoder features directly to the matching decoder stage, restoring spatial detail that pooling discarded.
Decoder stage k:
u_k = Upsample( d_{k-1} ) # coarse decoder features
skip_k = Encoder feature at same resolution
d_k = Conv( Concat[ u_k, AttentionGate(skip_k, u_k) ] )
# Attention gate (Oktay et al., 2018)
α = σ( ψ( ReLU( W_x · x + W_g · g ) ) ) # gate ∈ [0,1] per pixel
gated_skip = α ⊙ x The attention gate lets the decoder ask "which encoder pixels are relevant here?" and suppresses the rest — sharper boundaries, fewer false positives on background.
Loss Functions for Siamese Training
| Loss | Formula | When to use |
|---|---|---|
| Contrastive | L = y·D² + (1−y)·max(0, m−D)² | Pairwise similarity (face verify, signature) |
| Triplet | L = max(0, D(a,p) − D(a,n) + m) | Anchor/positive/negative — face recognition |
| Focal | −α(1−pₜ)^γ log(pₜ) | Imbalanced segmentation (damage) |
| Dice | 1 − 2|A∩B| / (|A|+|B|) | Region overlap — boundary quality |
| Focal + Dice | L_F + L_D | DAHiTrA / xBD damage assessment ✓ |
Siamese Variants
| Variant | Key idea | When to use |
|---|---|---|
| Classic Siamese | Two identical branches, shared θ | Same modality, same domain (xBD) |
| Pseudo-Siamese | Same architecture, separate θ | Slight domain shift (e.g. different sensors) |
| Asymmetric | Different architectures per branch | Cross-modality (RGB vs SAR, optical vs infrared) |
| Triplet | Three branches (anchor/pos/neg), shared θ | Metric learning, face recognition |
| Self-supervised (SimSiam / BYOL / DINO) | Two augmented views of one image | Label-scarce pre-training |
Vanishing Gradient — Cause & Cure
Why deep Siamese stacks need help, and how skip connections + deep supervision fix it.
- — activation entering layer \ell
- — the transformation applied by layer \ell
- — total depth
The gradient at the input is a product of L Jacobians. If most of them have magnitude less than 1, the product shrinks geometrically — by the time it reaches there is almost nothing left to learn from. This is the classic vanishing-gradient problem.
- — skip connection — adds the input back to the layer's output
- — identity matrix that the skip contributes to the Jacobian
Each factor in the product is now at least the identity. Even if a layer's learned mapping has tiny gradient, the guarantees a healthy floor — gradient survives all the way back to the early Siamese encoder layers.
Deep supervision goes further by attaching auxiliary Focal+Dice losses at intermediate decoder stages, so early encoder layers get strong gradient signal directly — not only through a long chain.
PyTorch — Minimal Siamese Skeleton
python1import torch, torch.nn as nn23class SharedEncoder(nn.Module):4 def __init__(self):5 super().__init__()6 # e.g. ResNet-50 backbone, output [B, 512, H/8, W/8]7 self.backbone = build_resnet50_encoder()89 def forward(self, x):10 return self.backbone(x)1112class SiameseDamageNet(nn.Module):13 def __init__(self, num_classes=5):14 super().__init__()15 self.encoder = SharedEncoder() # ONE encoder, used twice16 self.decoder = UNetDecoder(in_channels=512, out_channels=num_classes)1718 def forward(self, pre, post):19 f_pre = self.encoder(pre) # shared weights θ20 f_post = self.encoder(post) # same θ — literally same nn.Module21 diff = torch.abs(f_pre - f_post) # change signal22 return self.decoder(diff) # [B, 5, H, W] damage logits2324# Training (with Focal + Dice from L2)25model = SiameseDamageNet().cuda()26optim = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)2728for pre, post, mask in loader:29 logits = model(pre.cuda(), post.cuda())30 loss = focal_fn(logits, mask.cuda().long()) + dice_fn(logits, one_hot(mask))31 optim.zero_grad(); loss.backward(); optim.step()
Which variant for which task?
| Task | Recommended variant | Loss |
|---|---|---|
| xBD building damage (pre/post) | Classic Siamese + UNet decoder | Focal + Dice + deep supervision |
| Signature / face verification | Classic Siamese, embedding head | Contrastive or Triplet |
| Optical vs SAR change detection | Asymmetric Siamese | Focal + Dice |
| Self-supervised pre-training | SimSiam / BYOL / DINO | Cosine similarity (no negatives) |
| One-shot classification | Classic Siamese | Contrastive |
Triplet Loss — pull positives close, push negatives away
The standard metric-learning loss for Siamese / verification networks.
A triplet is three samples: an anchor (e.g. a building in the pre-event image), a positive (the same building in the post-event image — should look similar in feature space), and a negative (a different building, or a destroyed one — should look far away). Each sample is pushed through the same encoder to an embedding vector.
- Margin — the gap (typically 0.2–1.0) that the negative must exceed the positive by. Without it, the trivial solution would minimise the loss.
- Hinge / max(0,·) — once the negative is already further than the positive, the triplet contributes zero gradient. Training focuses only on still-violating triplets.
- Gradient intuition — for a violating triplet, the encoder is pulled so and pushed so away from , simultaneously.
| Strategy | Definition | Use when |
|---|---|---|
| Random | Sample (a,p,n) uniformly at random. | Quick sanity tests; converges slowly. |
| Easy | n is already far away → loss ≈ 0. | Avoid — provides no gradient. |
| Semi-hard | ‖f(a)−f(n)‖ > ‖f(a)−f(p)‖ but still inside the margin. | FaceNet-style training — best stability. |
| Hard | n closer to a than p is. | Strong signal but can collapse without warm-up. |
python1import torch, torch.nn as nn, torch.nn.functional as F23class TripletLoss(nn.Module):4 def __init__(self, margin=0.3):5 super().__init__()6 self.margin = margin7 def forward(self, anchor, positive, negative):8 # L2-normalise so distances live on the unit sphere (recommended)9 a = F.normalize(anchor, dim=1)10 p = F.normalize(positive, dim=1)11 n = F.normalize(negative, dim=1)12 d_pos = (a - p).pow(2).sum(dim=1) # ‖a - p‖²13 d_neg = (a - n).pow(2).sum(dim=1) # ‖a - n‖²14 return F.relu(d_pos - d_neg + self.margin).mean()1516# Use with a triplet sampler that emits (anchor, positive, negative) batches17# from the xBD index — same building (pre vs post) = positive,18# different building = negative.
Cosine Distance — angle, not magnitude
The default metric for embedding spaces.
Cosine similarity measures the angle between two vectors, ignoring how long they are:
If both vectors are L2-normalised (i.e. ), then cosine similarity collapses to the plain dot product: . In code that's one line:
python1u = F.normalize(u, dim=1)2v = F.normalize(v, dim=1)3sim = (u * v).sum(dim=1) # ∈ [-1, 1]4dist = 1.0 - sim # ∈ [0, 2]
For unit vectors, the squared Euclidean distance is exactly twice the cosine distance:
So a triplet loss with L2-normalised embeddings is mathematically a triplet loss in cosine space. Most modern face/verification systems do exactly this.
| When to use | Cosine distance | Euclidean (L2) distance |
|---|---|---|
| Embedding magnitude is meaningless (text, image features) | Yes — angle is all that matters | No — long vectors dominate |
| Pixel-space comparison | No — direction ignores intensity | Yes — captures absolute change |
| Mixed-norm vectors (different L2 lengths) | Yes — magnitude-invariant | No — bias toward larger vectors |
| High-dimensional embeddings (CLIP, ViT, face nets) | Yes — the de-facto standard | Use only after L2-normalisation |
- Siamese
- — two identical branches sharing the same weights, run on two inputs.
- θ (theta)
- — the network's learnable weights. "Shared θ" means both branches use the same numbers.
- F_pre, F_post
- — feature maps (stacks of pattern maps) from the pre- and post-event images.
- Difference block
- —
|F_pre − F_post|: bright where pixels changed, zero where they didn't. - U-Net
- — encoder-decoder shape with skip connections; standard for image-to-image tasks.
- Skip connection
- — wire from an early encoder layer straight to the decoder, restoring fine detail.
- Attention gate
- — learned 0–1 mask on a skip that says "use this region, ignore that one".
- ResNet
- — CNN family with residual (skip) connections; the "50" = 50 layers deep.
- ViT
- — Vision Transformer: encodes images as a sequence of patches with self-attention.
- BN (BatchNorm)
- — normalises activations per mini-batch so training stays stable.
- ReLU
- — activation
max(0,x): keep positives, zero negatives. - Jacobian
- — matrix of partial derivatives of a layer's output w.r.t. its input.
- Vanishing gradient
- — gradient shrinks toward 0 as it travels back through many layers → early layers stop learning.
- Contrastive / Triplet
- — Siamese losses that pull "same" pairs close and push "different" pairs apart in feature space.