DINCAE / scripts /result.py
zhangrenchao's picture
Publish DINCAE reproduction
43dce84 verified
Raw
History Blame Contribute Delete
4.19 kB
"""Evaluate DINCAE, residual calibration, and a rank-13 DINEOF-like baseline."""
import json
from pathlib import Path
import sys
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import yaml
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from model.dincae import reconstruction_metrics
def dineof_like(observed, ocean_mask, requested_rank):
matrix = observed.reshape(observed.shape[0], -1).astype(np.float64)
sea = ocean_mask.ravel()
values = matrix[:, sea]
missing = ~np.isfinite(values)
column_mean = np.nanmean(values, axis=0)
column_mean = np.nan_to_num(column_mean, nan=float(np.nanmean(values)))
filled = np.where(missing, column_mean[None], values)
rank = min(int(requested_rank), min(filled.shape) - 1)
for _ in range(4):
mean = filled.mean(axis=0, keepdims=True)
u, singular, vt = np.linalg.svd(filled - mean, full_matrices=False)
reconstructed = (u[:, :rank] * singular[:rank]) @ vt[:rank] + mean
filled[missing] = reconstructed[missing]
result = np.full_like(matrix, np.nan)
result[:, sea] = filled
return result.reshape(observed.shape).astype(np.float32), rank
def main():
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
with np.load(ROOT / config["paths"]["inference"]) as loaded:
pred = {key: loaded[key] for key in loaded.files}
with np.load(ROOT / config["data"]["root"] / config["data"]["file"]) as loaded:
full = {key: loaded[key] for key in loaded.files}
mask = pred["missing_mask"]
metrics = {"dincae": reconstruction_metrics(pred["target"], pred["prediction"], mask)}
std_residual = (pred["prediction"] - pred["target"]) / np.sqrt(np.maximum(pred["variance"], 1e-12))
residual = std_residual[mask & np.isfinite(std_residual)]
bins = int(config["evaluation"]["calibration_bins"])
edges = np.linspace(-4, 4, bins + 1)
counts, _ = np.histogram(residual, bins=edges)
metrics["standardized_residual_calibration"] = {
"mean": float(residual.mean()), "std": float(residual.std()),
"within_1sigma": float(np.mean(np.abs(residual) <= 1)),
"within_2sigma": float(np.mean(np.abs(residual) <= 2)),
"histogram_edges": edges.tolist(), "histogram_counts": counts.tolist(),
}
baseline_all, effective_rank = dineof_like(full["observed_anomaly"] + full["climatology"],
full["ocean_mask"], config["evaluation"]["dineof_rank"])
start = min(int(config["data"]["train_samples"]), len(full["timestamps"]) - 1)
baseline = baseline_all[start:]
metrics["dineof_like"] = reconstruction_metrics(pred["target"], baseline, mask)
metrics["dineof_like"]["requested_rank"] = int(config["evaluation"]["dineof_rank"])
metrics["dineof_like"]["effective_rank"] = effective_rank
out_dir = ROOT / config["paths"]["evaluation_dir"]
out_dir.mkdir(parents=True, exist_ok=True)
(out_dir / "metrics.json").write_text(json.dumps(metrics, indent=2))
index = 0
fields = (pred["observed"][index], pred["target"][index], pred["prediction"][index],
baseline[index], pred["prediction"][index] - pred["target"][index])
titles = ("Cloudy AVHRR-like input", "Complete target", "DINCAE", "DINEOF-like rank 13", "DINCAE error")
fig, axes = plt.subplots(1, 5, figsize=(17, 3.6), constrained_layout=True)
lower, upper = np.nanpercentile(pred["target"][index], (2, 98))
for axis, field, title in zip(axes, fields, titles):
if title.endswith("error"):
limit = max(float(np.nanpercentile(np.abs(field), 98)), 1e-3)
image = axis.imshow(field, cmap="RdBu_r", vmin=-limit, vmax=limit)
else:
image = axis.imshow(field, cmap="turbo", vmin=lower, vmax=upper)
axis.set_title(title, fontsize=9)
axis.set_axis_off()
fig.colorbar(image, ax=axis, fraction=0.046)
fig.savefig(out_dir / "comparison.png", dpi=150)
plt.close(fig)
print(json.dumps(metrics, indent=2))
print(f"comparison={out_dir / 'comparison.png'}")
if __name__ == "__main__":
main()