textmachine/eval/editor_harness/judge_variance.py

81 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Разброс СУДЬИ на повторе одной и той же клетки — последняя незакрытая дыра методики.
Проблема. Во всех раундах судейства каждая пара текстов судилась ровно один раз на раскладку.
Значит любое расхождение между армами нельзя отделить от собственного шума судьи: если он на
ОДНОМ И ТОМ ЖЕ входе отвечает по-разному, то «связка 6:4» может целиком лежать внутри его дисперсии.
Отчёт это признаёт («разброс судьи на повторе одной клетки не мерен ни разу»), но не измеряет.
Здесь: те же пакеты, та же ориентация, N независимых голосов. Меряется доля клеток, где все N
голосов совпали. Это ВЕРХНЯЯ граница разрешающей способности всей судейской методики: разница между
армами меньше собственного разброса судьи не может считаться сигналом.
Температура намеренно та же, что в основном судействе, — меряется разброс РЕАЛЬНОГО инструмента,
а не гипотетического детерминированного.
"""
from __future__ import annotations
import json
import re
import sys
import time
from collections import Counter
from pathlib import Path
REPO = Path("/home/ubuntu/projects/textmachine")
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(REPO / "eval" / "editor_contract"))
sys.path.insert(0, str(REPO / "eval" / "bank_arbitration"))
sys.path.insert(0, str(HERE))
import probe as Q # noqa: E402
import judge_crossmodel as J # noqa: E402
PACK = Path.home() / "books" / "gu-zhenren" / "editor-harness" / "judge-reflow"
REPS = 3
def main(model: str, ms: str) -> None:
nothink = Q.MODELS[model][5] in ("disable", "effort_none")
votes = {}
spent = 0.0
for k in range(6):
p = PACK / f"packet-{ms}-w{k}.md"
if not p.exists():
continue
body = p.read_text(encoding="utf-8")
for r in range(1, REPS + 1):
rec = Q.call(model, [{"role": "system", "content": J.RUBRIC},
{"role": "user", "content": body}],
f"jv-{ms}-w{k}-r{r}", nothink)
spent += rec["cost_usd"]
votes.setdefault(k, []).append(J.parse(rec["content"]))
time.sleep(0.2)
print(f"\nРАЗБРОС СУДЬИ {model} на {REPS} повторах одного входа (модель материала {ms})\n")
print(f"{'ось':11s}{'клеток':>8s}{'единогласно':>13s}{'доля':>8s}")
print("-" * 42)
total_cells = total_unan = 0
for ax in J.AXES:
unan = cells = 0
for k, vs in votes.items():
picks = [v.get(ax) for v in vs if v.get(ax)]
if len(picks) < REPS:
continue
cells += 1
unan += int(len(set(picks)) == 1)
if cells:
print(f"{ax:11s}{cells:8d}{unan:13d}{unan / cells:8.0%}")
total_cells += cells
total_unan += unan
if total_cells:
print(f"\nИТОГО единогласно {total_unan}/{total_cells} = {total_unan / total_cells:.0%}")
print("⇒ разница между армами МЕНЬШЕ этого разброса сигналом считаться не может.")
print(f"потрачено ${spent:.6f}")
for k, vs in sorted(votes.items()):
c = Counter(v.get("общий") for v in vs)
print(f" w{k} общий вердикт по повторам: {dict(c)}")
if __name__ == "__main__":
main(sys.argv[1] if len(sys.argv) > 1 else "deepseek-v4-pro",
sys.argv[2] if len(sys.argv) > 2 else "dp")