Publish FuXi-Weather reproduction
Browse files- .gitattributes +2 -35
- conf/config.yaml +7 -0
- config.json +1 -0
- model/fuxi_weather.py +15 -0
- scripts/fake_data.py +6 -0
- scripts/inference.py +7 -0
- scripts/result.py +4 -0
- scripts/train.py +13 -0
- weight/.gitkeep +0 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,2 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.
|
| 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
|