Skip to content

Commit cf63dee

Browse files
ci: align CV score and Ridge reference semantics
1 parent 242a908 commit cf63dee

1 file changed

Lines changed: 87 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: Temporary CV score alignment
2+
3+
on:
4+
push:
5+
branches:
6+
- agent/canonical-cv-benchmark-source
7+
paths:
8+
- .github/workflows/_temporary-cv-score-alignment.yml
9+
10+
permissions:
11+
contents: write
12+
13+
jobs:
14+
patch:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
with:
19+
ref: agent/canonical-cv-benchmark-source
20+
fetch-depth: 0
21+
- uses: actions/setup-python@v5
22+
with:
23+
python-version: '3.11'
24+
- name: Patch score and parameter mappings
25+
run: |
26+
python - <<'PY'
27+
from pathlib import Path
28+
29+
external = Path('dev/benchmarks/cv_external.py')
30+
text = external.read_text(encoding='utf-8')
31+
replacements = [
32+
(
33+
'def _candidate_values(spec: Any) -> list[dict[str, Any]]:\n',
34+
'def _candidate_values(spec: Any, n_samples: int) -> list[dict[str, Any]]:\n',
35+
),
36+
(
37+
' return [{"alpha": float(value)} for value in grid["alphas"]]\n',
38+
' return [{"alpha": float(value), "sklearn_alpha": float(value) * n_samples} for value in grid["alphas"]]\n',
39+
),
40+
(
41+
' return Ridge(alpha=candidate["alpha"], fit_intercept=True)\n',
42+
' return Ridge(alpha=candidate["sklearn_alpha"], fit_intercept=True)\n',
43+
),
44+
(
45+
' candidates = _candidate_values(spec)\n',
46+
' candidates = _candidate_values(spec, n_samples)\n',
47+
),
48+
(
49+
' "candidate_count": len(_candidate_values(spec)),\n',
50+
' "candidate_count": len(_candidate_values(spec, n_samples)),\n',
51+
),
52+
]
53+
for old, new in replacements:
54+
count = text.count(old)
55+
if count != 1:
56+
raise SystemExit(f'external mapping marker mismatch {count}: {old!r}')
57+
text = text.replace(old, new)
58+
external.write_text(text, encoding='utf-8')
59+
60+
runner = Path('dev/benchmarks/benchmark_cv_models.py')
61+
text = runner.read_text(encoding='utf-8')
62+
start = text.index('def _score_model(')
63+
end = text.index('\n\ndef _selected_parameters', start)
64+
replacement = '''def _to_numpy(value):\n import numpy as np\n module = type(value).__module__.split(".")[0]\n if module == "cupy":\n import cupy as cp\n return cp.asnumpy(value)\n if module == "torch":\n return value.detach().cpu().numpy()\n return np.asarray(value)\n\n\ndef _score_model(model, spec: CaseSpec, X_test, target_test) -> float:\n import numpy as np\n\n if spec.task == "regression":\n prediction = _to_numpy(model.predict(X_test)).reshape(-1)\n target = _to_numpy(target_test).reshape(-1)\n residual = prediction - target\n return float(np.mean(residual * residual))\n\n if spec.task == "classification":\n target = _to_numpy(target_test).astype(int).reshape(-1)\n if hasattr(model, "predict_proba"):\n probability = _to_numpy(model.predict_proba(X_test))\n if probability.ndim == 2:\n probability = probability[:, 1]\n else:\n decision = _to_numpy(model.decision_function(X_test)).reshape(-1)\n probability = 1.0 / (1.0 + np.exp(-decision))\n probability = np.clip(np.asarray(probability).reshape(-1), 1e-15, 1.0 - 1e-15)\n return float(-np.mean(target * np.log(probability) + (1 - target) * np.log(1 - probability)))\n\n time_value, event = target_test\n from statgpu.survival._cox_cv import _compute_partial_likelihood\n return float(\n _compute_partial_likelihood(\n _to_numpy(X_test),\n _to_numpy(time_value),\n _to_numpy(event),\n _to_numpy(model.coef_),\n ties=str(spec.grid_parameters.get("ties", "breslow")),\n )\n )\n'''
65+
text = text[:start] + replacement + text[end:]
66+
67+
start = text.index('def _best_score(')
68+
end = text.index('\n\ndef _run_once', start)
69+
replacement = '''def _validation_score(model, spec: CaseSpec) -> float:\n for name in ("best_score_", "best_cv_score_", "cv_score_"):\n value = getattr(model, name, None)\n if value is not None:\n score = float(value)\n if spec.scoring_direction == "minimize" and score < 0.0:\n score = -score\n return score\n raise RuntimeError("fitted CV estimator did not expose a validation score")\n'''
70+
text = text[:start] + replacement + text[end:]
71+
old = ' "validation_score": _best_score(model),\n'
72+
new = ' "validation_score": _validation_score(model, spec),\n'
73+
if text.count(old) != 1:
74+
raise SystemExit('validation score call marker mismatch')
75+
text = text.replace(old, new)
76+
runner.write_text(text, encoding='utf-8')
77+
PY
78+
- name: Validate and commit
79+
run: |
80+
python -m py_compile dev/benchmarks/benchmark_cv_models.py dev/benchmarks/cv_external.py
81+
python -m pip install pytest 'jsonschema[format]'
82+
pytest dev/tests/test_benchmark_cv_source.py dev/tests/test_cv_runner_instrumentation.py -q
83+
git config user.name "github-actions[bot]"
84+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
85+
git add dev/benchmarks/benchmark_cv_models.py dev/benchmarks/cv_external.py
86+
git commit -m "benchmark: align CV score and Ridge reference semantics"
87+
git push origin HEAD:agent/canonical-cv-benchmark-source

0 commit comments

Comments
 (0)