Upload models
Browse files- LICENSE +21 -0
- README.md +59 -0
- models.py +196 -0
- nn_state.pt +3 -0
- qobsTTFFFFFTF30FFTFTF30TTFTFTFFF80FFTFTTF2699FFFF_X01_no_qp_no_adv_surf_F_Tin_qin_disteq_O_Trad_rest_Tadv_qadv_qout_qsed_RESCALED_7epochs_no_drop_REAL_NN_layers5in61out148_BN_F_te70.nc +0 -0
- requirements.txt +7 -0
- test_python_net.py +40 -0
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2020, 2023 Janni Yuval and Institute of Computing for Climate Science
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
README.md
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Convection Parameterization in CAM
|
2 |
+
|
3 |
+
Note that this repository and code is still work in progress and undergoing significant development.
|
4 |
+
Once a useable release is produced it will be tagged.
|
5 |
+
|
6 |
+
## Description
|
7 |
+
This repository contains code as part of an effort to deploy machine learning (ML) models of geophysical parameterisations into the [Community Earth System Model (CESM)](https://www.cesm.ucar.edu/).
|
8 |
+
This work is part of the [M<sup>2</sup>LInES](https://m2lines.github.io/) project aiming to improve performance of climate models using ML models for subgrid parameterizations.
|
9 |
+
|
10 |
+
A Neural Net providing a subgrid parameterization of atmospheric convection in a [single column model](https://www.arm.gov/publications/proceedings/conf04/extended_abs/randall_da.pdf) has been developed and successfully deployed as part of an atmospheric simulation.
|
11 |
+
The work is described in a [GRL paper](https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2020GL091363) with [accompanying code available](https://github.com/yaniyuval/Neural_nework_parameterization/tree/v.1.0.3). The repository contains the neural net and its implementation into a simple system for atmospheric modelling, [SAM](http://rossby.msrc.sunysb.edu/~marat/SAM.html).
|
12 |
+
|
13 |
+
The aims of this repository are to:
|
14 |
+
1. develop a standalone fortran module based on this neural net that can be used elsewhere,
|
15 |
+
2. deploy the module in another atmospheric model, and
|
16 |
+
3. evaluate its performance.
|
17 |
+
|
18 |
+
We may also perform an investigation into interfacing the pytorch implementation of the Neural Net using the [pytorch-fortran bridging code](https://github.com/Cambridge-ICCS/fortran-pytorch-lib) developed at the [Institute of Computing for Climate Science](https://cambridge-iccs.github.io/).
|
19 |
+
|
20 |
+
The model will first be deployed into the [Single Column Atmospheric Model (SCAM)](https://www.cesm.ucar.edu/models/simple/scam) - a single column version of the CESM.
|
21 |
+
We plan to evaluate performance using SCAM in the gateIII configuration for tropical convection in a similar manner described by the [SCAM6 pulication in JAMES](https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2018MS001578).
|
22 |
+
This will compare model performance to data from an intense observation period (IOP) described in an [AMS publication](https://journals.ametsoc.org/view/journals/atsc/36/1/1520-0469_1979_036_0053_saposs_2_0_co_2.xml).
|
23 |
+
|
24 |
+
Long term developments of this project will seek to re-deploy more complex ML parameterizations into mode complex atmospheric models such as the [Community Atmospheric Model (CAM)](https://www.cesm.ucar.edu/models/cam) part of the CESM.
|
25 |
+
|
26 |
+
|
27 |
+
## Repository structure
|
28 |
+
|
29 |
+
```
|
30 |
+
├── NN_module
|
31 |
+
│ └── ...
|
32 |
+
└── torch_nets
|
33 |
+
└── ...
|
34 |
+
```
|
35 |
+
|
36 |
+
|
37 |
+
|
38 |
+
### Contents
|
39 |
+
|
40 |
+
### `NN_module/`
|
41 |
+
This folder contains the fortran neural net extracted from the [code referenced above](https://github.com/yaniyuval/Neural_nework_parameterization/tree/v.1.0.3), along with any dependencies, that may be compiled as a standalone fortran module.
|
42 |
+
|
43 |
+
Currently there is code that can be built on CSD3 using the included shell script.
|
44 |
+
|
45 |
+
This now needs cleaning up, testing, and a proper makefile creating (see open issues #9 and #10).
|
46 |
+
|
47 |
+
### ``torch_nets/``
|
48 |
+
The directory contains the PyTorch versions of the neural networks we are interested in.
|
49 |
+
|
50 |
+
|
51 |
+
## Contributing
|
52 |
+
|
53 |
+
This repository is currently private as it is new and work in progress.
|
54 |
+
Open tickets can be viewed at ['Issues'](https://github.com/m2lines/convection-parameterization-in-CAM/issues).
|
55 |
+
|
56 |
+
To contribute find a relevant issue or open a new one and assign yourself to work on it.
|
57 |
+
Then create a branch in which to add your contribution and open a pull request.
|
58 |
+
Once ready assign a reviewer and request a code review.
|
59 |
+
Merging should _only_ be performed once a code review has taken place.
|
models.py
ADDED
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Neural network architectures."""
|
2 |
+
|
3 |
+
from typing import Optional
|
4 |
+
|
5 |
+
import netCDF4 as nc # type: ignore
|
6 |
+
import torch
|
7 |
+
from torch import nn, Tensor
|
8 |
+
|
9 |
+
|
10 |
+
class ANN(nn.Sequential):
|
11 |
+
"""Model used in the paper.
|
12 |
+
|
13 |
+
Paper: https://doi.org/10.1029/2020GL091363
|
14 |
+
|
15 |
+
|
16 |
+
Parameters
|
17 |
+
----------
|
18 |
+
n_in : int
|
19 |
+
Number of input features.
|
20 |
+
n_out : int
|
21 |
+
Number of output features.
|
22 |
+
n_layers : int
|
23 |
+
Number of layers.
|
24 |
+
neurons : int
|
25 |
+
The number of neurons in the hidden layers.
|
26 |
+
dropout : float
|
27 |
+
The dropout probability to apply in the hidden layers.
|
28 |
+
device : str
|
29 |
+
The device to put the model on.
|
30 |
+
features_mean : ndarray
|
31 |
+
The mean of the input features.
|
32 |
+
features_std : ndarray
|
33 |
+
The standard deviation of the input features.
|
34 |
+
outputs_mean : ndarray
|
35 |
+
The mean of the output features.
|
36 |
+
outputs_std : ndarray
|
37 |
+
The standard deviation of the output features.
|
38 |
+
output_groups : ndarray
|
39 |
+
The number of output features in each group of the ouput.
|
40 |
+
|
41 |
+
Notes
|
42 |
+
-----
|
43 |
+
If you are doing inference, always remember to put the model in eval model,
|
44 |
+
by using ``model.eval()``, so the dropout layers are turned off.
|
45 |
+
|
46 |
+
"""
|
47 |
+
|
48 |
+
def __init__( # pylint: disable=too-many-arguments,too-many-locals
|
49 |
+
self,
|
50 |
+
n_in: int = 61,
|
51 |
+
n_out: int = 148,
|
52 |
+
n_layers: int = 5,
|
53 |
+
neurons: int = 128,
|
54 |
+
dropout: float = 0.0,
|
55 |
+
device: str = "cpu",
|
56 |
+
features_mean: Optional[Tensor] = None,
|
57 |
+
features_std: Optional[Tensor] = None,
|
58 |
+
outputs_mean: Optional[Tensor] = None,
|
59 |
+
outputs_std: Optional[Tensor] = None,
|
60 |
+
output_groups: Optional[list] = None,
|
61 |
+
):
|
62 |
+
"""Initialize the ANN model."""
|
63 |
+
dims = [n_in] + [neurons] * (n_layers - 1) + [n_out]
|
64 |
+
layers = []
|
65 |
+
|
66 |
+
for i in range(n_layers):
|
67 |
+
layers.append(nn.Linear(dims[i], dims[i + 1]))
|
68 |
+
if i < n_layers - 1:
|
69 |
+
layers.append(nn.ReLU()) # type: ignore
|
70 |
+
layers.append(nn.Dropout(dropout)) # type: ignore
|
71 |
+
|
72 |
+
super().__init__(*layers)
|
73 |
+
|
74 |
+
fmean = fstd = omean = ostd = None
|
75 |
+
|
76 |
+
if features_mean is not None:
|
77 |
+
assert features_std is not None
|
78 |
+
assert len(features_mean) == len(features_std)
|
79 |
+
fmean = torch.tensor(features_mean)
|
80 |
+
fstd = torch.tensor(features_std)
|
81 |
+
|
82 |
+
if outputs_mean is not None:
|
83 |
+
assert outputs_std is not None
|
84 |
+
assert len(outputs_mean) == len(outputs_std)
|
85 |
+
if output_groups is None:
|
86 |
+
omean = torch.tensor(outputs_mean)
|
87 |
+
ostd = torch.tensor(outputs_std)
|
88 |
+
else:
|
89 |
+
assert len(output_groups) == len(outputs_mean)
|
90 |
+
omean = torch.tensor(
|
91 |
+
[x for x, g in zip(outputs_mean, output_groups) for _ in range(g)]
|
92 |
+
)
|
93 |
+
ostd = torch.tensor(
|
94 |
+
[x for x, g in zip(outputs_std, output_groups) for _ in range(g)]
|
95 |
+
)
|
96 |
+
|
97 |
+
self.register_buffer("features_mean", fmean)
|
98 |
+
self.register_buffer("features_std", fstd)
|
99 |
+
self.register_buffer("outputs_mean", omean)
|
100 |
+
self.register_buffer("outputs_std", ostd)
|
101 |
+
|
102 |
+
self.to(torch.device(device))
|
103 |
+
|
104 |
+
def forward(self, input: Tensor): # pylint: disable=redefined-builtin
|
105 |
+
"""Pass the input through the model.
|
106 |
+
|
107 |
+
Override the forward method of nn.Sequential to add normalization
|
108 |
+
to the input and denormalization to the output.
|
109 |
+
|
110 |
+
Parameters
|
111 |
+
----------
|
112 |
+
input : Tensor
|
113 |
+
A mini-batch of inputs.
|
114 |
+
|
115 |
+
Returns
|
116 |
+
-------
|
117 |
+
Tensor
|
118 |
+
The model output.
|
119 |
+
|
120 |
+
"""
|
121 |
+
if self.features_mean is not None:
|
122 |
+
input = (input - self.features_mean) / self.features_std
|
123 |
+
|
124 |
+
# pass the input through the layers using nn.Sequential.forward
|
125 |
+
output = super().forward(input)
|
126 |
+
|
127 |
+
if self.outputs_mean is not None:
|
128 |
+
output = output * self.outputs_std + self.outputs_mean
|
129 |
+
|
130 |
+
return output
|
131 |
+
|
132 |
+
def load(self, path: str) -> "ANN":
|
133 |
+
"""Load the model from a checkpoint.
|
134 |
+
|
135 |
+
Parameters
|
136 |
+
----------
|
137 |
+
path : str
|
138 |
+
The path to the checkpoint.
|
139 |
+
|
140 |
+
"""
|
141 |
+
state = torch.load(path)
|
142 |
+
for key in ["features_mean", "features_std", "outputs_mean", "outputs_std"]:
|
143 |
+
if key in state and getattr(self, key) is None:
|
144 |
+
setattr(self, key, state[key])
|
145 |
+
self.load_state_dict(state)
|
146 |
+
return self
|
147 |
+
|
148 |
+
def save(self, path: str):
|
149 |
+
"""Save the model to a checkpoint.
|
150 |
+
|
151 |
+
Parameters
|
152 |
+
----------
|
153 |
+
path : str
|
154 |
+
The path to save the checkpoint to.
|
155 |
+
|
156 |
+
"""
|
157 |
+
torch.save(self.state_dict(), path)
|
158 |
+
|
159 |
+
|
160 |
+
def load_from_netcdf_params(nc_file: str, dtype: str = "float32") -> ANN:
|
161 |
+
"""Load the model with weights and biases from the netcdf file.
|
162 |
+
|
163 |
+
Parameters
|
164 |
+
----------
|
165 |
+
nc_file : str
|
166 |
+
The netcdf file containing the parameters.
|
167 |
+
dtype : str
|
168 |
+
The data type to cast the parameters to.
|
169 |
+
|
170 |
+
"""
|
171 |
+
data_set = nc.Dataset(nc_file) # pylint: disable=no-member
|
172 |
+
|
173 |
+
model = ANN(
|
174 |
+
features_mean=data_set["fscale_mean"][:].astype(dtype),
|
175 |
+
features_std=data_set["fscale_stnd"][:].astype(dtype),
|
176 |
+
outputs_mean=data_set["oscale_mean"][:].astype(dtype),
|
177 |
+
outputs_std=data_set["oscale_stnd"][:].astype(dtype),
|
178 |
+
output_groups=[30, 29, 29, 30, 30],
|
179 |
+
)
|
180 |
+
|
181 |
+
for i, layer in enumerate(l for l in model.modules() if isinstance(l, nn.Linear)):
|
182 |
+
layer.weight.data = torch.tensor(data_set[f"w{i+1}"][:].astype(dtype))
|
183 |
+
layer.bias.data = torch.tensor(data_set[f"b{i+1}"][:].astype(dtype))
|
184 |
+
|
185 |
+
return model
|
186 |
+
|
187 |
+
|
188 |
+
if __name__ == "__main__":
|
189 |
+
# Load the model from the netcdf file and save it to a checkpoint.
|
190 |
+
net = load_from_netcdf_params(
|
191 |
+
"qobsTTFFFFFTF30FFTFTF30TTFTFTFFF80FFTFTTF2699FFFF_X01_no_qp_no_adv_"
|
192 |
+
"surf_F_Tin_qin_disteq_O_Trad_rest_Tadv_qadv_qout_qsed_RESCALED_7epochs"
|
193 |
+
"_no_drop_REAL_NN_layers5in61out148_BN_F_te70.nc"
|
194 |
+
)
|
195 |
+
net.save("nn_state.pt")
|
196 |
+
print("Model saved to nn_state.pt")
|
nn_state.pt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:552b224668a40820e9afd0e4f83053dbc9f4ee7c75814ad32ed2805041afb1e1
|
3 |
+
size 312574
|
qobsTTFFFFFTF30FFTFTF30TTFTFTFFF80FFTFTTF2699FFFF_X01_no_qp_no_adv_surf_F_Tin_qin_disteq_O_Trad_rest_Tadv_qadv_qout_qsed_RESCALED_7epochs_no_drop_REAL_NN_layers5in61out148_BN_F_te70.nc
ADDED
Binary file (308 kB). View file
|
|
requirements.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
torch
|
2 |
+
black
|
3 |
+
pytest
|
4 |
+
pydocstyle
|
5 |
+
pylint
|
6 |
+
mypy
|
7 |
+
netcdf4
|
test_python_net.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""A smoke test for the ANN model.
|
2 |
+
|
3 |
+
This test checks that the model can be loaded from a weights file in both pt format and
|
4 |
+
netcdf format and that they produce the expected output when given an input of all ones.
|
5 |
+
This ensures that it is equivalent to the Fortran NN model.
|
6 |
+
"""
|
7 |
+
|
8 |
+
import os
|
9 |
+
from pathlib import Path
|
10 |
+
import torch
|
11 |
+
import numpy as np
|
12 |
+
from models import ANN, load_from_netcdf_params
|
13 |
+
|
14 |
+
|
15 |
+
os.chdir(Path(__file__).parent)
|
16 |
+
|
17 |
+
expected = np.loadtxt("nn_ones.txt").astype(np.float32)
|
18 |
+
# nn_ones.txt is the output of the Fortran NN model given an input of all ones.
|
19 |
+
|
20 |
+
model1 = ANN().load("nn_state.pt") # load from the pytorch weights
|
21 |
+
model2 = load_from_netcdf_params(
|
22 |
+
"qobsTTFFFFFTF30FFTFTF30TTFTFTFFF80FFTFTTF2699FFFF_X01_no_qp_no_adv_"
|
23 |
+
"surf_F_Tin_qin_disteq_O_Trad_rest_Tadv_qadv_qout_qsed_RESCALED_7epochs"
|
24 |
+
"_no_drop_REAL_NN_layers5in61out148_BN_F_te70.nc"
|
25 |
+
) # load from the NetCDF weights of the pretrained Fortran NN model
|
26 |
+
# file created at https://github.com/yaniyuval/Neural_nework_parameterization/blob/f81f5f695297888f0bd1e0e61524590b4566bf03/NN_training/src/ml_train_nn.py#L417 # pylint: disable=line-too-long
|
27 |
+
# (which the naming scheme integrating information about the training setup, see e.g., https://github.com/yaniyuval/Neural_nework_parameterization/blob/f81f5f695297888f0bd1e0e61524590b4566bf03/NN_training/src/ml_train_nn.py#L263-L265) # pylint: disable=line-too-long
|
28 |
+
# This Neural Net can be found at https://github.com/yaniyuval/Neural_nework_parameterization/tree/f81f5f695297888f0bd1e0e61524590b4566bf03/NNs # pylint: disable=line-too-long
|
29 |
+
|
30 |
+
|
31 |
+
x = torch.ones(61)
|
32 |
+
|
33 |
+
actual1 = model1.forward(x).detach().numpy()
|
34 |
+
actual2 = model2.forward(x).detach().numpy()
|
35 |
+
|
36 |
+
assert np.all(actual1 == actual2)
|
37 |
+
assert np.allclose(expected, actual1, atol=3e-8, rtol=2e-6)
|
38 |
+
# Values of atol and rtol are chosen to be the lowest that still pass the test.
|
39 |
+
|
40 |
+
print("Smoke tests passed")
|