zhangrenchao commited on
Commit
ae605ea
·
verified ·
1 Parent(s): 68fd2c6

Publish FuXi-Weather 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,7 @@
 
 
 
 
 
 
 
 
1
+ seed: 42
2
+ data: {path: data/fuxi_weather.npz, samples: 8, channels: 20, tile: 16, logical_grid: [721, 1440]}
3
+ model: {channels: 20, hidden: 24}
4
+ train: {epochs: 1, learning_rate: 0.001}
5
+ inference: {steps: 12, paper_steps: 40, switch_step: 6}
6
+ paths: {checkpoint: result/checkpoints/fuxi_weather.pt, training_metrics: result/training/metrics.json, predictions: result/output/predictions.npz, evaluation: result/evaluation/metrics.json, figure: result/evaluation/comparison.png}
7
+ paper_model: {resolution_degrees: 0.25, cycle_hours: 6, observation_window_hours: 8, forecast_days: 10, paper_license: CC-BY-NC-ND-4.0}
config.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"model_name":"FuXi-Weather","model_type":"fuxi_weather","architectures":["FuXiWeather"],"framework":"PyTorch","domain":"weather","task":"data-assimilation-and-forecast","implementation":{"entry_point":"model/fuxi_weather.py","scope":"L2 system-flow reproduction"},"architecture":{"logical_grid":[721,1440],"engineering_channels":20,"variables":["Z","T","U","V","R","T2M","MSLP","U10","V10","TP"],"observation_window_hours":8,"forecast_step_hours":6,"cascade_days":[4,10]},"data":{"reference":"ERA5","observations":["FY-3E","Metop-C","NOAA-20","GNSS-RO"],"synthetic_tiles":true},"configuration_sources":["conf/config.yaml","model/fuxi_weather.py","scripts/fake_data.py","scripts/train.py","scripts/inference.py","scripts/result.py"]}
model/fuxi_weather.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from pathlib import Path
3
+ import torch
4
+ from torch import nn
5
+ import yaml
6
+ def cfg(r):return yaml.safe_load((Path(r)/"conf/config.yaml").read_text())
7
+ def sample(i,s=0):
8
+ y,x=torch.meshgrid(torch.linspace(-1,1,16),torch.linspace(-1,1,16),indexing="ij");c=torch.arange(20)[:,None,None];b=torch.sin((c%5+1)*x+.1*(i+s))*torch.cos((c%4+1)*y);m=(((torch.arange(16)[None,:]+i+s)%4==0).float().expand(16,-1))[None].expand(20,-1,-1);o=b*m;return b.float(),o.float(),m.float()
9
+ class FuXiWeather(nn.Module):
10
+ def __init__(self,channels=20,hidden=24):super().__init__();self.obs=nn.Conv2d(channels*2,hidden,1);self.bg=nn.Conv2d(channels,hidden,1);self.refine=nn.Sequential(nn.Conv2d(hidden*2,hidden,3,padding=1),nn.GELU(),nn.Conv2d(hidden,channels,1));self.short=nn.Conv2d(channels,channels,3,padding=1);self.medium=nn.Conv2d(channels,channels,3,padding=1);self.model_config={"channels":channels,"hidden":hidden}
11
+ def analysis(self,b,o,m):return b+self.refine(torch.cat((self.bg(b),self.obs(torch.cat((o,m),1))),1))
12
+ def forecast(self,x,medium=False):return x+(self.medium(x) if medium else self.short(x))
13
+ def forward(self,b,o,m,medium=False):
14
+ a=self.analysis(b,o,m);return a,self.forecast(a,medium)
15
+ 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,6 @@
 
 
 
 
 
 
 
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.fuxi_weather import *
4
+ c=cfg(R);b=[];o=[];m=[];t=[]
5
+ for i in range(8):a,z,q=sample(i);b.append(a);o.append(z);m.append(q);t.append(sample(i,1)[0])
6
+ p=R/c['data']['path'];p.parent.mkdir(parents=True,exist_ok=True);np.savez_compressed(p,background=b,observation=o,mask=m,target=t,logical_shape=[20,721,1440],is_complete_global=False);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.fuxi_weather 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);m=FuXiWeather(**z['model_config']);m.load_state_dict(z['model']);x=m.analysis(*map(torch.tensor,(d['background'][:2],d['observation'][:2],d['mask'][:2])));seq=[]
5
+ with torch.no_grad():
6
+ for s in range(c['inference']['steps']):x=m.forecast(x,s>=c['inference']['switch_step']);seq.append(x.numpy())
7
+ target=np.stack([[sample(i,s+1)[0].numpy() for s in range(12)] for i in range(2)]);p=R/c['paths']['predictions'];p.parent.mkdir(parents=True,exist_ok=True);np.savez_compressed(p,prediction=np.stack(seq,1),target=target,lead_hours=np.arange(1,13)*6,logical_shape=np.array([20,721,1440]),is_complete_global=False);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.fuxi_weather import *
4
+ c=cfg(R);d=np.load(R/c['paths']['predictions']);p=d['prediction'];truth=np.stack([[sample(i,s+1)[0].numpy() for s in range(12)] for i in range(2)]);rmse=np.sqrt(np.mean((p-truth)**2,axis=(0,2,3,4)));write(R/c['paths']['evaluation'],{'rmse':rmse.tolist(),'is_complete_global':False,'synthetic':True});plt.plot(d['lead_hours'],rmse);plt.xlabel('Lead (h)');plt.ylabel('RMSE');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,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.fuxi_weather 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']);base=FuXiWeather(**c['model']);m=DDP(base,find_unused_parameters=True) if ddp else base;opt=torch.optim.Adam(m.parameters(),lr=c['train']['learning_rate']);ls=[]
8
+ for i in range(rank,8,world):b,o,q,t=map(torch.tensor,(d['background'][i:i+1],d['observation'][i:i+1],d['mask'][i:i+1],d['target'][i:i+1]));a,f=m(b,o,q,i%2==1);loss=((a-t)**2).mean()+((f-t)**2).mean();opt.zero_grad();loss.backward();opt.step();ls.append(float(loss))
9
+ v=torch.tensor([sum(ls),len(ls)],dtype=torch.float64)
10
+ if ddp:dist.all_reduce(v)
11
+ p=R/c['paths']['checkpoint']
12
+ if rank==0:p.parent.mkdir(parents=True,exist_ok=True);torch.save({'model':base.state_dict(),'model_config':c['model']},p);write(R/c['paths']['training_metrics'],{'loss':float(v[0]/v[1]),'world_size':world});print(p)
13
+ if ddp:dist.destroy_process_group()
weight/.gitkeep ADDED
File without changes