Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- .gitignore +8 -0
- README.md +171 -0
- conf/config.yaml +13 -0
- configuration.json +1 -0
- model/__init__.py +3 -0
- model/fake_data.py +23 -0
- model/tiny_atmorep.py +132 -0
- resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml105.bin +3 -0
- resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml114.bin +3 -0
- resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml123.bin +3 -0
- resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml137.bin +3 -0
- resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml96.bin +3 -0
- resources/id4nvwbetz/AtmoRep_id4nvwbetz.mod +3 -0
- resources/id4nvwbetz/model_id4nvwbetz.json +1 -0
- scripts/download_official_resources.sh +20 -0
- scripts/inference.py +57 -0
- scripts/result.py +46 -0
- scripts/train.py +50 -0
- vendor/atmorep-official/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- vendor/atmorep-official/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- vendor/atmorep-official/.github/ISSUE_TEMPLATE/scientific-discussion.md +20 -0
- vendor/atmorep-official/.gitignore +167 -0
- vendor/atmorep-official/CITATION.cff +56 -0
- vendor/atmorep-official/LICENSE +21 -0
- vendor/atmorep-official/README.md +200 -0
- vendor/atmorep-official/atmorep/__init__.py +0 -0
- vendor/atmorep-official/atmorep/config/__init__.py +0 -0
- vendor/atmorep-official/atmorep/config/config.py +15 -0
- vendor/atmorep-official/atmorep/core/__init__.py +0 -0
- vendor/atmorep-official/atmorep/core/atmorep_model.py +590 -0
- vendor/atmorep-official/atmorep/core/evaluate.py +74 -0
- vendor/atmorep-official/atmorep/core/evaluator.py +240 -0
- vendor/atmorep-official/atmorep/core/train.py +251 -0
- vendor/atmorep-official/atmorep/core/train_multi.py +277 -0
- vendor/atmorep-official/atmorep/core/trainer.py +863 -0
- vendor/atmorep-official/atmorep/datasets/__init__.py +0 -0
- vendor/atmorep-official/atmorep/datasets/data_writer.py +181 -0
- vendor/atmorep-official/atmorep/datasets/multifield_data_sampler.py +371 -0
- vendor/atmorep-official/atmorep/datasets/normalizer.py +82 -0
- vendor/atmorep-official/atmorep/tests/__init__.py +1 -0
- vendor/atmorep-official/atmorep/tests/conftest.py +8 -0
- vendor/atmorep-official/atmorep/tests/test_utils.py +77 -0
- vendor/atmorep-official/atmorep/tests/validation_test.py +126 -0
- vendor/atmorep-official/atmorep/training/__init__.py +0 -0
- vendor/atmorep-official/atmorep/training/bert.py +228 -0
- vendor/atmorep-official/atmorep/transformer/__init__.py +0 -0
- vendor/atmorep-official/atmorep/transformer/axial_attention.py +329 -0
- vendor/atmorep-official/atmorep/transformer/decoder.py +82 -0
- vendor/atmorep-official/atmorep/transformer/interformer.py +52 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
resources/id4nvwbetz/AtmoRep_id4nvwbetz.mod filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.py[cod]
|
| 3 |
+
*.pdf
|
| 4 |
+
result/
|
| 5 |
+
weight/*
|
| 6 |
+
!weight/.gitkeep
|
| 7 |
+
.pytest_cache/
|
| 8 |
+
.ipynb_checkpoints/
|
README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
- zh
|
| 6 |
+
tags:
|
| 7 |
+
- OneScience
|
| 8 |
+
- Earth Science
|
| 9 |
+
- Weather Forecasting
|
| 10 |
+
- ERA5
|
| 11 |
+
- Representation Learning
|
| 12 |
+
frameworks: PyTorch
|
| 13 |
+
datasets:
|
| 14 |
+
- OneScience/ERA5
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
<p align="center">
|
| 18 |
+
<strong><span style="font-size: 30px;">AtmoRep</span></strong>
|
| 19 |
+
</p>
|
| 20 |
+
|
| 21 |
+
# Model Overview
|
| 22 |
+
|
| 23 |
+
AtmoRep is a stochastic atmospheric dynamics model based on large-scale representation learning that captures the distribution of atmospheric states through masked-token training and ensemble outputs.
|
| 24 |
+
|
| 25 |
+
Paper: *AtmoRep: A stochastic model of atmosphere dynamics using large scale representation learning*
|
| 26 |
+
|
| 27 |
+
https://arxiv.org/abs/2308.13280
|
| 28 |
+
|
| 29 |
+
# Model Description
|
| 30 |
+
|
| 31 |
+
This directory retains the official vorticity single-field model weights, configuration, and normalization, and provides a tiny AtmoRep-style model for local training and inference verification.
|
| 32 |
+
|
| 33 |
+
# Use Cases
|
| 34 |
+
|
| 35 |
+
| Scenario | Description |
|
| 36 |
+
| :---: | :--- |
|
| 37 |
+
| Official Resource Validation | Load official `.mod` weights and verify configuration. |
|
| 38 |
+
| Local Rapid Verification | Run masked-token training and inference with the tiny model. |
|
| 39 |
+
| ERA5 Atmospheric Representation Learning | Subsequently interface with official GRIB or Zarr data. |
|
| 40 |
+
|
| 41 |
+
# Usage
|
| 42 |
+
|
| 43 |
+
## 1. OneCode
|
| 44 |
+
|
| 45 |
+
[Click to experience intelligent one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 46 |
+
|
| 47 |
+
## 2. Manual Installation & Usage
|
| 48 |
+
|
| 49 |
+
**Hardware Requirements**
|
| 50 |
+
|
| 51 |
+
- The tiny model runs on CPU.
|
| 52 |
+
- GPU is recommended for the official model and real-data inference.
|
| 53 |
+
|
| 54 |
+
### Download the Model Package
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
hf download --model OneScience-Group/AtmoRep --local-dir ./AtmoRep
|
| 58 |
+
cd AtmoRep
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
### Set Up the Runtime Environment
|
| 62 |
+
|
| 63 |
+
**DCU Environment**
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
conda create -n onescience311 python=3.11 -y
|
| 67 |
+
conda activate onescience311
|
| 68 |
+
pip install onescience[earth-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
**GPU Environment**
|
| 72 |
+
|
| 73 |
+
```bash
|
| 74 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 75 |
+
conda activate onescience311
|
| 76 |
+
pip install onescience[earth-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
If dependencies are missing from the official paths, install them with:
|
| 80 |
+
|
| 81 |
+
```bash
|
| 82 |
+
pip install zarr wandb cfgrib xarray dask netCDF4 torchinfo
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
### Weights & Data
|
| 86 |
+
|
| 87 |
+
The current directory preserves:
|
| 88 |
+
|
| 89 |
+
```text
|
| 90 |
+
resources/id4nvwbetz/AtmoRep_id4nvwbetz.mod
|
| 91 |
+
resources/id4nvwbetz/model_id4nvwbetz.json
|
| 92 |
+
resources/data/normalization/vorticity/
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
Before re-downloading official resources please note: the current vendor snapshot is not a Git checkout, and the existing download script cannot re-clone into a non-empty `vendor/atmorep-official`. The resources bundled with this package do not require re-downloading; the script is only suitable for an empty target directory.
|
| 96 |
+
|
| 97 |
+
```bash
|
| 98 |
+
bash scripts/download_official_resources.sh .
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
### Tiny Training
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
python scripts/train.py
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
This command performs multi-epoch masked-token training using independent train/validation fake Datasets, including DataLoader, AdamW, validation, learning rate scheduling, early stopping, best/latest checkpointing, and training history. Default parameters reside in `conf/config.yaml`.
|
| 108 |
+
|
| 109 |
+
Resuming training:
|
| 110 |
+
|
| 111 |
+
```bash
|
| 112 |
+
python scripts/train.py --resume weight/training/latest.pth --epochs 20
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
Training artifacts are `weight/training/latest.pth`, `best.pth`, and `history.json`; the inference-compatible weights `weight/tiny_atmorep.pth` are also updated. Each Dataset sample consists of `fields [T,V,H,W]` and a non-empty `mask [N]`; train/validation use different seeds. This is a complete training pipeline for the tiny model, not a reproduction of the paper's 3.5-billion-parameter official model training.
|
| 116 |
+
|
| 117 |
+
### Tiny Inference
|
| 118 |
+
|
| 119 |
+
```bash
|
| 120 |
+
python scripts/inference.py
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
Inference results are saved as:
|
| 124 |
+
|
| 125 |
+
```text
|
| 126 |
+
result/prediction.pt
|
| 127 |
+
result/target.pt
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
These include the ensemble, ensemble mean, ensemble std, and mask.
|
| 131 |
+
|
| 132 |
+
### Result Inspection
|
| 133 |
+
|
| 134 |
+
```bash
|
| 135 |
+
python scripts/result.py
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
This command generates `result/metrics.json` and `result/comparison.png`. The reported metrics are ensemble/mean/spread RMSE in normalized token space, not the paper's physical-unit RMSE, ACC, CRPS, or spread-skill.
|
| 139 |
+
|
| 140 |
+
### Paper vs. Current Implementation I/O
|
| 141 |
+
|
| 142 |
+
| Item | Paper / Official Model | Tiny Smoke Model |
|
| 143 |
+
| --- | --- | --- |
|
| 144 |
+
| Input | ERA5 local 4D neighborhoods, 5 mode levels, multiple physical fields | `[B,4,1,8,8]` single-field random tensor |
|
| 145 |
+
| Token | Variable-correlated 4D tokens with absolute space-time and level conditioning | `1×4×4` patches, 16 tokens, relative coordinates with single-level conditioning |
|
| 146 |
+
| Output | Multi-head ensembles supporting reconstruction, nowcasting, and interpolation | 4-member masked-token ensemble |
|
| 147 |
+
| Training | Large-scale masked-token distribution learning | Multi-epoch Dataset training with independent validation and checkpoint resumption |
|
| 148 |
+
| Weights | `resources/id4nvwbetz` official vorticity weights | `weight/tiny_atmorep.pth`; the two are mutually incompatible |
|
| 149 |
+
|
| 150 |
+
The complete tiny execution flow is `train.py -> inference.py -> result.py`. The result analysis reads `weight/training/history.json` when present; inference output includes `ensemble`, `ensemble_mean`, `ensemble_std`, `mask`, and `target`. Random data is generated by the Dataset by index; there is currently no independent fake dataset that can be used with the official Zarr sampler. The model package is distributed without local training weights or `result/` artifacts — these are created at the paths described above after running the commands.
|
| 151 |
+
|
| 152 |
+
### Official Real-Data Inference
|
| 153 |
+
|
| 154 |
+
A directly runnable official real-data inference command is not yet available. It further requires ERA5 vorticity GRIB/Zarr data, the ecCodes environment, and parameterization of the file paths in the official `evaluate.py`.
|
| 155 |
+
|
| 156 |
+
### Real Data
|
| 157 |
+
|
| 158 |
+
The official vorticity model requires ERA5 vorticity, model levels 96/105/114/123/137, hourly temporal axes, and 0.25° global GRIB/Zarr data.
|
| 159 |
+
|
| 160 |
+
# OneScience Official Information
|
| 161 |
+
|
| 162 |
+
| Platform | OneScience Main Repository | Skills Repository |
|
| 163 |
+
| --- | --- | --- |
|
| 164 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 165 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 166 |
+
|
| 167 |
+
# Citation & License
|
| 168 |
+
|
| 169 |
+
- Official code is MIT License.
|
| 170 |
+
- Official model weights are declared CC BY 4.0.
|
| 171 |
+
- ERA5 is subject to Copernicus/ECMWF data terms.
|
conf/config.yaml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
training:
|
| 2 |
+
epochs: 10
|
| 3 |
+
batch_size: 8
|
| 4 |
+
train_samples: 64
|
| 5 |
+
validation_samples: 16
|
| 6 |
+
learning_rate: 0.001
|
| 7 |
+
weight_decay: 0.000001
|
| 8 |
+
mask_fraction: 0.25
|
| 9 |
+
patience: 3
|
| 10 |
+
seed: 2026
|
| 11 |
+
num_workers: 0
|
| 12 |
+
device: cpu
|
| 13 |
+
checkpoint_dir: weight/training
|
configuration.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"framework":"Pytorch","task":"atmospheric-representation-learning","implementation":"tiny-smoke-and-official-resources"}
|
model/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .tiny_atmorep import TinyAtmoRep, ensemble_statistical_loss
|
| 2 |
+
|
| 3 |
+
__all__ = ["TinyAtmoRep", "ensemble_statistical_loss"]
|
model/fake_data.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Deterministic fake dataset for the tiny AtmoRep training pipeline."""
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from torch.utils.data import Dataset
|
| 5 |
+
|
| 6 |
+
from .tiny_atmorep import TinyAtmoRepConfig
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class FakeAtmoRepDataset(Dataset):
|
| 10 |
+
def __init__(self, config: TinyAtmoRepConfig, samples: int, seed: int, mask_fraction: float) -> None:
|
| 11 |
+
self.config, self.samples, self.seed = config, samples, seed
|
| 12 |
+
self.num_tokens = (config.input_shape[0] // config.patch_shape[0]) * (config.input_shape[2] // config.patch_shape[1]) * (config.input_shape[3] // config.patch_shape[2])
|
| 13 |
+
self.mask_count = max(1, round(self.num_tokens * mask_fraction))
|
| 14 |
+
|
| 15 |
+
def __len__(self):
|
| 16 |
+
return self.samples
|
| 17 |
+
|
| 18 |
+
def __getitem__(self, index):
|
| 19 |
+
generator = torch.Generator().manual_seed(self.seed + index)
|
| 20 |
+
fields = torch.randn(self.config.input_shape, generator=generator)
|
| 21 |
+
mask = torch.zeros(self.num_tokens, dtype=torch.bool)
|
| 22 |
+
mask[torch.randperm(self.num_tokens, generator=generator)[:self.mask_count]] = True
|
| 23 |
+
return fields, mask
|
model/tiny_atmorep.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Independent tiny single-field AtmoRep-style fallback for pipeline validation."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
from dataclasses import asdict, dataclass
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from torch import Tensor, nn
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass(frozen=True)
|
| 13 |
+
class TinyAtmoRepConfig:
|
| 14 |
+
input_shape: tuple[int, int, int, int] = (4, 1, 8, 8)
|
| 15 |
+
patch_shape: tuple[int, int, int] = (1, 4, 4)
|
| 16 |
+
embed_dim: int = 32
|
| 17 |
+
num_heads: int = 4
|
| 18 |
+
num_layers: int = 2
|
| 19 |
+
ensemble_size: int = 4
|
| 20 |
+
|
| 21 |
+
def to_dict(self) -> dict:
|
| 22 |
+
return asdict(self)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class TinyAtmoRep(nn.Module):
|
| 26 |
+
"""Masked-token transformer with four-dimensional token conditioning.
|
| 27 |
+
|
| 28 |
+
Inputs use ``[batch, time, variable, latitude, longitude]``. This fallback
|
| 29 |
+
intentionally supports one field only; level is supplied as token metadata.
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
def __init__(self, config: TinyAtmoRepConfig | None = None) -> None:
|
| 33 |
+
super().__init__()
|
| 34 |
+
self.config = config or TinyAtmoRepConfig()
|
| 35 |
+
time, variables, height, width = self.config.input_shape
|
| 36 |
+
pt, ph, pw = self.config.patch_shape
|
| 37 |
+
if variables != 1:
|
| 38 |
+
raise ValueError("TinyAtmoRep is a single-field fallback (V must equal 1)")
|
| 39 |
+
if time % pt or height % ph or width % pw:
|
| 40 |
+
raise ValueError("input_shape must be divisible by patch_shape")
|
| 41 |
+
|
| 42 |
+
self.grid_shape = (time // pt, height // ph, width // pw)
|
| 43 |
+
self.patch_dim = pt * ph * pw
|
| 44 |
+
self.patch_embed = nn.Conv3d(
|
| 45 |
+
1, self.config.embed_dim, kernel_size=self.config.patch_shape,
|
| 46 |
+
stride=self.config.patch_shape,
|
| 47 |
+
)
|
| 48 |
+
self.condition_embed = nn.Sequential(
|
| 49 |
+
nn.Linear(4, self.config.embed_dim), nn.GELU(),
|
| 50 |
+
nn.Linear(self.config.embed_dim, self.config.embed_dim),
|
| 51 |
+
)
|
| 52 |
+
self.mask_token = nn.Parameter(torch.zeros(1, 1, self.config.embed_dim))
|
| 53 |
+
layer = nn.TransformerEncoderLayer(
|
| 54 |
+
d_model=self.config.embed_dim,
|
| 55 |
+
nhead=self.config.num_heads,
|
| 56 |
+
dim_feedforward=4 * self.config.embed_dim,
|
| 57 |
+
dropout=0.0,
|
| 58 |
+
activation="gelu",
|
| 59 |
+
batch_first=True,
|
| 60 |
+
norm_first=True,
|
| 61 |
+
)
|
| 62 |
+
self.encoder = nn.TransformerEncoder(layer, self.config.num_layers)
|
| 63 |
+
self.ensemble_heads = nn.ModuleList(
|
| 64 |
+
nn.Linear(self.config.embed_dim, self.patch_dim)
|
| 65 |
+
for _ in range(self.config.ensemble_size)
|
| 66 |
+
)
|
| 67 |
+
nn.init.normal_(self.mask_token, std=0.02)
|
| 68 |
+
|
| 69 |
+
@property
|
| 70 |
+
def num_tokens(self) -> int:
|
| 71 |
+
return math.prod(self.grid_shape)
|
| 72 |
+
|
| 73 |
+
def token_conditions(self, batch_size: int, level: float, device: torch.device) -> Tensor:
|
| 74 |
+
"""Return normalized [time, level, latitude, longitude] per token."""
|
| 75 |
+
nt, nh, nw = self.grid_shape
|
| 76 |
+
axes = [torch.linspace(-1.0, 1.0, n, device=device) for n in (nt, nh, nw)]
|
| 77 |
+
time, latitude, longitude = torch.meshgrid(*axes, indexing="ij")
|
| 78 |
+
model_level = torch.full_like(time, float(level) / 137.0)
|
| 79 |
+
conditions = torch.stack((time, model_level, latitude, longitude), dim=-1)
|
| 80 |
+
return conditions.reshape(1, self.num_tokens, 4).expand(batch_size, -1, -1)
|
| 81 |
+
|
| 82 |
+
def tokenize(self, fields: Tensor) -> Tensor:
|
| 83 |
+
self._validate_fields(fields)
|
| 84 |
+
volume = fields.permute(0, 2, 1, 3, 4)
|
| 85 |
+
pt, ph, pw = self.config.patch_shape
|
| 86 |
+
patches = volume.unfold(2, pt, pt).unfold(3, ph, ph).unfold(4, pw, pw)
|
| 87 |
+
return patches.permute(0, 2, 3, 4, 1, 5, 6, 7).reshape(
|
| 88 |
+
fields.shape[0], self.num_tokens, self.patch_dim
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
def forward(self, fields: Tensor, mask: Tensor, level: float = 137.0) -> Tensor:
|
| 92 |
+
self._validate_fields(fields)
|
| 93 |
+
if mask.shape != (fields.shape[0], self.num_tokens) or mask.dtype != torch.bool:
|
| 94 |
+
raise ValueError(f"mask must be bool [B, {self.num_tokens}]")
|
| 95 |
+
tokens = self.patch_embed(fields.permute(0, 2, 1, 3, 4)).flatten(2).transpose(1, 2)
|
| 96 |
+
conditions = self.token_conditions(fields.shape[0], level, fields.device)
|
| 97 |
+
tokens = tokens + self.condition_embed(conditions)
|
| 98 |
+
tokens = torch.where(mask.unsqueeze(-1), self.mask_token.expand_as(tokens), tokens)
|
| 99 |
+
encoded = self.encoder(tokens)
|
| 100 |
+
return torch.stack([head(encoded) for head in self.ensemble_heads], dim=1)
|
| 101 |
+
|
| 102 |
+
def _validate_fields(self, fields: Tensor) -> None:
|
| 103 |
+
expected = self.config.input_shape
|
| 104 |
+
if fields.ndim != 5 or tuple(fields.shape[1:]) != expected:
|
| 105 |
+
raise ValueError(f"fields must have shape [B, {expected}], got {tuple(fields.shape)}")
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def ensemble_statistical_loss(
|
| 109 |
+
predictions: Tensor,
|
| 110 |
+
targets: Tensor,
|
| 111 |
+
mask: Tensor,
|
| 112 |
+
statistical_weight: float = 0.1,
|
| 113 |
+
) -> tuple[Tensor, dict[str, Tensor]]:
|
| 114 |
+
"""Combine masked ensemble MSE with ensemble mean/spread statistics."""
|
| 115 |
+
if predictions.ndim != 4 or targets.ndim != 3:
|
| 116 |
+
raise ValueError("predictions must be [B,E,N,P] and targets [B,N,P]")
|
| 117 |
+
selected = mask[:, None, :, None].expand_as(predictions)
|
| 118 |
+
expanded_targets = targets[:, None].expand_as(predictions)
|
| 119 |
+
ensemble_mse = (predictions[selected] - expanded_targets[selected]).square().mean()
|
| 120 |
+
|
| 121 |
+
ensemble_mean = predictions.mean(dim=1)
|
| 122 |
+
ensemble_std = predictions.std(dim=1, unbiased=False)
|
| 123 |
+
target_std = targets.std(dim=-1, unbiased=False, keepdim=True).expand_as(targets)
|
| 124 |
+
masked = mask.unsqueeze(-1).expand_as(targets)
|
| 125 |
+
mean_loss = (ensemble_mean[masked] - targets[masked]).square().mean()
|
| 126 |
+
spread_loss = (ensemble_std[masked] - target_std[masked]).square().mean()
|
| 127 |
+
stats_loss = mean_loss + spread_loss
|
| 128 |
+
total = ensemble_mse + statistical_weight * stats_loss
|
| 129 |
+
return total, {
|
| 130 |
+
"ensemble_mse": ensemble_mse.detach(),
|
| 131 |
+
"statistical": stats_loss.detach(),
|
| 132 |
+
}
|
resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml105.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6d509500b9b870f44f12e998e7e50ab3c5cb0646867d5c2789b63af51ad0836b
|
| 3 |
+
size 8640
|
resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml114.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e2fac57f8d31533a7a4c825e2240c6751a816be93ab79ebff7007368749b537e
|
| 3 |
+
size 8640
|
resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml123.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c5f5d843b9bbf5d876d1faa287ba09fa2556ecf9b3437bc6289f159d34a288de
|
| 3 |
+
size 8640
|
resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml137.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a980bfa8920ae1ddeca783852811b1d49aad80de695e57baa5dcab1b35249e0f
|
| 3 |
+
size 8640
|
resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml96.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e4344b3f123303183e2a2af5d58ab001982406225256e75ca158153a0d41f5a7
|
| 3 |
+
size 8640
|
resources/id4nvwbetz/AtmoRep_id4nvwbetz.mod
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fec447ac33bf6774dc552689199822f2803d772783a473a9452ce558c5d4ff55
|
| 3 |
+
size 2719496689
|
resources/id4nvwbetz/model_id4nvwbetz.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"num_accs_per_task": 1, "with_hvd": true, "hvd_rank": 0, "hvd_size": 16, "back_passes_per_step": 4, "comment": "", "file_format": "grib", "data_dir": "../data/", "level_type": "ml", "fields": [["vorticity", [1, 2048, [], 0], [96, 105, 114, 123, 137], [12, 6, 12], [3, 9, 9], [0.5, 0.9, 0.1, 0.05]]], "fields_prediction": [["vorticity", 1.0]], "fields_targets": [], "years_train": [1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017], "years_test": [2018], "month": null, "geo_range_sampling": [[-90.0, 90.0], [0.0, 360.0]], "time_sampling": 1, "data_smoothing": 0, "file_shape": [-1, 721, 1440], "num_t_samples": 744, "num_patches_per_t_train": 4, "num_patches_per_t_test": 1, "torch_seed": 5371822430435701752, "batch_size_test": 24, "batch_size_start": 6, "batch_size_max": 6, "batch_size_delta": 8, "num_epochs": 2048, "num_files_train": 2, "num_files_test": 2, "loader_num_workers": 8, "size_token_info": 8, "size_token_info_net": 16, "grad_checkpointing": true, "with_cls": false, "with_layernorm": true, "coupling_num_heads_per_field": 1, "dropout_rate": 0.05, "learnable_mask": false, "with_qk_lnorm": true, "num_neighborhoods": 1, "encoder_num_layers": 10, "encoder_num_heads": 16, "encoder_num_mlp_layers": 2, "encoder_att_type": "dense", "decoder_num_layers": 10, "decoder_num_heads": 16, "decoder_num_mlp_layers": 2, "decoder_self_att": false, "decoder_cross_att_ratio": 0.5, "decoder_cross_att_rate": 1.0, "decoder_att_type": "dense", "net_tail_num_nets": 16, "net_tail_num_layers": 0, "ensemble_weighted": false, "ensemble_weighted_T": 50.0, "losses": ["mse_ensemble", "stats"], "lr_start": 1e-05, "lr_max": 8e-05, "lr_min": 6.000000000000001e-05, "weight_decay": 0.025, "lr_decay_rate": 1.025, "lr_start_epochs": 3, "BERT_strategy": "BERT", "BERT_window": false, "BERT_fields_synced": false, "BERT_mr_max": 2, "log_test_num_ranks": 0, "save_grads": false, "profile": false, "test_initial": true, "rng_seed": null, "with_wandb": true, "slurm_job_id": "7749847", "wandb_id": "4nvwbetz", "lat_sampling_weighted": true}
|
scripts/download_official_resources.sh
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
ROOT="${1:?usage: download_official_resources.sh ROOT_DIR}"
|
| 5 |
+
COMMIT="055f858e68f5e0151eb6fece9f6b574d3da4af8d"
|
| 6 |
+
REPOSITORY="https://github.com/clessig/atmorep.git"
|
| 7 |
+
MODEL_URL="https://datapub.fz-juelich.de/atmorep/models/model_id4nvwbetz.tar.gz"
|
| 8 |
+
|
| 9 |
+
mkdir -p "$ROOT/vendor" "$ROOT/resources"
|
| 10 |
+
if [[ ! -d "$ROOT/vendor/atmorep-official/.git" ]]; then
|
| 11 |
+
git clone --no-checkout "$REPOSITORY" "$ROOT/vendor/atmorep-official"
|
| 12 |
+
fi
|
| 13 |
+
git -C "$ROOT/vendor/atmorep-official" fetch origin "$COMMIT"
|
| 14 |
+
git -C "$ROOT/vendor/atmorep-official" checkout --detach "$COMMIT"
|
| 15 |
+
curl -fL --retry 3 --continue-at - -o "$ROOT/resources/model_id4nvwbetz.tar.gz" "$MODEL_URL"
|
| 16 |
+
gzip -t "$ROOT/resources/model_id4nvwbetz.tar.gz"
|
| 17 |
+
tar -xzf "$ROOT/resources/model_id4nvwbetz.tar.gz" -C "$ROOT/resources"
|
| 18 |
+
sha256sum "$ROOT/resources/model_id4nvwbetz.tar.gz" \
|
| 19 |
+
"$ROOT/resources/id4nvwbetz/AtmoRep_id4nvwbetz.mod" \
|
| 20 |
+
"$ROOT/resources/id4nvwbetz/model_id4nvwbetz.json"
|
scripts/inference.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import argparse
|
| 5 |
+
import json
|
| 6 |
+
import sys
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 12 |
+
sys.path.insert(0, str(ROOT))
|
| 13 |
+
|
| 14 |
+
from model.tiny_atmorep import TinyAtmoRep, TinyAtmoRepConfig
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def main() -> None:
|
| 18 |
+
parser = argparse.ArgumentParser()
|
| 19 |
+
parser.add_argument("--checkpoint", type=Path, default=ROOT / "weight" / "tiny_atmorep.pth")
|
| 20 |
+
parser.add_argument("--output", type=Path, default=ROOT / "result" / "prediction.pt")
|
| 21 |
+
parser.add_argument("--seed", type=int, default=17)
|
| 22 |
+
args = parser.parse_args()
|
| 23 |
+
|
| 24 |
+
payload = torch.load(args.checkpoint, map_location="cpu", weights_only=True)
|
| 25 |
+
config = TinyAtmoRepConfig(**payload["config"])
|
| 26 |
+
model = TinyAtmoRep(config)
|
| 27 |
+
model.load_state_dict(payload["model"])
|
| 28 |
+
model.eval()
|
| 29 |
+
torch.manual_seed(args.seed)
|
| 30 |
+
fields = torch.randn(1, *config.input_shape)
|
| 31 |
+
mask = torch.zeros(1, model.num_tokens, dtype=torch.bool)
|
| 32 |
+
mask[:, 1::4] = True
|
| 33 |
+
with torch.inference_mode():
|
| 34 |
+
ensemble = model(fields, mask, level=137.0)
|
| 35 |
+
target = model.tokenize(fields)
|
| 36 |
+
result = {
|
| 37 |
+
"ensemble": ensemble,
|
| 38 |
+
"ensemble_mean": ensemble.mean(dim=1),
|
| 39 |
+
"ensemble_std": ensemble.std(dim=1, unbiased=False),
|
| 40 |
+
"mask": mask,
|
| 41 |
+
"target": target,
|
| 42 |
+
"input_shape": tuple(fields.shape),
|
| 43 |
+
}
|
| 44 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 45 |
+
torch.save(target, args.output.parent / "target.pt")
|
| 46 |
+
torch.save(result, args.output)
|
| 47 |
+
print(json.dumps({
|
| 48 |
+
"output": str(args.output),
|
| 49 |
+
"ensemble_shape": list(ensemble.shape),
|
| 50 |
+
"mean_shape": list(result["ensemble_mean"].shape),
|
| 51 |
+
"finite": bool(torch.isfinite(ensemble).all()),
|
| 52 |
+
"bytes": args.output.stat().st_size,
|
| 53 |
+
}, indent=2))
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
if __name__ == "__main__":
|
| 57 |
+
main()
|
scripts/result.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Show the saved tiny AtmoRep training and inference summaries."""
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import matplotlib
|
| 8 |
+
matplotlib.use("Agg")
|
| 9 |
+
import matplotlib.pyplot as plt
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
if __name__ == "__main__":
|
| 16 |
+
history_path = ROOT / "weight" / "training" / "history.json"
|
| 17 |
+
train_history = json.loads(history_path.read_text()) if history_path.exists() else []
|
| 18 |
+
prediction = torch.load(ROOT / "result" / "prediction.pt", map_location="cpu", weights_only=True)
|
| 19 |
+
target = torch.load(ROOT / "result" / "target.pt", map_location="cpu", weights_only=True)
|
| 20 |
+
ensemble = prediction["ensemble"]
|
| 21 |
+
target = target["target"] if isinstance(target, dict) else target
|
| 22 |
+
mean = ensemble.mean(dim=1)
|
| 23 |
+
std = ensemble.std(dim=1, unbiased=False)
|
| 24 |
+
ensemble_rmse = torch.sqrt((ensemble - target[:, None]).square().mean())
|
| 25 |
+
mean_rmse = torch.sqrt((mean - target).square().mean())
|
| 26 |
+
target_std = target.std(dim=-1, unbiased=False, keepdim=True).expand_as(target)
|
| 27 |
+
spread_rmse = torch.sqrt((std - target_std).square().mean())
|
| 28 |
+
summary = {
|
| 29 |
+
"train_history": train_history,
|
| 30 |
+
"ensemble_shape": list(ensemble.shape),
|
| 31 |
+
"target_shape": list(target.shape),
|
| 32 |
+
"ensemble_rmse": float(ensemble_rmse),
|
| 33 |
+
"mean_rmse": float(mean_rmse),
|
| 34 |
+
"spread_rmse": float(spread_rmse),
|
| 35 |
+
"finite": bool(torch.isfinite(ensemble).all()),
|
| 36 |
+
}
|
| 37 |
+
(ROOT / "result" / "metrics.json").write_text(json.dumps(summary, indent=2) + "\n")
|
| 38 |
+
fig, axes = plt.subplots(1, 4, figsize=(12, 3))
|
| 39 |
+
for ax, data, title in zip(axes, (target[0, 0], mean[0, 0], (mean - target)[0, 0].abs(), std[0, 0]), ("Target", "Mean", "Abs error", "Spread")):
|
| 40 |
+
image = ax.imshow(data.reshape(4, 4).numpy(), cmap="viridis")
|
| 41 |
+
ax.set_title(title)
|
| 42 |
+
plt.colorbar(image, ax=ax, shrink=0.75)
|
| 43 |
+
plt.tight_layout()
|
| 44 |
+
plt.savefig(ROOT / "result" / "comparison.png", dpi=150)
|
| 45 |
+
plt.close()
|
| 46 |
+
print(json.dumps(summary, indent=2))
|
scripts/train.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Train tiny AtmoRep with train/validation epochs and resumable checkpoints."""
|
| 2 |
+
|
| 3 |
+
import argparse, json, random, sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch, yaml
|
| 7 |
+
from torch.utils.data import DataLoader
|
| 8 |
+
|
| 9 |
+
ROOT = Path(__file__).resolve().parents[1]; sys.path.insert(0, str(ROOT))
|
| 10 |
+
from model.fake_data import FakeAtmoRepDataset
|
| 11 |
+
from model.tiny_atmorep import TinyAtmoRep, TinyAtmoRepConfig, ensemble_statistical_loss
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def args():
|
| 15 |
+
p0=argparse.ArgumentParser(add_help=False); p0.add_argument("--config",type=Path,default=ROOT/"conf/config.yaml"); k,_=p0.parse_known_args(); c=yaml.safe_load(k.config.read_text())["training"]
|
| 16 |
+
p=argparse.ArgumentParser(parents=[p0]);
|
| 17 |
+
for name, typ in (("epochs", int), ("batch-size", int), ("train-samples", int), ("validation-samples", int), ("seed", int), ("patience", int), ("num-workers", int), ("learning-rate", float), ("weight-decay", float), ("mask-fraction", float)):
|
| 18 |
+
p.add_argument("--" + name, type=typ, default=c[name.replace("-", "_")])
|
| 19 |
+
p.add_argument("--checkpoint-dir",type=Path,default=ROOT/c["checkpoint_dir"]); p.add_argument("--resume",type=Path); p.add_argument("--device",choices=("cpu","cuda"),default=c["device"]); return p.parse_args()
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def run_epoch(model, loader, device, optimizer=None):
|
| 23 |
+
model.train(optimizer is not None); totals=[]
|
| 24 |
+
context=torch.enable_grad() if optimizer else torch.inference_mode()
|
| 25 |
+
with context:
|
| 26 |
+
for fields,mask in loader:
|
| 27 |
+
fields,mask=fields.to(device),mask.to(device); pred=model(fields,mask,level=137.0); target=model.tokenize(fields); loss,_=ensemble_statistical_loss(pred,target,mask)
|
| 28 |
+
if optimizer: optimizer.zero_grad(set_to_none=True); loss.backward(); torch.nn.utils.clip_grad_norm_(model.parameters(),1.0); optimizer.step()
|
| 29 |
+
totals.append(float(loss.detach()))
|
| 30 |
+
return float(np.mean(totals))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def main():
|
| 34 |
+
a=args(); device=torch.device(a.device); random.seed(a.seed); np.random.seed(a.seed); torch.manual_seed(a.seed); a.checkpoint_dir.mkdir(parents=True,exist_ok=True)
|
| 35 |
+
config=TinyAtmoRepConfig(); model=TinyAtmoRep(config).to(device); opt=torch.optim.AdamW(model.parameters(),lr=a.learning_rate,weight_decay=a.weight_decay); sched=torch.optim.lr_scheduler.ReduceLROnPlateau(opt,patience=max(1,a.patience//2),factor=.5)
|
| 36 |
+
train=DataLoader(FakeAtmoRepDataset(config,a.train_samples,a.seed,a.mask_fraction),batch_size=a.batch_size,shuffle=True,num_workers=a.num_workers); val=DataLoader(FakeAtmoRepDataset(config,a.validation_samples,a.seed+100000,a.mask_fraction),batch_size=a.batch_size,num_workers=a.num_workers)
|
| 37 |
+
start=0; best=float("inf"); history=[]
|
| 38 |
+
if a.resume:
|
| 39 |
+
q=torch.load(a.resume,map_location=device,weights_only=False); model.load_state_dict(q["model"]); opt.load_state_dict(q["optimizer"]); sched.load_state_dict(q["scheduler"]); start=q["epoch"]+1; best=q["best_val_loss"]; history=q["history"]
|
| 40 |
+
stale=0
|
| 41 |
+
for epoch in range(start,a.epochs):
|
| 42 |
+
tr=run_epoch(model,train,device,opt); va=run_epoch(model,val,device); sched.step(va); row={"epoch":epoch,"train_loss":tr,"validation_loss":va,"learning_rate":opt.param_groups[0]["lr"]}; history.append(row); improved=va<best; best=min(best,va); stale=0 if improved else stale+1
|
| 43 |
+
train_config = {key: str(value) if isinstance(value, Path) else value for key, value in vars(a).items()}
|
| 44 |
+
payload={"model":model.state_dict(),"optimizer":opt.state_dict(),"scheduler":sched.state_dict(),"config":config.to_dict(),"epoch":epoch,"step":len(train)*(epoch+1),"best_val_loss":best,"history":history,"train_config":train_config}
|
| 45 |
+
torch.save(payload,a.checkpoint_dir/"latest.pth"); torch.save(payload,ROOT/"weight/tiny_atmorep.pth")
|
| 46 |
+
if improved: torch.save(payload,a.checkpoint_dir/"best.pth")
|
| 47 |
+
(a.checkpoint_dir/"history.json").write_text(json.dumps(history,indent=2)+"\n"); print(json.dumps(row))
|
| 48 |
+
if stale>=a.patience: break
|
| 49 |
+
print(json.dumps({"status":"completed","epochs_completed":len(history),"best_validation_loss":best},indent=2))
|
| 50 |
+
if __name__=="__main__": main()
|
vendor/atmorep-official/.github/ISSUE_TEMPLATE/bug_report.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: Bug report
|
| 3 |
+
about: Create a report to help us improve
|
| 4 |
+
title: "[BUG]"
|
| 5 |
+
labels: ''
|
| 6 |
+
assignees: ''
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
**Describe the bug**
|
| 11 |
+
A clear and concise description of what the bug is.
|
| 12 |
+
|
| 13 |
+
**To Reproduce**
|
| 14 |
+
Steps to reproduce the behavior:
|
| 15 |
+
1. Activate '...'
|
| 16 |
+
2. Change '....'
|
| 17 |
+
3. Run '....'
|
| 18 |
+
4. See error
|
| 19 |
+
|
| 20 |
+
**Expected behavior**
|
| 21 |
+
A clear and concise description of what you expected to happen.
|
| 22 |
+
|
| 23 |
+
**Screenshots**
|
| 24 |
+
If applicable, add screenshots to help explain your problem.
|
| 25 |
+
|
| 26 |
+
**Hardware and environment:**
|
| 27 |
+
- System: [e.g. ATOS, LUMI, JSC]
|
| 28 |
+
- Runtime: [e.g. interactive, sbatch]
|
| 29 |
+
- Special branch: [e.g. develop, main]
|
| 30 |
+
- Python Version: [e.g. 3.11]
|
| 31 |
+
|
| 32 |
+
**Additional context**
|
| 33 |
+
Add any other context about the problem here.
|
vendor/atmorep-official/.github/ISSUE_TEMPLATE/feature_request.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: Feature request
|
| 3 |
+
about: Suggest an idea for this project
|
| 4 |
+
title: "[FEATURE]"
|
| 5 |
+
labels: ''
|
| 6 |
+
assignees: ''
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
**Is your feature request related to a problem? Please describe.**
|
| 11 |
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
| 12 |
+
|
| 13 |
+
**Describe the solution you'd like**
|
| 14 |
+
A clear and concise description of what you want to happen. Please use bullet points and action items when possible
|
| 15 |
+
|
| 16 |
+
**Describe alternatives you've considered**
|
| 17 |
+
A clear and concise description of any alternative solutions or features you've considered.
|
| 18 |
+
|
| 19 |
+
**Additional context**
|
| 20 |
+
Add any other context or screenshots about the feature request here.
|
vendor/atmorep-official/.github/ISSUE_TEMPLATE/scientific-discussion.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: Scientific discussion
|
| 3 |
+
about: discuss scientific developments
|
| 4 |
+
title: "[SCIENTIFIC]"
|
| 5 |
+
labels: ''
|
| 6 |
+
assignees: ''
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
**Describe the topic**
|
| 11 |
+
A clear and concise description of the scientific topic you want to discuss.
|
| 12 |
+
|
| 13 |
+
**Describe the technical developments**
|
| 14 |
+
A clear and concise description of the technical advancements needed to make this happen. Ideally as bullet points
|
| 15 |
+
|
| 16 |
+
**Additional context**
|
| 17 |
+
Add any other context or screenshots about the discussed topic here.
|
| 18 |
+
|
| 19 |
+
** Related issues**
|
| 20 |
+
link here the issues related to this scientific discussion.
|
vendor/atmorep-official/.gitignore
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
|
| 9 |
+
# Distribution / packaging
|
| 10 |
+
.Python
|
| 11 |
+
build/
|
| 12 |
+
develop-eggs/
|
| 13 |
+
dist/
|
| 14 |
+
downloads/
|
| 15 |
+
eggs/
|
| 16 |
+
.eggs/
|
| 17 |
+
lib/
|
| 18 |
+
lib64/
|
| 19 |
+
parts/
|
| 20 |
+
sdist/
|
| 21 |
+
var/
|
| 22 |
+
wheels/
|
| 23 |
+
share/python-wheels/
|
| 24 |
+
*.egg-info/
|
| 25 |
+
.installed.cfg
|
| 26 |
+
*.egg
|
| 27 |
+
MANIFEST
|
| 28 |
+
|
| 29 |
+
# PyInstaller
|
| 30 |
+
# Usually these files are written by a python script from a template
|
| 31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 32 |
+
*.manifest
|
| 33 |
+
*.spec
|
| 34 |
+
|
| 35 |
+
# Installer logs
|
| 36 |
+
pip-log.txt
|
| 37 |
+
pip-delete-this-directory.txt
|
| 38 |
+
|
| 39 |
+
# Unit test / coverage reports
|
| 40 |
+
htmlcov/
|
| 41 |
+
.tox/
|
| 42 |
+
.nox/
|
| 43 |
+
.coverage
|
| 44 |
+
.coverage.*
|
| 45 |
+
.cache
|
| 46 |
+
nosetests.xml
|
| 47 |
+
coverage.xml
|
| 48 |
+
*.cover
|
| 49 |
+
*.py,cover
|
| 50 |
+
.hypothesis/
|
| 51 |
+
.pytest_cache/
|
| 52 |
+
cover/
|
| 53 |
+
|
| 54 |
+
# Translations
|
| 55 |
+
*.mo
|
| 56 |
+
*.pot
|
| 57 |
+
|
| 58 |
+
# Django stuff:
|
| 59 |
+
*.log
|
| 60 |
+
local_settings.py
|
| 61 |
+
db.sqlite3
|
| 62 |
+
db.sqlite3-journal
|
| 63 |
+
|
| 64 |
+
# Flask stuff:
|
| 65 |
+
instance/
|
| 66 |
+
.webassets-cache
|
| 67 |
+
|
| 68 |
+
# Scrapy stuff:
|
| 69 |
+
.scrapy
|
| 70 |
+
|
| 71 |
+
# Sphinx documentation
|
| 72 |
+
docs/_build/
|
| 73 |
+
|
| 74 |
+
# PyBuilder
|
| 75 |
+
.pybuilder/
|
| 76 |
+
target/
|
| 77 |
+
|
| 78 |
+
# Jupyter Notebook
|
| 79 |
+
.ipynb_checkpoints
|
| 80 |
+
|
| 81 |
+
# IPython
|
| 82 |
+
profile_default/
|
| 83 |
+
ipython_config.py
|
| 84 |
+
|
| 85 |
+
# pyenv
|
| 86 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 88 |
+
# .python-version
|
| 89 |
+
|
| 90 |
+
# pipenv
|
| 91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 94 |
+
# install all needed dependencies.
|
| 95 |
+
#Pipfile.lock
|
| 96 |
+
|
| 97 |
+
# poetry
|
| 98 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 100 |
+
# commonly ignored for libraries.
|
| 101 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 102 |
+
#poetry.lock
|
| 103 |
+
|
| 104 |
+
# pdm
|
| 105 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 106 |
+
#pdm.lock
|
| 107 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
| 108 |
+
# in version control.
|
| 109 |
+
# https://pdm.fming.dev/#use-with-ide
|
| 110 |
+
.pdm.toml
|
| 111 |
+
|
| 112 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 113 |
+
__pypackages__/
|
| 114 |
+
|
| 115 |
+
# Celery stuff
|
| 116 |
+
celerybeat-schedule
|
| 117 |
+
celerybeat.pid
|
| 118 |
+
|
| 119 |
+
# SageMath parsed files
|
| 120 |
+
*.sage.py
|
| 121 |
+
|
| 122 |
+
# Environments
|
| 123 |
+
.env
|
| 124 |
+
.venv
|
| 125 |
+
env/
|
| 126 |
+
venv/
|
| 127 |
+
ENV/
|
| 128 |
+
env.bak/
|
| 129 |
+
venv.bak/
|
| 130 |
+
|
| 131 |
+
# Spyder project settings
|
| 132 |
+
.spyderproject
|
| 133 |
+
.spyproject
|
| 134 |
+
|
| 135 |
+
# Rope project settings
|
| 136 |
+
.ropeproject
|
| 137 |
+
|
| 138 |
+
# mkdocs documentation
|
| 139 |
+
/site
|
| 140 |
+
|
| 141 |
+
# mypy
|
| 142 |
+
.mypy_cache/
|
| 143 |
+
.dmypy.json
|
| 144 |
+
dmypy.json
|
| 145 |
+
|
| 146 |
+
# Pyre type checker
|
| 147 |
+
.pyre/
|
| 148 |
+
|
| 149 |
+
# pytype static type analyzer
|
| 150 |
+
.pytype/
|
| 151 |
+
|
| 152 |
+
# Cython debug symbols
|
| 153 |
+
cython_debug/
|
| 154 |
+
|
| 155 |
+
# PyCharm
|
| 156 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 157 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 158 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 159 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 160 |
+
#.idea/
|
| 161 |
+
|
| 162 |
+
# data files
|
| 163 |
+
*.zarr
|
| 164 |
+
|
| 165 |
+
# image files
|
| 166 |
+
*.png
|
| 167 |
+
|
vendor/atmorep-official/CITATION.cff
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This CITATION.cff file was generated with cffinit.
|
| 2 |
+
# Visit https://bit.ly/cffinit to generate yours today!
|
| 3 |
+
|
| 4 |
+
cff-version: 1.2.0
|
| 5 |
+
title: Atmorep
|
| 6 |
+
message: >-
|
| 7 |
+
If you use this software, please cite it using the
|
| 8 |
+
metadata from this file.
|
| 9 |
+
type: software
|
| 10 |
+
authors:
|
| 11 |
+
- given-names: Christian
|
| 12 |
+
family-names: Lessig
|
| 13 |
+
email: christian.lessig@ecmwf.int
|
| 14 |
+
affiliation: European Centre for Medium-Range Weather Forecasts (ECMWF)
|
| 15 |
+
- given-names: Ilaria
|
| 16 |
+
family-names: Luise
|
| 17 |
+
email: ilaria.luise@cern.ch
|
| 18 |
+
affiliation: European Organization for Nuclear Research (CERN)
|
| 19 |
+
- given-names: Martin
|
| 20 |
+
family-names: Schultz
|
| 21 |
+
email: m.schultz@fz-juelich.de
|
| 22 |
+
orcid: 'https://orcid.org/0000-0003-3455-774X'
|
| 23 |
+
affiliation: Forschungszentrum Jülich (FZJ)
|
| 24 |
+
- given-names: Michael
|
| 25 |
+
family-names: Langguth
|
| 26 |
+
email: m.langguth@fz-juelich.de
|
| 27 |
+
orcid: 'https://orcid.org/0000-0003-3354-5333'
|
| 28 |
+
affiliation: Forschungszentrum Jülich (FZJ)
|
| 29 |
+
identifiers:
|
| 30 |
+
- type: url
|
| 31 |
+
value: 'https://arxiv.org/abs/2308.13280'
|
| 32 |
+
description: corresponding Preprint
|
| 33 |
+
repository-code: 'https://isggit.cs.uni-magdeburg.de/atmorep/atmorep'
|
| 34 |
+
url: 'https://www.atmorep.org'
|
| 35 |
+
abstract: >-
|
| 36 |
+
AtmoRep is a novel, task-independent stochastic computer
|
| 37 |
+
model of atmospheric dynamics that can provide skillful
|
| 38 |
+
results for a wide range of applications. AtmoRep uses
|
| 39 |
+
large-scale representation learning from artificial
|
| 40 |
+
intelligence to determine a general description of the
|
| 41 |
+
highly complex, stochastic dynamics of the atmosphere
|
| 42 |
+
from the best available estimate of the system's historical
|
| 43 |
+
trajectory as constrained by observations. This is enabled
|
| 44 |
+
by a novel self-supervised learning objective and a unique
|
| 45 |
+
ensemble that samples from the stochastic model with a
|
| 46 |
+
variability informed by the one in the historical record.
|
| 47 |
+
Our work establishes that large-scale neural networks can
|
| 48 |
+
provide skillful, task-independent models of atmospheric
|
| 49 |
+
dynamics. With this, they provide a novel means to make
|
| 50 |
+
the large record of atmospheric observations accessible
|
| 51 |
+
for applications and for scientific inquiry, complementing
|
| 52 |
+
existing simulations based on first principles.
|
| 53 |
+
license: MIT
|
| 54 |
+
commit: b0da5b32ec70295914bbb486dbcb77885671dc45
|
| 55 |
+
version: 2.0 (preprint)
|
| 56 |
+
date-released: '2023-11-28'
|
vendor/atmorep-official/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2023 Otto-von-Guericke-Universitaet Magdeburg, Forschungszentrum Juelich GmbH, European Organization for Nuclear Research
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
vendor/atmorep-official/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# IMPORTANT NOTE:
|
| 3 |
+
<p align="center">
|
| 4 |
+
<img src="https://github.com/user-attachments/assets/50518092-5386-4ef5-98f3-e2abf147556f" />
|
| 5 |
+
</p>
|
| 6 |
+
|
| 7 |
+
Please note that that the folder is **not maintained anymore since March 1st**. \
|
| 8 |
+
Please use the **WeatherGenerator** code instead: https://github.com/ecmwf/WeatherGenerator
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# AtmoRep
|
| 13 |
+
|
| 14 |
+
This repository contains the source code for the [AtmoRep](https://www.atmorep.org) models for large scale representation learning of atmospheric dynamics as well as links to the pre-trained models and the required model input data.
|
| 15 |
+
|
| 16 |
+
The pre-print for the work is available on ArXiv: https://arxiv.org/abs/2308.13280.
|
| 17 |
+
|
| 18 |
+
```
|
| 19 |
+
@misc{Lessig2023atmorep,
|
| 20 |
+
title = {AtmoRep: A stochastic model of atmosphere dynamics using large scale representation learning},
|
| 21 |
+
author = {Christian Lessig and Ilaria Luise and Bing Gong and Michael Langguth and Scarlet Stadler and Martin Schultz},
|
| 22 |
+
eprint = {2308.13280},
|
| 23 |
+
primaryclass = {physics.ao-ph},
|
| 24 |
+
url = {https://arxiv.org/abs/2308.13280},
|
| 25 |
+
year = {2023},
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
# Starter README
|
| 29 |
+
|
| 30 |
+
## 1. Pull code
|
| 31 |
+
|
| 32 |
+
`````
|
| 33 |
+
%> wget git@github.com:clessig/atmorep.git
|
| 34 |
+
`````
|
| 35 |
+
This creates a directory ``atmorep`` with the code that contains the source code including the python scripts for model training and evaluation.
|
| 36 |
+
|
| 37 |
+
After following the steps described below, the final directory structure will look as follows:
|
| 38 |
+
````
|
| 39 |
+
└── atmorep/
|
| 40 |
+
├── atmorep/
|
| 41 |
+
│ └── ...
|
| 42 |
+
├── data/ <- top level data directory
|
| 43 |
+
│ ├── normalisation/ <- directory for data normalisations
|
| 44 |
+
│ ├── vorticity/
|
| 45 |
+
│ │ ├── ml105/ <- model levels with monthly GRIB files
|
| 46 |
+
│ │ │ ├── era5_vorticity_y2021_m03_ml137.grib <- grib data file
|
| 47 |
+
│ │ │ ├── ...
|
| 48 |
+
│ │ ├── ml114/
|
| 49 |
+
│ │ ├── ml123/
|
| 50 |
+
│ │ ├── ml137/
|
| 51 |
+
│ │ ├── ml96/
|
| 52 |
+
. . .
|
| 53 |
+
│ ├── temperature/
|
| 54 |
+
. .
|
| 55 |
+
├── models
|
| 56 |
+
│ ├── id4nvwbetz <- Directory containing model weights and config
|
| 57 |
+
│ │ ├── model_id4nvwbetz.json
|
| 58 |
+
│ │ └── AtmoRep_id4nvwbetz.mod
|
| 59 |
+
│ ├── id<model_id>
|
| 60 |
+
. .
|
| 61 |
+
└── results
|
| 62 |
+
├── id4nvwbetz
|
| 63 |
+
...
|
| 64 |
+
````
|
| 65 |
+
The directories ``data``, ``models``, and ``results`` need to be created if they do not exist. All directories might be large and should thus be on a directory with sufficient storage space; in this case they can be soft-linked to the default ones above or they can be set in ``atmorep/config/config``.
|
| 66 |
+
|
| 67 |
+
## 2. Download the data
|
| 68 |
+
|
| 69 |
+
### 2.1 Download pre-trained models
|
| 70 |
+
|
| 71 |
+
Models can be downloaded from: https://datapub.fz-juelich.de/atmorep/trained-models.html
|
| 72 |
+
|
| 73 |
+
An example for downloading the pre-trained models is given here, in this case for the vorticity model.
|
| 74 |
+
|
| 75 |
+
`````
|
| 76 |
+
% atmorep/> mkdir models
|
| 77 |
+
% atmorep/> cd models
|
| 78 |
+
% atmorep/data/> wget https://datapub.fz-juelich.de/atmorep/models/model_id4nvwbetz.tar.gz
|
| 79 |
+
% atmorep/data/> tar xvzf model_id4nvwbetz.tar.gz
|
| 80 |
+
% atmorep/data/> ls id4nvwbetz
|
| 81 |
+
AtmoRep_id4nvwbetz.mod model_id4nvwbetz.json
|
| 82 |
+
`````
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
### 2.2 Download model input data (ERA5)
|
| 86 |
+
|
| 87 |
+
The input data in the required structure can be downloaded from the [Jülich datapub](https://datapub.fz-juelich.de/atmorep/era5-data.html) server. Direct link to WebDAV [https://datapub.fz-juelich.de/atmorep/data/](https://datapub.fz-juelich.de/atmorep/data/). Alternatively, it can be directly downloaded from MARS using the following [script](https://www.atmorep.org/code/mars_era5_download.py).
|
| 88 |
+
|
| 89 |
+
#### Download a subset of files
|
| 90 |
+
|
| 91 |
+
All data files (fields and normalizations) should be downloaded into the ``data`` directory. Un-taring the files will generate the correct folder structure. For example (we will use the vorticity example also below to run the first model so it is recommended to download it as a first step):
|
| 92 |
+
`````
|
| 93 |
+
% atmorep/> mkdir data
|
| 94 |
+
% atmorep/> cd data
|
| 95 |
+
% atmorep/data/> wget https://datapub.fz-juelich.de/atmorep/data/vorticity/ml137/era5_vorticity_y2021_ml137.tar
|
| 96 |
+
% atmorep/data/> tar xvf era5_vorticity_y2021_ml137.tar
|
| 97 |
+
% atmorep/data/> ls -lah vorticity/ml137/
|
| 98 |
+
total 18G
|
| 99 |
+
era5_vorticity_y2021_m01_ml137.grib
|
| 100 |
+
era5_vorticity_y2021_m02_ml137.grib
|
| 101 |
+
...
|
| 102 |
+
era5_vorticity_y2021_m12_ml137.grib
|
| 103 |
+
`````
|
| 104 |
+
For efficiency reasons, AtmoRep takes monthly ERA5 data as input. Therefore, each tar file contains 12 GRIB files of about 1.5 GBytes each.
|
| 105 |
+
|
| 106 |
+
Coefficients for data normalization per field and level can be downloaded here: https://datapub.fz-juelich.de/atmorep/data/normalization/. They should also be located in the ```data``` directory:
|
| 107 |
+
`````
|
| 108 |
+
% atmorep/data/> wget https://datapub.fz-juelich.de/atmorep/data/normalization/normalization_vorticity_ml137.tar.gz
|
| 109 |
+
% atmorep/data/> tar xvzf normalization_vorticity_ml137.tar.gz
|
| 110 |
+
`````
|
| 111 |
+
|
| 112 |
+
## 3. Install python packages
|
| 113 |
+
|
| 114 |
+
Create a python environment, e.g.
|
| 115 |
+
|
| 116 |
+
`````
|
| 117 |
+
% atmorep/> python3 -m venv pyenv
|
| 118 |
+
`````
|
| 119 |
+
|
| 120 |
+
and activate the environment:
|
| 121 |
+
|
| 122 |
+
`````
|
| 123 |
+
% atmorep/> source pyenv/bin/activate
|
| 124 |
+
`````
|
| 125 |
+
conda is also possible, no environment is strictly required although we would recommend it. Please make sure to use a recent python version (we tested with python3.10).
|
| 126 |
+
Then install the AtmoRep package:
|
| 127 |
+
`````
|
| 128 |
+
% atmorep/>
|
| 129 |
+
% atmorep/> pip install -e .
|
| 130 |
+
`````
|
| 131 |
+
|
| 132 |
+
torch is currently not included (since it is often available or has particular dependencies, e.g. a specific Cuda version). In the simplest case, it can just be installed by:
|
| 133 |
+
|
| 134 |
+
`````
|
| 135 |
+
% atmorep/> pip install torch
|
| 136 |
+
`````
|
| 137 |
+
We require torch 2.x. (A container solution allows to run even on systems where torch 2.x is not available.)
|
| 138 |
+
|
| 139 |
+
## 4. Run model:
|
| 140 |
+
Pre-trained models can normally be run by:
|
| 141 |
+
`````
|
| 142 |
+
% atmorep/> python atmorep/core/evaluate.py
|
| 143 |
+
`````
|
| 144 |
+
You can easily adapt the configuration by selecting the corresponding _model_id_ in ``evaluate.py`` (see below). It defaults to the single-field configuration of vorticity, of which we have downloaded the data above.
|
| 145 |
+
|
| 146 |
+
Depending on your compute hardware, you might also have to run the computations by submitting the job using a batch system or allocate a compute node in interactive mode (if an interactive seesion is possible, then this is recommended). If you run an interactive session you will likely need to use the following:
|
| 147 |
+
`````
|
| 148 |
+
% atmorep/> export CUDA_VISIBLE_DEVICES=0,1,2,3
|
| 149 |
+
% atmorep/> MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)"
|
| 150 |
+
`````
|
| 151 |
+
|
| 152 |
+
The default evaluation mode is currently global forecast. The output will be (similar to) this:
|
| 153 |
+
````
|
| 154 |
+
devices : ['cuda:0', 'cuda:1', 'cuda:2', 'cuda:3']
|
| 155 |
+
Wandb run: atmorep-ztvyw7k6-8932958
|
| 156 |
+
Running Evaluate.evaluate with mode = global_forecast
|
| 157 |
+
Loaded AtmoRep id=4nvwbetz, ignoring/missing 2 elements.
|
| 158 |
+
Loaded model id = 4nvwbetz at epoch = -2.
|
| 159 |
+
Number of batches per global forecast: 14
|
| 160 |
+
INFO:: data stats vorticity : 5.374998363549821e-05 / 0.9978392720222473
|
| 161 |
+
num_accs_per_task : 1
|
| 162 |
+
with_hvd : True
|
| 163 |
+
hvd_rank : 0
|
| 164 |
+
|
| 165 |
+
...
|
| 166 |
+
|
| 167 |
+
wandb_id : ztvyw7k6
|
| 168 |
+
dates : [[2021, 2, 10, 12]]
|
| 169 |
+
token_overlap : [0, 0]
|
| 170 |
+
forecast_num_tokens : 1
|
| 171 |
+
validation loss for strategy=forecast at epoch 0 : 0.12402566522359848
|
| 172 |
+
validation loss for vorticity : 0.12402566522359848
|
| 173 |
+
wandb: Waiting for W&B process to finish... (success).
|
| 174 |
+
wandb:
|
| 175 |
+
wandb: Run history:
|
| 176 |
+
wandb: val. loss forecast ▁
|
| 177 |
+
wandb: val., forecast, vorticity ▁
|
| 178 |
+
wandb:
|
| 179 |
+
wandb: Run summary:
|
| 180 |
+
wandb: val. loss forecast 0.12403
|
| 181 |
+
wandb: val., forecast, vorticity 0.12403
|
| 182 |
+
wandb:
|
| 183 |
+
wandb: You can sync this run to the cloud by running:
|
| 184 |
+
wandb: wandb sync /p/project/atmo-rep/lessig/atmorep/atmorep/lessig-cleanup/atmorep/wandb/offline-run-20231124_095428-ztvyw7k6
|
| 185 |
+
````
|
| 186 |
+
For the vorticity example above, we evaluate with ``global_forecast`` for a specific date and using only a single model level:
|
| 187 |
+
````
|
| 188 |
+
mode, options = 'global_forecast', { 'fields[0][2]' : [137],
|
| 189 |
+
'dates' : [ [2021, 2, 10, 12] ],
|
| 190 |
+
'token_overlap' : [0, 0],
|
| 191 |
+
'forecast_num_tokens' : 1,
|
| 192 |
+
'attention' : False}
|
| 193 |
+
````
|
| 194 |
+
We perform a 3 hour forecast, since 1 token is 3 hours wide. Another mode is the BERT masked token model mode used for pre-training:
|
| 195 |
+
`````
|
| 196 |
+
mode, options = 'BERT', {'years_test' : [2021], 'fields[0][2]' : [123, 137]}
|
| 197 |
+
`````
|
| 198 |
+
Again, we chose some custom options by using two levels instead of the five ones that are default and were used during pre-training and by using 2021 as the test year (since we downloaded the data).
|
| 199 |
+
|
| 200 |
+
The generated model output (stored in ``./results/id{wandbid}``) for the ```global_forecast``` example can be post-processed into a spatial map with the [following code](https://www.atmorep.org/code/plot_forecast.py). The run_id at the top needs to be replaced by the wandb_id of your run, it can be read off from the console output. Results will be stored as ``example_0000{0,1,2}.png``. The code is also an as-simple-as-possible example with many parameters hard-coded, see our analysis code for a proper handling.
|
vendor/atmorep-official/atmorep/__init__.py
ADDED
|
File without changes
|
vendor/atmorep-official/atmorep/config/__init__.py
ADDED
|
File without changes
|
vendor/atmorep-official/atmorep/config/config.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
fpath = os.path.dirname(os.path.realpath(__file__))
|
| 5 |
+
|
| 6 |
+
path_models = Path( fpath, '../../models/')
|
| 7 |
+
path_results = Path( fpath, '../../results')
|
| 8 |
+
path_plots = Path( fpath, '../results/plots/')
|
| 9 |
+
|
| 10 |
+
grib_index = { 'vorticity' : 'vo', 'divergence' : 'd', 'geopotential' : 'z',
|
| 11 |
+
'orography' : 'z', 'temperature': 't', 'specific_humidity' : 'q',
|
| 12 |
+
'mean_top_net_long_wave_radiation_flux' : 'mtnlwrf',
|
| 13 |
+
'velocity_u' : 'u', 'velocity_v': 'v', 'velocity_z' : 'w',
|
| 14 |
+
'total_precip' : 'tp', 'radar_precip' : 'yw_hourly',
|
| 15 |
+
't2m' : 't_2m', 'u_10m' : 'u_10m', 'v_10m' : 'v_10m', }
|
vendor/atmorep-official/atmorep/core/__init__.py
ADDED
|
File without changes
|
vendor/atmorep-official/atmorep/core/atmorep_model.py
ADDED
|
@@ -0,0 +1,590 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import numpy as np
|
| 19 |
+
import code
|
| 20 |
+
import os
|
| 21 |
+
# code.interact(local=locals())
|
| 22 |
+
|
| 23 |
+
# import horovod.torch as hvd
|
| 24 |
+
|
| 25 |
+
import atmorep.utils.utils as utils
|
| 26 |
+
from atmorep.utils.utils import identity
|
| 27 |
+
from atmorep.utils.utils import NetMode
|
| 28 |
+
from atmorep.utils.utils import get_model_filename
|
| 29 |
+
|
| 30 |
+
from atmorep.transformer.transformer_base import prepare_token
|
| 31 |
+
from atmorep.transformer.transformer_base import checkpoint_wrapper
|
| 32 |
+
|
| 33 |
+
from atmorep.datasets.multifield_data_sampler import MultifieldDataSampler
|
| 34 |
+
|
| 35 |
+
from atmorep.transformer.transformer_encoder import TransformerEncoder
|
| 36 |
+
from atmorep.transformer.transformer_decoder import TransformerDecoder
|
| 37 |
+
from atmorep.transformer.tail_ensemble import TailEnsemble
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
####################################################################################################
|
| 41 |
+
class AtmoRepData( torch.nn.Module) :
|
| 42 |
+
|
| 43 |
+
def __init__( self, net) :
|
| 44 |
+
'''Wrapper class for AtmoRep that handles data loading'''
|
| 45 |
+
|
| 46 |
+
super( AtmoRepData, self).__init__()
|
| 47 |
+
|
| 48 |
+
self.data_loader_test = None
|
| 49 |
+
self.data_loader_train = None
|
| 50 |
+
self.data_loader_iter = None
|
| 51 |
+
|
| 52 |
+
self.net = net
|
| 53 |
+
|
| 54 |
+
# ensure that all data loaders have the same seed and hence load the same data
|
| 55 |
+
self.rng_seed = net.cf.rng_seed
|
| 56 |
+
if not self.rng_seed :
|
| 57 |
+
self.rng_seed = int(torch.randint( 100000000, (1,)))
|
| 58 |
+
|
| 59 |
+
###################################################
|
| 60 |
+
def set_data( self, mode : NetMode, times_pos, batch_size = -1, num_loader_workers = -1) :
|
| 61 |
+
|
| 62 |
+
cf = self.net.cf
|
| 63 |
+
if batch_size < 0 :
|
| 64 |
+
batch_size = cf.batch_size_train if mode == NetMode.train else cf.batch_size_test
|
| 65 |
+
|
| 66 |
+
dataset = self.dataset_train if mode == NetMode.train else self.dataset_test
|
| 67 |
+
dataset.set_data( times_pos, batch_size)
|
| 68 |
+
|
| 69 |
+
self._set_data( dataset, mode, batch_size, num_loader_workers)
|
| 70 |
+
|
| 71 |
+
###################################################
|
| 72 |
+
def set_global( self, mode : NetMode, times, batch_size = -1, num_loader_workers = -1) :
|
| 73 |
+
|
| 74 |
+
cf = self.net.cf
|
| 75 |
+
if batch_size < 0 :
|
| 76 |
+
batch_size = cf.batch_size_train if mode == NetMode.train else cf.batch_size_test
|
| 77 |
+
dataset = self.dataset_train if mode == NetMode.train else self.dataset_test
|
| 78 |
+
dataset.set_global( times, batch_size, cf.token_overlap)
|
| 79 |
+
|
| 80 |
+
self._set_data( dataset, mode, batch_size, num_loader_workers)
|
| 81 |
+
|
| 82 |
+
###################################################
|
| 83 |
+
def set_location( self, mode : NetMode, pos, years, months, num_t_samples_per_month,
|
| 84 |
+
batch_size = -1, num_loader_workers = -1) :
|
| 85 |
+
|
| 86 |
+
cf = self.net.cf
|
| 87 |
+
if batch_size < 0 :
|
| 88 |
+
batch_size = cf.batch_size_train if mode == NetMode.train else cf.batch_size_test
|
| 89 |
+
|
| 90 |
+
dataset = self.dataset_train if mode == NetMode.train else self.dataset_test
|
| 91 |
+
dataset.set_location( pos, years, months, num_t_samples_per_month, batch_size)
|
| 92 |
+
|
| 93 |
+
self._set_data( dataset, mode, batch_size, num_loader_workers)
|
| 94 |
+
|
| 95 |
+
###################################################
|
| 96 |
+
def _set_data( self, dataset, mode : NetMode, batch_size = -1, loader_workers = -1) :
|
| 97 |
+
'''Private implementation for set_data, set_global'''
|
| 98 |
+
|
| 99 |
+
cf = self.net.cf
|
| 100 |
+
if loader_workers < 0 :
|
| 101 |
+
loader_workers = cf.num_loader_workers
|
| 102 |
+
|
| 103 |
+
loader_params = { 'batch_size': None, 'batch_sampler': None, 'shuffle': False,
|
| 104 |
+
'num_workers': loader_workers, 'pin_memory': True}
|
| 105 |
+
|
| 106 |
+
if mode == NetMode.train :
|
| 107 |
+
self.data_loader_train = torch.utils.data.DataLoader( dataset, **loader_params,
|
| 108 |
+
sampler = None)
|
| 109 |
+
elif mode == NetMode.test :
|
| 110 |
+
self.data_loader_test = torch.utils.data.DataLoader( dataset, **loader_params,
|
| 111 |
+
sampler = None)
|
| 112 |
+
else :
|
| 113 |
+
assert False
|
| 114 |
+
|
| 115 |
+
###################################################
|
| 116 |
+
def normalizer( self, field, vl_idx, lats_idx, lons_idx ) :
|
| 117 |
+
|
| 118 |
+
if isinstance( field, str) :
|
| 119 |
+
for fidx, field_info in enumerate(self.cf.fields) :
|
| 120 |
+
if field == field_info[0] :
|
| 121 |
+
break
|
| 122 |
+
assert fidx < len(self.cf.fields), 'invalid field'
|
| 123 |
+
normalizer = self.dataset_train.datasets[fidx].normalizer
|
| 124 |
+
|
| 125 |
+
elif isinstance( field, int) :
|
| 126 |
+
normalizer = self.dataset_train.normalizers[field][vl_idx]
|
| 127 |
+
if len(normalizer.shape) > 2:
|
| 128 |
+
normalizer = np.take( np.take( normalizer, lats_idx, -2), lons_idx, -1)
|
| 129 |
+
else :
|
| 130 |
+
assert False, 'invalid argument type (has to be index to cf.fields or field name)'
|
| 131 |
+
|
| 132 |
+
year_base = self.dataset_train.year_base
|
| 133 |
+
|
| 134 |
+
return normalizer, year_base
|
| 135 |
+
|
| 136 |
+
###################################################
|
| 137 |
+
def mode( self, mode : NetMode) :
|
| 138 |
+
|
| 139 |
+
if mode == NetMode.train :
|
| 140 |
+
self.data_loader_iter = iter(self.data_loader_train)
|
| 141 |
+
self.net.train()
|
| 142 |
+
elif mode == NetMode.test :
|
| 143 |
+
self.data_loader_iter = iter(self.data_loader_test)
|
| 144 |
+
self.net.eval()
|
| 145 |
+
else :
|
| 146 |
+
assert False
|
| 147 |
+
|
| 148 |
+
self.cur_mode = mode
|
| 149 |
+
|
| 150 |
+
###################################################
|
| 151 |
+
def len( self, mode : NetMode) :
|
| 152 |
+
if mode == NetMode.train :
|
| 153 |
+
return len(self.data_loader_train)
|
| 154 |
+
elif mode == NetMode.test :
|
| 155 |
+
return len(self.data_loader_test)
|
| 156 |
+
else :
|
| 157 |
+
assert False
|
| 158 |
+
|
| 159 |
+
###################################################
|
| 160 |
+
def next( self) :
|
| 161 |
+
return next(self.data_loader_iter)
|
| 162 |
+
|
| 163 |
+
###################################################
|
| 164 |
+
def forward( self, xin) :
|
| 165 |
+
pred = self.net.forward( xin)
|
| 166 |
+
return pred
|
| 167 |
+
|
| 168 |
+
###################################################
|
| 169 |
+
def get_attention( self, xin) :
|
| 170 |
+
attn = self.net.get_attention( xin)
|
| 171 |
+
return attn
|
| 172 |
+
|
| 173 |
+
###################################################
|
| 174 |
+
def create( self, pre_batch, devices, create_net = True, pre_batch_targets = None,
|
| 175 |
+
load_pretrained=True) :
|
| 176 |
+
|
| 177 |
+
if create_net :
|
| 178 |
+
self.net.create( devices, load_pretrained)
|
| 179 |
+
|
| 180 |
+
self.pre_batch = pre_batch
|
| 181 |
+
self.pre_batch_targets = pre_batch_targets
|
| 182 |
+
|
| 183 |
+
cf = self.net.cf
|
| 184 |
+
loader_params = { 'batch_size': None, 'batch_sampler': None, 'shuffle': False,
|
| 185 |
+
'num_workers': cf.num_loader_workers, 'pin_memory': True}
|
| 186 |
+
|
| 187 |
+
self.dataset_train = MultifieldDataSampler( cf.file_path, cf.fields, cf.years_train,
|
| 188 |
+
cf.batch_size,
|
| 189 |
+
pre_batch, cf.n_size, cf.num_samples_per_epoch,
|
| 190 |
+
with_shuffle = (cf.BERT_strategy != 'global_forecast'),
|
| 191 |
+
with_source_idxs = True,
|
| 192 |
+
compute_weights = (cf.losses.count('weighted_mse') > 0) )
|
| 193 |
+
self.data_loader_train = torch.utils.data.DataLoader( self.dataset_train, **loader_params,
|
| 194 |
+
sampler = None)
|
| 195 |
+
|
| 196 |
+
self.dataset_test = MultifieldDataSampler( cf.file_path, cf.fields, cf.years_val,
|
| 197 |
+
cf.batch_size_validation,
|
| 198 |
+
pre_batch, cf.n_size, cf.num_samples_validate,
|
| 199 |
+
with_shuffle = (cf.BERT_strategy != 'global_forecast'),
|
| 200 |
+
with_source_idxs = True,
|
| 201 |
+
compute_weights = (cf.losses.count('weighted_mse') > 0) )
|
| 202 |
+
self.data_loader_test = torch.utils.data.DataLoader( self.dataset_test, **loader_params,
|
| 203 |
+
sampler = None)
|
| 204 |
+
|
| 205 |
+
return self
|
| 206 |
+
|
| 207 |
+
####################################################################################################
|
| 208 |
+
class AtmoRep( torch.nn.Module) :
|
| 209 |
+
|
| 210 |
+
def __init__(self, cf) :
|
| 211 |
+
'''Constructor'''
|
| 212 |
+
|
| 213 |
+
super( AtmoRep, self).__init__()
|
| 214 |
+
|
| 215 |
+
self.cf = cf
|
| 216 |
+
|
| 217 |
+
###################################################
|
| 218 |
+
def create( self, devices, load_pretrained=True) :
|
| 219 |
+
'''Create network'''
|
| 220 |
+
|
| 221 |
+
cf = self.cf
|
| 222 |
+
self.devices = devices
|
| 223 |
+
self.fields_coupling_idx = []
|
| 224 |
+
|
| 225 |
+
self.fields_index = {}
|
| 226 |
+
for ifield, field_info in enumerate(cf.fields) :
|
| 227 |
+
self.fields_index[ field_info[0] ] = ifield
|
| 228 |
+
|
| 229 |
+
# # embedding network for global/auxiliary token infos
|
| 230 |
+
# TODO: only for backward compatibility, remove
|
| 231 |
+
self.embed_token_info = torch.nn.Linear( cf.size_token_info, cf.size_token_info_net)
|
| 232 |
+
torch.nn.init.constant_( self.embed_token_info.weight, 0.0)
|
| 233 |
+
|
| 234 |
+
self.embeds_token_info = torch.nn.ModuleList()
|
| 235 |
+
for ifield, field_info in enumerate( cf.fields) :
|
| 236 |
+
|
| 237 |
+
self.embeds_token_info.append( torch.nn.Linear( cf.size_token_info, cf.size_token_info_net))
|
| 238 |
+
|
| 239 |
+
if len(field_info[1]) > 4 and load_pretrained :
|
| 240 |
+
# TODO: inconsistent with embeds_token_info -> version that can handle both
|
| 241 |
+
# we could imply use the file name: embed_token_info vs embeds_token_info
|
| 242 |
+
name = 'AtmoRep' + '_embeds_token_info'
|
| 243 |
+
if not os.path.exists(get_model_filename( name, field_info[1][4][0], field_info[1][4][1])):
|
| 244 |
+
name = 'AtmoRep' + '_embed_token_info'
|
| 245 |
+
|
| 246 |
+
mloaded = torch.load( get_model_filename( name, field_info[1][4][0], field_info[1][4][1]))
|
| 247 |
+
|
| 248 |
+
if "weight" not in mloaded.keys(): #TODO: get rid of this
|
| 249 |
+
mloaded["weight"] = mloaded["0.weight"]
|
| 250 |
+
mloaded["bias"] = mloaded["0.bias"]
|
| 251 |
+
del mloaded["0.weight"]
|
| 252 |
+
del mloaded["0.bias"]
|
| 253 |
+
|
| 254 |
+
self.embeds_token_info[-1].load_state_dict( mloaded)
|
| 255 |
+
print( 'Loaded embed_token_info from id = {}.'.format( field_info[1][4][0] ) )
|
| 256 |
+
else :
|
| 257 |
+
# initalization
|
| 258 |
+
torch.nn.init.constant_( self.embeds_token_info[-1].weight, 0.0)
|
| 259 |
+
self.embeds_token_info[-1].bias.data.fill_(0.0)
|
| 260 |
+
|
| 261 |
+
# embedding and encoder
|
| 262 |
+
|
| 263 |
+
self.embeds = torch.nn.ModuleList()
|
| 264 |
+
self.encoders = torch.nn.ModuleList()
|
| 265 |
+
|
| 266 |
+
for field_idx, field_info in enumerate(cf.fields) :
|
| 267 |
+
|
| 268 |
+
# encoder
|
| 269 |
+
self.encoders.append( TransformerEncoder( cf, field_idx, True).create())
|
| 270 |
+
# load pre-trained model if specified
|
| 271 |
+
if len(field_info[1]) > 4 and load_pretrained :
|
| 272 |
+
self.load_block( field_info, 'encoder', self.encoders[-1])
|
| 273 |
+
self.embeds.append( self.encoders[-1].embed)
|
| 274 |
+
|
| 275 |
+
# indices of coupled fields for efficient access in forward
|
| 276 |
+
self.fields_coupling_idx.append( [field_idx])
|
| 277 |
+
for field_coupled in field_info[1][2] :
|
| 278 |
+
if 'axial' in cf.encoder_att_type :
|
| 279 |
+
self.fields_coupling_idx[field_idx].append( self.fields_index[field_coupled] )
|
| 280 |
+
else :
|
| 281 |
+
for _ in range(cf.coupling_num_heads_per_field) :
|
| 282 |
+
self.fields_coupling_idx[field_idx].append( self.fields_index[field_coupled] )
|
| 283 |
+
|
| 284 |
+
# decoder
|
| 285 |
+
|
| 286 |
+
self.decoders = torch.nn.ModuleList()
|
| 287 |
+
self.field_pred_idxs = []
|
| 288 |
+
for field in cf.fields_prediction :
|
| 289 |
+
|
| 290 |
+
for ifield, field_info in enumerate(cf.fields) :
|
| 291 |
+
if field_info[0] == field[0] :
|
| 292 |
+
self.field_pred_idxs.append( ifield)
|
| 293 |
+
break
|
| 294 |
+
|
| 295 |
+
self.decoders.append( TransformerDecoder( cf, field_info ) )
|
| 296 |
+
# load pre-trained model if specified
|
| 297 |
+
if len(field_info[1]) > 4 and load_pretrained :
|
| 298 |
+
self.load_block( field_info, 'decoder', self.decoders[-1])
|
| 299 |
+
|
| 300 |
+
# tail networks
|
| 301 |
+
|
| 302 |
+
self.tails = torch.nn.ModuleList()
|
| 303 |
+
for ifield, field in enumerate(cf.fields_prediction) :
|
| 304 |
+
|
| 305 |
+
field_idx = self.field_pred_idxs[ifield]
|
| 306 |
+
field_info = cf.fields[field_idx]
|
| 307 |
+
self.tails.append( TailEnsemble( cf, field_info[1][1], np.prod(field_info[4]) ).create())
|
| 308 |
+
# load pre-trained model if specified
|
| 309 |
+
if len(field_info[1]) > 4 and load_pretrained:
|
| 310 |
+
self.load_block( field_info, 'tail', self.tails[-1])
|
| 311 |
+
|
| 312 |
+
# set devices
|
| 313 |
+
|
| 314 |
+
for field_idx, field_info in enumerate(cf.fields) :
|
| 315 |
+
# find determined device, use default if nothing specified
|
| 316 |
+
device = self.devices[0]
|
| 317 |
+
if len(field_info[1]) > 3 :
|
| 318 |
+
assert field_info[1][3] < 4, 'Only single node model parallelism supported'
|
| 319 |
+
print(devices, field_info[1][3])
|
| 320 |
+
assert field_info[1][3] < len(devices), 'Per field device id larger than max devices'
|
| 321 |
+
device = self.devices[ field_info[1][3] ]
|
| 322 |
+
# set device
|
| 323 |
+
self.embeds[field_idx].to(device)
|
| 324 |
+
self.encoders[field_idx].to(device)
|
| 325 |
+
|
| 326 |
+
for field_idx, field in enumerate(cf.fields_prediction) :
|
| 327 |
+
field_info = cf.fields[ self.field_pred_idxs[field_idx] ]
|
| 328 |
+
device = self.devices[0]
|
| 329 |
+
if len(field_info[1]) > 3 :
|
| 330 |
+
device = self.devices[ field_info[1][3] ]
|
| 331 |
+
self.decoders[field_idx].to(device)
|
| 332 |
+
self.tails[field_idx].to(device)
|
| 333 |
+
|
| 334 |
+
# embed_token_info on device[0] since it is shared by all fields, potentially sub-optimal
|
| 335 |
+
self.embed_token_info.to(devices[0]) # TODO: only for backward compatibility, remove
|
| 336 |
+
self.embeds_token_info.to(devices[0])
|
| 337 |
+
|
| 338 |
+
self.checkpoint = identity
|
| 339 |
+
if cf.grad_checkpointing :
|
| 340 |
+
self.checkpoint = checkpoint_wrapper
|
| 341 |
+
|
| 342 |
+
return self
|
| 343 |
+
|
| 344 |
+
###################################################
|
| 345 |
+
def load_block( self, field_info, block_name, block ) :
|
| 346 |
+
|
| 347 |
+
# name = self.__class__.__name__ + '_' + block_name + '_' + field_info[0]
|
| 348 |
+
name = 'AtmoRep_' + block_name + '_' + field_info[0]
|
| 349 |
+
|
| 350 |
+
b_loaded = torch.load( get_model_filename(name, field_info[1][4][0], field_info[1][4][1]))
|
| 351 |
+
|
| 352 |
+
# in coupling mode, proj_out of attention heads needs separate treatment: only the pre-trained
|
| 353 |
+
# part can be loaded
|
| 354 |
+
keys_del = []
|
| 355 |
+
for name, param in block.named_parameters():
|
| 356 |
+
if 'proj_out' in name :
|
| 357 |
+
for k in b_loaded.keys() :
|
| 358 |
+
if name == k :
|
| 359 |
+
if param.shape[0] != param.shape[1] : # non-square proj_out indicate deviation from pre-training
|
| 360 |
+
with torch.no_grad() :
|
| 361 |
+
# load pre-trained part
|
| 362 |
+
param[ : , : b_loaded[k].shape[1] ] = b_loaded[k]
|
| 363 |
+
# initalize remaining part to small random value
|
| 364 |
+
param[ : , b_loaded[k].shape[1] : ] = 0.01 * torch.rand( param.shape[0],
|
| 365 |
+
param.shape[1] - b_loaded[k].shape[1])
|
| 366 |
+
keys_del += [ k ]
|
| 367 |
+
|
| 368 |
+
#for backward compatibility. solved in new runs
|
| 369 |
+
if 'proj_heads_other' in name:
|
| 370 |
+
for k in b_loaded.keys() :
|
| 371 |
+
if name == k :
|
| 372 |
+
if b_loaded[name].shape[0] == 0:
|
| 373 |
+
keys_del += [ name ]
|
| 374 |
+
|
| 375 |
+
for k in keys_del :
|
| 376 |
+
del b_loaded[k]
|
| 377 |
+
|
| 378 |
+
# use strict=False so that differing blocks, e.g. through coupling, are ignored
|
| 379 |
+
mkeys, _ = block.load_state_dict( b_loaded, False)
|
| 380 |
+
|
| 381 |
+
# missing keys = keys that are not pre-trained are initalized to small value
|
| 382 |
+
[mkeys.remove(k) for k in keys_del] # remove proj_out keys so that they are not over-written
|
| 383 |
+
[utils.init_weights_uniform( block.state_dict()[k], 0.01) for k in mkeys]
|
| 384 |
+
|
| 385 |
+
print( 'Loaded {} for {} from id = {} (ignoring/missing {} elements).'.format( block_name,
|
| 386 |
+
field_info[0], field_info[1][4][0], len(mkeys) ) )
|
| 387 |
+
|
| 388 |
+
###################################################
|
| 389 |
+
def translate_weights(self, mloaded, mkeys, ukeys):
|
| 390 |
+
'''
|
| 391 |
+
Function used for backward compatibility
|
| 392 |
+
'''
|
| 393 |
+
cf = self.cf
|
| 394 |
+
|
| 395 |
+
#encoder:
|
| 396 |
+
for layer in range(cf.encoder_num_layers) :
|
| 397 |
+
|
| 398 |
+
#shape([16, 3, 128, 2048])
|
| 399 |
+
mw = torch.cat([mloaded[f'encoders.0.heads.{layer}.heads_self.{head}.proj_{k}.weight'] for head in range(cf.encoder_num_heads) for k in ["qs", "ks", "vs"]])
|
| 400 |
+
mloaded[f'encoders.0.heads.{layer}.proj_heads.weight'] = mw
|
| 401 |
+
|
| 402 |
+
for head in range(cf.encoder_num_heads):
|
| 403 |
+
del mloaded[f'encoders.0.heads.{layer}.heads_self.{head}.proj_qs.weight']
|
| 404 |
+
del mloaded[f'encoders.0.heads.{layer}.heads_self.{head}.proj_ks.weight']
|
| 405 |
+
del mloaded[f'encoders.0.heads.{layer}.heads_self.{head}.proj_vs.weight']
|
| 406 |
+
|
| 407 |
+
#cross attention
|
| 408 |
+
if f'encoders.0.heads.{layer}.heads_other.0.proj_qs.weight' in ukeys:
|
| 409 |
+
mw = torch.cat([mloaded[f'encoders.0.heads.{layer}.heads_other.{head}.proj_{k}.weight'] for head in range(cf.encoder_num_heads) for k in ["qs", "ks", "vs"]])
|
| 410 |
+
|
| 411 |
+
for i in range(cf.encoder_num_heads):
|
| 412 |
+
del mloaded[f'encoders.0.heads.{layer}.heads_other.{head}.proj_qs.weight']
|
| 413 |
+
del mloaded[f'encoders.0.heads.{layer}.heads_other.{head}.proj_ks.weight']
|
| 414 |
+
del mloaded[f'encoders.0.heads.{layer}.heads_other.{head}.proj_vs.weight']
|
| 415 |
+
|
| 416 |
+
mloaded[f'encoders.0.heads.{layer}.proj_heads_other.0.weight'] = mw
|
| 417 |
+
|
| 418 |
+
#decoder
|
| 419 |
+
for iblock in range(0, 19, 2) :
|
| 420 |
+
mw = torch.cat([mloaded[f'decoders.0.blocks.{iblock}.heads.{head}.proj_{k}.weight'] for head in range(8) for k in ["qs", "ks", "vs"]])
|
| 421 |
+
mloaded[f'decoders.0.blocks.{iblock}.proj_heads.weight'] = mw
|
| 422 |
+
|
| 423 |
+
qs = [mloaded[f'decoders.0.blocks.{iblock}.heads_other.{head}.proj_qs.weight'] for head in range(8)]
|
| 424 |
+
mw = torch.cat([mloaded[f'decoders.0.blocks.{iblock}.heads_other.{head}.proj_{k}.weight'] for head in range(8) for k in ["ks", "vs"]])
|
| 425 |
+
|
| 426 |
+
mloaded[f'decoders.0.blocks.{iblock}.proj_heads_o_q.weight'] = torch.cat([*qs])
|
| 427 |
+
mloaded[f'decoders.0.blocks.{iblock}.proj_heads_o_kv.weight'] = mw
|
| 428 |
+
|
| 429 |
+
#self.num_samples_validate
|
| 430 |
+
decoder_dim = self.decoders[0].blocks[iblock].ln_q.weight.shape #128
|
| 431 |
+
mloaded[f'decoders.0.blocks.{iblock}.ln_q.weight'] = torch.tensor(np.ones(decoder_dim))
|
| 432 |
+
mloaded[f'decoders.0.blocks.{iblock}.ln_k.weight'] = torch.tensor(np.ones(decoder_dim))
|
| 433 |
+
mloaded[f'decoders.0.blocks.{iblock}.ln_q.bias'] = torch.tensor(np.ones(decoder_dim))
|
| 434 |
+
mloaded[f'decoders.0.blocks.{iblock}.ln_k.bias'] = torch.tensor(np.ones(decoder_dim))
|
| 435 |
+
|
| 436 |
+
for i in range(8):
|
| 437 |
+
del mloaded[f'decoders.0.blocks.{iblock}.heads.{i}.proj_qs.weight']
|
| 438 |
+
del mloaded[f'decoders.0.blocks.{iblock}.heads.{i}.proj_ks.weight']
|
| 439 |
+
del mloaded[f'decoders.0.blocks.{iblock}.heads.{i}.proj_vs.weight']
|
| 440 |
+
del mloaded[f'decoders.0.blocks.{iblock}.heads_other.{i}.proj_qs.weight']
|
| 441 |
+
del mloaded[f'decoders.0.blocks.{iblock}.heads_other.{i}.proj_ks.weight']
|
| 442 |
+
del mloaded[f'decoders.0.blocks.{iblock}.heads_other.{i}.proj_vs.weight']
|
| 443 |
+
|
| 444 |
+
return mloaded
|
| 445 |
+
|
| 446 |
+
###################################################
|
| 447 |
+
@staticmethod
|
| 448 |
+
def load( model_id, devices, cf = None, epoch = -2, load_pretrained=False) :
|
| 449 |
+
'''Load network from checkpoint'''
|
| 450 |
+
|
| 451 |
+
if not cf :
|
| 452 |
+
cf = utils.Config()
|
| 453 |
+
cf.load_json( model_id)
|
| 454 |
+
|
| 455 |
+
model = AtmoRep( cf).create( devices, load_pretrained=False)
|
| 456 |
+
mloaded = torch.load( utils.get_model_filename( model, model_id, epoch) )
|
| 457 |
+
mkeys, ukeys = model.load_state_dict( mloaded, False )
|
| 458 |
+
if (f'encoders.0.heads.0.proj_heads.weight') in mkeys:
|
| 459 |
+
mloaded = model.translate_weights(mloaded, mkeys, ukeys)
|
| 460 |
+
mkeys, ukeys = model.load_state_dict( mloaded, False )
|
| 461 |
+
|
| 462 |
+
if len(mkeys) > 0 :
|
| 463 |
+
print( f'Loaded AtmoRep: ignoring {len(mkeys)} elements: {mkeys}')
|
| 464 |
+
|
| 465 |
+
# TODO: remove, only for backward
|
| 466 |
+
if model.embeds_token_info[0].weight.abs().max() == 0. :
|
| 467 |
+
model.embeds_token_info = torch.nn.ModuleList()
|
| 468 |
+
|
| 469 |
+
return model
|
| 470 |
+
|
| 471 |
+
###################################################
|
| 472 |
+
def save( self, epoch = -2) :
|
| 473 |
+
'''Save network '''
|
| 474 |
+
|
| 475 |
+
# save entire network
|
| 476 |
+
torch.save( self.state_dict(), utils.get_model_filename( self, self.cf.wandb_id, epoch) )
|
| 477 |
+
|
| 478 |
+
# save parts also separately
|
| 479 |
+
|
| 480 |
+
# name = self.__class__.__name__ + '_embed_token_info'
|
| 481 |
+
# torch.save( self.embed_token_info.state_dict(),
|
| 482 |
+
# utils.get_model_filename( name, self.cf.wandb_id, epoch) )
|
| 483 |
+
name = self.__class__.__name__ + '_embeds_token_info'
|
| 484 |
+
torch.save( self.embeds_token_info.state_dict(),
|
| 485 |
+
utils.get_model_filename( name, self.cf.wandb_id, epoch) )
|
| 486 |
+
|
| 487 |
+
for ifield, enc in enumerate(self.encoders) :
|
| 488 |
+
name = self.__class__.__name__ + '_encoder_' + self.cf.fields[ifield][0]
|
| 489 |
+
torch.save( enc.state_dict(), utils.get_model_filename( name, self.cf.wandb_id, epoch) )
|
| 490 |
+
|
| 491 |
+
for ifield, dec in enumerate(self.decoders) :
|
| 492 |
+
name = self.__class__.__name__ + '_decoder_' + self.cf.fields_prediction[ifield][0]
|
| 493 |
+
torch.save( dec.state_dict(), utils.get_model_filename( name, self.cf.wandb_id, epoch) )
|
| 494 |
+
|
| 495 |
+
for ifield, tail in enumerate(self.tails) :
|
| 496 |
+
name = self.__class__.__name__ + '_tail_' + self.cf.fields_prediction[ifield][0]
|
| 497 |
+
torch.save( tail.state_dict(), utils.get_model_filename( name, self.cf.wandb_id, epoch) )
|
| 498 |
+
|
| 499 |
+
###################################################
|
| 500 |
+
def forward( self, xin) :
|
| 501 |
+
'''Evaluate network'''
|
| 502 |
+
|
| 503 |
+
# embedding
|
| 504 |
+
cf = self.cf
|
| 505 |
+
|
| 506 |
+
fields_embed = self.get_fields_embed(xin)
|
| 507 |
+
|
| 508 |
+
# attention maps (if requested)
|
| 509 |
+
atts = [ [] for _ in cf.fields ]
|
| 510 |
+
|
| 511 |
+
# encoder
|
| 512 |
+
embeds_layers = [[] for i in self.field_pred_idxs]
|
| 513 |
+
for ib in range(self.cf.encoder_num_layers) :
|
| 514 |
+
fields_embed, att = self.forward_encoder_block( ib, fields_embed)
|
| 515 |
+
[embeds_layers[idx].append( fields_embed[i]) for idx,i in enumerate(self.field_pred_idxs)]
|
| 516 |
+
[atts[i].append( att[i]) for i,_ in enumerate(cf.fields) ]
|
| 517 |
+
|
| 518 |
+
# encoder-decoder coupling / token transformations
|
| 519 |
+
(decoders_in, embeds_layers) = self.encoder_to_decoder( embeds_layers)
|
| 520 |
+
|
| 521 |
+
preds = []
|
| 522 |
+
for idx,i in enumerate(self.field_pred_idxs) :
|
| 523 |
+
|
| 524 |
+
# decoder
|
| 525 |
+
token_seq_embed, att = self.decoders[idx]( (decoders_in[idx], embeds_layers[idx]) )
|
| 526 |
+
|
| 527 |
+
# tail net
|
| 528 |
+
tail_in = self.decoder_to_tail( idx, token_seq_embed)
|
| 529 |
+
pred = self.checkpoint( self.tails[idx], tail_in)
|
| 530 |
+
|
| 531 |
+
preds.append( pred)
|
| 532 |
+
[atts[i].append( a) for a in att]
|
| 533 |
+
|
| 534 |
+
return preds, atts
|
| 535 |
+
|
| 536 |
+
###################################################
|
| 537 |
+
def forward_encoder_block( self, iblock, fields_embed) :
|
| 538 |
+
''' evaluate one block (attention and mlp) '''
|
| 539 |
+
|
| 540 |
+
# double buffer for commutation-invariant result (w.r.t evaluation order of transformers)
|
| 541 |
+
fields_embed_cur, atts = [], []
|
| 542 |
+
|
| 543 |
+
# attention heads
|
| 544 |
+
for ifield in range( len(fields_embed)) :
|
| 545 |
+
d = fields_embed[ifield].device
|
| 546 |
+
fields_in =[fields_embed[i].to(d,non_blocking=True) for i in self.fields_coupling_idx[ifield]]
|
| 547 |
+
# unpack list in argument for checkpointing
|
| 548 |
+
y, att = self.checkpoint( self.encoders[ifield].heads[iblock], *fields_in)
|
| 549 |
+
fields_embed_cur.append( y)
|
| 550 |
+
atts.append( att)
|
| 551 |
+
|
| 552 |
+
# MLPs
|
| 553 |
+
for ifield in range( len(fields_embed)) :
|
| 554 |
+
fields_embed_cur[ifield] = self.checkpoint( self.encoders[ifield].mlps[iblock],
|
| 555 |
+
fields_embed_cur[ifield] )
|
| 556 |
+
|
| 557 |
+
return fields_embed_cur, atts
|
| 558 |
+
|
| 559 |
+
###################################################
|
| 560 |
+
def get_fields_embed( self, xin ) :
|
| 561 |
+
if 0 == len(self.embeds_token_info) : # TODO: only for backward compatibility, remove
|
| 562 |
+
emb_net_ti = self.embed_token_info
|
| 563 |
+
return [prepare_token( field_data, emb_net, emb_net_ti )
|
| 564 |
+
for fidx,(field_data,emb_net) in enumerate(zip( xin, self.embeds))]
|
| 565 |
+
else :
|
| 566 |
+
embs_net_ti = self.embeds_token_info
|
| 567 |
+
return [prepare_token( field_data, emb_net, embs_net_ti[fidx] )
|
| 568 |
+
for fidx,(field_data,emb_net) in enumerate(zip( xin, self.embeds))]
|
| 569 |
+
|
| 570 |
+
###################################################
|
| 571 |
+
|
| 572 |
+
def get_attention( self, xin) :
|
| 573 |
+
|
| 574 |
+
cf = self.cf
|
| 575 |
+
attn = []
|
| 576 |
+
fields_embed = self.get_fields_embed(xin)
|
| 577 |
+
#either accumulated attention or last layer attention:
|
| 578 |
+
blocks = list(range(self.cf.encoder_num_layers)) if cf.attention_mode == 'accum' else [self.cf.encoder_num_layers-1]
|
| 579 |
+
for idx, ifield in enumerate(self.field_pred_idxs) :
|
| 580 |
+
d = fields_embed[ifield].device
|
| 581 |
+
fields_in =[fields_embed[i].to(d,non_blocking=True) for i in self.fields_coupling_idx[ifield]]
|
| 582 |
+
attn_field = self.encoders[ifield].heads[blocks[0]].get_attention(fields_in)
|
| 583 |
+
if cf.attention_mode == 'accum':
|
| 584 |
+
for iblock in blocks[1:]:
|
| 585 |
+
attn_layer = self.encoders[ifield].heads[iblock].get_attention(fields_in)
|
| 586 |
+
attn_field = attn_field + attn_layer
|
| 587 |
+
attn_field = torch.sum(attn_field, dim = 0, keepdim=True)
|
| 588 |
+
attn.append(attn_field)
|
| 589 |
+
# print("att FINAL", ifield, len(attn), attn[0].shape)
|
| 590 |
+
return attn
|
vendor/atmorep-official/atmorep/core/evaluate.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
from atmorep.core.evaluator import Evaluator
|
| 18 |
+
import time
|
| 19 |
+
|
| 20 |
+
if __name__ == '__main__':
|
| 21 |
+
|
| 22 |
+
# arXiv 2023: models for individual fields
|
| 23 |
+
#model_id = '4nvwbetz' # vorticity
|
| 24 |
+
#model_id = 'oxpycr7w' # divergence
|
| 25 |
+
#model_id = '1565pb1f' # specific_humidity
|
| 26 |
+
#model_id = '3kdutwqb' # total precip
|
| 27 |
+
#model_id = 'dys79lgw' # velocity_u
|
| 28 |
+
#model_id = '22j6gysw' # velocity_v
|
| 29 |
+
#model_id = '15oisw8d' # velocity_z
|
| 30 |
+
#model_id = '3qou60es' # temperature
|
| 31 |
+
#model_id = '2147fkco' # temperature (also 2147fkco)
|
| 32 |
+
|
| 33 |
+
# new runs 2024
|
| 34 |
+
#model_id='j8dwr5qj' #velocity_u
|
| 35 |
+
#model_id='0tlnm5up' #velocity_v
|
| 36 |
+
#model_id='v63l01zu' #specific humidity
|
| 37 |
+
#model_id='9l1errbo' #velocity_z
|
| 38 |
+
model_id='7ojls62c' #temperature 1024
|
| 39 |
+
|
| 40 |
+
# supported modes: test, forecast, fixed_location, temporal_interpolation, global_forecast,
|
| 41 |
+
# global_forecast_range
|
| 42 |
+
# options can be used to over-write parameters in config; some modes also have specific options,
|
| 43 |
+
# e.g. global_forecast where a start date can be specified
|
| 44 |
+
|
| 45 |
+
#Add 'attention' : True to options to store the attention maps. NB. supported only for single field runs.
|
| 46 |
+
|
| 47 |
+
# BERT masked token model
|
| 48 |
+
#mode, options = 'BERT', {'years_val' : [2021], 'num_samples_validate' : 128, 'with_pytest' : True}
|
| 49 |
+
|
| 50 |
+
# BERT forecast mode
|
| 51 |
+
#mode, options = 'forecast', {'forecast_num_tokens' : 2, 'num_samples_validate' : 128, 'with_pytest' : True }
|
| 52 |
+
|
| 53 |
+
#temporal interpolation
|
| 54 |
+
#idx_time_mask: list of relative time positions of the masked tokens within the cube wrt num_tokens[0]
|
| 55 |
+
#mode, options = 'temporal_interpolation', {'idx_time_mask': [5,6,7], 'num_samples_validate' : 128, 'with_pytest' : True}
|
| 56 |
+
|
| 57 |
+
# BERT forecast with patching to obtain global forecast
|
| 58 |
+
mode, options = 'global_forecast', {
|
| 59 |
+
#'dates' : [[2021, 2, 10, 12]]
|
| 60 |
+
'dates' : [
|
| 61 |
+
[2021, 1, 10, 12] , [2021, 1, 11, 0], [2021, 1, 11, 12], [2021, 1, 12, 0], #[2021, 1, 12, 12], [2021, 1, 13, 0],
|
| 62 |
+
[2021, 4, 10, 12], [2021, 4, 11, 0], [2021, 4, 11, 12], [2021, 4, 12, 0], #[2021, 4, 12, 12], [2021, 4, 13, 0],
|
| 63 |
+
[2021, 7, 10, 12], [2021, 7, 11, 0], [2021, 7, 11, 12], [2021, 7, 12, 0], #[2021, 7, 12, 12], [2021, 7, 13, 0],
|
| 64 |
+
[2021, 10, 10, 12], [2021, 10, 11, 0], [2021, 10, 11, 12], #[2021, 10, 12, 0], [2021, 10, 12, 12], [2021, 10, 13, 0]
|
| 65 |
+
],
|
| 66 |
+
'token_overlap' : [0, 0],
|
| 67 |
+
'forecast_num_tokens' : 2,
|
| 68 |
+
'with_pytest' : True }
|
| 69 |
+
|
| 70 |
+
file_path = '/gpfs/scratch/ehpc03/era5_y1979_2021_res025_chunk8.zarr'
|
| 71 |
+
|
| 72 |
+
now = time.time()
|
| 73 |
+
Evaluator.evaluate( mode, model_id, file_path, options)
|
| 74 |
+
print("time", time.time() - now)
|
vendor/atmorep-official/atmorep/core/evaluator.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
import os
|
| 19 |
+
import code
|
| 20 |
+
import pytest
|
| 21 |
+
import datetime
|
| 22 |
+
|
| 23 |
+
import wandb
|
| 24 |
+
|
| 25 |
+
from atmorep.core.trainer import Trainer_BERT
|
| 26 |
+
from atmorep.utils.utils import Config
|
| 27 |
+
from atmorep.utils.utils import setup_ddp
|
| 28 |
+
from atmorep.utils.utils import setup_wandb
|
| 29 |
+
from atmorep.utils.utils import init_torch
|
| 30 |
+
from atmorep.utils.utils import NetMode
|
| 31 |
+
import atmorep.utils.utils as utils
|
| 32 |
+
|
| 33 |
+
import atmorep.config.config as config
|
| 34 |
+
|
| 35 |
+
class Evaluator( Trainer_BERT) :
|
| 36 |
+
|
| 37 |
+
##############################################
|
| 38 |
+
def __init__( self, cf, devices) :
|
| 39 |
+
Trainer_BERT.__init__( self, cf, devices)
|
| 40 |
+
|
| 41 |
+
##############################################
|
| 42 |
+
def parse_args( cf, args) :
|
| 43 |
+
|
| 44 |
+
# set/over-write options as desired
|
| 45 |
+
for (key,val) in args.items() :
|
| 46 |
+
if '[' in key : # handle lists, e.g. fields[0][2]
|
| 47 |
+
key_split = key.split( '[')
|
| 48 |
+
k, v = key_split[0], key_split[1:]
|
| 49 |
+
v = [int(a[0]) for a in v]
|
| 50 |
+
utils.list_replace_rec( getattr( cf, k), v, val)
|
| 51 |
+
else :
|
| 52 |
+
setattr( cf, key, val)
|
| 53 |
+
|
| 54 |
+
##############################################
|
| 55 |
+
@staticmethod
|
| 56 |
+
def run( cf, model_id, model_epoch, devices) :
|
| 57 |
+
|
| 58 |
+
cf.with_mixed_precision = True
|
| 59 |
+
|
| 60 |
+
# set/over-write options as desired
|
| 61 |
+
evaluator = Evaluator.load( cf, model_id, model_epoch, devices)
|
| 62 |
+
|
| 63 |
+
if 0 == cf.par_rank :
|
| 64 |
+
cf.print()
|
| 65 |
+
cf.write_json( wandb)
|
| 66 |
+
evaluator.validate( 0, cf.BERT_strategy)
|
| 67 |
+
|
| 68 |
+
##############################################
|
| 69 |
+
@staticmethod
|
| 70 |
+
def evaluate( mode, model_id, file_path, args = {}, model_epoch=-2) :
|
| 71 |
+
|
| 72 |
+
devices = init_torch()
|
| 73 |
+
with_ddp = True
|
| 74 |
+
par_rank, par_size = setup_ddp( with_ddp)
|
| 75 |
+
|
| 76 |
+
cf = Config().load_json( model_id)
|
| 77 |
+
|
| 78 |
+
cf.num_accs_per_task = len(devices)
|
| 79 |
+
cf.file_path = file_path
|
| 80 |
+
cf.with_wandb = True
|
| 81 |
+
cf.with_ddp = with_ddp
|
| 82 |
+
cf.par_rank = par_rank
|
| 83 |
+
cf.par_size = par_size
|
| 84 |
+
cf.losses = cf.losses
|
| 85 |
+
# overwrite old config
|
| 86 |
+
cf.attention = False
|
| 87 |
+
setup_wandb( cf.with_wandb, cf, par_rank, '', mode='offline')
|
| 88 |
+
if 0 == cf.par_rank :
|
| 89 |
+
print( 'Running Evaluate.evaluate with mode =', mode)
|
| 90 |
+
|
| 91 |
+
# if not hasattr( cf, 'num_loader_workers'):
|
| 92 |
+
cf.num_loader_workers = 12 #cf.loader_num_workers
|
| 93 |
+
cf.rng_seed = None
|
| 94 |
+
|
| 95 |
+
#backward compatibility
|
| 96 |
+
if not hasattr( cf, 'n_size'):
|
| 97 |
+
cf.n_size = [36, 0.25*9*6, 0.25*9*12]
|
| 98 |
+
#cf.n_size = [36, 0.25*27*2, 0.25*27*4]
|
| 99 |
+
if not hasattr(cf, 'num_samples_per_epoch'):
|
| 100 |
+
cf.num_samples_per_epoch = 1024
|
| 101 |
+
if not hasattr(cf, 'with_mixed_precision'):
|
| 102 |
+
cf.with_mixed_precision = False
|
| 103 |
+
if not hasattr(cf, 'with_pytest'):
|
| 104 |
+
cf.with_pytest = False
|
| 105 |
+
if not hasattr(cf, 'batch_size'):
|
| 106 |
+
cf.batch_size = cf.batch_size_max
|
| 107 |
+
if not hasattr(cf, 'batch_size_validation'):
|
| 108 |
+
cf.batch_size_validation = cf.batch_size_max
|
| 109 |
+
if not hasattr(cf, 'years_val'):
|
| 110 |
+
cf.years_val = cf.years_test
|
| 111 |
+
|
| 112 |
+
func = getattr( Evaluator, mode)
|
| 113 |
+
func( cf, model_id, model_epoch, devices, args)
|
| 114 |
+
|
| 115 |
+
if cf.with_pytest:
|
| 116 |
+
fields = [field[0] for field in cf.fields_prediction]
|
| 117 |
+
for field in fields:
|
| 118 |
+
pytest.main(["-x", "-s", "./atmorep/tests/validation_test.py", "--field", field, "--model_id", cf.wandb_id, "--strategy", cf.BERT_strategy])
|
| 119 |
+
|
| 120 |
+
##############################################
|
| 121 |
+
@staticmethod
|
| 122 |
+
def BERT( cf, model_id, model_epoch, devices, args = {}) :
|
| 123 |
+
|
| 124 |
+
cf.lat_sampling_weighted = False
|
| 125 |
+
cf.BERT_strategy = 'BERT'
|
| 126 |
+
cf.log_test_num_ranks = 4
|
| 127 |
+
cf.num_samples_validate = 10 #28 #1472
|
| 128 |
+
Evaluator.parse_args( cf, args)
|
| 129 |
+
utils.check_num_samples(cf.num_samples_validate, cf.batch_size)
|
| 130 |
+
Evaluator.run( cf, model_id, model_epoch, devices)
|
| 131 |
+
|
| 132 |
+
##############################################
|
| 133 |
+
@staticmethod
|
| 134 |
+
def forecast( cf, model_id, model_epoch, devices, args = {}) :
|
| 135 |
+
|
| 136 |
+
cf.lat_sampling_weighted = False
|
| 137 |
+
cf.BERT_strategy = 'forecast'
|
| 138 |
+
cf.log_test_num_ranks = 4
|
| 139 |
+
cf.forecast_num_tokens = 1 # will be overwritten when user specified
|
| 140 |
+
cf.num_samples_validate = 128 #128
|
| 141 |
+
Evaluator.parse_args( cf, args)
|
| 142 |
+
utils.check_num_samples(cf.num_samples_validate, cf.batch_size)
|
| 143 |
+
Evaluator.run( cf, model_id, model_epoch, devices)
|
| 144 |
+
|
| 145 |
+
##############################################
|
| 146 |
+
@staticmethod
|
| 147 |
+
def global_forecast( cf, model_id, model_epoch, devices, args = {}) :
|
| 148 |
+
|
| 149 |
+
cf.BERT_strategy = 'global_forecast'
|
| 150 |
+
cf.batch_size_test = 24
|
| 151 |
+
cf.num_loader_workers = 12 #1
|
| 152 |
+
cf.log_test_num_ranks = 1
|
| 153 |
+
|
| 154 |
+
#TODO: temporary solution. Add support for batch_size > 1
|
| 155 |
+
cf.batch_size_validation = 1 #64
|
| 156 |
+
cf.batch_size = 1
|
| 157 |
+
|
| 158 |
+
if not hasattr(cf, 'num_samples_validate'):
|
| 159 |
+
cf.num_samples_validate = 196
|
| 160 |
+
#if not hasattr(cf,'with_mixed_precision'):
|
| 161 |
+
cf.with_mixed_precision = True
|
| 162 |
+
|
| 163 |
+
Evaluator.parse_args( cf, args)
|
| 164 |
+
|
| 165 |
+
dates = args['dates']
|
| 166 |
+
evaluator = Evaluator.load( cf, model_id, model_epoch, devices)
|
| 167 |
+
evaluator.model.set_global( NetMode.test, np.array( dates))
|
| 168 |
+
if 0 == cf.par_rank :
|
| 169 |
+
cf.print()
|
| 170 |
+
cf.write_json( wandb)
|
| 171 |
+
evaluator.validate( 0, cf.BERT_strategy)
|
| 172 |
+
|
| 173 |
+
##############################################
|
| 174 |
+
@staticmethod
|
| 175 |
+
def global_forecast_range( cf, model_id, model_epoch, devices, args = {}) :
|
| 176 |
+
|
| 177 |
+
cf.forecast_num_tokens = 2
|
| 178 |
+
cf.BERT_strategy = 'global_forecast'
|
| 179 |
+
cf.token_overlap = [0, 0]
|
| 180 |
+
|
| 181 |
+
cf.batch_size_test = 24
|
| 182 |
+
cf.num_loader_workers = 1
|
| 183 |
+
cf.log_test_num_ranks = 1
|
| 184 |
+
cf.batch_size_start = 14
|
| 185 |
+
if not hasattr(cf, 'num_samples_validate'):
|
| 186 |
+
cf.num_samples_validate = 196
|
| 187 |
+
|
| 188 |
+
Evaluator.parse_args( cf, args)
|
| 189 |
+
|
| 190 |
+
if 0 == cf.par_rank :
|
| 191 |
+
cf.print()
|
| 192 |
+
cf.write_json( wandb)
|
| 193 |
+
|
| 194 |
+
# generate temporal sequence
|
| 195 |
+
dates = [ ]
|
| 196 |
+
num_steps = 31*2
|
| 197 |
+
cur_date = [2018, 1, 1, 0+6] #6h models
|
| 198 |
+
for _ in range(num_steps) :
|
| 199 |
+
tdate = datetime.datetime( cur_date[0], cur_date[1], cur_date[2], cur_date[3])
|
| 200 |
+
tdate += datetime.timedelta( hours = 12 )
|
| 201 |
+
cur_date = [tdate.year, tdate.month, tdate.day, tdate.hour]
|
| 202 |
+
dates += [cur_date]
|
| 203 |
+
|
| 204 |
+
evaluator = Evaluator.load( cf, model_id, model_epoch, devices)
|
| 205 |
+
evaluator.model.set_global( NetMode.test, np.array( dates))
|
| 206 |
+
evaluator.evaluate( 0, cf.BERT_strategy)
|
| 207 |
+
|
| 208 |
+
##############################################
|
| 209 |
+
@staticmethod
|
| 210 |
+
def temporal_interpolation( cf, model_id, model_epoch, devices, args = {}) :
|
| 211 |
+
|
| 212 |
+
# set/over-write options
|
| 213 |
+
cf.BERT_strategy = 'temporal_interpolation'
|
| 214 |
+
cf.log_test_num_ranks = 4
|
| 215 |
+
cf.num_samples_validate = 128
|
| 216 |
+
Evaluator.parse_args( cf, args)
|
| 217 |
+
utils.check_num_samples(cf.num_samples_validate, cf.batch_size)
|
| 218 |
+
Evaluator.run( cf, model_id, model_epoch, devices)
|
| 219 |
+
|
| 220 |
+
##############################################
|
| 221 |
+
@staticmethod
|
| 222 |
+
def fixed_location( cf, model_id, model_epoch, devices, args = {}) :
|
| 223 |
+
|
| 224 |
+
# set/over-write options
|
| 225 |
+
cf.BERT_strategy = 'BERT'
|
| 226 |
+
cf.num_files_test = 2
|
| 227 |
+
cf.num_patches_per_t_test = 2
|
| 228 |
+
cf.log_test_num_ranks = 4
|
| 229 |
+
|
| 230 |
+
pos = [ 33.55 , 18.25 ]
|
| 231 |
+
years = [2018]
|
| 232 |
+
months = list(range(1,12+1))
|
| 233 |
+
num_t_samples_per_month = 2
|
| 234 |
+
|
| 235 |
+
evaluator = Evaluator.load( cf, model_id, model_epoch, devices)
|
| 236 |
+
evaluator.model.set_location( NetMode.test, pos, years, months, num_t_samples_per_month)
|
| 237 |
+
if 0 == cf.par_rank :
|
| 238 |
+
cf.print()
|
| 239 |
+
cf.write_json( wandb)
|
| 240 |
+
evaluator.evaluate( 0)
|
vendor/atmorep-official/atmorep/core/train.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import os
|
| 19 |
+
import sys
|
| 20 |
+
import traceback
|
| 21 |
+
import pdb
|
| 22 |
+
import wandb
|
| 23 |
+
|
| 24 |
+
from atmorep.core.trainer import Trainer_BERT
|
| 25 |
+
from atmorep.utils.utils import Config
|
| 26 |
+
from atmorep.utils.utils import setup_ddp
|
| 27 |
+
from atmorep.utils.utils import setup_wandb
|
| 28 |
+
from atmorep.utils.utils import init_torch
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
####################################################################################################
|
| 32 |
+
def train_continue( wandb_id, epoch, Trainer, epoch_continue = -1) :
|
| 33 |
+
|
| 34 |
+
devices = init_torch()
|
| 35 |
+
with_ddp = True
|
| 36 |
+
par_rank, par_size = setup_ddp( with_ddp)
|
| 37 |
+
|
| 38 |
+
cf = Config().load_json( wandb_id)
|
| 39 |
+
|
| 40 |
+
cf.num_accs_per_task = len(devices) # number of GPUs / accelerators per task
|
| 41 |
+
cf.with_ddp = with_ddp
|
| 42 |
+
cf.par_rank = par_rank
|
| 43 |
+
cf.par_size = par_size
|
| 44 |
+
cf.optimizer_zero = False
|
| 45 |
+
cf.attention = False
|
| 46 |
+
# name has changed but ensure backward compatibility
|
| 47 |
+
if hasattr( cf, 'loader_num_workers') :
|
| 48 |
+
cf.num_loader_workers = cf.loader_num_workers
|
| 49 |
+
if not hasattr( cf, 'n_size'):
|
| 50 |
+
cf.n_size = [36, 0.25*9*6, 0.25*9*12]
|
| 51 |
+
if not hasattr(cf, 'num_samples_per_epoch'):
|
| 52 |
+
cf.num_samples_per_epoch = 1024
|
| 53 |
+
if not hasattr(cf, 'num_samples_validate'):
|
| 54 |
+
cf.num_samples_validate = 128
|
| 55 |
+
if not hasattr(cf, 'with_mixed_precision'):
|
| 56 |
+
cf.with_mixed_precision = True
|
| 57 |
+
if not hasattr(cf, 'years_val'):
|
| 58 |
+
cf.years_val = cf.years_test
|
| 59 |
+
|
| 60 |
+
# any parameter in cf can be overwritten when training is continued, e.g. we can increase the
|
| 61 |
+
# masking rate
|
| 62 |
+
# cf.fields = [ [ 'specific_humidity', [ 1, 2048, [ ], 0 ],
|
| 63 |
+
# [ 96, 105, 114, 123, 137 ],
|
| 64 |
+
# [12, 6, 12], [3, 9, 9], [0.5, 0.9, 0.1, 0.05] ] ]
|
| 65 |
+
|
| 66 |
+
setup_wandb( cf.with_wandb, cf, par_rank, project_name='train', mode='offline')
|
| 67 |
+
# resuming a run requires online mode, which is not available everywhere
|
| 68 |
+
#setup_wandb( cf.with_wandb, cf, par_rank, wandb_id = wandb_id)
|
| 69 |
+
|
| 70 |
+
if cf.with_wandb and 0 == cf.par_rank :
|
| 71 |
+
cf.write_json( wandb)
|
| 72 |
+
cf.print()
|
| 73 |
+
|
| 74 |
+
if -1 == epoch_continue :
|
| 75 |
+
epoch_continue = epoch
|
| 76 |
+
|
| 77 |
+
# run
|
| 78 |
+
trainer = Trainer.load( cf, wandb_id, epoch, devices)
|
| 79 |
+
print( 'Loaded run \'{}\' at epoch {}.'.format( wandb_id, epoch))
|
| 80 |
+
trainer.run( epoch_continue)
|
| 81 |
+
|
| 82 |
+
####################################################################################################
|
| 83 |
+
def train() :
|
| 84 |
+
|
| 85 |
+
devices = init_torch()
|
| 86 |
+
with_ddp = True
|
| 87 |
+
par_rank, par_size = setup_ddp( with_ddp)
|
| 88 |
+
|
| 89 |
+
# torch.cuda.set_sync_debug_mode(1)
|
| 90 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 91 |
+
|
| 92 |
+
cf = Config()
|
| 93 |
+
# parallelization
|
| 94 |
+
cf.with_ddp = with_ddp
|
| 95 |
+
cf.num_accs_per_task = len(devices) # number of GPUs / accelerators per task
|
| 96 |
+
cf.par_rank = par_rank
|
| 97 |
+
cf.par_size = par_size
|
| 98 |
+
|
| 99 |
+
# format: list of fields where for each field the list is
|
| 100 |
+
# [ name ,
|
| 101 |
+
# [ dynamic or static field { 1, 0 }, embedding dimension, , device id ],
|
| 102 |
+
# [ vertical levels ],
|
| 103 |
+
# [ num_tokens],
|
| 104 |
+
# [ token size],
|
| 105 |
+
# [ total masking rate, rate masking, rate noising, rate for multi-res distortion]
|
| 106 |
+
# ]
|
| 107 |
+
|
| 108 |
+
# cf.fields = [ [ 'temperature', [ 1, 1024, [ ], 0 ],
|
| 109 |
+
# [ 96, 105, 114, 123, 137 ],
|
| 110 |
+
# [12, 2, 4], [3, 27, 27], [0.5, 0.9, 0.2, 0.05], 'local' ] ]
|
| 111 |
+
# cf.fields_prediction = [ [cf.fields[0][0], 1.] ]
|
| 112 |
+
|
| 113 |
+
cf.fields = [ [ 'velocity_u', [ 1, 1024, [ ], 0 ],
|
| 114 |
+
[ 96, 105, 114, 123, 137 ],
|
| 115 |
+
[12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ] ]
|
| 116 |
+
|
| 117 |
+
cf.fields_prediction = [ [cf.fields[0][0], 1.] ]
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
# cf.fields = [ [ 'velocity_v', [ 1, 1024, [ ], 0 ],
|
| 121 |
+
# [ 96, 105, 114, 123, 137 ],
|
| 122 |
+
# [12, 3, 6], [3, 18, 18], [0.25, 0.9, 0.1, 0.05] ] ]
|
| 123 |
+
|
| 124 |
+
# cf.fields = [ [ 'velocity_z', [ 1, 1024, [ ], 0 ],
|
| 125 |
+
# [ 96, 105, 114, 123, 137 ],
|
| 126 |
+
# [12, 3, 6], [3, 18, 18], [0.25, 0.9, 0.1, 0.05] ] ]
|
| 127 |
+
|
| 128 |
+
# cf.fields = [ [ 'specific_humidity', [ 1, 1024, [ ], 0 ],
|
| 129 |
+
# [ 96, 105, 114, 123, 137 ],
|
| 130 |
+
# [12, 3, 6], [3, 18, 18], [0.25, 0.9, 0.1, 0.05] ] ]
|
| 131 |
+
|
| 132 |
+
cf.fields_targets = []
|
| 133 |
+
|
| 134 |
+
cf.years_train = list( range( 1979, 2021))
|
| 135 |
+
cf.years_val = [2021] #[2018]
|
| 136 |
+
cf.month = None
|
| 137 |
+
cf.geo_range_sampling = [[ -90., 90.], [ 0., 360.]]
|
| 138 |
+
cf.time_sampling = 1 # sampling rate for time steps
|
| 139 |
+
# random seeds
|
| 140 |
+
cf.torch_seed = torch.initial_seed()
|
| 141 |
+
# training params
|
| 142 |
+
cf.batch_size_validation = 1 #64
|
| 143 |
+
cf.batch_size = 96
|
| 144 |
+
cf.num_epochs = 400 #128
|
| 145 |
+
cf.num_samples_per_epoch = 4096*12
|
| 146 |
+
cf.num_samples_validate = 128*12
|
| 147 |
+
cf.num_loader_workers = 8
|
| 148 |
+
|
| 149 |
+
# additional infos
|
| 150 |
+
cf.size_token_info = 8
|
| 151 |
+
cf.size_token_info_net = 16
|
| 152 |
+
cf.grad_checkpointing = True
|
| 153 |
+
cf.with_cls = False
|
| 154 |
+
# network config
|
| 155 |
+
cf.with_mixed_precision = True
|
| 156 |
+
cf.with_layernorm = True
|
| 157 |
+
cf.coupling_num_heads_per_field = 1
|
| 158 |
+
cf.dropout_rate = 0.05
|
| 159 |
+
cf.with_qk_lnorm = False
|
| 160 |
+
# encoder
|
| 161 |
+
cf.encoder_num_layers = 6
|
| 162 |
+
cf.encoder_num_heads = 16
|
| 163 |
+
cf.encoder_num_mlp_layers = 2
|
| 164 |
+
cf.encoder_att_type = 'dense'
|
| 165 |
+
# decoder
|
| 166 |
+
cf.decoder_num_layers = 6
|
| 167 |
+
cf.decoder_num_heads = 16
|
| 168 |
+
cf.decoder_num_mlp_layers = 2
|
| 169 |
+
cf.decoder_self_att = False
|
| 170 |
+
cf.decoder_cross_att_ratio = 0.5
|
| 171 |
+
cf.decoder_cross_att_rate = 1.0
|
| 172 |
+
cf.decoder_att_type = 'dense'
|
| 173 |
+
# tail net
|
| 174 |
+
cf.net_tail_num_nets = 16
|
| 175 |
+
cf.net_tail_num_layers = 0
|
| 176 |
+
# loss
|
| 177 |
+
cf.losses = ['mse_ensemble', 'stats'] # mse, mse_ensemble, stats, crps, weighted_mse
|
| 178 |
+
# training
|
| 179 |
+
cf.optimizer_zero = False
|
| 180 |
+
cf.lr_start = 5. * 10e-7
|
| 181 |
+
cf.lr_max = 0.00005*3
|
| 182 |
+
cf.lr_min = 0.00004 #0.00002
|
| 183 |
+
cf.weight_decay = 0.05 #0.1
|
| 184 |
+
cf.lr_decay_rate = 1.025
|
| 185 |
+
cf.lr_start_epochs = 3
|
| 186 |
+
cf.model_log_frequency = 256 #save checkpoint every X batches
|
| 187 |
+
# BERT
|
| 188 |
+
# strategies: 'BERT', 'forecast', 'temporal_interpolation'
|
| 189 |
+
cf.BERT_strategy = 'BERT'
|
| 190 |
+
cf.forecast_num_tokens = 2 # only needed / used for BERT_strategy 'forecast
|
| 191 |
+
cf.BERT_fields_synced = False # apply synchronized / identical masking to all fields
|
| 192 |
+
# (fields need to have same BERT params for this to have effect)
|
| 193 |
+
cf.BERT_mr_max = 2 # maximum reduction rate for resolution
|
| 194 |
+
|
| 195 |
+
# debug / output
|
| 196 |
+
cf.log_test_num_ranks = 0
|
| 197 |
+
cf.save_grads = False
|
| 198 |
+
cf.profile = False
|
| 199 |
+
cf.test_initial = False
|
| 200 |
+
cf.attention = False
|
| 201 |
+
|
| 202 |
+
cf.rng_seed = None
|
| 203 |
+
|
| 204 |
+
# usually use %>wandb offline to switch to disable syncing with server
|
| 205 |
+
cf.with_wandb = True
|
| 206 |
+
setup_wandb( cf.with_wandb, cf, par_rank, 'train', mode='offline')
|
| 207 |
+
|
| 208 |
+
# cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res100_chunk32.zarr'
|
| 209 |
+
# cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res100_chunk32.zarr'
|
| 210 |
+
# # # in steps x lat_degrees x lon_degrees
|
| 211 |
+
# cf.n_size = [36, 1*9*6, 1.*9*12]
|
| 212 |
+
|
| 213 |
+
# # # # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk16.zarr'
|
| 214 |
+
# # # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk32.zarr'
|
| 215 |
+
# cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk32.zarr'
|
| 216 |
+
# # #
|
| 217 |
+
# # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk8.zarr'
|
| 218 |
+
# # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk8_lat180_lon180.zarr'
|
| 219 |
+
# # # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk16.zarr'
|
| 220 |
+
cf.file_path = '/gpfs/scratch/ehpc03/era5_y1979_2021_res025_chunk8.zarr/'
|
| 221 |
+
# # # in steps x lat_degrees x lon_degrees
|
| 222 |
+
cf.n_size = [36, 0.25*9*6, 0.25*9*12]
|
| 223 |
+
|
| 224 |
+
# cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res100_chunk16.zarr'
|
| 225 |
+
#cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res100_chunk16.zarr'
|
| 226 |
+
#cf.n_size = [36, 1*9*6, 1.*9*12]
|
| 227 |
+
|
| 228 |
+
if cf.with_wandb and 0 == cf.par_rank :
|
| 229 |
+
cf.write_json( wandb)
|
| 230 |
+
cf.print()
|
| 231 |
+
|
| 232 |
+
trainer = Trainer_BERT( cf, devices).create()
|
| 233 |
+
trainer.run()
|
| 234 |
+
|
| 235 |
+
####################################################################################################
|
| 236 |
+
if __name__ == '__main__':
|
| 237 |
+
|
| 238 |
+
try :
|
| 239 |
+
|
| 240 |
+
train()
|
| 241 |
+
|
| 242 |
+
# wandb_id, epoch, epoch_continue = 'gxfywjzl', 127, 127
|
| 243 |
+
# Trainer = Trainer_BERT
|
| 244 |
+
# train_continue( wandb_id, epoch, Trainer, epoch_continue)
|
| 245 |
+
|
| 246 |
+
except :
|
| 247 |
+
|
| 248 |
+
extype, value, tb = sys.exc_info()
|
| 249 |
+
traceback.print_exc()
|
| 250 |
+
pdb.post_mortem(tb)
|
| 251 |
+
|
vendor/atmorep-official/atmorep/core/train_multi.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import os
|
| 19 |
+
import sys
|
| 20 |
+
import traceback
|
| 21 |
+
import pdb
|
| 22 |
+
import wandb
|
| 23 |
+
|
| 24 |
+
import atmorep.config.config as config
|
| 25 |
+
from atmorep.core.trainer import Trainer_BERT
|
| 26 |
+
from atmorep.utils.utils import Config
|
| 27 |
+
from atmorep.utils.utils import setup_ddp
|
| 28 |
+
from atmorep.utils.utils import setup_wandb
|
| 29 |
+
from atmorep.utils.utils import init_torch
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
####################################################################################################
|
| 33 |
+
def train_continue( wandb_id, epoch, Trainer, epoch_continue = -1) :
|
| 34 |
+
|
| 35 |
+
devices = init_torch()
|
| 36 |
+
with_ddp = True
|
| 37 |
+
par_rank, par_size = setup_ddp( with_ddp)
|
| 38 |
+
|
| 39 |
+
cf = Config().load_json( wandb_id)
|
| 40 |
+
|
| 41 |
+
cf.num_accs_per_task = len(devices) # number of GPUs / accelerators per task
|
| 42 |
+
cf.with_ddp = with_ddp
|
| 43 |
+
cf.par_rank = par_rank
|
| 44 |
+
cf.par_size = par_size
|
| 45 |
+
cf.optimizer_zero = False
|
| 46 |
+
cf.attention = False
|
| 47 |
+
# name has changed but ensure backward compatibility
|
| 48 |
+
if hasattr( cf, 'loader_num_workers') :
|
| 49 |
+
cf.num_loader_workers = cf.loader_num_workers
|
| 50 |
+
if not hasattr( cf, 'n_size'):
|
| 51 |
+
cf.n_size = [36, 0.25*9*6, 0.25*9*12]
|
| 52 |
+
if not hasattr(cf, 'num_samples_per_epoch'):
|
| 53 |
+
cf.num_samples_per_epoch = 1024
|
| 54 |
+
if not hasattr(cf, 'num_samples_validate'):
|
| 55 |
+
cf.num_samples_validate = 128
|
| 56 |
+
if not hasattr(cf, 'with_mixed_precision'):
|
| 57 |
+
cf.with_mixed_precision = True
|
| 58 |
+
|
| 59 |
+
if not hasattr(cf, 'years_val'):
|
| 60 |
+
cf.years_val = cf.years_test
|
| 61 |
+
|
| 62 |
+
#cf.with_mixed_precision = False
|
| 63 |
+
# any parameter in cf can be overwritten when training is continued, e.g. we can increase the
|
| 64 |
+
# masking rate
|
| 65 |
+
# cf.fields = [ [ 'specific_humidity', [ 1, 2048, [ ], 0 ],
|
| 66 |
+
# [ 96, 105, 114, 123, 137 ],
|
| 67 |
+
# [12, 6, 12], [3, 9, 9], [0.5, 0.9, 0.1, 0.05] ] ]
|
| 68 |
+
|
| 69 |
+
setup_wandb( cf.with_wandb, cf, par_rank, project_name='train', mode='offline')
|
| 70 |
+
# resuming a run requires online mode, which is not available everywhere
|
| 71 |
+
#setup_wandb( cf.with_wandb, cf, par_rank, wandb_id = wandb_id)
|
| 72 |
+
|
| 73 |
+
if cf.with_wandb and 0 == cf.par_rank :
|
| 74 |
+
cf.write_json( wandb)
|
| 75 |
+
cf.print()
|
| 76 |
+
|
| 77 |
+
if -1 == epoch_continue :
|
| 78 |
+
epoch_continue = epoch
|
| 79 |
+
|
| 80 |
+
# run
|
| 81 |
+
trainer = Trainer.load( cf, wandb_id, epoch, devices)
|
| 82 |
+
print( 'Loaded run \'{}\' at epoch {}.'.format( wandb_id, epoch))
|
| 83 |
+
trainer.run( epoch_continue)
|
| 84 |
+
|
| 85 |
+
####################################################################################################
|
| 86 |
+
def train() :
|
| 87 |
+
|
| 88 |
+
devices = init_torch()
|
| 89 |
+
with_ddp = True
|
| 90 |
+
par_rank, par_size = setup_ddp( with_ddp)
|
| 91 |
+
|
| 92 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 93 |
+
|
| 94 |
+
cf = Config()
|
| 95 |
+
# parallelization
|
| 96 |
+
cf.with_ddp = with_ddp
|
| 97 |
+
cf.num_accs_per_task = len(devices) # number of GPUs / accelerators per task
|
| 98 |
+
cf.par_rank = par_rank
|
| 99 |
+
cf.par_size = par_size
|
| 100 |
+
|
| 101 |
+
# format: list of fields where for each field the list is
|
| 102 |
+
# [ name ,
|
| 103 |
+
# [ dynamic or static field { 1, 0 }, embedding dimension, , device id ],
|
| 104 |
+
# [ vertical levels ],
|
| 105 |
+
# [ num_tokens],
|
| 106 |
+
# [ token size],
|
| 107 |
+
# [ total masking rate, rate masking, rate noising, rate for multi-res distortion]
|
| 108 |
+
# ]
|
| 109 |
+
|
| 110 |
+
# cf.fields = [
|
| 111 |
+
# [ 'velocity_u', [ 1, 1024, ['velocity_v', 'temperature'], 0 ],
|
| 112 |
+
# [ 96, 105, 114, 123, 137 ],
|
| 113 |
+
# [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
|
| 114 |
+
# [ 'velocity_v', [ 1, 1024, ['velocity_u', 'temperature'], 1 ],
|
| 115 |
+
# [ 96, 105, 114, 123, 137 ],
|
| 116 |
+
# [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
|
| 117 |
+
# [ 'specific_humidity', [ 1, 1024, ['velocity_u', 'velocity_v', 'temperature'], 2 ],
|
| 118 |
+
# [ 96, 105, 114, 123, 137 ],
|
| 119 |
+
# [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
|
| 120 |
+
# [ 'velocity_z', [ 1, 1024, ['velocity_u', 'velocity_v', 'temperature'], 3 ],
|
| 121 |
+
# [ 96, 105, 114, 123, 137 ],
|
| 122 |
+
# [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
|
| 123 |
+
# [ 'temperature', [ 1, 512, ['velocity_u', 'velocity_v', 'specific_humidity'], 3 ],
|
| 124 |
+
# [ 96, 105, 114, 123, 137 ],
|
| 125 |
+
# [12, 2, 4], [3, 27, 27], [0.5, 0.9, 0.2, 0.05], 'local' ],
|
| 126 |
+
# ]
|
| 127 |
+
|
| 128 |
+
cf.fields = [
|
| 129 |
+
[ 'velocity_u', [ 1, 1024, ['velocity_v', 'temperature'], 0, ['j8dwr5qj', -2] ],
|
| 130 |
+
[ 96, 105, 114, 123, 137 ],
|
| 131 |
+
[12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
|
| 132 |
+
[ 'velocity_v', [ 1, 1024, ['velocity_u', 'temperature'], 1, ['0tlnm5up', -2] ],
|
| 133 |
+
[ 96, 105, 114, 123, 137 ],
|
| 134 |
+
[12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
|
| 135 |
+
[ 'specific_humidity', [ 1, 1024, ['velocity_u', 'velocity_v', 'temperature'], 2, ['v63l01zu', -2] ],
|
| 136 |
+
[ 96, 105, 114, 123, 137 ],
|
| 137 |
+
[12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
|
| 138 |
+
[ 'velocity_z', [ 1, 1024, ['velocity_u', 'velocity_v', 'temperature'], 3, ['9l1errbo', -2] ],
|
| 139 |
+
[ 96, 105, 114, 123, 137 ],
|
| 140 |
+
[12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
|
| 141 |
+
[ 'temperature', [ 1, 1024, ['velocity_u', 'velocity_v', 'specific_humidity'], 3, ['7ojls62c', -2] ],
|
| 142 |
+
[ 96, 105, 114, 123, 137 ],
|
| 143 |
+
[12, 2, 4], [3, 27, 27], [0.5, 0.9, 0.2, 0.05], 'local' ],
|
| 144 |
+
# ['total_precip', [1, 1536, ['velocity_u', 'velocity_v', 'velocity_z', 'specific_humidity'], 3, ['3kdutwqb', 900]],
|
| 145 |
+
# [0],
|
| 146 |
+
# [12, 6, 12], [3, 9, 9], [0.25, 0.9, 0.1, 0.05]]
|
| 147 |
+
]
|
| 148 |
+
|
| 149 |
+
cf.fields_prediction = [
|
| 150 |
+
['velocity_u', 0.225], ['velocity_v', 0.225],
|
| 151 |
+
['specific_humidity', 0.15], ['velocity_z', 0.1], ['temperature', 0.2]
|
| 152 |
+
# ['total_precip', 0.1]
|
| 153 |
+
]
|
| 154 |
+
|
| 155 |
+
cf.fields_targets = []
|
| 156 |
+
|
| 157 |
+
cf.years_train = list( range( 1979, 2021))
|
| 158 |
+
cf.years_val = [2021] #[2018]
|
| 159 |
+
cf.month = None
|
| 160 |
+
cf.geo_range_sampling = [[ -90., 90.], [ 0., 360.]]
|
| 161 |
+
cf.time_sampling = 1 # sampling rate for time steps
|
| 162 |
+
# random seeds
|
| 163 |
+
cf.torch_seed = torch.initial_seed()
|
| 164 |
+
# training params
|
| 165 |
+
cf.batch_size_validation = 1 #64
|
| 166 |
+
|
| 167 |
+
cf.batch_size = 96
|
| 168 |
+
cf.num_epochs = 128
|
| 169 |
+
cf.num_samples_per_epoch = 4096*12
|
| 170 |
+
cf.num_samples_validate = 128*12
|
| 171 |
+
cf.num_loader_workers = 5
|
| 172 |
+
|
| 173 |
+
# additional infos
|
| 174 |
+
cf.size_token_info = 8
|
| 175 |
+
cf.size_token_info_net = 16
|
| 176 |
+
cf.grad_checkpointing = True
|
| 177 |
+
cf.with_cls = False
|
| 178 |
+
# network config
|
| 179 |
+
cf.with_mixed_precision = True
|
| 180 |
+
cf.with_layernorm = True
|
| 181 |
+
cf.coupling_num_heads_per_field = 1
|
| 182 |
+
cf.dropout_rate = 0.05
|
| 183 |
+
cf.with_qk_lnorm = True
|
| 184 |
+
# encoder
|
| 185 |
+
cf.encoder_num_layers = 6
|
| 186 |
+
cf.encoder_num_heads = 16
|
| 187 |
+
cf.encoder_num_mlp_layers = 2
|
| 188 |
+
cf.encoder_att_type = 'dense'
|
| 189 |
+
# decoder
|
| 190 |
+
cf.decoder_num_layers = 6
|
| 191 |
+
cf.decoder_num_heads = 16
|
| 192 |
+
cf.decoder_num_mlp_layers = 2
|
| 193 |
+
cf.decoder_self_att = False
|
| 194 |
+
cf.decoder_cross_att_ratio = 0.5
|
| 195 |
+
cf.decoder_cross_att_rate = 1.0
|
| 196 |
+
cf.decoder_att_type = 'dense'
|
| 197 |
+
# tail net
|
| 198 |
+
cf.net_tail_num_nets = 16
|
| 199 |
+
cf.net_tail_num_layers = 0
|
| 200 |
+
# loss
|
| 201 |
+
cf.losses = ['mse_ensemble', 'stats'] # mse, mse_ensemble, stats, crps, weighted_mse
|
| 202 |
+
# training
|
| 203 |
+
cf.optimizer_zero = False
|
| 204 |
+
|
| 205 |
+
cf.lr_start = 0.00001 #5. * 10e-7
|
| 206 |
+
cf.lr_max = 0.00002
|
| 207 |
+
cf.lr_min = 0.00001
|
| 208 |
+
cf.weight_decay = 0.025 #0.1
|
| 209 |
+
cf.lr_decay_rate = 1.025
|
| 210 |
+
cf.lr_start_epochs = 3
|
| 211 |
+
cf.model_log_frequency = 256 #save checkpoint every X batches
|
| 212 |
+
|
| 213 |
+
# BERT
|
| 214 |
+
# strategies: 'BERT', 'forecast', 'temporal_interpolation'
|
| 215 |
+
cf.BERT_strategy = 'BERT' #'BERT'
|
| 216 |
+
cf.forecast_num_tokens = 2 # only needed / used for BERT_strategy 'forecast
|
| 217 |
+
|
| 218 |
+
cf.BERT_fields_synced = False # apply synchronized / identical masking to all fields
|
| 219 |
+
# (fields need to have same BERT params for this to have effect)
|
| 220 |
+
cf.BERT_mr_max = 2 # maximum reduction rate for resolution
|
| 221 |
+
|
| 222 |
+
# debug / output
|
| 223 |
+
cf.log_test_num_ranks = 0
|
| 224 |
+
cf.save_grads = False
|
| 225 |
+
cf.profile = False
|
| 226 |
+
cf.test_initial = False
|
| 227 |
+
cf.attention = False
|
| 228 |
+
|
| 229 |
+
cf.rng_seed = None
|
| 230 |
+
|
| 231 |
+
# usually use %>wandb offline to switch to disable syncing with server
|
| 232 |
+
cf.with_wandb = True
|
| 233 |
+
setup_wandb( cf.with_wandb, cf, par_rank, 'train', mode='offline')
|
| 234 |
+
|
| 235 |
+
# cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res100_chunk32.zarr'
|
| 236 |
+
# cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res100_chunk32.zarr'
|
| 237 |
+
# # # in steps x lat_degrees x lon_degrees
|
| 238 |
+
# cf.n_size = [36, 1*9*6, 1.*9*12]
|
| 239 |
+
|
| 240 |
+
# # # # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk16.zarr'
|
| 241 |
+
# # # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk32.zarr'
|
| 242 |
+
# cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk32.zarr'
|
| 243 |
+
# # #
|
| 244 |
+
# # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk8.zarr'
|
| 245 |
+
# # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk8_lat180_lon180.zarr'
|
| 246 |
+
# # # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk16.zarr'
|
| 247 |
+
cf.file_path = '/gpfs/scratch/ehpc03/era5_y1979_2021_res025_chunk8.zarr/'
|
| 248 |
+
# # # in steps x lat_degrees x lon_degrees
|
| 249 |
+
cf.n_size = [36, 0.25*9*6, 0.25*9*12]
|
| 250 |
+
|
| 251 |
+
# cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res100_chunk16.zarr'
|
| 252 |
+
#cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res100_chunk16.zarr'
|
| 253 |
+
#cf.n_size = [36, 1*9*6, 1.*9*12]
|
| 254 |
+
|
| 255 |
+
if cf.with_wandb and 0 == cf.par_rank :
|
| 256 |
+
cf.write_json( wandb)
|
| 257 |
+
cf.print()
|
| 258 |
+
|
| 259 |
+
trainer = Trainer_BERT( cf, devices).create()
|
| 260 |
+
trainer.run()
|
| 261 |
+
|
| 262 |
+
####################################################################################################
|
| 263 |
+
if __name__ == '__main__':
|
| 264 |
+
|
| 265 |
+
try :
|
| 266 |
+
|
| 267 |
+
train()
|
| 268 |
+
|
| 269 |
+
# wandb_id, epoch, epoch_continue = 'uvrdtc0a', 95, 95 #multiformer from scratch
|
| 270 |
+
# Trainer = Trainer_BERT
|
| 271 |
+
# train_continue( wandb_id, epoch, Trainer, epoch_continue)
|
| 272 |
+
|
| 273 |
+
except :
|
| 274 |
+
|
| 275 |
+
extype, value, tb = sys.exc_info()
|
| 276 |
+
traceback.print_exc()
|
| 277 |
+
pdb.post_mortem(tb)
|
vendor/atmorep-official/atmorep/core/trainer.py
ADDED
|
@@ -0,0 +1,863 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import torchinfo
|
| 19 |
+
import numpy as np
|
| 20 |
+
import time
|
| 21 |
+
import code
|
| 22 |
+
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
import os
|
| 25 |
+
import datetime
|
| 26 |
+
import functools
|
| 27 |
+
|
| 28 |
+
import wandb
|
| 29 |
+
# import horovod.torch as hvd
|
| 30 |
+
import torch.distributed as dist
|
| 31 |
+
from torch.distributed.optim import ZeroRedundancyOptimizer
|
| 32 |
+
import torch.utils.data.distributed
|
| 33 |
+
|
| 34 |
+
import atmorep.config.config as config
|
| 35 |
+
|
| 36 |
+
from atmorep.core.atmorep_model import AtmoRep
|
| 37 |
+
from atmorep.core.atmorep_model import AtmoRepData
|
| 38 |
+
from atmorep.training.bert import prepare_batch_BERT_multifield
|
| 39 |
+
from atmorep.transformer.transformer_base import positional_encoding_harmonic
|
| 40 |
+
|
| 41 |
+
import atmorep.utils.token_infos_transformations as token_infos_transformations
|
| 42 |
+
|
| 43 |
+
from atmorep.utils.utils import Gaussian, CRPS, kernel_crps, weighted_mse, NetMode, tokenize, detokenize
|
| 44 |
+
from atmorep.datasets.data_writer import write_forecast, write_BERT, write_attention
|
| 45 |
+
from atmorep.datasets.normalizer import denormalize
|
| 46 |
+
|
| 47 |
+
####################################################################################################
|
| 48 |
+
class Trainer_Base() :
|
| 49 |
+
|
| 50 |
+
def __init__( self, cf, devices ) :
|
| 51 |
+
|
| 52 |
+
self.cf = cf
|
| 53 |
+
self.devices = devices
|
| 54 |
+
self.device_in = devices[0]
|
| 55 |
+
self.device_out = devices[-1]
|
| 56 |
+
|
| 57 |
+
self.fields_prediction_idx = []
|
| 58 |
+
self.loss_weights = torch.zeros( len(cf.fields_prediction) )
|
| 59 |
+
for ifield, field in enumerate(cf.fields_prediction) :
|
| 60 |
+
self.loss_weights[ifield] = self.cf.fields_prediction[ifield][1]
|
| 61 |
+
for idx, field_info in enumerate(cf.fields) :
|
| 62 |
+
if field_info[0] == field[0] :
|
| 63 |
+
self.fields_prediction_idx.append( idx)
|
| 64 |
+
break
|
| 65 |
+
self.loss_weights = self.loss_weights.to( self.device_out)
|
| 66 |
+
|
| 67 |
+
self.MSELoss = torch.nn.MSELoss()
|
| 68 |
+
|
| 69 |
+
# transformation for token infos
|
| 70 |
+
if hasattr( cf, 'token_infos_transformation') :
|
| 71 |
+
self.tok_infos_trans = getattr( token_infos_transformations, cf.token_infos_transformation)
|
| 72 |
+
else :
|
| 73 |
+
self.tok_infos_trans = getattr( token_infos_transformations, 'identity')
|
| 74 |
+
|
| 75 |
+
if 0 == cf.par_rank :
|
| 76 |
+
directory = Path( config.path_results, 'id{}'.format( cf.wandb_id))
|
| 77 |
+
if not os.path.exists(directory):
|
| 78 |
+
os.makedirs( directory)
|
| 79 |
+
directory = Path( config.path_models, 'id{}'.format( cf.wandb_id))
|
| 80 |
+
if not os.path.exists(directory):
|
| 81 |
+
os.makedirs( directory)
|
| 82 |
+
|
| 83 |
+
###################################################
|
| 84 |
+
def create( self, load_embeds=True) :
|
| 85 |
+
net = AtmoRep( self.cf)
|
| 86 |
+
self.model = AtmoRepData( net)
|
| 87 |
+
|
| 88 |
+
self.model.create( self.pre_batch, self.devices, load_embeds)
|
| 89 |
+
|
| 90 |
+
# TODO: pass the properly to model / net
|
| 91 |
+
self.model.net.encoder_to_decoder = self.encoder_to_decoder
|
| 92 |
+
self.model.net.decoder_to_tail = self.decoder_to_tail
|
| 93 |
+
return self
|
| 94 |
+
|
| 95 |
+
###################################################
|
| 96 |
+
@classmethod
|
| 97 |
+
def load( Typename, cf, model_id, epoch, devices) :
|
| 98 |
+
trainer = Typename( cf, devices).create( load_embeds=False)
|
| 99 |
+
trainer.model.net = trainer.model.net.load( model_id, devices, cf, epoch)
|
| 100 |
+
|
| 101 |
+
# TODO: pass the properly to model / net
|
| 102 |
+
trainer.model.net.encoder_to_decoder = trainer.encoder_to_decoder
|
| 103 |
+
trainer.model.net.decoder_to_tail = trainer.decoder_to_tail
|
| 104 |
+
|
| 105 |
+
str = 'Loaded model id = {}{}.'.format( model_id, f' at epoch = {epoch}' if epoch> -2 else '')
|
| 106 |
+
print( str)
|
| 107 |
+
return trainer
|
| 108 |
+
|
| 109 |
+
###################################################
|
| 110 |
+
def save( self, epoch) :
|
| 111 |
+
self.model.net.save( epoch)
|
| 112 |
+
|
| 113 |
+
###################################################
|
| 114 |
+
def get_learn_rates( self) :
|
| 115 |
+
|
| 116 |
+
cf = self.cf
|
| 117 |
+
size_padding = 5
|
| 118 |
+
learn_rates = np.zeros( cf.num_epochs + size_padding)
|
| 119 |
+
|
| 120 |
+
learn_rates[:cf.lr_start_epochs] = np.linspace( cf.lr_start, cf.lr_max, num = cf.lr_start_epochs)
|
| 121 |
+
lr = learn_rates[cf.lr_start_epochs-1]
|
| 122 |
+
ic = 0
|
| 123 |
+
for epoch in range( cf.lr_start_epochs, cf.num_epochs + size_padding) :
|
| 124 |
+
lr = max( lr / cf.lr_decay_rate, cf.lr_min)
|
| 125 |
+
learn_rates[epoch] = lr
|
| 126 |
+
if ic > 9999 : # sanity check
|
| 127 |
+
assert "Maximum number of epochs exceeded."
|
| 128 |
+
|
| 129 |
+
return learn_rates
|
| 130 |
+
|
| 131 |
+
###################################################
|
| 132 |
+
def run( self, epoch = -1) :
|
| 133 |
+
|
| 134 |
+
cf = self.cf
|
| 135 |
+
model = self.model
|
| 136 |
+
|
| 137 |
+
learn_rates = self.get_learn_rates()
|
| 138 |
+
|
| 139 |
+
if cf.with_ddp :
|
| 140 |
+
self.model_ddp = torch.nn.parallel.DistributedDataParallel( model, static_graph=True)
|
| 141 |
+
if not cf.optimizer_zero :
|
| 142 |
+
self.optimizer = torch.optim.AdamW( self.model_ddp.parameters(), lr=cf.lr_start,
|
| 143 |
+
weight_decay=cf.weight_decay)
|
| 144 |
+
else :
|
| 145 |
+
self.optimizer = ZeroRedundancyOptimizer(self.model_ddp.parameters(),
|
| 146 |
+
optimizer_class=torch.optim.AdamW,
|
| 147 |
+
lr=cf.lr_start )
|
| 148 |
+
else :
|
| 149 |
+
self.optimizer = torch.optim.AdamW( self.model.parameters(), lr=cf.lr_start,
|
| 150 |
+
weight_decay=cf.weight_decay)
|
| 151 |
+
|
| 152 |
+
self.grad_scaler = torch.cuda.amp.GradScaler(enabled=cf.with_mixed_precision)
|
| 153 |
+
|
| 154 |
+
if 0 == cf.par_rank :
|
| 155 |
+
# print( self.model.net)
|
| 156 |
+
model_parameters = filter(lambda p: p.requires_grad, self.model_ddp.parameters())
|
| 157 |
+
num_params = sum([np.prod(p.size()) for p in model_parameters])
|
| 158 |
+
print( f'Number of trainable parameters: {num_params:,}')
|
| 159 |
+
|
| 160 |
+
if cf.test_initial :
|
| 161 |
+
cur_test_loss = self.validate( epoch, cf.BERT_strategy).cpu().numpy()
|
| 162 |
+
test_loss = np.array( [cur_test_loss])
|
| 163 |
+
else :
|
| 164 |
+
# generic value based on data normalization
|
| 165 |
+
test_loss = np.array( [1.0])
|
| 166 |
+
epoch += 1
|
| 167 |
+
|
| 168 |
+
if cf.profile :
|
| 169 |
+
lr = learn_rates[epoch]
|
| 170 |
+
for g in self.optimizer.param_groups:
|
| 171 |
+
g['lr'] = lr
|
| 172 |
+
self.profile()
|
| 173 |
+
|
| 174 |
+
# training loop
|
| 175 |
+
while True :
|
| 176 |
+
|
| 177 |
+
if epoch >= cf.num_epochs :
|
| 178 |
+
break
|
| 179 |
+
|
| 180 |
+
lr = learn_rates[epoch]
|
| 181 |
+
for g in self.optimizer.param_groups:
|
| 182 |
+
g['lr'] = lr
|
| 183 |
+
|
| 184 |
+
tstr = datetime.datetime.now().strftime("%H:%M:%S")
|
| 185 |
+
print( '{} : {} :: batch_size = {}, lr = {}'.format( epoch, tstr, cf.batch_size, lr) )
|
| 186 |
+
|
| 187 |
+
self.train( epoch)
|
| 188 |
+
|
| 189 |
+
if cf.with_wandb and 0 == cf.par_rank :
|
| 190 |
+
self.save( epoch)
|
| 191 |
+
|
| 192 |
+
cur_test_loss = self.validate( epoch, cf.BERT_strategy).cpu().numpy()
|
| 193 |
+
# self.validate( epoch, 'forecast')
|
| 194 |
+
|
| 195 |
+
# save model
|
| 196 |
+
if cur_test_loss < test_loss.min() :
|
| 197 |
+
self.save( -2)
|
| 198 |
+
test_loss = np.append( test_loss, [cur_test_loss])
|
| 199 |
+
|
| 200 |
+
epoch += 1
|
| 201 |
+
|
| 202 |
+
tstr = datetime.datetime.now().strftime("%H:%M:%S")
|
| 203 |
+
print( 'Finished training at {} with test loss = {}.'.format( tstr, test_loss[-1]) )
|
| 204 |
+
|
| 205 |
+
# save final network
|
| 206 |
+
if cf.with_wandb and 0 == cf.par_rank :
|
| 207 |
+
self.save( -2)
|
| 208 |
+
|
| 209 |
+
###################################################
|
| 210 |
+
def train( self, epoch):
|
| 211 |
+
|
| 212 |
+
model = self.model
|
| 213 |
+
cf = self.cf
|
| 214 |
+
|
| 215 |
+
model.mode( NetMode.train)
|
| 216 |
+
self.optimizer.zero_grad()
|
| 217 |
+
|
| 218 |
+
loss_total = [[] for i in range(len(cf.losses)) ]
|
| 219 |
+
std_dev_total = [[] for i in range(len(self.fields_prediction_idx)) ]
|
| 220 |
+
mse_loss_total = []
|
| 221 |
+
grad_loss_total = []
|
| 222 |
+
ctr = 0
|
| 223 |
+
|
| 224 |
+
self.optimizer.zero_grad()
|
| 225 |
+
time_start = time.time()
|
| 226 |
+
|
| 227 |
+
for batch_idx in range( model.len( NetMode.train)) :
|
| 228 |
+
|
| 229 |
+
batch_data = self.model.next()
|
| 230 |
+
_, _, _, tmksd_list, weight_list = batch_data[0]
|
| 231 |
+
with torch.autocast(device_type='cuda', dtype=torch.float16, enabled=cf.with_mixed_precision):
|
| 232 |
+
batch_data = self.prepare_batch( batch_data)
|
| 233 |
+
preds, _ = self.model_ddp( batch_data)
|
| 234 |
+
loss, mse_loss, losses = self.loss( preds, batch_idx, tmksd_list, weight_list)
|
| 235 |
+
|
| 236 |
+
self.grad_scaler.scale(loss).backward()
|
| 237 |
+
self.grad_scaler.step(self.optimizer)
|
| 238 |
+
self.grad_scaler.update()
|
| 239 |
+
|
| 240 |
+
self.optimizer.zero_grad()
|
| 241 |
+
|
| 242 |
+
[loss_total[idx].append( losses[key]) for idx, key in enumerate(losses)]
|
| 243 |
+
mse_loss_total.append( mse_loss.detach().cpu() )
|
| 244 |
+
grad_loss_total.append( loss.detach().cpu() )
|
| 245 |
+
[std_dev_total[idx].append( pred[1].detach().cpu()) for idx, pred in enumerate(preds)]
|
| 246 |
+
|
| 247 |
+
# logging
|
| 248 |
+
|
| 249 |
+
if int((batch_idx * cf.batch_size) / 8) > ctr :
|
| 250 |
+
|
| 251 |
+
# wandb logging
|
| 252 |
+
if cf.with_wandb and (0 == cf.par_rank) :
|
| 253 |
+
loss_dict = { "training loss": torch.mean( torch.tensor( mse_loss_total)),
|
| 254 |
+
"gradient loss": torch.mean( torch.tensor( grad_loss_total)) }
|
| 255 |
+
# log individual loss terms for individual fields
|
| 256 |
+
for idx, cur_loss in enumerate(loss_total) :
|
| 257 |
+
loss_name = self.cf.losses[idx]
|
| 258 |
+
lt = torch.tensor(cur_loss)
|
| 259 |
+
for i, field in enumerate(cf.fields_prediction) :
|
| 260 |
+
idx_name = loss_name + ', ' + field[0]
|
| 261 |
+
idx_std_name = 'stddev, ' + field[0]
|
| 262 |
+
loss_dict[idx_name] = torch.mean( lt[:,i]).cpu().detach()
|
| 263 |
+
loss_dict[idx_std_name] = torch.mean(torch.cat(std_dev_total[i],0)).cpu().detach()
|
| 264 |
+
wandb.log( loss_dict )
|
| 265 |
+
|
| 266 |
+
# console output
|
| 267 |
+
samples_sec = cf.batch_size / (time.time() - time_start)
|
| 268 |
+
str = 'epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:1.5f} : {:1.5f} :: {:1.5f} ({:2.2f} s/sec)'
|
| 269 |
+
print( str.format( epoch, batch_idx, model.len( NetMode.train),
|
| 270 |
+
100. * batch_idx/model.len(NetMode.train),
|
| 271 |
+
torch.mean( torch.tensor( grad_loss_total)),
|
| 272 |
+
torch.mean(torch.tensor(mse_loss_total)),
|
| 273 |
+
torch.mean( preds[0][1]), samples_sec ), flush=True)
|
| 274 |
+
|
| 275 |
+
# save model (use -2 as epoch to indicate latest, stored without epoch specification)
|
| 276 |
+
if batch_idx % cf.model_log_frequency == 0 :
|
| 277 |
+
self.save( -2)
|
| 278 |
+
|
| 279 |
+
# reset
|
| 280 |
+
loss_total = [[] for i in range(len(cf.losses)) ]
|
| 281 |
+
mse_loss_total = []
|
| 282 |
+
grad_loss_total = []
|
| 283 |
+
std_dev_total = [[] for i in range(len(self.fields_prediction_idx)) ]
|
| 284 |
+
|
| 285 |
+
ctr += 1
|
| 286 |
+
time_start = time.time()
|
| 287 |
+
|
| 288 |
+
# save gradients
|
| 289 |
+
if cf.save_grads and cf.with_wandb and (0 == cf.par_rank) :
|
| 290 |
+
|
| 291 |
+
dir_name = './grads/id{}'.format( cf.wandb_id)
|
| 292 |
+
if not os.path.exists(dir_name):
|
| 293 |
+
os.makedirs(dir_name)
|
| 294 |
+
|
| 295 |
+
rmsprop_ws = []
|
| 296 |
+
for k in range( len(self.optimizer.state_dict()['state']) ) :
|
| 297 |
+
rmsprop_ws.append(self.optimizer.state_dict()['state'][k]['exp_avg_sq'].mean().unsqueeze(0))
|
| 298 |
+
rmsprop_ws = torch.cat( rmsprop_ws)
|
| 299 |
+
fname = '{}/{}_epoch{}_rmsprop.npy'.format( dir_name, cf.wandb_id, epoch)
|
| 300 |
+
np.save( fname, rmsprop_ws.cpu().detach().numpy() )
|
| 301 |
+
|
| 302 |
+
idx = 0
|
| 303 |
+
for name, param in self.model.named_parameters():
|
| 304 |
+
if param.requires_grad :
|
| 305 |
+
fname = '{}/{}_epoch{}_{:05d}_{}_grad.npy'.format( dir_name, cf.wandb_id, epoch, idx,name)
|
| 306 |
+
np.save( fname, param.grad.cpu().detach().numpy() )
|
| 307 |
+
idx += 1
|
| 308 |
+
|
| 309 |
+
# clean memory
|
| 310 |
+
self.optimizer.zero_grad()
|
| 311 |
+
del batch_data, loss, loss_total, mse_loss_total, grad_loss_total, std_dev_total
|
| 312 |
+
|
| 313 |
+
###################################################
|
| 314 |
+
def profile( self):
|
| 315 |
+
|
| 316 |
+
model = self.model
|
| 317 |
+
cf = self.cf
|
| 318 |
+
|
| 319 |
+
model.mode( NetMode.train)
|
| 320 |
+
self.optimizer.zero_grad()
|
| 321 |
+
|
| 322 |
+
# See https://pytorch.org/tutorials/intermediate/tensorboard_profiler_tutorial.html
|
| 323 |
+
# for details on how to load and analyse report
|
| 324 |
+
# https://pytorch.org/blog/trace-analysis-for-masses/
|
| 325 |
+
|
| 326 |
+
# do for all par_ranks to avoid that they run out of sync
|
| 327 |
+
print( '---------------------------------')
|
| 328 |
+
print( 'Profiling:')
|
| 329 |
+
pname = './logs/profile_par_rank' + str(cf.par_rank) + '_' + cf.wandb_id + '/profile'
|
| 330 |
+
with torch.profiler.profile( activities=[torch.profiler.ProfilerActivity.CPU,
|
| 331 |
+
torch.profiler.ProfilerActivity.CUDA],
|
| 332 |
+
schedule=torch.profiler.schedule(wait=1, warmup=1, active=3, repeat=2),
|
| 333 |
+
on_trace_ready=torch.profiler.tensorboard_trace_handler(pname),
|
| 334 |
+
profile_memory=True, record_shapes=True, with_stack=True) as prof:
|
| 335 |
+
for batch_idx in range( 2 * (1+1+3) ) :
|
| 336 |
+
|
| 337 |
+
batch_data = self.model.next()
|
| 338 |
+
|
| 339 |
+
with torch.autocast(device_type='cuda',dtype=torch.float16, enabled=cf.with_mixed_precision):
|
| 340 |
+
batch_data = self.prepare_batch( batch_data)
|
| 341 |
+
preds, _ = self.model_ddp( batch_data)
|
| 342 |
+
loss, mse_loss, losses = self.loss( preds, batch_idx)
|
| 343 |
+
|
| 344 |
+
self.grad_scaler.scale(loss).backward()
|
| 345 |
+
self.grad_scaler.step(self.optimizer)
|
| 346 |
+
self.grad_scaler.update()
|
| 347 |
+
self.optimizer.zero_grad()
|
| 348 |
+
|
| 349 |
+
prof.step()
|
| 350 |
+
|
| 351 |
+
print( 'Profiling finished.')
|
| 352 |
+
print( '---------------------------------')
|
| 353 |
+
|
| 354 |
+
###################################################
|
| 355 |
+
def validate( self, epoch, BERT_test_strategy = 'BERT'):
|
| 356 |
+
|
| 357 |
+
cf = self.cf
|
| 358 |
+
BERT_strategy_train = cf.BERT_strategy
|
| 359 |
+
cf.BERT_strategy = BERT_test_strategy
|
| 360 |
+
self.model.mode( NetMode.test)
|
| 361 |
+
total_loss = 0.
|
| 362 |
+
total_losses = torch.zeros( len(self.fields_prediction_idx) )
|
| 363 |
+
test_len = 0
|
| 364 |
+
|
| 365 |
+
self.mode_test = True
|
| 366 |
+
|
| 367 |
+
# run test set evaluation
|
| 368 |
+
with torch.no_grad() :
|
| 369 |
+
for it in range( self.model.len( NetMode.test)) :
|
| 370 |
+
batch_data = self.model.next()
|
| 371 |
+
if cf.par_rank < cf.log_test_num_ranks :
|
| 372 |
+
# keep on cpu since it will otherwise clog up GPU memory
|
| 373 |
+
(sources, _ , targets, tmis_list, _) = batch_data[0]
|
| 374 |
+
log_sources = ( [source.detach().clone().cpu() for source in sources ],
|
| 375 |
+
[target.detach().clone().cpu() for target in targets ],
|
| 376 |
+
tmis_list)
|
| 377 |
+
|
| 378 |
+
with torch.autocast(device_type='cuda',dtype=torch.float16,enabled=cf.with_mixed_precision):
|
| 379 |
+
batch_data = self.prepare_batch( batch_data)
|
| 380 |
+
preds, atts = self.model( batch_data)
|
| 381 |
+
loss = torch.tensor( 0.)
|
| 382 |
+
ifield = 0
|
| 383 |
+
for pred, idx in zip( preds, self.fields_prediction_idx) :
|
| 384 |
+
|
| 385 |
+
target = self.targets[idx]
|
| 386 |
+
# hook for custom test loss
|
| 387 |
+
self.test_loss( pred, target)
|
| 388 |
+
# base line loss
|
| 389 |
+
cur_loss = self.MSELoss( pred[0], target = target ).cpu().item()
|
| 390 |
+
|
| 391 |
+
loss += cur_loss
|
| 392 |
+
total_losses[ifield] += cur_loss
|
| 393 |
+
ifield += 1
|
| 394 |
+
|
| 395 |
+
total_loss += loss
|
| 396 |
+
test_len += 1
|
| 397 |
+
|
| 398 |
+
# store detailed results on current test set for book keeping
|
| 399 |
+
if cf.par_rank < cf.log_test_num_ranks :
|
| 400 |
+
log_preds = [[p.detach().clone().cpu() for p in pred] for pred in preds]
|
| 401 |
+
self.log_validate( epoch, it, log_sources, log_preds)
|
| 402 |
+
if cf.attention:
|
| 403 |
+
self.log_attention( epoch, it, atts)
|
| 404 |
+
|
| 405 |
+
# average over all nodes
|
| 406 |
+
total_loss /= test_len * len(self.cf.fields_prediction)
|
| 407 |
+
total_losses /= test_len
|
| 408 |
+
|
| 409 |
+
if cf.with_ddp :
|
| 410 |
+
total_loss_cuda = total_loss.cuda()
|
| 411 |
+
total_losses_cuda = total_losses.cuda()
|
| 412 |
+
dist.all_reduce( total_loss_cuda, op=torch.distributed.ReduceOp.AVG )
|
| 413 |
+
dist.all_reduce( total_losses_cuda, op=torch.distributed.ReduceOp.AVG )
|
| 414 |
+
total_loss = total_loss_cuda.cpu()
|
| 415 |
+
total_losses = total_losses_cuda.cpu()
|
| 416 |
+
|
| 417 |
+
if 0 == cf.par_rank :
|
| 418 |
+
print( 'validation loss for strategy={} at epoch {} : {}'.format( BERT_test_strategy,
|
| 419 |
+
epoch, total_loss),
|
| 420 |
+
flush=True)
|
| 421 |
+
if cf.with_wandb and (0 == cf.par_rank) :
|
| 422 |
+
loss_dict = {"val. loss {}".format(BERT_test_strategy) : total_loss}
|
| 423 |
+
total_losses = total_losses.cpu().detach()
|
| 424 |
+
for i, field in enumerate(cf.fields_prediction) :
|
| 425 |
+
idx_name = 'val., {}, '.format(BERT_test_strategy) + field[0]
|
| 426 |
+
loss_dict[idx_name] = total_losses[i]
|
| 427 |
+
print( 'validation loss for {} : {}'.format( field[0], total_losses[i] ))
|
| 428 |
+
wandb.log( loss_dict)
|
| 429 |
+
batch_data = []
|
| 430 |
+
torch.cuda.empty_cache()
|
| 431 |
+
|
| 432 |
+
cf.BERT_strategy = BERT_strategy_train
|
| 433 |
+
self.mode_test = False
|
| 434 |
+
|
| 435 |
+
return total_loss
|
| 436 |
+
|
| 437 |
+
###################################################
|
| 438 |
+
def test_loss( self, pred, target) :
|
| 439 |
+
'''Hook for custom test loss'''
|
| 440 |
+
pass
|
| 441 |
+
|
| 442 |
+
###################################################
|
| 443 |
+
def loss( self, preds, batch_idx = 0, tmidx_list = None, weights_list = None) :
|
| 444 |
+
|
| 445 |
+
# TODO: move implementations to individual files
|
| 446 |
+
|
| 447 |
+
cf = self.cf
|
| 448 |
+
mse_loss_total = torch.tensor( 0.,)
|
| 449 |
+
losses = dict(zip(cf.losses,[[] for loss in cf.losses ]))
|
| 450 |
+
|
| 451 |
+
for pred, idx in zip( preds, self.fields_prediction_idx) :
|
| 452 |
+
target = self.targets[idx]
|
| 453 |
+
|
| 454 |
+
mse_loss = self.MSELoss( pred[0], target = target)
|
| 455 |
+
mse_loss_total += mse_loss.cpu().detach()
|
| 456 |
+
|
| 457 |
+
# MSE loss
|
| 458 |
+
if 'mse' in self.cf.losses :
|
| 459 |
+
losses['mse'].append( mse_loss)
|
| 460 |
+
|
| 461 |
+
# MSE loss
|
| 462 |
+
if 'mse_ensemble' in self.cf.losses :
|
| 463 |
+
loss_en = torch.tensor( 0., device=target.device)
|
| 464 |
+
for en in torch.transpose( pred[2], 1, 0) :
|
| 465 |
+
loss_en += self.MSELoss( en, target = target)
|
| 466 |
+
losses['mse_ensemble'].append( loss_en / pred[2].shape[1])
|
| 467 |
+
|
| 468 |
+
if 'weighted_mse' in self.cf.losses :
|
| 469 |
+
loss_en = torch.tensor( 0., device=target.device)
|
| 470 |
+
field_info = cf.fields[idx]
|
| 471 |
+
token_size = field_info[4]
|
| 472 |
+
|
| 473 |
+
weights = torch.Tensor(np.array([w for batch in weights_list[idx] for w in batch]))
|
| 474 |
+
weights = weights.view(*weights.shape, 1, 1).repeat(1, 1, token_size[0], token_size[2]).swapaxes(1, 2)
|
| 475 |
+
weights = weights.reshape([weights.shape[0], -1]).to(target.get_device())
|
| 476 |
+
|
| 477 |
+
for en in torch.transpose( pred[2], 1, 0) :
|
| 478 |
+
loss_en += weighted_mse( en, target, weights)
|
| 479 |
+
|
| 480 |
+
losses['weighted_mse'].append( loss_en / pred[2].shape[1])
|
| 481 |
+
|
| 482 |
+
# Generalized cross entroy loss for continuous distributions
|
| 483 |
+
if 'stats' in self.cf.losses :
|
| 484 |
+
stats_loss = Gaussian( target, pred[0], pred[1])
|
| 485 |
+
diff = (stats_loss-1.)
|
| 486 |
+
# stats_loss = 0.01 * torch.mean( diff * diff) + torch.mean( torch.sqrt(torch.abs( pred[1])) )
|
| 487 |
+
stats_loss = torch.mean( diff * diff) + torch.mean( torch.sqrt( torch.abs( pred[1])) )
|
| 488 |
+
losses['stats'].append( stats_loss)
|
| 489 |
+
|
| 490 |
+
# Generalized cross entroy loss for continuous distributions
|
| 491 |
+
if 'stats_area' in self.cf.losses :
|
| 492 |
+
diff = torch.abs( torch.special.erf( (target - pred[0]) / (pred[1] * pred[1])) )
|
| 493 |
+
stats_area = 0.2 * torch.mean( diff * diff) + torch.mean( torch.sqrt(torch.abs( pred[1])) )
|
| 494 |
+
losses['stats_area'].append( stats_area)
|
| 495 |
+
|
| 496 |
+
# CRPS score
|
| 497 |
+
if 'crps' in self.cf.losses :
|
| 498 |
+
crps_loss = torch.mean( CRPS( target, pred[0], pred[1]))
|
| 499 |
+
losses['crps'].append( crps_loss)
|
| 500 |
+
|
| 501 |
+
if 'kernel_crps' in self.cf.losses :
|
| 502 |
+
kcrps_loss = torch.mean( kernel_crps( target,torch.transpose( pred[2], 1, 0)))
|
| 503 |
+
losses['kernel_crps'].append( kcrps_loss)
|
| 504 |
+
|
| 505 |
+
#TODO: uncomment it and add it when running in debug mode
|
| 506 |
+
# field_losses = ""
|
| 507 |
+
# for ifield, field in enumerate(cf.fields):
|
| 508 |
+
# ifield_loss = 0
|
| 509 |
+
# for key in losses :
|
| 510 |
+
# ifield_loss += losses[key][ifield].to(self.device_out)
|
| 511 |
+
# ifield_loss /= len(losses.keys())
|
| 512 |
+
# field_losses += f"{field[0]}: {ifield_loss}; "
|
| 513 |
+
# print(field_losses, flush = True)
|
| 514 |
+
|
| 515 |
+
loss = torch.tensor( 0., device=self.device_out)
|
| 516 |
+
tot_weight = torch.tensor( 0., device=self.device_out)
|
| 517 |
+
for key in losses :
|
| 518 |
+
#print( 'LOSS : {} :: {}'.format( key, losses[key]))
|
| 519 |
+
for ifield, val in enumerate(losses[key]) :
|
| 520 |
+
loss += self.loss_weights[ifield] * val.to( self.device_out)
|
| 521 |
+
tot_weight += self.loss_weights[ifield]
|
| 522 |
+
loss /= tot_weight
|
| 523 |
+
mse_loss = mse_loss_total / len(self.cf.fields_prediction)
|
| 524 |
+
|
| 525 |
+
return loss, mse_loss, losses
|
| 526 |
+
|
| 527 |
+
####################################################################################################
|
| 528 |
+
class Trainer_BERT( Trainer_Base) :
|
| 529 |
+
|
| 530 |
+
###################################################
|
| 531 |
+
def __init__( self, cf, devices) :
|
| 532 |
+
|
| 533 |
+
Trainer_Base.__init__( self, cf, devices)
|
| 534 |
+
|
| 535 |
+
self.rng_seed = cf.rng_seed
|
| 536 |
+
if not self.rng_seed :
|
| 537 |
+
self.rng_seed = int(torch.randint( 100000000, (1,)))
|
| 538 |
+
# TODO: generate only rngs that are needed
|
| 539 |
+
ll = len(cf.fields) * 8 #len(cf.vertical_levels)
|
| 540 |
+
if cf.BERT_fields_synced :
|
| 541 |
+
self.rngs = [np.random.default_rng(self.rng_seed) for _ in range(ll)]
|
| 542 |
+
else :
|
| 543 |
+
self.rngs = [np.random.default_rng(self.rng_seed+i) for i in range(ll)]
|
| 544 |
+
|
| 545 |
+
# batch preprocessing to be done in loader (mainly for performance reasons since it's
|
| 546 |
+
# parallelized there)
|
| 547 |
+
self.pre_batch = functools.partial( prepare_batch_BERT_multifield, self.cf, self.rngs,
|
| 548 |
+
self.cf.fields, self.cf.BERT_strategy )
|
| 549 |
+
|
| 550 |
+
###################################################
|
| 551 |
+
def prepare_batch( self, xin) :
|
| 552 |
+
'''Move data to device and some additional final preprocessing before model eval'''
|
| 553 |
+
|
| 554 |
+
cf = self.cf
|
| 555 |
+
devs = self.devices
|
| 556 |
+
|
| 557 |
+
# unpack loader output
|
| 558 |
+
# xin[0] since BERT does not have targets
|
| 559 |
+
(sources, token_infos, targets, fields_tokens_masked_idx_list, _) = xin[0]
|
| 560 |
+
(self.sources_idxs, self.sources_info) = xin[2]
|
| 561 |
+
|
| 562 |
+
# network input
|
| 563 |
+
batch_data = [ ( sources[i].to( devs[ cf.fields[i][1][3] ], non_blocking=True),
|
| 564 |
+
self.tok_infos_trans(token_infos[i]).to( self.devices[0], non_blocking=True))
|
| 565 |
+
for i in range(len(sources)) ]
|
| 566 |
+
|
| 567 |
+
# store token number since BERT selects sub-cube (optionally)
|
| 568 |
+
self.num_tokens = []
|
| 569 |
+
for field_idx in range(len(batch_data)) :
|
| 570 |
+
self.num_tokens.append( list(batch_data[field_idx][0].shape[2:5]))
|
| 571 |
+
|
| 572 |
+
# target
|
| 573 |
+
self.targets = []
|
| 574 |
+
for ifield in self.fields_prediction_idx :
|
| 575 |
+
self.targets.append( targets[ifield].to( devs[cf.fields[ifield][1][3]], non_blocking=True ))
|
| 576 |
+
|
| 577 |
+
# idxs of masked tokens
|
| 578 |
+
tmi_out = [ ]
|
| 579 |
+
for i,tmi in enumerate(fields_tokens_masked_idx_list) :
|
| 580 |
+
cdev = devs[cf.fields[i][1][3]]
|
| 581 |
+
tmi_out += [ [torch.cat(tmi_l).to( cdev, non_blocking=True) for tmi_l in tmi] ]
|
| 582 |
+
self.tokens_masked_idx = tmi_out
|
| 583 |
+
|
| 584 |
+
return batch_data
|
| 585 |
+
|
| 586 |
+
###################################################
|
| 587 |
+
def encoder_to_decoder( self, embeds_layers) :
|
| 588 |
+
return ([embeds_layers[i][-1] for i in range(len(embeds_layers))] , embeds_layers )
|
| 589 |
+
|
| 590 |
+
###################################################
|
| 591 |
+
def decoder_to_tail( self, idx_pred, pred) :
|
| 592 |
+
'''Positional encoding of masked tokens for tail network evaluation'''
|
| 593 |
+
|
| 594 |
+
field_idx = self.fields_prediction_idx[idx_pred]
|
| 595 |
+
dev = self.devices[ self.cf.fields[field_idx][1][3] ]
|
| 596 |
+
target_idx = self.tokens_masked_idx[field_idx]
|
| 597 |
+
assert len(target_idx) > 0, 'no masked tokens but target variable'
|
| 598 |
+
|
| 599 |
+
# select "fixed" masked tokens for loss computation
|
| 600 |
+
|
| 601 |
+
# flatten token dimensions: remove space-time separation
|
| 602 |
+
pred = torch.flatten( pred, 2, 3).to( dev)
|
| 603 |
+
# extract masked token level by level
|
| 604 |
+
pred_masked = []
|
| 605 |
+
for lidx, level in enumerate(self.cf.fields[field_idx][2]) :
|
| 606 |
+
# select masked tokens, flattened along batch dimension for easier indexing and processing
|
| 607 |
+
pred_l = torch.flatten( pred[:,lidx], 0, 1)
|
| 608 |
+
pred_masked.append( pred_l[ target_idx[lidx] ])
|
| 609 |
+
|
| 610 |
+
# flatten along level dimension, for loss evaluation we effectively have level, batch, ...
|
| 611 |
+
# as ordering of dimensions
|
| 612 |
+
pred_masked = torch.cat( pred_masked, 0)
|
| 613 |
+
|
| 614 |
+
return pred_masked
|
| 615 |
+
|
| 616 |
+
###################################################
|
| 617 |
+
def log_validate( self, epoch, bidx, log_sources, log_preds) :
|
| 618 |
+
'''Hook for logging: output associated with concrete training strategy.'''
|
| 619 |
+
|
| 620 |
+
if not hasattr( self.cf, 'wandb_id') :
|
| 621 |
+
return
|
| 622 |
+
|
| 623 |
+
if 'forecast' in self.cf.BERT_strategy :
|
| 624 |
+
self.log_validate_forecast( epoch, bidx, log_sources, log_preds)
|
| 625 |
+
elif 'BERT' in self.cf.BERT_strategy or 'temporal_interpolation' == self.cf.BERT_strategy :
|
| 626 |
+
self.log_validate_BERT( epoch, bidx, log_sources, log_preds)
|
| 627 |
+
else :
|
| 628 |
+
assert False
|
| 629 |
+
|
| 630 |
+
###################################################
|
| 631 |
+
def log_validate_forecast( self, epoch, batch_idx, log_sources, log_preds) :
|
| 632 |
+
'''Logging for BERT_strategy=forecast.'''
|
| 633 |
+
|
| 634 |
+
cf = self.cf
|
| 635 |
+
|
| 636 |
+
# save source: remains identical so just save ones
|
| 637 |
+
(sources, targets, _) = log_sources
|
| 638 |
+
|
| 639 |
+
sources_out, targets_out, preds_out, ensembles_out = [ ], [ ], [ ], [ ]
|
| 640 |
+
batch_size = len(self.sources_info)
|
| 641 |
+
# reconstruct geo-coords (identical for all fields)
|
| 642 |
+
forecast_num_tokens = 1
|
| 643 |
+
if hasattr( cf, 'forecast_num_tokens') :
|
| 644 |
+
forecast_num_tokens = cf.forecast_num_tokens
|
| 645 |
+
|
| 646 |
+
coords = []
|
| 647 |
+
for fidx, field_info in enumerate(cf.fields) :
|
| 648 |
+
# reshape from tokens to contiguous physical field
|
| 649 |
+
num_levels = len(field_info[2])
|
| 650 |
+
source = detokenize( sources[fidx].cpu().detach().numpy())
|
| 651 |
+
# recover tokenized shape
|
| 652 |
+
target = detokenize( targets[fidx].cpu().detach().numpy().reshape( [ num_levels, -1,
|
| 653 |
+
forecast_num_tokens, *field_info[3][1:], *field_info[4] ]).swapaxes(0,1))
|
| 654 |
+
|
| 655 |
+
coords_b = []
|
| 656 |
+
|
| 657 |
+
for bidx in range(batch_size):
|
| 658 |
+
dates = self.sources_info[bidx][0]
|
| 659 |
+
lats = self.sources_info[bidx][1]
|
| 660 |
+
lons = self.sources_info[bidx][2]
|
| 661 |
+
dates_t = self.sources_info[bidx][0][ -forecast_num_tokens*field_info[4][0] : ]
|
| 662 |
+
|
| 663 |
+
lats_idx = self.sources_idxs[bidx][1]
|
| 664 |
+
lons_idx = self.sources_idxs[bidx][2]
|
| 665 |
+
|
| 666 |
+
for vidx, _ in enumerate(field_info[2]) :
|
| 667 |
+
normalizer, year_base = self.model.normalizer( fidx, vidx, lats_idx, lons_idx)
|
| 668 |
+
source[bidx,vidx] = denormalize( source[bidx,vidx], normalizer, dates, year_base)
|
| 669 |
+
target[bidx,vidx] = denormalize( target[bidx,vidx], normalizer, dates_t, year_base)
|
| 670 |
+
|
| 671 |
+
coords_b += [[dates, 90.-lats, lons, dates_t]]
|
| 672 |
+
|
| 673 |
+
# append
|
| 674 |
+
sources_out.append( [field_info[0], source])
|
| 675 |
+
targets_out.append( [field_info[0], target])
|
| 676 |
+
coords.append(coords_b)
|
| 677 |
+
|
| 678 |
+
# process predicted fields
|
| 679 |
+
for fidx, fn in enumerate(cf.fields_prediction) :
|
| 680 |
+
field_info = cf.fields[ self.fields_prediction_idx[fidx] ]
|
| 681 |
+
num_levels = len(field_info[2])
|
| 682 |
+
# predictions
|
| 683 |
+
pred = log_preds[fidx][0].cpu().detach().numpy()
|
| 684 |
+
pred = detokenize( pred.reshape( [ num_levels, -1,
|
| 685 |
+
forecast_num_tokens, *field_info[3][1:], *field_info[4] ]).swapaxes(0,1))
|
| 686 |
+
# ensemble
|
| 687 |
+
ensemble = log_preds[fidx][2].cpu().detach().numpy().swapaxes(0,1)
|
| 688 |
+
ensemble = detokenize( ensemble.reshape( [ cf.net_tail_num_nets, num_levels, -1,
|
| 689 |
+
forecast_num_tokens, *field_info[3][1:], *field_info[4] ]).swapaxes(1, 2)).swapaxes(0,1)
|
| 690 |
+
|
| 691 |
+
# denormalize
|
| 692 |
+
for bidx in range(batch_size) :
|
| 693 |
+
lats = self.sources_info[bidx][1]
|
| 694 |
+
lons = self.sources_info[bidx][2]
|
| 695 |
+
dates_t = self.sources_info[bidx][0][ -forecast_num_tokens*field_info[4][0] : ]
|
| 696 |
+
|
| 697 |
+
for vidx, vl in enumerate(field_info[2]) :
|
| 698 |
+
normalizer, year_base = self.model.normalizer( self.fields_prediction_idx[fidx], vidx, lats_idx, lons_idx)
|
| 699 |
+
pred[bidx,vidx] = denormalize( pred[bidx,vidx], normalizer, dates_t, year_base)
|
| 700 |
+
ensemble[bidx,:,vidx] = denormalize(ensemble[bidx,:,vidx], normalizer, dates_t, year_base)
|
| 701 |
+
|
| 702 |
+
# append
|
| 703 |
+
preds_out.append( [fn[0], pred])
|
| 704 |
+
ensembles_out.append( [fn[0], ensemble])
|
| 705 |
+
|
| 706 |
+
levels = np.array(cf.fields[0][2])
|
| 707 |
+
|
| 708 |
+
write_forecast( cf.wandb_id, epoch, batch_idx,
|
| 709 |
+
levels, sources_out,
|
| 710 |
+
targets_out, preds_out,
|
| 711 |
+
ensembles_out, coords)
|
| 712 |
+
|
| 713 |
+
###################################################
|
| 714 |
+
|
| 715 |
+
def split_data(self, data, idx_list, token_size) :
|
| 716 |
+
lens_batches = [[len(t) for t in tt] for tt in idx_list]
|
| 717 |
+
lens_levels = [torch.tensor( tt).sum() for tt in lens_batches]
|
| 718 |
+
data_b = torch.split( data, lens_levels)
|
| 719 |
+
# split according to batch
|
| 720 |
+
return [torch.split( data_b[vidx], lens) for vidx,lens in enumerate(lens_batches)]
|
| 721 |
+
|
| 722 |
+
def get_masked_data(self, field_info, data, idx_list, ensemble = False):
|
| 723 |
+
|
| 724 |
+
cf = self.cf
|
| 725 |
+
batch_size = len(self.sources_info)
|
| 726 |
+
num_levels = len(field_info[2])
|
| 727 |
+
num_tokens = field_info[3]
|
| 728 |
+
token_size = field_info[4]
|
| 729 |
+
data_b = self.split_data(data, idx_list, token_size)
|
| 730 |
+
|
| 731 |
+
# recover token shape
|
| 732 |
+
if ensemble:
|
| 733 |
+
return [[data_b[vidx][bidx].reshape([-1, cf.net_tail_num_nets, *token_size])
|
| 734 |
+
for bidx in range(batch_size)]
|
| 735 |
+
for vidx in range(num_levels)]
|
| 736 |
+
else:
|
| 737 |
+
return [[data_b[vidx][bidx].reshape([-1, *token_size]) for bidx in range(batch_size)]
|
| 738 |
+
for vidx in range(num_levels)]
|
| 739 |
+
|
| 740 |
+
###################################################
|
| 741 |
+
def log_validate_BERT( self, epoch, batch_idx, log_sources, log_preds) :
|
| 742 |
+
'''Logging for BERT_strategy=BERT.'''
|
| 743 |
+
|
| 744 |
+
cf = self.cf
|
| 745 |
+
batch_size = len(self.sources_info)
|
| 746 |
+
|
| 747 |
+
# save source: remains identical so just save ones
|
| 748 |
+
(sources, targets, tokens_masked_idx_list) = log_sources
|
| 749 |
+
|
| 750 |
+
sources_out, targets_out, preds_out, ensembles_out = [ ], [ ], [ ], [ ]
|
| 751 |
+
coords = []
|
| 752 |
+
|
| 753 |
+
for fidx, field_info in enumerate(cf.fields) :
|
| 754 |
+
|
| 755 |
+
# reconstruct coordinates
|
| 756 |
+
is_predicted = fidx in self.fields_prediction_idx
|
| 757 |
+
num_levels = len(field_info[2])
|
| 758 |
+
num_tokens = field_info[3]
|
| 759 |
+
token_size = field_info[4]
|
| 760 |
+
sources_b = detokenize( sources[fidx].numpy())
|
| 761 |
+
|
| 762 |
+
if is_predicted :
|
| 763 |
+
targets_b = self.get_masked_data(field_info, targets[fidx], tokens_masked_idx_list[fidx])
|
| 764 |
+
preds_mu_b = self.get_masked_data(field_info, log_preds[fidx][0], tokens_masked_idx_list[fidx])
|
| 765 |
+
preds_ens_b = self.get_masked_data(field_info, log_preds[fidx][2], tokens_masked_idx_list[fidx], ensemble = True)
|
| 766 |
+
|
| 767 |
+
# for all batch items
|
| 768 |
+
coords_b = []
|
| 769 |
+
for bidx in range(batch_size):
|
| 770 |
+
dates = self.sources_info[bidx][0]
|
| 771 |
+
lats = self.sources_info[bidx][1]
|
| 772 |
+
lons = self.sources_info[bidx][2]
|
| 773 |
+
|
| 774 |
+
lats_idx = self.sources_idxs[bidx][1]
|
| 775 |
+
lons_idx = self.sources_idxs[bidx][2]
|
| 776 |
+
|
| 777 |
+
# target etc are aliasing targets_b which simplifies bookkeeping below
|
| 778 |
+
if is_predicted :
|
| 779 |
+
target = [targets_b[vidx][bidx] for vidx in range(num_levels)]
|
| 780 |
+
pred_mu = [preds_mu_b[vidx][bidx] for vidx in range(num_levels)]
|
| 781 |
+
pred_ens = [preds_ens_b[vidx][bidx] for vidx in range(num_levels)]
|
| 782 |
+
|
| 783 |
+
coords_mskd_l = []
|
| 784 |
+
for vidx, _ in enumerate(field_info[2]) :
|
| 785 |
+
|
| 786 |
+
normalizer, year_base = self.model.normalizer( fidx, vidx, lats_idx, lons_idx)
|
| 787 |
+
sources_b[bidx,vidx] = denormalize(sources_b[bidx,vidx], normalizer, dates, year_base = 2021)
|
| 788 |
+
|
| 789 |
+
if is_predicted :
|
| 790 |
+
idx = tokens_masked_idx_list[fidx][vidx][bidx]
|
| 791 |
+
grid = np.flip(np.array( np.meshgrid( lons, lats)), axis = 0) #flip to have lat on pos 0 and lon on pos 1
|
| 792 |
+
grid_idx = np.flip(np.array( np.meshgrid( lons_idx, lats_idx)), axis = 0) #flip to have lat on pos 0 and lon on pos 1
|
| 793 |
+
|
| 794 |
+
# recover time dimension since idx assumes the full space-time cube
|
| 795 |
+
grid = torch.from_numpy( np.array( np.broadcast_to( grid,
|
| 796 |
+
shape = [token_size[0]*num_tokens[0], *grid.shape])).swapaxes(0,1))
|
| 797 |
+
grid_lats_toked = tokenize( grid[0], token_size).flatten( 0, 2)
|
| 798 |
+
grid_lons_toked = tokenize( grid[1], token_size).flatten( 0, 2)
|
| 799 |
+
|
| 800 |
+
idx_loc = idx - np.prod(num_tokens) * bidx
|
| 801 |
+
#save only useful info for each bidx. shape e.g. [n_bidx, lat_token_size*lat_num_tokens]
|
| 802 |
+
lats_mskd = np.array([np.unique(t) for t in grid_lats_toked[ idx_loc ].numpy()])
|
| 803 |
+
lons_mskd = np.array([np.unique(t) for t in grid_lons_toked[ idx_loc ].numpy()])
|
| 804 |
+
|
| 805 |
+
#time: idx ranges from 0->863 12x6x12
|
| 806 |
+
t_idx = (idx_loc // (num_tokens[1]*num_tokens[2])) * token_size[0]
|
| 807 |
+
#create range from t_idx-2 to t_idx
|
| 808 |
+
t_idx = np.array([np.arange(t, t + token_size[0]) for t in t_idx])
|
| 809 |
+
dates_mskd = dates[t_idx]
|
| 810 |
+
|
| 811 |
+
for ii,(t,p,e,da,la,lo) in enumerate(zip( target[vidx], pred_mu[vidx], pred_ens[vidx],
|
| 812 |
+
dates_mskd, lats_mskd, lons_mskd)) :
|
| 813 |
+
normalizer_ii = normalizer
|
| 814 |
+
if len(normalizer.shape) > 2: #local normalization
|
| 815 |
+
lats_mskd_idx = np.where(np.isin(lats,la))[0]
|
| 816 |
+
lons_mskd_idx = np.where(np.isin(lons,lo))[0]
|
| 817 |
+
#normalizer_ii = normalizer[:, :, lats_mskd_idx, lons_mskd_idx] problems in python 3.9
|
| 818 |
+
normalizer_ii = normalizer[:, :, lats_mskd_idx[0]:lats_mskd_idx[-1]+1, lons_mskd_idx[0]:lons_mskd_idx[-1]+1]
|
| 819 |
+
|
| 820 |
+
targets_b[vidx][bidx][ii] = denormalize(t, normalizer_ii, da, year_base)
|
| 821 |
+
preds_mu_b[vidx][bidx][ii] = denormalize(p, normalizer_ii, da, year_base)
|
| 822 |
+
preds_ens_b[vidx][bidx][ii] = denormalize(e, normalizer_ii, da, year_base)
|
| 823 |
+
|
| 824 |
+
coords_mskd_l += [[dates_mskd, 90.-lats_mskd, lons_mskd] ]
|
| 825 |
+
|
| 826 |
+
coords_b += [ [dates, 90. - lats, lons] + coords_mskd_l ]
|
| 827 |
+
|
| 828 |
+
coords += [ coords_b ]
|
| 829 |
+
fn = field_info[0]
|
| 830 |
+
sources_out.append( [fn, sources_b])
|
| 831 |
+
|
| 832 |
+
targets_out.append([fn, [[t.numpy(force=True) for t in t_v] for t_v in targets_b]] if is_predicted else [fn, []])
|
| 833 |
+
preds_out.append( [fn, [[p.numpy(force=True) for p in p_v] for p_v in preds_mu_b]] if is_predicted else [fn, []] )
|
| 834 |
+
ensembles_out.append( [fn, [[p.numpy(force=True) for p in p_v] for p_v in preds_ens_b]] if is_predicted else [fn, []] )
|
| 835 |
+
|
| 836 |
+
levels = [[np.array(l) for l in field[2]] for field in cf.fields]
|
| 837 |
+
write_BERT( cf.wandb_id, epoch, batch_idx,
|
| 838 |
+
levels, sources_out, targets_out,
|
| 839 |
+
preds_out, ensembles_out, coords )
|
| 840 |
+
|
| 841 |
+
######################################################
|
| 842 |
+
|
| 843 |
+
def log_attention( self, epoch, bidx, attention) :
|
| 844 |
+
'''Hook for logging: output attention maps.'''
|
| 845 |
+
cf = self.cf
|
| 846 |
+
|
| 847 |
+
attn_out = []
|
| 848 |
+
for fidx, field_info in enumerate(cf.fields) :
|
| 849 |
+
|
| 850 |
+
# coordinates
|
| 851 |
+
coords_b = []
|
| 852 |
+
for bidx in range(batch_size):
|
| 853 |
+
dates = self.sources_info[bidx][0]
|
| 854 |
+
lats = 90. - self.sources_info[bidx][1]
|
| 855 |
+
lons = self.sources_info[bidx][2]
|
| 856 |
+
coords_b += [ [dates, lats, lons] ]
|
| 857 |
+
|
| 858 |
+
is_predicted = fidx in self.fields_prediction_idx
|
| 859 |
+
attn_out.append([field_info[0], attention[fidx]] if is_predicted else [fn, []])
|
| 860 |
+
|
| 861 |
+
levels = [[np.array(l) for l in field[2]] for field in cf.fields]
|
| 862 |
+
write_attention(cf.wandb_id, epoch,
|
| 863 |
+
bidx, levels, attn_out, coords_b )
|
vendor/atmorep-official/atmorep/datasets/__init__.py
ADDED
|
File without changes
|
vendor/atmorep-official/atmorep/datasets/data_writer.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
import xarray as xr
|
| 19 |
+
import zarr
|
| 20 |
+
import atmorep.config.config as config
|
| 21 |
+
|
| 22 |
+
def write_item(ds_field, name_idx, data, levels, coords, name = 'sample' ):
|
| 23 |
+
ds_batch_item = ds_field.create_group( f'{name}={name_idx:05d}' )
|
| 24 |
+
ds_batch_item.create_dataset( 'data', data=data)
|
| 25 |
+
ds_batch_item.create_dataset( 'ml', data=levels)
|
| 26 |
+
ds_batch_item.create_dataset( 'datetime', data=coords[0].astype('datetime64[ns]'))
|
| 27 |
+
ds_batch_item.create_dataset( 'lat', data=np.array(coords[1]).astype(np.float32))
|
| 28 |
+
ds_batch_item.create_dataset( 'lon', data=np.array(coords[2]).astype(np.float32))
|
| 29 |
+
return ds_batch_item
|
| 30 |
+
|
| 31 |
+
####################################################################################################
|
| 32 |
+
def write_forecast( model_id, epoch, batch_idx, levels, sources,
|
| 33 |
+
targets, preds, ensembles, coords,
|
| 34 |
+
zarr_store_type = 'ZipStore' ) :
|
| 35 |
+
'''
|
| 36 |
+
sources : num_fields x [field name , data]
|
| 37 |
+
targets :
|
| 38 |
+
preds, ensemble share coords with targets
|
| 39 |
+
'''
|
| 40 |
+
sources_coords = [[c[:3] for c in coord_field ] for coord_field in coords]
|
| 41 |
+
targets_coords = [[[c[-1], c[1], c[2]] for c in coord_field ] for coord_field in coords]
|
| 42 |
+
fname = f'{config.path_results}/id{model_id}/results_id{model_id}_epoch{epoch:05d}' + '_{}.zarr'
|
| 43 |
+
|
| 44 |
+
zarr_store = getattr( zarr, zarr_store_type)
|
| 45 |
+
|
| 46 |
+
store_source = zarr_store( fname.format( 'source'))
|
| 47 |
+
exp_source = zarr.group(store=store_source)
|
| 48 |
+
|
| 49 |
+
for fidx, field in enumerate(sources) :
|
| 50 |
+
ds_field = exp_source.require_group( f'{field[0]}')
|
| 51 |
+
batch_size = field[1].shape[0]
|
| 52 |
+
for bidx in range( field[1].shape[0]) :
|
| 53 |
+
sample = batch_idx * batch_size + bidx
|
| 54 |
+
write_item(ds_field, sample, field[1][bidx], levels, sources_coords[fidx][bidx])
|
| 55 |
+
store_source.close()
|
| 56 |
+
|
| 57 |
+
store_target = zarr_store( fname.format( 'target'))
|
| 58 |
+
exp_target = zarr.group(store=store_target)
|
| 59 |
+
for fidx, field in enumerate(targets) :
|
| 60 |
+
ds_field = exp_target.require_group( f'{field[0]}')
|
| 61 |
+
batch_size = field[1].shape[0]
|
| 62 |
+
for bidx in range( field[1].shape[0]) :
|
| 63 |
+
sample = batch_idx * batch_size + bidx
|
| 64 |
+
write_item(ds_field, sample, field[1][bidx], levels, targets_coords[fidx][bidx])
|
| 65 |
+
store_target.close()
|
| 66 |
+
|
| 67 |
+
store_pred = zarr_store( fname.format( 'pred'))
|
| 68 |
+
exp_pred = zarr.group(store=store_pred)
|
| 69 |
+
for fidx, field in enumerate(preds) :
|
| 70 |
+
ds_field = exp_pred.require_group( f'{field[0]}')
|
| 71 |
+
batch_size = field[1].shape[0]
|
| 72 |
+
for bidx in range( field[1].shape[0]) :
|
| 73 |
+
sample = batch_idx * batch_size + bidx
|
| 74 |
+
write_item(ds_field, sample, field[1][bidx], levels, targets_coords[fidx][bidx])
|
| 75 |
+
store_pred.close()
|
| 76 |
+
|
| 77 |
+
store_ens = zarr_store( fname.format( 'ens'))
|
| 78 |
+
exp_ens = zarr.group(store=store_ens)
|
| 79 |
+
for fidx, field in enumerate(ensembles) :
|
| 80 |
+
ds_field = exp_ens.require_group( f'{field[0]}')
|
| 81 |
+
batch_size = field[1].shape[0]
|
| 82 |
+
for bidx in range( field[1].shape[0]) :
|
| 83 |
+
sample = batch_idx * batch_size + bidx
|
| 84 |
+
write_item(ds_field, sample, field[1][bidx], levels, targets_coords[fidx][bidx])
|
| 85 |
+
store_ens.close()
|
| 86 |
+
|
| 87 |
+
####################################################################################################
|
| 88 |
+
def write_BERT( model_id, epoch, batch_idx, levels, sources,
|
| 89 |
+
targets, preds, ensembles, coords,
|
| 90 |
+
zarr_store_type = 'ZipStore' ) :
|
| 91 |
+
|
| 92 |
+
'''
|
| 93 |
+
sources : num_fields x [field name , data]
|
| 94 |
+
targets :
|
| 95 |
+
preds, ensemble share coords with targets
|
| 96 |
+
'''
|
| 97 |
+
|
| 98 |
+
sources_coords = [[c[:3] for c in coord_field ] for coord_field in coords]
|
| 99 |
+
targets_coords = [[c[3:] for c in coord_field ] for coord_field in coords]
|
| 100 |
+
|
| 101 |
+
fname = f'{config.path_results}/id{model_id}/results_id{model_id}_epoch{epoch:05d}' + '_{}.zarr'
|
| 102 |
+
|
| 103 |
+
zarr_store = getattr( zarr, zarr_store_type)
|
| 104 |
+
|
| 105 |
+
store_source = zarr_store( fname.format( 'source'))
|
| 106 |
+
exp_source = zarr.group(store=store_source)
|
| 107 |
+
for fidx, field in enumerate(sources) :
|
| 108 |
+
ds_field = exp_source.require_group( f'{field[0]}')
|
| 109 |
+
batch_size = field[1].shape[0]
|
| 110 |
+
for bidx in range( field[1].shape[0]) :
|
| 111 |
+
sample = batch_idx * batch_size + bidx
|
| 112 |
+
write_item(ds_field, sample, field[1][bidx], levels[fidx], sources_coords[fidx][bidx] )
|
| 113 |
+
store_source.close()
|
| 114 |
+
|
| 115 |
+
store_target = zarr_store( fname.format( 'target'))
|
| 116 |
+
exp_target = zarr.group(store=store_target)
|
| 117 |
+
for fidx, field in enumerate(targets) :
|
| 118 |
+
if 0 == len(field[1]) : # skip fields that were not predicted
|
| 119 |
+
continue
|
| 120 |
+
batch_size = len(field[1][0])
|
| 121 |
+
ds_field = exp_target.require_group( f'{field[0]}')
|
| 122 |
+
for bidx in range( len(field[1][0])) :
|
| 123 |
+
sample = batch_idx * batch_size + bidx
|
| 124 |
+
ds_target_b = ds_field.create_group( f'sample={sample:05d}')
|
| 125 |
+
for vidx in range(len(levels[fidx])) :
|
| 126 |
+
write_item(ds_target_b, levels[fidx][vidx], field[1][vidx][bidx], levels[fidx][vidx], targets_coords[fidx][bidx][vidx], name = 'ml' )
|
| 127 |
+
store_target.close()
|
| 128 |
+
|
| 129 |
+
store_pred = zarr_store( fname.format( 'pred'))
|
| 130 |
+
exp_pred = zarr.group(store=store_pred)
|
| 131 |
+
for fidx, field in enumerate(preds) :
|
| 132 |
+
if 0 == len(field[1]) : # skip fields that were not predicted
|
| 133 |
+
continue
|
| 134 |
+
batch_size = len(field[1][0])
|
| 135 |
+
ds_pred = exp_pred.require_group( f'{field[0]}')
|
| 136 |
+
for bidx in range( len(field[1][0])) :
|
| 137 |
+
sample = batch_idx * batch_size + bidx
|
| 138 |
+
ds_pred_b = ds_pred.create_group( f'sample={sample:05d}')
|
| 139 |
+
for vidx in range(len(levels[fidx])) :
|
| 140 |
+
write_item(ds_pred_b, levels[fidx][vidx], field[1][vidx][bidx], levels[fidx][vidx],
|
| 141 |
+
targets_coords[fidx][bidx][vidx], name = 'ml' )
|
| 142 |
+
store_pred.close()
|
| 143 |
+
|
| 144 |
+
store_ens = zarr_store( fname.format( 'ens'))
|
| 145 |
+
exp_ens = zarr.group(store=store_ens)
|
| 146 |
+
for fidx, field in enumerate(ensembles) :
|
| 147 |
+
if 0 == len(field[1]) : # skip fields that were not predicted
|
| 148 |
+
continue
|
| 149 |
+
batch_size = len(field[1][0])
|
| 150 |
+
ds_ens = exp_ens.require_group( f'{field[0]}')
|
| 151 |
+
for bidx in range( len(field[1][0])) :
|
| 152 |
+
sample = batch_idx * batch_size + bidx
|
| 153 |
+
ds_ens_b = ds_ens.create_group( f'sample={sample:05d}')
|
| 154 |
+
for vidx in range(len(levels[fidx])) :
|
| 155 |
+
write_item(ds_ens_b, levels[fidx][vidx], field[1][vidx][bidx], levels[fidx][vidx],
|
| 156 |
+
targets_coords[fidx][bidx][vidx], name = 'ml' )
|
| 157 |
+
store_ens.close()
|
| 158 |
+
|
| 159 |
+
####################################################################################################
|
| 160 |
+
def write_attention(model_id, epoch, batch_idx, levels, attn, coords, zarr_store_type = 'ZipStore' ) :
|
| 161 |
+
|
| 162 |
+
fname = f'{config.path_results}/id{model_id}/results_id{model_id}_epoch{epoch:05d}' + '_{}.zarr'
|
| 163 |
+
zarr_store = getattr( zarr, zarr_store_type)
|
| 164 |
+
|
| 165 |
+
store_attn = zarr_store( fname.format( 'attention'))
|
| 166 |
+
exp_attn = zarr.group(store=store_attn)
|
| 167 |
+
|
| 168 |
+
for fidx, atts_f in enumerate(attn) :
|
| 169 |
+
ds_field = exp_attn.require_group( f'{atts_f[0]}')
|
| 170 |
+
ds_field_b = ds_field.require_group( f'batch={batch_idx:05d}')
|
| 171 |
+
for lidx, atts_f_l in enumerate(atts_f[1]) : # layer in the network
|
| 172 |
+
ds_f_l = ds_field_b.require_group( f'layer={lidx:05d}')
|
| 173 |
+
ds_f_l.create_dataset( 'ml', data=levels[fidx])
|
| 174 |
+
ds_f_l.create_dataset( 'datetime', data=coords[0][fidx])
|
| 175 |
+
ds_f_l.create_dataset( 'lat', data=coords[1][fidx])
|
| 176 |
+
ds_f_l.create_dataset( 'lon', data=coords[2][fidx])
|
| 177 |
+
ds_f_l_h = ds_f_l.require_group('heads')
|
| 178 |
+
for hidx, atts_f_l_head in enumerate(atts_f_l) : # number of attention head
|
| 179 |
+
if atts_f_l_head != None :
|
| 180 |
+
ds_f_l_h.create_dataset(f'{hidx}', data=atts_f_l_head.numpy() )
|
| 181 |
+
store_attn.close()
|
vendor/atmorep-official/atmorep/datasets/multifield_data_sampler.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
import dask.config as dc
|
| 18 |
+
import dask.array as da
|
| 19 |
+
import torch
|
| 20 |
+
import numpy as np
|
| 21 |
+
import zarr
|
| 22 |
+
import pandas as pd
|
| 23 |
+
from datetime import datetime
|
| 24 |
+
import time
|
| 25 |
+
import os
|
| 26 |
+
|
| 27 |
+
from atmorep.datasets.normalizer import normalize
|
| 28 |
+
from atmorep.utils.utils import tokenize, get_weights
|
| 29 |
+
|
| 30 |
+
class MultifieldDataSampler( torch.utils.data.IterableDataset):
|
| 31 |
+
|
| 32 |
+
###################################################
|
| 33 |
+
def __init__( self, file_path, fields, years, batch_size, pre_batch, n_size,
|
| 34 |
+
num_samples, with_shuffle = False, time_sampling = 1, with_source_idxs = False, compute_weights = False,
|
| 35 |
+
fields_targets = None, pre_batch_targets = None ) :
|
| 36 |
+
'''
|
| 37 |
+
Data set for single dynamic field at an arbitrary number of vertical levels
|
| 38 |
+
|
| 39 |
+
nsize : neighborhood in (tsteps, deg_lat, deg_lon)
|
| 40 |
+
'''
|
| 41 |
+
super( MultifieldDataSampler).__init__()
|
| 42 |
+
|
| 43 |
+
self.fields = fields
|
| 44 |
+
self.batch_size = batch_size
|
| 45 |
+
self.n_size = n_size
|
| 46 |
+
self.num_samples = num_samples
|
| 47 |
+
self.with_source_idxs = with_source_idxs
|
| 48 |
+
self.compute_weights = compute_weights
|
| 49 |
+
self.with_shuffle = with_shuffle
|
| 50 |
+
self.pre_batch = pre_batch
|
| 51 |
+
|
| 52 |
+
assert os.path.exists(file_path), f"File path {file_path} does not exist"
|
| 53 |
+
self.ds = zarr.open( file_path)
|
| 54 |
+
|
| 55 |
+
self.dask_array_data = da.from_zarr(self.ds['data'])
|
| 56 |
+
self.dask_array_sfc = da.from_zarr(self.ds['data_sfc'])
|
| 57 |
+
|
| 58 |
+
self.ds_global = self.ds.attrs['is_global']
|
| 59 |
+
|
| 60 |
+
self.lats = np.array( self.ds['lats'])
|
| 61 |
+
self.lons = np.array( self.ds['lons'])
|
| 62 |
+
|
| 63 |
+
sh = self.ds['data'].shape
|
| 64 |
+
st = self.ds['time'].shape
|
| 65 |
+
self.ds_len = st[0]
|
| 66 |
+
print( f'self.ds[\'data\'] : {sh} :: {st}')
|
| 67 |
+
print( f'self.lats : {self.lats.shape}', flush=True)
|
| 68 |
+
print( f'self.lons : {self.lons.shape}', flush=True)
|
| 69 |
+
self.fields_idxs = []
|
| 70 |
+
|
| 71 |
+
self.time_sampling = time_sampling
|
| 72 |
+
self.range_lat = np.array( self.lats[ [0,-1] ])
|
| 73 |
+
self.range_lon = np.array( self.lons[ [0,-1] ])
|
| 74 |
+
self.res = np.array(self.ds.attrs['res'])
|
| 75 |
+
self.year_base = self.ds['time'][0].astype(datetime).year
|
| 76 |
+
|
| 77 |
+
# ensure neighborhood does not exceed domain (either at pole or for finite domains)
|
| 78 |
+
self.range_lat += np.array([n_size[1] / 2., -n_size[1] / 2.])
|
| 79 |
+
# lon: no change for periodic case
|
| 80 |
+
if self.ds_global < 1.:
|
| 81 |
+
self.range_lon += np.array([n_size[2]/2., -n_size[2]/2.])
|
| 82 |
+
|
| 83 |
+
# data normalizers
|
| 84 |
+
self.normalizers = []
|
| 85 |
+
for ifield, field_info in enumerate(fields) :
|
| 86 |
+
corr_type = 'global' if len(field_info) <= 6 else field_info[6]
|
| 87 |
+
nf_name = 'global_norm' if corr_type == 'global' else 'norm'
|
| 88 |
+
self.normalizers.append( [] )
|
| 89 |
+
for vl in field_info[2]:
|
| 90 |
+
if vl == 0:
|
| 91 |
+
field_idx = self.ds.attrs['fields_sfc'].index( field_info[0])
|
| 92 |
+
n_name = f'normalization/{nf_name}_sfc'
|
| 93 |
+
self.normalizers[ifield] += [self.ds[n_name].oindex[ :, :, field_idx]]
|
| 94 |
+
else:
|
| 95 |
+
vl_idx = self.ds.attrs['levels'].index(vl)
|
| 96 |
+
field_idx = self.ds.attrs['fields'].index( field_info[0])
|
| 97 |
+
n_name = f'normalization/{nf_name}'
|
| 98 |
+
self.normalizers[ifield] += [self.ds[n_name].oindex[ :, :, field_idx, vl_idx]]
|
| 99 |
+
|
| 100 |
+
# extract indices for selected years
|
| 101 |
+
self.times = pd.DatetimeIndex( self.ds['time'])
|
| 102 |
+
idxs_years = self.times.year == years[0]
|
| 103 |
+
for year in years[1:] :
|
| 104 |
+
idxs_years = np.logical_or( idxs_years, self.times.year == year)
|
| 105 |
+
self.idxs_years = np.where( idxs_years)[0]
|
| 106 |
+
|
| 107 |
+
self.num_samples = min( self.num_samples, self.idxs_years.shape[0])
|
| 108 |
+
|
| 109 |
+
###################################################
|
| 110 |
+
def shuffle( self) :
|
| 111 |
+
|
| 112 |
+
worker_info = torch.utils.data.get_worker_info()
|
| 113 |
+
rng_seed = None
|
| 114 |
+
if worker_info is not None :
|
| 115 |
+
rng_seed = int(time.time()) // (worker_info.id+1) + worker_info.id
|
| 116 |
+
|
| 117 |
+
rng = np.random.default_rng( rng_seed)
|
| 118 |
+
self.idxs_perm_t = rng.permutation( self.idxs_years)[ : self.num_samples // self.batch_size]
|
| 119 |
+
|
| 120 |
+
lats = rng.random(self.num_samples) * (self.range_lat[1] - self.range_lat[0]) +self.range_lat[0]
|
| 121 |
+
lons = rng.random(self.num_samples) * (self.range_lon[1] - self.range_lon[0]) +self.range_lon[0]
|
| 122 |
+
|
| 123 |
+
# align with grid
|
| 124 |
+
res_inv = 1.0 / self.res * 1.00001
|
| 125 |
+
lats = self.res[0] * np.round( lats * res_inv[0])
|
| 126 |
+
lons = self.res[1] * np.round( lons * res_inv[1])
|
| 127 |
+
|
| 128 |
+
self.idxs_perm = np.stack( [lats, lons], axis=1)
|
| 129 |
+
|
| 130 |
+
###################################################
|
| 131 |
+
def __iter__(self):
|
| 132 |
+
|
| 133 |
+
if self.with_shuffle :
|
| 134 |
+
self.shuffle()
|
| 135 |
+
|
| 136 |
+
lats, lons = self.lats, self.lons
|
| 137 |
+
ts, n_size = self.time_sampling, self.n_size
|
| 138 |
+
ns_2 = np.array(self.n_size) / 2.
|
| 139 |
+
res = self.res
|
| 140 |
+
|
| 141 |
+
iter_start, iter_end = self.worker_workset()
|
| 142 |
+
|
| 143 |
+
for bidx in range( iter_start, iter_end) :
|
| 144 |
+
|
| 145 |
+
sources, token_infos = [[] for _ in self.fields], [[] for _ in self.fields]
|
| 146 |
+
sources_infos, source_idxs = [], []
|
| 147 |
+
|
| 148 |
+
i_bidx = self.idxs_perm_t[bidx]
|
| 149 |
+
idxs_t = list(np.arange( i_bidx - n_size[0]*ts, i_bidx, ts, dtype=np.int64))
|
| 150 |
+
# data_tt_sfc = self.ds['data_sfc'].oindex[idxs_t]
|
| 151 |
+
# data_tt = self.ds['data'].oindex[idxs_t]
|
| 152 |
+
with dc.set(**{'array.slicing.split_large_chunks': True}):
|
| 153 |
+
data_tt_sfc = self.dask_array_sfc[idxs_t].compute()
|
| 154 |
+
data_tt = self.dask_array_data[idxs_t].compute()
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
for sidx in range(self.batch_size) :
|
| 158 |
+
|
| 159 |
+
idx = self.idxs_perm[bidx*self.batch_size+sidx]
|
| 160 |
+
# slight asymetry with offset by res/2 is required to match desired token count
|
| 161 |
+
lat_ran = np.where(np.logical_and(lats>idx[0]-ns_2[1]-res[0]/2.,lats<idx[0]+ns_2[1]))[0]
|
| 162 |
+
# handle periodicity of lon
|
| 163 |
+
assert not ((idx[1]-ns_2[2]) < 0. and (idx[1]+ns_2[2]) > 360.)
|
| 164 |
+
il, ir = (idx[1]-ns_2[2]-res[1]/2., idx[1]+ns_2[2])
|
| 165 |
+
if il < 0. :
|
| 166 |
+
lon_ran = np.concatenate( [np.where( lons > il+360)[0], np.where(lons < ir)[0]], 0)
|
| 167 |
+
elif ir > 360. :
|
| 168 |
+
lon_ran = np.concatenate( [np.where( lons > il)[0], np.where(lons < ir-360)[0]], 0)
|
| 169 |
+
else :
|
| 170 |
+
lon_ran = np.where(np.logical_and( lons > il, lons < ir))[0]
|
| 171 |
+
|
| 172 |
+
sources_infos += [ [ self.ds['time'][ idxs_t ].astype(datetime),
|
| 173 |
+
self.lats[lat_ran], self.lons[lon_ran], self.res ] ]
|
| 174 |
+
|
| 175 |
+
if self.with_source_idxs :
|
| 176 |
+
source_idxs += [ (idxs_t, lat_ran, lon_ran) ]
|
| 177 |
+
|
| 178 |
+
# extract data
|
| 179 |
+
for ifield, field_info in enumerate(self.fields):
|
| 180 |
+
source_lvl, tok_info_lvl = [], []
|
| 181 |
+
tok_size = field_info[4]
|
| 182 |
+
num_tokens = field_info[3]
|
| 183 |
+
corr_type = 'global' if len(field_info) <= 6 else field_info[6]
|
| 184 |
+
|
| 185 |
+
for ilevel, vl in enumerate(field_info[2]):
|
| 186 |
+
if vl == 0 : #surface level
|
| 187 |
+
field_idx = self.ds.attrs['fields_sfc'].index( field_info[0])
|
| 188 |
+
data_t = data_tt_sfc[ :, field_idx ]
|
| 189 |
+
else :
|
| 190 |
+
field_idx = self.ds.attrs['fields'].index( field_info[0])
|
| 191 |
+
vl_idx = self.ds.attrs['levels'].index(vl)
|
| 192 |
+
data_t = data_tt[ :, field_idx, vl_idx ]
|
| 193 |
+
|
| 194 |
+
source_data, tok_info = [], []
|
| 195 |
+
# extract data, normalize and tokenize
|
| 196 |
+
cdata = data_t[ ... , lat_ran[:,np.newaxis], lon_ran[np.newaxis,:]]
|
| 197 |
+
|
| 198 |
+
normalizer = self.normalizers[ifield][ilevel]
|
| 199 |
+
|
| 200 |
+
if corr_type != 'global':
|
| 201 |
+
#normalizer = normalizer[ ... , lat_ran[:,np.newaxis], lon_ran[np.newaxis,:]]
|
| 202 |
+
if lat_ran[0] < lat_ran[-1] and lon_ran[0] < lon_ran[-1]:
|
| 203 |
+
lat_max, lat_min = max(lat_ran), min(lat_ran)
|
| 204 |
+
lon_max, lon_min = max(lon_ran), min(lon_ran)
|
| 205 |
+
normalizer = normalizer[:,:,lat_min:lat_max+1,lon_min:lon_max+1]
|
| 206 |
+
#normalizer_vu = normalizer[:,:,lat_min:lat_max+1,lon_min:lon_max+1]
|
| 207 |
+
#cdata = normalize(cdata, normalizer_vu, sources_infos[-1][0], year_base = self.year_base)
|
| 208 |
+
else:
|
| 209 |
+
normalizer = normalizer[ ... , lat_ran[:,np.newaxis], lon_ran[np.newaxis,:]]
|
| 210 |
+
#cdata = normalize(cdata, normalizer, sources_infos[-1][0], year_base = self.year_base)
|
| 211 |
+
#else:
|
| 212 |
+
cdata = normalize(cdata, normalizer, sources_infos[-1][0], year_base = self.year_base)
|
| 213 |
+
|
| 214 |
+
source_data = tokenize( torch.from_numpy( cdata), tok_size )
|
| 215 |
+
# token_infos uses center of the token: *last* datetime and center in space
|
| 216 |
+
dates = self.ds['time'][ idxs_t ].astype(datetime)
|
| 217 |
+
cdates = dates[tok_size[0]-1::tok_size[0]]
|
| 218 |
+
# use -1 is to start days from 0
|
| 219 |
+
dates = [(d.year, d.timetuple().tm_yday-1, d.hour) for d in cdates]
|
| 220 |
+
lats_sidx = self.lats[lat_ran][ tok_size[1]//2 :: tok_size[1] ]
|
| 221 |
+
lons_sidx = self.lons[lon_ran][ tok_size[2]//2 :: tok_size[2] ]
|
| 222 |
+
# tensor product for token_infos
|
| 223 |
+
tok_info += [[[[[ year, day, hour, vl, lat, lon, vl, self.res[0]] for lon in lons_sidx]
|
| 224 |
+
for lat in lats_sidx]
|
| 225 |
+
for (year, day, hour) in dates]]
|
| 226 |
+
|
| 227 |
+
source_lvl += [ source_data ]
|
| 228 |
+
tok_info_lvl += [ torch.tensor(tok_info, dtype=torch.float32).flatten( 1, -2)]
|
| 229 |
+
sources[ifield] += [ torch.stack(source_lvl, 0) ]
|
| 230 |
+
token_infos[ifield] += [ torch.stack(tok_info_lvl, 0) ]
|
| 231 |
+
|
| 232 |
+
# concatenate batches
|
| 233 |
+
sources = [torch.stack(sources_field).transpose(1,0) for sources_field in sources]
|
| 234 |
+
token_infos = [torch.stack(tis_field).transpose(1,0) for tis_field in token_infos]
|
| 235 |
+
sources = self.pre_batch( sources, token_infos )
|
| 236 |
+
|
| 237 |
+
tmidx_list = sources[-1]
|
| 238 |
+
weights_idx_list = []
|
| 239 |
+
if self.compute_weights:
|
| 240 |
+
for ifield, field_info in enumerate(self.fields):
|
| 241 |
+
weights = []
|
| 242 |
+
for ilevel, vl in enumerate(field_info[2]):
|
| 243 |
+
for ibatch in range(self.batch_size):
|
| 244 |
+
|
| 245 |
+
lats_idx = source_idxs[ibatch][1]
|
| 246 |
+
lons_idx = source_idxs[ibatch][2]
|
| 247 |
+
|
| 248 |
+
idx_base = tmidx_list[ifield][ilevel][ibatch]
|
| 249 |
+
idx_loc = idx_base - np.prod(num_tokens) * ibatch
|
| 250 |
+
|
| 251 |
+
grid = np.flip(np.array( np.meshgrid( lons_idx, lats_idx)), axis = 0) #flip to have lat on pos 0 and lon on pos 1
|
| 252 |
+
grid = torch.from_numpy( np.array( np.broadcast_to( grid,
|
| 253 |
+
shape = [tok_size[0]*num_tokens[0], *grid.shape])).swapaxes(0,1))
|
| 254 |
+
|
| 255 |
+
grid_lats_toked = tokenize( grid[0], tok_size).flatten( 0, 2)
|
| 256 |
+
|
| 257 |
+
lats_mskd_b = np.array([np.unique(t) for t in grid_lats_toked[ idx_loc ].numpy()])
|
| 258 |
+
|
| 259 |
+
weights.append([get_weights(la) for la in lats_mskd_b])
|
| 260 |
+
|
| 261 |
+
weights_idx_list.append(weights)
|
| 262 |
+
sources = (*sources, weights_idx_list)
|
| 263 |
+
|
| 264 |
+
# TODO: implement (only required when prediction target comes from different data stream)
|
| 265 |
+
targets, target_info = None, None
|
| 266 |
+
target_idxs = None
|
| 267 |
+
|
| 268 |
+
yield ( sources, targets, (source_idxs, sources_infos), (target_idxs, target_info))
|
| 269 |
+
|
| 270 |
+
###################################################
|
| 271 |
+
def set_data( self, times_pos, batch_size = None) :
|
| 272 |
+
'''
|
| 273 |
+
times_pos = np.array( [ [year, month, day, hour, lat, lon], ...] )
|
| 274 |
+
- lat \in [90,-90] = [90N, 90S]
|
| 275 |
+
- lon \in [0,360]
|
| 276 |
+
- (year,month) pairs should be a limited number since all data for these is loaded
|
| 277 |
+
'''
|
| 278 |
+
# generate all the data
|
| 279 |
+
self.idxs_perm = np.zeros( (len(times_pos), 2))
|
| 280 |
+
self.idxs_perm_t = []
|
| 281 |
+
self.num_samples = len(times_pos)
|
| 282 |
+
for idx, item in enumerate( times_pos) :
|
| 283 |
+
|
| 284 |
+
assert item[2] >= 1 and item[2] <= 31
|
| 285 |
+
assert item[3] >= 0 and item[3] < int(24 / self.time_sampling)
|
| 286 |
+
assert item[4] >= -90. and item[4] <= 90.
|
| 287 |
+
|
| 288 |
+
tstamp = pd.to_datetime( f'{item[0]}-{item[1]}-{item[2]}-{item[3]}', format='%Y-%m-%d-%H')
|
| 289 |
+
|
| 290 |
+
self.idxs_perm_t += [ np.where( self.times == tstamp)[0]+1 ] #The +1 assures that tsamp is included in the range
|
| 291 |
+
|
| 292 |
+
# work with mathematical lat coordinates from here on
|
| 293 |
+
self.idxs_perm[idx] = np.array( [90. - item[4], item[5]])
|
| 294 |
+
|
| 295 |
+
self.idxs_perm_t = np.array(self.idxs_perm_t).squeeze()
|
| 296 |
+
|
| 297 |
+
###################################################
|
| 298 |
+
def set_global( self, times, batch_size = None, token_overlap = [0, 0]) :
|
| 299 |
+
''' generate patch/token positions for global grid '''
|
| 300 |
+
token_overlap = np.array( token_overlap).astype(np.int64)
|
| 301 |
+
|
| 302 |
+
# assumed that sanity checking that field data is consistent has been done
|
| 303 |
+
ifield = 0
|
| 304 |
+
field = self.fields[ifield]
|
| 305 |
+
|
| 306 |
+
res = self.res
|
| 307 |
+
side_len = np.array( [field[3][1] * field[4][1]*res[0], field[3][2] * field[4][2]*res[1]] )
|
| 308 |
+
overlap = np.array([token_overlap[0]*field[4][1]*res[0],token_overlap[1]*field[4][2]*res[1]])
|
| 309 |
+
side_len_2 = side_len / 2.
|
| 310 |
+
assert all( overlap <= side_len_2), 'token_overlap too large for #tokens, reduce if possible'
|
| 311 |
+
|
| 312 |
+
# generate tiles
|
| 313 |
+
times_pos = []
|
| 314 |
+
for ctime in times :
|
| 315 |
+
|
| 316 |
+
lat = side_len_2[0].item()
|
| 317 |
+
num_tiles_lat = 0
|
| 318 |
+
while (lat + side_len_2[0].item()) < 180. :
|
| 319 |
+
num_tiles_lat += 1
|
| 320 |
+
lon = side_len_2[1].item() - overlap[1].item()/2.
|
| 321 |
+
num_tiles_lon = 0
|
| 322 |
+
while (lon - side_len_2[1]) < 360. :
|
| 323 |
+
times_pos += [[*ctime, -lat + 90., np.mod(lon,360.) ]]
|
| 324 |
+
lon += side_len[1].item() - overlap[1].item()
|
| 325 |
+
num_tiles_lon += 1
|
| 326 |
+
lat += side_len[0].item() - overlap[0].item()
|
| 327 |
+
|
| 328 |
+
# add one additional row if no perfect tiling (sphere is toric in longitude so no special
|
| 329 |
+
# handling necessary but not in latitude)
|
| 330 |
+
# the added row is such that it goes exaclty down to the South pole and the offset North-wards
|
| 331 |
+
# is computed based on this
|
| 332 |
+
lat -= side_len[0] - overlap[0]
|
| 333 |
+
if lat - side_len_2[0] < 180. :
|
| 334 |
+
num_tiles_lat += 1
|
| 335 |
+
lat = 180. - side_len_2[0].item() + res[0]
|
| 336 |
+
lon = side_len_2[1].item() - overlap[1].item()/2.
|
| 337 |
+
while (lon - side_len_2[1]) < 360. :
|
| 338 |
+
times_pos += [[*ctime, -lat + 90., np.mod(lon,360.) ]]
|
| 339 |
+
lon += side_len[1].item() - overlap[1].item()
|
| 340 |
+
|
| 341 |
+
# adjust batch size if necessary so that the evaluations split up across batches of equal size
|
| 342 |
+
batch_size = len(times_pos) #num_tiles_lon
|
| 343 |
+
|
| 344 |
+
print( 'Number of batches per global forecast: {}'.format( num_tiles_lat) )
|
| 345 |
+
|
| 346 |
+
self.set_data( times_pos, batch_size)
|
| 347 |
+
|
| 348 |
+
###################################################
|
| 349 |
+
def __len__(self):
|
| 350 |
+
return self.num_samples // self.batch_size
|
| 351 |
+
|
| 352 |
+
###################################################
|
| 353 |
+
def worker_workset( self) :
|
| 354 |
+
|
| 355 |
+
worker_info = torch.utils.data.get_worker_info()
|
| 356 |
+
|
| 357 |
+
if worker_info is None:
|
| 358 |
+
iter_start = 0
|
| 359 |
+
iter_end = self.num_samples
|
| 360 |
+
|
| 361 |
+
else:
|
| 362 |
+
# split workload
|
| 363 |
+
per_worker = len(self) // worker_info.num_workers
|
| 364 |
+
worker_id = worker_info.id
|
| 365 |
+
iter_start = int(worker_id * per_worker)
|
| 366 |
+
iter_end = int(iter_start + per_worker)
|
| 367 |
+
if worker_info.id+1 == worker_info.num_workers :
|
| 368 |
+
iter_end = len(self)
|
| 369 |
+
|
| 370 |
+
return iter_start, iter_end
|
| 371 |
+
|
vendor/atmorep-official/atmorep/datasets/normalizer.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
import code
|
| 18 |
+
import numpy as np
|
| 19 |
+
import xarray as xr
|
| 20 |
+
import atmorep.config.config as config
|
| 21 |
+
|
| 22 |
+
######################################################
|
| 23 |
+
# Normalize #
|
| 24 |
+
######################################################
|
| 25 |
+
|
| 26 |
+
def normalize( data, norm, dates, year_base = 1979) :
|
| 27 |
+
corr_data = np.array([norm[12*(dt.year-year_base) + dt.month-1] for dt in dates])
|
| 28 |
+
mean, var = corr_data[:, 0], corr_data[:, 1]
|
| 29 |
+
if (var == 0.).all() :
|
| 30 |
+
print( f'Warning: var == 0')
|
| 31 |
+
assert False
|
| 32 |
+
if len(norm.shape) > 2 : #global norm
|
| 33 |
+
return normalize_local(data, mean, var)
|
| 34 |
+
else:
|
| 35 |
+
return normalize_global( data, mean, var)
|
| 36 |
+
|
| 37 |
+
######################################################
|
| 38 |
+
def normalize_local( data, mean, var) :
|
| 39 |
+
data = (data - mean) / var
|
| 40 |
+
return data
|
| 41 |
+
|
| 42 |
+
######################################################
|
| 43 |
+
def normalize_global( data, mean, var) :
|
| 44 |
+
for i in range( data.shape[0]) :
|
| 45 |
+
data[i] = (data[i] - mean[i]) / var[i]
|
| 46 |
+
return data
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
######################################################
|
| 50 |
+
# Denormalize #
|
| 51 |
+
######################################################
|
| 52 |
+
def denormalize(data, norm, dates, year_base = 1979) :
|
| 53 |
+
corr_data = np.array([norm[12*(dt.year-year_base) + dt.month-1] for dt in dates])
|
| 54 |
+
mean, var = corr_data[:, 0], corr_data[:, 1]
|
| 55 |
+
if len(norm.shape) > 2 :
|
| 56 |
+
return denormalize_local(data, mean, var)
|
| 57 |
+
else:
|
| 58 |
+
return denormalize_global(data, mean, var)
|
| 59 |
+
|
| 60 |
+
######################################################
|
| 61 |
+
|
| 62 |
+
def denormalize_local(data, mean, var) :
|
| 63 |
+
if len(data.shape) > 3: #ensemble
|
| 64 |
+
for i in range( data.shape[0]) :
|
| 65 |
+
data[i] = (data[i] * var) + mean
|
| 66 |
+
else:
|
| 67 |
+
data = (data * var) + mean
|
| 68 |
+
return data
|
| 69 |
+
|
| 70 |
+
######################################################
|
| 71 |
+
|
| 72 |
+
def denormalize_global(data, mean, var) :
|
| 73 |
+
if len(data.shape) > 3: #ensemble
|
| 74 |
+
data = data.swapaxes(0,1)
|
| 75 |
+
for i in range( data.shape[0]) :
|
| 76 |
+
data[i] = ((data[i] * var[i]) + mean[i])
|
| 77 |
+
data = data.swapaxes(0,1)
|
| 78 |
+
else:
|
| 79 |
+
for i in range( data.shape[0]) :
|
| 80 |
+
data[i] = (data[i] * var[i]) + mean[i]
|
| 81 |
+
|
| 82 |
+
return data
|
vendor/atmorep-official/atmorep/tests/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
vendor/atmorep-official/atmorep/tests/conftest.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def pytest_addoption(parser):
|
| 2 |
+
parser.addoption("--field", action="store", help="field to run the test on")
|
| 3 |
+
parser.addoption("--model_id", action="store", help="wandb ID of the atmorep model")
|
| 4 |
+
parser.addoption("--epoch", action="store", help="field to run the test on", default = "0")
|
| 5 |
+
parser.addoption("--strategy", action="store", help="BERT or forecast")
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
|
vendor/atmorep-official/atmorep/tests/test_utils.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pandas as pd
|
| 3 |
+
|
| 4 |
+
def era5_fname():
|
| 5 |
+
return "/gpfs/scratch/ehpc03/data/{}/ml{}/era5_{}_y{}_m{}_ml{}.grib"
|
| 6 |
+
|
| 7 |
+
def atmorep_pred():
|
| 8 |
+
return "./results/id{}/results_id{}_epoch{}_pred.zarr"
|
| 9 |
+
|
| 10 |
+
def atmorep_target():
|
| 11 |
+
return "./results/id{}/results_id{}_epoch{}_target.zarr"
|
| 12 |
+
|
| 13 |
+
def grib_index(field):
|
| 14 |
+
grib_idxs = {"velocity_u": "u",
|
| 15 |
+
"temperature": "t",
|
| 16 |
+
"total_precip": "tp",
|
| 17 |
+
"velocity_v": "v",
|
| 18 |
+
"velocity_z": "z",
|
| 19 |
+
"vorticity" : "vo",
|
| 20 |
+
"divergence" : "d",
|
| 21 |
+
"specific_humidity": "q"}
|
| 22 |
+
|
| 23 |
+
return grib_idxs[field]
|
| 24 |
+
|
| 25 |
+
##################################################################
|
| 26 |
+
|
| 27 |
+
def get_BERT(atmorep, field, sample, level):
|
| 28 |
+
atmorep_sample = atmorep[f"{field}/sample={sample:05d}/ml={level:05d}"]
|
| 29 |
+
data = atmorep_sample.data[0,0]
|
| 30 |
+
datetime = pd.Timestamp(atmorep_sample.datetime[0,0])
|
| 31 |
+
lats = atmorep_sample.lat[0]
|
| 32 |
+
lons = atmorep_sample.lon[0]
|
| 33 |
+
return data, datetime, lats, lons
|
| 34 |
+
|
| 35 |
+
def get_forecast(atmorep, field, sample,level_idx):
|
| 36 |
+
atmorep_sample = atmorep[f"{field}/sample={sample:05d}"]
|
| 37 |
+
data = atmorep_sample.data[level_idx, 0]
|
| 38 |
+
datetime = pd.Timestamp(atmorep_sample.datetime[0])
|
| 39 |
+
lats = atmorep_sample.lat
|
| 40 |
+
lons = atmorep_sample.lon
|
| 41 |
+
return data, datetime, lats, lons
|
| 42 |
+
|
| 43 |
+
######################################
|
| 44 |
+
|
| 45 |
+
def check_lats(lats_pred, lats_target):
|
| 46 |
+
assert (lats_pred[:] == lats_target[:]).all(), "Mismatch between latitudes"
|
| 47 |
+
assert (lats_pred[:] <= 90.).all(), f"latitudes are between {np.amin(lats_pred)}- {np.amax(lats_pred)}"
|
| 48 |
+
assert (lats_pred[:] >= -90.).all(), f"latitudes are between {np.amin(lats_pred)}- {np.amax(lats_pred)}"
|
| 49 |
+
|
| 50 |
+
def check_lons(lons_pred, lons_target):
|
| 51 |
+
assert (lons_pred[:] == lons_target[:]).all(), "Mismatch between longitudes"
|
| 52 |
+
assert (lons_pred[:] >= 0.).all(), "longitudes are between {np.amin(lons_pred)}- {np.amax(lons_pred)}"
|
| 53 |
+
assert (lons_pred[:] <= 360.).all(), "longitudes are between {np.amin(lons_pred)}- {np.amax(lons_pred)}"
|
| 54 |
+
|
| 55 |
+
def check_datetimes(datetimes_pred, datetimes_target):
|
| 56 |
+
assert (datetimes_pred == datetimes_target), "Mismatch between datetimes"
|
| 57 |
+
|
| 58 |
+
######################################
|
| 59 |
+
|
| 60 |
+
#calculate RMSE
|
| 61 |
+
def compute_RMSE(pred, target):
|
| 62 |
+
return np.sqrt(np.mean((pred-target)**2))
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def get_max_RMSE(field):
|
| 66 |
+
#TODO: optimize thresholds
|
| 67 |
+
values = {"temperature" : 3,
|
| 68 |
+
"velocity_u" : 0.2, #????
|
| 69 |
+
"velocity_v": 0.2, #????
|
| 70 |
+
"velocity_z": 0.2, #????
|
| 71 |
+
"vorticity" : 0.2, #????
|
| 72 |
+
"divergence": 0.2, #????
|
| 73 |
+
"specific_humidity": 0.2, #????
|
| 74 |
+
"total_precip": 1, #?????
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
return values[field]
|
vendor/atmorep-official/atmorep/tests/validation_test.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
import zarr
|
| 3 |
+
import cfgrib
|
| 4 |
+
import xarray as xr
|
| 5 |
+
import numpy as np
|
| 6 |
+
import random as rnd
|
| 7 |
+
import warnings
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
from atmorep.tests.test_utils import *
|
| 11 |
+
|
| 12 |
+
# run it with e.g. pytest -s atmorep/tests/validation_test.py --field temperature --model_id ztsut0mr --strategy BERT
|
| 13 |
+
|
| 14 |
+
@pytest.fixture
|
| 15 |
+
def field(request):
|
| 16 |
+
return request.config.getoption("field")
|
| 17 |
+
|
| 18 |
+
@pytest.fixture
|
| 19 |
+
def model_id(request):
|
| 20 |
+
return request.config.getoption("model_id")
|
| 21 |
+
|
| 22 |
+
@pytest.fixture
|
| 23 |
+
def epoch(request):
|
| 24 |
+
request.config.getoption("epoch")
|
| 25 |
+
|
| 26 |
+
@pytest.fixture(autouse = True)
|
| 27 |
+
def BERT(request):
|
| 28 |
+
strategy = request.config.getoption("strategy")
|
| 29 |
+
return (strategy == 'BERT' or strategy == 'temporal_interpolation')
|
| 30 |
+
|
| 31 |
+
@pytest.fixture(autouse = True)
|
| 32 |
+
def strategy(request):
|
| 33 |
+
return request.config.getoption("strategy")
|
| 34 |
+
|
| 35 |
+
#TODO: add test for global_forecast vs ERA5
|
| 36 |
+
|
| 37 |
+
def test_datetime(field, model_id, BERT, epoch = 0):
|
| 38 |
+
|
| 39 |
+
"""
|
| 40 |
+
Check against ERA5 timestamps.
|
| 41 |
+
Loop over all levels individually. 50 random samples for each level.
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
store = zarr.ZipStore(atmorep_target().format(model_id, model_id, str(epoch).zfill(5)))
|
| 45 |
+
atmorep = zarr.group(store)
|
| 46 |
+
|
| 47 |
+
nsamples = min(len(atmorep[field]), 50)
|
| 48 |
+
samples = rnd.sample(range(len(atmorep[field])), nsamples)
|
| 49 |
+
levels = [int(f.split("=")[1]) for f in atmorep[f"{field}/sample=00000"]] if BERT else atmorep[f"{field}/sample=00000"].ml[:]
|
| 50 |
+
|
| 51 |
+
get_data = get_BERT if BERT else get_forecast
|
| 52 |
+
|
| 53 |
+
for level in levels:
|
| 54 |
+
#TODO: make it more elegant
|
| 55 |
+
level_idx = level if BERT else np.where(levels == level)[0].tolist()[0]
|
| 56 |
+
|
| 57 |
+
for s in samples:
|
| 58 |
+
data, datetime, lats, lons = get_data(atmorep, field, s, level_idx)
|
| 59 |
+
year, month = datetime.year, str(datetime.month).zfill(2)
|
| 60 |
+
|
| 61 |
+
era5_path = era5_fname().format(field, level, field, year, month, level)
|
| 62 |
+
if not os.path.isfile(era5_path):
|
| 63 |
+
warnings.warn(UserWarning((f"Timestamp {datetime} not found in ERA5. Skipping")))
|
| 64 |
+
continue
|
| 65 |
+
era5 = xr.open_dataset(era5_path, engine = "cfgrib")[grib_index(field)].sel(time = datetime, latitude = lats, longitude = lons)
|
| 66 |
+
|
| 67 |
+
#assert (data[0] == era5.values[0]).all(), "Mismatch between ERA5 and AtmoRep Timestamps"
|
| 68 |
+
assert np.isclose(data[0], era5.values[0],rtol=1e-04, atol=1e-07).all(), "Mismatch between ERA5 and AtmoRep Timestamps"
|
| 69 |
+
|
| 70 |
+
#############################################################################
|
| 71 |
+
|
| 72 |
+
def test_coordinates(field, model_id, BERT, epoch = 0):
|
| 73 |
+
"""
|
| 74 |
+
Check that coordinates match between target and prediction.
|
| 75 |
+
Check also that latitude and longitudes are in geographical coordinates
|
| 76 |
+
50 random samples.
|
| 77 |
+
"""
|
| 78 |
+
|
| 79 |
+
store_t = zarr.ZipStore(atmorep_target().format(model_id, model_id, str(epoch).zfill(5)))
|
| 80 |
+
target = zarr.group(store_t)
|
| 81 |
+
|
| 82 |
+
store_p = zarr.ZipStore(atmorep_pred().format(model_id, model_id, str(epoch).zfill(5)))
|
| 83 |
+
pred = zarr.group(store_p)
|
| 84 |
+
|
| 85 |
+
nsamples = min(len(target[field]), 50)
|
| 86 |
+
samples = rnd.sample(range(len(target[field])), nsamples)
|
| 87 |
+
levels = [int(f.split("=")[1]) for f in target[f"{field}/sample=00000"]] if BERT else target[f"{field}/sample=00000"].ml[:]
|
| 88 |
+
|
| 89 |
+
get_data = get_BERT if BERT else get_forecast
|
| 90 |
+
|
| 91 |
+
for level in levels:
|
| 92 |
+
level_idx = level if BERT else np.where(levels == level)[0].tolist()[0]
|
| 93 |
+
for s in samples:
|
| 94 |
+
_, datetime_target, lats_target, lons_target = get_data(target,field, s, level_idx)
|
| 95 |
+
_, datetime_pred, lats_pred, lons_pred = get_data(pred, field, s, level_idx)
|
| 96 |
+
|
| 97 |
+
check_lats(lats_pred, lats_target)
|
| 98 |
+
check_lons(lons_pred, lons_target)
|
| 99 |
+
check_datetimes(datetime_pred, datetime_target)
|
| 100 |
+
|
| 101 |
+
#########################################################################
|
| 102 |
+
|
| 103 |
+
def test_rmse(field, model_id, BERT, epoch = 0):
|
| 104 |
+
"""
|
| 105 |
+
Test that for each field the RMSE does not exceed a certain value.
|
| 106 |
+
50 random samples.
|
| 107 |
+
"""
|
| 108 |
+
store_t = zarr.ZipStore(atmorep_target().format(model_id, model_id, str(epoch).zfill(5)))
|
| 109 |
+
target = zarr.group(store_t)
|
| 110 |
+
|
| 111 |
+
store_p = zarr.ZipStore(atmorep_pred().format(model_id, model_id, str(epoch).zfill(5)))
|
| 112 |
+
pred = zarr.group(store_p)
|
| 113 |
+
|
| 114 |
+
nsamples = min(len(target[field]), 50)
|
| 115 |
+
samples = rnd.sample(range(len(target[field])), nsamples)
|
| 116 |
+
levels = [int(f.split("=")[1]) for f in target[f"{field}/sample=00000"]] if BERT else target[f"{field}/sample=00000"].ml[:]
|
| 117 |
+
|
| 118 |
+
get_data = get_BERT if BERT else get_forecast
|
| 119 |
+
|
| 120 |
+
for level in levels:
|
| 121 |
+
level_idx = level if BERT else np.where(levels == level)[0].tolist()[0]
|
| 122 |
+
for s in samples:
|
| 123 |
+
sample_target, _, _, _ = get_data(target,field, s, level_idx)
|
| 124 |
+
sample_pred, _, _, _ = get_data(pred,field, s, level_idx)
|
| 125 |
+
|
| 126 |
+
assert compute_RMSE(sample_target, sample_pred).mean() < get_max_RMSE(field)
|
vendor/atmorep-official/atmorep/training/__init__.py
ADDED
|
File without changes
|
vendor/atmorep-official/atmorep/training/bert.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import numpy as np
|
| 19 |
+
from functools import partial
|
| 20 |
+
import code
|
| 21 |
+
|
| 22 |
+
####################################################################################################
|
| 23 |
+
def prepare_batch_BERT_multifield( cf, rngs, fields, BERT_strategy, fields_data, fields_infos) :
|
| 24 |
+
|
| 25 |
+
fields_tokens_masked_idx_list = [[] for _ in fields_data]
|
| 26 |
+
fields_targets = [[] for _ in fields_data]
|
| 27 |
+
sources = [[] for _ in fields_data]
|
| 28 |
+
token_infos = [[] for _ in fields_data]
|
| 29 |
+
|
| 30 |
+
if not BERT_strategy :
|
| 31 |
+
BERT_strategy = cf.BERT_strategy
|
| 32 |
+
|
| 33 |
+
if BERT_strategy == 'BERT' :
|
| 34 |
+
bert_f = prepare_batch_BERT_field
|
| 35 |
+
elif BERT_strategy == 'global_forecast' :
|
| 36 |
+
bert_f = prepare_batch_BERT_forecast_field
|
| 37 |
+
elif BERT_strategy == 'forecast' :
|
| 38 |
+
bert_f = prepare_batch_BERT_forecast_field
|
| 39 |
+
elif BERT_strategy == 'temporal_interpolation' :
|
| 40 |
+
bert_f = prepare_batch_BERT_temporal_field
|
| 41 |
+
else :
|
| 42 |
+
assert False
|
| 43 |
+
|
| 44 |
+
rng_idx = 1
|
| 45 |
+
for ifield, (field, infos) in enumerate(zip(fields_data, fields_infos)) :
|
| 46 |
+
for ilevel, (field_data, token_info) in enumerate(zip(field, infos)) :
|
| 47 |
+
|
| 48 |
+
# no masking for static fields or if masking rate = 0
|
| 49 |
+
if fields[ifield][1][0] > 0 and fields[ifield][5][0] > 0. :
|
| 50 |
+
|
| 51 |
+
ret = bert_f( cf, ifield, field_data, token_info, rngs[rng_idx])
|
| 52 |
+
(field_data, token_info, target, tokens_masked_idx_list) = ret
|
| 53 |
+
|
| 54 |
+
if target is not None :
|
| 55 |
+
fields_targets[ifield].append( target)
|
| 56 |
+
fields_tokens_masked_idx_list[ifield].append( tokens_masked_idx_list)
|
| 57 |
+
|
| 58 |
+
rng_idx += 1
|
| 59 |
+
|
| 60 |
+
sources[ifield].append( field_data.unsqueeze(1) )
|
| 61 |
+
token_infos[ifield].append( token_info )
|
| 62 |
+
|
| 63 |
+
# merge along vertical level
|
| 64 |
+
sources[ifield] = torch.cat( sources[ifield], 1)
|
| 65 |
+
token_infos[ifield] = torch.cat( token_infos[ifield], 1)
|
| 66 |
+
# merge along vertical level, for target we have level, batch, ... ordering
|
| 67 |
+
fields_targets[ifield] = torch.cat( fields_targets[ifield],0) \
|
| 68 |
+
if len(fields_targets[ifield]) > 0 else fields_targets[ifield]
|
| 69 |
+
|
| 70 |
+
return (sources, token_infos, fields_targets, fields_tokens_masked_idx_list)
|
| 71 |
+
|
| 72 |
+
####################################################################################################
|
| 73 |
+
def prepare_batch_BERT_field( cf, ifield, source, token_info, rng) :
|
| 74 |
+
|
| 75 |
+
# shortcuts
|
| 76 |
+
mr = partial( torch.nn.functional.interpolate, mode='trilinear')
|
| 77 |
+
fl = torch.flatten
|
| 78 |
+
tr = torch.transpose
|
| 79 |
+
sq = torch.squeeze
|
| 80 |
+
usq = torch.unsqueeze
|
| 81 |
+
cnt_nz = torch.count_nonzero
|
| 82 |
+
|
| 83 |
+
# collapse token dimensions
|
| 84 |
+
source_shape0 = source.shape
|
| 85 |
+
source = torch.flatten( torch.flatten( source, 1, 3), 2, 4)
|
| 86 |
+
|
| 87 |
+
# select random token in the selected space-time cube to be masked/deleted
|
| 88 |
+
BERT_frac = cf.fields[ifield][5][0]
|
| 89 |
+
BERT_frac_mask = cf.fields[ifield][5][1]
|
| 90 |
+
BERT_frac_rndm = cf.fields[ifield][5][2]
|
| 91 |
+
BERT_frac_mr = cf.fields[ifield][5][3]
|
| 92 |
+
BERT_mr_max = 2
|
| 93 |
+
token_size = cf.fields[ifield][4]
|
| 94 |
+
batch_dim = source.shape[0]
|
| 95 |
+
num_tokens = source.shape[1]
|
| 96 |
+
|
| 97 |
+
masking_ratios = rng.random( batch_dim) * BERT_frac
|
| 98 |
+
# number of tokens masked per batch entry
|
| 99 |
+
nums_masked = np.ceil( num_tokens * masking_ratios).astype(np.int64)
|
| 100 |
+
tokens_masked_idx_list = [ torch.tensor(rng.permutation(num_tokens)[:nms]) for nms in nums_masked]
|
| 101 |
+
|
| 102 |
+
# linear indices for masking
|
| 103 |
+
tokens_masked_idx_list = [tokens_masked_idx_list[i] + num_tokens * i for i in range(batch_dim)]
|
| 104 |
+
idx = torch.cat( tokens_masked_idx_list)
|
| 105 |
+
|
| 106 |
+
# flatten along first two dimension to simplify linear indexing (which then requires an
|
| 107 |
+
# easily computable row offset)
|
| 108 |
+
source_shape = source.shape
|
| 109 |
+
source = torch.flatten( source, 0, 1)
|
| 110 |
+
|
| 111 |
+
# keep masked tokens for loss computation
|
| 112 |
+
target = source[idx].clone()
|
| 113 |
+
|
| 114 |
+
# climatological mean of normalized data
|
| 115 |
+
global_mean = 0. * torch.mean(source, 0)
|
| 116 |
+
global_std = torch.std(source, 0)
|
| 117 |
+
|
| 118 |
+
# Conditional masking (all are sampled independently so the fractions are only amortized)
|
| 119 |
+
# BERT_frac_mask = fraction masked (80%)
|
| 120 |
+
# BERT_frac_rndm = fraction noisy tokens (10%)
|
| 121 |
+
# face_mr_BERT = fraction of multi-res "masked" tokens (downsampled instead of fully masked)
|
| 122 |
+
# remainder = untouched (10%)
|
| 123 |
+
#
|
| 124 |
+
# conditional idx for tokens to be masked with mean or random values
|
| 125 |
+
idx_mask_cond = torch.tensor( rng.random( idx.shape[0])) < BERT_frac_mask
|
| 126 |
+
idx_rndm_cond = torch.tensor( rng.random( idx.shape[0])) < BERT_frac_rndm
|
| 127 |
+
idx_mr_cond = torch.tensor( rng.random( idx.shape[0])) < BERT_frac_mr
|
| 128 |
+
|
| 129 |
+
# set mask
|
| 130 |
+
source[ idx[idx_mask_cond] ] = global_mean
|
| 131 |
+
|
| 132 |
+
# set noisy tokens (noise is field independent)
|
| 133 |
+
# TODO: fudge factor of 0.1
|
| 134 |
+
cnt_nz_rndm = cnt_nz(idx_rndm_cond)
|
| 135 |
+
dim_embed = source.shape[1]
|
| 136 |
+
rnd_w = tr( torch.tensor( rng.random( cnt_nz_rndm, dtype=np.float32)).repeat((dim_embed,1)), 0,1 )
|
| 137 |
+
source[ idx[idx_rndm_cond] ] = rnd_w * global_mean + (1.-rnd_w) * source[ idx[idx_rndm_cond] ] + \
|
| 138 |
+
0.1 * global_std * torch.randn( (cnt_nz_rndm, source.shape[1]))
|
| 139 |
+
|
| 140 |
+
# randomly coarsened tokens
|
| 141 |
+
if BERT_frac_mr > 0. and idx_mr_cond.shape[0] > 0 and idx_mr_cond.any().item() :
|
| 142 |
+
# version with uniform multi-res downsampling per batch for computational efficiency
|
| 143 |
+
# (in particular on GPU)
|
| 144 |
+
ts = torch.tensor( [token_size[1], token_size[2]], dtype=torch.int)
|
| 145 |
+
mrs = ((rng.random() * (ts - BERT_mr_max*ts)) + BERT_mr_max*ts).int()
|
| 146 |
+
mrs = (token_size[0], mrs[0], mrs[1])
|
| 147 |
+
ts = token_size
|
| 148 |
+
# interpolate to smaller size and then interpolate up -> coarsened version with same size
|
| 149 |
+
# unsqueeze(usq()) is required since channel dimension is expected
|
| 150 |
+
temp = mr( mr( usq( source[ idx[idx_mr_cond] ].reshape( (-1,ts[0],ts[1],ts[2])), 1), mrs), ts)
|
| 151 |
+
source[ idx[idx_mr_cond] ] = sq( fl( temp, -3, -1))
|
| 152 |
+
|
| 153 |
+
# recover batch dimension which was flattend for easier indexing and also token dimensions
|
| 154 |
+
source = torch.reshape( torch.reshape( source, source_shape), source_shape0)
|
| 155 |
+
|
| 156 |
+
return (source, token_info, target, tokens_masked_idx_list)
|
| 157 |
+
|
| 158 |
+
####################################################################################################
|
| 159 |
+
def prepare_batch_BERT_forecast_field( cf, ifield, source, token_info, rng) :
|
| 160 |
+
|
| 161 |
+
nt = cf.forecast_num_tokens
|
| 162 |
+
num_tokens = source.shape[-6:-3]
|
| 163 |
+
num_tokens_space = num_tokens[1] * num_tokens[2]
|
| 164 |
+
idxs = (num_tokens[0]-nt) * num_tokens_space + torch.arange(nt * num_tokens_space)
|
| 165 |
+
|
| 166 |
+
# collapse token dimensions
|
| 167 |
+
source_shape0 = source.shape
|
| 168 |
+
source = torch.flatten( torch.flatten( source, 1, 3), 2, 4)
|
| 169 |
+
|
| 170 |
+
# linear indices for masking
|
| 171 |
+
num_tokens = source.shape[1]
|
| 172 |
+
tokens_masked_idx_list = [idxs + num_tokens * i for i in range( source.shape[0] )]
|
| 173 |
+
idx = torch.cat( tokens_masked_idx_list)
|
| 174 |
+
|
| 175 |
+
source_shape = source.shape
|
| 176 |
+
# flatten along first two dimension to simplify linear indexing (which then requires an
|
| 177 |
+
# easily computable row offset)
|
| 178 |
+
source = torch.flatten( source, 0, 1)
|
| 179 |
+
|
| 180 |
+
# keep masked tokens for loss computation
|
| 181 |
+
target = source[idx].clone()
|
| 182 |
+
|
| 183 |
+
# masking
|
| 184 |
+
global_mean = 0. * torch.mean(source, 0)
|
| 185 |
+
source[ idx ] = global_mean
|
| 186 |
+
|
| 187 |
+
# recover batch dimension which was flattend for easier indexing
|
| 188 |
+
source = torch.reshape( torch.reshape( source, source_shape), source_shape0)
|
| 189 |
+
|
| 190 |
+
return (source, token_info, target, tokens_masked_idx_list)
|
| 191 |
+
|
| 192 |
+
####################################################################################################
|
| 193 |
+
def prepare_batch_BERT_temporal_field( cf, ifield, source, token_info, rng) :
|
| 194 |
+
|
| 195 |
+
num_tokens = source.shape[-6:-3]
|
| 196 |
+
num_tokens_space = num_tokens[1] * num_tokens[2]
|
| 197 |
+
|
| 198 |
+
#backward compatibility: mask only middle token
|
| 199 |
+
if not hasattr( cf, 'idx_time_mask'):
|
| 200 |
+
idx_time_mask = int( np.floor(num_tokens[0] / 2.))
|
| 201 |
+
idxs = idx_time_mask * num_tokens_space + torch.arange(num_tokens_space)
|
| 202 |
+
else: #list of idx_time_mask
|
| 203 |
+
idxs = torch.concat([i*num_tokens_space + torch.arange(num_tokens_space) for i in cf.idx_time_mask])
|
| 204 |
+
|
| 205 |
+
# collapse token dimensions
|
| 206 |
+
source_shape0 = source.shape
|
| 207 |
+
source = torch.flatten( torch.flatten( source, 1, 3), 2, 4)
|
| 208 |
+
|
| 209 |
+
# linear indices for masking
|
| 210 |
+
num_tokens = source.shape[1]
|
| 211 |
+
idx = torch.cat( [idxs + num_tokens * i for i in range( source.shape[0] )] )
|
| 212 |
+
tokens_masked_idx_list = [idxs + num_tokens * i for i in range( source.shape[0] )]
|
| 213 |
+
source_shape = source.shape
|
| 214 |
+
# flatten along first two dimension to simplify linear indexing (which then requires an
|
| 215 |
+
# easily computable row offset)
|
| 216 |
+
source = torch.flatten( source, 0, 1)
|
| 217 |
+
|
| 218 |
+
# keep masked tokens for loss computation
|
| 219 |
+
target = source[idx].clone()
|
| 220 |
+
|
| 221 |
+
# masking
|
| 222 |
+
global_mean = 0. * torch.mean(source, 0)
|
| 223 |
+
source[ idx ] = global_mean
|
| 224 |
+
|
| 225 |
+
# recover batch dimension which was flattend for easier indexing
|
| 226 |
+
source = torch.reshape( torch.reshape( source, source_shape), source_shape0)
|
| 227 |
+
|
| 228 |
+
return (source, token_info, target, tokens_masked_idx_list)
|
vendor/atmorep-official/atmorep/transformer/__init__.py
ADDED
|
File without changes
|
vendor/atmorep-official/atmorep/transformer/axial_attention.py
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration / lucidrains@github.com
|
| 10 |
+
#
|
| 11 |
+
# description : Based on:
|
| 12 |
+
# https://github.com/lucidrains/axial-attention/blob/master/axial_attention/axial_attention.py
|
| 13 |
+
#
|
| 14 |
+
# license :
|
| 15 |
+
#
|
| 16 |
+
####################################################################################################
|
| 17 |
+
|
| 18 |
+
#
|
| 19 |
+
import torch
|
| 20 |
+
from torch import nn
|
| 21 |
+
from operator import itemgetter
|
| 22 |
+
import code
|
| 23 |
+
# code.interact(local=locals())
|
| 24 |
+
|
| 25 |
+
####################################################################################################
|
| 26 |
+
|
| 27 |
+
# helper functions
|
| 28 |
+
|
| 29 |
+
def exists(val):
|
| 30 |
+
return val is not None
|
| 31 |
+
|
| 32 |
+
def map_el_ind(arr, ind):
|
| 33 |
+
return list(map(itemgetter(ind), arr))
|
| 34 |
+
|
| 35 |
+
def sort_and_return_indices(arr):
|
| 36 |
+
indices = [ind for ind in range(len(arr))]
|
| 37 |
+
arr = zip(arr, indices)
|
| 38 |
+
arr = sorted(arr)
|
| 39 |
+
return map_el_ind(arr, 0), map_el_ind(arr, 1)
|
| 40 |
+
|
| 41 |
+
# calculates the permutation to bring the input tensor to something attend-able
|
| 42 |
+
# also calculates the inverse permutation to bring the tensor back to its original shape
|
| 43 |
+
|
| 44 |
+
def calculate_permutations(num_dimensions, emb_dim, axial_dims = None) :
|
| 45 |
+
|
| 46 |
+
total_dimensions = num_dimensions + 2
|
| 47 |
+
emb_dim = emb_dim if emb_dim > 0 else (emb_dim + total_dimensions)
|
| 48 |
+
if not axial_dims :
|
| 49 |
+
axial_dims = [ind for ind in range(1, total_dimensions) if ind != emb_dim]
|
| 50 |
+
|
| 51 |
+
permutations = []
|
| 52 |
+
|
| 53 |
+
for axial_dim in axial_dims:
|
| 54 |
+
last_two_dims = [axial_dim, emb_dim]
|
| 55 |
+
dims_rest = set(range(0, total_dimensions)) - set(last_two_dims)
|
| 56 |
+
permutation = [*dims_rest, *last_two_dims]
|
| 57 |
+
permutations.append(permutation)
|
| 58 |
+
|
| 59 |
+
return permutations
|
| 60 |
+
|
| 61 |
+
####################################################################################################
|
| 62 |
+
|
| 63 |
+
# helper classes
|
| 64 |
+
|
| 65 |
+
class PermuteToFrom(nn.Module):
|
| 66 |
+
|
| 67 |
+
def __init__(self, permutation, fn):
|
| 68 |
+
|
| 69 |
+
super().__init__()
|
| 70 |
+
self.fn = fn
|
| 71 |
+
_, inv_permutation = sort_and_return_indices(permutation)
|
| 72 |
+
self.permutation = permutation
|
| 73 |
+
self.inv_permutation = inv_permutation
|
| 74 |
+
|
| 75 |
+
def forward(self, x, kv = None, **kwargs):
|
| 76 |
+
|
| 77 |
+
if None == kv :
|
| 78 |
+
|
| 79 |
+
axial = x.permute(*self.permutation).contiguous()
|
| 80 |
+
|
| 81 |
+
shape = axial.shape
|
| 82 |
+
*_, t, d = shape
|
| 83 |
+
|
| 84 |
+
# merge all but axial dimension
|
| 85 |
+
axial = axial.reshape(-1, t, d)
|
| 86 |
+
|
| 87 |
+
# attention
|
| 88 |
+
axial = self.fn(axial, **kwargs)
|
| 89 |
+
|
| 90 |
+
# restore to original shape and permutation
|
| 91 |
+
axial = axial.reshape(*shape)
|
| 92 |
+
axial = axial.permute(*self.inv_permutation).contiguous()
|
| 93 |
+
|
| 94 |
+
else :
|
| 95 |
+
|
| 96 |
+
axial = x.permute(*self.permutation)
|
| 97 |
+
axial_kv = kv.permute(*self.permutation)
|
| 98 |
+
|
| 99 |
+
shape = list(axial.shape)
|
| 100 |
+
shape_kv = axial_kv.shape
|
| 101 |
+
*_, t, d = shape
|
| 102 |
+
*_, t_kv, d_kv = shape_kv
|
| 103 |
+
|
| 104 |
+
# adjust token number along non-axial dimensions for efficient axial attention
|
| 105 |
+
# physically consistent by inserting the missing tokens for a lower res version
|
| 106 |
+
# axial dimension does not have to be matched; repeat_interleave is noop if ratio = 1
|
| 107 |
+
ratio1, ratio2 = shape[1] / shape_kv[1], shape[2] / shape_kv[2]
|
| 108 |
+
if int(ratio1) != 1 :
|
| 109 |
+
axial_kv = torch.repeat_interleave( axial_kv, max(1,int(ratio1)), dim=1 ).contiguous()
|
| 110 |
+
axial = torch.repeat_interleave( axial, max(1,int(1./ratio1)), dim=1 ).contiguous()
|
| 111 |
+
if int(ratio2) != 1 :
|
| 112 |
+
axial_kv = torch.repeat_interleave( axial_kv, max(1,int(ratio2)), dim=2 ).contiguous()
|
| 113 |
+
axial = torch.repeat_interleave( axial, max(1,int(1./ratio2)), dim=2 ).contiguous()
|
| 114 |
+
assert axial.shape[1:3] == axial_kv.shape[1:3], 'axial attn requires matching token numbers'
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
# merge all but axial dimension
|
| 118 |
+
axial_shape_perm = list(axial.shape)
|
| 119 |
+
axial = axial.reshape(-1, t, d)
|
| 120 |
+
axial_kv = axial_kv.reshape(-1, t_kv, d_kv)
|
| 121 |
+
|
| 122 |
+
# attention
|
| 123 |
+
axial = self.fn( axial, axial_kv, **kwargs)
|
| 124 |
+
|
| 125 |
+
# embedding dimension does not match input since not un-embedded in head
|
| 126 |
+
axial_shape_perm[-1] = -1
|
| 127 |
+
shape[-1] = -1
|
| 128 |
+
|
| 129 |
+
# restore original, permuted shape modulo embedding dimension
|
| 130 |
+
axial = axial.reshape( *axial_shape_perm)
|
| 131 |
+
|
| 132 |
+
# recover original token numbers for q if necessary by taking mean over inserted
|
| 133 |
+
if shape[1] < shape_kv[1] :
|
| 134 |
+
axial = axial.reshape( shape[:2] + [int(1./ratio1)] + shape[2:] ).mean( dim=2)
|
| 135 |
+
if shape[2] < shape_kv[2] :
|
| 136 |
+
axial = axial.reshape( shape[:3] + [int(1./ratio2)] + shape[3:] ).mean( dim=3)
|
| 137 |
+
|
| 138 |
+
# restore to original shape modulo embedding dimension
|
| 139 |
+
axial = axial.reshape( *shape)
|
| 140 |
+
axial = axial.permute( *self.inv_permutation).contiguous()
|
| 141 |
+
|
| 142 |
+
return axial
|
| 143 |
+
|
| 144 |
+
####################################################################################################
|
| 145 |
+
# self attention
|
| 146 |
+
|
| 147 |
+
class SelfAttention(nn.Module):
|
| 148 |
+
|
| 149 |
+
#############################################
|
| 150 |
+
def __init__(self, dim, heads, dim_heads = None):
|
| 151 |
+
|
| 152 |
+
super().__init__()
|
| 153 |
+
|
| 154 |
+
self.dim_heads = (dim // heads) if dim_heads is None else dim_heads
|
| 155 |
+
dim_hidden = self.dim_heads * heads
|
| 156 |
+
|
| 157 |
+
self.heads = heads
|
| 158 |
+
self.to_q = nn.Linear(dim, dim_hidden, bias = False)
|
| 159 |
+
self.to_kv = nn.Linear(dim, 2 * dim_hidden, bias = False)
|
| 160 |
+
self.to_out = nn.Linear(dim_hidden, dim)
|
| 161 |
+
|
| 162 |
+
#############################################
|
| 163 |
+
def forward(self, x, kv = None):
|
| 164 |
+
|
| 165 |
+
kv = x if kv is None else kv
|
| 166 |
+
q, k, v = (self.to_q(x), *self.to_kv(kv).chunk(2, dim=-1))
|
| 167 |
+
|
| 168 |
+
b, t, d, h, e = *q.shape, self.heads, self.dim_heads
|
| 169 |
+
|
| 170 |
+
merge_heads = lambda x: x.reshape(b, -1, h, e).transpose(1, 2).reshape(b * h, -1, e)
|
| 171 |
+
q, k, v = map(merge_heads, (q, k, v))
|
| 172 |
+
|
| 173 |
+
dots = torch.einsum('bie,bje->bij', q, k) * (e ** -0.5)
|
| 174 |
+
dots = dots.softmax(dim=-1)
|
| 175 |
+
out = torch.einsum('bij,bje->bie', dots, v)
|
| 176 |
+
|
| 177 |
+
out = out.reshape(b, h, -1, e).transpose(1, 2).reshape(b, -1, d)
|
| 178 |
+
out = self.to_out(out)
|
| 179 |
+
|
| 180 |
+
return out
|
| 181 |
+
|
| 182 |
+
####################################################################################################
|
| 183 |
+
# axial attention class
|
| 184 |
+
|
| 185 |
+
class AxialAttention( nn.Module):
|
| 186 |
+
|
| 187 |
+
#############################################
|
| 188 |
+
def __init__(self, dim, num_dimensions = 2, heads = 8, dim_heads = None, dim_index = -1,
|
| 189 |
+
sum_axial_out = True):
|
| 190 |
+
|
| 191 |
+
assert (dim % heads) == 0, 'hidden dimension must be divisible by number of heads'
|
| 192 |
+
super().__init__()
|
| 193 |
+
|
| 194 |
+
self.dim = dim
|
| 195 |
+
self.total_dimensions = num_dimensions + 2
|
| 196 |
+
self.dim_index = dim_index if dim_index > 0 else (dim_index + self.total_dimensions)
|
| 197 |
+
|
| 198 |
+
attentions = []
|
| 199 |
+
for permutation in calculate_permutations(num_dimensions, dim_index):
|
| 200 |
+
attentions.append(PermuteToFrom(permutation, SelfAttention(dim, heads,dim_heads)))
|
| 201 |
+
|
| 202 |
+
self.axial_attentions = nn.ModuleList(attentions)
|
| 203 |
+
self.sum_axial_out = sum_axial_out
|
| 204 |
+
|
| 205 |
+
#############################################
|
| 206 |
+
def forward(self, x, kv = None):
|
| 207 |
+
|
| 208 |
+
assert len(x.shape) == self.total_dimensions, 'input does not have correct number of dimensions'
|
| 209 |
+
assert x.shape[self.dim_index] == self.dim, 'input does not have correct input dimension'
|
| 210 |
+
|
| 211 |
+
if self.sum_axial_out:
|
| 212 |
+
return sum(map(lambda axial_attn: axial_attn(x, kv), self.axial_attentions))
|
| 213 |
+
|
| 214 |
+
out = x
|
| 215 |
+
for axial_attn in self.axial_attentions:
|
| 216 |
+
out = axial_attn(out)
|
| 217 |
+
|
| 218 |
+
return out
|
| 219 |
+
|
| 220 |
+
####################################################################################################
|
| 221 |
+
|
| 222 |
+
class CrossAttention(nn.Module):
|
| 223 |
+
|
| 224 |
+
#############################################
|
| 225 |
+
def __init__(self, dims_embed, num_heads, dim_heads):
|
| 226 |
+
|
| 227 |
+
super().__init__()
|
| 228 |
+
|
| 229 |
+
assert 2 == len(dims_embed)
|
| 230 |
+
self.dim_heads = dim_heads
|
| 231 |
+
dim_hidden = self.dim_heads * num_heads
|
| 232 |
+
|
| 233 |
+
self.num_heads = num_heads
|
| 234 |
+
self.to_q = nn.Linear( dims_embed[0], dim_hidden, bias = False)
|
| 235 |
+
self.to_kv = nn.Linear( dims_embed[1], 2 * dim_hidden, bias = False)
|
| 236 |
+
|
| 237 |
+
#############################################
|
| 238 |
+
def forward(self, x, kv):
|
| 239 |
+
|
| 240 |
+
# per head projection
|
| 241 |
+
q, k, v = (self.to_q(x), *self.to_kv(kv).chunk(2, dim=-1))
|
| 242 |
+
|
| 243 |
+
# extract require shape / dimension parameters
|
| 244 |
+
b, t, d, h, e = *q.shape, self.num_heads, self.dim_heads
|
| 245 |
+
|
| 246 |
+
# reshape and transpose so that axial dimension is second but last
|
| 247 |
+
mh = lambda x: x.reshape(b, -1, h, e).transpose(1, 2).reshape(b * h, -1, e)
|
| 248 |
+
q, k, v = map( mh, (q, k, v))
|
| 249 |
+
|
| 250 |
+
# compute attention
|
| 251 |
+
dots = torch.einsum('bie,bje->bij', q, k) * (e ** -0.5)
|
| 252 |
+
dots = dots.softmax(dim=-1)
|
| 253 |
+
out = torch.einsum('bij,bje->bie', dots, v)
|
| 254 |
+
|
| 255 |
+
# recover input shape
|
| 256 |
+
out = out.reshape(b, h, -1, e).transpose(1, 2).reshape(b, -1, d)
|
| 257 |
+
|
| 258 |
+
return out
|
| 259 |
+
|
| 260 |
+
####################################################################################################
|
| 261 |
+
|
| 262 |
+
class MultiFieldAxialAttention( nn.Module):
|
| 263 |
+
|
| 264 |
+
#############################################
|
| 265 |
+
def __init__(self, att_dims, dims_embed, num_heads_self, num_heads_cross,
|
| 266 |
+
sum_axial_out = False, dropout_rate = 0.0):
|
| 267 |
+
|
| 268 |
+
super().__init__()
|
| 269 |
+
|
| 270 |
+
assert 0 == (dims_embed[0] % (num_heads_self + num_heads_cross))
|
| 271 |
+
dim_embed_heads = int(dims_embed[0] / (num_heads_self + num_heads_cross))
|
| 272 |
+
|
| 273 |
+
self.num_fields = len(dims_embed)
|
| 274 |
+
self.dims_embed = dims_embed
|
| 275 |
+
self.att_dims = att_dims
|
| 276 |
+
num_dimensions = 3 # vertical, time, space (folded together)
|
| 277 |
+
idx_embed = -1
|
| 278 |
+
|
| 279 |
+
self.sum_axial_out = sum_axial_out
|
| 280 |
+
|
| 281 |
+
self.lnorms = torch.nn.ModuleList()
|
| 282 |
+
for ifield in range( self.num_fields) :
|
| 283 |
+
self.lnorms.append( nn.LayerNorm( dims_embed[ifield], elementwise_affine=False) )
|
| 284 |
+
|
| 285 |
+
# for all axes, create self and cross attention heads
|
| 286 |
+
self.axial_attentions = nn.ModuleList( [nn.ModuleList() for _ in att_dims] )
|
| 287 |
+
for ip, permutation in enumerate(calculate_permutations( num_dimensions, idx_embed, att_dims)) :
|
| 288 |
+
|
| 289 |
+
# self-attention
|
| 290 |
+
self.axial_attentions[ip].append( PermuteToFrom( permutation, CrossAttention( [dims_embed[0],dims_embed[0]], num_heads_self, dim_embed_heads)))
|
| 291 |
+
|
| 292 |
+
# cross attention
|
| 293 |
+
for n in range( 1, len(dims_embed)) :
|
| 294 |
+
hs = PermuteToFrom( permutation, CrossAttention( [dims_embed[0], dims_embed[n]],
|
| 295 |
+
num_heads_cross, dim_embed_heads))
|
| 296 |
+
self.axial_attentions[ip].append( hs)
|
| 297 |
+
|
| 298 |
+
dim_hidden = dim_embed_heads * (num_heads_self + (num_heads_cross * (self.num_fields-1)) )
|
| 299 |
+
to_outs = [nn.Linear( dim_hidden, dims_embed[0]) for _ in att_dims]
|
| 300 |
+
self.to_outs = torch.nn.ModuleList( to_outs)
|
| 301 |
+
|
| 302 |
+
self.dropout = torch.nn.Dropout( p=dropout_rate)
|
| 303 |
+
|
| 304 |
+
#############################################
|
| 305 |
+
def forward(self, *args):
|
| 306 |
+
|
| 307 |
+
pass_through = args[0]
|
| 308 |
+
fields_lnormed = [self.lnorms[ifield](field) for ifield, field in enumerate( args)]
|
| 309 |
+
|
| 310 |
+
out = []
|
| 311 |
+
for i_ax, axial_attns in enumerate( self.axial_attentions):
|
| 312 |
+
outs_axis = []
|
| 313 |
+
for i, attn_heads in enumerate(axial_attns) :
|
| 314 |
+
outs_axis.append( attn_heads( fields_lnormed[0], fields_lnormed[i]) )
|
| 315 |
+
ax_out = self.to_outs[i_ax]( torch.cat( outs_axis, -1) )
|
| 316 |
+
# parallel processing with sum_axial_out and then summation or serial otherwise
|
| 317 |
+
if self.sum_axial_out :
|
| 318 |
+
out.append( ax_out)
|
| 319 |
+
else :
|
| 320 |
+
fields_lnormed[0] = ax_out
|
| 321 |
+
|
| 322 |
+
if self.sum_axial_out :
|
| 323 |
+
out = sum( out)
|
| 324 |
+
else :
|
| 325 |
+
out = fields_lnormed[0]
|
| 326 |
+
|
| 327 |
+
out = self.dropout( out)
|
| 328 |
+
|
| 329 |
+
return pass_through + out
|
vendor/atmorep-official/atmorep/transformer/decoder.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import numpy as np
|
| 19 |
+
import math
|
| 20 |
+
|
| 21 |
+
from atmorep.transformer.transformer_base import MLP
|
| 22 |
+
from atmorep.transformer.transformer_attention import MultiSelfAttentionHead, MultiCrossAttentionHead
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class Decoder(torch.nn.Module) :
|
| 26 |
+
|
| 27 |
+
###################################
|
| 28 |
+
def __init__(self, num_layers, dim_embed = 2048,
|
| 29 |
+
num_heads = 8, num_mlp_layers = 2,
|
| 30 |
+
self_att = False, cross_att_ratio = 0.5 ):
|
| 31 |
+
'''
|
| 32 |
+
Vaswani transformer corresponds to self_att = True and cross_att_ratio = 1.
|
| 33 |
+
'''
|
| 34 |
+
super( Decoder, self).__init__()
|
| 35 |
+
|
| 36 |
+
self.num_layers = num_layers
|
| 37 |
+
self.dim_embed = dim_embed
|
| 38 |
+
|
| 39 |
+
self.len_block = 2
|
| 40 |
+
if self_att :
|
| 41 |
+
self.len_block = 3
|
| 42 |
+
|
| 43 |
+
num_heads_other = int(num_heads * cross_att_ratio)
|
| 44 |
+
num_heads_self = num_heads - num_heads_other
|
| 45 |
+
|
| 46 |
+
self.blocks = torch.nn.ModuleList()
|
| 47 |
+
for _ in range( self.num_layers) :
|
| 48 |
+
# attention sub-block
|
| 49 |
+
if self_att :
|
| 50 |
+
self.blocks.append( MultiSelfAttentionHead( dim_embed, num_heads_self))
|
| 51 |
+
# cross attention between encoder and decoder
|
| 52 |
+
self.blocks.append( MultiCrossAttentionHead( dim_embed, num_heads_self, num_heads_other))
|
| 53 |
+
# feature space mapping sub-block
|
| 54 |
+
self.blocks.append( MLP( dim_embed, num_mlp_layers))
|
| 55 |
+
|
| 56 |
+
###################################
|
| 57 |
+
def forward(self, token_seq_embed, encoder_out):
|
| 58 |
+
'''Evaluate decoder'''
|
| 59 |
+
|
| 60 |
+
for il in range(self.num_layers) :
|
| 61 |
+
token_seq_embed = self.blocks[2*il]( token_seq_embed, encoder_out[il] )
|
| 62 |
+
token_seq_embed = self.blocks[2*il+1]( token_seq_embed, encoder_out[il] )
|
| 63 |
+
|
| 64 |
+
return token_seq_embed
|
| 65 |
+
|
| 66 |
+
###################################
|
| 67 |
+
def get_attention( self, xin, iblock) :
|
| 68 |
+
'''
|
| 69 |
+
Get attention and projected values from specific layer and her head
|
| 70 |
+
'''
|
| 71 |
+
|
| 72 |
+
assert False
|
| 73 |
+
|
| 74 |
+
# # embedding
|
| 75 |
+
# token_seq_embed = prepare_token( self, xin, self.size_token_info)
|
| 76 |
+
|
| 77 |
+
# # attention heads + feature space mappings
|
| 78 |
+
# for idx in range(2*iblock) :
|
| 79 |
+
# token_seq_embed = self.blocks[idx]( token_seq_embed)
|
| 80 |
+
# atts, vsh = self.blocks[2*iblock].get_attention( token_seq_embed)
|
| 81 |
+
|
| 82 |
+
# return (atts, vsh)
|
vendor/atmorep-official/atmorep/transformer/interformer.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####################################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2022
|
| 4 |
+
#
|
| 5 |
+
####################################################################################################
|
| 6 |
+
#
|
| 7 |
+
# project : atmorep
|
| 8 |
+
#
|
| 9 |
+
# author : atmorep collaboration
|
| 10 |
+
#
|
| 11 |
+
# description :
|
| 12 |
+
#
|
| 13 |
+
# license :
|
| 14 |
+
#
|
| 15 |
+
####################################################################################################
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import numpy as np
|
| 19 |
+
import math
|
| 20 |
+
|
| 21 |
+
from atmorep.transformer.transformer_base import MLP
|
| 22 |
+
from atmorep.transformer.transformer_attention import MultiInterAttentionHead
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class Interformer(torch.nn.Module) :
|
| 26 |
+
|
| 27 |
+
def __init__(self, num_layers, dim_input, dims_embed,
|
| 28 |
+
num_heads_self = 2, num_heads_coupling = 2, num_mlp_layers = 2,
|
| 29 |
+
num_tokens = [], size_token_info = 6):
|
| 30 |
+
'''
|
| 31 |
+
|
| 32 |
+
'''
|
| 33 |
+
|
| 34 |
+
super(Interformer, self).__init__()
|
| 35 |
+
self.num_layers = num_layers
|
| 36 |
+
self.num_tokens = num_tokens
|
| 37 |
+
self.size_token_info = size_token_info
|
| 38 |
+
|
| 39 |
+
# learnable linear embedding
|
| 40 |
+
self.token_embed = torch.nn.Linear( dim_input, dims_embed[0] - size_token_info)
|
| 41 |
+
|
| 42 |
+
self.blocks = torch.nn.ModuleList()
|
| 43 |
+
for _ in range( num_layers) :
|
| 44 |
+
# attention sub-block
|
| 45 |
+
self.blocks.append( MultiInterAttentionHead( dims_embed, num_heads_self, num_heads_coupling,
|
| 46 |
+
num_tokens ))
|
| 47 |
+
# feature space mapping sub-block
|
| 48 |
+
self.blocks.append( MLP( dims_embed[0], num_mlp_layers))
|
| 49 |
+
|
| 50 |
+
def forward(self, xin):
|
| 51 |
+
# never called directly but only through multiformer
|
| 52 |
+
assert False
|