Skip to content

edaphos 1.2.0 — Pillar 4 foundation model reaches the user

Choose a tag to compare

@HugoMachadoRodrigues HugoMachadoRodrigues released this 22 Apr 22:01
· 172 commits to main since this release

Summary

Promotes Pillar 4 from a self-supervised-training scaffold to a
ready-to-use transfer-learning pipeline. Three capabilities land
together: a downstream fine-tuning API, a public pretrained encoder
hosted on Zenodo, and GPU (Apple Silicon MPS / NVIDIA CUDA) dispatch.

Downstream fine-tuning API

Two new entry points attach a classification or regression head on
top of any MoCo v2 / SimCLR encoder and train it against a labelled
patch set:

  • foundation_fit_classifier() — linear probe (frozen backbone)
    or full fine-tuning with a two-group learning-rate schedule
    (Kornblith, Shlens and Le 2019). predict(..., type = "class")
    or type = "prob".
  • foundation_fit_regressor() — same API for continuous
    targets; target normalisation is handled internally (center + scale
    at fit time, un-scale at predict) and a Huber loss option is
    provided for robustness against pedogenic outliers.

Both dispatch via device = c("cpu", "mps", "cuda") and fall back
to CPU with a clear message when the requested backend is
unavailable.

Public pretrained encoder: edaphos-cerrado-moco-v1

The first published Pillar 4 foundation model is live on Zenodo:

It is a MoCo v2 (He et al. 2020; Chen et al. 2020) encoder
pretrained for 20 000 InfoNCE steps on an Apple Silicon M1 Max (MPS
backend) over 50 000 16×16 raster patches sampled from a core
Cerrado AoI (longitude −53 to −43, latitude −23 to −10). Input
channels (31 total) are a multi-source stack aligned to a 0.01
degree (~1 km) grid:

Source Channels
SoilGrids 250m (0–5 cm mean) SOC, clay, sand, pH(H₂O), bulk density
WorldClim 2.1 (Brazil country pack) 12 monthly precipitation + 12 monthly mean temperature
SRTM 30 arc-second elevation, slope

Architecture: 5-block convolutional backbone → 64-dimensional
feature → 2-layer MLP projection head (proj_dim = 32). Training
used a queue of 4096 negatives, InfoNCE temperature 0.07, momentum
0.999, Adam learning rate 3e-4, batch size 64. Final InfoNCE loss
~1.64 (from an initial ~7.68 — a 4.7× reduction).

Weights distribution infrastructure

Three new functions consume the encoder registry:

  • foundation_weights_list() — catalogue of every published
    encoder (name, DOI, SHA-256, AoI, channel count, feature
    dimension, edaphos version).
  • foundation_weights_download() — fetches from Zenodo, verifies
    the SHA-256 digest, caches under
    tools::R_user_dir("edaphos", "cache") / weights /.
  • foundation_weights_load() — rebuilds the in-memory
    edaphos_foundation_moco wrapper with the state dict loaded and
    shape metadata populated.
moco <- foundation_weights_load("edaphos-cerrado-moco-v1")
fit  <- foundation_fit_classifier(moco, labelled_patches, soil_order,
                                    epochs = 40L, device = "mps")

Reproducibility

Two scripts under data-raw/ rebuild the deposit from scratch on
any R ≥ 4.3 + torch ≥ 0.16 machine:

  1. pretrain_cerrado_prepare.R — downloads the SoilGrids, WorldClim
    and SRTM tiles; crops them to the AoI; aligns them to the
    analysis grid; samples 50 000 patches.
  2. pretrain_cerrado_train.R — runs 20 000 MoCo v2 steps on the
    requested backend; writes the state dict + metadata + loss
    history; computes the SHA-256 for registry verification.

The seed is fixed (seed = 2026L) so re-running on the same
hardware produces byte-identical artefacts. The seed, the AoI, the
variable list, the alignment grid and the training hyperparameters
are all recorded in the Zenodo deposit's metadata.json.

A companion data-raw/zenodo_upload.R is the reproducible
create / publish / discard pipeline that produced this deposit and
will produce every subsequent one.

Bug fixes

  • foundation_moco_embed() now forces the encoder into eval()
    mode before the forward pass, so BatchNorm uses its saved
    running_mean / running_var rather than batch-level statistics.
    Previously the embeddings depended on the current batch
    composition and disagreed with any reloaded copy of the same
    encoder — the bug that would have made the Zenodo round-trip
    miscompare.
  • foundation_tile_source_soilgrids() now accepts both the
    human-readable depth strings ("0-5cm", "5-15cm", …) documented
    in its @param block and the integer form
    geodata::soil_world() expects internally.

Quality

  • 49 new tests (test-foundation-finetune,
    test-foundation-weights) land green.
  • Full Foundation test suite: 94 / 94 green.
  • R CMD check --as-cran: 0 errors / 0 warnings / 1 harmless NOTE.