The masked autoencoder learned to reconstruct cat photos. Satellites do not photograph cats — they photograph the same patch of Earth across thirteen wavelengths and across months of time. SatMAE asks: what is the right way to mask, embed, and reconstruct that?
You are an analyst at an agriculture ministry. You have petabytes of Sentinel-2 imagery — a European satellite that photographs every point on Earth roughly every five days, in thirteen spectral bands (not three RGB channels, but thirteen wavelengths from deep blue to shortwave infrared). You want a model that can map cropland, predict yields, and flag deforestation. You have almost no labels — labelling a single field requires a person to walk it. But you have an ocean of unlabelled pixels.
This is exactly the setting where self-supervised pre-training wins: learn a good representation from the unlabelled ocean, then fine-tune on the tiny labelled puddle. In natural images, the reigning recipe in 2022 was the Masked Autoencoder (MAE) of He et al.: hide 75% of the image patches, ask a Vision Transformer to reconstruct the missing pixels, and throw the decoder away — the encoder is now a strong feature extractor.
So you take MAE off the shelf and point it at Sentinel-2. It works... poorly. Why? Because MAE was designed for a photograph: one image, three colour channels (R, G, B), one moment in time. Satellite data violates all three assumptions, and the violations are not cosmetic.
Left: the world MAE was built for — one frame, three channels. Right: a satellite observation — the SAME ground patch stacked across many wavelengths and many revisit dates. Click "Reveal" to peel the cube apart.
Consider the three mismatches concretely:
| Assumption in plain MAE | Reality in satellite imagery | Consequence if ignored |
|---|---|---|
| 3 channels (R,G,B), tightly correlated | 13 bands at very different resolutions (10m, 20m, 60m) and very different physics (visible vs near-IR vs SWIR) | Stacking all 13 into one patch embedding forces unrelated wavelengths to share weights — the model averages away the spectral signal that distinguishes water from concrete |
| One image, one timestamp | A time series: the same field in March (bare soil), June (green crop), September (harvested) | Treating each date as an independent image throws away the temporal structure that is often the most label-relevant signal (phenology) |
| Random masking over a single 2D grid | Masking must decide: do all timestamps share the same mask? do all bands share the same mask? | A naive shared mask lets the model "cheat" — copy the visible co-located band/time instead of learning to predict |
SatMAE's thesis, in one sentence: keep the MAE recipe, but redesign three things — the input grouping, the positional/temporal embeddings, and the masking strategy — so that the encoder is forced to learn spectral and temporal structure rather than route around it. Everything in this paper is a careful answer to "how should a masked autoencoder treat a band, a timestamp, and a mask?"
By the end of this Veanor you will be able to implement SatMAE's two signature mechanisms — spectral group masking and the MAE reconstruction loss on masked patches — from scratch in NumPy (both are Code Labs), and you will understand exactly why each design choice was made.
SatMAE is a modification of MAE, so we must first nail the base recipe. The Masked Autoencoder is an asymmetric encoder-decoder that learns by filling in the blanks.
Start with an image of resolution H×W. A Vision Transformer chops it into a grid of non-overlapping patches — say 16×16 pixels each — giving N = (H/16)(W/16) patch tokens. For a 224×224 image that is N = 14×14 = 196 patches. Each patch is flattened and linearly projected to a token of dimension D (e.g. D = 1024 for ViT-Large).
Now the masking. Choose a high masking ratio ρ (MAE uses ρ = 0.75). Sample a random subset of the N tokens to keep visible; the rest are masked. The numbers matter:
The asymmetry is the key engineering decision: the encoder sees only the visible tokens. With ρ = 0.75, the encoder processes only Nvis = 0.25N = 49 tokens instead of 196. Attention is O(N2), so processing a quarter of the tokens makes the encoder roughly 16× cheaper — this is why MAE can afford a huge encoder and pre-train fast.
After the encoder, a shared learned [MASK] token is inserted at every masked position (so the decoder sees a full-length sequence of N tokens again), positional embeddings are added back, and a small, shallow decoder reconstructs the pixels of the masked patches. Then — crucially — the decoder is discarded. Only the encoder survives into fine-tuning.
A 196-patch grid. Slide the masking ratio ρ and watch how few tokens the encoder actually processes (teal = visible, faded = masked). At ρ=0.75 only ~49 tokens reach the encoder — the source of MAE's speed.
Worked example. Take a 96×96 satellite chip with patch size p = 8. Then the grid is (96/8)×(96/8) = 12×12 = 144 patches. With ρ = 0.75: Nvis = 0.25×144 = 36 visible tokens and Nmask = 108 masked tokens. The encoder runs self-attention over 36 tokens (36² = 1296 attention entries) instead of 144 (144² = 20,736) — a 16× reduction. The decoder, by contrast, must process all 144 positions, which is why it is kept deliberately small.
SatMAE inherits this entire skeleton: asymmetric encoder-decoder, high mask ratio, predict-the-masked-pixels objective, discard-the-decoder. What it changes is how the tokens are formed (Chapters 3-4) and how the mask is chosen (Chapter 5). Everything else is MAE.
The entire learning signal in SatMAE (and MAE) comes from one loss. Get this exactly right and the rest of the paper is plumbing. The objective is a mean squared error between predicted and true pixels, computed on the masked patches only. Let us build it up term by term.
Let the decoder output, for each patch i, a vector of predicted pixel values x̂i ∈ ℝP, where P = p·p·C is the number of pixels-times-channels in a patch (for a 16×16 RGB patch, P = 16·16·3 = 768). Let xi be the true patch pixels. The per-element squared error for patch i is:
This is just the average squared pixel error for one patch. Now the two design choices that make it the MAE loss rather than a generic autoencoder loss.
Let M be the set of masked patch indices (|M| = Nmask). The total loss averages ℓi over the masked set, and over no others:
Why only masked patches? The visible patches were handed to the encoder unchanged — reconstructing them is trivial copying and provides no learning pressure. Including them in the loss would let the model lower its loss by perfecting the easy copy task while neglecting the hard prediction task. MAE found that computing loss on visible patches hurts the learned representation. So the loss looks only where the model had to actually predict.
MAE (and SatMAE) found that reconstructing normalized pixels gives a better representation. For each patch, compute the mean μi and standard deviation σi of its true pixels, and ask the decoder to predict the standardized target:
Per-patch normalization makes the target locally contrastive: it strips out each patch's overall brightness and contrast, so the model must predict relative structure (texture, edges) rather than just the average tone. For satellite bands this matters even more — different bands and different scenes have wildly different absolute reflectance ranges, and per-patch standardization equalizes them before the loss.
Take a toy with 4 patches, P = 2 pixels each. Suppose patches 1 and 3 are masked (M = {1, 3}), patches 0 and 2 are visible. Predictions and targets (already normalized for simplicity):
| Patch i | masked? | x̂i (pred) | x̃i (target) | ℓi = mean sq err |
|---|---|---|---|---|
| 0 | no | [0.2, 0.2] | [0.0, 0.0] | 0.04 (ignored) |
| 1 | yes | [1.0, 0.0] | [0.0, 0.0] | (1²+0)/2 = 0.50 |
| 2 | no | [0.9, 0.9] | [1.0, 1.0] | 0.01 (ignored) |
| 3 | yes | [0.0, 0.5] | [0.0, 0.0] | (0+0.25)/2 = 0.125 |
The masked-only loss is L = (0.50 + 0.125) / 2 = 0.3125. Note that patch 0's error of 0.04 and patch 2's error of 0.01 are not in the sum at all — the gradient flows only from patches 1 and 3. If we had (wrongly) averaged all four, we would get (0.04+0.50+0.01+0.125)/4 = 0.169 — a smaller, but meaningless, number diluted by the visible patches.
Slide the model's "skill" from random to perfect. The teal curve is the correct masked-only loss; the faded curve averages over all patches and is contaminated by the easy visible patches. Watch how the all-patches loss can look low while the model still fails on the masked patches.
python # The MAE / SatMAE reconstruction loss import torch def mae_loss(pred, target, mask, norm_pix=True, eps=1e-6): """ pred : [B, N, P] decoder output per patch (P = p*p*C) target : [B, N, P] true patch pixels mask : [B, N] 1 = masked (predict it), 0 = visible (ignore) """ if norm_pix: # Choice 2: per-patch standardize mu = target.mean(dim=-1, keepdim=True) var = target.var(dim=-1, keepdim=True) target = (target - mu) / (var + eps).sqrt() se = (pred - target) ** 2 # [B, N, P] per_patch = se.mean(dim=-1) # [B, N] -> l_i # Choice 1: average over MASKED patches only loss = (per_patch * mask).sum() / mask.sum() return loss
Now the first satellite-specific contribution. A field is not a single image; it is a time series of images of the same place. SatMAE's temporal variant feeds the model a stack of T timestamps of the same scene and must answer two questions: how do we tell the model when each patch was taken, and how do we mask across time?
Plain ViT adds a positional embedding to each token so the model knows where a patch is. With multiple dates, the model also needs to know when. SatMAE adds a temporal embedding on top of the spatial position. Crucially, the timestamp is not just an index 0,1,2 — it is the actual acquisition date, because the gap between images is irregular (one image in March, the next in June, the next two weeks later).
SatMAE encodes each component of the date — year, month, and hour/day-of-year — with its own sinusoidal positional encoding (the same sin/cos-at-many-frequencies scheme as the original Transformer), then concatenates them into the temporal embedding. So a token's full embedding is:
where s indexes spatial position and t indexes the date. The same spatial patch at two different dates gets the same pos_embed(s) but a different time_embed — so the model can line up "this is the same field, three months later." Encoding the real date (not the index) means the model learns calendar structure: it can tell that the gap from March to June is larger than March to April, and that June crops differ from March bare soil.
Here is the second temporal choice, and it mirrors the spectral choice we will see in Chapter 5. Given T frames of the same scene, do we mask the same spatial patches in every frame (shared mask) or different patches in each frame (independent mask)?
SatMAE masks independently across time. If patch (s=12) is masked in the March frame, it might be visible in the June frame. Why? Because a shared mask creates a shortcut: if patch 12 is masked in every frame, the model genuinely has to predict it from spatial neighbours. But that is the easy spatial-interpolation task again. With independent masking, patch 12 is hidden in March but visible in June — so the model can (and is encouraged to) use the temporal neighbour: "what did this field look like at the same location a few months ago?" Independent masking is what forces the encoder to learn temporal correspondence rather than purely spatial inpainting.
Three frames (dates) of the same 6×6 scene. Toggle between shared masking (the same cells hidden every frame) and independent masking (different cells per frame). Notice: under independent masking a hidden cell in frame 1 is often visible in frame 2 — the model can borrow across time.
Worked example. Suppose a scene has T = 3 frames, each a 12×12 = 144-patch grid, and ρ = 0.75. Under independent masking we draw a fresh 75%-mask per frame, so per frame 36 patches are visible. Across the stack of 3×144 = 432 tokens, 3×36 = 108 are visible to the encoder and 324 are masked. A given ground location (one of the 144 spatial cells) is, on average, visible in 3×0.25 = 0.75 of its 3 frames — so most locations are visible in at least one frame, giving the model a temporal anchor to predict the others. Under a shared mask, a masked location is masked in all 3 frames, destroying that anchor.
This is the headline mechanism for multi-spectral SatMAE, and the one most worth your attention. The question: a Sentinel-2 pixel has 13 bands — how should the model ingest them?
The obvious move is to treat the 13 bands like 13 colour channels: a 16×16 patch becomes a vector of 16·16·13 = 3328 numbers, projected by a single learned matrix to a token. This is the "flat 13-channel MAE." It is simple, and it is what you'd do first. But it has a structural flaw: every band shares the same projection and the same positional embedding. The model is told nothing about which dimension is the deep-blue band vs the shortwave-infrared band. The wildly different physics of those bands are forced through one undifferentiated embedding, and the model must disentangle them with no help.
SatMAE partitions the 13 bands into spectral groups of bands that share similar physical character and ground resolution. For Sentinel-2 the paper uses three groups, roughly:
| Group | Bands (Sentinel-2) | Character | Native resolution |
|---|---|---|---|
| g0 — visible/RGB+NIR (10m) | B2, B3, B4, B8 | Blue, Green, Red, near-IR — the sharp bands | 10 m |
| g1 — red-edge / NIR (20m) | B5, B6, B7, B8A, B11, B12 | Vegetation red-edge + shortwave-IR | 20 m |
| g2 — atmospheric (60m) | B1, B9, B10 | Coastal aerosol, water vapour, cirrus — coarse | 60 m |
Each group is patchified and projected separately, and — the crucial part — each group gets its own learned spectral positional encoding, a vector eg added to every token in group g. So a token's embedding now carries three position signals:
The spectral embedding spec_embed(g) plays the same role for "which band group am I?" that the positional embedding plays for "where in the image am I?" It lets attention treat the deep-blue token and the SWIR token as distinct kinds of thing, even at the same spatial location. The model can now learn band-specific structure: that the red-edge group is the one that lights up over healthy vegetation, that the SWIR group separates bare soil from water.
Top: all 13 bands crushed into one token with one positional code — the model can't tell blue from SWIR. Bottom: SatMAE groups the bands; each group gets its own projection and its own spectral position code (coloured). Click a group to highlight which bands it owns.
There are two extremes and SatMAE sits between them deliberately:
Worked example of the token count. Take a 96×96 chip, patch p = 8, so 12×12 = 144 spatial positions. Flat 13-channel: 144 tokens (each of input dim 8·8·13 = 832). Grouped into 3 groups: 3×144 = 432 tokens (each of input dim 8·8·|bands in group|, e.g. 8·8·4 = 256 for g0). Grouping multiplies the sequence length by the number of groups — that is the cost — but in exchange each band group gets a clean, dedicated representation and its own spectral position, which is what buys the accuracy.
We have grouped the bands and given each group a spectral code. Now the masking question returns, in spectral form: given the same spatial location across G band groups, do we mask the same patches in every group, or mask independently per group? This is the spectral twin of Chapter 3's temporal masking choice — and it is the core mechanism you will implement in Code Lab 1.
Bands are correlated. The red band and the green band at the same pixel move together; the red-edge bands track vegetation jointly. If you mask the same spatial patch in every group, then to reconstruct group g's masked patch the model truly has to predict it from spatial neighbours. But there is a sneakier failure: with a shared mask, the visible band groups at a location are also all masked at the masked locations, so the model never sees the easy cross-band correlation. SatMAE instead masks independently within each group — a fresh 75% mask drawn per group.
With independent group masking, a spatial location might be masked in group g0 (visible bands) but visible in group g1 (red-edge bands). Now the model can — and must learn to — exploit the strong cross-band correlation: "I can see the red-edge reflectance here; predict the masked visible-band reflectance from it." This is the spectral analogue of borrowing across time. It forces the encoder to learn the inter-band relationships that carry the physical signal (e.g. NDVI-like contrasts), instead of treating each band group as an isolated spatial inpainting problem.
Three band groups over the same 6×6 location grid. Each group draws its OWN 75% mask. Click a location: see how it is hidden in some groups but visible in others — the visible groups become the evidence for predicting the hidden ones (cross-band inference).
For each band group independently, take the N spatial patch indices, shuffle them, keep the first Nvis = (1−ρ)N as visible and mark the rest masked. The shuffle is the whole trick — it is how you draw a uniform random subset without replacement, and doing it per group with a fresh shuffle is what makes the masks independent. This is exactly the argsort(random) idiom MAE uses, applied once per group.
python # Independent per-group random masking (the SatMAE spectral mechanism) import numpy as np def group_random_mask(N, G, mask_ratio, rng): """Return mask[G, N]: 1 = masked, 0 = visible. A FRESH random subset per group -> the masks are independent across groups.""" n_vis = int(round((1 - mask_ratio) * N)) mask = np.ones((G, N)) for g in range(G): order = rng.permutation(N) # fresh shuffle per group mask[g, order[:n_vis]] = 0 # first n_vis kept visible return mask
Worked example. N = 8 spatial patches, G = 3 groups, ρ = 0.5 so Nvis = 4 per group. Group 0 might keep visible {1,3,4,6}, group 1 {0,2,4,7}, group 2 {2,3,5,6}. Look at spatial location 4: visible in g0 and g1, masked in g2. So to reconstruct location 4's group-2 bands, the model has its group-0 and group-1 bands as evidence — cross-band inference is available because the masks differ. Under a shared mask, location 4 would be all-visible or all-masked, and that evidence channel would never exist.
np.random draw applied identically). It looks like masking, the shapes are right, the loss runs — but the model is learning per-group spatial inpainting, not the cross-band physics that makes SatMAE work. The check verifies that the per-group visible sets actually differ across groups, and that each group keeps exactly Nvis patches.Let us trace a single multi-spectral SatMAE forward pass with explicit shapes, so you could implement it. We use a concrete config: input chip 96×96, patch p = 8, so N = 144 spatial patches; G = 3 band groups; embedding dim D = 768 (ViT-Base scale); mask ratio ρ = 0.75.
Read the shapes carefully — they encode the whole design:
How the encoder's visible-token count grows with the number of band groups G, the number of timestamps T, and the mask ratio ρ. The encoder cost is roughly quadratic in this count — drag the sliders and watch the tradeoff that SatMAE balances.
Worked example. With G = 3, T = 1, ρ = 0.75 and N = 144 per (group,time): total tokens = 3·1·144 = 432; visible = 0.25·432 = 108. Bump to T = 3 dates: tokens = 3·3·144 = 1296; visible = 324. The encoder attention cost scales like (visible)², so going from 108 to 324 visible tokens is ~9× more encoder compute — which is why SatMAE keeps ρ high (to claw back the multiplicative cost of groups and time) and keeps the decoder shallow.
A method paper lives or dies on whether the design choices actually help. SatMAE pre-trains a ViT-Large encoder with the MAE objective on a large corpus of unlabelled satellite imagery (the fMoW — Functional Map of the World — dataset, in both RGB and full Sentinel-2 multi-spectral form), then evaluates two regimes: fine-tuning on the same domain and transfer learning to other remote-sensing tasks.
Against the strongest prior self-supervised baselines for satellite imagery, SatMAE reports:
| Setting | Result (vs prior SOTA) | What it shows |
|---|---|---|
| Supervised fine-tuning on benchmark datasets | up to about +7% | The pre-trained representation transfers within the satellite domain better than prior pre-training |
| Transfer to land-cover classification | up to about +14% | Features generalize to a downstream task the model never trained on |
| Transfer to semantic segmentation | consistent gains | The encoder is useful for dense prediction, not just classification |
The transfer numbers are the more important story. A +14% jump on land cover from better pre-training alone — same labelled data, same fine-tuning budget — is the kind of result that says the representation, not the classifier head, is doing the work. This is the promise of self-supervision realized: the unlabelled ocean paid off.
The paper isolates each design decision. The qualitative verdict (the directions, which is what you should remember):
| Ablation | Finding | Why it makes sense |
|---|---|---|
| Flat 13-channel input vs spectral band groups | Grouping with spectral position helps | Distinct band physics get distinct representations instead of being averaged |
| Shared vs independent group masking | Independent masking helps | Forces cross-band inference instead of per-group inpainting |
| Index time embedding vs real-date temporal embedding | Encoding the actual acquisition date helps | Captures calendar/phenology structure of irregular revisit times |
| Pixel reconstruction without vs with per-patch normalization | Normalized targets help (inherited from MAE) | Forces predicting relative structure, not absolute brightness |
A stylized accuracy ladder (illustrative, showing the direction of each ablation, not exact paper numbers). Toggle each SatMAE design choice on; watch the downstream accuracy climb as the satellite-specific structure is added on top of the plain-MAE baseline.
Reading the ablation correctly. The choices are not redundant — each addresses a different failure of plain MAE. Band groups fix the spectral averaging failure; independent masking fixes the cheat-via-correlated-bands failure; the real-date embedding fixes the lost phenology failure. Because they target different failures, they stack: the full SatMAE is meaningfully above any single addition. This is the hallmark of a well-decomposed paper — orthogonal contributions that compose.
This is the payoff. Everything we have built — band groups, independent per-group masking, the encoder seeing only visible tokens, and the masked-only reconstruction loss — is wired into one interactive pre-training step you can drive. There is no quiz; the simulation is the test. If you can predict what each control does before you move it, you understand SatMAE.
The grid shows a small scene's spatial patches stacked across the G band groups you select. Drive the controls and watch: which tokens reach the encoder, which become [MASK], and where the reconstruction loss is actually measured. The "cross-band evidence" overlay lights up the visible band groups that a hovered masked patch can borrow from — the mechanism from Chapter 5 made literal.
Each row is a band group; each column a spatial location. Teal = visible (goes to encoder), faded = masked (reinserted as [MASK], scored by the loss). Toggle shared vs independent masking and click a masked cell to see which co-located groups remain as cross-band evidence. The readout tracks visible-token count and encoder cost.
Things to try, and what they reveal:
SatMAE is a clean, influential result, but no paper is the last word. Knowing its limits is how you know what came next.
| Concept | One-line summary |
|---|---|
| Base recipe | MAE: mask 75% of patches, deep encoder on visible only, shallow decoder reconstructs masked pixels, keep encoder |
| Reconstruction loss | MSE on the masked patches only, with per-patch pixel normalization |
| Spectral band groups | Partition the 13 bands into physically-similar groups; each group gets its own projection + learned spectral position embedding |
| Independent group masking | Draw a fresh random mask per band group → locations visible in some groups, masked in others → forces cross-band inference |
| Temporal embedding | Encode the real acquisition date (year/month/day) with sinusoids; mask independently across time too |
| Why it works | Injects spectral/temporal identity the model would otherwise have to discover; independent masking prevents the cheap-copy shortcut |
| Results | Up to ~+7% fine-tuning, ~+14% transfer on land cover, gains on segmentation — from pre-training alone |
| Backbone | Vanilla ViT-Large; objective is vanilla MAE — the novelty is tokenization + masking, not architecture |
"What I cannot create, I do not understand." — and you can now create both of SatMAE's signature mechanisms in NumPy, from the two Code Labs above.