zhangrenchao commited on
Commit
ffdf763
·
verified ·
1 Parent(s): 36e4eb5

Publish PrecipExtremes-GAN reproduction

Browse files
.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
- *.pth filter=lfs diff=lfs merge=lfs -text
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