zhangrenchao commited on
Commit
bd6dbd0
·
verified ·
1 Parent(s): 281c4f7

Publish GRACE-SEDA 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 binary
2
+ *.npz binary
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/config.yaml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ seed: 42
2
+ data: {path: data/twsa.npz, samples: 20, channels: 9, patch: 32}
3
+ model: {channels: 9, hidden: 16}
4
+ train: {epochs: 2, learning_rate: 0.001, ensemble_members: 5}
5
+ paths: {checkpoint: result/checkpoints/grace_seda.pt, training_metrics: result/training/metrics.json, predictions: result/output/predictions.npz, evaluation: result/evaluation/metrics.json, figure: result/evaluation/comparison.png}
6
+ paper_model: {resolution_degrees: 0.5, patch: [32, 32], channels: 9, encoder_filters: [16, 32, 64], months: 180, optimizer: Adam, batch_size: 512, ensemble_members: 5, paper_license: CC-BY-4.0}
config.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"model_name":"GRACE-SEDA","model_type":"grace_seda","architectures":["GRACESEDA"],"framework":"PyTorch","domain":"terrestrial-water-storage","task":"self-supervised-data-assimilation","implementation":{"entry_point":"model/grace_seda.py","scope":"L2 self-supervised patch reproduction"},"architecture":{"input_channels":9,"patch_shape":[32,32],"resolution_degrees":0.5,"encoder_filters":[16,32,64],"deep_ensemble_members":5,"core":"residual encoder-decoder"},"data":{"sources":["JPL GRACE","WGHM","GLDAS"],"period":["2002-04","2019-12"],"months":180,"global_land":true},"configuration_sources":["conf/config.yaml","model/grace_seda.py","scripts/fake_data.py","scripts/train.py","scripts/inference.py","scripts/result.py"]}
model/grace_seda.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json,torch
2
+ from pathlib import Path
3
+ from torch import nn
4
+ import torch.nn.functional as F
5
+ import yaml
6
+ FEATURES=('GRACE','WGHM','precipitation','ET','surface_runoff','baseflow','snowmelt','latitude','longitude')
7
+ def cfg(r):return yaml.safe_load((Path(r)/'conf/config.yaml').read_text())
8
+ def sample(i):
9
+ y,x=torch.meshgrid(torch.linspace(-1,1,32),torch.linspace(-1,1,32),indexing='ij');w=torch.sin(3*x+.1*i)*torch.cos(2*y);g=F.avg_pool2d(w[None,None],6,1,3)[0,0,:32,:32];g=F.interpolate(g[None,None],size=(32,32),mode='bilinear')[0,0];return torch.stack((g,w,torch.sin(x),torch.cos(y),w*.2,w*.1,w*.05,y,x)).float()
10
+ class Block(nn.Module):
11
+ def __init__(self,c):super().__init__();self.n=nn.Sequential(nn.Conv2d(c,c,3,padding=1),nn.ReLU(),nn.BatchNorm2d(c),nn.Conv2d(c,c,3,padding=1),nn.BatchNorm2d(c))
12
+ def forward(self,x):return F.relu(x+self.n(x))
13
+ class GRACESEDA(nn.Module):
14
+ def __init__(self,channels=9,hidden=16):super().__init__();self.e1=nn.Conv2d(channels,hidden,3,2,1);self.e2=nn.Conv2d(hidden,hidden*2,3,2,1);self.e3=nn.Conv2d(hidden*2,hidden*4,3,2,1);self.b=Block(hidden*4);self.d=nn.Sequential(nn.Conv2d(hidden*4,hidden*2,3,padding=1),nn.ReLU(),nn.Conv2d(hidden*2,hidden,3,padding=1),nn.ReLU(),nn.Conv2d(hidden,1,1));self.model_config={'channels':channels,'hidden':hidden}
15
+ def forward(self,x):z=F.relu(self.e1(x));z=F.relu(self.e2(z));z=self.b(F.relu(self.e3(z)));return F.interpolate(self.d(z),size=(32,32),mode='bilinear',align_corners=False)[:,0]
16
+ def loss_fn(p,x):g=x[:,0];w=x[:,1];ae=abs(p.mean((1,2))-g.mean((1,2)));pc=((p-p.mean((1,2),keepdim=True))*(w-w.mean((1,2),keepdim=True))).mean((1,2))/(p.std((1,2))*w.std((1,2))+1e-6);mae=abs(p-w).mean((1,2));return (ae+(1-pc)*mae).mean()
17
+ def write(p,o):p=Path(p);p.parent.mkdir(parents=True,exist_ok=True);p.write_text(json.dumps(o,indent=2)+'\n')
scripts/fake_data.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from pathlib import Path
2
+ import sys,numpy as np
3
+ R=Path(__file__).resolve().parents[1];sys.path.insert(0,str(R));from model.grace_seda import *
4
+ c=cfg(R);x=np.stack([sample(i).numpy() for i in range(20)]);p=R/c['data']['path'];p.parent.mkdir(parents=True,exist_ok=True);np.savez_compressed(p,input=x,features=FEATURES,shape=[20,9,32,32],resolution=.5);print(p)
scripts/inference.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import sys,numpy as np,torch
3
+ R=Path(__file__).resolve().parents[1];sys.path.insert(0,str(R));from model.grace_seda import *
4
+ c=cfg(R);d=np.load(R/c['data']['path']);z=torch.load(R/c['paths']['checkpoint'],map_location='cpu',weights_only=True);x=torch.tensor(d['input']);pred=[]
5
+ with torch.no_grad():
6
+ for s in z['states']:m=GRACESEDA(**z['model_config']);m.load_state_dict(s);pred.append(m(x).numpy())
7
+ p=R/c['paths']['predictions'];p.parent.mkdir(parents=True,exist_ok=True);np.savez_compressed(p,ensemble=pred,grace=d['input'][:,0],wghm=d['input'][:,1],mean=np.mean(pred,0),uncertainty=np.std(pred,0));print(p)
scripts/result.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from pathlib import Path
2
+ import sys,numpy as np;import matplotlib;matplotlib.use('Agg');import matplotlib.pyplot as plt
3
+ R=Path(__file__).resolve().parents[1];sys.path.insert(0,str(R));from model.grace_seda import cfg,write
4
+ c=cfg(R);d=np.load(R/c['paths']['predictions']);p=d['mean'];g=d['grace'];w=d['wghm'];corr=float(np.corrcoef(p.ravel(),w.ravel())[0,1]);aggregate=float(abs(p.mean((1,2))-g.mean((1,2))).mean());unc=float(d['uncertainty'].mean());write(R/c['paths']['evaluation'],{'wghm_correlation':corr,'grace_aggregate_error':aggregate,'mean_uncertainty':unc,'synthetic':True});plt.imshow(p[0]-w[0],cmap='coolwarm');plt.colorbar();q=R/c['paths']['figure'];q.parent.mkdir(parents=True,exist_ok=True);plt.savefig(q,dpi=150);print(q)
scripts/train.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import sys,os,numpy as np,torch;import torch.distributed as dist
3
+ from torch.nn.parallel import DistributedDataParallel as DDP
4
+ R=Path(__file__).resolve().parents[1];sys.path.insert(0,str(R));from model.grace_seda import *
5
+ c=cfg(R);rank=int(os.getenv('RANK',0));world=int(os.getenv('WORLD_SIZE',1));ddp=world>1
6
+ if ddp:dist.init_process_group('gloo')
7
+ d=np.load(R/c['data']['path']);states=[];hist=[]
8
+ for member in range(5):
9
+ torch.manual_seed(c['seed']+member);base=GRACESEDA(**c['model']);m=DDP(base) if ddp else base;opt=torch.optim.Adam(m.parameters(),lr=c['train']['learning_rate']);ls=[]
10
+ for i in range(rank,20,world):x=torch.tensor(d['input'][i:i+1]);loss=loss_fn(m(x),x);opt.zero_grad();loss.backward();opt.step();ls.append(float(loss))
11
+ if rank==0:states.append(base.state_dict());hist.append(float(np.mean(ls)))
12
+ p=R/c['paths']['checkpoint']
13
+ if rank==0:p.parent.mkdir(parents=True,exist_ok=True);torch.save({'states':states,'model_config':c['model']},p);write(R/c['paths']['training_metrics'],{'ensemble_losses':hist,'world_size':world});print(p)
14
+ if ddp:dist.destroy_process_group()
weight/.gitkeep ADDED
File without changes