mirror of
https://github.com/jung-geun/PSO.git
synced 2026-09-20 14:11:48 +09:00
Migrate the package and examples to the tensor-native PyTorch implementation, add benchmark evidence, and add the guarded post-training convergence protocol with TensorBoard progress monitoring and hash-verified recovery. Constraint: Preserve one-shot official-test sealing and auditable research artifacts Rejected: Commit local .omc runs and downloaded datasets | multi-gigabyte runtime state is machine-local Confidence: high Scope-risk: broad Not-tested: Production CUDA run on pieroot-server
1067 lines
48 KiB
Python
1067 lines
48 KiB
Python
"""Strict Evaluator for Post-Training PSO Ensemble Study.
|
|
|
|
Evaluates experiment artifacts against the frozen mission and evaluator contract
|
|
defined in .omc/autoresearch/post-training-pso-ensemble/evaluator.json and mission.md.
|
|
|
|
Recomputes 14 development hard gates and 9 confirmation hard gates, verifies
|
|
leakage control, frozen-policy consistency, exact query/sample/cache accounting,
|
|
simplex probability weight constraints, SLSQP solver status, finiteness, and metric consistency.
|
|
|
|
Produces a structured evaluation payload containing score, pass/fail status, gate
|
|
results, and issue categories. Never trusts self-reported artifact pass flags.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import math
|
|
import os
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
|
|
|
EVALUATOR_VERSION = "POST-TRAINING-PSO-ENSEMBLE-EVALUATOR 1.2.0"
|
|
EXPECTED_PROTOCOL_VERSION = "POST-TRAINING-PSO-ENSEMBLE 1.1.0"
|
|
EXPECTED_DATASETS = ["mnist", "fashion_mnist"]
|
|
EXPECTED_SPLIT_SEED = 20260904
|
|
EXPECTED_SEARCH_SAMPLES = 50000
|
|
EXPECTED_VAL_SAMPLES = 10000
|
|
EXPECTED_POOL_SEEDS = [201, 202, 203, 204, 205]
|
|
EXPECTED_REF_SINGLE_SEED = 201
|
|
EXPECTED_50E_SINGLE_EPOCHS = 50
|
|
EXPECTED_SWARM_SEEDS = [301, 302, 303]
|
|
EXPECTED_PARTICLES = 30
|
|
EXPECTED_EPOCHS = 30
|
|
EXPECTED_QUERIES_PER_SEED = 900
|
|
EXPECTED_SAMPLES_PER_SEED = 9000000
|
|
|
|
REQUIRED_BASELINES = [
|
|
"reference_single_10e",
|
|
"best_single_10e",
|
|
"single_50e",
|
|
"uniform_ensemble",
|
|
"uniform_temperature",
|
|
"slsqp_weights",
|
|
"pso_weights",
|
|
]
|
|
REQUIRED_BASELINES_SET = set(REQUIRED_BASELINES)
|
|
|
|
# Weighted methods that store explicit simplex weight vectors
|
|
WEIGHTED_METHODS = [
|
|
"slsqp_weights",
|
|
"pso_weights",
|
|
]
|
|
|
|
|
|
def _is_finite_number(value: Any) -> bool:
|
|
"""Returns True if value is a numeric int/float (not bool) and finite."""
|
|
return (
|
|
isinstance(value, (int, float))
|
|
and not isinstance(value, bool)
|
|
and math.isfinite(float(value))
|
|
)
|
|
|
|
|
|
def _append_issue(issues: Dict[str, List[str]], category: str, message: str) -> None:
|
|
"""Appends an issue string to the given category list."""
|
|
if category not in issues:
|
|
issues[category] = []
|
|
issues[category].append(message)
|
|
|
|
|
|
def save_json_atomic(data: Dict[str, Any], json_path: Union[str, Path]) -> None:
|
|
"""Atomically writes JSON payload to destination path using a temporary file."""
|
|
path = Path(json_path)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
tmp_path = path.with_suffix(f".tmp_{os.getpid()}_{time.time_ns()}")
|
|
with open(tmp_path, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, indent=2)
|
|
tmp_path.replace(path)
|
|
|
|
|
|
def _to_pp(acc: float) -> float:
|
|
"""Converts accuracy to percentage points (0-100 scale)."""
|
|
return acc * 100.0 if acc <= 1.0 else acc
|
|
|
|
|
|
def _unwrap_metrics(entry: Any) -> Tuple[Optional[float], Optional[float]]:
|
|
"""Unwraps accuracy and NLL/loss from base method dict or nested weighted method metrics dict."""
|
|
if not isinstance(entry, dict):
|
|
return None, None
|
|
metrics_dict = entry.get("metrics") if isinstance(entry.get("metrics"), dict) else entry
|
|
|
|
acc = None
|
|
for key in ("accuracy", "acc", "val_acc", "test_acc", "val_selected_acc"):
|
|
if key in metrics_dict and _is_finite_number(metrics_dict[key]):
|
|
acc = float(metrics_dict[key])
|
|
break
|
|
|
|
nll = None
|
|
for key in ("nll", "loss", "val_nll", "test_nll", "val_loss", "val_selected_loss"):
|
|
if key in metrics_dict and _is_finite_number(metrics_dict[key]):
|
|
nll = float(metrics_dict[key])
|
|
break
|
|
|
|
return acc, nll
|
|
|
|
|
|
def _validate_simplex_weights(weights: Any, tolerance: float = 1e-6) -> bool:
|
|
"""Validates that weights form a 5-element probability simplex summing to 1 within tolerance."""
|
|
if not isinstance(weights, (list, tuple)) or len(weights) != 5:
|
|
return False
|
|
for w in weights:
|
|
if not _is_finite_number(w) or float(w) < -tolerance:
|
|
return False
|
|
total = math.fsum([float(w) for w in weights])
|
|
return abs(total - 1.0) <= tolerance
|
|
|
|
def _sequences_close(left: Any, right: Any, tolerance: float = 1e-6) -> bool:
|
|
"""Return whether two finite numeric sequences agree elementwise."""
|
|
if not isinstance(left, (list, tuple)) or not isinstance(right, (list, tuple)):
|
|
return False
|
|
if len(left) != len(right):
|
|
return False
|
|
return all(
|
|
_is_finite_number(a)
|
|
and _is_finite_number(b)
|
|
and math.isclose(float(a), float(b), abs_tol=tolerance, rel_tol=tolerance)
|
|
for a, b in zip(left, right)
|
|
)
|
|
|
|
|
|
def _scan_for_non_finite(data: Any, path: str = "") -> List[str]:
|
|
"""Recursively scans a data structure for any NaN/Inf values."""
|
|
non_finites: List[str] = []
|
|
if isinstance(data, float):
|
|
if not math.isfinite(data):
|
|
non_finites.append(f"{path}: {data}")
|
|
elif isinstance(data, dict):
|
|
for k, v in data.items():
|
|
non_finites.extend(_scan_for_non_finite(v, f"{path}.{k}" if path else str(k)))
|
|
elif isinstance(data, (list, tuple)):
|
|
for idx, item in enumerate(data):
|
|
non_finites.extend(_scan_for_non_finite(item, f"{path}[{idx}]"))
|
|
return non_finites
|
|
|
|
|
|
def evaluate_artifact(artifact: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Strictly evaluates a post-training PSO ensemble experiment artifact.
|
|
|
|
Args:
|
|
artifact: Parsed JSON experiment artifact dictionary.
|
|
|
|
Returns:
|
|
Structured evaluation payload with score, pass/fail status, gate counts,
|
|
and categorized issues. Never relies on self-reported artifact pass flags.
|
|
"""
|
|
issues: Dict[str, List[str]] = {
|
|
"schema": [],
|
|
"config": [],
|
|
"finite": [],
|
|
"weights": [],
|
|
"accounting": [],
|
|
"leakage": [],
|
|
"tuning": [],
|
|
"slsqp": [],
|
|
"consistency": [],
|
|
"gates": [],
|
|
}
|
|
|
|
if not isinstance(artifact, dict):
|
|
_append_issue(issues, "schema", "Artifact must be a JSON object")
|
|
return {
|
|
"evaluator_version": EVALUATOR_VERSION,
|
|
"pass": False,
|
|
"score": -1000.0,
|
|
"development_pass": False,
|
|
"confirmation_pass": False,
|
|
"failed_hard_gate_count": 1,
|
|
"issues": issues,
|
|
"development_gates": {},
|
|
"confirmation_gates": None,
|
|
"metrics": {},
|
|
}
|
|
|
|
# 1. Non-finite value scan
|
|
non_finite_locations = _scan_for_non_finite(artifact)
|
|
if non_finite_locations:
|
|
for loc in non_finite_locations[:10]:
|
|
_append_issue(issues, "finite", f"Non-finite value found at {loc}")
|
|
|
|
# Protocol version check
|
|
protocol_version = artifact.get("protocol_version")
|
|
if protocol_version != EXPECTED_PROTOCOL_VERSION:
|
|
_append_issue(
|
|
issues,
|
|
"config",
|
|
f"Artifact protocol_version must be '{EXPECTED_PROTOCOL_VERSION}', got '{protocol_version}'",
|
|
)
|
|
|
|
# 2. Config & Protocol verification
|
|
config = artifact.get("config")
|
|
if not isinstance(config, dict):
|
|
_append_issue(issues, "schema", "Missing or non-object top-level 'config'")
|
|
config = {}
|
|
|
|
datasets = config.get("datasets")
|
|
if not isinstance(datasets, list) or sorted(datasets) != sorted(EXPECTED_DATASETS):
|
|
_append_issue(issues, "config", f"Config 'datasets' must be {EXPECTED_DATASETS}")
|
|
|
|
if config.get("split_seed") != EXPECTED_SPLIT_SEED:
|
|
_append_issue(issues, "config", f"Config 'split_seed' must be {EXPECTED_SPLIT_SEED}")
|
|
|
|
if config.get("search_samples") != EXPECTED_SEARCH_SAMPLES:
|
|
_append_issue(
|
|
issues, "config", f"Config 'search_samples' must be {EXPECTED_SEARCH_SAMPLES}"
|
|
)
|
|
|
|
if config.get("validation_samples") != EXPECTED_VAL_SAMPLES:
|
|
_append_issue(
|
|
issues, "config", f"Config 'validation_samples' must be {EXPECTED_VAL_SAMPLES}"
|
|
)
|
|
|
|
if config.get("pool_seeds") != EXPECTED_POOL_SEEDS:
|
|
_append_issue(issues, "config", f"Config 'pool_seeds' must be {EXPECTED_POOL_SEEDS}")
|
|
|
|
if config.get("reference_single_seed") != EXPECTED_REF_SINGLE_SEED:
|
|
_append_issue(
|
|
issues, "config", f"Config 'reference_single_seed' must be {EXPECTED_REF_SINGLE_SEED}"
|
|
)
|
|
|
|
if config.get("equal_budget_single_epochs") != EXPECTED_50E_SINGLE_EPOCHS:
|
|
_append_issue(
|
|
issues,
|
|
"config",
|
|
f"Config 'equal_budget_single_epochs' must be {EXPECTED_50E_SINGLE_EPOCHS}",
|
|
)
|
|
|
|
pso_cfg = config.get("pso", {}) if isinstance(config.get("pso"), dict) else {}
|
|
if pso_cfg.get("particles") != EXPECTED_PARTICLES:
|
|
_append_issue(
|
|
issues, "config", f"Config 'pso.particles' must be {EXPECTED_PARTICLES}"
|
|
)
|
|
if pso_cfg.get("epochs") != EXPECTED_EPOCHS:
|
|
_append_issue(issues, "config", f"Config 'pso.epochs' must be {EXPECTED_EPOCHS}")
|
|
if pso_cfg.get("swarm_seeds") != EXPECTED_SWARM_SEEDS:
|
|
_append_issue(
|
|
issues, "config", f"Config 'pso.swarm_seeds' must be {EXPECTED_SWARM_SEEDS}"
|
|
)
|
|
if pso_cfg.get("queries_per_seed") != EXPECTED_QUERIES_PER_SEED:
|
|
_append_issue(
|
|
issues,
|
|
"accounting",
|
|
f"Config 'pso.queries_per_seed' must be {EXPECTED_QUERIES_PER_SEED}",
|
|
)
|
|
if pso_cfg.get("sample_evaluations_per_seed") != EXPECTED_SAMPLES_PER_SEED:
|
|
_append_issue(
|
|
issues,
|
|
"accounting",
|
|
f"Config 'pso.sample_evaluations_per_seed' must be {EXPECTED_SAMPLES_PER_SEED}",
|
|
)
|
|
|
|
# Validate frozen PSO hyperparameters in config
|
|
if pso_cfg.get("method") != "constriction":
|
|
_append_issue(issues, "config", f"Config 'pso.method' must be 'constriction', got '{pso_cfg.get('method')}'")
|
|
if pso_cfg.get("evaluation") != "full":
|
|
_append_issue(issues, "config", f"Config 'pso.evaluation' must be 'full', got '{pso_cfg.get('evaluation')}'")
|
|
if pso_cfg.get("renewal") != "loss":
|
|
_append_issue(issues, "config", f"Config 'pso.renewal' must be 'loss', got '{pso_cfg.get('renewal')}'")
|
|
if pso_cfg.get("particle_bounds") != [-4.0, 4.0]:
|
|
_append_issue(issues, "config", f"Config 'pso.particle_bounds' must be [-4.0, 4.0], got '{pso_cfg.get('particle_bounds')}'")
|
|
if pso_cfg.get("boundary_strategy") != "reflect":
|
|
_append_issue(issues, "config", f"Config 'pso.boundary_strategy' must be 'reflect', got '{pso_cfg.get('boundary_strategy')}'")
|
|
if pso_cfg.get("velocity_limit_ratio") != 0.1:
|
|
_append_issue(issues, "config", f"Config 'pso.velocity_limit_ratio' must be 0.1, got '{pso_cfg.get('velocity_limit_ratio')}'")
|
|
if pso_cfg.get("initial_position_noise") != 0.0:
|
|
_append_issue(issues, "config", f"Config 'pso.initial_position_noise' must be 0.0, got '{pso_cfg.get('initial_position_noise')}'")
|
|
|
|
# 3. Leakage and Post-Test Tuning checks
|
|
post_test_tuning = artifact.get("post_test_tuning_or_reruns", 0)
|
|
if post_test_tuning != 0:
|
|
_append_issue(
|
|
issues,
|
|
"tuning",
|
|
f"post_test_tuning_or_reruns must be 0, got {post_test_tuning}",
|
|
)
|
|
|
|
if "official_test_data_loaded_before_freeze" in artifact:
|
|
global_pre_loaded = artifact["official_test_data_loaded_before_freeze"]
|
|
if global_pre_loaded is not False:
|
|
_append_issue(
|
|
issues,
|
|
"leakage",
|
|
"Top-level official_test_data_loaded_before_freeze must be False "
|
|
f"when present, got {global_pre_loaded}",
|
|
)
|
|
if "official_test_evaluations_before_freeze" in artifact:
|
|
global_pre_evals = artifact["official_test_evaluations_before_freeze"]
|
|
if global_pre_evals != 0:
|
|
_append_issue(
|
|
issues,
|
|
"leakage",
|
|
"Top-level official_test_evaluations_before_freeze must be 0 "
|
|
f"when present, got {global_pre_evals}",
|
|
)
|
|
|
|
# 4. Workloads & Validation Analysis
|
|
workloads = artifact.get("workloads")
|
|
if not isinstance(workloads, dict):
|
|
_append_issue(issues, "schema", "Missing or non-object top-level 'workloads'")
|
|
workloads = {}
|
|
|
|
pre_freeze_loaded_ok = (
|
|
artifact.get("official_test_data_loaded_before_freeze", False) is False
|
|
)
|
|
pre_freeze_evals_ok = (
|
|
artifact.get("official_test_evaluations_before_freeze", 0) == 0
|
|
)
|
|
|
|
val_metrics_by_dataset: Dict[str, Dict[str, Dict[str, float]]] = {}
|
|
test_metrics_by_dataset: Dict[str, Dict[str, Dict[str, float]]] = {}
|
|
val_cache_counts: Dict[str, Dict[str, int]] = {}
|
|
|
|
pso_wall_times: Dict[str, List[float]] = {}
|
|
adam_pool_wall_times: Dict[str, float] = {}
|
|
|
|
for ds in EXPECTED_DATASETS:
|
|
if ds not in workloads:
|
|
_append_issue(issues, "schema", f"Workloads missing dataset '{ds}'")
|
|
pre_freeze_loaded_ok = False
|
|
pre_freeze_evals_ok = False
|
|
continue
|
|
wl = workloads[ds]
|
|
if not isinstance(wl, dict):
|
|
_append_issue(issues, "schema", f"Workload '{ds}' must be a JSON object")
|
|
pre_freeze_loaded_ok = False
|
|
pre_freeze_evals_ok = False
|
|
continue
|
|
|
|
# Per-workload declarations are mandatory and cannot mask contradictory
|
|
# top-level leakage counters.
|
|
wl_pre_loaded = wl.get("official_test_data_loaded_before_freeze")
|
|
if wl_pre_loaded is not False:
|
|
pre_freeze_loaded_ok = False
|
|
_append_issue(
|
|
issues,
|
|
"leakage",
|
|
f"Dataset '{ds}' official_test_data_loaded_before_freeze must be False, got {wl_pre_loaded}",
|
|
)
|
|
|
|
wl_pre_evals = wl.get("official_test_evaluations_before_freeze")
|
|
if wl_pre_evals != 0:
|
|
pre_freeze_evals_ok = False
|
|
_append_issue(
|
|
issues,
|
|
"leakage",
|
|
f"Dataset '{ds}' official_test_evaluations_before_freeze must be 0, got {wl_pre_evals}",
|
|
)
|
|
|
|
# Validation cache key checks:
|
|
# pool_forward_passes (5), long_single_forward_passes (1), base_cnn_forward_passes_during_optimization (0)
|
|
val_cache = wl.get("validation_cache") if isinstance(wl.get("validation_cache"), dict) else wl
|
|
val_pool_passes = val_cache.get("pool_forward_passes", val_cache.get("validation_pool_forward_passes"))
|
|
long_single_passes = val_cache.get("long_single_forward_passes", val_cache.get("val_long_single_passes", 1))
|
|
opt_base_passes = val_cache.get("base_cnn_forward_passes_during_optimization", val_cache.get("optimization_base_model_forward_passes"))
|
|
|
|
val_cache_counts[ds] = {
|
|
"pool_forward_passes": int(val_pool_passes) if _is_finite_number(val_pool_passes) else -1,
|
|
"long_single_forward_passes": int(long_single_passes) if _is_finite_number(long_single_passes) else -1,
|
|
"base_cnn_forward_passes_during_optimization": int(opt_base_passes) if _is_finite_number(opt_base_passes) else -1,
|
|
}
|
|
|
|
if val_cache_counts[ds]["pool_forward_passes"] != 5:
|
|
_append_issue(
|
|
issues,
|
|
"accounting",
|
|
f"Dataset '{ds}' validation pool_forward_passes must be 5, got {val_pool_passes}",
|
|
)
|
|
if val_cache_counts[ds]["base_cnn_forward_passes_during_optimization"] != 0:
|
|
_append_issue(
|
|
issues,
|
|
"accounting",
|
|
f"Dataset '{ds}' base_cnn_forward_passes_during_optimization must be 0, got {opt_base_passes}",
|
|
)
|
|
|
|
# Extract Adam pool training wall time (key: adam_pool_wall_time_seconds)
|
|
training_info = wl.get("training") if isinstance(wl.get("training"), dict) else wl
|
|
adam_wall = training_info.get("adam_pool_wall_time_seconds", training_info.get("adam_pool_wall_time"))
|
|
if _is_finite_number(adam_wall):
|
|
adam_pool_wall_times[ds] = float(adam_wall)
|
|
|
|
# Validate validation methods dict & exact method set
|
|
val_sec = wl.get("validation") if isinstance(wl.get("validation"), dict) else wl
|
|
methods_dict = val_sec.get("methods") if isinstance(val_sec.get("methods"), dict) else val_sec
|
|
|
|
if not isinstance(methods_dict, dict):
|
|
_append_issue(issues, "schema", f"Dataset '{ds}' validation methods must be a dictionary")
|
|
methods_dict = {}
|
|
|
|
present_methods = set(methods_dict.keys())
|
|
if present_methods != REQUIRED_BASELINES_SET:
|
|
_append_issue(
|
|
issues,
|
|
"schema",
|
|
f"Dataset '{ds}' validation methods set {present_methods} does not match required {REQUIRED_BASELINES_SET}",
|
|
)
|
|
|
|
ds_val_metrics: Dict[str, Dict[str, float]] = {}
|
|
for method in REQUIRED_BASELINES:
|
|
if method not in methods_dict:
|
|
_append_issue(issues, "schema", f"Dataset '{ds}' validation missing method '{method}'")
|
|
continue
|
|
|
|
entry = methods_dict[method]
|
|
acc, nll = _unwrap_metrics(entry)
|
|
|
|
if acc is None or nll is None:
|
|
_append_issue(
|
|
issues,
|
|
"finite",
|
|
f"Dataset '{ds}' validation method '{method}' has missing or non-finite acc/nll",
|
|
)
|
|
else:
|
|
ds_val_metrics[method] = {"acc": acc, "nll": nll}
|
|
|
|
# Simplex weight checks for weighted methods (slsqp_weights, pso_weights)
|
|
if method in WEIGHTED_METHODS:
|
|
weights = entry.get("weights", entry.get("selected_weights")) if isinstance(entry, dict) else None
|
|
if not _validate_simplex_weights(weights):
|
|
_append_issue(
|
|
issues,
|
|
"weights",
|
|
f"Dataset '{ds}' validation method '{method}' has invalid simplex weights: {weights}",
|
|
)
|
|
|
|
# SLSQP solver success check
|
|
if method == "slsqp_weights":
|
|
solver_success = entry.get("success", entry.get("status") in (0, "success", True)) if isinstance(entry, dict) else False
|
|
if solver_success is False:
|
|
_append_issue(
|
|
issues,
|
|
"slsqp",
|
|
f"Dataset '{ds}' SLSQP solver failed (success=False)",
|
|
)
|
|
|
|
# PSO detailed run & seed verification
|
|
if method == "pso_weights":
|
|
seed_runs = entry.get("per_seed_runs") if isinstance(entry, dict) else None
|
|
if not isinstance(seed_runs, list) or len(seed_runs) != len(EXPECTED_SWARM_SEEDS):
|
|
_append_issue(
|
|
issues,
|
|
"schema",
|
|
f"Dataset '{ds}' PSO pso_weights per_seed_runs must contain {len(EXPECTED_SWARM_SEEDS)} seed runs",
|
|
)
|
|
else:
|
|
recorded_seeds: List[int] = []
|
|
run_times: List[float] = []
|
|
best_run_nll = float("inf")
|
|
best_run_entry: Optional[Dict[str, Any]] = None
|
|
|
|
for idx, run in enumerate(seed_runs):
|
|
if not isinstance(run, dict):
|
|
_append_issue(
|
|
issues, "schema", f"Dataset '{ds}' PSO seed run {idx} is non-dict"
|
|
)
|
|
continue
|
|
|
|
seed = run.get("seed")
|
|
if seed not in EXPECTED_SWARM_SEEDS:
|
|
_append_issue(
|
|
issues, "config", f"Dataset '{ds}' PSO seed run seed {seed} unexpected"
|
|
)
|
|
if isinstance(seed, int) and not isinstance(seed, bool):
|
|
recorded_seeds.append(seed)
|
|
|
|
queries = run.get("queries", run.get("total_queries"))
|
|
# PSO per-seed sample key: sample_evaluations, samples, or total_sample_evaluations
|
|
samples = run.get("sample_evaluations", run.get("samples", run.get("total_sample_evaluations")))
|
|
if queries != EXPECTED_QUERIES_PER_SEED:
|
|
_append_issue(
|
|
issues,
|
|
"accounting",
|
|
f"Dataset '{ds}' PSO seed {seed} queries must be {EXPECTED_QUERIES_PER_SEED}, got {queries}",
|
|
)
|
|
if samples != EXPECTED_SAMPLES_PER_SEED:
|
|
_append_issue(
|
|
issues,
|
|
"accounting",
|
|
f"Dataset '{ds}' PSO seed {seed} samples must be {EXPECTED_SAMPLES_PER_SEED}, got {samples}",
|
|
)
|
|
|
|
r_weights = run.get("weights")
|
|
if not _validate_simplex_weights(r_weights):
|
|
_append_issue(
|
|
issues,
|
|
"weights",
|
|
f"Dataset '{ds}' PSO seed {seed} weights invalid: {r_weights}",
|
|
)
|
|
|
|
w_time = run.get("wall_time_seconds", run.get("wall_time", run.get("time")))
|
|
if _is_finite_number(w_time):
|
|
run_times.append(float(w_time))
|
|
else:
|
|
_append_issue(
|
|
issues,
|
|
"finite",
|
|
f"Dataset '{ds}' PSO seed {seed} missing or non-finite wall_time_seconds",
|
|
)
|
|
|
|
r_acc, r_nll = _unwrap_metrics(run)
|
|
if r_nll is not None and r_nll < best_run_nll:
|
|
best_run_nll = r_nll
|
|
best_run_entry = run
|
|
|
|
if sorted(recorded_seeds) != EXPECTED_SWARM_SEEDS:
|
|
_append_issue(
|
|
issues,
|
|
"config",
|
|
f"Dataset '{ds}' PSO seed runs must contain each frozen seed exactly once; "
|
|
f"got {recorded_seeds}",
|
|
)
|
|
|
|
if run_times:
|
|
pso_wall_times[ds] = run_times
|
|
|
|
# Validate selected PSO top metrics/weights/seed equal best per-seed NLL record
|
|
if isinstance(entry, dict):
|
|
top_selected_seed = entry.get("selected_seed")
|
|
top_weights = entry.get("selected_weights", entry.get("weights"))
|
|
top_acc, top_nll = _unwrap_metrics(entry)
|
|
|
|
if best_run_entry is not None:
|
|
best_seed = best_run_entry.get("seed")
|
|
best_weights = best_run_entry.get("weights")
|
|
best_acc, _ = _unwrap_metrics(best_run_entry)
|
|
|
|
if top_selected_seed != best_seed:
|
|
_append_issue(
|
|
issues,
|
|
"consistency",
|
|
f"Dataset '{ds}' pso_weights selected_seed ({top_selected_seed}) != best seed ({best_seed})",
|
|
)
|
|
if top_weights != best_weights and not (
|
|
isinstance(top_weights, list)
|
|
and isinstance(best_weights, list)
|
|
and len(top_weights) == len(best_weights)
|
|
and all(math.isclose(a, b, abs_tol=1e-6) for a, b in zip(top_weights, best_weights))
|
|
):
|
|
_append_issue(
|
|
issues,
|
|
"consistency",
|
|
f"Dataset '{ds}' pso_weights weights disagree with best seed run weights",
|
|
)
|
|
if top_nll is not None and not math.isclose(top_nll, best_run_nll, abs_tol=1e-6, rel_tol=1e-5):
|
|
_append_issue(
|
|
issues,
|
|
"consistency",
|
|
f"Dataset '{ds}' pso_weights top NLL ({top_nll}) != best seed run NLL ({best_run_nll})",
|
|
)
|
|
if top_acc is not None and best_acc is not None and not math.isclose(top_acc, best_acc, abs_tol=1e-6, rel_tol=1e-5):
|
|
_append_issue(
|
|
issues,
|
|
"consistency",
|
|
f"Dataset '{ds}' pso_weights top acc ({top_acc}) != best seed run acc ({best_acc})",
|
|
)
|
|
|
|
val_metrics_by_dataset[ds] = ds_val_metrics
|
|
|
|
# 5. Development Hard Gates Recomputation
|
|
# Named booleans assess their own fields directly
|
|
dev_gates: Dict[str, bool] = {
|
|
"all_values_finite": len(issues["finite"]) == 0,
|
|
"simplex_tolerance": len(issues["weights"]) == 0,
|
|
"validation_pool_forward_passes_each_dataset": all(
|
|
val_cache_counts.get(ds, {}).get("pool_forward_passes") == 5
|
|
for ds in EXPECTED_DATASETS
|
|
),
|
|
"optimization_base_model_forward_passes": all(
|
|
val_cache_counts.get(ds, {}).get("base_cnn_forward_passes_during_optimization") == 0
|
|
for ds in EXPECTED_DATASETS
|
|
),
|
|
"official_test_data_loaded_before_freeze": pre_freeze_loaded_ok,
|
|
"official_test_evaluations_before_freeze": pre_freeze_evals_ok,
|
|
"query_and_sample_accounting_exact": len(issues["accounting"]) == 0,
|
|
"maximum_pso_nll_regression_vs_uniform": True,
|
|
"maximum_pso_accuracy_regression_vs_uniform_pp": True,
|
|
"pso_nll_below_reference_single": True,
|
|
"maximum_pso_nll_regression_vs_equal_budget_single": True,
|
|
"maximum_relative_pso_nll_gap_vs_slsqp": True,
|
|
"cross_dataset_mean_relative_pso_nll_reduction_vs_uniform_minimum": True,
|
|
"maximum_median_one_seed_pso_to_pool_training_wall_ratio": True,
|
|
}
|
|
|
|
# Evaluate metric-dependent development gates across datasets
|
|
rel_nll_reductions_vs_uniform: List[float] = []
|
|
pso_wall_ratios: Dict[str, float] = {}
|
|
|
|
for ds in EXPECTED_DATASETS:
|
|
m = val_metrics_by_dataset.get(ds, {})
|
|
pso_nll = m.get("pso_weights", {}).get("nll")
|
|
pso_acc = m.get("pso_weights", {}).get("acc")
|
|
unif_nll = m.get("uniform_ensemble", {}).get("nll")
|
|
unif_acc = m.get("uniform_ensemble", {}).get("acc")
|
|
ref_nll = m.get("reference_single_10e", {}).get("nll")
|
|
s50_nll = m.get("single_50e", {}).get("nll")
|
|
slsqp_nll = m.get("slsqp_weights", {}).get("nll")
|
|
|
|
# Nominal gate booleans cannot stay True if required inputs are missing!
|
|
if any(v is None for v in (pso_nll, unif_nll, ref_nll, s50_nll, slsqp_nll, pso_acc, unif_acc)):
|
|
dev_gates["maximum_pso_nll_regression_vs_uniform"] = False
|
|
dev_gates["maximum_pso_accuracy_regression_vs_uniform_pp"] = False
|
|
dev_gates["pso_nll_below_reference_single"] = False
|
|
dev_gates["maximum_pso_nll_regression_vs_equal_budget_single"] = False
|
|
dev_gates["maximum_relative_pso_nll_gap_vs_slsqp"] = False
|
|
dev_gates["cross_dataset_mean_relative_pso_nll_reduction_vs_uniform_minimum"] = False
|
|
|
|
# Gate 8: maximum PSO NLL regression vs uniform <= 1e-7
|
|
if pso_nll is not None and unif_nll is not None:
|
|
if (pso_nll - unif_nll) > 1e-7:
|
|
dev_gates["maximum_pso_nll_regression_vs_uniform"] = False
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Dataset '{ds}' val PSO NLL ({pso_nll:.6f}) > uniform NLL ({unif_nll:.6f}) by > 1e-7",
|
|
)
|
|
rel_nll_reductions_vs_uniform.append((unif_nll - pso_nll) / unif_nll)
|
|
|
|
# Gate 9: maximum PSO accuracy regression vs uniform <= 0.10 pp
|
|
if pso_acc is not None and unif_acc is not None:
|
|
acc_diff_pp = _to_pp(unif_acc) - _to_pp(pso_acc)
|
|
if acc_diff_pp > 0.10:
|
|
dev_gates["maximum_pso_accuracy_regression_vs_uniform_pp"] = False
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Dataset '{ds}' val PSO acc regression vs uniform ({acc_diff_pp:.4f} pp) > 0.10 pp",
|
|
)
|
|
|
|
# Gate 10: PSO NLL strictly below reference single
|
|
if pso_nll is not None and ref_nll is not None:
|
|
if pso_nll >= ref_nll:
|
|
dev_gates["pso_nll_below_reference_single"] = False
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Dataset '{ds}' val PSO NLL ({pso_nll:.6f}) >= reference single NLL ({ref_nll:.6f})",
|
|
)
|
|
|
|
# Gate 11: maximum PSO NLL regression vs equal-budget 50e single <= 1e-7
|
|
if pso_nll is not None and s50_nll is not None:
|
|
if (pso_nll - s50_nll) > 1e-7:
|
|
dev_gates["maximum_pso_nll_regression_vs_equal_budget_single"] = False
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Dataset '{ds}' val PSO NLL ({pso_nll:.6f}) > 50e single NLL ({s50_nll:.6f}) by > 1e-7",
|
|
)
|
|
|
|
# Gate 12: maximum relative PSO NLL gap vs SLSQP <= 0.005
|
|
if pso_nll is not None and slsqp_nll is not None and slsqp_nll > 0:
|
|
rel_gap = (pso_nll - slsqp_nll) / slsqp_nll
|
|
if rel_gap > 0.005:
|
|
dev_gates["maximum_relative_pso_nll_gap_vs_slsqp"] = False
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Dataset '{ds}' val PSO NLL gap vs SLSQP ({rel_gap:.4%}) > 0.5%",
|
|
)
|
|
|
|
# Gate 14 wall time ratio accounting
|
|
if ds in pso_wall_times and ds in adam_pool_wall_times and adam_pool_wall_times[ds] > 0:
|
|
sorted_times = sorted(pso_wall_times[ds])
|
|
median_pso = sorted_times[len(sorted_times) // 2]
|
|
pso_wall_ratios[ds] = median_pso / adam_pool_wall_times[ds]
|
|
|
|
# Gate 13: cross-dataset mean relative PSO NLL reduction vs uniform >= 0.0
|
|
if rel_nll_reductions_vs_uniform:
|
|
mean_reduction = math.fsum(rel_nll_reductions_vs_uniform) / len(rel_nll_reductions_vs_uniform)
|
|
if mean_reduction < 0.0:
|
|
dev_gates["cross_dataset_mean_relative_pso_nll_reduction_vs_uniform_minimum"] = False
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Mean relative val PSO NLL reduction vs uniform ({mean_reduction:.4%}) < 0.0",
|
|
)
|
|
else:
|
|
dev_gates["cross_dataset_mean_relative_pso_nll_reduction_vs_uniform_minimum"] = False
|
|
|
|
# Gate 14: every workload must satisfy the frozen 10% wall-time ceiling.
|
|
res_totals = (
|
|
artifact.get("resource_totals")
|
|
if isinstance(artifact.get("resource_totals"), dict)
|
|
else {}
|
|
)
|
|
if set(pso_wall_ratios) != set(EXPECTED_DATASETS):
|
|
dev_gates["maximum_median_one_seed_pso_to_pool_training_wall_ratio"] = False
|
|
_append_issue(
|
|
issues,
|
|
"accounting",
|
|
"Cannot recompute a finite positive PSO/Adam wall ratio for every dataset",
|
|
)
|
|
else:
|
|
for ds, ratio in pso_wall_ratios.items():
|
|
if not math.isfinite(ratio) or ratio > 0.10:
|
|
dev_gates["maximum_median_one_seed_pso_to_pool_training_wall_ratio"] = False
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Dataset '{ds}' median PSO wall time to Adam pool wall ratio "
|
|
f"({ratio:.2%}) > 10%",
|
|
)
|
|
|
|
sorted_ratios = sorted(pso_wall_ratios.values())
|
|
mid = len(sorted_ratios) // 2
|
|
recomputed_ratio = (
|
|
sorted_ratios[mid]
|
|
if len(sorted_ratios) % 2
|
|
else 0.5 * (sorted_ratios[mid - 1] + sorted_ratios[mid])
|
|
)
|
|
reported_ratio = res_totals.get("pso_to_pool_wall_ratio")
|
|
if not _is_finite_number(reported_ratio) or not math.isclose(
|
|
float(reported_ratio),
|
|
recomputed_ratio,
|
|
abs_tol=1e-12,
|
|
rel_tol=1e-9,
|
|
):
|
|
dev_gates["maximum_median_one_seed_pso_to_pool_training_wall_ratio"] = False
|
|
_append_issue(
|
|
issues,
|
|
"accounting",
|
|
"resource_totals.pso_to_pool_wall_ratio does not match the "
|
|
f"per-dataset recomputation ({recomputed_ratio:.12f}); got {reported_ratio}",
|
|
)
|
|
|
|
# Check structural/config/accounting/leakage/SLSQP/weights/finite/tuning errors
|
|
dev_has_structural_errors = (
|
|
len(issues["schema"]) > 0
|
|
or len(issues["config"]) > 0
|
|
or len(issues["finite"]) > 0
|
|
or len(issues["weights"]) > 0
|
|
or len(issues["accounting"]) > 0
|
|
or len(issues["leakage"]) > 0
|
|
or len(issues["tuning"]) > 0
|
|
or len(issues["slsqp"]) > 0
|
|
or len(issues["consistency"]) > 0
|
|
)
|
|
|
|
development_pass = all(dev_gates.values()) and not dev_has_structural_errors
|
|
|
|
# 6. Confirmation Phase Verification
|
|
official_test_data_loaded = artifact.get("official_test_data_loaded")
|
|
confirmation_pass = False
|
|
conf_gates: Optional[Dict[str, bool]] = None
|
|
|
|
if not development_pass:
|
|
if official_test_data_loaded is not False and official_test_data_loaded is True:
|
|
_append_issue(
|
|
issues,
|
|
"leakage",
|
|
"official_test_data_loaded must be False when development fails",
|
|
)
|
|
|
|
conf_improper = False
|
|
for ds in EXPECTED_DATASETS:
|
|
wl = workloads.get(ds) if isinstance(workloads.get(ds), dict) else {}
|
|
conf_wl = wl.get("confirmation")
|
|
if conf_wl is not None and conf_wl != {}:
|
|
conf_improper = True
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Dataset '{ds}' confirmation present despite development failure",
|
|
)
|
|
|
|
if conf_improper or (official_test_data_loaded is not False and official_test_data_loaded is True):
|
|
conf_gates = {"confirmation_absent_when_dev_failed": False}
|
|
else:
|
|
conf_gates = None
|
|
else: # development_pass is True
|
|
if official_test_data_loaded is not True:
|
|
_append_issue(
|
|
issues,
|
|
"leakage",
|
|
"official_test_data_loaded must be True when development passed",
|
|
)
|
|
|
|
conf_missing = False
|
|
for ds in EXPECTED_DATASETS:
|
|
wl = workloads.get(ds) if isinstance(workloads.get(ds), dict) else {}
|
|
conf_wl = wl.get("confirmation")
|
|
if conf_wl is None or not isinstance(conf_wl, dict) or conf_wl == {}:
|
|
conf_missing = True
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Dataset '{ds}' confirmation missing when development passed",
|
|
)
|
|
|
|
if conf_missing or official_test_data_loaded is not True:
|
|
conf_gates = {"confirmation_present_and_loaded": False}
|
|
else:
|
|
conf_gates = {
|
|
"all_values_finite": True,
|
|
"official_test_dataset_loads_each_dataset": True,
|
|
"official_test_pool_forward_passes_each_dataset": True,
|
|
"official_test_long_single_forward_passes_each_dataset": True,
|
|
"frozen_policy_consistency": artifact.get("policy_frozen") is True,
|
|
"maximum_pso_accuracy_regression_vs_uniform_pp": True,
|
|
"pso_nll_below_reference_single": True,
|
|
"maximum_pso_nll_regression_vs_equal_budget_single": True,
|
|
"post_test_tuning_or_reruns": artifact.get("post_test_tuning_or_reruns", 0) == 0,
|
|
}
|
|
if not conf_gates["frozen_policy_consistency"]:
|
|
_append_issue(
|
|
issues,
|
|
"leakage",
|
|
"policy_frozen must be True before official confirmation",
|
|
)
|
|
|
|
for ds in EXPECTED_DATASETS:
|
|
wl = workloads.get(ds, {})
|
|
conf_wl = wl.get("confirmation", {})
|
|
val_methods = wl.get("validation", {}).get("methods", {})
|
|
frozen_methods = conf_wl.get("frozen_methods")
|
|
expected_pso = val_methods.get("pso_weights", {})
|
|
expected_slsqp = val_methods.get("slsqp_weights", {})
|
|
expected_temp = val_methods.get("uniform_temperature", {})
|
|
frozen_ok = (
|
|
isinstance(frozen_methods, dict)
|
|
and frozen_methods.get("selected_pso_seed")
|
|
== expected_pso.get("selected_seed")
|
|
and _sequences_close(
|
|
frozen_methods.get("selected_pso_weights"),
|
|
expected_pso.get("selected_weights"),
|
|
)
|
|
and _sequences_close(
|
|
frozen_methods.get("slsqp_weights"),
|
|
expected_slsqp.get("weights"),
|
|
)
|
|
and _is_finite_number(frozen_methods.get("fitted_temperature"))
|
|
and _is_finite_number(expected_temp.get("fitted_temperature"))
|
|
and math.isclose(
|
|
float(frozen_methods["fitted_temperature"]),
|
|
float(expected_temp["fitted_temperature"]),
|
|
abs_tol=1e-6,
|
|
rel_tol=1e-6,
|
|
)
|
|
)
|
|
if not frozen_ok:
|
|
conf_gates["frozen_policy_consistency"] = False
|
|
_append_issue(
|
|
issues,
|
|
"consistency",
|
|
f"Dataset '{ds}' confirmation frozen_methods do not match "
|
|
"the validation-frozen PSO seed/weights, SLSQP weights, and temperature",
|
|
)
|
|
|
|
# Per-workload confirmation cache is test_cache_counts with dataset_loads, pool_forward_passes, long_single_forward_passes
|
|
c_cache = conf_wl.get("test_cache_counts") if isinstance(conf_wl.get("test_cache_counts"), dict) else conf_wl.get("cache", conf_wl)
|
|
t_loads = c_cache.get("dataset_loads", c_cache.get("official_test_dataset_loads"))
|
|
t_pool_passes = c_cache.get("pool_forward_passes", c_cache.get("official_test_pool_forward_passes"))
|
|
t_single_passes = c_cache.get("long_single_forward_passes", c_cache.get("official_test_long_single_forward_passes"))
|
|
|
|
if t_loads != 1:
|
|
conf_gates["official_test_dataset_loads_each_dataset"] = False
|
|
_append_issue(issues, "accounting", f"Dataset '{ds}' test dataset_loads must be 1, got {t_loads}")
|
|
if t_pool_passes != 5:
|
|
conf_gates["official_test_pool_forward_passes_each_dataset"] = False
|
|
_append_issue(issues, "accounting", f"Dataset '{ds}' test pool_forward_passes must be 5, got {t_pool_passes}")
|
|
if t_single_passes != 1:
|
|
conf_gates["official_test_long_single_forward_passes_each_dataset"] = False
|
|
_append_issue(issues, "accounting", f"Dataset '{ds}' test long_single_forward_passes must be 1, got {t_single_passes}")
|
|
|
|
# Test methods dict verification
|
|
methods_dict = conf_wl.get("methods") if isinstance(conf_wl.get("methods"), dict) else conf_wl
|
|
if not isinstance(methods_dict, dict):
|
|
conf_gates["all_values_finite"] = False
|
|
_append_issue(issues, "schema", f"Dataset '{ds}' confirmation methods must be a dictionary")
|
|
methods_dict = {}
|
|
|
|
present_methods = set(methods_dict.keys())
|
|
if present_methods != REQUIRED_BASELINES_SET:
|
|
conf_gates["all_values_finite"] = False
|
|
_append_issue(
|
|
issues,
|
|
"schema",
|
|
f"Dataset '{ds}' confirmation methods set {present_methods} does not match required {REQUIRED_BASELINES_SET}",
|
|
)
|
|
|
|
ds_test_metrics: Dict[str, Dict[str, float]] = {}
|
|
for method in REQUIRED_BASELINES:
|
|
if method not in methods_dict:
|
|
conf_gates["all_values_finite"] = False
|
|
_append_issue(issues, "schema", f"Dataset '{ds}' confirmation missing method '{method}'")
|
|
continue
|
|
|
|
entry = methods_dict[method]
|
|
acc, nll = _unwrap_metrics(entry)
|
|
|
|
if acc is None or nll is None:
|
|
conf_gates["all_values_finite"] = False
|
|
_append_issue(
|
|
issues,
|
|
"finite",
|
|
f"Dataset '{ds}' confirmation method '{method}' has missing or non-finite acc/nll",
|
|
)
|
|
else:
|
|
ds_test_metrics[method] = {"acc": acc, "nll": nll}
|
|
|
|
test_metrics_by_dataset[ds] = ds_test_metrics
|
|
|
|
pso_test_nll = ds_test_metrics.get("pso_weights", {}).get("nll")
|
|
pso_test_acc = ds_test_metrics.get("pso_weights", {}).get("acc")
|
|
unif_test_acc = ds_test_metrics.get("uniform_ensemble", {}).get("acc")
|
|
ref_test_nll = ds_test_metrics.get("reference_single_10e", {}).get("nll")
|
|
s50_test_nll = ds_test_metrics.get("single_50e", {}).get("nll")
|
|
|
|
if any(v is None for v in (pso_test_nll, pso_test_acc, unif_test_acc, ref_test_nll, s50_test_nll)):
|
|
conf_gates["all_values_finite"] = False
|
|
conf_gates["maximum_pso_accuracy_regression_vs_uniform_pp"] = False
|
|
conf_gates["pso_nll_below_reference_single"] = False
|
|
conf_gates["maximum_pso_nll_regression_vs_equal_budget_single"] = False
|
|
|
|
if pso_test_acc is not None and unif_test_acc is not None:
|
|
diff_pp = _to_pp(unif_test_acc) - _to_pp(pso_test_acc)
|
|
if diff_pp > 0.20:
|
|
conf_gates["maximum_pso_accuracy_regression_vs_uniform_pp"] = False
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Dataset '{ds}' test PSO acc regression vs uniform ({diff_pp:.4f} pp) > 0.20 pp",
|
|
)
|
|
|
|
if pso_test_nll is not None and ref_test_nll is not None:
|
|
if pso_test_nll >= ref_test_nll:
|
|
conf_gates["pso_nll_below_reference_single"] = False
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Dataset '{ds}' test PSO NLL ({pso_test_nll:.6f}) >= reference single NLL ({ref_test_nll:.6f})",
|
|
)
|
|
|
|
if pso_test_nll is not None and s50_test_nll is not None:
|
|
if (pso_test_nll - s50_test_nll) > 1e-7:
|
|
conf_gates["maximum_pso_nll_regression_vs_equal_budget_single"] = False
|
|
_append_issue(
|
|
issues,
|
|
"gates",
|
|
f"Dataset '{ds}' test PSO NLL ({pso_test_nll:.6f}) > 50e single NLL ({s50_test_nll:.6f}) by > 1e-7",
|
|
)
|
|
|
|
confirmation_pass = all(conf_gates.values())
|
|
|
|
# 7. Failed Gate Counting & Numeric Score Calculation
|
|
# Count structural/config/accounting/leakage/SLSQP/weights/finite/tuning errors as hard failures
|
|
structural_issue_count = sum(len(lst) for lst in issues.values())
|
|
failed_dev_gates = sum(1 for v in dev_gates.values() if not v)
|
|
failed_conf_gates = (
|
|
sum(1 for v in conf_gates.values() if not v) if conf_gates is not None else (1 if development_pass else 0)
|
|
)
|
|
failed_hard_gate_count = max(failed_dev_gates + failed_conf_gates, structural_issue_count)
|
|
|
|
# Calculate validation score metrics
|
|
val_rel_nll_reductions: List[float] = []
|
|
val_acc_gains_pp: List[float] = []
|
|
test_rel_nll_reductions: List[float] = []
|
|
test_acc_gains_pp: List[float] = []
|
|
|
|
for ds in EXPECTED_DATASETS:
|
|
m_val = val_metrics_by_dataset.get(ds, {})
|
|
pso_v_nll = m_val.get("pso_weights", {}).get("nll")
|
|
pso_v_acc = m_val.get("pso_weights", {}).get("acc")
|
|
s50_v_nll = m_val.get("single_50e", {}).get("nll")
|
|
s50_v_acc = m_val.get("single_50e", {}).get("acc")
|
|
|
|
if pso_v_nll is not None and s50_v_nll is not None and s50_v_nll > 0:
|
|
val_rel_nll_reductions.append((s50_v_nll - pso_v_nll) / s50_v_nll)
|
|
if pso_v_acc is not None and s50_v_acc is not None:
|
|
val_acc_gains_pp.append(_to_pp(pso_v_acc) - _to_pp(s50_v_acc))
|
|
|
|
m_test = test_metrics_by_dataset.get(ds, {})
|
|
pso_t_nll = m_test.get("pso_weights", {}).get("nll")
|
|
pso_t_acc = m_test.get("pso_weights", {}).get("acc")
|
|
s50_t_nll = m_test.get("single_50e", {}).get("nll")
|
|
s50_t_acc = m_test.get("single_50e", {}).get("acc")
|
|
|
|
if pso_t_nll is not None and s50_t_nll is not None and s50_t_nll > 0:
|
|
test_rel_nll_reductions.append((s50_t_nll - pso_t_nll) / s50_t_nll)
|
|
if pso_t_acc is not None and s50_t_acc is not None:
|
|
test_acc_gains_pp.append(_to_pp(pso_t_acc) - _to_pp(s50_t_acc))
|
|
|
|
mean_val_rel_nll = (
|
|
math.fsum(val_rel_nll_reductions) / len(val_rel_nll_reductions) if val_rel_nll_reductions else 0.0
|
|
)
|
|
mean_val_acc_gain = (
|
|
math.fsum(val_acc_gains_pp) / len(val_acc_gains_pp) if val_acc_gains_pp else 0.0
|
|
)
|
|
|
|
mean_test_rel_nll = (
|
|
math.fsum(test_rel_nll_reductions) / len(test_rel_nll_reductions) if test_rel_nll_reductions else None
|
|
)
|
|
mean_test_acc_gain = (
|
|
math.fsum(test_acc_gains_pp) / len(test_acc_gains_pp) if test_acc_gains_pp else None
|
|
)
|
|
|
|
if confirmation_pass and mean_test_rel_nll is not None and mean_test_acc_gain is not None:
|
|
raw_score = 100.0 * mean_test_rel_nll + mean_test_acc_gain
|
|
else:
|
|
raw_score = 100.0 * mean_val_rel_nll + mean_val_acc_gain
|
|
|
|
score = float(raw_score - 1000.0 * failed_hard_gate_count)
|
|
if not math.isfinite(score):
|
|
score = -1000.0 * float(failed_hard_gate_count if failed_hard_gate_count > 0 else 1)
|
|
|
|
overall_pass = development_pass and confirmation_pass and (failed_hard_gate_count == 0)
|
|
|
|
return {
|
|
"evaluator_version": EVALUATOR_VERSION,
|
|
"pass": overall_pass,
|
|
"score": score,
|
|
"development_pass": development_pass,
|
|
"confirmation_pass": confirmation_pass,
|
|
"failed_hard_gate_count": failed_hard_gate_count,
|
|
"issues": issues,
|
|
"development_gates": dev_gates,
|
|
"confirmation_gates": conf_gates,
|
|
"metrics": {
|
|
"mean_val_relative_nll_reduction_vs_equal_budget_single": mean_val_rel_nll,
|
|
"mean_val_accuracy_gain_vs_equal_budget_single_pp": mean_val_acc_gain,
|
|
"mean_test_relative_nll_reduction_vs_equal_budget_single": mean_test_rel_nll,
|
|
"mean_test_accuracy_gain_vs_equal_budget_single_pp": mean_test_acc_gain,
|
|
},
|
|
}
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(
|
|
description="Strict Evaluator for Post-Training PSO Ensemble Study"
|
|
)
|
|
parser.add_argument("--artifact", type=Path, required=True, help="Path to study artifact JSON")
|
|
parser.add_argument("--output", type=Path, required=True, help="Path to output evaluation JSON")
|
|
args = parser.parse_args()
|
|
|
|
if not args.artifact.is_file():
|
|
print(f"Error: Artifact file not found at '{args.artifact}'", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
try:
|
|
with args.artifact.open("r", encoding="utf-8") as f:
|
|
artifact = json.load(f)
|
|
except Exception as exc:
|
|
print(f"Error reading artifact JSON: {exc}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
eval_result = evaluate_artifact(artifact)
|
|
|
|
save_json_atomic(eval_result, args.output)
|
|
print(
|
|
f"Evaluation complete. Pass: {eval_result['pass']}, Score: {eval_result['score']:.6f}, Failed Gates: {eval_result['failed_hard_gate_count']}"
|
|
)
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|