SevdanurGenc's picture
Upload 300 files
9438a57 verified
Raw History Blame Contribute Delete
13 kB
"""Deney sonuçlarının istatistiksel analizi (yalnızca standart kütüphane).
* Öğe bazında tekrarların ortalaması alınır; karşılaştırmalar öğeler üzerinde eşleştirilmiş yapılır.
* Wilcoxon işaretli sıra testi: sıfır farklar atılır, bağlı sıralara ortalama sıra verilir; p değeri
n ≤ 25 için kesin dağılımdan (dinamik programlama), daha büyük n için normal yaklaşımla hesaplanır.
* Vargha–Delaney A12: B yapılandırmasının A'dan büyük değer üretme olasılığı (0,5 = fark yok;
|A12 − 0,5| ≥ 0,06 / 0,14 / 0,21 → küçük / orta / büyük etki).
"""
from __future__ import annotations
import math
from collections import defaultdict
from statistics import mean, stdev
from typing import Any, Iterable, Optional
from .benchmark import EvalState, ItemResult
METRICS = {
"final_compiled": "Derlenme (%)",
"final_pass_rate": "Geçen test (%)",
"final_coverage": "Satır kapsama (%)",
"mutation_score": "Mutasyon skoru (%)",
"seeded_rate": "Yerleştirilmiş hata yakalama (%)",
}
def _val(i: ItemResult, metric: str) -> Optional[float]:
if i.status != "done":
return None
if metric == "final_compiled":
return 100.0 if i.final_compiled else 0.0
if metric == "seeded_rate":
return 100.0 * i.seeded_caught / i.seeded_total if i.seeded_total else None
if metric == "first_seeded_rate":
return None
return getattr(i, metric)
def per_item(ev: EvalState, metric: str, reps: Optional[set[int]] = None) -> dict[str, float]:
"""Öğe → tekrarların ortalaması (değeri olmayan tekrarlar atlanır; `reps` verilirse yalnızca o tekrarlar)."""
acc: dict[str, list[float]] = defaultdict(list)
for i in ev.items:
if reps is not None and i.rep not in reps:
continue
v = _val(i, metric)
if v is not None:
acc[i.id].append(v)
return {k: mean(v) for k, v in acc.items() if v}
def rep_means(ev: EvalState, metric: str) -> list[float]:
"""Her tekrarın (tüm öğeler üzerinden) ortalaması — tekrarlar arası yayılım için."""
acc: dict[int, list[float]] = defaultdict(list)
for i in ev.items:
v = _val(i, metric)
if v is not None:
acc[i.rep].append(v)
return [mean(v) for _, v in sorted(acc.items()) if v]
def _ranks(values: list[float]) -> list[float]:
order = sorted(range(len(values)), key=lambda k: values[k])
ranks = [0.0] * len(values)
i = 0
while i < len(order):
j = i
while j + 1 < len(order) and values[order[j + 1]] == values[order[i]]:
j += 1
for k in range(i, j + 1):
ranks[order[k]] = (i + j) / 2 + 1
i = j + 1
return ranks
def wilcoxon(a: list[float], b: list[float]) -> dict[str, Any]:
"""Eşleştirilmiş iki taraflı Wilcoxon işaretli sıra testi (b − a)."""
d = [y - x for x, y in zip(a, b) if y - x != 0]
n = len(d)
if n == 0:
return {"n": 0, "w_plus": 0.0, "p": 1.0}
r = _ranks([abs(x) for x in d])
w_plus = sum(rk for rk, x in zip(r, d) if x > 0)
if n <= 25:
# sıraları ikiyle çarpıp tam sayıya çevir (bağlı sıralar .5 olabilir) ve W+ dağılımını say
r2 = [int(round(2 * x)) for x in r]
total = sum(r2)
counts = [0] * (total + 1)
counts[0] = 1
for x in r2:
for s in range(total, x - 1, -1):
counts[s] += counts[s - x]
w2 = int(round(2 * w_plus))
denom = 2 ** n
lo = sum(counts[: w2 + 1]) / denom
hi = sum(counts[w2:]) / denom
p = min(1.0, 2 * min(lo, hi))
else:
mu = n * (n + 1) / 4
ties: dict[float, int] = defaultdict(int)
for rk in r:
ties[rk] += 1
sigma = math.sqrt(n * (n + 1) * (2 * n + 1) / 24 - sum(t ** 3 - t for t in ties.values()) / 48)
z = (w_plus - mu) / sigma if sigma else 0.0
p = math.erfc(abs(z) / math.sqrt(2))
return {"n": n, "w_plus": w_plus, "p": round(p, 4)}
def a12(a: Iterable[float], b: Iterable[float]) -> Optional[float]:
"""Vargha–Delaney A12: P(B > A) + 0,5·P(B = A)."""
a, b = list(a), list(b)
if not a or not b:
return None
gt = sum(1 for y in b for x in a if y > x)
eq = sum(1 for y in b for x in a if y == x)
return round((gt + 0.5 * eq) / (len(a) * len(b)), 3)
def magnitude(v: Optional[float]) -> str:
if v is None:
return "—"
d = abs(v - 0.5)
return "ihmal" if d < 0.06 else "küçük" if d < 0.14 else "orta" if d < 0.21 else "büyük"
def compare(a: EvalState, b: EvalState, metric: str) -> dict[str, Any]:
# Tekrar sayıları farklıysa (ör. 3 tekrarlı tam yapılandırma ↔ tek tekrarlı ablasyon) yalnızca ortak
# tekrar numaraları karşılaştırılır; böylece iki taraf aynı sayıda örnekle temsil edilir.
ra, rb = {i.rep for i in a.items}, {i.rep for i in b.items}
reps = None if ra == rb else (ra & rb)
pa, pb = per_item(a, metric, reps), per_item(b, metric, reps)
common = sorted(set(pa) & set(pb))
xa, xb = [pa[k] for k in common], [pb[k] for k in common]
w = wilcoxon(xa, xb)
eff = a12(xa, xb)
return {"metric": metric, "items": len(common), "mean_a": round(mean(xa), 1) if xa else None,
"mean_b": round(mean(xb), 1) if xb else None,
"delta": round(mean(xb) - mean(xa), 1) if xa else None,
"p": w["p"], "a12": eff, "effect": magnitude(eff)}
def _fmt(v: Optional[float], suf: str = "") -> str:
return "—" if v is None else f"{v:.1f}{suf}"
def _msd(vals: list[float]) -> str:
if not vals:
return "—"
return f"{mean(vals):.1f}" + (f" ± {stdev(vals):.1f}" if len(vals) > 1 else "")
def _gpu(done: list[ItemResult], lang: str = "tr") -> str:
vals = [i.gpu_percent for i in done if i.gpu_percent is not None]
if not vals:
return "—"
low = min(vals)
if lang == "en":
return f"{low}%" + ("" if low == 100 else " ⚠ some runs spilled to CPU")
return f"%{low}" + ("" if low == 100 else " ⚠ CPU'ya taşan koşu var")
METRICS_EN = {
"final_compiled": "Compiled (%)",
"final_pass_rate": "Passing tests (%)",
"final_coverage": "Line coverage (%)",
"mutation_score": "Mutation score (%)",
"seeded_rate": "Seeded-bug detection (%)",
}
EFFECT_EN = {"ihmal": "negligible", "küçük": "small", "orta": "medium", "büyük": "large", "—": "—"}
A_TEXT = {
"tr": {"title": "# Deney analizi",
"intro": "Değerler, tekrarların ortalaması ± tekrarlar arası standart sapmadır. Karşılaştırmalar öğe bazında "
"(tekrar ortalamaları) eşleştirilmiş iki taraflı Wilcoxon işaretli sıra testi ve Vargha–Delaney A12 "
"etki büyüklüğü ile yapılmıştır.",
"s1": "## 1. Yapılandırmalar", "s2": "## 2. Eşleştirilmiş karşılaştırmalar",
"s3": "## 3. Bölümlere göre: geliştirme (dev), ayrılmış test ve bağımsız (QuixBugs)",
"sum_head": "| Yapılandırma | Model | Tur | Ablasyon | Bölüm | Tekrar | ",
"sum_tail": " | Ayıklanan (ort.) | Hata bildirimi kabul / ret | Ort. süre (sn) | GPU payı (en düşük) | Hız (token/sn) | Enerji (Wh/koşu) |",
"cmp_head": "| Karşılaştırma (A → B) | Ölçüt | Öğe | A | B | Δ (B − A) | Wilcoxon p | A12 | Etki |",
"split_head": "| Yapılandırma | Bölüm | Öğe | ", "round": "tur", "full": "tam"},
"en": {"title": "# Experiment analysis",
"intro": "Values are the mean over repetitions ± the standard deviation across repetitions. Comparisons use "
"the paired two-sided Wilcoxon signed-rank test over items (repetition means) and the "
"Vargha–Delaney A12 effect size.",
"s1": "## 1. Configurations", "s2": "## 2. Paired comparisons",
"s3": "## 3. By split: development (dev), held-out test and independent (QuixBugs)",
"sum_head": "| Configuration | Model | Rounds | Ablation | Split | Repeats | ",
"sum_tail": " | Pruned (avg.) | Bug reports accepted / rejected | Avg. time (s) | GPU share (lowest) | Speed (tokens/s) | Energy (Wh/run) |",
"cmp_head": "| Comparison (A → B) | Metric | Items | A | B | Δ (B − A) | Wilcoxon p | A12 | Effect |",
"split_head": "| Configuration | Split | Items | ", "round": "round", "full": "full"},
}
def _metrics(lang: str) -> dict[str, str]:
return METRICS_EN if lang == "en" else METRICS
def summary_rows(evals: list[EvalState], lang: str = "tr") -> list[str]:
A = A_TEXT["en" if lang == "en" else "tr"]
out = [A["sum_head"] + " | ".join(_metrics(lang).values()) + A["sum_tail"],
"|---|---|---|---|---|---|" + "---|" * len(METRICS) + "---|---|---|---|---|---|"]
for ev in evals:
done = [i for i in ev.items if i.status == "done"]
cells = [_msd(rep_means(ev, m)) for m in METRICS]
out.append(
f"| {ev.label or ev.id} | `{ev.model}` | {ev.config.get('max_iterations', '—')} | "
f"{', '.join(ev.config.get('ablate') or []) or '—'} | {ev.config.get('split', 'all')} | "
f"{ev.config.get('repeats', 1)} | " + " | ".join(cells) + " | "
f"{_fmt(mean([i.pruned_tests for i in done]) if done else None)} | "
f"{sum(i.suspected_bugs for i in done)} / {sum(i.rejected_bug_reports for i in done)} | "
f"{_fmt(mean([i.duration_s for i in done]) if done else None)} | "
f"{_gpu(done, lang)} | {_fmt(mean(tps) if (tps := [i.tokens_per_s for i in done if i.tokens_per_s]) else None)} | "
f"{_fmt(mean(en), '') if (en := [i.energy_wh for i in done if i.energy_wh is not None]) else '—'} |")
return out
def comparison_rows(pairs: list[tuple[str, EvalState, EvalState]], lang: str = "tr") -> list[str]:
out = [A_TEXT["en" if lang == "en" else "tr"]["cmp_head"],
"|---|---|---|---|---|---|---|---|---|"]
for name, a, b in pairs:
for m, label in _metrics(lang).items():
c = compare(a, b, m)
out.append(f"| {name} | {label} | {c['items']} | {_fmt(c['mean_a'])} | {_fmt(c['mean_b'])} | "
f"{_fmt(c['delta'])} | {c['p']} | {c['a12'] if c['a12'] is not None else '—'} | "
f"{EFFECT_EN.get(c['effect'], c['effect']) if lang == 'en' else c['effect']} |")
return out
def find_pairs(evals: list[EvalState], lang: str = "tr") -> list[tuple[str, EvalState, EvalState]]:
"""Otomatik eşleştirme: aynı model için tur 1 → tur>1 (ablasyonsuz) ve tam yapılandırma → her ablasyon."""
def key(ev):
return ev.model, ev.config.get("split", "all")
full: dict[tuple, EvalState] = {}
base: dict[tuple, EvalState] = {}
abl: list[EvalState] = []
for ev in sorted(evals, key=lambda e: e.created_at):
k = ev.config.get("max_iterations") or 1
if ev.config.get("ablate"):
abl.append(ev)
elif k == 1:
base[key(ev)] = ev
else:
full[key(ev)] = ev
pairs = []
for kk, b in full.items():
if kk in base:
r = A_TEXT["en" if lang == "en" else "tr"]["round"]
pairs.append((f"{kk[0]}: {r} 1 → {r} {b.config.get('max_iterations')}", base[kk], b))
for ev in abl:
f = full.get(key(ev))
if f:
full_w = A_TEXT["en" if lang == "en" else "tr"]["full"]
pairs.append((f"{ev.model}: {full_w} → {'−' + ', −'.join(ev.config['ablate'])}", f, ev))
return pairs
def split_rows(evals: list[EvalState], lang: str = "tr") -> list[str]:
out = [A_TEXT["en" if lang == "en" else "tr"]["split_head"] + " | ".join(_metrics(lang).values()) + " |",
"|---|---|---|" + "---|" * len(METRICS)]
for ev in evals:
if ev.config.get("ablate"):
continue
for split in ("dev", "test", "bagimsiz"):
sub = EvalState(id=ev.id, created_at=ev.created_at, model=ev.model, label=ev.label, config=ev.config,
items=[i for i in ev.items if i.split == split])
if not sub.items:
continue
cells = [_fmt(mean(list(per_item(sub, m).values())) if per_item(sub, m) else None) for m in METRICS]
out.append(f"| {ev.label or ev.id} | {split} | {len({i.id for i in sub.items})} | " + " | ".join(cells) + " |")
return out
def render_analysis(evals: list[EvalState], lang: str = "tr") -> str:
A = A_TEXT["en" if lang == "en" else "tr"]
evals = sorted(evals, key=lambda e: e.created_at)
lines = [A["title"], "", A["intro"], "", A["s1"], ""]
lines += summary_rows(evals, lang)
pairs = find_pairs(evals, lang)
if pairs:
lines += ["", A["s2"], ""] + comparison_rows(pairs, lang)
lines += ["", A["s3"], ""] + split_rows(evals, lang)
return "\n".join(lines) + "\n"