Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
df1105f
initial example of two year data
rogerkuou Feb 27, 2026
fef01cf
update examples notebook
rogerkuou Feb 27, 2026
2b3ac47
add example training scritps
rogerkuou Feb 27, 2026
2bdf8b5
add example slurm file
rogerkuou Feb 27, 2026
1159fbc
update fig dir
rogerkuou Feb 27, 2026
3e2c4b4
add README
rogerkuou Feb 27, 2026
994d36b
Merge branch 'main' into 25_test_two_year_data
rogerkuou Feb 27, 2026
8cd1c8f
Apply suggestions from code review
rogerkuou Mar 18, 2026
fe8f024
fix conflicts
rogerkuou Mar 18, 2026
a3ba05d
separate training and inference
rogerkuou Mar 18, 2026
3c99673
update model exportation with checkpoint
rogerkuou Mar 18, 2026
2b4c7c5
add inference scripts
rogerkuou Mar 18, 2026
9ed2e00
use logging to replace print
rogerkuou Mar 18, 2026
1de1cfb
update example slurm scripts
rogerkuou Mar 18, 2026
efa17a1
force example notebook to be identical as main
rogerkuou Mar 26, 2026
25297dc
Apply suggestions from code review
rogerkuou Mar 26, 2026
6b04a27
revert changes in model file
rogerkuou Mar 26, 2026
ba0408f
remove inference script
rogerkuou Mar 26, 2026
74a180c
maintain the same config in example script as example notebook
rogerkuou Mar 26, 2026
eeb29ff
update the training loop
rogerkuou Mar 26, 2026
8481c08
update logger and log file
rogerkuou Mar 26, 2026
baba960
update the training script and slurm file
rogerkuou Mar 27, 2026
e89d4e4
document the efficiency calculation in README
rogerkuou Mar 27, 2026
3a5dbd6
add an example slurm log
rogerkuou Mar 27, 2026
4fd95d9
Merge branch 'main' into 25_test_two_year_data
rogerkuou Apr 20, 2026
fb94642
add docstring to datasets
rogerkuou Apr 20, 2026
17a8294
update example training script with train_monthly_model function
rogerkuou Apr 20, 2026
13714f9
update slurm
rogerkuou Apr 20, 2026
228c18a
update training script
rogerkuou Apr 20, 2026
8898cbf
add logger to training script
rogerkuou Apr 23, 2026
c799baf
enable printing in slurm logv files
rogerkuou Apr 23, 2026
92fbdeb
add constraints on the lattitude
rogerkuou Apr 23, 2026
3b9cae6
add slurm output file of a subset
rogerkuou Apr 23, 2026
7494f4a
add a full SLURM log file
rogerkuou Apr 23, 2026
a13472b
update readme
rogerkuou Apr 23, 2026
9cbd7e6
update slurm job time to 4hrs default
rogerkuou Apr 23, 2026
e9996e4
Update scripts/README.md
rogerkuou Apr 29, 2026
6bd6391
update longitude constraint
rogerkuou May 4, 2026
cfb2d7c
Merge branch 'main' into 25_test_two_year_data
rogerkuou Jun 15, 2026
2981bbd
update training script with new etting
rogerkuou Jun 15, 2026
4fe2aa2
config data paths
rogerkuou Jun 15, 2026
7b2eef3
update training script
rogerkuou Jun 17, 2026
8ea313d
update training script
rogerkuou Jun 17, 2026
7fac495
update training script
rogerkuou Jun 17, 2026
e5c3e48
update training parameters
rogerkuou Jun 18, 2026
1c0a61a
fix residuals
rogerkuou Jun 19, 2026
3dd0364
variable names
rogerkuou Jun 19, 2026
78b3a8c
add scripts to run best tuned model on test dataset
rogerkuou Aug 13, 2026
66560b0
doc best hypterparameters
rogerkuou Aug 13, 2026
1e93be5
remove shebang
rogerkuou Aug 14, 2026
eaa9d8f
solve conflict
rogerkuou Aug 16, 2026
dbfbd47
reorgnize folder
rogerkuou Aug 16, 2026
aa2779b
formatting
rogerkuou Aug 16, 2026
f1a8fab
merge run best model for example
rogerkuou Aug 16, 2026
90f94f1
rename training.slurm
rogerkuou Aug 16, 2026
379f953
update training script
rogerkuou Aug 16, 2026
5cae0b8
update the latest parameters
rogerkuou Aug 18, 2026
a59e227
reduce node number and training batch number
rogerkuou Aug 20, 2026
0d02180
Merge branch 'main' into 25_test_two_year_data
SarahAlidoost Aug 20, 2026
193eb1a
remove old logs
SarahAlidoost Aug 20, 2026
66083a7
clean up the scripts
SarahAlidoost Aug 20, 2026
8bedfed
add early stopping print statement to train
SarahAlidoost Aug 20, 2026
235a912
expose some more tune parameters in training, adjust scheduler min_lr…
SarahAlidoost Aug 20, 2026
35bbcf7
undo the unneccessary changes to data_preparation scripts
SarahAlidoost Aug 20, 2026
1a6716e
update scripts
SarahAlidoost Aug 20, 2026
1a5996e
update scripts
SarahAlidoost Aug 20, 2026
f049d25
adjust batch size
SarahAlidoost Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions climanet/train.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class TrainConfig:
patience: int = 10
accumulation_steps: int = 1
optimizer_lr: float = 1e-3
optimizer_weight_decay: float = 1e-2
scheduler_lr_factor: float = 0.1
scheduler_min_lr: float = 1e-5
device: str = "cpu"
verbose: bool = False
verbose_epoch_interval: int = 20
Expand Down Expand Up @@ -104,7 +107,9 @@ def train_monthly_model(

# Set the optimizer
optimizer = torch.optim.AdamW(
model.parameters(), lr=training_config.optimizer_lr, weight_decay=1e-2
model.parameters(),
lr=training_config.optimizer_lr,
weight_decay=training_config.optimizer_weight_decay,
)

best_loss = float("inf")
Expand All @@ -120,9 +125,9 @@ def train_monthly_model(
scheduler = ReduceLROnPlateau(
optimizer,
mode="min",
factor=0.5,
factor=training_config.scheduler_lr_factor,
patience=training_config.patience // 2, # Reduce LR before early stop triggers
min_lr=1e-7,
min_lr=training_config.scheduler_min_lr,
)

model.train()
Expand Down Expand Up @@ -215,6 +220,8 @@ def train_monthly_model(
if counter >= training_config.patience and current_lr <= scheduler.min_lrs[0]:
if training_config.store_logs:
writer.add_text("Training", f"Early stop at epoch {epoch}", epoch)
if training_config.verbose:
print(f"Early stopping triggered at epoch {epoch}. Best loss: {best_loss:.6f}")
break

# Restore best model
Expand Down
42 changes: 42 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Scripts

## Structure

- `data_preparation.*`: Scripts for preparing the data for training, tuning and evaluation. Mainly converting large netCDF files to Zarr storage with specific chunking strategies. This allows executing training for larger-than-memory datasets.
- `example_training.*`: example training script
- `tuning.*`: Scripts for hyperparameter tuning.
- `run_best_tuned_model.*`: Scripts for running the best tuned model on the test set.
- `logs`:
- `eso4clima_24438134_subset.out`: example SLURM job output file of an execution on a subset of the global dataset. The dataset has two years of data (2020-2021) and the spatial coverage is from 30S to 30N and from 30W to 30E.
- `eso4clima_24449471_full.out`: example SLURM job output file of an execution on the full dataset, two years of data (2020-2021) and almost global coverage (from 80S to 80N and from 179.99W to 179.99E). The training only executed for 1 hour and cuted off by SLURM time limit.

## Experiments

### Tuning experiments

- datasplit: train set = 2020, validation set = 2021, test set = 2022
- path of tuning results: `/work/<account_id>/eso4clima/tune/`.
- test loss: 0.036662004509047774
- hyperparameters of the best model:

```
{'patch_size': 8,
'overlap': 1,
'embed_dim': 64,
'dropout': 0.2,
'hidden': 32,
'spatial_depth': 3,
'spatial_heads': 2,
'optimizer_lr': 0.001787422899066508,
'batch_config': {'accumulation_steps': 2}}
```

### Training experiments

- Use the best hyperparameters found in the tuning experiments to train the model on the training set and evaluate it on the test set.
- Use three years 2018-2020 for training, 2021 for validation and 2022 for testing.
- Prepared data is stored in `/work/<account_id>/eso4clima/preprocessed/sst/`.
- Load one year data following example in `run_best_tuned_model.py` script. Then concatenate the three years as `xr.concat([da_2018, da_2019, da_2020], dim="M")`.
- In dataloader, use `load_lazy=True`.


184 changes: 184 additions & 0 deletions scripts/run_best_tuned_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import argparse
from pathlib import Path

import xarray as xr
from ray import tune

from climanet.dataset import DataLoaderConfig, STDataset
from climanet.predict import PredictionConfig, predict_monthly_var
from climanet.utils import data_preparation, read_st_data


def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description=(
"Load the best Ray Tune checkpoint, prepare the test data, and evaluate the "
"trained model on the 2023 test period."
)
)
parser.add_argument(
"--experiment-path",
type=Path,
required=True,
help="Path to the Ray Tune experiment directory containing the checkpoint.",
)
parser.add_argument(
"--test-data-dir",
type=Path,
required=True,
help="Directory containing the test NetCDF files.",
)
parser.add_argument(
"--lsm-file-path",
type=Path,
required=True,
help="Path to the land-sea mask NetCDF file.",
)
parser.add_argument(
"--run-dir",
type=Path,
default=Path("./run_dir_tune_test").resolve(),
help="Directory used for the evaluation run and saved logs.",
)
parser.add_argument(
"--var-name",
type=str,
default="tos",
help="Variable name to evaluate in the NetCDF files.",
)
parser.add_argument(
"--year",
type=str,
default="2022",
help="Year pattern to include in the test files (e.g. 2022).",
)
return parser


def main() -> None:
args = build_parser().parse_args()
experiment_path = args.experiment_path.resolve()
test_data_dir = args.test_data_dir.resolve()
lsm_file_path = args.lsm_file_path.resolve()
run_dir = args.run_dir.resolve()
run_dir.mkdir(parents=True, exist_ok=True)

if not experiment_path.exists():
raise FileNotFoundError(
f"Experiment directory does not exist: {experiment_path}"
)
if not test_data_dir.exists():
raise FileNotFoundError(f"Test data directory does not exist: {test_data_dir}")
if not lsm_file_path.exists():
raise FileNotFoundError(f"LSM file does not exist: {lsm_file_path}")

daily_files = list(
test_data_dir.glob(f"{args.year}*_hr_ERA5dc_masked_{args.var_name}*.nc")
)
monthly_files = list(
test_data_dir.glob(f"{args.year}*_mon_ERA5dc_masked_{args.var_name}*.nc")
)

if not daily_files:
raise FileNotFoundError(
f"No daily test files found for year '{args.year}' in '{test_data_dir}'"
)
if not monthly_files:
raise FileNotFoundError(
f"No monthly test files found for year '{args.year}' in '{test_data_dir}'"
)

print(f"Using daily files ({len(daily_files)}): {daily_files[:3]} ...")
print(f"Using monthly files ({len(monthly_files)}): {monthly_files[:3]} ...")

daily_data_test = xr.open_mfdataset(
daily_files, combine="by_coords", parallel=False
)
monthly_data_test = xr.open_mfdataset(
monthly_files, combine="by_coords", parallel=False
)

test_data_zarr_dir = run_dir / "test_data_zarr"
test_data_zarr_dir.mkdir(parents=True, exist_ok=True)

_ = data_preparation(
daily_data_test[args.var_name],
monthly_data_test[args.var_name],
calculate_residuals=True,
is_hourly=True,
save_to_zarr=True,
run_dir=test_data_zarr_dir,
)

input_da, input_da_nan_mask, monthly_da, padded_days_mask, time_features = (
read_st_data(
data_path=test_data_zarr_dir,
var_name=args.var_name,
)
)

lsm_mask = xr.open_dataset(lsm_file_path)

num_patches = (10, 10)
patch_size = (1, 4, 4)
spatial_patch_size = (
patch_size[1] * num_patches[0],
patch_size[2] * num_patches[1],
)
stride = (spatial_patch_size[0] // 5, spatial_patch_size[1] // 5)

dataset_test = STDataset(
input_da=input_da,
input_da_nan_mask=input_da_nan_mask,
monthly_da=monthly_da,
padded_days_mask=padded_days_mask,
time_features=time_features,
land_mask=lsm_mask["lsm"],
patch_size=(1, *spatial_patch_size),
stride=stride,
sh_embed_dim=96,
sh_order_L=10,
verbose=True,
load_lazy=False,
)
print(f"Created test dataset with {len(dataset_test)} patches.")

analysis = tune.ExperimentAnalysis(str(experiment_path))
best_result = analysis.get_best_trial("loss", "min")
best_checkpoint = best_result.checkpoint
model_path = Path(best_checkpoint.path) / "checkpoint.pt"
print(f"Best checkpoint path: {model_path}")

prediction_config = PredictionConfig(
calculate_residuals=True,
return_numpy=True,
save_predictions=False,
return_loss=True,
device="cpu",
verbose=False,
)

dataloader_config = DataLoaderConfig(
batch_size=10,
shuffle=True,
num_workers=0,
pin_memory=False,
persistent_workers=False,
device="cpu",
multiprocessing_context=None,
)

test_loss = predict_monthly_var(
model=model_path,
dataset=dataset_test,
dataloader_config=dataloader_config,
prediction_config=prediction_config,
run_dir=run_dir,
)

print("Test loss:")
print(test_loss)


if __name__ == "__main__":
main()
24 changes: 24 additions & 0 deletions scripts/run_best_tuned_model.slurm
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
#SBATCH --job-name=climanet_eval
#SBATCH --nodes=1
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=128
#SBATCH --time=02:00:00
#SBATCH --account=bd0854
#SBATCH --partition=compute
#SBATCH --output=climanet_eval_%j.out
#SBATCH --error=climanet_eval_%j.err

set -euo pipefail

source /home/b/b383704/eso4clima/ClimaNet/.venv/bin/activate

python -u /home/b/b383704/eso4clima/run_best_tuned_model/run_best_tuned_model.py \
--experiment-path /work/bd0854/eso4clima/tune/sst_01 \
--test-data-dir /work/bd0854/b380103/eso4clima/output/sst/concatenated/ \
--lsm-file-path /home/b/b383704/eso4clima/data/era5_lsm_bool.nc \
--run-dir /home/b/b383704/eso4clima/run_best_tuned_model/run_dir \
--var-name tos \
--year 2022

printf "\nFinished evaluation run.\n"
Loading