File size: 4,577 Bytes
0f8dedb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
"""P3 评测:对一批预测跑完整指标(ROUGE-1/2/L + 三种嵌入的 Semantic P/R/F1)。

输入是 JSONL,每行至少要有 `prediction` 和 `reference`(或 `gold`)。
其它字段(uid / variant / …)原样透传到报告里。

跑法:
    python scripts/04_eval.py --in data/processed/preds_50.jsonl --embedders bert,pubmedbert \
        --report reports/eval_50.json
"""

from __future__ import annotations

import argparse
import json
import sys
import time
from collections import defaultdict
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))

from rnaqwen.eval.rouge import rouge_scores  # noqa: E402
from rnaqwen.eval.semantic import build_embedder, semantic_scores  # noqa: E402

PRED_KEYS = ("prediction", "pred", "hypothesis", "output", "response")
REF_KEYS = ("reference", "ref", "gold", "target", "annotation", "answer")


def pick(d: dict, keys) -> str | None:
    for k in keys:
        if k in d and isinstance(d[k], str):
            return d[k]
    return None


def load_pairs(path: Path) -> tuple[list[dict], list[str], list[str]]:
    rows, hyps, refs = [], [], []
    for line in path.read_text(encoding="utf-8").splitlines():
        line = line.strip()
        if not line:
            continue
        d = json.loads(line)
        h, r = pick(d, PRED_KEYS), pick(d, REF_KEYS)
        if h is None or r is None:
            raise ValueError(f"缺 prediction/reference 字段:{sorted(d)[:10]}")
        rows.append(d)
        hyps.append(h)
        refs.append(r)
    return rows, hyps, refs


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--in", dest="inp", type=Path, required=True)
    ap.add_argument("--embedders", default="bert", help="逗号分隔:bert,pubmedbert,gpt 或 HF repo id")
    ap.add_argument("--group-by", default="variant", help="按哪个字段分组报告(没有就整体报)")
    ap.add_argument("--beta", type=float, default=1.0, help="ROUGE-L 的 beta(1.0=rouge_score 口径,1.2=ROUGE-1.5.5)")
    ap.add_argument("--report", type=Path, default=ROOT / "reports" / "eval.json")
    args = ap.parse_args()

    rows, hyps, refs = load_pairs(args.inp)
    print(f"[eval] {len(hyps)} 条样本 <- {args.inp}")

    report: dict = {
        "input": str(args.inp),
        "n": len(hyps),
        "rouge_beta": args.beta,
        "overall": {},
        "by_group": {},
        "started": time.strftime("%Y-%m-%d %H:%M:%S"),
    }

    r = rouge_scores(hyps, refs, beta=args.beta)
    report["overall"]["rouge"] = r.as_dict()
    print(f"[rouge] R-1={r.rouge1:.4f}  R-2={r.rouge2:.4f}  R-L={r.rougeL:.4f}")

    specs = [s.strip() for s in args.embedders.split(",") if s.strip()]
    for spec in specs:
        t0 = time.time()
        emb = build_embedder(spec)
        s = semantic_scores(hyps, refs, emb)
        report["overall"][f"semantic_{spec}"] = s.as_dict()
        print(f"[sem  ] {spec:12s} P={s.precision:.4f} R={s.recall:.4f} F1={s.f1:.4f}  "
              f"({time.time()-t0:.1f}s, {s.n_sent_hyp} vs {s.n_sent_ref} 句)")

    # 分组(例如 AIS / D&C 两个变体要分开报)
    groups: dict[str, list[int]] = defaultdict(list)
    for i, d in enumerate(rows):
        groups[str(d.get(args.group_by, "all"))].append(i)
    if len(groups) > 1:
        print()
        for gname, idxs in sorted(groups.items()):
            gr = rouge_scores([hyps[i] for i in idxs], [refs[i] for i in idxs], beta=args.beta)
            entry = {"n": len(idxs), "rouge": gr.as_dict()}
            print(f"[group] {gname:8s} n={len(idxs):4d}  R-1={gr.rouge1:.4f}  R-2={gr.rouge2:.4f}  R-L={gr.rougeL:.4f}")
            for spec in specs:
                emb = build_embedder(spec)
                s = semantic_scores([hyps[i] for i in idxs], [refs[i] for i in idxs], emb)
                entry[f"semantic_{spec}"] = s.as_dict()
                print(f"          {spec:12s} F1={s.f1:.4f}  (P={s.precision:.4f} R={s.recall:.4f})")
            report["by_group"][gname] = entry

    report["finished"] = time.strftime("%Y-%m-%d %H:%M:%S")
    report["disclaimer"] = (
        "评测集为重建,绝对分与原论文报数不可直接比较;"
        "原版代码/权重/评测集均未开源。报告数字时必须带这句。"
    )
    args.report.parent.mkdir(parents=True, exist_ok=True)
    args.report.write_text(json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8")
    print(f"\n[report] -> {args.report}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())