Publish CausalModelEvaluation engineering reproduction
Browse files- .gitattributes +1 -24
- README.md +139 -0
- README_zh.md +140 -0
- conf/config.yaml +34 -0
- config.json +38 -0
- configuration.json +13 -0
- model/causalmodelevaluation.py +182 -0
- scripts/fake_data.py +91 -0
- scripts/inference.py +52 -0
- scripts/result.py +83 -0
- scripts/train.py +53 -0
- weight/.gitkeep +0 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,12 @@
|
|
| 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 |
-
*
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
| 1 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 2 |
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
| 3 |
*.joblib filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 4 |
*.model filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 5 |
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 6 |
*.npz filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 8 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 9 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 10 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
weight/* filter=lfs diff=lfs merge=lfs -text
|
|
|
README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-4.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
tags:
|
| 6 |
+
- OneScience
|
| 7 |
+
- Earth Science
|
| 8 |
+
- Causal Networks
|
| 9 |
+
- Climate Model Evaluation
|
| 10 |
+
frameworks: PyTorch
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
<p align="center">
|
| 14 |
+
<strong><span style="font-size: 30px;">CausalModelEvaluation</span></strong>
|
| 15 |
+
</p>
|
| 16 |
+
|
| 17 |
+
# Model Introduction
|
| 18 |
+
|
| 19 |
+
CausalModelEvaluation (CME) addresses the limitations of climate-model evaluations that rely mainly on mean-state errors and do not determine whether key process relationships are represented correctly. It is primarily intended to construct causal climate fingerprints, identify dependencies among models with shared development backgrounds, evaluate precipitation simulation skill, and use historical process skill to constrain uncertainty in future precipitation change.
|
| 20 |
+
|
| 21 |
+
Paper: Causal networks for climate model evaluation and constrained projections
|
| 22 |
+
https://doi.org/10.1038/s41467-020-15195-y
|
| 23 |
+
|
| 24 |
+
# Model Description
|
| 25 |
+
|
| 26 |
+
Causal Model Evaluation was proposed by research teams from Imperial College London, the German Aerospace Center, the University of Bremen, and the University of East Anglia. The paper evaluates CMIP5 sea-level-pressure and precipitation simulations with NCEP-NCAR and ERA-Interim reanalyses and CRU TS v4.02 precipitation observations. The model supports causal climate-fingerprint reconstruction, process-oriented climate-model evaluation, and constrained precipitation-change projection.
|
| 27 |
+
|
| 28 |
+
# Use Cases
|
| 29 |
+
|
| 30 |
+
| Use Case | Description |
|
| 31 |
+
| :---: | :--- |
|
| 32 |
+
| Causal fingerprinting | Estimate a directed, signed, lagged network from seasonal 50-node time series. |
|
| 33 |
+
| Climate model evaluation | Compute reference-oriented asymmetric F1 with direction, sign, and lag tolerance. |
|
| 34 |
+
| Precipitation skill | Compute latitude-area-weighted Taylor S-score and pattern correlation. |
|
| 35 |
+
| Constrained projection | Fit an RBF plus white-noise GP between CME F1 and precipitation change. |
|
| 36 |
+
| ModelScope/OneCode | Validate data, fitting, checkpoint, inference, evaluation, and task-figure workflows. |
|
| 37 |
+
|
| 38 |
+
# Usage Instructions
|
| 39 |
+
|
| 40 |
+
## 1.OneCode
|
| 41 |
+
|
| 42 |
+
Experience intelligent, one-click AI4S programming through the OneCode online environment:
|
| 43 |
+
|
| 44 |
+
[Try intelligent, one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 45 |
+
|
| 46 |
+
## 2. Download and Installation
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
hf download OneScience-Group/CausalModelEvaluation --local-dir ./CausalModelEvaluation
|
| 50 |
+
cd CausalModelEvaluation
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
### Environment Dependencies
|
| 54 |
+
|
| 55 |
+
**Hardware Requirements**
|
| 56 |
+
|
| 57 |
+
- A GPU or DCU is recommended.
|
| 58 |
+
- A CPU can be used for connectivity validation with the default small-sample configuration.
|
| 59 |
+
- DCU users must install DTK in advance. DTK 25.04.2 or later, or the OneScience-recommended version matching the current cluster, is recommended.
|
| 60 |
+
|
| 61 |
+
**DCU Environment**
|
| 62 |
+
|
| 63 |
+
```bash
|
| 64 |
+
# Activate DTK and Conda first
|
| 65 |
+
conda create -n onescience311 python=3.11 -y
|
| 66 |
+
conda activate onescience311
|
| 67 |
+
pip install onescience[earth-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
**GPU Environment**
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
# Activate Conda first
|
| 74 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 75 |
+
conda activate onescience311
|
| 76 |
+
pip install onescience[earth-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
### Training Data
|
| 80 |
+
|
| 81 |
+
```bash
|
| 82 |
+
python scripts/fake_data.py
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
This repository uses a small number of structured synthetic samples to validate the engineering workflow. The data contains seasonal sea-level-pressure mode time series, distinct lagged causal relationships across models, and precipitation fields and changes related to model process skill. It preserves the paper's node, time, lag, and spatial dimensions and validates causal-network construction, model comparison, and constrained projection only; it does not represent the real climate-data distribution or paper performance.
|
| 86 |
+
|
| 87 |
+
### Training
|
| 88 |
+
|
| 89 |
+
For single-device training, use:
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
python scripts/train.py
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
For multi-GPU training, use:
|
| 96 |
+
|
| 97 |
+
```bash
|
| 98 |
+
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
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
Training results include reference and model causal networks, model-comparison scores, and the precipitation-constraint relationship, with a checkpoint and training metrics saved for subsequent inference.
|
| 102 |
+
|
| 103 |
+
```text
|
| 104 |
+
result/checkpoints/causalmodelevaluation.pt
|
| 105 |
+
result/training/metrics.json
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
### Trained Weights
|
| 109 |
+
|
| 110 |
+
No weights are bundled under `weight/`. The paper method produces no conventional neural-network weights; the engineering checkpoint stores statistical networks and GP state and is not claimed to be compatible with external weights.
|
| 111 |
+
|
| 112 |
+
### Inference
|
| 113 |
+
|
| 114 |
+
```bash
|
| 115 |
+
python scripts/inference.py
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
Inference reloads the checkpoint and exports complete model and four-season `edges/pvalues/mci`, complete reference networks, reference/model precipitation fields, CME F1, precipitation changes, GP means and 95% intervals, and network/projection metadata to `result/output/inference.npz`.
|
| 119 |
+
|
| 120 |
+
### Evaluation and Visualization
|
| 121 |
+
|
| 122 |
+
```bash
|
| 123 |
+
python scripts/result.py
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
Evaluation generates structured results for climate-model causal-network comparison, precipitation simulation skill, and constrained projection, and saves them to `result/evaluation/metrics.json`. It also creates the model-skill and precipitation-change comparison figure `result/evaluation/cme_task.png`. Synthetic-data results are only for engineering-workflow validation and do not represent paper performance on the real test set.
|
| 127 |
+
|
| 128 |
+
# Official OneScience Information
|
| 129 |
+
|
| 130 |
+
| Platform | OneScience Main Repository | Skills Repository |
|
| 131 |
+
| --- | --- | --- |
|
| 132 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 133 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 134 |
+
|
| 135 |
+
# Citation and License
|
| 136 |
+
|
| 137 |
+
This repository is an independent engineering reproduction of the public Causal Model Evaluation paper specifications.
|
| 138 |
+
|
| 139 |
+
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,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-4.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
- zh
|
| 6 |
+
tags:
|
| 7 |
+
- OneScience
|
| 8 |
+
- 地球科学
|
| 9 |
+
- 因果网络
|
| 10 |
+
- 气候模型评估
|
| 11 |
+
frameworks: PyTorch
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
<p align="center">
|
| 15 |
+
<strong><span style="font-size: 30px;">CausalModelEvaluation</span></strong>
|
| 16 |
+
</p>
|
| 17 |
+
|
| 18 |
+
# 模型介绍
|
| 19 |
+
|
| 20 |
+
CausalModelEvaluation(CME)用于解决传统气候模型评价过度依赖平均状态误差、难以衡量过程关系是否正确的问题,通过比较气候模型与再分析资料中的因果联系评价模型对关键气候过程的表示能力。该方法主要用于构建气候因果指纹、识别具有共同开发背景的模型依赖性、评价降水模拟能力,并利用历史过程技能约束未来降水变化的不确定性。
|
| 21 |
+
|
| 22 |
+
论文:Causal networks for climate model evaluation and constrained projections
|
| 23 |
+
https://doi.org/10.1038/s41467-020-15195-y
|
| 24 |
+
|
| 25 |
+
# 模型描述
|
| 26 |
+
|
| 27 |
+
Causal Model Evaluation 由 Imperial College London、German Aerospace Center、University of Bremen 和 University of East Anglia 的研究团队提出。论文使用 CMIP5 海平面气压与降水模拟、NCEP-NCAR 和 ERA-Interim 再分析以及 CRU TS v4.02 降水观测开展评价。模型适用于气候因果指纹重建、过程导向的气候模型评价和降水变化约束投影。
|
| 28 |
+
|
| 29 |
+
# 适用场景
|
| 30 |
+
|
| 31 |
+
| 场景 | 说明 |
|
| 32 |
+
| :---: | :--- |
|
| 33 |
+
| 因果指纹重建 | 从 50 节点季节时间序列估计有向、有符号、时滞网络。 |
|
| 34 |
+
| 气候模型评估 | 以参考网络为基准计算方向、符号和时滞容忍的非对称 F1。 |
|
| 35 |
+
| 降水技能评估 | 计算带纬度面积权重的 Taylor S-score 和空间型相关。 |
|
| 36 |
+
| 约束投影 | 拟合 CME F1 与降水变化之间的 RBF 加白噪声 GP。 |
|
| 37 |
+
| ModelScope/OneCode | 验证数据、拟合、checkpoint、推理、评估和任务图流程。 |
|
| 38 |
+
|
| 39 |
+
# 使用说明
|
| 40 |
+
|
| 41 |
+
## 1.OneCode
|
| 42 |
+
|
| 43 |
+
可通过 OneCode 在线环境体验智能化一键式 AI4S 编程:
|
| 44 |
+
|
| 45 |
+
[点击体验智能化一键式 AI4S 编程](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 46 |
+
|
| 47 |
+
## 2.下载安装
|
| 48 |
+
|
| 49 |
+
```bash
|
| 50 |
+
modelscope download --model OneScience/CausalModelEvaluation --local_dir ./CausalModelEvaluation
|
| 51 |
+
cd CausalModelEvaluation
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
### 环境依赖
|
| 55 |
+
|
| 56 |
+
**硬件要求**
|
| 57 |
+
|
| 58 |
+
- 推荐使用 GPU 或 DCU 运行。
|
| 59 |
+
- CPU 可用于默认小样本配置的连通性验证。
|
| 60 |
+
- DCU 用户需预先安装 DTK,建议使用 DTK 25.04.2 以上版本或与当前集群匹配的 OneScience 推荐版本。
|
| 61 |
+
|
| 62 |
+
**DCU环境**
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
# 请首先激活DTK及CONDA
|
| 66 |
+
conda create -n onescience311 python=3.11 -y
|
| 67 |
+
conda activate onescience311
|
| 68 |
+
pip install onescience[earth-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
**GPU环境**
|
| 72 |
+
|
| 73 |
+
```bash
|
| 74 |
+
# 请首先激活CONDA
|
| 75 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 76 |
+
conda activate onescience311
|
| 77 |
+
pip install onescience[earth-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### 训练数据介绍
|
| 81 |
+
|
| 82 |
+
```bash
|
| 83 |
+
python scripts/fake_data.py
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
本仓库使用少量结构化虚拟样本验证工程流程,数据包含四季海平面气压模态时间序列、模型间不同的滞后因果关系,以及与模型过程技能相关的降水空间场和降水变化。虚拟数据保持论文的节点、时间、时滞和空间维度,仅用于验证因果网络构建、模型比较和约束投影流程,不代表真实气候数据分布与论文性能。
|
| 87 |
+
|
| 88 |
+
### 训练
|
| 89 |
+
|
| 90 |
+
单卡训练可使用:
|
| 91 |
+
|
| 92 |
+
```bash
|
| 93 |
+
python scripts/train.py
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
多卡训练可使用:
|
| 97 |
+
|
| 98 |
+
```bash
|
| 99 |
+
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
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
训练结果包含参考与模型因果网络、模型比较分数和降水约束关系,并保存 checkpoint 与训练指标供后续推理使用。
|
| 103 |
+
|
| 104 |
+
```text
|
| 105 |
+
result/checkpoints/causalmodelevaluation.pt
|
| 106 |
+
result/training/metrics.json
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
### 训练权重
|
| 110 |
+
|
| 111 |
+
本仓库不在 `weight/` 中内置权重。论文方法不产生传统神经网络权重,工程 checkpoint 保存统计网络与 GP 状态,不声明兼容外部权重。
|
| 112 |
+
|
| 113 |
+
### 推理
|
| 114 |
+
|
| 115 |
+
```bash
|
| 116 |
+
python scripts/inference.py
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
推理重新加载 checkpoint,输出所有模型与四季的完整 `edges/pvalues/mci`、完整参考网络、参考及模型降水场、CME F1、降水变化、GP 均值与 95% 区间,以及网络和投影元数据到 `result/output/inference.npz`。
|
| 120 |
+
|
| 121 |
+
### 评估和可视化
|
| 122 |
+
|
| 123 |
+
```bash
|
| 124 |
+
python scripts/result.py
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
评估生成气候模型因果网络比较、降水模拟能力和约束投影的结构化结果,并保存到 `result/evaluation/metrics.json`。脚本同时生成模型技能与降水变化关系的对比图 `result/evaluation/cme_task.png`。虚拟数据结果仅用于验证工程流程,不代表论文真实测试集性能。
|
| 128 |
+
|
| 129 |
+
# OneScience官方信息
|
| 130 |
+
|
| 131 |
+
| 平台 | OneScience 主仓库 | Skills 仓库 |
|
| 132 |
+
| --- | --- | --- |
|
| 133 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 134 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 135 |
+
|
| 136 |
+
# 引用与许可证
|
| 137 |
+
|
| 138 |
+
本仓库为 Causal Model Evaluation 论文公开规格的独立工程复现版本。
|
| 139 |
+
|
| 140 |
+
本仓库代码、官方模型权重和数据的使用仍应以各自项目中的许可证及使用条款为准。
|
conf/config.yaml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
seed: 42
|
| 2 |
+
data:
|
| 3 |
+
root: data
|
| 4 |
+
format_version: cme_structured_var_seasonal_v1
|
| 5 |
+
samples: 1
|
| 6 |
+
time_steps: 2100
|
| 7 |
+
nodes: 50
|
| 8 |
+
seasons: [DJF, MAM, JJA, SON]
|
| 9 |
+
model_count: 4
|
| 10 |
+
grid_shape: [73, 144]
|
| 11 |
+
years_per_segment: 70
|
| 12 |
+
time_step_days: 3
|
| 13 |
+
model:
|
| 14 |
+
max_lag: 10
|
| 15 |
+
alpha: 0.02
|
| 16 |
+
ridge: 1.0e-6
|
| 17 |
+
exclude_self_links: true
|
| 18 |
+
paper_model:
|
| 19 |
+
nodes: 50
|
| 20 |
+
time_step_days: 3
|
| 21 |
+
max_lag: 10
|
| 22 |
+
maximum_delay_days: 30
|
| 23 |
+
alpha: 1.0e-4
|
| 24 |
+
method: PCMCI with partial correlation
|
| 25 |
+
engineering_approximation: target-history conditional regression with partial correlation
|
| 26 |
+
evaluation:
|
| 27 |
+
lag_tolerance: 2
|
| 28 |
+
reference_f1_for_projection: 0.64
|
| 29 |
+
paths:
|
| 30 |
+
dataset: data/cme_fake.npz
|
| 31 |
+
checkpoint: result/checkpoints/causalmodelevaluation.pt
|
| 32 |
+
training_metrics: result/training/metrics.json
|
| 33 |
+
inference: result/output/inference.npz
|
| 34 |
+
evaluation_dir: result/evaluation
|
config.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "CausalModelEvaluation",
|
| 3 |
+
"model_type": "causalmodelevaluation",
|
| 4 |
+
"architectures": ["LaggedPartialCorrelationCME", "PrecipitationConstraintGP"],
|
| 5 |
+
"framework": "PyTorch/NumPy/scikit-learn",
|
| 6 |
+
"domain": "climate-science",
|
| 7 |
+
"task": "causal-network-model-evaluation-and-constrained-precipitation-projection",
|
| 8 |
+
"implementation": {
|
| 9 |
+
"entry_point": "model/causalmodelevaluation.py",
|
| 10 |
+
"scope": "linear PCMCI-ParCorr engineering approximation at the paper node and lag dimensions",
|
| 11 |
+
"train_script": "scripts/train.py",
|
| 12 |
+
"inference_script": "scripts/inference.py",
|
| 13 |
+
"evaluation_script": "scripts/result.py",
|
| 14 |
+
"synthetic_data_script": "scripts/fake_data.py"
|
| 15 |
+
},
|
| 16 |
+
"architecture": {
|
| 17 |
+
"family": "lagged partial correlation conditional regression and Gaussian process",
|
| 18 |
+
"nodes": 50,
|
| 19 |
+
"maximum_lag_steps": 10,
|
| 20 |
+
"time_step_days": 3,
|
| 21 |
+
"paper_significance_threshold": 0.0001,
|
| 22 |
+
"engineering_fake_data_threshold": 0.02,
|
| 23 |
+
"network_layout": ["source", "target", "lag"],
|
| 24 |
+
"network_shape": [50, 50, 10]
|
| 25 |
+
},
|
| 26 |
+
"data": {
|
| 27 |
+
"protocol": "cme_structured_var_seasonal_v1",
|
| 28 |
+
"node_series_layout": ["member", "three_day_step", "node"],
|
| 29 |
+
"node_series_shape": ["B", 2100, 50],
|
| 30 |
+
"years_per_segment": 70,
|
| 31 |
+
"seasons": ["DJF", "MAM", "JJA", "SON"],
|
| 32 |
+
"precipitation_grid_shape": [73, 144],
|
| 33 |
+
"precipitation_grid_degrees": 2.5,
|
| 34 |
+
"network_output_shape": [50, 50, 10],
|
| 35 |
+
"model_count": 4
|
| 36 |
+
},
|
| 37 |
+
"configuration_sources": ["conf/config.yaml", "model/causalmodelevaluation.py", "scripts/fake_data.py", "scripts/train.py", "scripts/inference.py", "scripts/result.py"]
|
| 38 |
+
}
|
configuration.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"framework": "PyTorch/NumPy/scikit-learn",
|
| 3 |
+
"task": "causal-network-evaluation-and-precipitation-constraint",
|
| 4 |
+
"model": "CausalModelEvaluation",
|
| 5 |
+
"input_format": "T50 or BT50",
|
| 6 |
+
"output_format": "source-target-lag tensors [50,50,10]",
|
| 7 |
+
"protocol": "lagged conditional regression approximation to linear PCMCI plus asymmetric signed F1, Taylor S-score, and RBF+white GP",
|
| 8 |
+
"default_config": "conf/config.yaml",
|
| 9 |
+
"training": "scripts/train.py",
|
| 10 |
+
"inference": "scripts/inference.py",
|
| 11 |
+
"evaluation": "scripts/result.py",
|
| 12 |
+
"visualization": "scripts/result.py"
|
| 13 |
+
}
|
model/causalmodelevaluation.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Core statistical models and metrics for causal model evaluation (CME)."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
from scipy import stats
|
| 10 |
+
from sklearn.gaussian_process import GaussianProcessRegressor
|
| 11 |
+
from sklearn.gaussian_process.kernels import ConstantKernel, RBF, WhiteKernel
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
FORMAT_VERSION = "cme_lagged_parcorr_v1"
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def _as_series(data: np.ndarray | torch.Tensor, nodes: int) -> np.ndarray:
|
| 18 |
+
"""Normalize [T,N] or [B,T,N] input without joining sample boundaries."""
|
| 19 |
+
array = data.detach().cpu().numpy() if isinstance(data, torch.Tensor) else np.asarray(data)
|
| 20 |
+
if array.ndim == 2:
|
| 21 |
+
array = array[None, ...]
|
| 22 |
+
if array.ndim != 3 or array.shape[-1] != nodes:
|
| 23 |
+
raise ValueError(f"expected [T,{nodes}] or [B,T,{nodes}], got {array.shape}")
|
| 24 |
+
if not np.isfinite(array).all():
|
| 25 |
+
raise ValueError("node time series contains NaN or Inf")
|
| 26 |
+
return array.astype(np.float64, copy=False)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@dataclass
|
| 30 |
+
class CausalNetwork:
|
| 31 |
+
"""A directed signed lagged network with source-target-lag tensor layout."""
|
| 32 |
+
|
| 33 |
+
edges: torch.Tensor
|
| 34 |
+
pvalues: torch.Tensor
|
| 35 |
+
mci: torch.Tensor
|
| 36 |
+
|
| 37 |
+
def state_dict(self) -> dict[str, torch.Tensor]:
|
| 38 |
+
return {"edges": self.edges.cpu(), "pvalues": self.pvalues.cpu(), "mci": self.mci.cpu()}
|
| 39 |
+
|
| 40 |
+
@classmethod
|
| 41 |
+
def from_state_dict(cls, state: dict[str, torch.Tensor]) -> "CausalNetwork":
|
| 42 |
+
return cls(state["edges"].bool(), state["pvalues"].float(), state["mci"].float())
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class LaggedPartialCorrelationCME:
|
| 46 |
+
"""Linear conditional-regression approximation to PCMCI's ParCorr MCI step.
|
| 47 |
+
|
| 48 |
+
Each target is conditioned on its own history through ``max_lag``. All
|
| 49 |
+
source-lag candidates are then residualized against that common condition
|
| 50 |
+
set and tested with a two-sided t test. This captures directed lagged
|
| 51 |
+
conditional dependence, but does not implement PCMCI's iterative PC stage.
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
def __init__(self, nodes: int = 50, max_lag: int = 10, alpha: float = 0.02,
|
| 55 |
+
ridge: float = 1e-6, exclude_self_links: bool = True):
|
| 56 |
+
self.nodes = int(nodes)
|
| 57 |
+
self.max_lag = int(max_lag)
|
| 58 |
+
self.alpha = float(alpha)
|
| 59 |
+
self.ridge = float(ridge)
|
| 60 |
+
self.exclude_self_links = bool(exclude_self_links)
|
| 61 |
+
if self.nodes <= 1 or self.max_lag < 1 or not 0 < self.alpha < 1:
|
| 62 |
+
raise ValueError("nodes, max_lag, and alpha must define a valid test")
|
| 63 |
+
|
| 64 |
+
def fit(self, data: np.ndarray | torch.Tensor) -> CausalNetwork:
|
| 65 |
+
samples = _as_series(data, self.nodes)
|
| 66 |
+
if samples.shape[1] <= self.max_lag + 2:
|
| 67 |
+
raise ValueError("time dimension is too short for requested maximum lag")
|
| 68 |
+
current = np.concatenate([x[self.max_lag:] for x in samples], axis=0)
|
| 69 |
+
lagged = np.concatenate([
|
| 70 |
+
np.stack([x[self.max_lag - lag:-lag] for lag in range(1, self.max_lag + 1)], axis=2)
|
| 71 |
+
for x in samples
|
| 72 |
+
], axis=0) # [observations, source, lag]
|
| 73 |
+
candidates = lagged.reshape(len(current), -1)
|
| 74 |
+
candidates -= candidates.mean(axis=0, keepdims=True)
|
| 75 |
+
pvalues = np.ones((self.nodes, self.nodes, self.max_lag), dtype=np.float32)
|
| 76 |
+
mci = np.zeros_like(pvalues)
|
| 77 |
+
for target in range(self.nodes):
|
| 78 |
+
controls = lagged[:, target, :]
|
| 79 |
+
controls = np.column_stack([np.ones(len(controls)), controls])
|
| 80 |
+
gram = controls.T @ controls + self.ridge * np.eye(controls.shape[1])
|
| 81 |
+
projection = np.linalg.solve(gram, controls.T)
|
| 82 |
+
residual_x = candidates - controls @ (projection @ candidates)
|
| 83 |
+
y = current[:, target]
|
| 84 |
+
residual_y = y - controls @ (projection @ y)
|
| 85 |
+
numerator = residual_x.T @ residual_y
|
| 86 |
+
denominator = np.sqrt(np.sum(residual_x ** 2, axis=0) * np.sum(residual_y ** 2))
|
| 87 |
+
correlation = np.divide(numerator, denominator, out=np.zeros_like(numerator), where=denominator > 1e-12)
|
| 88 |
+
correlation = np.clip(correlation, -0.999999, 0.999999)
|
| 89 |
+
dof = max(len(y) - controls.shape[1] - 1, 1)
|
| 90 |
+
statistic = np.abs(correlation) * np.sqrt(dof / np.maximum(1.0 - correlation ** 2, 1e-12))
|
| 91 |
+
probability = 2.0 * stats.t.sf(statistic, dof)
|
| 92 |
+
mci[:, target, :] = correlation.reshape(self.nodes, self.max_lag)
|
| 93 |
+
pvalues[:, target, :] = probability.reshape(self.nodes, self.max_lag)
|
| 94 |
+
if self.exclude_self_links:
|
| 95 |
+
diagonal = np.arange(self.nodes)
|
| 96 |
+
pvalues[diagonal, diagonal, :] = 1.0
|
| 97 |
+
mci[diagonal, diagonal, :] = 0.0
|
| 98 |
+
edges = pvalues < self.alpha
|
| 99 |
+
return CausalNetwork(torch.from_numpy(edges), torch.from_numpy(pvalues), torch.from_numpy(mci))
|
| 100 |
+
|
| 101 |
+
def config(self) -> dict:
|
| 102 |
+
return {"nodes": self.nodes, "max_lag": self.max_lag, "alpha": self.alpha,
|
| 103 |
+
"ridge": self.ridge, "exclude_self_links": self.exclude_self_links}
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def asymmetric_f1(reference: CausalNetwork, candidate: CausalNetwork, lag_tolerance: int = 2) -> dict[str, float | int]:
|
| 107 |
+
"""Compare direction and sign while allowing candidate lag error of +/- tolerance."""
|
| 108 |
+
ref_edges, pred_edges = reference.edges.numpy(), candidate.edges.numpy()
|
| 109 |
+
ref_sign, pred_sign = np.sign(reference.mci.numpy()), np.sign(candidate.mci.numpy())
|
| 110 |
+
|
| 111 |
+
def matched(edges_a, sign_a, edges_b, sign_b):
|
| 112 |
+
hits = 0
|
| 113 |
+
for source, target, lag in np.argwhere(edges_a):
|
| 114 |
+
lo, hi = max(0, lag - lag_tolerance), min(edges_b.shape[2], lag + lag_tolerance + 1)
|
| 115 |
+
hits += int(np.any(edges_b[source, target, lo:hi] &
|
| 116 |
+
(sign_b[source, target, lo:hi] == sign_a[source, target, lag])))
|
| 117 |
+
return hits
|
| 118 |
+
|
| 119 |
+
ref_count, pred_count = int(ref_edges.sum()), int(pred_edges.sum())
|
| 120 |
+
recall_hits = matched(ref_edges, ref_sign, pred_edges, pred_sign)
|
| 121 |
+
precision_hits = matched(pred_edges, pred_sign, ref_edges, ref_sign)
|
| 122 |
+
recall = recall_hits / ref_count if ref_count else float(pred_count == 0)
|
| 123 |
+
precision = precision_hits / pred_count if pred_count else float(ref_count == 0)
|
| 124 |
+
f1 = 2 * precision * recall / (precision + recall) if precision + recall else 0.0
|
| 125 |
+
return {"f1": float(f1), "precision": float(precision), "recall": float(recall),
|
| 126 |
+
"reference_edges": ref_count, "candidate_edges": pred_count,
|
| 127 |
+
"precision_matches": precision_hits, "recall_matches": recall_hits,
|
| 128 |
+
"lag_tolerance": int(lag_tolerance)}
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def pattern_correlation(reference: np.ndarray, model: np.ndarray, weights: np.ndarray | None = None) -> float:
|
| 132 |
+
reference, model = np.asarray(reference, float).ravel(), np.asarray(model, float).ravel()
|
| 133 |
+
weights = np.ones_like(reference) if weights is None else np.asarray(weights, float).ravel()
|
| 134 |
+
weights = weights / weights.sum()
|
| 135 |
+
ref_centered = reference - np.sum(weights * reference)
|
| 136 |
+
model_centered = model - np.sum(weights * model)
|
| 137 |
+
denominator = np.sqrt(np.sum(weights * ref_centered ** 2) * np.sum(weights * model_centered ** 2))
|
| 138 |
+
return float(np.sum(weights * ref_centered * model_centered) / denominator) if denominator > 0 else 0.0
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def taylor_s_score(reference: np.ndarray, model: np.ndarray, weights: np.ndarray | None = None) -> dict[str, float]:
|
| 142 |
+
"""Paper Eq. 5: (1+R)^4 / (4*(SDR + 1/SDR)^2)."""
|
| 143 |
+
reference, model = np.asarray(reference, float).ravel(), np.asarray(model, float).ravel()
|
| 144 |
+
weights = np.ones_like(reference) if weights is None else np.asarray(weights, float).ravel()
|
| 145 |
+
weights = weights / weights.sum()
|
| 146 |
+
correlation = pattern_correlation(reference, model, weights)
|
| 147 |
+
ref_std = np.sqrt(np.sum(weights * (reference - np.sum(weights * reference)) ** 2))
|
| 148 |
+
model_std = np.sqrt(np.sum(weights * (model - np.sum(weights * model)) ** 2))
|
| 149 |
+
ratio = model_std / max(ref_std, 1e-12)
|
| 150 |
+
score = (1.0 + correlation) ** 4 / (4.0 * (ratio + 1.0 / max(ratio, 1e-12)) ** 2)
|
| 151 |
+
return {"s_score": float(score), "pattern_correlation": correlation, "standard_deviation_ratio": float(ratio)}
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
class PrecipitationConstraintGP:
|
| 155 |
+
"""RBF plus white-noise GP for F1-to-delta-precipitation constraints."""
|
| 156 |
+
|
| 157 |
+
def __init__(self, random_state: int = 42, restarts: int = 2):
|
| 158 |
+
kernel = ConstantKernel(1.0, (1e-3, 1e3)) * RBF(0.15, (1e-2, 10.0)) + WhiteKernel(0.01, (1e-6, 1.0))
|
| 159 |
+
self.model = GaussianProcessRegressor(kernel=kernel, normalize_y=True,
|
| 160 |
+
n_restarts_optimizer=int(restarts), random_state=random_state)
|
| 161 |
+
|
| 162 |
+
def fit(self, f1_scores: np.ndarray, delta_precipitation: np.ndarray) -> "PrecipitationConstraintGP":
|
| 163 |
+
self.x_train = np.asarray(f1_scores, float).reshape(-1, 1)
|
| 164 |
+
self.y_train = np.asarray(delta_precipitation, float)
|
| 165 |
+
self.model.fit(self.x_train, self.y_train)
|
| 166 |
+
return self
|
| 167 |
+
|
| 168 |
+
def predict(self, f1_scores: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 169 |
+
mean, std = self.model.predict(np.asarray(f1_scores, float).reshape(-1, 1), return_std=True)
|
| 170 |
+
return mean, mean - 1.96 * std, mean + 1.96 * std
|
| 171 |
+
|
| 172 |
+
def state_dict(self) -> dict:
|
| 173 |
+
return {"x_train": self.x_train.astype(np.float64), "y_train": self.y_train.astype(np.float64),
|
| 174 |
+
"kernel_theta": self.model.kernel_.theta.astype(np.float64)}
|
| 175 |
+
|
| 176 |
+
@classmethod
|
| 177 |
+
def from_state_dict(cls, state: dict, random_state: int = 42) -> "PrecipitationConstraintGP":
|
| 178 |
+
instance = cls(random_state=random_state, restarts=0)
|
| 179 |
+
instance.model.kernel.theta = np.asarray(state["kernel_theta"])
|
| 180 |
+
instance.model.optimizer = None
|
| 181 |
+
instance.model.fit(np.asarray(state["x_train"]), np.asarray(state["y_train"]))
|
| 182 |
+
return instance
|
scripts/fake_data.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Generate seasonal directed VAR networks and precipitation fields for CME."""
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import yaml
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def stable_network(rng, nodes, season_index):
|
| 14 |
+
coefficients = np.zeros((nodes, nodes, 10), dtype=np.float32)
|
| 15 |
+
coefficients[np.arange(nodes), np.arange(nodes), 0] = 0.48 + 0.04 * season_index
|
| 16 |
+
for source in range(nodes):
|
| 17 |
+
target = (source + 3 + season_index) % nodes
|
| 18 |
+
lag = (source + 2 * season_index) % 4
|
| 19 |
+
coefficients[source, target, lag] = (0.13 + 0.03 * (source % 3)) * (-1 if source % 5 == 0 else 1)
|
| 20 |
+
if source % 4 == 0:
|
| 21 |
+
coefficients[source, (source + 11) % nodes, (lag + 1) % 6] = -0.11
|
| 22 |
+
return coefficients
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def simulate(rng, coefficients, samples, time_steps):
|
| 26 |
+
nodes, max_lag = coefficients.shape[0], coefficients.shape[2]
|
| 27 |
+
output = np.zeros((samples, time_steps, nodes), dtype=np.float32)
|
| 28 |
+
for sample in range(samples):
|
| 29 |
+
series = rng.normal(0, 0.35, (time_steps + max_lag, nodes)).astype(np.float32)
|
| 30 |
+
for time in range(max_lag, time_steps + max_lag):
|
| 31 |
+
forcing = np.zeros(nodes, dtype=np.float32)
|
| 32 |
+
for lag in range(1, max_lag + 1):
|
| 33 |
+
forcing += series[time - lag] @ coefficients[:, :, lag - 1]
|
| 34 |
+
series[time] = forcing + rng.normal(0, 0.38, nodes)
|
| 35 |
+
output[sample] = series[max_lag:]
|
| 36 |
+
return output
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def main():
|
| 40 |
+
parser = argparse.ArgumentParser()
|
| 41 |
+
parser.add_argument("--force", action="store_true")
|
| 42 |
+
args = parser.parse_args()
|
| 43 |
+
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
|
| 44 |
+
data, seed = config["data"], int(config["seed"])
|
| 45 |
+
path = ROOT / config["paths"]["dataset"]
|
| 46 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 47 |
+
if path.exists() and not args.force:
|
| 48 |
+
print(f"exists={path.relative_to(ROOT)} use --force to regenerate")
|
| 49 |
+
return
|
| 50 |
+
rng = np.random.default_rng(seed)
|
| 51 |
+
seasons, models = len(data["seasons"]), int(data["model_count"])
|
| 52 |
+
nodes, samples, steps = int(data["nodes"]), int(data["samples"]), int(data["time_steps"])
|
| 53 |
+
reference_coefficients = np.stack([stable_network(rng, nodes, season) for season in range(seasons)])
|
| 54 |
+
reference_series = np.stack([simulate(rng, reference_coefficients[s], samples, steps) for s in range(seasons)])
|
| 55 |
+
model_series = np.empty((models, seasons, samples, steps, nodes), dtype=np.float32)
|
| 56 |
+
model_coefficients = np.empty((models, seasons, nodes, nodes, 10), dtype=np.float32)
|
| 57 |
+
quality = np.linspace(0.92, 0.35, models).astype(np.float32)
|
| 58 |
+
for model in range(models):
|
| 59 |
+
for season in range(seasons):
|
| 60 |
+
coefficients = reference_coefficients[season].copy()
|
| 61 |
+
cross = ~np.eye(nodes, dtype=bool)
|
| 62 |
+
coefficients[cross] *= quality[model]
|
| 63 |
+
mutation_count = 3 + 3 * model
|
| 64 |
+
for _ in range(mutation_count):
|
| 65 |
+
source, target = rng.integers(0, nodes, 2)
|
| 66 |
+
if source != target:
|
| 67 |
+
coefficients[source, target, rng.integers(0, 7)] = rng.choice([-1, 1]) * rng.uniform(0.08, 0.16)
|
| 68 |
+
model_coefficients[model, season] = coefficients
|
| 69 |
+
model_series[model, season] = simulate(rng, coefficients, samples, steps)
|
| 70 |
+
lat_count, lon_count = map(int, data["grid_shape"])
|
| 71 |
+
latitude = np.linspace(-90.0, 90.0, lat_count, dtype=np.float32)
|
| 72 |
+
longitude = np.linspace(0, 360, lon_count, endpoint=False, dtype=np.float32)
|
| 73 |
+
lat2d, lon2d = np.meshgrid(latitude, longitude, indexing="ij")
|
| 74 |
+
reference_precip = (3.0 + 2.1 * np.cos(np.deg2rad(lat2d)) ** 2 +
|
| 75 |
+
0.45 * np.sin(np.deg2rad(2 * lon2d))).astype(np.float32)
|
| 76 |
+
precip_fields, delta = [], []
|
| 77 |
+
for model, q in enumerate(quality):
|
| 78 |
+
bias = (1 - q) * (0.8 * np.sin(np.deg2rad(lat2d)) + 0.35 * np.cos(np.deg2rad(lon2d)))
|
| 79 |
+
precip_fields.append(reference_precip * (0.88 + 0.12 * q) + bias + rng.normal(0, 0.06 + 0.08 * (1 - q), reference_precip.shape))
|
| 80 |
+
delta.append(0.18 + 0.95 * (q - 0.62) ** 2 + rng.normal(0, 0.025))
|
| 81 |
+
np.savez_compressed(path, format_version=np.asarray(data["format_version"]), seasons=np.asarray(data["seasons"]),
|
| 82 |
+
reference_series=reference_series, model_series=model_series,
|
| 83 |
+
reference_coefficients=reference_coefficients, model_coefficients=model_coefficients,
|
| 84 |
+
reference_precipitation=reference_precip, model_precipitation=np.asarray(precip_fields, np.float32),
|
| 85 |
+
delta_precipitation=np.asarray(delta, np.float32), latitude_degrees=latitude,
|
| 86 |
+
longitude_degrees=longitude, model_quality=quality, time_step_days=np.asarray(data["time_step_days"]))
|
| 87 |
+
print(f"generated={path.relative_to(ROOT)} reference={reference_series.shape} models={model_series.shape}")
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
if __name__ == "__main__":
|
| 91 |
+
main()
|
scripts/inference.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Reload the CME checkpoint and export complete network and projection outputs."""
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import sys
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import yaml
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 13 |
+
sys.path.insert(0, str(ROOT))
|
| 14 |
+
from model.causalmodelevaluation import CausalNetwork, FORMAT_VERSION, PrecipitationConstraintGP
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def stack_networks(states, field):
|
| 18 |
+
return np.stack([[getattr(CausalNetwork.from_state_dict(state), field).numpy() for state in group] for group in states])
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def main():
|
| 22 |
+
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
|
| 23 |
+
checkpoint = torch.load(ROOT / config["paths"]["checkpoint"], map_location="cpu", weights_only=False)
|
| 24 |
+
if checkpoint["version"] != FORMAT_VERSION:
|
| 25 |
+
raise ValueError("unsupported checkpoint version")
|
| 26 |
+
reference_states = [checkpoint["reference_networks"]]
|
| 27 |
+
model_states = checkpoint["model_networks"]
|
| 28 |
+
f1 = checkpoint["model_f1"].numpy()
|
| 29 |
+
gp = PrecipitationConstraintGP.from_state_dict(checkpoint["gp"], int(config["seed"]))
|
| 30 |
+
query = np.sort(np.unique(np.append(f1, float(config["evaluation"]["reference_f1_for_projection"]))))
|
| 31 |
+
mean, lower, upper = gp.predict(query)
|
| 32 |
+
source = np.load(ROOT / config["paths"]["dataset"])
|
| 33 |
+
output = ROOT / config["paths"]["inference"]
|
| 34 |
+
output.parent.mkdir(parents=True, exist_ok=True)
|
| 35 |
+
np.savez_compressed(
|
| 36 |
+
output, edges=stack_networks(model_states, "edges"), pvalues=stack_networks(model_states, "pvalues"),
|
| 37 |
+
mci=stack_networks(model_states, "mci"), reference_edges=stack_networks(reference_states, "edges")[0],
|
| 38 |
+
reference_pvalues=stack_networks(reference_states, "pvalues")[0],
|
| 39 |
+
reference_mci=stack_networks(reference_states, "mci")[0], model_f1=f1,
|
| 40 |
+
reference_precipitation=source["reference_precipitation"], model_precipitation=source["model_precipitation"],
|
| 41 |
+
delta_precipitation=source["delta_precipitation"], latitude_degrees=source["latitude_degrees"],
|
| 42 |
+
longitude_degrees=source["longitude_degrees"], gp_query_f1=query, gp_mean_delta_precipitation=mean,
|
| 43 |
+
gp_lower_95=lower, gp_upper_95=upper, seasons=source["seasons"],
|
| 44 |
+
network_metadata=np.asarray(json.dumps(checkpoint["metadata"])),
|
| 45 |
+
projection_metadata=np.asarray(json.dumps({"kernel": str(gp.model.kernel_), "confidence": 0.95,
|
| 46 |
+
"input": "CME asymmetric F1", "output": "delta precipitation"})),
|
| 47 |
+
format_version=np.asarray(FORMAT_VERSION))
|
| 48 |
+
print(f"inference={output.relative_to(ROOT)} edges={stack_networks(model_states, 'edges').shape} reference={stack_networks(reference_states, 'edges')[0].shape}")
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
if __name__ == "__main__":
|
| 52 |
+
main()
|
scripts/result.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Compute CME, precipitation metrics, GP intervals, and the task figure."""
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import sys
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import matplotlib
|
| 8 |
+
matplotlib.use("Agg")
|
| 9 |
+
import matplotlib.pyplot as plt
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
+
import yaml
|
| 13 |
+
from scipy import stats
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 17 |
+
sys.path.insert(0, str(ROOT))
|
| 18 |
+
from model.causalmodelevaluation import CausalNetwork, asymmetric_f1, taylor_s_score
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def network(edges, pvalues, mci):
|
| 22 |
+
return CausalNetwork(torch.from_numpy(edges), torch.from_numpy(pvalues), torch.from_numpy(mci))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def main():
|
| 26 |
+
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
|
| 27 |
+
data = np.load(ROOT / config["paths"]["inference"])
|
| 28 |
+
tolerance = int(config["evaluation"]["lag_tolerance"])
|
| 29 |
+
by_model, model_s = [], []
|
| 30 |
+
weights = np.cos(np.deg2rad(data["latitude_degrees"]))[:, None] * np.ones((1, len(data["longitude_degrees"])))
|
| 31 |
+
for model_index in range(data["edges"].shape[0]):
|
| 32 |
+
seasonal = []
|
| 33 |
+
for season in range(data["edges"].shape[1]):
|
| 34 |
+
seasonal.append(asymmetric_f1(
|
| 35 |
+
network(data["reference_edges"][season], data["reference_pvalues"][season], data["reference_mci"][season]),
|
| 36 |
+
network(data["edges"][model_index, season], data["pvalues"][model_index, season], data["mci"][model_index, season]), tolerance))
|
| 37 |
+
taylor = taylor_s_score(data["reference_precipitation"], data["model_precipitation"][model_index], weights)
|
| 38 |
+
model_s.append(taylor["s_score"])
|
| 39 |
+
edge_count = int(data["edges"][model_index].sum())
|
| 40 |
+
possible = int(np.prod(data["edges"][model_index].shape))
|
| 41 |
+
by_model.append({"model": model_index, "cme_f1": float(np.mean([x["f1"] for x in seasonal])),
|
| 42 |
+
"seasonal_cme": seasonal, "edge_count": edge_count, "edge_density": edge_count / possible,
|
| 43 |
+
"taylor": taylor, "delta_precipitation": float(data["delta_precipitation"][model_index])})
|
| 44 |
+
f1 = np.asarray([item["cme_f1"] for item in by_model])
|
| 45 |
+
s_scores = np.asarray(model_s)
|
| 46 |
+
delta = data["delta_precipitation"]
|
| 47 |
+
f1_delta = stats.pearsonr(f1, delta)
|
| 48 |
+
f1_s = stats.pearsonr(f1, s_scores)
|
| 49 |
+
report = {
|
| 50 |
+
"models": by_model,
|
| 51 |
+
"reference": {"edge_count": int(data["reference_edges"].sum()),
|
| 52 |
+
"edge_density": float(data["reference_edges"].mean())},
|
| 53 |
+
"correlations": {"f1_vs_delta_precipitation": {"r": float(f1_delta.statistic), "pvalue": float(f1_delta.pvalue)},
|
| 54 |
+
"f1_vs_taylor_s": {"r": float(f1_s.statistic), "pvalue": float(f1_s.pvalue)}},
|
| 55 |
+
"gp_projection": {"query_f1": data["gp_query_f1"].tolist(), "mean": data["gp_mean_delta_precipitation"].tolist(),
|
| 56 |
+
"lower_95": data["gp_lower_95"].tolist(), "upper_95": data["gp_upper_95"].tolist(),
|
| 57 |
+
"metadata": json.loads(str(data["projection_metadata"]))},
|
| 58 |
+
"protocol": {"direction_sign_required": True, "lag_tolerance_steps": tolerance,
|
| 59 |
+
"lag_tolerance_days": tolerance * int(config["data"]["time_step_days"]),
|
| 60 |
+
"paper_alpha": config["paper_model"]["alpha"], "engineering_alpha": config["model"]["alpha"]}
|
| 61 |
+
}
|
| 62 |
+
output = ROOT / config["paths"]["evaluation_dir"]
|
| 63 |
+
output.mkdir(parents=True, exist_ok=True)
|
| 64 |
+
(output / "metrics.json").write_text(json.dumps(report, indent=2) + "\n")
|
| 65 |
+
order = np.argsort(data["gp_query_f1"])
|
| 66 |
+
figure, axes = plt.subplots(1, 2, figsize=(11, 4.3))
|
| 67 |
+
axes[0].scatter(f1, s_scores, c=np.arange(len(f1)), cmap="viridis", s=55)
|
| 68 |
+
axes[0].set(xlabel="CME asymmetric F1", ylabel="Precipitation Taylor S-score", title=f"Network and precipitation skill (R={f1_s.statistic:.2f})")
|
| 69 |
+
axes[1].scatter(f1, delta, color="#9b3a2e", label="Synthetic models")
|
| 70 |
+
axes[1].plot(data["gp_query_f1"][order], data["gp_mean_delta_precipitation"][order], color="#173f5f", label="RBF + white GP")
|
| 71 |
+
axes[1].fill_between(data["gp_query_f1"][order], data["gp_lower_95"][order], data["gp_upper_95"][order], color="#4f8fba", alpha=0.25, label="95% interval")
|
| 72 |
+
axes[1].set(xlabel="CME asymmetric F1", ylabel="Delta precipitation", title="Constrained precipitation relationship")
|
| 73 |
+
axes[1].legend(fontsize=8)
|
| 74 |
+
figure.tight_layout()
|
| 75 |
+
figure.savefig(output / "cme_task.png", dpi=160)
|
| 76 |
+
plt.close(figure)
|
| 77 |
+
if not np.isfinite([f1_delta.statistic, f1_s.statistic, *data["gp_mean_delta_precipitation"]]).all():
|
| 78 |
+
raise FloatingPointError("evaluation contains non-finite values")
|
| 79 |
+
print(f"evaluation={output.relative_to(ROOT)} models={len(by_model)} figure=cme_task.png")
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
if __name__ == "__main__":
|
| 83 |
+
main()
|
scripts/train.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Fit statistical CME networks and the precipitation-constraint GP."""
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import sys
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import yaml
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 13 |
+
sys.path.insert(0, str(ROOT))
|
| 14 |
+
from model.causalmodelevaluation import (FORMAT_VERSION, LaggedPartialCorrelationCME,
|
| 15 |
+
PrecipitationConstraintGP, asymmetric_f1)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def main():
|
| 19 |
+
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
|
| 20 |
+
np.random.seed(int(config["seed"]))
|
| 21 |
+
data = np.load(ROOT / config["paths"]["dataset"])
|
| 22 |
+
if str(data["format_version"]) != config["data"]["format_version"]:
|
| 23 |
+
raise ValueError("incompatible synthetic data format")
|
| 24 |
+
estimator = LaggedPartialCorrelationCME(**config["model"])
|
| 25 |
+
references = [estimator.fit(series) for series in data["reference_series"]]
|
| 26 |
+
model_networks, scores = [], []
|
| 27 |
+
for model_series in data["model_series"]:
|
| 28 |
+
networks = [estimator.fit(series) for series in model_series]
|
| 29 |
+
model_networks.append(networks)
|
| 30 |
+
scores.append(float(np.mean([asymmetric_f1(ref, net, int(config["evaluation"]["lag_tolerance"]))["f1"]
|
| 31 |
+
for ref, net in zip(references, networks)])))
|
| 32 |
+
gp = PrecipitationConstraintGP(int(config["seed"])).fit(np.asarray(scores), data["delta_precipitation"])
|
| 33 |
+
checkpoint = {
|
| 34 |
+
"version": FORMAT_VERSION, "config": config, "network_config": estimator.config(),
|
| 35 |
+
"reference_networks": [network.state_dict() for network in references],
|
| 36 |
+
"model_networks": [[network.state_dict() for network in networks] for networks in model_networks],
|
| 37 |
+
"model_f1": torch.tensor(scores), "gp": gp.state_dict(),
|
| 38 |
+
"metadata": {"method": "lagged target-history conditional regression ParCorr approximation",
|
| 39 |
+
"gradient_training": False, "tensor_layout": "source,target,lag", "time_step_days": 3}
|
| 40 |
+
}
|
| 41 |
+
checkpoint_path = ROOT / config["paths"]["checkpoint"]
|
| 42 |
+
metrics_path = ROOT / config["paths"]["training_metrics"]
|
| 43 |
+
checkpoint_path.parent.mkdir(parents=True, exist_ok=True)
|
| 44 |
+
metrics_path.parent.mkdir(parents=True, exist_ok=True)
|
| 45 |
+
torch.save(checkpoint, checkpoint_path)
|
| 46 |
+
metrics_path.write_text(json.dumps({"model_f1": scores, "gp_kernel": str(gp.model.kernel_),
|
| 47 |
+
"paper_alpha": config["paper_model"]["alpha"],
|
| 48 |
+
"engineering_alpha": config["model"]["alpha"]}, indent=2) + "\n")
|
| 49 |
+
print(f"checkpoint={checkpoint_path.relative_to(ROOT)} networks={len(references) * (1 + len(model_networks))} gp_samples={len(scores)}")
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
if __name__ == "__main__":
|
| 53 |
+
main()
|
weight/.gitkeep
ADDED
|
File without changes
|