Methodology · Open Protocol · Companion Paper Planned

The four ways NDVI forecasting models accidentally cheat — and the audit that catches them

Most published vegetation-forecasting studies report accuracy figures that don't survive a leakage audit. The Leakage Audit Protocol names four specific channels (V/O/C/P), provides a test for each, and ships as an open leakage_features.yaml reference list that any framework can adopt.

4
Leakage channels named & tested
5–20
R² points inflated in unaudited literature
33
Driver columns audited per training run
0
Prior published taxonomies for this
Active methodology · Open protocol · Currently the validation gate inside the Desertflow framework · Standalone methodology paper planned for 2027
The problem being solved
4 Channels

Four ways your training set can secretly include the answer

Vegetation forecasting models reach impressive accuracy numbers — often R² = 0.85–0.95. But many of those numbers come from training pipelines where the model can quietly peek at the target during training, in ways that aren't obvious unless you look for them. The audit names the four most common channels and gives you a test for each.

The protocol applies to any per-region remote-sensing prediction task — not just NDVI. The V/O/C/P naming is specific to vegetation work, but the four shapes of the failure are general.

What makes this protocol novel

First named taxonomy

Leakage in NDVI forecasting has been discussed informally in the literature, but no single source previously enumerated the four channels by name with a test for each. V/O/C/P gives the community a shared vocabulary.

4 channels named: Vegetation · Optical · Classifier · Pheno-prior

First open test protocol

Each channel has a concrete check that any pipeline can run. The protocol ships as an open leakage_features.yaml reference list — a versioned column-name registry that any framework can adopt without rewriting their pipeline.

YAML · single-file reference, versioned, contributable

First production gate

Already running as the training-time validation gate inside Bundle Framework. Auto-detects matched columns, sets them to "(ignore)" with a FAIL-2030 marker, and logs the exclusion in the bundle metadata for reviewers.

Auto · runs at every training start, can't be silently skipped

Why this matters beyond one paper

Inflated accuracy figures don't stay inside the paper they were published in. They propagate into policy reports, climate-funding allocations, and conservation prioritization — at scale.

5–20
R² points typically inflated by one or more leakage channels
measured on internal ablations
169
countries reporting SDG 15.3.1 (degraded-land indicator) using vegetation baselines
UNCCD
$$
climate-adaptation funds allocated using projected vegetation vulnerability
Green Climate Fund, etc.
REDD+
restoration claims verified against forecasted vs. observed vegetation
UNFCCC programmes

What goes wrong when the audit is skipped

  • Inflated baselines hide regions that actually need help. If a model claims 95% accuracy in a region where its real accuracy is 70%, the region looks "well-monitored" and gets de-prioritized for adaptation funding.
  • Restoration claims become unverifiable. REDD+ verification depends on independent forecasts. If the verifier model is leaky, restoration projects can pass review while delivering nothing.
  • SDG 15.3.1 reporting becomes wrong. Countries report degraded-land area using vegetation baselines. Leakage in the baseline distorts the country-by-country comparison that drives international policy.
  • Reproducibility breaks silently. Other researchers try to replicate the high accuracy, fail to reproduce, and the discrepancy gets blamed on "data quality" rather than the methodology gap.

What is leakage, in plain English

The setup

You want to predict X from other variables

Say you want to predict vegetation health in a region from climate, soil, and topography. You train a model, test it on held-out data, and report the accuracy.

The trap

One of your "inputs" was secretly built from the answer

If one of your input columns was computed using vegetation data (even indirectly — say, a "leaf area index" derived from the same satellite bands as your target), your model is partly predicting X from X. The reported accuracy is inflated, not measured.

The fix

Audit every input against a known list of leaky features

Keep a versioned list of columns known to be derived from the target, and check every input against that list before training. If a match is found, drop the column and log the exclusion. That's the protocol.

TL;DR: Leakage = using the answer (or something derived from it) as one of your inputs without realizing it. The audit catches the most common cases automatically.

The four channels (V/O/C/P)

Each channel describes a distinct way a feature can secretly carry information from the target. They overlap somewhat — a single feature can fail more than one check — but having four named buckets makes the audit explicit and contributable.

V
Vegetation circularity
The most common leak

What's the failure mode?

Using features computed from the NDVI time series — like phenology start-of-season day, peak-NDVI day, growing-season length, GPP, fPAR, LAI — as inputs when predicting NDVI itself. The model effectively learns the identity function dressed up as climate inference.

Concrete examples that get flagged

  • lai_mean (Leaf Area Index — derived from NDVI bands)
  • fpar_mean (fraction of PAR — derived from NDVI)
  • gpp_mean (Gross Primary Production — modeled from NDVI)
  • sos_doy (start-of-season day-of-year — peak of NDVI curve)
  • pos_doy (peak-of-season day — argmax of NDVI)

The smell test

Ask: "Could you compute this feature without ever having seen NDVI?" If the answer is no, it fails the V check.

O
Optical bands
The subtle leak

What's the failure mode?

Including the raw MODIS / Landsat / Sentinel optical bands (red, NIR, blue) as inputs — even though NDVI itself is just (NIR − red) / (NIR + red). The model can trivially reconstruct NDVI from those two bands, so the reported accuracy reflects band-arithmetic, not climate inference.

Concrete examples that get flagged

  • modis_red, modis_nir (raw bands)
  • evi_mean (Enhanced Vegetation Index — uses the same bands)
  • savi_mean (Soil-Adjusted Vegetation Index — same bands)
  • nbr_mean (Normalized Burn Ratio — overlapping band)
  • Anything labeled "spectral index" derived from visible/NIR

The smell test

Ask: "Does this feature use the red or NIR band that NDVI is computed from?" If yes, it fails the O check.

C
Classifier-derived
The hidden leak

What's the failure mode?

Using features produced by another supervised model that was itself trained against vegetation observations. MODIS land cover (MCD12Q1), tree cover percentage, biome maps — all of these are model outputs that encode vegetation information learned from past NDVI / red / NIR observations.

Concrete examples that get flagged

  • mcd12q1_landcover (MODIS supervised land-cover class)
  • treecover_pct (Hansen et al. — trained on Landsat)
  • cropland_pct (GFSAD30 — trained on optical bands)
  • biome_class (WWF biome — derived from vegetation patterns)
  • plant_functional_type (PFT classifications)

The smell test

Ask: "Was this feature produced by a model trained on vegetation observations?" If yes, it fails the C check — even if the supervision happened years ago at a different institution.

P
Pheno-prior contamination
The temporal leak

What's the failure mode?

Including features that depend on knowing the target year's phenological behavior — for example, "growing-season-mean NDVI" computed over the year being predicted, or "peak-NDVI date" for that year. The feature label says it's a "prior," but its value was computed from the future.

Concrete examples that get flagged

  • growing_season_mean_y when y is the prediction year
  • peak_ndvi_value_y for the prediction year
  • annual_max_ndvi_y (the answer, basically)
  • year_of_max_greening when computed including the test year
  • Any "long-term mean" computed from a window that overlaps the prediction year

The smell test

Ask: "Could this feature value be computed in advance, before the prediction year began?" If no, it fails the P check.

The 2030 rule. A unifying way to think about all four channels: could you compute this feature for a 2030 forward prediction, without ever having seen 2030 NDVI? If the answer is no for any reason, the feature is out — and the V/O/C/P breakdown just tells you which kind of "no" it was.

The audit protocol — how to run it

The protocol is intentionally simple: a single YAML file, a single audit step at the start of training, and a logged exclusion list in the output metadata. The full check runs in five concrete steps:

  1. Load the reference list. Read leakage_features.yaml — a versioned registry of column names known to fail at least one of V/O/C/P, organized by channel for traceability.
  2. Scan your schema. For each column in your training data, check whether it matches any entry in the registry. Matching can be exact, prefix-based, or regex (the YAML supports all three).
  3. Auto-ignore + log. Matched columns are set to "(ignore)" in the schema with a FAIL-V, FAIL-O, FAIL-C, or FAIL-P marker. A banner alerts the operator that columns were excluded.
  4. Run training. The model never sees the flagged columns. Training proceeds normally on the remaining (leakage-safe) features.
  5. Record the audit in metadata. The bundle artifact includes a training.audit block listing every excluded column, the matched rule, and the YAML version used. Reviewers can verify exactly what was skipped without re-running the pipeline.

The leakage_features.yaml reference

The audit is data, not code. The entire reference list is a single human-readable YAML file with this shape:

# leakage_features.yaml (version: 0.2.0) # # Canonical registry of column names that fail the V/O/C/P audit # for NDVI forecasting. Contributable via PR. V_vegetation_circularity: description: "Features computed from the NDVI time series itself." rules: - lai_mean - lai_* - fpar_* - gpp_* - npp_* - sos_doy - pos_doy - eos_doy - growing_season_length - pheno_* O_optical_bands: description: "Raw or derived MODIS/Landsat/Sentinel red+NIR bands." rules: - modis_red - modis_nir - landsat_red - landsat_nir - sentinel_b04 - sentinel_b08 - evi* - savi* - nbr* - msavi* C_classifier_derived: description: "Outputs of supervised models trained on vegetation observations." rules: - mcd12q1_* - landcover_* - treecover_* - cropland_pct - biome_class - pft_* - hansen_* P_pheno_prior_contamination: description: "Features whose value requires knowing the target-year NDVI." rules: - "*_target_year" - "annual_max_ndvi_*" - "growing_season_mean_y" - "peak_ndvi_*" - "year_of_max_*"

The same YAML is read by the Bundle Framework's SchemaWidget and by any external pipeline that adopts the protocol. New columns get added by pull request as new leakage patterns are discovered.

Applying the audit to your own work

You don't need to use Bundle Framework to use the protocol. Three integration levels, in order of effort:

Level 1 — Manual

Read the YAML and check by hand

Open leakage_features.yaml, scan your input feature list against it, drop the matches. Takes 10 minutes per project. Fine for one-off papers.

Level 2 — Scripted

Load the YAML in your pipeline

Add a 30-line script that reads the YAML, matches your schema against the rules, and raises an error if any leaky columns are present. Stops accidents during refactoring.

Level 3 — Framework

Use Bundle Framework or adopt the gate pattern

The audit becomes a hard gate at training start: matched columns are auto-set to ignore, the bundle metadata records what was excluded, reviewers can verify the audit without re-running training.

Contributing channels & columns

The protocol is intentionally open. New leakage patterns get discovered as new datasets and feature pipelines appear — the YAML should grow to track them.

Roadmap

Now — v0.2 (inside Bundle Framework)
  • V/O/C/P channels defined
  • leakage_features.yaml ships with the framework
  • SchemaWidget integration in progress (Task #45)
  • Used as the validation gate for all Desertflow training
2027 — v1.0 standalone library
  • Pip-installable leakage-audit package (no Bundle dependency)
  • CLI: leakage-audit check schema.yaml --rules v0.2.yaml
  • Pre-commit hook for ML-pipeline repos
  • Standalone methodology paper (separate from Desertflow)
Beyond — extensions
  • Channels for other Earth-observation targets (soil moisture, fire, surface temperature) — each domain has its own leakage patterns
  • Adversarial test suite: synthetic datasets where the leakage is engineered, to verify the audit catches every channel
  • Community-maintained reference list (the YAML as a living document)

References & prior art

Leakage in machine-learning evaluation is a long-discussed topic; the contribution here is specifically the named-channel taxonomy for NDVI forecasting and the operational gate around it.

The companion paper (planned for 2027) will provide a more thorough literature review, with worked examples of each channel applied to a small public dataset so the protocol can be cited independently of Desertflow.

FAQ

Why "V/O/C/P" specifically? Why not three channels, or five?

Four was the smallest number that covers the failure modes we kept hitting in practice during Desertflow's development, without merging distinct mechanisms. Vegetation circularity (V) and Optical bands (O) look related but require different tests — one is about derived products, the other is about raw bands. Classifier-derived (C) is its own thing because the leakage is laundered through an intermediate model. Pheno-prior (P) is temporal-only, which makes it different from V/O/C in how the test runs. If a 5th channel proves necessary later, the protocol allows adding one — it's not closed.

Does this only apply to NDVI?

The V/O/C/P naming is specific to NDVI forecasting. But the shapes of the four failures generalize: "circularity" (target-derived features), "raw inputs" (the underlying bands the target was computed from), "classifier leak" (features from supervised models trained on the target), and "temporal contamination" (features whose value depends on the prediction-window data). For soil-moisture forecasting, fire-risk forecasting, or surface-temperature forecasting, the same four shapes apply with different column names — that's a follow-up domain-specific YAML.

Is this just for academic work? My team ships production models.

Production models suffer more from leakage, not less — because a leaky training set produces a model that quietly underperforms in deployment, and the gap doesn't show up until weeks later when the test-set numbers diverge from real-world performance. Adopting the audit as a pre-deployment gate is cheaper than debugging the deployment gap.

What if my reviewer says my paper has none of these problems?

Most papers do have at least one — usually O (including raw optical bands) or P (computing a "prior" over a window that includes the target year). The audit isn't accusatory; it's a single-page YAML you can run your column list against in 10 minutes. If the audit comes back clean, you have something concrete to cite in your methodology section.

Can I name a leakage failure mode after my own group?

The naming is intentionally boring (single letters) so credit stays with the protocol, not any individual contributor. If your group identifies a fifth channel, the PR adding it credits you in the YAML metadata; the channel itself gets a letter.

How is this different from Ploton et al. 2020 or Meyer & Pebesma 2022?

Those papers document the existence of the problem — they show that published cross-region accuracy collapses when spatial validation is applied properly. This protocol is one level downstream: it provides an operational gate with a versioned reference list, so the failure modes get caught before training instead of being diagnosed after publication. The two complement each other.

Get involved

Pull requests against leakage_features.yaml, channel proposals, or replication offers — (click to copy)

Code: github.com/mohtheghost

← Back to all projects