|
| 1 | +#!/usr/bin/env python |
| 2 | +""" |
| 3 | +Check that a model stored in MLflow loads and evaluates correctly, |
| 4 | +using the same logic as the dashboard. |
| 5 | +
|
| 6 | +Usage: |
| 7 | + python check_model.py --config_file <path/to/config.yaml> --model <GP|NN|ensemble_NN> |
| 8 | +""" |
| 9 | + |
| 10 | +import argparse |
| 11 | +import os |
| 12 | +import sys |
| 13 | +import torch |
| 14 | +import yaml |
| 15 | + |
| 16 | +DASHBOARD_DIR = os.path.join( |
| 17 | + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "dashboard" |
| 18 | +) # similar to "cd ../dashboard" |
| 19 | +sys.path.insert(0, DASHBOARD_DIR) |
| 20 | +from model_manager import ModelManager # noqa: E402 |
| 21 | +from utils import load_database, load_data # noqa: E402 |
| 22 | + |
| 23 | + |
| 24 | +MODEL_TYPES = ["GP", "NN", "ensemble_NN"] |
| 25 | +ACCURACY_TOLERANCE = 0.80 |
| 26 | + |
| 27 | + |
| 28 | +def load_experimental_data(config_dict): |
| 29 | + """Fetch all experimental points from the database.""" |
| 30 | + input_names = [v["name"] for v in config_dict["inputs"].values()] |
| 31 | + output_names = [v["name"] for v in config_dict["outputs"].values()] |
| 32 | + |
| 33 | + db = load_database(config_dict) |
| 34 | + exp_data, _ = load_data(db, config_dict["experiment"]) |
| 35 | + |
| 36 | + return exp_data, input_names, output_names |
| 37 | + |
| 38 | + |
| 39 | +def check_evaluate(config_dict, model_type): |
| 40 | + """Load model and evaluate with experimental data; verify accuracy (relative RMSE <= threshold).""" |
| 41 | + # Load model |
| 42 | + mm = ModelManager(config_dict=config_dict, model_type_tag=model_type) |
| 43 | + # Load experimental data |
| 44 | + df_exp, input_names, output_names = load_experimental_data(config_dict) |
| 45 | + |
| 46 | + # Skip accuracy check if no experimental data available |
| 47 | + if len(df_exp) == 0: |
| 48 | + print( |
| 49 | + f"[SKIP] No experimental data available for {config_dict['experiment']}; skipping accuracy check." |
| 50 | + ) |
| 51 | + return |
| 52 | + |
| 53 | + # Convert input to the format expected by the model manager |
| 54 | + inputs = {n: torch.tensor(df_exp[n].values) for n in input_names} |
| 55 | + |
| 56 | + # Check accuracy |
| 57 | + all_passed = True |
| 58 | + for output_name in output_names: |
| 59 | + actual = torch.tensor(df_exp[output_name].values) |
| 60 | + if actual.isnan().all(): |
| 61 | + print( |
| 62 | + f" [SKIP] Output '{output_name}': all actual values are NaN; skipping." |
| 63 | + ) |
| 64 | + continue |
| 65 | + prediction, _, _ = mm.evaluate(inputs, output_name) |
| 66 | + rel_errors = (prediction - actual) / torch.max( |
| 67 | + torch.abs(actual), torch.abs(prediction) |
| 68 | + ) |
| 69 | + rmse = torch.sqrt(torch.nanmean(rel_errors**2)).item() |
| 70 | + if rmse <= ACCURACY_TOLERANCE: |
| 71 | + status = "PASS" |
| 72 | + else: |
| 73 | + status = "FAIL" |
| 74 | + all_passed = False |
| 75 | + print( |
| 76 | + f" [{status}] Output '{output_name}': relative RMSE = {rmse:.1%} (tolerance {ACCURACY_TOLERANCE:.0%})" |
| 77 | + ) |
| 78 | + |
| 79 | + if not all_passed: |
| 80 | + raise RuntimeError( |
| 81 | + f"Accuracy check failed: relative RMSE exceeded {ACCURACY_TOLERANCE:.0%} for one or more outputs." |
| 82 | + ) |
| 83 | + |
| 84 | + |
| 85 | +if __name__ == "__main__": |
| 86 | + parser = argparse.ArgumentParser( |
| 87 | + description="Verify that an MLflow model loads and evaluates correctly." |
| 88 | + ) |
| 89 | + parser.add_argument( |
| 90 | + "--config_file", |
| 91 | + help="Path to the configuration file", |
| 92 | + type=str, |
| 93 | + required=True, |
| 94 | + ) |
| 95 | + parser.add_argument( |
| 96 | + "--model", |
| 97 | + help="Model type: GP, NN, or ensemble_NN", |
| 98 | + choices=MODEL_TYPES, |
| 99 | + required=True, |
| 100 | + ) |
| 101 | + args = parser.parse_args() |
| 102 | + |
| 103 | + # Load configuration |
| 104 | + with open(args.config_file) as f: |
| 105 | + config_dict = yaml.safe_load(f) |
| 106 | + print(f"Experiment: {config_dict['experiment']}") |
| 107 | + |
| 108 | + # Load model and evaluate with experimental data |
| 109 | + try: |
| 110 | + check_evaluate(config_dict, args.model) |
| 111 | + except Exception as e: |
| 112 | + print(f"[FAIL] {e}") |
| 113 | + sys.exit(1) |
| 114 | + |
| 115 | + print("[PASS] Model loaded and evaluated successfully.") |
0 commit comments