Download experiment_utils.py from Crystalbullet/lunar_project: direct link, hf CLI and curl.
- Browser
- Download file 3.65 kB
-
https://huggingface.co/Crystalbullet/lunar_project/resolve/main/experiment_utils.py
- Command line
-
hf download hf://Crystalbullet/lunar_project/experiment_utils.py
-
curl -L -o experiment_utils.py https://huggingface.co/Crystalbullet/lunar_project/resolve/main/experiment_utils.py
3.65 kB
| """Small persistence/metrics primitives shared by the audited experiment runner.""" | |
| import hashlib | |
| import json | |
| import os | |
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| from sklearn.metrics import balanced_accuracy_score, confusion_matrix, log_loss, roc_auc_score | |
| ROOT = Path(__file__).resolve().parent | |
| DEFAULT_EXPERIMENT = ROOT / 'runs' / 'phasewise_v3' | |
| def sha(data): | |
| return hashlib.sha256(data).hexdigest() | |
| def fingerprint(value): | |
| return sha(json.dumps(value, sort_keys=True, default=str).encode()) | |
| def atomic_json(path, value): | |
| path = Path(path) | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| temp = path.with_suffix(path.suffix + '.tmp') | |
| def clean(item): | |
| if isinstance(item, dict): return {k:clean(v) for k,v in item.items()} | |
| if isinstance(item, (list,tuple)): return [clean(v) for v in item] | |
| if isinstance(item, (float,np.floating)) and not np.isfinite(item): return None | |
| return item | |
| temp.write_text(json.dumps(clean(value), indent=2, default=str, allow_nan=False), encoding='utf-8') | |
| os.replace(temp, path) | |
| def atomic_csv(path, frame): | |
| path = Path(path) | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| temp = path.with_suffix(path.suffix + '.tmp') | |
| frame.to_csv(temp, index=False) | |
| os.replace(temp, path) | |
| def threshold_for(y, p): | |
| """O(n log n) exact sweep. Ties prefer the threshold closest to 0.5.""" | |
| y, p = np.asarray(y, dtype=int), np.asarray(p, dtype=float) | |
| if set(y) != {0, 1} or not np.isfinite(p).all(): | |
| raise ValueError('Threshold calibration needs both classes and finite probabilities.') | |
| order = np.argsort(p, kind='stable') | |
| y, p = y[order], p[order] | |
| starts = np.r_[0, np.flatnonzero(np.diff(p)) + 1] | |
| candidates = np.r_[p[starts], np.nextafter(p[-1], np.inf), 0.5] | |
| left = np.searchsorted(p, candidates, side='left') | |
| pos = np.r_[0, np.cumsum(y)] | |
| neg = np.r_[0, np.cumsum(1-y)] | |
| scores = 0.5 * ((pos[-1]-pos[left])/pos[-1] + neg[left]/neg[-1]) | |
| tied = np.flatnonzero(np.isclose(scores, scores.max(), rtol=0, atol=1e-12)) | |
| best = tied[np.argmin(np.abs(candidates[tied] - 0.5))] | |
| return float(candidates[best]), float(scores[best]) | |
| def metrics(y, p, threshold=0.5): | |
| y, p = np.asarray(y, dtype=int), np.asarray(p, dtype=float) | |
| if len(y) == 0 or not np.isfinite(p).all(): | |
| raise ValueError('Empty/nonfinite predictions.') | |
| pred = (p >= threshold).astype(int) | |
| tn, fp, fn, tp = confusion_matrix(y, pred, labels=[0, 1]).ravel() | |
| both = len(np.unique(y)) == 2 | |
| return dict(n=len(y), bacc=float(balanced_accuracy_score(y, pred)) if both else None, | |
| recall_0=float(tn/(tn+fp)) if tn+fp else None, | |
| recall_1=float(tp/(tp+fn)) if tp+fn else None, | |
| tn=int(tn), fp=int(fp), fn=int(fn), tp=int(tp), | |
| auc=float(roc_auc_score(y, p)) if both else None, | |
| log_loss=float(log_loss(y, np.clip(p, 1e-7, 1-1e-7), labels=[0, 1])), | |
| threshold=float(threshold)) | |
| def validate_submission(frame, ids): | |
| if frame.columns.tolist() != ['image_id', 'label']: | |
| raise ValueError('Submission must have exactly image_id,label.') | |
| if frame.image_id.tolist() != list(ids) or frame.image_id.duplicated().any(): | |
| raise ValueError('Submission image order/identity differs from evaluation metadata.') | |
| if frame.isna().any().any() or not pd.api.types.is_integer_dtype(frame.label): | |
| raise ValueError('Submission labels must be non-null integers.') | |
| if not set(frame.label).issubset({0, 1}): | |
| raise ValueError('Submission labels must be binary.') | |