Instructions to use RomanCohort/RNAQwen with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use RomanCohort/RNAQwen with Transformers:
# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("RomanCohort/RNAQwen", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download scripts/03_eval_selfcheck.py from RomanCohort/RNAQwen: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/RomanCohort/RNAQwen/resolve/main/scripts/03_eval_selfcheck.py
- Command line
-
hf download hf://RomanCohort/RNAQwen/scripts/03_eval_selfcheck.py
-
curl -L -o 03_eval_selfcheck.py https://huggingface.co/RomanCohort/RNAQwen/resolve/main/scripts/03_eval_selfcheck.py
10.5 kB
| """PLAN §7.3 —— 评测脚本自检。 | |
| 上量之前先证明"尺子是准的"。三关: | |
| A. **ROUGE 对数**:自实现 vs Google `rouge_score`,逐样本必须一致(1e-9)。 | |
| 不一致 = 口径错了,后面所有数字都不可比。 | |
| B. **语义指标不变量**: | |
| * 完美答案(hyp == ref)→ P/R/F1 ≈ 1.0 | |
| * 完全无关的两段 → F1 明显低 | |
| * 单调性:部分重叠的答案,F1 落在两者之间 | |
| C. **原版消融表的量级自检**:用 PLAN §4 那三行做"人工完美答案"实验, | |
| 确认指标在 0.09 / 0.25 / 0.48 这种稀疏度上确实是单调响应的 | |
| (注意:**不是**去复现原报数,那需要他们的评测集;这里只验证尺子的响应方向)。 | |
| 跑法: | |
| python scripts/03_eval_selfcheck.py --embedder bert | |
| python scripts/03_eval_selfcheck.py --skip-embedders # 只对数 ROUGE(不用下模型) | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(ROOT / "src")) | |
| import rnaqwen # noqa: E402,F401 —— import 本包即自动装载 env.shim(本机 torch 缺分布式符号) | |
| from rnaqwen.eval.rouge import ( # noqa: E402 | |
| rouge_1, | |
| rouge_2, | |
| rouge_l, | |
| rouge_scores, | |
| ) | |
| from rnaqwen.eval.semantic import build_embedder, semantic_scores, split_sentences # noqa: E402 | |
| # ---------------------------------------------------------------- 测试样本 | |
| # 取自 RNA 领域的真实风格文本(避免用"hello world"这种测不出分词问题的样本) | |
| REFERENCE = ( | |
| "The RNA molecule is a non-coding RNA that regulates gene expression. " | |
| "It is processed by the RNase III enzyme Drosha in the nucleus. " | |
| "Its secondary structure contains a conserved stem-loop motif that is required for binding." | |
| ) | |
| PARAPHRASE = ( | |
| "This non-coding RNA controls the expression of genes. " | |
| "Drosha, an RNase III enzyme, processes it inside the nucleus. " | |
| "A conserved stem-loop in its secondary structure is needed for binding." | |
| ) | |
| PARTIAL = "The RNA regulates gene expression and is processed by Drosha in the nucleus." | |
| UNRELATED = ( | |
| "The protein adopts a beta-barrel fold. " | |
| "Crystallography at 1.8 angstrom resolution revealed a bound calcium ion." | |
| ) | |
| ROUGE_CASES: list[tuple[str, str, str]] = [ | |
| ("identical", REFERENCE, REFERENCE), | |
| ("paraphrase", PARAPHRASE, REFERENCE), | |
| ("partial", PARTIAL, REFERENCE), | |
| ("unrelated", UNRELATED, REFERENCE), | |
| ("empty", "", REFERENCE), | |
| ("punctuation", "The RNA, molecule; is a non-coding RNA!!!", REFERENCE), | |
| ("numbers", "The 1.8 angstrom 42 kDa complex", "The 1.8 angstrom 42 kDa complex was solved"), | |
| ] | |
| def section(title: str) -> None: | |
| print(f"\n{'=' * 78}\n{title}\n{'=' * 78}") | |
| def check_rouge_parity(tolerance: float = 1e-9) -> dict: | |
| """A 关:自实现 vs rouge_score 逐样本对数。""" | |
| section("A. ROUGE 对数(自实现 vs rouge_score)") | |
| report: dict = {"available": False, "cases": [], "max_abs_diff": None, "passed": None} | |
| try: | |
| from rouge_score import rouge_scorer | |
| except ImportError: | |
| print("[skip] 没装 rouge_score —— 先 pip install rouge-score 再跑这一关") | |
| return report | |
| report["available"] = True | |
| scorer = rouge_scorer.RougeScorer(["rouge1", "rouge2", "rougeL"], use_stemmer=False) | |
| max_diff = 0.0 | |
| for name, hyp, ref in ROUGE_CASES: | |
| ref_scores = scorer.score(ref, hyp) | |
| ours = { | |
| "rouge1": rouge_1(hyp, ref)[2], | |
| "rouge2": rouge_2(hyp, ref)[2], | |
| "rougeL": rouge_l(hyp, ref)[2], | |
| } | |
| theirs = { | |
| "rouge1": ref_scores["rouge1"].fmeasure, | |
| "rouge2": ref_scores["rouge2"].fmeasure, | |
| "rougeL": ref_scores["rougeL"].fmeasure, | |
| } | |
| diffs = {k: abs(ours[k] - theirs[k]) for k in ours} | |
| case_max = max(diffs.values()) | |
| max_diff = max(max_diff, case_max) | |
| ok = case_max <= tolerance | |
| report["cases"].append( | |
| {"case": name, "ours": ours, "rouge_score": theirs, "max_abs_diff": case_max, "ok": ok} | |
| ) | |
| flag = "ok " if ok else "FAIL" | |
| print( | |
| f"[{flag}] {name:12s} r1={ours['rouge1']:.6f}/{theirs['rouge1']:.6f} " | |
| f"r2={ours['rouge2']:.6f}/{theirs['rouge2']:.6f} " | |
| f"rL={ours['rougeL']:.6f}/{theirs['rougeL']:.6f} Δmax={case_max:.2e}" | |
| ) | |
| report["max_abs_diff"] = max_diff | |
| report["passed"] = max_diff <= tolerance | |
| print(f"\n-> 最大偏差 {max_diff:.3e}(阈值 {tolerance:.0e}):{'一致' if report['passed'] else '不一致,口径有问题'}") | |
| return report | |
| def check_semantic_invariants(embedder_spec: str) -> dict: | |
| """B 关:语义指标不变量。""" | |
| section(f"B. 语义指标不变量(embedder={embedder_spec})") | |
| emb = build_embedder(embedder_spec) | |
| report: dict = {"embedder": embedder_spec, "cases": [], "passed": None} | |
| cases = [ | |
| ("identical", REFERENCE, REFERENCE, "high"), | |
| ("paraphrase", PARAPHRASE, REFERENCE, "high"), | |
| ("partial", PARTIAL, REFERENCE, "mid"), | |
| ("unrelated", UNRELATED, REFERENCE, "low"), | |
| ] | |
| scores: dict[str, float] = {} | |
| for name, hyp, ref, expect in cases: | |
| s = semantic_scores([hyp], [ref], emb) | |
| scores[name] = s.f1 | |
| report["cases"].append({"case": name, "expect": expect, **s.as_dict()}) | |
| print(f"[{expect:4s}] {name:12s} P={s.precision:.4f} R={s.recall:.4f} F1={s.f1:.4f}") | |
| # 「无关」这条对照的分数**不等于**指标地板 —— 它取决于两段文本的语域距离。 | |
| # 实测(reports/semantic_floor.json): | |
| # 跨域随机配对的地板 bert ≈ 0.52 / pubmedbert ≈ 0.95 | |
| # 「RNA vs 蛋白质晶体学」这种同语域对照 bert ≈ 0.77 / pubmedbert ≈ 0.96 | |
| # 所以阈值不能写死 0.95 —— 那个数只在 BERT 上成立, | |
| # 在 PubMedBERT 上**数学上不可能通过**(它自己的地板就是 0.9518)。 | |
| KNOWN_FLOORS = {"bert": 0.52, "pubmedbert": 0.95} | |
| key = next((k for k in KNOWN_FLOORS if k in embedder_spec.lower() | |
| or ("bert-base-uncased" in embedder_spec.lower() and k == "bert")), None) | |
| floor = KNOWN_FLOORS.get(key) if key else None | |
| checks = { | |
| "identical_ge_0.999": scores["identical"] >= 0.999, | |
| "paraphrase_gt_partial": scores["paraphrase"] > scores["partial"], | |
| "partial_gt_unrelated": scores["partial"] > scores["unrelated"], | |
| } | |
| if floor is None: | |
| checks["unrelated_vs_known_floor"] = True | |
| print("[skip] 未知嵌入的地板,跳过绝对阈值检查(请查 reports/semantic_floor.json)") | |
| else: | |
| checks["unrelated_within_floor"] = scores["unrelated"] < floor + 0.06 | |
| report["known_floor"] = floor | |
| report["dynamic_range"] = [floor, scores["identical"]] | |
| print(f"[info] 该嵌入的跨域地板 ≈ {floor:.2f} → 有效动态范围 " | |
| f"[{floor:.2f}, {scores['identical']:.2f}],宽 {scores['identical'] - floor:.3f}") | |
| print(f"[info] 本次 unrelated={scores['unrelated']:.4f}(同语域对照,本就会比跨域地板高)") | |
| report["checks"] = checks | |
| report["passed"] = all(checks.values()) | |
| for k, v in checks.items(): | |
| print(f"[{'ok ' if v else 'FAIL'}] {k}") | |
| return report | |
| def check_ablation_scale(embedder_spec: str) -> dict: | |
| """C 关:用 PLAN §4 的稀疏度做响应方向检查。""" | |
| section("C. 消融阶梯的响应方向(不是复现原报数,是验证尺子会动)") | |
| # 三段参考文本;构造"说了 25% / 50% / 100%"的答案,看指标是否单调 | |
| ref = REFERENCE | |
| ref_sents = split_sentences(ref) | |
| variants = { | |
| "perfect_100pct": ref, | |
| "half_50pct": " ".join(ref_sents[: max(1, len(ref_sents) // 2)]), | |
| "one_third": ref_sents[0] if ref_sents else ref, | |
| "bare_sequence_like": "ACGUACGUACGUACGUACGUACGUACGUACGU", | |
| "empty": "", | |
| } | |
| hyps = list(variants.values()) | |
| refs = [ref] * len(hyps) | |
| r = rouge_scores(hyps, refs) | |
| emb = build_embedder(embedder_spec) | |
| s = semantic_scores(hyps, refs, emb) | |
| report: dict = {"rouge": r.as_dict(), "semantic": s.as_dict(), "per_variant": []} | |
| print(f"{'variant':20s} {'R-1':>8s} {'R-2':>8s} {'R-L':>8s} {'F1@emb':>8s}") | |
| for i, (name, _) in enumerate(variants.items()): | |
| r1 = rouge_1(hyps[i], ref)[2] | |
| r2 = rouge_2(hyps[i], ref)[2] | |
| rl = rouge_l(hyps[i], ref)[2] | |
| f1 = semantic_scores([hyps[i]], [ref], emb).f1 | |
| report["per_variant"].append({"variant": name, "rouge1": r1, "rouge2": r2, "rougeL": rl, "semantic_f1": f1}) | |
| print(f"{name:20s} {r1:8.4f} {r2:8.4f} {rl:8.4f} {f1:8.4f}") | |
| ordered = [v["rouge1"] for v in report["per_variant"][:3]] | |
| monotonic = ordered[0] >= ordered[1] >= ordered[2] | |
| report["monotonic_rouge1"] = monotonic | |
| print(f"\n-> ROUGE-1 随答案完整度单调:{'是' if monotonic else '否 —— 尺子有问题'}") | |
| print(" (原版 D&C 阶梯 0.2472 → 0.0922 → 0.4791 是同一把尺子量出来的三档)") | |
| return report | |
| def main() -> int: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--embedder", default="bert", help="bert / pubmedbert / gpt / HF repo id") | |
| ap.add_argument("--skip-embedders", action="store_true", help="只跑 ROUGE 对数(不下嵌入模型)") | |
| ap.add_argument("--out", type=Path, default=ROOT / "reports" / "eval_selfcheck.json") | |
| args = ap.parse_args() | |
| report: dict = {"embedder": args.embedder} | |
| report["A_rouge_parity"] = check_rouge_parity() | |
| if not args.skip_embedders: | |
| report["B_semantic_invariants"] = check_semantic_invariants(args.embedder) | |
| report["C_ablation_scale"] = check_ablation_scale(args.embedder) | |
| args.out.parent.mkdir(parents=True, exist_ok=True) | |
| args.out.write_text(json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8") | |
| print(f"\n[done] 自检报告 -> {args.out}") | |
| verdicts = [v.get("passed") for k, v in report.items() if isinstance(v, dict) and "passed" in v] | |
| if any(v is False for v in verdicts): | |
| print("[FAIL] 有自检项没过,别上量。") | |
| return 1 | |
| if any(v is None for v in verdicts): | |
| print("[PARTIAL] 有自检项被跳过(缺依赖)。") | |
| print("[OK] 尺子可用。") | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |