DINCAE / scripts /train.py
zhangrenchao's picture
Publish DINCAE reproduction
43dce84 verified
Raw
History Blame Contribute Delete
5.13 kB
"""Train DINCAE with random cloud masking and masked Gaussian NLL."""
import json
import os
import sys
from datetime import date
from pathlib import Path
import numpy as np
import torch
import yaml
from torch.nn.parallel import DistributedDataParallel
from torch.utils.data import DataLoader, Dataset, DistributedSampler
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from model.dincae import DINCAE, build_input, masked_gaussian_nll
class SSTDataset(Dataset):
def __init__(self, arrays, indices, cloud_fraction, seed):
self.data, self.indices = arrays, list(indices)
self.cloud_fraction, self.seed = float(cloud_fraction), int(seed)
def __len__(self):
return len(self.indices)
def __getitem__(self, item):
index = self.indices[item]
timestamp = date.fromisoformat(str(self.data["timestamps"][index]))
observed = self.data["observed_anomaly"].copy()
precision = self.data["precision"].copy()
rng = np.random.default_rng(self.seed + index)
original_valid = np.isfinite(observed[index]) & self.data["ocean_mask"]
withheld = original_valid & (rng.random(original_valid.shape) < self.cloud_fraction)
observed[index, withheld] = np.nan
precision[index, withheld] = 0.0
inputs = build_input(observed, precision, index, self.data["longitude"],
self.data["latitude"], timestamp.timetuple().tm_yday)
target = self.data["sst_anomaly"][index][None].astype(np.float32)
return torch.from_numpy(inputs), torch.from_numpy(target), torch.from_numpy(withheld[None])
def main():
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
rank = int(os.environ.get("RANK", "0"))
world_size = int(os.environ.get("WORLD_SIZE", "1"))
local_rank = int(os.environ.get("LOCAL_RANK", "0"))
distributed = world_size > 1
if distributed:
backend = "nccl" if torch.cuda.is_available() else "gloo"
torch.distributed.init_process_group(backend=backend)
requested = config["runtime"]["device"]
use_cuda = torch.cuda.is_available() and requested != "cpu"
device = torch.device(f"cuda:{local_rank}" if use_cuda else "cpu")
if use_cuda:
torch.cuda.set_device(device)
torch.manual_seed(int(config["seed"]) + rank)
path = ROOT / config["data"]["root"] / config["data"]["file"]
with np.load(path) as loaded:
arrays = {key: loaded[key] for key in loaded.files}
train_count = min(int(config["data"]["train_samples"]), len(arrays["timestamps"]) - 1)
dataset = SSTDataset(arrays, range(train_count), config["data"]["random_cloud_fraction"], config["seed"])
sampler = DistributedSampler(dataset, shuffle=True, seed=int(config["seed"])) if distributed else None
loader = DataLoader(dataset, batch_size=int(config["training"]["batch_size"]),
shuffle=sampler is None, sampler=sampler,
num_workers=int(config["runtime"]["num_workers"]))
model = DINCAE(**config["model"]).to(device)
if distributed:
model = DistributedDataParallel(model, device_ids=[local_rank] if use_cuda else None)
optimizer = torch.optim.Adam(model.parameters(), lr=float(config["training"]["learning_rate"]))
history = []
for epoch in range(int(config["training"]["epochs"])):
if sampler is not None:
sampler.set_epoch(epoch)
model.train()
total, batches = 0.0, 0
for inputs, target, mask in loader:
inputs, target, mask = inputs.to(device), target.to(device), mask.to(device)
optimizer.zero_grad(set_to_none=True)
output = model(inputs)
loss = masked_gaussian_nll(output, target, mask,
config["training"]["gamma"], config["training"]["delta"])
loss.backward()
optimizer.step()
total, batches = total + float(loss.detach()), batches + 1
history.append({"epoch": epoch + 1, "masked_gaussian_nll": total / max(batches, 1)})
if rank == 0:
print(f"epoch={epoch + 1} loss={history[-1]['masked_gaussian_nll']:.6f}")
if rank == 0:
raw_model = model.module if distributed else model
checkpoint = ROOT / config["paths"]["checkpoint"]
checkpoint.parent.mkdir(parents=True, exist_ok=True)
torch.save({"model": raw_model.state_dict(), "model_config": config["model"],
"gamma": config["training"]["gamma"], "delta": config["training"]["delta"],
"format_version": "dincae_checkpoint_v1"}, checkpoint)
metrics = ROOT / config["paths"]["training_metrics"]
metrics.parent.mkdir(parents=True, exist_ok=True)
metrics.write_text(json.dumps({"history": history, "world_size": world_size,
"parameter_count": sum(p.numel() for p in raw_model.parameters())}, indent=2))
print(f"checkpoint={checkpoint}")
if distributed:
torch.distributed.destroy_process_group()
if __name__ == "__main__":
main()