"""Evaluate and visualize generated SEEDS ensembles.""" from __future__ import annotations import argparse from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import numpy as np from common import load_config, resolve_path def _crps(prediction: np.ndarray, target: np.ndarray) -> float: first = np.mean(np.abs(prediction - target[:, None]), axis=1) pairwise = np.mean(np.abs(prediction[:, :, None] - prediction[:, None, :]), axis=(1, 2)) return float(np.mean(first - 0.5 * pairwise)) def _acc(prediction: np.ndarray, target: np.ndarray) -> float: forecast = prediction.mean(axis=1).reshape(prediction.shape[0], -1) truth = target.reshape(target.shape[0], -1) forecast = forecast - forecast.mean(axis=1, keepdims=True) truth = truth - truth.mean(axis=1, keepdims=True) numerator = np.sum(forecast * truth, axis=1) denominator = np.sqrt(np.sum(forecast**2, axis=1) * np.sum(truth**2, axis=1)) return float(np.mean(numerator / np.maximum(denominator, 1e-8))) def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--config", default="conf/config.yaml") parser.add_argument("--prediction", default=None) parser.add_argument("--target", default=None) parser.add_argument("--sample-index", type=int, default=0) args = parser.parse_args() config = load_config(args.config) output_dir = resolve_path(config["paths"]["result_dir"], args.config) / "output" prediction = np.load(args.prediction or output_dir / "prediction.npy") target = np.load(args.target or output_dir / "target.npy") if prediction.ndim != 6 or target.ndim != 5 or prediction.shape[0] != target.shape[0]: raise ValueError(f"unexpected prediction/target shapes: {prediction.shape}, {target.shape}") ensemble_mean = prediction.mean(axis=1) ensemble_std = prediction.std(axis=1) rmse = float(np.sqrt(np.mean((ensemble_mean - target) ** 2))) acc = _acc(prediction, target) crps = _crps(prediction, target) result_dir = resolve_path(config["paths"]["result_dir"], args.config) result_dir.mkdir(parents=True, exist_ok=True) np.save(result_dir / "rmse.npy", np.asarray(rmse, dtype=np.float32)) np.save(result_dir / "acc.npy", np.asarray(acc, dtype=np.float32)) np.save(result_dir / "crps.npy", np.asarray(crps, dtype=np.float32)) sample = min(max(args.sample_index, 0), len(target) - 1) variable = config["validation"]["plot_variable"] channel = config["data"]["variables"].index(variable) truth = target[sample, channel].mean(axis=0) mean = ensemble_mean[sample, channel].mean(axis=0) spread = ensemble_std[sample, channel].mean(axis=0) figure, axes = plt.subplots(1, 3, figsize=(12, 4), constrained_layout=True) for axis, field, title in zip(axes, (truth, mean, spread), ("Target", "Ensemble mean", "Ensemble spread")): image = axis.imshow(field, cmap="coolwarm") axis.set_title(title) axis.set_xticks([]) axis.set_yticks([]) figure.colorbar(image, ax=axis, fraction=0.046, pad=0.04) figure.savefig(result_dir / "forecast.png", dpi=150) loss_dir = resolve_path(config["paths"]["checkpoint_dir"], args.config) if (loss_dir / "train_loss.npy").exists() and (loss_dir / "val_loss.npy").exists(): figure, axis = plt.subplots(figsize=(6, 4)) axis.plot(np.load(loss_dir / "train_loss.npy"), label="train") axis.plot(np.linspace(0, len(np.load(loss_dir / "train_loss.npy")), len(np.load(loss_dir / "val_loss.npy"))), np.load(loss_dir / "val_loss.npy"), label="validation") axis.set_xlabel("step") axis.set_ylabel("loss") axis.legend() figure.tight_layout() figure.savefig(result_dir / "loss.png", dpi=150) print(f"RMSE={rmse:.6f} ACC={acc:.6f} CRPS={crps:.6f}") print(f"saved results to {result_dir}") if __name__ == "__main__": main()