Paya / code /paya /calibrate.py
yeabwang's picture
Paya model bundle and revised model card
534cc76
Raw History Blame Contribute Delete
3.15 kB
"""Fit a global probability temperature on held-out outcomes."""
import argparse
import json
import math
from pathlib import Path
from scipy.optimize import minimize_scalar
import yaml
from .checkpoint import learned_digest, load_checkpoint, save_checkpoint
from .data import ChoiceDataset, assert_disjoint
from .evaluate import collect, metrics
from .features import open_feature_cache
from .model import PayaModel
def fit_temperature(model, dataset, forbidden, batch_size=8, cache=None, output_path=None):
if "datasets" in forbidden:
for reference in forbidden["datasets"]:
previous=ChoiceDataset(reference["path"])
if previous.sha256 != reference["sha256"]:
raise ValueError("Training/selection dataset changed since fitting")
try:
assert_disjoint(previous,dataset)
except ValueError as error:
raise ValueError(f"Calibration overlaps training/selection: {error}") from error
else:
for field in ("source_id", "group_id", "recording_id"):
for identifier in forbidden.get(field, []):
if dataset.db.execute('SELECT 1 FROM audit WHERE field=? AND value=?',(field,identifier)).fetchone():
raise ValueError(f"Calibration overlaps training/selection on {field}")
records = collect(model, dataset, batch_size, cache, output_path=output_path)
before = metrics(records)
objective = lambda log_t: metrics(records, math.exp(log_t))["nll"]
result = minimize_scalar(objective, bounds=(-4, 4), method="bounded")
value = math.exp(result.x) if result.success and result.fun < before["nll"] else 1.0
model.temperature.fill_(value)
model.calibration = {"learned_sha256": learned_digest(model), "temperature": float(model.temperature),
"dataset_sha256": dataset.sha256, "count": len(records),
"before": before, "after": metrics(records, float(model.temperature))}
return model.calibration
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--config", type=Path, default=Path("config/paya.yaml"))
parser.add_argument("--checkpoint", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--device", default="cpu")
args = parser.parse_args()
config = yaml.safe_load(args.config.read_text(encoding="utf-8"))
model = PayaModel.from_assets(config["assets"], config.get("preparation"), config.get("head"), args.device)
payload = load_checkpoint(args.checkpoint, model)
if not payload["training"]:
raise ValueError("Calibration requires a retrained decision checkpoint")
dataset = ChoiceDataset(config["data"]["calibration"])
cache = open_feature_cache(config)
fitted = fit_temperature(model, dataset, payload["training"]["forbidden_calibration"],
config.get("batch_size", 8), cache, args.output.with_suffix('.predictions.jsonl'))
save_checkpoint(args.output, model, payload["training"])
print(json.dumps(fitted, indent=2))