Download backend/app/eval/stats.py from SevdanurGenc/LLM-Automated-Test-Generation-Agent: direct link, hf CLI and curl.
- Browser
- Download file 13 kB
-
https://huggingface.co/SevdanurGenc/LLM-Automated-Test-Generation-Agent/resolve/main/backend/app/eval/stats.py
- Command line
-
hf download hf://SevdanurGenc/LLM-Automated-Test-Generation-Agent/backend/app/eval/stats.py
-
curl -L -o stats.py https://huggingface.co/SevdanurGenc/LLM-Automated-Test-Generation-Agent/resolve/main/backend/app/eval/stats.py
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" | |