An ensemble foundation model for Earth Observation that aggregates features from multiple specialist backbone encoders to achieve generalist capabilities across diverse remote sensing tasks.
EoS-FM addresses a fundamental question in remote sensing foundation models: rather than training a single large generalist model on massive amounts of data, can we ensemble multiple specialist models trained on different tasks and band combinations to achieve comparable or better generalist performance?
The EosFM encoder implements a late fusion ensemble approach:
- Specialist Models: Multiple pre-trained backbone networks, each trained on different datasets and band combinations (RGB, multispectral, SAR)
- Band Adaptation Layer: Automatically adapts input bands to match each specialist's requirements using rule-based strategies
- Feature Concatenation: Combines specialist features channel-wise at each pyramid level for downstream decoders
- Encoder Selection Layer: Selects an optimal subset of encoders to automatically prune the encoder at training time
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -e .
# Install additional dependencies (adjust based on your needs)
pip install terratorch torchgeo lightning rasterio torch torchvision# Set up environment
source venv/bin/activate
export PYTHONPATH=$(pwd):$PYTHONPATH
# Train on a dataset (e.g., Cloud Cover)
python run.py experiment=train/cloud_cover
# List all available experiments
python run.py --help | grep "experiment"
# Available experiments: ben_rgb, ben_s1, ben_all, caffe, cloud_cover, deepglobe_lcc,
# dfc2022, dior, etci2021, eurosat_rgb, eurosat_s2, firerisk, inria_aerial,
# kenya_croptype, landcoverai, levircdplus, loveda, minifrance, opencanopy, oscd,
# potsdam, sen12ms_rgb, sen12ms_s1, sen12ms_s2, sen12ms_all# Check that configuration is valid without training
python run.py experiment=train/potsdam train=false# Example: in conf/experiment/my_experiment.yaml
defaults:
- override /model: segmentation
data:
_target_: eosfm.datamodules.MyDataModule
batch_size: 32
num_workers: 8
root: /path/to/data
trainer:
max_epochs: 100
accelerator: auto
devices: auto
precision: bf16-mixed
logger:
name: my_experiment
model:
model_args:
backbone: EosFM
backbone_in_chans: 3
model_weights: models/eosfm.pth
freeze: true # Freeze encoders during training (recommended)
num_classes: 6
loss: dice1. Basic Concatenation (Default)
model:
model_args:
backbone: EosFM
num_classes: 62. Encoder Selection with Top-K
model:
model_args:
backbone: EosFM
max_encoders: 5 # Select only 5 most relevant encoders
num_classes: 63. Feature Normalization
model:
model_args:
backbone: EosFM
normalize_features: true
normalization_type: "batch" # "batch" or "layer"
num_classes: 6The ensemble file format is a list of tuples: [(in_chans, state_dict, config), ...]
The utils.py script provides utilities to load and export ensemble models from a folder of trained Lightning checkpoints:
# Export all trained models from an experiments folder to an ensemble .pth file
python utils.py export \
--encoders-folder experiments/train \
--output-path eosfm_ensemble.pthThis command:
- Recursively loads all Lightning checkpoints from
experiments/train - Extracts encoder weights and configurations from each checkpoint
- Automatically detects the input channels and backbone architecture
- Saves them in the correct ensemble format
Custom datamodules in eosfm/datamodules/:
- Potsdam2D: Urban semantic segmentation (RGBIR, 6000×6000 images)
- MiniFrance: Land cover classification
- SEN12MS: Multi-modal Sentinel-1/2 with land cover labels
- BigEarthNetV2: Multi-label classification
- ETCI2021: Flood detection
- And more...
Also supports TorchGeo datasets: EuroSAT, DeepGlobe, FireRisk, OSCD, etc.
class MyDataModule(PinNonGeoDataModule):
def setup(self, stage):
if stage in ['fit', 'validate']:
self.train_dataset = MyDataset(use_tiling=False) # Random crops
self.val_dataset = MyDataset(use_tiling=True) # Systematic tilingConfigurations use Hydra for composable, modular setup. Each experiment is self-contained with data, model, and trainer configurations.
Each experiment is a minimal YAML file that specifies data, model, and training settings:
# config/experiment/train/cloud_cover.yaml
# @package _global_
defaults:
- override /model: segmentation
data:
_target_: eosfm.datamodules.CloudCoverDetectionDataModule
batch_size: 32
num_workers: 4
root: data/cloud_cover
trainer:
max_epochs: 20
check_val_every_n_epoch: 1
logger:
name: cloud_cover
model:
model_args:
num_classes: 2
loss: ce- Create a new YAML file in
config/experiment/train/:
# config/experiment/train/my_dataset.yaml
# @package _global_
defaults:
- override /model: segmentation
data:
_target_: my.custom.DataModule
batch_size: 32
num_workers: 8
root: /path/to/data
trainer:
max_epochs: 100
logger:
name: my_dataset
model:
model_args:
num_classes: 10
backbone: timm_convnextv2_atto
loss: dice- Run the experiment:
python run.py experiment=train/my_dataset
# Or override specific settings from CLI:
python run.py experiment=train/my_dataset trainer.max_epochs=50 data.batch_size=64Override any configuration value from the command line:
# Override epochs, batch size, learning rate
python run.py experiment=train/potsdam trainer.max_epochs=200 data.batch_size=16
# Disable training, only validate
python run.py experiment=train/potsdam train=false
# Run a dry run to verify configuration
python run.py experiment=train/potsdam train=false test=false --cfg jobTraining automatically tracks CO₂ emissions via CodeCarbon. The energy consumption and estimated CO₂eq for this project are detailed below. Note that these are not the values for a single training of the model, but rather for all of the trial and error runs during the project, up to the final full training run.
| Total emissions | Total energy consumed |
|---|---|
| 4.15 kg CO₂eq | 76.05 kWh |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Contributions welcome! Please:
- Follow the existing code structure
- Update configs and documentation
@article{adorni2025eos,
title={EoS-FM: Can an Ensemble of Specialist Models act as a Generalist Feature Extractor?},
author={Adorni, Pierre and Pham, Minh-Tan and May, St{\'e}phane and Lef{\`e}vre, S{\'e}bastien},
journal={arXiv preprint arXiv:2511.21523},
year={2025}
}

