Streamflow-LSTM / model /streamflow_lstm.py
zhangrenchao's picture
Publish Streamflow-LSTM reproduction
186a48a verified
Raw
History Blame Contribute Delete
2.67 kB
import json
from pathlib import Path
import numpy as np
import torch
from torch import nn
def load_config(path):
with open(path, encoding="utf-8") as handle:
return json.load(handle)
def write_json(path, value):
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", encoding="utf-8") as handle:
json.dump(value, handle, indent=2, sort_keys=True)
class StreamflowLSTM(nn.Module):
"""Paper stack with explicit inter-layer activation semantics."""
def __init__(self, input_size=23, hidden_size=50, dropout=0.1):
super().__init__()
self.layers = nn.ModuleList([
nn.LSTM(input_size, hidden_size, batch_first=True),
nn.LSTM(hidden_size, hidden_size, batch_first=True),
nn.LSTM(hidden_size, hidden_size, batch_first=True),
])
self.dropout = nn.Dropout(dropout)
self.dense = nn.Linear(hidden_size, 1)
def forward(self, inputs):
if inputs.ndim != 3 or inputs.shape[-2:] != (28, 23):
raise ValueError(f"expected [B,28,23], got {tuple(inputs.shape)}")
values, _ = self.layers[0](inputs)
values = self.dropout(torch.relu(values))
values, _ = self.layers[1](values)
values = self.dropout(torch.relu(values))
values, _ = self.layers[2](values)
values = torch.tanh(values[:, -1])
return self.dense(values).squeeze(-1)
def nse(prediction, target):
prediction, target = np.asarray(prediction), np.asarray(target)
denominator = np.sum((target - target.mean()) ** 2)
return float(1.0 - np.sum((prediction - target) ** 2) / max(denominator, 1e-12))
def metrics(prediction, target):
prediction = np.asarray(prediction, dtype=np.float64)
target = np.asarray(target, dtype=np.float64)
correlation = float(np.corrcoef(prediction, target)[0, 1]) if prediction.size > 1 else 0.0
if not np.isfinite(correlation):
correlation = 0.0
alpha = float(prediction.std() / max(target.std(), 1e-12))
beta = float(prediction.mean() / max(target.mean(), 1e-12))
kge = float(1.0 - np.sqrt((correlation - 1) ** 2 + (alpha - 1) ** 2 + (beta - 1) ** 2))
return {
"kge": kge,
"kge_r": correlation,
"kge_alpha": alpha,
"kge_beta": beta,
"nse": nse(prediction, target),
"rmse_m3_s": float(np.sqrt(np.mean((prediction - target) ** 2))),
}
def load_member(payload, device):
model = StreamflowLSTM(hidden_size=payload["hidden_size"], dropout=payload["dropout"]).to(device)
model.load_state_dict(payload["state_dict"])
model.eval()
return model, payload