Zhongning commited on
Commit
5a5d1a8
·
verified ·
1 Parent(s): 8e2f109

Upload folder using huggingface_hub

Browse files
.DS_Store ADDED
Binary file (6.15 kB). View file
 
.ms_upload_cache ADDED
@@ -0,0 +1 @@
 
 
1
+ {"version": 3, "repo_id": "OneScience/Deep-Generative-Model-of-Rain", "files": {"README.md|1787118190.0|6414": {"hash": "ae8b571106cc431550791a52cd3d4c6710031e84363fda4e4a07a3da2ce590d5", "size": 6414, "status": "c"}, "model/__pycache__/dgmr.cpython-311.pyc|1787129401.0|5498": {"hash": "319d638e43312b0c32a8a81e3283ebf22a0c12842183c09091a40a588d08ba69", "size": 5498, "status": "c"}, "conf/config.yaml|1787189067.0|2278": {"hash": "5f07acfc8e3e487a48ebea90399a0207f3ac805a64d16ae7e64da0c749fc2ec8", "size": 2278, "status": "c"}, "model/dgmr_official/__pycache__/__init__.cpython-311.pyc|1787129401.0|506": {"hash": "55bd930076be8ece868e147ca91dbeebee990f54f13d2e470e68221ce5fb631a", "size": 506, "status": "c"}, "LICENSE|1787047051.0|1073": {"hash": "6a9b90043bd7e6836db9a58125c81141a4ee4b8a538aabe33f6dc5180ecff09f", "size": 1073, "status": "c"}, "configuration.json|1787059848.0|39": {"hash": "6b6f30f1e82ad5243c2ff583dcd33b77949442fbf942d80d9f236ffd7a9fe796", "size": 39, "status": "c"}, "model/dgmr.py|1787047183.0|4699": {"hash": "7568f5283b46786a9c034a4cbe5dae727f465bc6184091e239e8f5227bba12a7", "size": 4699, "status": "c"}, "model/dgmr_official/__init__.py|1787047065.0|275": {"hash": "203ab3311b15f33e6130ef33d2da92b820aee85278ba2883ddecdc2af957e790", "size": 275, "status": "c"}, "model/dgmr_official/__pycache__/common.cpython-311.pyc|1787129401.0|20655": {"hash": "52fa87eb304c8ee51b27c65095747576f5b7bf101362b22609f7f4ae87ad299b", "size": 20655, "status": "c"}, "model/dgmr_official/__pycache__/discriminators.cpython-311.pyc|1787129401.0|10887": {"hash": "944d0b3e3fb10fe60264dc396a777d0d10bc3b7a0f55a6676985cb3bfd326d7d", "size": 10887, "status": "c"}, "model/dgmr_official/__pycache__/generators.cpython-311.pyc|1787129402.0|11778": {"hash": "2d7c85d6ed1eab70efce07cc40f227282fd85fa8cd115b79187f59a4a7e7cb81", "size": 11778, "status": "c"}, "model/dgmr_official/__pycache__/losses.cpython-311.pyc|1787129402.0|5091": {"hash": "0e3704bb86d91c76e3a42989c87f622d6c1f989665ea13fe962e00ff10d1efa8", "size": 5091, "status": "c"}, "model/dgmr_official/common.py|1787047241.0|17391": {"hash": "91883f5d9de44bf2821bfa01991fa1b2280f74e2f63089ea1d3c4ad75aea1afa", "size": 17391, "status": "c"}, "model/dgmr_official/discriminators.py|1787047246.0|8891": {"hash": "8f6d93f6008527e3882467d2d57f4244f050076c7bcc633b8dcfb4f33410f260", "size": 8891, "status": "c"}, "model/dgmr_official/generators.py|1787047246.0|7749": {"hash": "1e9ceb835b6d0b7f5aae389169569c29dc8419ccb21c1145f50a8f7c22470102", "size": 7749, "status": "c"}, "model/dgmr_official/layers/Attention.py|1787047051.0|2824": {"hash": "a0e04195b17d9fba45f1b58e37a58524c855f949b86bee52eb370511527e3b57", "size": 2824, "status": "c"}, "model/dgmr_official/layers/ConvGRU.py|1787047051.0|3474": {"hash": "8e97e7df8deeaa3e497365fbb5932b1a7ae2d13cf82656f82e2c40d9985c7e41", "size": 3474, "status": "c"}, "model/dgmr_official/layers/CoordConv.py|1787047051.0|2482": {"hash": "e076d5d5034bcf1bc8a16693fbbf8e348e83b65399c66250dad6b9c6bbcd9915", "size": 2482, "status": "c"}, "model/dgmr_official/layers/__init__.py|1787047051.0|134": {"hash": "37a61b078b8814e0a8c5dce59c246f4cbf83acc6a885a60e7a5afc7bf347d27b", "size": 134, "status": "c"}, "model/dgmr_official/layers/__pycache__/Attention.cpython-311.pyc|1787129401.0|3816": {"hash": "80461302aaefcdbd65d0c30e5bba4e6fbec53f20d2f0b287d3011ba5065b2ac7", "size": 3816, "status": "c"}, "model/dgmr_official/layers/__pycache__/ConvGRU.cpython-311.pyc|1787129401.0|4885": {"hash": "e12ad2510292b13e7a135eeeb59a50f7e4d4221d984fa5c1eccccdb495910f32", "size": 4885, "status": "c"}, "model/dgmr_official/layers/__pycache__/CoordConv.cpython-311.pyc|1787129401.0|4614": {"hash": "870a9883bfc11c72463b31ae5b8c3362147f74c8760870169fbb66067f66f446", "size": 4614, "status": "c"}, "model/dgmr_official/layers/__pycache__/__init__.cpython-311.pyc|1787129401.0|423": {"hash": "ca06f7dc005aca0179c32edcdfbde241b54ca923694ab232a4c1f220b5be73b2", "size": 423, "status": "c"}, "model/dgmr_official/layers/__pycache__/utils.cpython-311.pyc|1787129401.0|1008": {"hash": "dcb03036c637d7fed95d1d5f794857a1d4662a77b1b366a978fc985cb9b8d84e", "size": 1008, "status": "c"}, "model/dgmr_official/layers/utils.py|1787047246.0|528": {"hash": "37926142863e60911d80c7208ee72380a1dccbc759c14101208c6c4abfe3271f", "size": 528, "status": "c"}, "model/dgmr_official/losses.py|1787047133.0|2873": {"hash": "38a72b2b11ca5cd6f2920ac5fbb32fb70bb6c8c6eda7e34a04e381d1ec5571b5", "size": 2873, "status": "c"}, "scripts/fake_data.py|1787118238.0|3066": {"hash": "88dc76ffa492214503a7446d8880234fe14ec88504b1a97bcc28b232f005f8f5", "size": 3066, "status": "c"}, "scripts/inference.py|1787060096.0|3338": {"hash": "e52039c1c3fe77bd130b2379b5d15e8db518e41bb92c206ca8f6f107ea77ecd3", "size": 3338, "status": "c"}, "scripts/train.py|1787060016.0|11345": {"hash": "62c589a0058374338138d065856a6290a839c614cf62133efa4f338621fa761e", "size": 11345, "status": "c"}, "weight/.gitkeep|1787059908.0|1": {"hash": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", "size": 1, "status": "c"}, "scripts/result.py|1787129360.0|8669": {"hash": "2041021d7575eb66e89f2577174a338e6cd2f063826f556a8880bd344306a05c", "size": 8669, "status": "c"}}}
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Open Climate Fix
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.
README.md ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ language:
4
+ - en
5
+ - zh
6
+ tags:
7
+ - OneScience
8
+ - Earth science
9
+ - Weather forecasting
10
+ - Short-term precipitation nowcasting
11
+ - Deep generative models
12
+ - Generative adversarial networks
13
+ frameworks: PyTorch
14
+ datasets:
15
+ - OneScience/ERA5
16
+ ---
17
+ <p align="center">
18
+ <strong>
19
+ <span style="font-size: 30px;">DGMR</span>
20
+ </strong>
21
+ </p>
22
+
23
+
24
+ # Model Introduction
25
+
26
+ DGMR (Deep Generative Model of Radar) was proposed by DeepMind in 2021. It is a short-term precipitation nowcasting model based on a conditional generative adversarial network (cGAN). The generator consists of a latent conditioning stack, a context conditioning stack, and an autoregressive sampler based on multiple ConvGRU layers. The discriminator evaluates both spatial and temporal dimensions, and the training objective combines hinge GAN loss with a grid-cell regularizer.
27
+
28
+ Paper:Skillful Precipitation Nowcasting using Deep Generative Models of Radar
29
+
30
+ https://arxiv.org/abs/2104.00954
31
+
32
+ # Model Description
33
+
34
+ DGMR is a probabilistic short-term precipitation nowcasting model: it takes four consecutive radar echo frames and generates 18 future frames at once (5-minute steps, 90 minutes in total, in the paper). Its output is a sample rather than a deterministic estimate. This repository is organized from the official Open Climate Fix PyTorch implementation (`openclimatefix/dgmr`, MIT License) and integrated with the OneScience data loading and training workflow.
35
+
36
+ # Use Cases
37
+
38
+ | Scenario | Description |
39
+ | :---: | :--- |
40
+ | Short-term precipitation nowcasting research | Train a cGAN on radar echo sequences to generate future radar echo frames. |
41
+ | Probabilistic forecast output | Obtain multiple generated samples of future fields through latent-variable sampling. |
42
+ | Local quick validation | Use synthetic data to check data loading, training, inference, and result scripts. |
43
+ | ModelScope/OneCode execution | Download the model package, install dependencies, and run the scripts directly. |
44
+ | Multi-card training | Launch multi-process training with `torchrun`. |
45
+
46
+
47
+ # Usage
48
+
49
+ ## 1. OneCode Usage
50
+
51
+ Use the OneCode online environment for intelligent one-click AI4S programming:
52
+
53
+ [Try intelligent one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
54
+
55
+ ## 2. Manual Installation and Usage
56
+
57
+ **Hardware Requirements**
58
+
59
+ - GPU or DCU is recommended.
60
+ - CPU can be used for imports and small-configuration connectivity validation, but full training and inference are slower.
61
+ - DCU users must install DTK beforehand. DTK 25.04.2 or later, or the OneScience-recommended version matching the current cluster, is recommended.
62
+
63
+
64
+ ### Download the Model Package
65
+
66
+ ```bash
67
+ hf download OneScience-Group/DGMR --local-dir ./DGMR
68
+ cd DGMR
69
+ ```
70
+
71
+ ### Install the Runtime Environment
72
+
73
+ **DCU Environment**
74
+
75
+ ```bash
76
+ # Activate DTK and CONDA first
77
+ conda create -n onescience311 python=3.11 -y
78
+ conda activate onescience311
79
+ # uv installation is supported
80
+ pip install onescience[earth-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
81
+ ```
82
+
83
+ **GPU Environment**
84
+ ```bash
85
+ # Activate CONDA first
86
+ conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
87
+ conda activate onescience311
88
+ # uv installation is supported
89
+ pip install onescience[earth-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
90
+ ```
91
+
92
+ ### Training Data
93
+
94
+ The OneScience community provides ERA5 data for training (the current repository contains complete data slices subject to data-file size limits). Download it with the command below and confirm that the data path in `conf/config.yaml` is correct:
95
+
96
+ ```bash
97
+ hf download --repo-type dataset OneScience-Group/ERA5 --local-dir ./data
98
+ ```
99
+
100
+ For a quick workflow validation, run the synthetic data script first:
101
+
102
+ ```bash
103
+ python scripts/fake_data.py
104
+ ```
105
+
106
+ > Note: `scripts/fake_data.py` generates a single-channel radar sequence from `num_context`, `forecast_steps`, batch size, and radar grid size. The current small configuration uses 4 input frames, 6 output frames, and a 128×128 grid.
107
+
108
+ ### Training
109
+
110
+ Single card:
111
+
112
+ ```bash
113
+ python scripts/train.py
114
+ ```
115
+
116
+ Multiple cards:
117
+
118
+ ```bash
119
+ torchrun --nproc_per_node=8 --nnodes=1 --rdzv_id=1000 --rdzv_backend=c10d --max_restarts=0 --master_addr="localhost" --master_port=29500 scripts/train.py
120
+ ```
121
+
122
+ Training outputs:
123
+
124
+ ```text
125
+ data/checkpoints/model_bak.pth
126
+ data/checkpoints/trloss.npy
127
+ data/checkpoints/valoss.npy
128
+ ```
129
+
130
+ ### Training Weights
131
+ The `weight/` folder is reserved for model weights. Pretrained weights are not provided by default; users may train the model using the paper configuration. Official DeepMind pretrained weights require access approval and differ from the current configuration in channel count and grid size, so they must be aligned before loading.
132
+
133
+ ### Inference
134
+
135
+ Inference reads `data/checkpoints/model_bak.pth`:
136
+
137
+ ```bash
138
+ python scripts/inference.py
139
+ ```
140
+
141
+ Prediction results are written frame by frame to:
142
+
143
+ ```text
144
+ result/output/
145
+ ```
146
+
147
+ ### Evaluation and Visualization
148
+
149
+ ```bash
150
+ python scripts/result.py
151
+ ```
152
+
153
+ Outputs include:
154
+
155
+ - `result/rmse.npy`
156
+ - `result/acc.npy`
157
+ - `result/loss.png`
158
+ - Forecast comparison plots for the specified date and variables
159
+
160
+
161
+ # Official Source and Reproduction Notes
162
+
163
+ - The model implementation comes from the official Open Climate Fix PyTorch package `openclimatefix/dgmr` (MIT License). The related network modules (common/layers/generators/discriminators/losses) are embedded unchanged in `model/dgmr_official/` (with the HuggingFace hub mixin and pytorch_lightning training loop removed); `model/dgmr.py` is only a YAML-driven thin wrapper.
164
+ - `conf/config.yaml` uses a small configuration (`forecast_steps=6`, `output_shape=128`, `latent_channels=384`, `context_channels=192`) for connectivity validation by default; paper-level reproduction requires 4→18 frames, a 256×256 grid, `latent_channels=768`, and `context_channels=384`.
165
+ - The paper-level configuration (`num_context=4`, `forecast_steps=18`) requires at least `T >= num_context + forecast_steps + 1 = 23` frames per year; the current synthetic data uses `T=10` (`time_step=6h`), so the default configuration uses 4→6 frames.
166
+ - The discriminator contains BatchNorm1d, so the training batch must be `>= 2` (with synthetic data, the training set uses two samples from two years, exactly one batch).
167
+ - The following details are not disclosed in the paper and are assumptions in this reproduction:data normalization statistics (identity normalization is currently used; real statistics will be supplied with the data), and the number of randomly sampled time frames in the discriminator.
168
+
169
+ # Official OneScience Information
170
+
171
+ | Platform | OneScience Main Repository | Skills Repository |
172
+ | --- | --- | --- |
173
+ | Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
174
+ | GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
175
+
176
+ # Citation and License
177
+
178
+ - This repository is an independent organization and adaptation of DGMR. The model source is based on the Open Climate Fix `openclimatefix/dgmr` implementation and follows the MIT License.
179
+ - Please cite:Ravuri et al. Skilful Precipitation Nowcasting using Deep Generative Models of Radar. Nature 597, 672-677, 2021.
conf/config.yaml ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DGMR 训练配置示例
2
+ # 论文配置:4 帧雷达观测上下文(num_context=4)预测 18 帧(forecast_steps=18),
3
+ # 空间 256×256 单通道,latent_channels=768 / context_channels=384(生成器约 13.4M 参数)。
4
+ # 当前默认配置为连通性验证小配置:空间 128×128,预测 6 帧;fake_data.py
5
+ # 根据 num_context、forecast_steps 与 batch_size 自动推导每年所需时间长度 T。
6
+ model:
7
+ start_epoch: 0
8
+ max_epoch: 100
9
+ lr: 1E-4 # 论文使用 Adam lr=1e-4 分别训练生成器与判别器
10
+ lr_disc: 1E-4
11
+ patience: 50
12
+ checkpoint_dir: "./data/checkpoints"
13
+
14
+ # DGMR 结构参数(论文值见注释)
15
+ forecast_steps: 6 # 预测帧数,论文为 18;小配置取 6
16
+ num_context: 4 # 上下文(观测)帧数,论文为 4
17
+ input_channels: 1 # 每帧通道数,论文为 1(雷达回波)
18
+ output_shape: 128 # 空间尺寸(需 32 整除),论文为 256
19
+ conv_type: "standard" # standard / coord / 3d
20
+ latent_channels: 384 # 论文为 768
21
+ context_channels: 192 # 论文为 384
22
+ generation_steps: 6 # 网格单元正则器 Monte-Carlo 采样次数,论文为 6
23
+ grid_lambda: 20.0 # 网格单元正则器权重,论文为 20
24
+ precip_weight_cap: 24.0 # 网格单元权重函数上限,论文为 24
25
+
26
+ # 整个数据读取流程
27
+ datapipe:
28
+ name: "ERA5"
29
+ task: "weather_forecasting"
30
+
31
+ dataset:
32
+ type: "hdf5"
33
+ data_dir: './data/'
34
+ train_time: [1951, 1952]
35
+ val_time: [1953]
36
+ test_time: [1954]
37
+ img_size: [128, 128]
38
+ verbose: true
39
+ cache: false
40
+
41
+ # 气象变量(DGMR 面向单通道雷达回波场,此处为占位变量名)
42
+ channels: ['rain_radar']
43
+
44
+ # DataLoader 配置
45
+ # 注意:DGMR 判别器内部含 BatchNorm1d,训练 batch 必须 >= 2。
46
+ # fake_data.py 会保证每个年份至少产生 batch_size 个完整时间窗口。
47
+ dataloader:
48
+ mask_dtype: "float32"
49
+ batch_size: 2
50
+ num_workers: 1
51
+ pin_memory: true
52
+ drop_last: true
53
+ shuffle: false
54
+ prefetch_factor: 2
55
+ persistent_workers: true
56
+
57
+ # 分布式配置
58
+ distributed:
59
+ enabled: true
60
+ sampler: "DistributedSampler"
61
+ rank: 0
62
+ world_size: 2
63
+ shuffle: true
64
+ seed: 42
65
+ drop_last: true
config.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_name": "DGMR",
3
+ "model_type": "dgmr",
4
+ "architectures": [
5
+ "DGMR"
6
+ ],
7
+ "framework": "PyTorch",
8
+ "domain": "climate-and-atmosphere",
9
+ "task": "precipitation-nowcasting",
10
+ "implementation": {
11
+ "entry_point": "model/dgmr.py",
12
+ "scope": "YAML-configured DGMR conditional GAN wrapper with vendored Open Climate Fix generator, spatial and temporal discriminators, and training losses"
13
+ },
14
+ "architecture": {
15
+ "family": "conditional generative adversarial network for probabilistic radar nowcasting",
16
+ "generator": "context conditioning stack plus latent conditioning stack and a four-scale ConvGRU autoregressive sampler",
17
+ "discriminator": "combined spatial and temporal discriminators with spectrally normalized residual blocks",
18
+ "training_objective": "hinge GAN loss plus weighted grid-cell regularizer",
19
+ "input_format": "BTCHW",
20
+ "output_format": "BTCHW",
21
+ "activation": "ReLU",
22
+ "normalization": "BatchNorm",
23
+ "repository_default_config": {
24
+ "purpose": "small connectivity-validation configuration",
25
+ "num_context": 4,
26
+ "forecast_steps": 6,
27
+ "input_channels": 1,
28
+ "output_shape": 128,
29
+ "conv_type": "standard",
30
+ "latent_channels": 384,
31
+ "context_channels": 192,
32
+ "generation_steps": 6,
33
+ "grid_lambda": 20.0,
34
+ "precip_weight_cap": 24.0
35
+ },
36
+ "paper_configuration": {
37
+ "num_context": 4,
38
+ "forecast_steps": 18,
39
+ "input_channels": 1,
40
+ "output_shape": 256,
41
+ "latent_channels": 768,
42
+ "context_channels": 384,
43
+ "generation_steps": 6,
44
+ "grid_lambda": 20.0,
45
+ "precip_weight_cap": 24.0
46
+ }
47
+ },
48
+ "data": {
49
+ "dataset": "ERA5Datapipe-compatible HDF5 placeholder radar sequences",
50
+ "variable": "rain_radar",
51
+ "input_length": 4,
52
+ "output_length": 6,
53
+ "channels": 1,
54
+ "default_smoke_spatial_size": [
55
+ 128,
56
+ 128
57
+ ],
58
+ "paper_frame_interval_minutes": 5,
59
+ "paper_output_length": 18,
60
+ "paper_spatial_size": [
61
+ 256,
62
+ 256
63
+ ],
64
+ "normalization": "identity statistics in synthetic data; real-data statistics are read from HDF5"
65
+ },
66
+ "configuration_sources": [
67
+ "conf/config.yaml",
68
+ "model/dgmr.py",
69
+ "model/dgmr_official/",
70
+ "scripts/train.py",
71
+ "scripts/fake_data.py",
72
+ "README.md"
73
+ ]
74
+ }
configuration.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"framework":"Pytorch","task":"other"}
model/__pycache__/dgmr.cpython-311.pyc ADDED
Binary file (5.5 kB). View file
 
model/dgmr.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ #
5
+ # Copyright (c) 2021 Open Climate Fix
6
+ #
7
+ # This module is a thin configuration wrapper around the DGMR (Deep
8
+ # Generative Model of Radar) architecture from Ravuri et al. (2021,
9
+ # "Skilful Precipitation Nowcasting using Deep Generative Models of Radar",
10
+ # Nature 597), as re-implemented in PyTorch by Open Climate Fix
11
+ # (``openclimatefix/skillful_nowcasting``, MIT License). The network modules
12
+ # are vendored verbatim (minus HuggingFace hub mixins) under
13
+ # ``dgmr_official/``; only config plumbing and the plain ``forward`` are
14
+ # added here for YAML-driven usage.
15
+ import torch
16
+ import torch.nn as nn
17
+
18
+ from model.dgmr_official.common import ContextConditioningStack, LatentConditioningStack
19
+ from model.dgmr_official.discriminators import Discriminator
20
+ from model.dgmr_official.generators import Generator, Sampler
21
+ from model.dgmr_official.losses import GridCellLoss
22
+
23
+
24
+ def weight_fn(y, precip_weight_cap=24.0):
25
+ """
26
+ Weight function for the grid cell loss: w(y) = max(y + 1, cap).
27
+ """
28
+ return torch.max(y + 1, torch.tensor(precip_weight_cap, device=y.device))
29
+
30
+
31
+ class DGMR(nn.Module):
32
+ """
33
+ Config-driven DGMR wrapper (generator + discriminator).
34
+
35
+ The generator is a conditional GAN generator that takes ``num_context``
36
+ observed radar frames of shape [B, T, C, H, W] and produces
37
+ ``forecast_steps`` future frames of the same spatial size. The
38
+ discriminator scores full sequences (context + forecast) spatially and
39
+ temporally; during GAN training the hinge losses plus the grid-cell
40
+ regularizer are applied (see ``dgmr_official/losses.py`` and the paper).
41
+
42
+ Args:
43
+ forecast_steps: Number of frames to predict in the future (paper: 18).
44
+ num_context: Number of input/context frames (paper: 4).
45
+ input_channels: Number of channels per frame (paper: 1, radar).
46
+ output_shape: Spatial size of the frames; must be divisible by 32
47
+ (paper: 256). Discriminators additionally need >= 128 px.
48
+ conv_type: Convolution flavour used by the conditioning stack,
49
+ one of "standard" / "coord" / "3d".
50
+ latent_channels / context_channels: DGMR architecture sizes
51
+ (paper: 768 / 384).
52
+ generation_steps: Number of Monte-Carlo generator samples used when
53
+ computing the grid-cell regularizer during training (paper: 6).
54
+ grid_lambda: Weight of the grid-cell regularizer (paper: 20).
55
+ precip_weight_cap: Ceiling for the grid-cell weight function (paper: 24).
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ forecast_steps: int = 18,
61
+ num_context: int = 4,
62
+ input_channels: int = 1,
63
+ output_shape: int = 256,
64
+ conv_type: str = "standard",
65
+ latent_channels: int = 768,
66
+ context_channels: int = 384,
67
+ generation_steps: int = 6,
68
+ grid_lambda: float = 20.0,
69
+ precip_weight_cap: float = 24.0,
70
+ ):
71
+ super().__init__()
72
+ self.forecast_steps = int(forecast_steps)
73
+ self.num_context = int(num_context)
74
+ self.input_channels = int(input_channels)
75
+ self.output_shape = int(output_shape)
76
+ self.conv_type = conv_type
77
+ self.latent_channels = int(latent_channels)
78
+ self.context_channels = int(context_channels)
79
+ self.generation_steps = int(generation_steps)
80
+ self.grid_lambda = float(grid_lambda)
81
+ self.precip_weight_cap = float(precip_weight_cap)
82
+
83
+ self.conditioning_stack = ContextConditioningStack(
84
+ input_channels=self.input_channels,
85
+ conv_type=self.conv_type,
86
+ output_channels=self.context_channels,
87
+ )
88
+ self.latent_stack = LatentConditioningStack(
89
+ shape=(
90
+ 8 * self.input_channels,
91
+ self.output_shape // 32,
92
+ self.output_shape // 32,
93
+ ),
94
+ output_channels=self.latent_channels,
95
+ )
96
+ self.sampler = Sampler(
97
+ forecast_steps=self.forecast_steps,
98
+ latent_channels=self.latent_channels,
99
+ context_channels=self.context_channels,
100
+ )
101
+ self.generator = Generator(self.conditioning_stack, self.latent_stack, self.sampler)
102
+ self.discriminator = Discriminator(self.input_channels)
103
+ self.grid_regularizer = GridCellLoss(
104
+ weight_fn=weight_fn, precip_weight_cap=self.precip_weight_cap
105
+ )
106
+
107
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
108
+ """
109
+ Args:
110
+ x: Observed radar frames, shape [batch, num_context, C, H, W].
111
+ Returns:
112
+ Forecast frames, shape [batch, forecast_steps, C, H, W].
113
+ """
114
+ return self.generator(x)
model/dgmr_official/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """Vendored DGMR (Deep Generative Model of Radar) modules.
2
+
3
+ Stripped from the ``dgmr`` PyTorch package (Open Climate Fix, MIT License):
4
+ the ``PyTorchModelHubMixin`` HuggingFace mixin and the Lightning training
5
+ loop are removed; the model modules themselves are verbatim.
6
+ """
model/dgmr_official/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (506 Bytes). View file
 
model/dgmr_official/__pycache__/common.cpython-311.pyc ADDED
Binary file (20.7 kB). View file
 
model/dgmr_official/__pycache__/discriminators.cpython-311.pyc ADDED
Binary file (10.9 kB). View file
 
model/dgmr_official/__pycache__/generators.cpython-311.pyc ADDED
Binary file (11.8 kB). View file
 
model/dgmr_official/__pycache__/losses.cpython-311.pyc ADDED
Binary file (5.09 kB). View file
 
model/dgmr_official/common.py ADDED
@@ -0,0 +1,497 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Modules for generator blocks."""
2
+
3
+ from typing import Tuple
4
+
5
+ import einops
6
+ import torch
7
+ import torch.nn.functional as F
8
+
9
+ from torch.distributions import normal
10
+ from torch.nn.modules.pixelshuffle import PixelUnshuffle
11
+ from torch.nn.utils.parametrizations import spectral_norm
12
+
13
+ from .layers import AttentionLayer
14
+ from .layers.utils import get_conv_layer
15
+
16
+
17
+ class GBlock(torch.nn.Module):
18
+ """Residual generator block without upsampling."""
19
+
20
+ def __init__(
21
+ self,
22
+ input_channels: int = 12,
23
+ output_channels: int = 12,
24
+ conv_type: str = "standard",
25
+ spectral_normalized_eps=0.0001,
26
+ ):
27
+ """
28
+ G Block from Skillful Nowcasting, see https://arxiv.org/pdf/2104.00954.pdf.
29
+
30
+ Args:
31
+ input_channels: Number of input channels
32
+ output_channels: Number of output channels
33
+ conv_type: Type of convolution desired, see satflow/models/utils.py for options
34
+ spectral_normalized_eps: constrains the spectral norm of the weights.
35
+ """
36
+ super().__init__()
37
+ self.output_channels = output_channels
38
+ self.bn1 = torch.nn.BatchNorm2d(input_channels)
39
+ self.bn2 = torch.nn.BatchNorm2d(input_channels)
40
+ self.relu = torch.nn.ReLU()
41
+ # Upsample in the 1x1
42
+ conv2d = get_conv_layer(conv_type)
43
+ self.conv_1x1 = spectral_norm(
44
+ conv2d(
45
+ in_channels=input_channels,
46
+ out_channels=output_channels,
47
+ kernel_size=1,
48
+ ),
49
+ eps=spectral_normalized_eps,
50
+ )
51
+ # Upsample 2D conv
52
+ self.first_conv_3x3 = spectral_norm(
53
+ conv2d(
54
+ in_channels=input_channels,
55
+ out_channels=input_channels,
56
+ kernel_size=3,
57
+ padding=1,
58
+ ),
59
+ eps=spectral_normalized_eps,
60
+ )
61
+ self.last_conv_3x3 = spectral_norm(
62
+ conv2d(
63
+ in_channels=input_channels, out_channels=output_channels, kernel_size=3, padding=1
64
+ ),
65
+ eps=spectral_normalized_eps,
66
+ )
67
+
68
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
69
+ """Apply the forward function."""
70
+ # Optionally spectrally normalized 1x1 convolution
71
+ if x.shape[1] != self.output_channels:
72
+ sc = self.conv_1x1(x)
73
+ else:
74
+ sc = x
75
+
76
+ x2 = self.bn1(x)
77
+ x2 = self.relu(x2)
78
+ x2 = self.first_conv_3x3(x2) # Make sure size is doubled
79
+ x2 = self.bn2(x2)
80
+ x2 = self.relu(x2)
81
+ x2 = self.last_conv_3x3(x2)
82
+ # Sum combine, residual connection
83
+ x = x2 + sc
84
+ return x
85
+
86
+
87
+ class UpsampleGBlock(torch.nn.Module):
88
+ """Residual generator block with upsampling."""
89
+
90
+ def __init__(
91
+ self,
92
+ input_channels: int = 12,
93
+ output_channels: int = 12,
94
+ conv_type: str = "standard",
95
+ spectral_normalized_eps=0.0001,
96
+ ):
97
+ """
98
+ G Block from Skillful Nowcasting, see https://arxiv.org/pdf/2104.00954.pdf.
99
+
100
+ Args:
101
+ input_channels: Number of input channels.
102
+ output_channels: Number of output channels.
103
+ conv_type: Type of convolution desired, see satflow/models/utils.py for options.
104
+ spectral_normalized_eps: constrains the spectral norm of the weights.
105
+ """
106
+ super().__init__()
107
+ self.output_channels = output_channels
108
+ self.bn1 = torch.nn.BatchNorm2d(input_channels)
109
+ self.bn2 = torch.nn.BatchNorm2d(input_channels)
110
+ self.relu = torch.nn.ReLU()
111
+ # Upsample in the 1x1
112
+ conv2d = get_conv_layer(conv_type)
113
+ self.conv_1x1 = spectral_norm(
114
+ conv2d(
115
+ in_channels=input_channels,
116
+ out_channels=output_channels,
117
+ kernel_size=1,
118
+ ),
119
+ eps=spectral_normalized_eps,
120
+ )
121
+ self.upsample = torch.nn.Upsample(scale_factor=2, mode="nearest")
122
+ # Upsample 2D conv
123
+ self.first_conv_3x3 = spectral_norm(
124
+ conv2d(
125
+ in_channels=input_channels,
126
+ out_channels=input_channels,
127
+ kernel_size=3,
128
+ padding=1,
129
+ ),
130
+ eps=spectral_normalized_eps,
131
+ )
132
+ self.last_conv_3x3 = spectral_norm(
133
+ conv2d(
134
+ in_channels=input_channels, out_channels=output_channels, kernel_size=3, padding=1
135
+ ),
136
+ eps=spectral_normalized_eps,
137
+ )
138
+
139
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
140
+ """Apply the forward function."""
141
+ # Spectrally nsormalized 1x1 convolution
142
+ sc = self.upsample(x)
143
+ sc = self.conv_1x1(sc)
144
+
145
+ x2 = self.bn1(x)
146
+ x2 = self.relu(x2)
147
+ # Upsample
148
+ x2 = self.upsample(x2)
149
+ x2 = self.first_conv_3x3(x2) # Make sure size is doubled
150
+ x2 = self.bn2(x2)
151
+ x2 = self.relu(x2)
152
+ x2 = self.last_conv_3x3(x2)
153
+ # Sum combine, residual connection
154
+ x = x2 + sc
155
+ return x
156
+
157
+
158
+ class DBlock(torch.nn.Module):
159
+ """D block class."""
160
+
161
+ def __init__(
162
+ self,
163
+ input_channels: int = 12,
164
+ output_channels: int = 12,
165
+ conv_type: str = "standard",
166
+ first_relu: bool = True,
167
+ keep_same_output: bool = False,
168
+ ):
169
+ """
170
+ D and 3D Block from Skillful Nowcasting, see https://arxiv.org/pdf/2104.00954.pdf.
171
+
172
+ Args:
173
+ input_channels: Number of input channels
174
+ output_channels: Number of output channels
175
+ conv_type: Convolution type, see satflow/models/utils.py for options
176
+ first_relu: Whether to have an ReLU before the first 3x3 convolution
177
+ keep_same_output: Whether the output should have the same spatial dimensions
178
+ as input, if False, downscales by 2
179
+ """
180
+ super().__init__()
181
+ self.input_channels = input_channels
182
+ self.output_channels = output_channels
183
+ self.first_relu = first_relu
184
+ self.keep_same_output = keep_same_output
185
+ self.conv_type = conv_type
186
+ conv2d = get_conv_layer(conv_type)
187
+ if conv_type == "3d":
188
+ # 3D Average pooling
189
+ self.pooling = torch.nn.AvgPool3d(kernel_size=2, stride=2)
190
+ else:
191
+ self.pooling = torch.nn.AvgPool2d(kernel_size=2, stride=2)
192
+ self.conv_1x1 = spectral_norm(
193
+ conv2d(
194
+ in_channels=input_channels,
195
+ out_channels=output_channels,
196
+ kernel_size=1,
197
+ )
198
+ )
199
+ self.first_conv_3x3 = spectral_norm(
200
+ conv2d(
201
+ in_channels=input_channels,
202
+ out_channels=output_channels,
203
+ kernel_size=3,
204
+ padding=1,
205
+ )
206
+ )
207
+ self.last_conv_3x3 = spectral_norm(
208
+ conv2d(
209
+ in_channels=output_channels,
210
+ out_channels=output_channels,
211
+ kernel_size=3,
212
+ padding=1,
213
+ stride=1,
214
+ )
215
+ )
216
+ # Downsample at end of 3x3
217
+ self.relu = torch.nn.ReLU()
218
+ # Concatenate to double final channels and keep reduced spatial extent
219
+
220
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
221
+ """Apply the D residual block."""
222
+ if self.input_channels != self.output_channels:
223
+ x1 = self.conv_1x1(x)
224
+ if not self.keep_same_output:
225
+ x1 = self.pooling(x1)
226
+ else:
227
+ x1 = x
228
+
229
+ if self.first_relu:
230
+ x = self.relu(x)
231
+ x = self.first_conv_3x3(x)
232
+ x = self.relu(x)
233
+ x = self.last_conv_3x3(x)
234
+
235
+ if not self.keep_same_output:
236
+ x = self.pooling(x)
237
+ x = x1 + x # Sum the outputs should be half spatial and double channels
238
+ return x
239
+
240
+
241
+ class LBlock(torch.nn.Module):
242
+ """Residual block for the Latent Stack."""
243
+
244
+ def __init__(
245
+ self,
246
+ input_channels: int = 12,
247
+ output_channels: int = 12,
248
+ kernel_size: int = 3,
249
+ conv_type: str = "standard",
250
+ ):
251
+ """
252
+ Initialize the L-block.
253
+
254
+ L-Block for increasing the number of channels in the input
255
+ from Skillful Nowcasting, see https://arxiv.org/pdf/2104.00954.pdf
256
+ Args:
257
+ input_channels: Number of input channels
258
+ output_channels: Number of output channels
259
+ conv_type: Which type of convolution desired, see satflow/models/utils.py for options
260
+ """
261
+ super().__init__()
262
+ # Output size should be channel_out - channel_in
263
+ self.input_channels = input_channels
264
+ self.output_channels = output_channels
265
+ conv2d = get_conv_layer(conv_type)
266
+ self.conv_1x1 = conv2d(
267
+ in_channels=input_channels,
268
+ out_channels=output_channels - input_channels,
269
+ kernel_size=1,
270
+ )
271
+
272
+ self.first_conv_3x3 = conv2d(
273
+ input_channels,
274
+ out_channels=output_channels,
275
+ kernel_size=kernel_size,
276
+ padding=1,
277
+ stride=1,
278
+ )
279
+ self.relu = torch.nn.ReLU()
280
+ self.last_conv_3x3 = conv2d(
281
+ in_channels=output_channels,
282
+ out_channels=output_channels,
283
+ kernel_size=kernel_size,
284
+ padding=1,
285
+ stride=1,
286
+ )
287
+
288
+ def forward(self, x) -> torch.Tensor:
289
+ """Apply the L residual block to this tensor."""
290
+ if self.input_channels < self.output_channels:
291
+ sc = self.conv_1x1(x)
292
+ sc = torch.cat([x, sc], dim=1)
293
+ else:
294
+ sc = x
295
+
296
+ x2 = self.relu(x)
297
+ x2 = self.first_conv_3x3(x2)
298
+ x2 = self.relu(x2)
299
+ x2 = self.last_conv_3x3(x2)
300
+ return x2 + sc
301
+
302
+
303
+ class ContextConditioningStack(torch.nn.Module):
304
+ """Context conditioning stack."""
305
+
306
+ def __init__(
307
+ self,
308
+ input_channels: int = 1,
309
+ output_channels: int = 768,
310
+ num_context_steps: int = 4,
311
+ conv_type: str = "standard",
312
+ ):
313
+ """
314
+ Conditioning Stack using the context images from Skillful Nowcasting, see https://arxiv.org/pdf/2104.00954.pdf.
315
+
316
+ Args:
317
+ input_channels: Number of input channels per timestep
318
+ output_channels: Number of output channels for the lowest block
319
+ num_context_steps: number of context steps (int)
320
+ conv_type: Type of 2D convolution to use, see satflow/models/utils.py for options
321
+ **kwargs: Allow initialize of the parameters above through key pairs
322
+ """
323
+ super().__init__()
324
+
325
+ conv2d = get_conv_layer(conv_type)
326
+ self.space2depth = PixelUnshuffle(downscale_factor=2)
327
+ # Process each observation processed separately with 4 downsample blocks
328
+ # Concatenate across channel dimension, and for each output, 3x3 spectrally
329
+ # normalized convolution to reduce number of channels by 2, followed by ReLU
330
+ self.d1 = DBlock(
331
+ input_channels=4 * input_channels,
332
+ output_channels=((output_channels // 4) * input_channels) // num_context_steps,
333
+ conv_type=conv_type,
334
+ )
335
+ self.d2 = DBlock(
336
+ input_channels=((output_channels // 4) * input_channels) // num_context_steps,
337
+ output_channels=((output_channels // 2) * input_channels) // num_context_steps,
338
+ conv_type=conv_type,
339
+ )
340
+ self.d3 = DBlock(
341
+ input_channels=((output_channels // 2) * input_channels) // num_context_steps,
342
+ output_channels=(output_channels * input_channels) // num_context_steps,
343
+ conv_type=conv_type,
344
+ )
345
+ self.d4 = DBlock(
346
+ input_channels=(output_channels * input_channels) // num_context_steps,
347
+ output_channels=(output_channels * 2 * input_channels) // num_context_steps,
348
+ conv_type=conv_type,
349
+ )
350
+ self.conv1 = spectral_norm(
351
+ conv2d(
352
+ in_channels=(output_channels // 4) * input_channels,
353
+ out_channels=(output_channels // 8) * input_channels,
354
+ kernel_size=3,
355
+ padding=1,
356
+ )
357
+ )
358
+
359
+ self.conv2 = spectral_norm(
360
+ conv2d(
361
+ in_channels=(output_channels // 2) * input_channels,
362
+ out_channels=(output_channels // 4) * input_channels,
363
+ kernel_size=3,
364
+ padding=1,
365
+ )
366
+ )
367
+
368
+ self.conv3 = spectral_norm(
369
+ conv2d(
370
+ in_channels=output_channels * input_channels,
371
+ out_channels=(output_channels // 2) * input_channels,
372
+ kernel_size=3,
373
+ padding=1,
374
+ )
375
+ )
376
+
377
+ self.conv4 = spectral_norm(
378
+ conv2d(
379
+ in_channels=output_channels * 2 * input_channels,
380
+ out_channels=output_channels * input_channels,
381
+ kernel_size=3,
382
+ padding=1,
383
+ )
384
+ )
385
+
386
+ self.relu = torch.nn.ReLU()
387
+
388
+ def forward(
389
+ self, x: torch.Tensor
390
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
391
+ """Generate the condition representation."""
392
+ # Each timestep processed separately
393
+ x = self.space2depth(x)
394
+ steps = x.size(1) # Number of timesteps
395
+ scale_1 = []
396
+ scale_2 = []
397
+ scale_3 = []
398
+ scale_4 = []
399
+ for i in range(steps):
400
+ s1 = self.d1(x[:, i, :, :, :])
401
+ s2 = self.d2(s1)
402
+ s3 = self.d3(s2)
403
+ s4 = self.d4(s3)
404
+ scale_1.append(s1)
405
+ scale_2.append(s2)
406
+ scale_3.append(s3)
407
+ scale_4.append(s4)
408
+ scale_1 = torch.stack(scale_1, dim=1) # B, T, C, H, W and want along C dimension
409
+ scale_2 = torch.stack(scale_2, dim=1) # B, T, C, H, W and want along C dimension
410
+ scale_3 = torch.stack(scale_3, dim=1) # B, T, C, H, W and want along C dimension
411
+ scale_4 = torch.stack(scale_4, dim=1) # B, T, C, H, W and want along C dimension
412
+ # Mixing layer
413
+ scale_1 = self._mixing_layer(scale_1, self.conv1)
414
+ scale_2 = self._mixing_layer(scale_2, self.conv2)
415
+ scale_3 = self._mixing_layer(scale_3, self.conv3)
416
+ scale_4 = self._mixing_layer(scale_4, self.conv4)
417
+ return scale_1, scale_2, scale_3, scale_4
418
+
419
+ def _mixing_layer(self, inputs, conv_block):
420
+ """Combine the inputs and then passed into the convolution stack."""
421
+ # Convert from [batch_size, time, h, w, c] -> [batch_size, h, w, c * time]
422
+ # then perform convolution on the output while preserving number of c.
423
+ stacked_inputs = einops.rearrange(inputs, "b t c h w -> b (c t) h w")
424
+ return F.relu(conv_block(stacked_inputs))
425
+
426
+
427
+ class LatentConditioningStack(torch.nn.Module):
428
+ """Latent conditioning stack class."""
429
+
430
+ def __init__(
431
+ self,
432
+ shape: (int, int, int) = (8, 8, 8),
433
+ output_channels: int = 768,
434
+ use_attention: bool = True,
435
+ ):
436
+ """
437
+ Latent conditioning stack from Skillful Nowcasting, see https://arxiv.org/pdf/2104.00954.pdf.
438
+
439
+ Args:
440
+ shape: Shape of the latent space, Should be (H/32,W/32,x) of the final image shape
441
+ output_channels: Number of output channels for the conditioning stack
442
+ use_attention: Whether to have a self-attention block or not
443
+ **kwargs: allow initialize of the parameters above through key pairs
444
+ """
445
+ super().__init__()
446
+
447
+ self.shape = shape
448
+ self.use_attention = use_attention
449
+ self.distribution = normal.Normal(loc=torch.Tensor([0.0]), scale=torch.Tensor([1.0]))
450
+
451
+ self.conv_3x3 = spectral_norm(
452
+ torch.nn.Conv2d(
453
+ in_channels=shape[0], out_channels=shape[0], kernel_size=(3, 3), padding=1
454
+ )
455
+ )
456
+ self.l_block1 = LBlock(input_channels=shape[0], output_channels=output_channels // 32)
457
+ self.l_block2 = LBlock(
458
+ input_channels=output_channels // 32, output_channels=output_channels // 16
459
+ )
460
+ self.l_block3 = LBlock(
461
+ input_channels=output_channels // 16, output_channels=output_channels // 4
462
+ )
463
+ if self.use_attention:
464
+ self.att_block = AttentionLayer(
465
+ input_channels=output_channels // 4, output_channels=output_channels // 4
466
+ )
467
+ self.l_block4 = LBlock(input_channels=output_channels // 4, output_channels=output_channels)
468
+
469
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
470
+ """
471
+ Apply convolution, l blocks and spatial attention module to the tensor.
472
+
473
+ Args:
474
+ x: tensor on the correct device, to move over the latent distribution
475
+
476
+ Returns:
477
+ tensor
478
+
479
+ """
480
+ # Independent draws from Norma ldistribution
481
+ z = self.distribution.sample(self.shape)
482
+ # Batch is at end for some reason, reshape
483
+ z = torch.permute(z, (3, 0, 1, 2)).type_as(x)
484
+
485
+ # 3x3 Convolution
486
+ z = self.conv_3x3(z)
487
+
488
+ # 3 L Blocks to increase number of channels
489
+ z = self.l_block1(z)
490
+ z = self.l_block2(z)
491
+ z = self.l_block3(z)
492
+ # Spatial attention module
493
+ z = self.att_block(z)
494
+
495
+ # L block to increase number of channel to 768
496
+ z = self.l_block4(z)
497
+ return z
model/dgmr_official/discriminators.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Discriminators."""
2
+
3
+ import torch
4
+ import torch.nn.functional as F
5
+
6
+ from torch.nn.modules.pixelshuffle import PixelUnshuffle
7
+ from torch.nn.utils.parametrizations import spectral_norm
8
+
9
+ from .common import DBlock
10
+
11
+
12
+ class Discriminator(torch.nn.Module):
13
+ """Discriminators class."""
14
+
15
+ def __init__(
16
+ self,
17
+ input_channels: int = 12,
18
+ num_spatial_frames: int = 8,
19
+ conv_type: str = "standard",
20
+ ):
21
+ """
22
+ Initialize the discriminator.
23
+
24
+ Args:
25
+ input_channels: Number of input channels (int)
26
+ num_spatial_frames: Number of spatial frames (int)
27
+ conv_type: the specified convolution type (str)
28
+ **kwargs: allow initialize of the parameters above through key pairs
29
+ """
30
+ super().__init__()
31
+
32
+ self.spatial_discriminator = SpatialDiscriminator(
33
+ input_channels=input_channels, num_timesteps=num_spatial_frames, conv_type=conv_type
34
+ )
35
+ self.temporal_discriminator = TemporalDiscriminator(
36
+ input_channels=input_channels, conv_type=conv_type
37
+ )
38
+
39
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
40
+ """Mixes the spatial loss and temporal loss of the tensor prior to returning it."""
41
+ spatial_loss = self.spatial_discriminator(x)
42
+ temporal_loss = self.temporal_discriminator(x)
43
+
44
+ return torch.cat([spatial_loss, temporal_loss], dim=1)
45
+
46
+
47
+ class TemporalDiscriminator(torch.nn.Module):
48
+ """Temporal Discriminator class."""
49
+
50
+ def __init__(
51
+ self,
52
+ input_channels: int = 12,
53
+ num_layers: int = 3,
54
+ conv_type: str = "standard",
55
+ ):
56
+ """
57
+ Temporal Discriminator from the Skillful Nowcasting, see https://arxiv.org/pdf/2104.00954.pdf.
58
+
59
+ Args:
60
+ input_channels: Number of channels per timestep
61
+ crop_size: Size of the crop, in the paper half the width of the input images
62
+ num_layers: Number of intermediate DBlock layers to use
63
+ conv_type: Type of 2d convolutions to use, see satflow/models/utils.py for options
64
+ **kwargs: allow initialize of the parameters above through key pairs
65
+ """
66
+ super().__init__()
67
+
68
+ self.downsample = torch.nn.AvgPool3d(kernel_size=(1, 2, 2), stride=(1, 2, 2))
69
+ self.space2depth = PixelUnshuffle(downscale_factor=2)
70
+ internal_chn = 48
71
+ self.d1 = DBlock(
72
+ input_channels=4 * input_channels,
73
+ output_channels=internal_chn * input_channels,
74
+ conv_type="3d",
75
+ first_relu=False,
76
+ )
77
+ self.d2 = DBlock(
78
+ input_channels=internal_chn * input_channels,
79
+ output_channels=2 * internal_chn * input_channels,
80
+ conv_type="3d",
81
+ )
82
+ self.intermediate_dblocks = torch.nn.ModuleList()
83
+ for _ in range(num_layers):
84
+ internal_chn *= 2
85
+ self.intermediate_dblocks.append(
86
+ DBlock(
87
+ input_channels=internal_chn * input_channels,
88
+ output_channels=2 * internal_chn * input_channels,
89
+ conv_type=conv_type,
90
+ )
91
+ )
92
+
93
+ self.d_last = DBlock(
94
+ input_channels=2 * internal_chn * input_channels,
95
+ output_channels=2 * internal_chn * input_channels,
96
+ keep_same_output=True,
97
+ conv_type=conv_type,
98
+ )
99
+
100
+ self.fc = spectral_norm(torch.nn.Linear(2 * internal_chn * input_channels, 1))
101
+ self.relu = torch.nn.ReLU()
102
+ self.bn = torch.nn.BatchNorm1d(2 * internal_chn * input_channels)
103
+
104
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
105
+ """Apply the forward function."""
106
+ x = self.downsample(x)
107
+
108
+ x = self.space2depth(x)
109
+ # Have to move time and channels
110
+ x = torch.permute(x, dims=(0, 2, 1, 3, 4))
111
+ # 2 residual 3D blocks to halve resolution if image, double number of channels and reduce
112
+ # number of time steps
113
+ x = self.d1(x)
114
+ x = self.d2(x)
115
+ # Convert back to T x C x H x W
116
+ x = torch.permute(x, dims=(0, 2, 1, 3, 4))
117
+ # Per Timestep part now, same as spatial discriminator
118
+ representations = []
119
+ for idx in range(x.size(1)):
120
+ # Intermediate DBlocks
121
+ # Three residual D Blocks to halve the resolution of the image and double
122
+ # the number of channels.
123
+ rep = x[:, idx, :, :, :]
124
+ for d in self.intermediate_dblocks:
125
+ rep = d(rep)
126
+ # One more D Block without downsampling or increase number of channels
127
+ rep = self.d_last(rep)
128
+
129
+ rep = torch.sum(F.relu(rep), dim=[2, 3])
130
+ rep = self.bn(rep)
131
+ rep = self.fc(rep)
132
+
133
+ representations.append(rep)
134
+ # The representations are summed together before the ReLU
135
+ x = torch.stack(representations, dim=1)
136
+ # Should be [Batch, N, 1]
137
+ x = torch.sum(x, keepdim=True, dim=1)
138
+ return x
139
+
140
+
141
+ class SpatialDiscriminator(torch.nn.Module):
142
+ """Spatial Discriminator class."""
143
+
144
+ def __init__(
145
+ self,
146
+ input_channels: int = 12,
147
+ num_timesteps: int = 8,
148
+ num_layers: int = 4,
149
+ conv_type: str = "standard",
150
+ ):
151
+ """
152
+ Spatial discriminator from Skillful Nowcasting, see https://arxiv.org/pdf/2104.00954.pdf.
153
+
154
+ Args:
155
+ input_channels: Number of input channels per timestep
156
+ num_timesteps: Number of timesteps to use, in the paper 8/18 timesteps were chosen
157
+ num_layers: Number of intermediate DBlock layers to use
158
+ conv_type: Type of 2d convolutions to use, see satflow/models/utils.py for options
159
+ **kwargs: allow initialize of the parameters above through key pairs
160
+ """
161
+ super().__init__()
162
+ # Randomly, uniformly, select 8 timesteps to do this on from the input
163
+ self.num_timesteps = num_timesteps
164
+ # First step is mean pooling 2x2 to reduce input by half
165
+ self.mean_pool = torch.nn.AvgPool2d(2)
166
+ self.space2depth = PixelUnshuffle(downscale_factor=2)
167
+ internal_chn = 24
168
+ self.d1 = DBlock(
169
+ input_channels=4 * input_channels,
170
+ output_channels=2 * internal_chn * input_channels,
171
+ first_relu=False,
172
+ conv_type=conv_type,
173
+ )
174
+ self.intermediate_dblocks = torch.nn.ModuleList()
175
+ for _ in range(num_layers):
176
+ internal_chn *= 2
177
+ self.intermediate_dblocks.append(
178
+ DBlock(
179
+ input_channels=internal_chn * input_channels,
180
+ output_channels=2 * internal_chn * input_channels,
181
+ conv_type=conv_type,
182
+ )
183
+ )
184
+ self.d6 = DBlock(
185
+ input_channels=2 * internal_chn * input_channels,
186
+ output_channels=2 * internal_chn * input_channels,
187
+ keep_same_output=True,
188
+ conv_type=conv_type,
189
+ )
190
+
191
+ # Spectrally normalized linear layer for binary classification
192
+ self.fc = spectral_norm(torch.nn.Linear(2 * internal_chn * input_channels, 1))
193
+ self.relu = torch.nn.ReLU()
194
+ self.bn = torch.nn.BatchNorm1d(2 * internal_chn * input_channels)
195
+
196
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
197
+ """Apply the forward function to the tensor."""
198
+ # x should be the chosen 8 or so
199
+ idxs = torch.randint(low=0, high=x.size()[1], size=(self.num_timesteps,))
200
+ representations = []
201
+ for idx in idxs:
202
+ rep = self.mean_pool(x[:, idx, :, :, :]) # 128x128
203
+ rep = self.space2depth(rep) # 64x64x4
204
+ rep = self.d1(rep) # 32x32
205
+ # Intermediate DBlocks
206
+ for d in self.intermediate_dblocks:
207
+ rep = d(rep)
208
+ rep = self.d6(rep) # 2x2
209
+ rep = torch.sum(F.relu(rep), dim=[2, 3])
210
+ rep = self.bn(rep)
211
+ rep = self.fc(rep)
212
+ """
213
+ Pseudocode from DeepMind
214
+ # Sum-pool the representations and feed to spectrally normalized lin. layer.
215
+ y = tf.reduce_sum(tf.nn.relu(y), axis=[1, 2])
216
+ y = layers.BatchNorm(calc_sigma=False)(y)
217
+ output_layer = layers.Linear(output_size=1)
218
+ output = output_layer(y)
219
+
220
+ # Take the sum across the t samples. Note: we apply the ReLU to
221
+ # (1 - score_real) and (1 + score_generated) in the loss.
222
+ output = tf.reshape(output, [b, n, 1])
223
+ output = tf.reduce_sum(output, keepdims=True, axis=1)
224
+ return output
225
+ """
226
+ representations.append(rep)
227
+
228
+ # The representations are summed together before the ReLU
229
+ x = torch.stack(representations, dim=1)
230
+ # Should be [Batch, N, 1]
231
+ x = torch.sum(x, keepdim=True, dim=1)
232
+ return x
model/dgmr_official/generators.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generators."""
2
+
3
+ import logging
4
+ from typing import List
5
+
6
+ import einops
7
+ import torch
8
+ import torch.nn.functional as F
9
+
10
+ from torch.nn.modules.pixelshuffle import PixelShuffle
11
+ from torch.nn.utils.parametrizations import spectral_norm
12
+
13
+ from .common import GBlock, UpsampleGBlock
14
+ from .layers import ConvGRU
15
+
16
+ logger = logging.getLogger(__name__)
17
+ logger.setLevel(logging.WARN)
18
+
19
+
20
+ class Sampler(torch.nn.Module):
21
+ """Sampler class."""
22
+
23
+ def __init__(
24
+ self,
25
+ forecast_steps: int = 18,
26
+ latent_channels: int = 768,
27
+ context_channels: int = 384,
28
+ output_channels: int = 1,
29
+ ):
30
+ """
31
+ Sampler from the Skillful Nowcasting, see https://arxiv.org/pdf/2104.00954.pdf.
32
+
33
+ The sampler takes the output from the Latent and Context conditioning stacks and
34
+ creates one stack of ConvGRU layers per future timestep.
35
+
36
+ Args:
37
+ forecast_steps: Number of forecast steps (int)
38
+ latent_channels: Number of input channels to the lowest ConvGRU layer (int)
39
+ context_channels: Number of context channels (int)
40
+ output_channels: Number of output channels (int)
41
+ **kwargs: allow initialize of the parameters above through key pairs
42
+ """
43
+ super().__init__()
44
+
45
+ self.forecast_steps = forecast_steps
46
+
47
+ self.convGRU1 = ConvGRU(
48
+ input_channels=latent_channels + context_channels,
49
+ output_channels=context_channels,
50
+ kernel_size=3,
51
+ )
52
+ self.gru_conv_1x1 = spectral_norm(
53
+ torch.nn.Conv2d(
54
+ in_channels=context_channels, out_channels=latent_channels, kernel_size=(1, 1)
55
+ )
56
+ )
57
+ self.g1 = GBlock(input_channels=latent_channels, output_channels=latent_channels)
58
+ self.up_g1 = UpsampleGBlock(
59
+ input_channels=latent_channels, output_channels=latent_channels // 2
60
+ )
61
+
62
+ self.convGRU2 = ConvGRU(
63
+ input_channels=latent_channels // 2 + context_channels // 2,
64
+ output_channels=context_channels // 2,
65
+ kernel_size=3,
66
+ )
67
+ self.gru_conv_1x1_2 = spectral_norm(
68
+ torch.nn.Conv2d(
69
+ in_channels=context_channels // 2,
70
+ out_channels=latent_channels // 2,
71
+ kernel_size=(1, 1),
72
+ )
73
+ )
74
+ self.g2 = GBlock(input_channels=latent_channels // 2, output_channels=latent_channels // 2)
75
+ self.up_g2 = UpsampleGBlock(
76
+ input_channels=latent_channels // 2, output_channels=latent_channels // 4
77
+ )
78
+
79
+ self.convGRU3 = ConvGRU(
80
+ input_channels=latent_channels // 4 + context_channels // 4,
81
+ output_channels=context_channels // 4,
82
+ kernel_size=3,
83
+ )
84
+ self.gru_conv_1x1_3 = spectral_norm(
85
+ torch.nn.Conv2d(
86
+ in_channels=context_channels // 4,
87
+ out_channels=latent_channels // 4,
88
+ kernel_size=(1, 1),
89
+ )
90
+ )
91
+ self.g3 = GBlock(input_channels=latent_channels // 4, output_channels=latent_channels // 4)
92
+ self.up_g3 = UpsampleGBlock(
93
+ input_channels=latent_channels // 4, output_channels=latent_channels // 8
94
+ )
95
+
96
+ self.convGRU4 = ConvGRU(
97
+ input_channels=latent_channels // 8 + context_channels // 8,
98
+ output_channels=context_channels // 8,
99
+ kernel_size=3,
100
+ )
101
+ self.gru_conv_1x1_4 = spectral_norm(
102
+ torch.nn.Conv2d(
103
+ in_channels=context_channels // 8,
104
+ out_channels=latent_channels // 8,
105
+ kernel_size=(1, 1),
106
+ )
107
+ )
108
+ self.g4 = GBlock(input_channels=latent_channels // 8, output_channels=latent_channels // 8)
109
+ self.up_g4 = UpsampleGBlock(
110
+ input_channels=latent_channels // 8, output_channels=latent_channels // 16
111
+ )
112
+
113
+ self.bn = torch.nn.BatchNorm2d(latent_channels // 16)
114
+ self.relu = torch.nn.ReLU()
115
+ self.conv_1x1 = spectral_norm(
116
+ torch.nn.Conv2d(
117
+ in_channels=latent_channels // 16,
118
+ out_channels=4 * output_channels,
119
+ kernel_size=(1, 1),
120
+ )
121
+ )
122
+
123
+ self.depth2space = PixelShuffle(upscale_factor=2)
124
+
125
+ def forward(
126
+ self, conditioning_states: List[torch.Tensor], latent_dim: torch.Tensor
127
+ ) -> torch.Tensor:
128
+ """
129
+ Perform the sampling from Skillful Nowcasting with GANs.
130
+
131
+ Args:
132
+ conditioning_states: Outputs from the `ContextConditioningStack` with the 4 input
133
+ states, ordered from largest to smallest spatially latent_dim: Output from
134
+ `LatentConditioningStack` for input into the ConvGRUs
135
+
136
+ latent_dim: (torch.Tensor)
137
+
138
+ Returns:
139
+ forecast_steps-length output of images for future timesteps
140
+
141
+ """
142
+ # Iterate through each forecast step
143
+ # Initialize with conditioning state for first one, output for second one
144
+ init_states = conditioning_states
145
+ # Expand latent dim to match batch size
146
+ latent_dim = einops.repeat(
147
+ latent_dim, "b c h w -> (repeat b) c h w", repeat=init_states[0].shape[0]
148
+ )
149
+ hidden_states = [latent_dim] * self.forecast_steps
150
+
151
+ # Layer 4 (bottom most)
152
+ hidden_states = self.convGRU1(hidden_states, init_states[3])
153
+ hidden_states = [self.gru_conv_1x1(h) for h in hidden_states]
154
+ hidden_states = [self.g1(h) for h in hidden_states]
155
+ hidden_states = [self.up_g1(h) for h in hidden_states]
156
+
157
+ # Layer 3.
158
+ hidden_states = self.convGRU2(hidden_states, init_states[2])
159
+ hidden_states = [self.gru_conv_1x1_2(h) for h in hidden_states]
160
+ hidden_states = [self.g2(h) for h in hidden_states]
161
+ hidden_states = [self.up_g2(h) for h in hidden_states]
162
+
163
+ # Layer 2.
164
+ hidden_states = self.convGRU3(hidden_states, init_states[1])
165
+ hidden_states = [self.gru_conv_1x1_3(h) for h in hidden_states]
166
+ hidden_states = [self.g3(h) for h in hidden_states]
167
+ hidden_states = [self.up_g3(h) for h in hidden_states]
168
+
169
+ # Layer 1 (top-most).
170
+ hidden_states = self.convGRU4(hidden_states, init_states[0])
171
+ hidden_states = [self.gru_conv_1x1_4(h) for h in hidden_states]
172
+ hidden_states = [self.g4(h) for h in hidden_states]
173
+ hidden_states = [self.up_g4(h) for h in hidden_states]
174
+
175
+ # Output layer.
176
+ hidden_states = [F.relu(self.bn(h)) for h in hidden_states]
177
+ hidden_states = [self.conv_1x1(h) for h in hidden_states]
178
+ hidden_states = [self.depth2space(h) for h in hidden_states]
179
+
180
+ # Convert forecasts to a torch Tensor
181
+ forecasts = torch.stack(hidden_states, dim=1)
182
+ return forecasts
183
+
184
+
185
+ class Generator(torch.nn.Module):
186
+ """Generator class."""
187
+
188
+ def __init__(
189
+ self,
190
+ conditioning_stack: torch.nn.Module,
191
+ latent_stack: torch.nn.Module,
192
+ sampler: torch.nn.Module,
193
+ ):
194
+ """
195
+ Wrap the three parts of the generator for simpler calling.
196
+
197
+ Args:
198
+ conditioning_stack: (torch.nn.Module)
199
+ latent_stack: (torch.nn.Module)
200
+ sampler: Combines the conditioning information and latent information (torch.nn.Module)
201
+ """
202
+ super().__init__()
203
+ self.conditioning_stack = conditioning_stack
204
+ self.latent_stack = latent_stack
205
+ self.sampler = sampler
206
+
207
+ def forward(self, x: torch.Tensor):
208
+ """Apply a forward pass on the tensor."""
209
+ conditioning_states = self.conditioning_stack(x)
210
+ latent_dim = self.latent_stack(x)
211
+ x = self.sampler(conditioning_states, latent_dim)
212
+ return x
model/dgmr_official/layers/Attention.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Attention module."""
2
+
3
+ import einops
4
+ import torch
5
+ import torch.nn as nn
6
+ from torch.nn import functional as F
7
+
8
+
9
+ def attention_einsum(q, k, v):
10
+ """Apply the attention operator to tensors of shape [h, w, c]."""
11
+ # Reshape 3D tensors to 2D tensor with first dimension L = h x w.
12
+ k = einops.rearrange(k, "h w c -> (h w) c") # [h, w, c] -> [L, c]
13
+ v = einops.rearrange(v, "h w c -> (h w) c") # [h, w, c] -> [L, c]
14
+
15
+ # Einstein summation corresponding to the query * key operation.
16
+ beta = F.softmax(torch.einsum("hwc, Lc->hwL", q, k), dim=-1)
17
+
18
+ # Einstein summation corresponding to the attention * value operation.
19
+ out = torch.einsum("hwL, Lc->hwc", beta, v)
20
+ return out
21
+
22
+
23
+ class AttentionLayer(torch.nn.Module):
24
+ """Attention layer class."""
25
+
26
+ def __init__(
27
+ self, input_channels: int, output_channels: int, ratio_kq: int = 8, ratio_v: int = 8
28
+ ):
29
+ """Initialize the attention layer."""
30
+ super(AttentionLayer, self).__init__()
31
+
32
+ self.ratio_kq = ratio_kq
33
+ self.ratio_v = ratio_v
34
+ self.output_channels = output_channels
35
+ self.input_channels = input_channels
36
+
37
+ # Compute query, key and value using 1x1 convolutions.
38
+ self.query = torch.nn.Conv2d(
39
+ in_channels=input_channels,
40
+ out_channels=self.output_channels // self.ratio_kq,
41
+ kernel_size=(1, 1),
42
+ padding="valid",
43
+ bias=False,
44
+ )
45
+ self.key = torch.nn.Conv2d(
46
+ in_channels=input_channels,
47
+ out_channels=self.output_channels // self.ratio_kq,
48
+ kernel_size=(1, 1),
49
+ padding="valid",
50
+ bias=False,
51
+ )
52
+ self.value = torch.nn.Conv2d(
53
+ in_channels=input_channels,
54
+ out_channels=self.output_channels // self.ratio_v,
55
+ kernel_size=(1, 1),
56
+ padding="valid",
57
+ bias=False,
58
+ )
59
+
60
+ self.last_conv = torch.nn.Conv2d(
61
+ in_channels=self.output_channels // 8,
62
+ out_channels=self.output_channels,
63
+ kernel_size=(1, 1),
64
+ padding="valid",
65
+ bias=False,
66
+ )
67
+
68
+ # Learnable gain parameter
69
+ self.gamma = nn.Parameter(torch.zeros(1))
70
+
71
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
72
+ """Apply the forward function."""
73
+ # Compute query, key and value using 1x1 convolutions.
74
+ query = self.query(x)
75
+ key = self.key(x)
76
+ value = self.value(x)
77
+ # Apply the attention operation.
78
+ out = []
79
+ for b in range(x.shape[0]):
80
+ # Apply to each in batch
81
+ out.append(attention_einsum(query[b], key[b], value[b]))
82
+ out = torch.stack(out, dim=0)
83
+ out = self.gamma * self.last_conv(out)
84
+ # Residual connection.
85
+ return out + x
model/dgmr_official/layers/ConvGRU.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Implementation of Conv GRU and cell module."""
2
+
3
+ import torch
4
+ import torch.nn.functional as F
5
+ from torch.nn.utils.parametrizations import spectral_norm
6
+
7
+
8
+ class ConvGRUCell(torch.nn.Module):
9
+ """A ConvGRU implementation."""
10
+
11
+ def __init__(
12
+ self,
13
+ input_channels: int,
14
+ output_channels: int,
15
+ kernel_size: int = 3,
16
+ sn_eps: float = 0.0001,
17
+ ):
18
+ """Conv GRU class.
19
+
20
+ Args:
21
+ input_channels: number of input channels (int)
22
+ output_channels: number of output channels (int)
23
+ kernel_size: kernel size of the convolutions. Default: 3.
24
+ sn_eps: constant for spectral normalization. Default: 1e-4.
25
+ """
26
+ super().__init__()
27
+ self._kernel_size: int = kernel_size
28
+ self._sn_eps: float = sn_eps
29
+ self.read_gate_conv = spectral_norm(
30
+ torch.nn.Conv2d(
31
+ in_channels=input_channels,
32
+ out_channels=output_channels,
33
+ kernel_size=(kernel_size, kernel_size),
34
+ padding=1,
35
+ ),
36
+ eps=sn_eps,
37
+ )
38
+ self.update_gate_conv = spectral_norm(
39
+ torch.nn.Conv2d(
40
+ in_channels=input_channels,
41
+ out_channels=output_channels,
42
+ kernel_size=(kernel_size, kernel_size),
43
+ padding=1,
44
+ ),
45
+ eps=sn_eps,
46
+ )
47
+ self.output_conv = spectral_norm(
48
+ torch.nn.Conv2d(
49
+ in_channels=input_channels,
50
+ out_channels=output_channels,
51
+ kernel_size=(kernel_size, kernel_size),
52
+ padding=1,
53
+ ),
54
+ eps=sn_eps,
55
+ )
56
+
57
+ def forward(self, x, prev_state):
58
+ """
59
+ Conv GRU forward, returning the current+new state.
60
+
61
+ Args:
62
+ x: Input tensor
63
+ prev_state: Previous state
64
+
65
+ Returns:
66
+ New tensor plus the new state
67
+ """
68
+ # Concatenate the inputs and previous state along the channel axis.
69
+ xh = torch.cat([x, prev_state], dim=1)
70
+
71
+ # Read gate of the GRU.
72
+ read_gate = F.sigmoid(self.read_gate_conv(xh))
73
+
74
+ # Update gate of the GRU.
75
+ update_gate = F.sigmoid(self.update_gate_conv(xh))
76
+
77
+ # Gate the inputs.
78
+ gated_input = torch.cat([x, read_gate * prev_state], dim=1)
79
+
80
+ # Gate the cell and state / outputs.
81
+ c = F.relu(self.output_conv(gated_input))
82
+ out = update_gate * prev_state + (1.0 - update_gate) * c
83
+ new_state = out
84
+
85
+ return out, new_state
86
+
87
+
88
+ class ConvGRU(torch.nn.Module):
89
+ """ConvGRU Cell wrapper to replace tf.static_rnn in TF implementation."""
90
+
91
+ def __init__(
92
+ self,
93
+ input_channels: int,
94
+ output_channels: int,
95
+ kernel_size: int = 3,
96
+ sn_eps=0.0001,
97
+ ):
98
+ """Initialize the convolution layer."""
99
+ super().__init__()
100
+ self.cell = ConvGRUCell(input_channels, output_channels, kernel_size, sn_eps)
101
+
102
+ def forward(self, x: torch.Tensor, hidden_state=None) -> torch.Tensor:
103
+ """Apply the forward function on each cell prior to returning it as a stack."""
104
+ outputs = []
105
+ for step in range(len(x)):
106
+ # Compute current timestep
107
+ output, hidden_state = self.cell(x[step], hidden_state)
108
+ outputs.append(output)
109
+ # Stack outputs to return as tensor
110
+ outputs = torch.stack(outputs, dim=0)
111
+ return outputs
model/dgmr_official/layers/CoordConv.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The coordinate convolution class."""
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+
6
+
7
+ class AddCoords(nn.Module):
8
+ """argument input tensors with spatial information."""
9
+
10
+ def __init__(self, with_r: bool = False):
11
+ """
12
+ Initialize the add coordinates class.
13
+
14
+ Args:
15
+ with_r: a condition to check if radical distance should included in the spatial
16
+ information (bool) default = false
17
+ """
18
+ super().__init__()
19
+ self.with_r: bool = with_r
20
+
21
+ def forward(self, input_tensor):
22
+ """
23
+ Add spatial information to the input tensor.
24
+
25
+ Args:
26
+ input_tensor: shape(batch, channel, x_dim, y_dim)
27
+ """
28
+ batch_size, _, x_dim, y_dim = input_tensor.size()
29
+
30
+ xx_channel = torch.arange(x_dim).repeat(1, y_dim, 1)
31
+ yy_channel = torch.arange(y_dim).repeat(1, x_dim, 1).transpose(1, 2)
32
+
33
+ xx_channel = xx_channel.float() / (x_dim - 1)
34
+ yy_channel = yy_channel.float() / (y_dim - 1)
35
+
36
+ xx_channel = xx_channel * 2 - 1
37
+ yy_channel = yy_channel * 2 - 1
38
+
39
+ xx_channel = xx_channel.repeat(batch_size, 1, 1, 1).transpose(2, 3)
40
+ yy_channel = yy_channel.repeat(batch_size, 1, 1, 1).transpose(2, 3)
41
+
42
+ ret = torch.cat(
43
+ [input_tensor, xx_channel.type_as(input_tensor), yy_channel.type_as(input_tensor)],
44
+ dim=1,
45
+ )
46
+
47
+ if self.with_r:
48
+ rr = torch.sqrt(
49
+ torch.pow(xx_channel.type_as(input_tensor) - 0.5, 2)
50
+ + torch.pow(yy_channel.type_as(input_tensor) - 0.5, 2)
51
+ )
52
+ ret = torch.cat([ret, rr], dim=1)
53
+
54
+ return ret
55
+
56
+
57
+ class CoordConv(nn.Module):
58
+ """Coordinate convolution class."""
59
+
60
+ def __init__(self, in_channels, out_channels, with_r=False, **kwargs):
61
+ """
62
+ Initialize the coordinate convolution.
63
+
64
+ Args:
65
+ in_channels : number of input channels
66
+ out_channels : number of output channels,
67
+ with_r : boolean =False,
68
+ **kwargs : dict[str, Unknown]
69
+ """
70
+ super().__init__()
71
+ self.addcoords = AddCoords(with_r=with_r)
72
+ in_size = in_channels + 2
73
+ if with_r:
74
+ in_size += 1
75
+ self.conv = nn.Conv2d(in_size, out_channels, **kwargs)
76
+
77
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
78
+ """Apply a forward pass on the input tensor."""
79
+ ret = self.addcoords(x)
80
+ ret = self.conv(ret)
81
+ return ret
model/dgmr_official/layers/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """Submodules for the layers."""
2
+
3
+ from .Attention import AttentionLayer
4
+ from .ConvGRU import ConvGRU
5
+ from .CoordConv import CoordConv
model/dgmr_official/layers/__pycache__/Attention.cpython-311.pyc ADDED
Binary file (3.82 kB). View file
 
model/dgmr_official/layers/__pycache__/ConvGRU.cpython-311.pyc ADDED
Binary file (4.89 kB). View file
 
model/dgmr_official/layers/__pycache__/CoordConv.cpython-311.pyc ADDED
Binary file (4.61 kB). View file
 
model/dgmr_official/layers/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (423 Bytes). View file
 
model/dgmr_official/layers/__pycache__/utils.cpython-311.pyc ADDED
Binary file (1.01 kB). View file
 
model/dgmr_official/layers/utils.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Utilities functions for the layer submodules."""
2
+
3
+ import torch
4
+
5
+ from . import CoordConv
6
+
7
+
8
+ def get_conv_layer(conv_type: str = "standard") -> torch.nn.Module:
9
+ """Return a conv layer based on the passed in string name."""
10
+ if conv_type == "standard":
11
+ conv_layer = torch.nn.Conv2d
12
+ elif conv_type == "coord":
13
+ conv_layer = CoordConv
14
+ elif conv_type == "3d":
15
+ conv_layer = torch.nn.Conv3d
16
+ else:
17
+ raise ValueError(f"{conv_type} is not a recognized Conv method")
18
+ return conv_layer
model/dgmr_official/losses.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Loss functions for DGMR (trimmed from Open Climate Fix ``dgmr`` package).
2
+
3
+ Only the GAN losses actually used by the DGMR objective are kept; the SSIM
4
+ variants (which depend on ``pytorch_msssim``) are omitted.
5
+ """
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ from torch.nn import functional as F
10
+
11
+
12
+ class GridCellLoss(nn.Module):
13
+ """Grid Cell Regularizer loss from Skillful Nowcasting, see https://arxiv.org/pdf/2104.00954.pdf."""
14
+
15
+ def __init__(self, weight_fn=None, precip_weight_cap=24.0):
16
+ """
17
+ Initialize GridCellLoss.
18
+
19
+ Args:
20
+ weight_fn: A function to compute weights for the loss.
21
+ precip_weight_cap: Custom ceiling value for the weight function.
22
+ """
23
+ super().__init__()
24
+ self.weight_fn = lambda y: weight_fn(y, precip_weight_cap) if weight_fn else None
25
+
26
+ def forward(self, generated_images, targets):
27
+ """
28
+ Forward function.
29
+
30
+ Calculates the grid cell regularizer value, assumes generated images are the mean
31
+ predictions from 6 calls to the generater (Monte Carlo estimation of the
32
+ expectations for the latent variable)
33
+
34
+ Args:
35
+ generated_images: Mean generated images from the generator
36
+ targets: Ground truth future frames
37
+
38
+ Returns:
39
+ Grid Cell Regularizer term
40
+ """
41
+ difference = generated_images - targets
42
+ if self.weight_fn is not None:
43
+ weights = self.weight_fn(targets)
44
+ difference = difference * weights
45
+ difference = difference.norm(p=1)
46
+ return difference / targets.size(1) * targets.size(3) * targets.size(4)
47
+
48
+
49
+ class NowcastingLoss(nn.Module):
50
+ """Nowcast Loss class."""
51
+
52
+ def __init__(self):
53
+ """Initialize function."""
54
+ super().__init__()
55
+
56
+ def forward(self, x, real_flag):
57
+ """Apply the relu function to the input tensor."""
58
+ if real_flag is True:
59
+ x = -x
60
+ return F.relu(1.0 + x).mean()
61
+
62
+
63
+ def loss_hinge_disc(score_generated, score_real):
64
+ """Discriminator hinge loss."""
65
+ l1 = F.relu(1.0 - score_real)
66
+ loss = torch.mean(l1)
67
+ l2 = F.relu(1.0 + score_generated)
68
+ loss += torch.mean(l2)
69
+ return loss
70
+
71
+
72
+ def loss_hinge_gen(score_generated):
73
+ """Generate hinge loss."""
74
+ loss = -torch.mean(score_generated)
75
+ return loss
76
+
77
+
78
+ def grid_cell_regularizer(generated_samples, batch_targets):
79
+ """Grid cell regularizer.
80
+
81
+ Args:
82
+ generated_samples: Tensor of size [n_samples, batch_size, 18, 256, 256, 1].
83
+ batch_targets: Tensor of size [batch_size, 18, 256, 256, 1].
84
+
85
+ Returns:
86
+ loss: A tensor of shape [batch_size].
87
+ """
88
+ gen_mean = torch.mean(generated_samples, dim=0)
89
+ # TODO Possibly change clip here?
90
+ weights = torch.clip(batch_targets, 0.0, 24.0)
91
+ loss = torch.mean(torch.abs(gen_mean - batch_targets) * weights)
92
+ return loss
scripts/fake_data.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import h5py
3
+ import numpy as np
4
+ from onescience.utils.YParams import YParams
5
+
6
+
7
+ # DGMR 使用 4 帧上下文预测后续雷达帧;T 随模型配置自动变化。
8
+ def get_dims(cfg_model, cfg_data):
9
+ H, W = map(int, cfg_data.dataset.img_size)
10
+ if (H, W) != (int(cfg_model.output_shape), int(cfg_model.output_shape)):
11
+ raise ValueError("dataset.img_size must match model.output_shape")
12
+ input_steps = int(cfg_model.num_context)
13
+ output_steps = int(cfg_model.forecast_steps)
14
+ samples = int(cfg_data.dataloader.batch_size)
15
+ T = input_steps + output_steps + samples - 1
16
+ return {
17
+ "T": T, "H": H, "W": W, "time_step": 1,
18
+ "input_steps": input_steps, "output_steps": output_steps,
19
+ }
20
+
21
+
22
+ def generate_fake_h5(data_dir, var_names, years, dims):
23
+ """
24
+ 为每个年份生成一个空 h5 文件。
25
+ 利用 HDF5 chunked 数据集未写入 chunk 即返回 fill_value=0 的特性,
26
+ 文件实际只含元数据,极小,但 shape 与真实数据完全一致。
27
+ 均值/标准差也作为数据集内嵌进每年的 h5,与 era5.py 新版读取方式对应。
28
+
29
+ 注意:ERA5Datapipe 要求 samples_per_year = T - input_steps - output_steps + 1 >= 1,
30
+ T 由 num_context、forecast_steps 与 batch_size 自动计算。
31
+ """
32
+ os.makedirs(os.path.join(data_dir, "data"), exist_ok=True)
33
+ T, C = dims["T"], len(var_names)
34
+ H, W = dims["H"], dims["W"]
35
+
36
+ means = np.zeros((1, C, 1, 1), dtype=np.float32)
37
+ stds = np.ones((1, C, 1, 1), dtype=np.float32)
38
+
39
+ for year in years:
40
+ path = os.path.join(data_dir, "data", f"{year}.h5")
41
+ with h5py.File(path, "w") as f:
42
+ ds = f.create_dataset(
43
+ "fields",
44
+ shape=(T, C, H, W),
45
+ dtype="float32",
46
+ chunks=(1, C, H, W),
47
+ fillvalue=0.0,
48
+ )
49
+ ds.attrs["variables"] = var_names
50
+ ds.attrs["time_step"] = dims["time_step"]
51
+ f.create_dataset("global_means", data=means)
52
+ f.create_dataset("global_stds", data=stds)
53
+
54
+ size_kb = os.path.getsize(path) / 1024
55
+ print(f" {year}.h5 shape=({T},{C},{H},{W}) "
56
+ f"logical={T*C*H*W*4/1024**3:.1f}GB actual={size_kb:.1f}KB")
57
+
58
+
59
+ if __name__ == "__main__":
60
+ cfg_model = YParams("conf/config.yaml", "model")
61
+ cfg_datapipe = YParams("conf/config.yaml", "datapipe")
62
+
63
+ if cfg_datapipe.dataset.data_dir.startswith("/public/") or cfg_datapipe.dataset.data_dir.startswith("/work2/"):
64
+ print("请检查 config,确保各 *_dir 指向本地测试路径而非生产路径。")
65
+ exit()
66
+
67
+ years = cfg_datapipe.dataset.train_time + cfg_datapipe.dataset.val_time + cfg_datapipe.dataset.test_time
68
+ atm_vars = cfg_datapipe.dataset.channels
69
+ if len(atm_vars) != int(cfg_model.input_channels):
70
+ raise ValueError("channel count must match model.input_channels")
71
+
72
+ generate_fake_h5(cfg_datapipe.dataset.data_dir, atm_vars, years, get_dims(cfg_model, cfg_datapipe))
73
+
74
+ print("\n✅ Fake datasets generated.")
scripts/inference.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ # 获取项目根目录(inference.py上级的上级)
5
+ root_path = Path(__file__).parent.parent
6
+ sys.path.append(str(root_path))
7
+ import torch
8
+ import os
9
+ import glob
10
+ import numpy as np
11
+ import h5py
12
+ from tqdm import tqdm
13
+ from model.dgmr import DGMR
14
+ from onescience.utils.YParams import YParams
15
+ from onescience.datapipes.climate import ERA5Datapipe
16
+
17
+
18
+ def get_stats(data_dir, channels):
19
+ """从新版 h5 中读取变量列表与归一化参数(均值/标准差)"""
20
+ h5_files = sorted(glob.glob(os.path.join(data_dir, "data", "*.h5")))
21
+ with h5py.File(h5_files[0], "r") as f:
22
+ ds = f["fields"]
23
+ all_variables = [v.decode() if isinstance(v, bytes) else v for v in ds.attrs["variables"]]
24
+ mu = f["global_means"][:] # [1, C, 1, 1]
25
+ std = f["global_stds"][:]
26
+
27
+ channel_indices = [all_variables.index(v) for v in channels]
28
+ means = mu[:, channel_indices, :, :]
29
+ stds = std[:, channel_indices, :, :]
30
+ return means, stds
31
+
32
+
33
+ if __name__ == "__main__":
34
+ current_path = os.getcwd()
35
+ sys.path.append(current_path)
36
+
37
+ ## Model config init
38
+ config_file_path = os.path.join(current_path, "conf/config.yaml")
39
+ cfg = YParams(config_file_path, "model")
40
+
41
+ ## DataLoader init
42
+ cfg_data = YParams(config_file_path, "datapipe")
43
+ means, stds = get_stats(cfg_data.dataset.data_dir, cfg_data.dataset.channels)
44
+
45
+ cfg['N_in_channels'] = len(cfg_data.dataset.channels)
46
+ cfg['N_out_channels'] = len(cfg_data.dataset.channels)
47
+
48
+ datapipe = ERA5Datapipe(
49
+ dataset_dir=cfg_data.dataset.data_dir,
50
+ used_variables=cfg_data.dataset.channels,
51
+ used_years=cfg_data.dataset.test_time,
52
+ distributed=False,
53
+ input_steps=cfg.num_context,
54
+ output_steps=cfg.forecast_steps,
55
+ batch_size=1,
56
+ num_workers=4,
57
+ )
58
+ test_dataloader, _ = datapipe.get_dataloader("test")
59
+
60
+ device = "cuda:0" if torch.cuda.is_available() else "cpu"
61
+ ckpt = torch.load(f"{cfg.checkpoint_dir}/model_bak.pth", map_location=device, weights_only=False)
62
+ model = DGMR(
63
+ forecast_steps=cfg.forecast_steps,
64
+ num_context=cfg.num_context,
65
+ input_channels=cfg.input_channels,
66
+ output_shape=cfg.output_shape,
67
+ conv_type=cfg.conv_type,
68
+ latent_channels=cfg.latent_channels,
69
+ context_channels=cfg.context_channels,
70
+ generation_steps=cfg.generation_steps,
71
+ grid_lambda=cfg.grid_lambda,
72
+ precip_weight_cap=cfg.precip_weight_cap,
73
+ ).to(device)
74
+ model.load_state_dict(ckpt["model_state_dict"])
75
+
76
+ model.eval()
77
+ os.makedirs('result/output/', exist_ok=True)
78
+ print(f"📂 infer results will be generated to './result/output/'")
79
+ with torch.no_grad():
80
+ for data in tqdm(test_dataloader, desc="Inferring testset", unit="batch"):
81
+ invar = data[0].to(device, dtype=torch.float32) # [1, num_context, C, H, W]
82
+ gen = model.generator(invar).cpu().numpy() # [1, forecast_steps, C, H, W]
83
+ for t in range(gen.shape[1]):
84
+ fname = data[4][cfg.num_context + t][0] # 该预测帧对应的时刻
85
+ pred_var = gen[0, t] # [C, H, W]
86
+ pred_var = pred_var * stds + means
87
+ np.save(f"result/output/{fname}.npy", pred_var)
scripts/result.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ import os
4
+ import sys
5
+ import glob
6
+ import h5py
7
+ from datetime import datetime
8
+ from tqdm import tqdm
9
+ from onescience.utils.fcn.YParams import YParams
10
+ from matplotlib import rcParams
11
+
12
+ # rcParams['font.family'] = 'serif'
13
+ # rcParams['font.serif'] = ['DejaVu Serif']
14
+ rcParams['mathtext.fontset'] = 'stix'
15
+ rcParams['axes.linewidth'] = 0.9
16
+ rcParams['xtick.major.width'] = 0.9
17
+ rcParams['ytick.major.width'] = 0.9
18
+
19
+
20
+ def get_metadata(data_dir, channels):
21
+ """从新版 h5 attrs 中读取变量列表和 time_step"""
22
+ h5_files = sorted(glob.glob(os.path.join(data_dir, "data", "*.h5")))
23
+ with h5py.File(h5_files[0], "r") as f:
24
+ ds = f["fields"]
25
+ all_variables = [v.decode() if isinstance(v, bytes) else v for v in ds.attrs["variables"]]
26
+ time_step = int(ds.attrs["time_step"])
27
+
28
+ channel_indices = [all_variables.index(v) for v in channels]
29
+
30
+ total_files = [f for f in os.listdir('./result/output/') if f.endswith('.npy')]
31
+ total_files.sort()
32
+ return total_files, channel_indices, time_step
33
+
34
+
35
+ def filename_to_index(filename, time_step):
36
+ """将 YYYYMMDDHH 格式的文件名转换为年度 h5 文件中的时间步索引"""
37
+ dt = datetime.strptime(filename, "%Y%m%d%H")
38
+ year_start = datetime(dt.year, 1, 1)
39
+ hours = (dt - year_start).total_seconds() / 3600
40
+ return int(hours / time_step)
41
+
42
+
43
+ def get_result(total_files, channel_indices, time_step, data_dir, clim_mean):
44
+ channel_rmse = np.zeros(len(channel_indices))
45
+ channel_acc = np.zeros(len(channel_indices))
46
+ clim_mean = clim_mean[0, :, :, :]
47
+ if not os.path.exists('./result/rmse.npy') or not os.path.exists('result/acc.npy'):
48
+ numerator = np.zeros(len(channel_indices))
49
+ pred_sq_sum = np.zeros(len(channel_indices))
50
+ label_sq_sum = np.zeros(len(channel_indices))
51
+ for file in tqdm(total_files, unit="files"):
52
+ fname = file[:-4] # 去掉 .npy
53
+ year = fname[:4]
54
+ t_idx = filename_to_index(fname, time_step)
55
+ with h5py.File(os.path.join(data_dir, 'data', f'{year}.h5'), "r") as f:
56
+ label = f["fields"][t_idx] # [C, H, W]
57
+ label = label[channel_indices]
58
+ pred = np.load(f'result/output/{file}').squeeze()
59
+ if pred.ndim == 2:
60
+ pred = pred[np.newaxis] # 单通道时 squeeze 会压缩掉通道维,恢复为 [C, H, W]
61
+
62
+ label_anom = label - clim_mean
63
+ pred_anom = pred - clim_mean
64
+ # 累加
65
+ numerator += np.sum(pred_anom * label_anom, axis=(1, 2))
66
+ pred_sq_sum += np.sum(pred_anom ** 2, axis=(1, 2))
67
+ label_sq_sum += np.sum(label_anom ** 2, axis=(1, 2))
68
+
69
+ channel_rmse += np.sqrt(np.mean((label - pred) ** 2, axis=(1, 2)))
70
+ channel_rmse /= len(total_files)
71
+ channel_acc = numerator / (np.sqrt(pred_sq_sum * label_sq_sum) + 1e-8)
72
+ np.save('./result/acc.npy', channel_acc)
73
+ np.save('./result/rmse.npy', channel_rmse)
74
+
75
+
76
+ def show_result():
77
+ channel_rmse = np.load('./result/rmse.npy')
78
+ channel_acc = np.load('./result/acc.npy')
79
+
80
+ channels = [cfg_data.dataset.channels[i] for i in range(len(channel_indices))]
81
+ w = 24 # 最长 channel 名宽度
82
+
83
+ # 表头
84
+ print(f"┌{'─' * (w + 2)}┬{'─' * 14}┬{'─' * 14}┐")
85
+ print(f"│ {'Channel':<{w}} │ {'RMSE':>12} │ {'ACC':>12} │")
86
+ print(f"├{'─' * (w + 2)}┼{'─' * 14}┼{'─' * 14}┤")
87
+ # 数据行
88
+ for i, ch in enumerate(channels):
89
+ print(f"│ {ch:<{w}} │ {channel_rmse[i]:>12.4f} | {channel_acc[i]:>12.4f} |")
90
+ print(f"├{'─' * (w + 2)}┼{'─' * 14}┼{'─' * 14}┤")
91
+ print(f"│ {'Average':<{w}} │ {np.mean(channel_rmse):>12.4f} │ {np.mean(channel_acc):>12.4f} │")
92
+ print(f"└{'─' * (w + 2)}┴{'─' * 14}┴{'─' * 14}┘")
93
+
94
+
95
+ def plot(label, pred, var, filename):
96
+ fig, axes = plt.subplots(1, 3, figsize=(15, 4))
97
+
98
+ xtick_labels = ['180°W', '90°W', '0°', '90°E', '180°E']
99
+ ytick_labels = ['90°S', '45°S', '0°', '45°N', '90°N']
100
+ xticks = np.linspace(0, label.shape[-1] - 1, 5)
101
+ yticks = np.linspace(0, label.shape[-2] - 1, 5)
102
+
103
+ vmin = min(label.min(), pred.min())
104
+ vmax = max(label.max(), pred.max())
105
+
106
+ diff = label - pred
107
+ rmse = np.sqrt(np.mean(diff ** 2))
108
+ diff_abs_max = np.abs(diff).max()
109
+
110
+ plot_configs = [
111
+ {'data': label, 'title': 'Truth', 'cmap': 'viridis', 'vmin': vmin, 'vmax': vmax},
112
+ {'data': pred, 'title': 'Prediction', 'cmap': 'viridis', 'vmin': vmin, 'vmax': vmax},
113
+ {'data': diff, 'title': f'Difference (RMSE={rmse:.2f})', 'cmap': 'RdBu_r', 'vmin': -diff_abs_max, 'vmax': diff_abs_max},
114
+ ]
115
+
116
+ for ax, cfg in zip(axes, plot_configs):
117
+ im = ax.imshow(cfg['data'], cmap=cfg['cmap'], vmin=cfg['vmin'], vmax=cfg['vmax'])
118
+ ax.set_title(cfg['title'], fontsize=12, pad=4)
119
+ ax.set_xlabel('Longitude')
120
+ ax.set_ylabel('Latitude')
121
+ ax.set_xticks(xticks)
122
+ ax.set_xticklabels(xtick_labels)
123
+ ax.set_yticks(yticks)
124
+ ax.set_yticklabels(ytick_labels)
125
+ plt.colorbar(im, ax=ax, orientation='horizontal')
126
+
127
+ fig.suptitle(var, fontsize=14, fontweight='bold', y=0.98)
128
+ plt.savefig(filename, dpi=300, bbox_inches='tight')
129
+ plt.close()
130
+
131
+
132
+ def plot_loss(train_loss, valid_loss):
133
+ mask = ~(np.isnan(train_loss) | np.isnan(valid_loss))
134
+ train_loss = train_loss[mask]
135
+ valid_loss = valid_loss[mask]
136
+
137
+ fig, ax = plt.subplots(figsize=(5, 3.5))
138
+ colors = {'train': '#2563EB', 'valid': '#EA580C'}
139
+ epochs = np.arange(1, len(train_loss) + 1)
140
+
141
+ ax.plot(epochs, train_loss, color=colors['train'], linewidth=1.5, label='Train')
142
+ ax.plot(epochs, valid_loss, color=colors['valid'], linewidth=1.5, label='Valid', linestyle='--')
143
+ min_idx = np.argmin(valid_loss)
144
+ ax.scatter(epochs[min_idx], valid_loss[min_idx],
145
+ color=colors['valid'], s=40, zorder=5, edgecolors='white')
146
+ ax.annotate(f'Best: {valid_loss[min_idx]:.3f}',
147
+ xy=(epochs[min_idx], valid_loss[min_idx]),
148
+ xytext=(10, 10), textcoords='offset points', fontsize=8, color=colors['valid'],
149
+ arrowprops=dict(arrowstyle='-', color=colors['valid'], lw=0.5))
150
+
151
+ ax.set(xlabel='Epoch', ylabel='Loss', xlim=(0, len(train_loss) + 1))
152
+ ax.legend(frameon=False, loc='upper right')
153
+ ax.grid(True, linestyle='--', alpha=0.3)
154
+ ax.spines[['top', 'right']].set_visible(False)
155
+
156
+ plt.tight_layout()
157
+ plt.savefig('./result/loss.png', dpi=300, bbox_inches='tight')
158
+ plt.close()
159
+
160
+
161
+ if __name__ == "__main__":
162
+ current_path = os.getcwd()
163
+ sys.path.append(current_path)
164
+ config_file_path = os.path.join(current_path, 'conf/config.yaml')
165
+ cfg = YParams(config_file_path, 'model')
166
+ cfg_data = YParams(config_file_path, "datapipe")
167
+
168
+ train_loss = np.load('./data/checkpoints/trloss.npy')
169
+ valid_loss = np.load('./data/checkpoints/valoss.npy')
170
+ plot_loss(train_loss, valid_loss)
171
+
172
+ data_dir = cfg_data.dataset.data_dir
173
+ total_files, channel_indices, time_step = get_metadata(data_dir, cfg_data.dataset.channels)
174
+
175
+ # Load data & Compute RMSE/ACC per channel
176
+ h5_files = sorted(glob.glob(os.path.join(data_dir, "data", "*.h5")))
177
+ with h5py.File(h5_files[0], "r") as f:
178
+ mu = f["global_means"][:]
179
+ clim_mean = mu[:, channel_indices, :, :]
180
+ get_result(total_files, channel_indices, time_step, data_dir, clim_mean)
181
+ show_result()
182
+
183
+ ##### 默认绘制 result/output 中第一个预测结果,用户可自行指定日期和变量 #####
184
+ eg_files = [total_files[0][:-4]]
185
+ # DGMR 为单通道场,默认绘制该通道(后续帧的时刻可通过 h5 time_step 推算)
186
+ channel_index = list(range(len(cfg_data.dataset.channels)))
187
+
188
+ selected_var = [cfg_data.dataset.channels[int(i)] for i in channel_index]
189
+ print(f"seleted date: {eg_files}")
190
+ print(f"selected channels: {selected_var}")
191
+ for file in eg_files:
192
+ year = file[:4]
193
+ t_idx = filename_to_index(file, time_step)
194
+ with h5py.File(os.path.join(data_dir, 'data', f'{year}.h5'), "r") as f:
195
+ label = f["fields"][t_idx] # [C, H, W]
196
+ label = label[channel_indices]
197
+ pred = np.load(f'result/output/{file}.npy').squeeze()
198
+ if pred.ndim == 2:
199
+ pred = pred[np.newaxis] # 单通道时 squeeze 会压缩掉通道维,恢复为 [C, H, W]
200
+ for i in range(len(selected_var)):
201
+ filename = f'./result/{file}_{selected_var[i]}.png'
202
+ plot(label[channel_index[i]], pred[channel_index[i]], selected_var[i], filename)
203
+ print(f'✅plot {filename}')
scripts/train.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ # 获取项目根目录(train.py上级的上级)
5
+ root_path = Path(__file__).parent.parent
6
+ sys.path.append(str(root_path))
7
+ import torch
8
+ import os
9
+ import numpy as np
10
+ import torch.distributed as dist
11
+ import logging
12
+ import time
13
+
14
+ from model.dgmr import DGMR
15
+ from model.dgmr_official.losses import loss_hinge_disc, loss_hinge_gen
16
+ from onescience.datapipes.climate import ERA5Datapipe
17
+ from onescience.utils.YParams import YParams
18
+
19
+ try:
20
+ from apex import optimizers
21
+ _FUSED_ADAM = True
22
+ except Exception:
23
+ _FUSED_ADAM = False
24
+
25
+
26
+ def main():
27
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
28
+ logger = logging.getLogger()
29
+
30
+ ## Model config init
31
+ config_file_path = os.path.join(current_path, "conf/config.yaml")
32
+ cfg = YParams(config_file_path, "model")
33
+
34
+ ## Distributed config init
35
+ cfg.world_size = 1
36
+ if "WORLD_SIZE" in os.environ:
37
+ cfg.world_size = int(os.environ["WORLD_SIZE"])
38
+ world_rank = 0
39
+ local_rank = 0
40
+ if cfg.world_size > 1 and torch.cuda.is_available():
41
+ dist.init_process_group(backend="nccl", init_method="env://")
42
+ local_rank = int(os.environ["LOCAL_RANK"])
43
+ world_rank = dist.get_rank()
44
+ device = f"cuda:{local_rank}" if torch.cuda.is_available() else "cpu"
45
+
46
+ ## DataLoader init
47
+ cfg_data = YParams(config_file_path, "datapipe")
48
+ cfg['N_in_channels'] = len(cfg_data.dataset.channels)
49
+ cfg['N_out_channels'] = len(cfg_data.dataset.channels)
50
+ datapipe = ERA5Datapipe(
51
+ dataset_dir=cfg_data.dataset.data_dir,
52
+ used_variables=cfg_data.dataset.channels,
53
+ used_years=cfg_data.dataset.train_time,
54
+ distributed=dist.is_initialized(),
55
+ input_steps=cfg.num_context,
56
+ output_steps=cfg.forecast_steps,
57
+ batch_size=cfg_data.dataloader.batch_size,
58
+ num_workers=cfg_data.dataloader.num_workers,
59
+ )
60
+ train_dataloader, train_sampler = datapipe.get_dataloader("train")
61
+ datapipe = ERA5Datapipe(
62
+ dataset_dir=cfg_data.dataset.data_dir,
63
+ used_variables=cfg_data.dataset.channels,
64
+ used_years=cfg_data.dataset.val_time,
65
+ distributed=dist.is_initialized(),
66
+ input_steps=cfg.num_context,
67
+ output_steps=cfg.forecast_steps,
68
+ batch_size=cfg_data.dataloader.batch_size,
69
+ num_workers=cfg_data.dataloader.num_workers,
70
+ )
71
+ val_dataloader, val_sampler = datapipe.get_dataloader("valid")
72
+
73
+ # Model init
74
+ model = DGMR(
75
+ forecast_steps=cfg.forecast_steps,
76
+ num_context=cfg.num_context,
77
+ input_channels=cfg.input_channels,
78
+ output_shape=cfg.output_shape,
79
+ conv_type=cfg.conv_type,
80
+ latent_channels=cfg.latent_channels,
81
+ context_channels=cfg.context_channels,
82
+ generation_steps=cfg.generation_steps,
83
+ grid_lambda=cfg.grid_lambda,
84
+ precip_weight_cap=cfg.precip_weight_cap,
85
+ ).to(device)
86
+
87
+ # 生成器与判别器使用独立的 Adam 优化器(论文 lr=1e-4)
88
+ if _FUSED_ADAM:
89
+ optimizer_g = optimizers.FusedAdam(model.generator.parameters(), lr=cfg.lr)
90
+ optimizer_d = optimizers.FusedAdam(model.discriminator.parameters(), lr=cfg.lr_disc)
91
+ else:
92
+ optimizer_g = torch.optim.Adam(model.generator.parameters(), lr=cfg.lr)
93
+ optimizer_d = torch.optim.Adam(model.discriminator.parameters(), lr=cfg.lr_disc)
94
+ scheduler_g = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer_g, factor=0.2, patience=5, mode='min')
95
+ scheduler_d = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer_d, factor=0.2, patience=5, mode='min')
96
+
97
+ ## Train process init
98
+ os.makedirs(cfg.checkpoint_dir, exist_ok=True)
99
+ train_loss_file = f"{cfg.checkpoint_dir}/trloss.npy"
100
+ valid_loss_file = f"{cfg.checkpoint_dir}/valoss.npy"
101
+ best_valid_loss = float("inf")
102
+ best_loss_epoch = 0
103
+ train_losses = np.empty((0,), dtype=np.float32)
104
+ valid_losses = np.empty((0,), dtype=np.float32)
105
+
106
+ ## Get model params count
107
+ if cfg.world_size == 1:
108
+ total_params = sum(p.numel() for p in model.parameters())
109
+ print("\n\n")
110
+ print("-" * 50)
111
+ print(f"📂 now params is {total_params}, {total_params / 1e6:.2f}M, {total_params / 1e9:.2f}B")
112
+ print("-" * 50, "\n")
113
+
114
+ ## Load model weight if there exist well-trained model
115
+ if os.path.exists(f"{cfg.checkpoint_dir}/model_bak.pth"):
116
+ if world_rank == 0:
117
+ print("\n\n")
118
+ print("-" * 50)
119
+ print(f"✅ There has a model weight, load and continue training...")
120
+ print(f'If you want to train a new model, ensure there is no *.pth file in {cfg.checkpoint_dir}')
121
+ print("-" * 50, "\n")
122
+ ckpt = torch.load(f"{cfg.checkpoint_dir}/model_bak.pth", map_location=device, weights_only=False)
123
+ model.load_state_dict(ckpt["model_state_dict"])
124
+ optimizer_g.load_state_dict(ckpt["optimizer_g_state_dict"])
125
+ optimizer_d.load_state_dict(ckpt["optimizer_d_state_dict"])
126
+ scheduler_g.load_state_dict(ckpt["scheduler_g_state_dict"])
127
+ scheduler_d.load_state_dict(ckpt["scheduler_d_state_dict"])
128
+ best_valid_loss = ckpt["best_valid_loss"]
129
+ best_loss_epoch = ckpt["best_loss_epoch"]
130
+ train_losses = np.load(train_loss_file)
131
+ valid_losses = np.load(valid_loss_file)
132
+
133
+ ## Distributed model
134
+ if dist.is_initialized():
135
+ model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[local_rank], output_device=local_rank)
136
+ world_rank == 0 and logger.info(f"start training ...")
137
+
138
+ for epoch in range(cfg.max_epoch):
139
+ if dist.is_initialized():
140
+ train_sampler.set_epoch(epoch)
141
+ val_sampler.set_epoch(epoch)
142
+ model.train()
143
+ train_loss = 0
144
+ start_time = time.time()
145
+ for j, data in enumerate(train_dataloader):
146
+ invar = data[0].to(device, dtype=torch.float32) # [B, num_context, C, H, W]
147
+ outvar = data[1].to(device, dtype=torch.float32) # [B, forecast_steps, C, H, W]
148
+ full_real = torch.cat([invar, outvar], dim=1) # 上下文 + 真实未来帧
149
+
150
+ # --- 判别器(hinge loss,生成图像 detach 不传梯度) ---
151
+ gen_images = model.generator(invar) # [B, forecast_steps, C, H, W]
152
+ full_fake = torch.cat([invar, gen_images], dim=1)
153
+ score_real = model.discriminator(full_real)
154
+ score_generated = model.discriminator(full_fake.detach())
155
+ disc_loss = loss_hinge_disc(score_generated, score_real)
156
+ optimizer_d.zero_grad()
157
+ disc_loss.backward()
158
+ optimizer_d.step()
159
+
160
+ # --- 生成器(hinge + 网格单元正则器,MC 采样估计期望) ---
161
+ score_generated = model.discriminator(full_fake)
162
+ gen_loss = loss_hinge_gen(score_generated)
163
+ gen_samples = torch.stack(
164
+ [model.generator(invar) for _ in range(cfg.generation_steps)], dim=0
165
+ ).mean(dim=0) # 取 MC 均值作为生成均值图像
166
+ grid_loss = model.grid_regularizer(gen_samples, outvar)
167
+ gen_loss = gen_loss + cfg.grid_lambda * grid_loss
168
+ optimizer_g.zero_grad()
169
+ gen_loss.backward()
170
+ optimizer_g.step()
171
+
172
+ loss = gen_loss.item() + disc_loss.item()
173
+ train_loss += loss
174
+ if world_rank == 0:
175
+ logger.info(f'Train: Epoch {epoch}-{j+1}/{len(train_dataloader)} '
176
+ f'[cost {int((time.time()-start_time) // 60):02}:{int((time.time()-start_time) % 60):02}] '
177
+ f'[{(time.time()-start_time)/(j+1): .02f}s/{cfg_data.dataloader.batch_size}batch] '
178
+ f'gen_loss:{gen_loss.item(): .04f} disc_loss:{disc_loss.item(): .04f} '
179
+ f'loss:{train_loss / (j+1): .04f}')
180
+
181
+ train_loss /= len(train_dataloader)
182
+
183
+ # 验证只使用网格单元正则器(确定性,单个生成样本)
184
+ model.eval()
185
+ valid_loss = 0
186
+ with torch.no_grad():
187
+ start_time = time.time()
188
+ for j, data in enumerate(val_dataloader):
189
+ invar = data[0].to(device, dtype=torch.float32)
190
+ outvar = data[1].to(device, dtype=torch.float32)
191
+ gen_images = model.generator(invar)
192
+ grid_loss = model.grid_regularizer(gen_images, outvar)
193
+ loss = grid_loss
194
+
195
+ if dist.is_initialized():
196
+ loss_tensor = loss.detach().to(device)
197
+ dist.all_reduce(loss_tensor)
198
+ loss = loss_tensor.item() / cfg.world_size
199
+ valid_loss += loss
200
+ else:
201
+ valid_loss += loss.item()
202
+ if world_rank == 0:
203
+ logger.info(f'Valid: Epoch {epoch}-{j+1}/{len(val_dataloader)} '
204
+ f'[cost {int((time.time()-start_time) // 60):02}:{int((time.time()-start_time) % 60):02}] '
205
+ f'loss:{valid_loss / (j+1): .04f}')
206
+
207
+ valid_loss /= len(val_dataloader)
208
+ is_save_ckp = False
209
+ if valid_loss < best_valid_loss:
210
+ best_valid_loss = valid_loss
211
+ best_loss_epoch = epoch
212
+ world_rank == 0 and save_checkpoint(model, optimizer_g, optimizer_d, scheduler_g, scheduler_d, best_valid_loss, best_loss_epoch, cfg.checkpoint_dir)
213
+ is_save_ckp = True
214
+ scheduler_g.step(valid_loss)
215
+ scheduler_d.step(valid_loss)
216
+
217
+ if world_rank == 0:
218
+ logger.info(f"Epoch [{epoch + 1}/{cfg.max_epoch}], "
219
+ f"Train Loss: {train_loss:.4f}, "
220
+ f"Valid Loss: {valid_loss:.4f}, "
221
+ f"Best loss at Epoch: {best_loss_epoch + 1}"
222
+ + (", saving checkpoint" if is_save_ckp else "")
223
+ )
224
+ train_losses = np.append(train_losses, train_loss)
225
+ valid_losses = np.append(valid_losses, valid_loss)
226
+ np.save(train_loss_file, train_losses)
227
+ np.save(valid_loss_file, valid_losses)
228
+
229
+ if epoch - best_loss_epoch > cfg.patience:
230
+ print(f"Loss has not decrease in {cfg.patience} epochs, stopping training...")
231
+ exit()
232
+
233
+
234
+ def save_checkpoint(model, optimizer_g, optimizer_d, scheduler_g, scheduler_d, best_valid_loss, best_loss_epoch, model_path):
235
+ model_to_save = model.module if hasattr(model, "module") else model
236
+ state = {"model_state_dict": model_to_save.state_dict(),
237
+ "optimizer_g_state_dict": optimizer_g.state_dict(),
238
+ "optimizer_d_state_dict": optimizer_d.state_dict(),
239
+ "scheduler_g_state_dict": scheduler_g.state_dict(),
240
+ "scheduler_d_state_dict": scheduler_d.state_dict(),
241
+ "best_valid_loss": best_valid_loss,
242
+ "best_loss_epoch": best_loss_epoch,
243
+ }
244
+ torch.save(state, f"{model_path}/model.pth")
245
+ ### the weight file saving may interrupted due to DCU queue limit, get a backup to ensure there at least has one model
246
+ os.system(f"mv {model_path}/model.pth {model_path}/model_bak.pth")
247
+
248
+
249
+ if __name__ == "__main__":
250
+ current_path = os.getcwd()
251
+ sys.path.append(current_path)
252
+ main()
weight/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+