Publish PrecipExtremes-GAN reproduction
Browse files- .gitattributes +1 -34
- README.md +130 -0
- README_zh.md +131 -0
- conf/config.yaml +43 -0
- config.json +12 -0
- configuration.json +12 -0
- model/precip_extremes_gan.py +47 -0
- scripts/fake_data.py +5 -0
- scripts/inference.py +9 -0
- scripts/result.py +14 -0
- scripts/train.py +22 -0
- weight/.gitkeep +0 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,2 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
tags:
|
| 6 |
+
- OneScience
|
| 7 |
+
- Earth Science
|
| 8 |
+
- Extreme Precipitation
|
| 9 |
+
- Generative Downscaling
|
| 10 |
+
frameworks: PyTorch
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
<p align="center"><strong><span style="font-size: 30px;">PrecipExtremes-GAN</span></strong></p>
|
| 14 |
+
|
| 15 |
+
# Model Introduction
|
| 16 |
+
|
| 17 |
+
PrecipExtremes-GAN uses a residual generative adversarial network to map coarse atmospheric fields to high-resolution daily precipitation and assess extrapolation of extreme changes to warmer climates.
|
| 18 |
+
|
| 19 |
+
Paper: On the Extrapolation of Generative Adversarial Networks for downscaling precipitation extremes in warmer climates
|
| 20 |
+
https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2024GL112492
|
| 21 |
+
|
| 22 |
+
# Model Description
|
| 23 |
+
|
| 24 |
+
The method was proposed by teams from New Zealand's National Institute of Water and Atmospheric Research, the University of New South Wales, and collaborating institutions. The paper trains on ACCESS-CM2-driven CCAM simulations and evaluates four independent GCM-driven historical and SSP3-7.0 simulations. The model supports daily precipitation downscaling and extreme-precipitation climate-change analysis over New Zealand.
|
| 25 |
+
|
| 26 |
+
# Use Cases
|
| 27 |
+
|
| 28 |
+
| Use Case | Description |
|
| 29 |
+
| :---: | :--- |
|
| 30 |
+
| Daily precipitation downscaling | Generate high-resolution precipitation from eight coarse atmospheric predictors. |
|
| 31 |
+
| Extreme extrapolation | Compare historical and future training for 99.5th-percentile precipitation change. |
|
| 32 |
+
| Local engineering validation | Validate deterministic U-Net, residual GAN, and ensemble inference. |
|
| 33 |
+
| ModelScope/OneCode execution | Validate structured data, training, inference, extreme-precipitation metrics, and visualization in ModelScope or OneCode environments. |
|
| 34 |
+
| Multi-GPU training | Validate distributed training and checkpoint workflows through `torchrun`. |
|
| 35 |
+
|
| 36 |
+
# Usage Instructions
|
| 37 |
+
|
| 38 |
+
## 1.OneCode
|
| 39 |
+
|
| 40 |
+
[Try intelligent, one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 41 |
+
|
| 42 |
+
## 2. Download and Installation
|
| 43 |
+
|
| 44 |
+
```bash
|
| 45 |
+
hf download OneScience-Group/PrecipExtremes-GAN --local-dir ./PrecipExtremes-GAN
|
| 46 |
+
cd PrecipExtremes-GAN
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
### Environment Dependencies
|
| 50 |
+
|
| 51 |
+
**Hardware Requirements**
|
| 52 |
+
|
| 53 |
+
- A GPU or DCU is recommended.
|
| 54 |
+
- A CPU can be used for connectivity validation with the default small-sample configuration.
|
| 55 |
+
- DCU users must install DTK in advance. DTK 25.04.2 or later, or the OneScience-recommended version matching the current cluster, is recommended.
|
| 56 |
+
|
| 57 |
+
**DCU Environment**
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
# Activate DTK and Conda first
|
| 61 |
+
conda create -n onescience311 python=3.11 -y
|
| 62 |
+
conda activate onescience311
|
| 63 |
+
pip install onescience[earth-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
**GPU Environment**
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
# Activate Conda first
|
| 70 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 71 |
+
conda activate onescience311
|
| 72 |
+
pip install onescience[earth-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
### Training Data
|
| 76 |
+
|
| 77 |
+
The paper predicts approximately 12 km CCAM daily precipitation from eight 1.5-degree U, V, T, and Q fields at 500 and 850 hPa. Since final tensor dimensions are not reported, this repository uses an explicitly documented `24×24 → 96×96` engineering grid. Synthetic data validate engineering only and do not represent CCAM or GCM distributions, scale, or paper performance.
|
| 78 |
+
|
| 79 |
+
```bash
|
| 80 |
+
python scripts/fake_data.py
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
### Training
|
| 84 |
+
|
| 85 |
+
For single-GPU training, use:
|
| 86 |
+
|
| 87 |
+
```bash
|
| 88 |
+
python scripts/train.py
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
For multi-GPU training, use:
|
| 92 |
+
|
| 93 |
+
```bash
|
| 94 |
+
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
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
Training first fits a deterministic U-Net and then trains a residual generator with MSE, adversarial, and intensity losses. The default reduces samples, width, ensemble size, and epochs, writing `result/checkpoints/precip_extremes_gan.pt` and `result/training/metrics.json`.
|
| 98 |
+
|
| 99 |
+
### Trained Weights
|
| 100 |
+
|
| 101 |
+
The paper does not provide directly loadable official model weights, and this repository bundles no weights under `weight/`.
|
| 102 |
+
|
| 103 |
+
### Inference
|
| 104 |
+
|
| 105 |
+
```bash
|
| 106 |
+
python scripts/inference.py
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
Inference writes deterministic, residual-GAN member, and ensemble-mean outputs to `result/output/predictions.npz`.
|
| 110 |
+
|
| 111 |
+
### Evaluation and Visualization
|
| 112 |
+
|
| 113 |
+
```bash
|
| 114 |
+
python scripts/result.py
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
Evaluation computes daily MAE, 99.5th-percentile error, and future-versus-historical climate-signal error and writes `result/evaluation/metrics.json` and `result/evaluation/comparison.png`.
|
| 118 |
+
|
| 119 |
+
# Official OneScience Information
|
| 120 |
+
|
| 121 |
+
| Platform | OneScience Main Repository | Skills Repository |
|
| 122 |
+
| --- | --- | --- |
|
| 123 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 124 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 125 |
+
|
| 126 |
+
# Citation and License
|
| 127 |
+
|
| 128 |
+
This repository is an independent engineering reproduction of the public PrecipExtremes-GAN specifications.
|
| 129 |
+
|
| 130 |
+
Use of this repository's code, official model weights, and data remains subject to the licenses and terms of their respective projects.
|
README_zh.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- zh
|
| 5 |
+
- en
|
| 6 |
+
tags:
|
| 7 |
+
- OneScience
|
| 8 |
+
- 地球科学
|
| 9 |
+
- 极端降水
|
| 10 |
+
- 生成式降尺度
|
| 11 |
+
frameworks: PyTorch
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
<p align="center"><strong><span style="font-size: 30px;">PrecipExtremes-GAN</span></strong></p>
|
| 15 |
+
|
| 16 |
+
# 模型介绍
|
| 17 |
+
|
| 18 |
+
PrecipExtremes-GAN 使用残差生成对抗网络将粗分辨率大气场转换为高分辨率日降水,并评估模型向更暖气候外推极端降水变化的能力。
|
| 19 |
+
|
| 20 |
+
论文:On the Extrapolation of Generative Adversarial Networks for downscaling precipitation extremes in warmer climates
|
| 21 |
+
https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2024GL112492
|
| 22 |
+
|
| 23 |
+
# 模型描述
|
| 24 |
+
|
| 25 |
+
该方法由新西兰国家水与大气研究所和新南威尔士大学等机构的研究团队提出。论文使用 ACCESS-CM2 驱动的 CCAM 模拟训练,并在四个独立 GCM 驱动的历史和 SSP3-7.0 模拟上评估。模型适用于新西兰区域的日降水降尺度及极端降水气候变化信号分析。
|
| 26 |
+
|
| 27 |
+
# 适用场景
|
| 28 |
+
|
| 29 |
+
| 场景 | 说明 |
|
| 30 |
+
| :---: | :--- |
|
| 31 |
+
| 日降水降尺度 | 根据 8 个粗分辨率大气变量生成高分辨率日降水。 |
|
| 32 |
+
| 极端降水外推 | 比较历史和未来训练对 99.5 百分位降水变化的影响。 |
|
| 33 |
+
| 本地工程验证 | 验证 deterministic U-Net、残差 GAN 和集合推理。 |
|
| 34 |
+
| ModelScope/OneCode 运行 | 在 ModelScope 或 OneCode 环境中验证结构化数据、训练、推理、极端降水指标和可视化流程。 |
|
| 35 |
+
| 多卡训练 | 通过 `torchrun` 验证分布式训练和 checkpoint 流程。 |
|
| 36 |
+
|
| 37 |
+
# 使用说明
|
| 38 |
+
|
| 39 |
+
## 1.OneCode
|
| 40 |
+
|
| 41 |
+
[点击体验智能化一键式 AI4S 编程](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 42 |
+
|
| 43 |
+
## 2.下载安装
|
| 44 |
+
|
| 45 |
+
```bash
|
| 46 |
+
modelscope download --model OneScience/PrecipExtremes-GAN --local_dir ./PrecipExtremes-GAN
|
| 47 |
+
cd PrecipExtremes-GAN
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### 环境依赖
|
| 51 |
+
|
| 52 |
+
**硬件要求**
|
| 53 |
+
|
| 54 |
+
- 推荐使用 GPU 或 DCU 运行。
|
| 55 |
+
- CPU 可用于默认小样本配置的连通性验证。
|
| 56 |
+
- DCU 用户需预先安装 DTK,建议使用 DTK 25.04.2 以上版本或与当前集群匹配的 OneScience 推荐版本。
|
| 57 |
+
|
| 58 |
+
**DCU环境**
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
# 请首先激活 DTK 及 Conda
|
| 62 |
+
conda create -n onescience311 python=3.11 -y
|
| 63 |
+
conda activate onescience311
|
| 64 |
+
pip install onescience[earth-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
**GPU环境**
|
| 68 |
+
|
| 69 |
+
```bash
|
| 70 |
+
# 请首先激活 Conda
|
| 71 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 72 |
+
conda activate onescience311
|
| 73 |
+
pip install onescience[earth-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
### 训练数据介绍
|
| 77 |
+
|
| 78 |
+
论文使用 1.5° 粗分辨率的 U、V、T 和 Q 在 500/850 hPa 的 8 个通道预测约 12 km CCAM 日降水。公开正文未给最终张量网格点数,本仓库使用 `24×24 → 96×96` 工程网格并明确标记为假设。虚拟数据仅用于验证工程流程,不代表 CCAM 或 GCM 的真实分布、训练规模或论文性能。
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
python scripts/fake_data.py
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
### 训练
|
| 85 |
+
|
| 86 |
+
单卡训练可使用:
|
| 87 |
+
|
| 88 |
+
```bash
|
| 89 |
+
python scripts/train.py
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
多卡训练可使用:
|
| 93 |
+
|
| 94 |
+
```bash
|
| 95 |
+
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
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
训练先拟合 deterministic U-Net,再使用 MSE、对抗损失和强度约束训练残差生成器。默认缩小样本、网络宽度、ensemble 和训练轮数,训练产物保存到 `result/checkpoints/precip_extremes_gan.pt` 和 `result/training/metrics.json`。
|
| 99 |
+
|
| 100 |
+
### 训练权重
|
| 101 |
+
|
| 102 |
+
论文未提供可直接加载的官方模型权重,本仓库不在 `weight/` 中内置权重。本地 checkpoint 不得描述为官方预训练权重。
|
| 103 |
+
|
| 104 |
+
### 推理
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
python scripts/inference.py
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
推理输出 deterministic baseline、残差 GAN 成员及集合均值,保存到 `result/output/predictions.npz`。
|
| 111 |
+
|
| 112 |
+
### 评估和可视化
|
| 113 |
+
|
| 114 |
+
```bash
|
| 115 |
+
python scripts/result.py
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
评估计算日降水 MAE、99.5 百分位误差和未来相对历史气候变化信号误差,并生成对比图。结果保存到 `result/evaluation/metrics.json` 和 `result/evaluation/comparison.png`。
|
| 119 |
+
|
| 120 |
+
# OneScience官方信息
|
| 121 |
+
|
| 122 |
+
| 平台 | OneScience 主仓库 | Skills 仓库 |
|
| 123 |
+
| --- | --- | --- |
|
| 124 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 125 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 126 |
+
|
| 127 |
+
# 引用与许可证
|
| 128 |
+
|
| 129 |
+
本仓库为 PrecipExtremes-GAN 公开规格的独立工程复现版本。
|
| 130 |
+
|
| 131 |
+
本仓库代码、官方模型权重和数据的使用仍应以各自项目中的许可证及使用条款为准。
|
conf/config.yaml
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
seed: 42
|
| 2 |
+
data:
|
| 3 |
+
path: data/precip_extremes_gan_fake.npz
|
| 4 |
+
format_version: precip_extremes_gan_v1
|
| 5 |
+
samples_per_period: 16
|
| 6 |
+
low_grid: [24, 24]
|
| 7 |
+
high_grid: [96, 96]
|
| 8 |
+
input_channels: 8
|
| 9 |
+
log_epsilon: 0.001
|
| 10 |
+
model:
|
| 11 |
+
filters: [8, 16, 32]
|
| 12 |
+
noise_channels: 1
|
| 13 |
+
train:
|
| 14 |
+
baseline_epochs: 1
|
| 15 |
+
gan_epochs: 1
|
| 16 |
+
batch_size: 2
|
| 17 |
+
baseline_lr: 0.0007
|
| 18 |
+
gan_lr: 0.0002
|
| 19 |
+
adversarial_weight: 0.01
|
| 20 |
+
intensity_weight: 1.0
|
| 21 |
+
runtime:
|
| 22 |
+
device: cpu
|
| 23 |
+
paths:
|
| 24 |
+
checkpoint: result/checkpoints/precip_extremes_gan.pt
|
| 25 |
+
training_metrics: result/training/metrics.json
|
| 26 |
+
predictions: result/output/predictions.npz
|
| 27 |
+
evaluation: result/evaluation/metrics.json
|
| 28 |
+
figure: result/evaluation/comparison.png
|
| 29 |
+
evaluation:
|
| 30 |
+
ensemble_members: 3
|
| 31 |
+
extreme_percentile: 99.5
|
| 32 |
+
wet_day_threshold: 1.0
|
| 33 |
+
paper_model:
|
| 34 |
+
filters: [32, 64, 128]
|
| 35 |
+
parameters_baseline: 1500000
|
| 36 |
+
epochs: 250
|
| 37 |
+
batch_size: 32
|
| 38 |
+
adversarial_weight: 0.01
|
| 39 |
+
ensemble_members: 10
|
| 40 |
+
historical_period: 1960-2014
|
| 41 |
+
future_period: 2044-2099
|
| 42 |
+
evaluation_historical: 1985-2014
|
| 43 |
+
evaluation_future: 2070-2099
|
config.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "PrecipExtremes-GAN",
|
| 3 |
+
"model_type": "precip_extremes_gan",
|
| 4 |
+
"architectures": ["PrecipExtremesGAN"],
|
| 5 |
+
"framework": "PyTorch",
|
| 6 |
+
"domain": "climate",
|
| 7 |
+
"task": "extreme-precipitation-downscaling",
|
| 8 |
+
"implementation": {"entry_point": "model/precip_extremes_gan.py", "scope": "core-method reduced-sample engineering reproduction", "train_script": "scripts/train.py", "inference_script": "scripts/inference.py", "evaluation_script": "scripts/result.py", "synthetic_data_script": "scripts/fake_data.py"},
|
| 9 |
+
"architecture": {"input_channels": 8, "output_channels": 1, "baseline": "residual U-Net", "generator": "stochastic residual U-Net", "critic": "conditional patch critic", "paper_filters": [32,64,128]},
|
| 10 |
+
"data": {"dataset": "CCAM New Zealand perfect-framework simulations", "format_version": "precip_extremes_gan_v1", "input_variables": ["U500","U850","V500","V850","T500","T850","Q500","Q850"], "target": "daily precipitation", "target_unit": "mm day-1", "engineering_low_grid": [24,24], "engineering_high_grid": [96,96], "grid_status": "engineering assumption because final tensor dimensions are not reported", "synthetic": true},
|
| 11 |
+
"configuration_sources": ["conf/config.yaml", "model/precip_extremes_gan.py", "scripts/fake_data.py", "scripts/train.py", "scripts/inference.py", "scripts/result.py"]
|
| 12 |
+
}
|
configuration.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"framework": "PyTorch",
|
| 3 |
+
"task": "extreme precipitation downscaling",
|
| 4 |
+
"model": "PrecipExtremes-GAN",
|
| 5 |
+
"input_format": "BCHW: eight coarse atmospheric predictor fields",
|
| 6 |
+
"protocol": "deterministic U-Net plus residual conditional GAN for historical-to-future extrapolation",
|
| 7 |
+
"default_config": "conf/config.yaml",
|
| 8 |
+
"training": "scripts/train.py",
|
| 9 |
+
"inference": "scripts/inference.py",
|
| 10 |
+
"evaluation": "scripts/result.py",
|
| 11 |
+
"visualization": "scripts/result.py"
|
| 12 |
+
}
|
model/precip_extremes_gan.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Residual U-Net and conditional GAN for precipitation extremes."""
|
| 2 |
+
import json
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from torch import nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
import yaml
|
| 9 |
+
|
| 10 |
+
def load_config(root): return yaml.safe_load((Path(root)/"conf/config.yaml").read_text())
|
| 11 |
+
|
| 12 |
+
class ResidualBlock(nn.Module):
|
| 13 |
+
def __init__(self, cin, cout):
|
| 14 |
+
super().__init__(); self.c1=nn.Conv2d(cin,cout,3,padding=1);self.c2=nn.Conv2d(cout,cout,3,padding=1);self.skip=nn.Conv2d(cin,cout,1) if cin!=cout else nn.Identity()
|
| 15 |
+
def forward(self,x): return F.relu(self.c2(F.relu(self.c1(x)))+self.skip(x))
|
| 16 |
+
|
| 17 |
+
class ResidualUNet(nn.Module):
|
| 18 |
+
def __init__(self,in_channels=8,filters=(8,16,32),out_channels=1):
|
| 19 |
+
super().__init__();a,b,c=filters;self.e1=ResidualBlock(in_channels,a);self.e2=ResidualBlock(a,b);self.mid=ResidualBlock(b,c);self.d2=ResidualBlock(c+b,b);self.d1=ResidualBlock(b+a,a);self.out=nn.Conv2d(a,out_channels,1)
|
| 20 |
+
def forward(self,x,out_size=(96,96)):
|
| 21 |
+
e1=self.e1(x);e2=self.e2(F.avg_pool2d(e1,2));m=self.mid(F.avg_pool2d(e2,2));d2=self.d2(torch.cat((F.interpolate(m,e2.shape[-2:],mode="bilinear",align_corners=False),e2),1));d1=self.d1(torch.cat((F.interpolate(d2,e1.shape[-2:],mode="bilinear",align_corners=False),e1),1));return F.interpolate(self.out(d1),size=out_size,mode="bilinear",align_corners=False)
|
| 22 |
+
|
| 23 |
+
class PrecipExtremesGAN(nn.Module):
|
| 24 |
+
def __init__(self,input_channels=8,filters=(8,16,32),noise_channels=1):
|
| 25 |
+
super().__init__();self.baseline=ResidualUNet(input_channels,filters);self.generator=ResidualUNet(input_channels+noise_channels,filters);self.model_config={"input_channels":input_channels,"filters":list(filters),"noise_channels":noise_channels}
|
| 26 |
+
def forward(self,x,noise=None,out_size=(96,96)):
|
| 27 |
+
base=self.baseline(x,out_size)
|
| 28 |
+
if noise is None: noise=torch.randn(x.shape[0],1,*x.shape[-2:],device=x.device)
|
| 29 |
+
return base,base+self.generator(torch.cat((x,noise),1),out_size)
|
| 30 |
+
|
| 31 |
+
class PatchCritic(nn.Module):
|
| 32 |
+
def __init__(self,input_channels=8):
|
| 33 |
+
super().__init__();self.net=nn.Sequential(nn.Conv2d(input_channels+1,16,4,2,1),nn.LeakyReLU(.2),nn.Conv2d(16,32,4,2,1),nn.LeakyReLU(.2),nn.Conv2d(32,1,3,1,1))
|
| 34 |
+
def forward(self,x,y): return self.net(torch.cat((F.interpolate(x,y.shape[-2:],mode="bilinear",align_corners=False),y),1))
|
| 35 |
+
|
| 36 |
+
def make_data(n,h,w,lh,lw,seed):
|
| 37 |
+
rng=np.random.default_rng(seed); yy,xx=np.mgrid[-1:1:complex(h),-1:1:complex(w)]; periods=[]
|
| 38 |
+
for future in (0,1):
|
| 39 |
+
for i in range(n):
|
| 40 |
+
phase=2*np.pi*i/n; coarse=np.stack([np.sin((k+1)*xx+phase)+np.cos((k+2)*yy-phase)+future*.2 for k in range(8)]).astype(np.float32)
|
| 41 |
+
low=torch.from_numpy(coarse[None]);low=F.interpolate(low,(lh,lw),mode="area")[0].numpy(); ups=F.interpolate(torch.from_numpy(low[None]),(h,w),mode="bilinear",align_corners=False)[0]
|
| 42 |
+
moisture=torch.sigmoid(ups[7:8]*1.2+ups[6:7]*.7);lift=torch.relu(ups[2:3]+.4*ups[0:1]);extreme=(1+future*.35)*8*np.exp(-((xx-.35*np.sin(phase))**2+(yy-.25*np.cos(phase))**2)/.018)
|
| 43 |
+
pr=np.maximum(0,(5*moisture*lift).numpy()[0]+extreme+np.maximum(0,rng.normal(0,.4,(h,w)))).astype(np.float32)
|
| 44 |
+
periods.append((low,pr[None],future))
|
| 45 |
+
return periods
|
| 46 |
+
|
| 47 |
+
def write_json(path,value): path=Path(path);path.parent.mkdir(parents=True,exist_ok=True);path.write_text(json.dumps(value,indent=2)+"\n")
|
scripts/fake_data.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
import sys,numpy as np
|
| 3 |
+
ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT))
|
| 4 |
+
from model.precip_extremes_gan import load_config,make_data
|
| 5 |
+
c=load_config(ROOT);d=c["data"];records=make_data(d["samples_per_period"],*d["high_grid"],*d["low_grid"],c["seed"]);x=np.stack([r[0] for r in records]);y=np.stack([r[1] for r in records]);period=np.array([r[2] for r in records]);split=np.array(["train" if i%d["samples_per_period"]<d["samples_per_period"]-4 else "test" for i in range(len(records))]);path=ROOT/d["path"];path.parent.mkdir(parents=True,exist_ok=True);np.savez_compressed(path,format_version=np.array(d["format_version"]),predictors=x,precipitation=y,period=period,split=split,unit=np.array("mm day-1"));print(path,x.shape,y.shape)
|
scripts/inference.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
import sys,numpy as np,torch
|
| 3 |
+
ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT))
|
| 4 |
+
from model.precip_extremes_gan import *
|
| 5 |
+
c=load_config(ROOT);d=np.load(ROOT/c["data"]["path"]);ck=torch.load(ROOT/c["paths"]["checkpoint"],map_location="cpu",weights_only=True);net=PrecipExtremesGAN(**ck["model_config"]);net.load_state_dict(ck["model"]);net.eval();mask=d["split"]=="test";x=torch.from_numpy(d["predictors"][mask]);members=[]
|
| 6 |
+
with torch.no_grad():
|
| 7 |
+
base=net.baseline(x,c["data"]["high_grid"])
|
| 8 |
+
for _ in range(c["evaluation"]["ensemble_members"]):members.append(base+net.generator(torch.cat((x,torch.randn(len(x),1,*x.shape[-2:])),1),c["data"]["high_grid"]))
|
| 9 |
+
z=torch.stack(members);p=(torch.exp(z)-c["data"]["log_epsilon"]).clamp_min(0).numpy();b=(torch.exp(base)-c["data"]["log_epsilon"]).clamp_min(0).numpy();path=ROOT/c["paths"]["predictions"];path.parent.mkdir(parents=True,exist_ok=True);np.savez_compressed(path,predictors=x.numpy(),target=d["precipitation"][mask],baseline=b,members=p,mean=p.mean(0),period=d["period"][mask],unit=d["unit"]);print(path)
|
scripts/result.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
import sys,numpy as np
|
| 3 |
+
import matplotlib;matplotlib.use("Agg");import matplotlib.pyplot as plt
|
| 4 |
+
ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT))
|
| 5 |
+
from model.precip_extremes_gan import load_config,write_json
|
| 6 |
+
c=load_config(ROOT);d=np.load(ROOT/c["paths"]["predictions"]);q=c["evaluation"]["extreme_percentile"];metrics={}
|
| 7 |
+
for name,p in (("deterministic",d["baseline"]),("gan",d["mean"])):
|
| 8 |
+
metrics[name]={"mae":float(np.mean(abs(p-d["target"]))),"extreme_percentile_mae":float(np.mean(abs(np.percentile(p,q,axis=0)-np.percentile(d["target"],q,axis=0))))}
|
| 9 |
+
def signal(a):
|
| 10 |
+
h=a[d["period"]==0].mean(0);f=a[d["period"]==1].mean(0);return 100*(f-h)/np.maximum(h,.1)
|
| 11 |
+
truth=signal(d["target"]);metrics["climate_signal_mae_percent"]={"deterministic":float(np.mean(abs(signal(d["baseline"])-truth))),"gan":float(np.mean(abs(signal(d["mean"])-truth)))};metrics["synthetic"]=True;write_json(ROOT/c["paths"]["evaluation"],metrics)
|
| 12 |
+
fig,ax=plt.subplots(1,4,figsize=(14,3.5));fields=(d["target"][0,0],d["baseline"][0,0],d["mean"][0,0],abs(d["mean"][0,0]-d["target"][0,0]));titles=("CCAM target","Deterministic","Residual GAN","Absolute error")
|
| 13 |
+
for a,f,t in zip(ax,fields,titles):im=a.imshow(f,cmap="Blues");a.set_title(t);a.axis("off");fig.colorbar(im,ax=a,shrink=.7)
|
| 14 |
+
fig.tight_layout();path=ROOT/c["paths"]["figure"];path.parent.mkdir(parents=True,exist_ok=True);fig.savefig(path,dpi=140);plt.close(fig);print(path)
|
scripts/train.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
import sys,os
|
| 3 |
+
import numpy as np,torch
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
from torch.nn.parallel import DistributedDataParallel
|
| 6 |
+
ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT))
|
| 7 |
+
from model.precip_extremes_gan import *
|
| 8 |
+
c=load_config(ROOT);rank=int(os.environ.get("RANK",0));world=int(os.environ.get("WORLD_SIZE",1));local=int(os.environ.get("LOCAL_RANK",0));distributed=world>1;use_cuda=torch.cuda.is_available() and torch.cuda.device_count()>=world and c["runtime"]["device"]!="cpu"
|
| 9 |
+
if distributed:torch.distributed.init_process_group(backend="nccl" if use_cuda else "gloo")
|
| 10 |
+
device=torch.device(f"cuda:{local}" if use_cuda else "cpu");torch.manual_seed(c["seed"]+rank);torch.set_num_threads(2);d=np.load(ROOT/c["data"]["path"]);m=d["split"]=="train";x=torch.from_numpy(d["predictors"][m]).to(device);y=torch.log(torch.from_numpy(d["precipitation"][m])+c["data"]["log_epsilon"]).to(device);net=PrecipExtremesGAN(c["data"]["input_channels"],c["model"]["filters"],c["model"]["noise_channels"]).to(device);critic=PatchCritic(c["data"]["input_channels"]).to(device)
|
| 11 |
+
baseline=DistributedDataParallel(net.baseline,device_ids=[local] if use_cuda else None) if distributed else net.baseline;generator=DistributedDataParallel(net.generator,device_ids=[local] if use_cuda else None) if distributed else net.generator;critic_train=DistributedDataParallel(critic,device_ids=[local] if use_cuda else None) if distributed else critic
|
| 12 |
+
ob=torch.optim.Adam(baseline.parameters(),lr=c["train"]["baseline_lr"])
|
| 13 |
+
for _ in range(c["train"]["baseline_epochs"]): pred=baseline(x,c["data"]["high_grid"]);loss=F.mse_loss(pred,y);ob.zero_grad();loss.backward();ob.step()
|
| 14 |
+
og=torch.optim.Adam(generator.parameters(),lr=c["train"]["gan_lr"]);oc=torch.optim.Adam(critic_train.parameters(),lr=c["train"]["gan_lr"])
|
| 15 |
+
for _ in range(c["train"]["gan_epochs"]):
|
| 16 |
+
with torch.no_grad():base=baseline(x,c["data"]["high_grid"])
|
| 17 |
+
fake=base+generator(torch.cat((x,torch.randn(len(x),1,*x.shape[-2:],device=device)),1),c["data"]["high_grid"]);cl=F.softplus(critic_train(x,fake.detach())).mean()+F.softplus(-critic_train(x,y)).mean();oc.zero_grad();cl.backward();oc.step();adv=F.softplus(-critic_train(x,fake)).mean();intensity=F.mse_loss(fake.mean((2,3)),y.mean((2,3)));gl=F.mse_loss(fake.mean(0),y.mean(0))+c["train"]["adversarial_weight"]*adv+intensity;og.zero_grad();gl.backward();og.step()
|
| 18 |
+
vals=torch.tensor([float(loss),float(gl),float(cl)],device=device);
|
| 19 |
+
if distributed:torch.distributed.all_reduce(vals);vals/=world
|
| 20 |
+
if rank==0:
|
| 21 |
+
path=ROOT/c["paths"]["checkpoint"];path.parent.mkdir(parents=True,exist_ok=True);torch.save({"model":net.state_dict(),"critic":critic.state_dict(),"model_config":net.model_config,"format_version":c["data"]["format_version"]},path);write_json(ROOT/c["paths"]["training_metrics"],{"baseline_mse":float(vals[0]),"generator_loss":float(vals[1]),"critic_loss":float(vals[2]),"world_size":world});print(path)
|
| 22 |
+
if distributed:torch.distributed.destroy_process_group()
|
weight/.gitkeep
ADDED
|
File without changes
|