#!/usr/bin/env python3 """9×9 交叉混淆矩阵:每个模型实测 D 层分布 vs 全部 9 个参考(纯离线)。 混淆判定:某模型的全部 JSD 里,最低者(top-1)若不是自己的参考 → 记一次混淆。 附带:glm_53 复跑样本作为第二独立样本验证比对稳定性。 """ import json import sys from pathlib import Path # noqa: E402 sys.path.insert(0, str(Path(__file__).resolve().parents[2])) # 仓库根/site-packages, 使 evalharness 包可导入 from evalharness.fingerprint.engine import (build_d_normalized, compare_cells, # noqa: E402 distributions_by_cell, load_reference) BFD = "/tmp/bfd" R = str(Path(__file__).resolve().parent / 'references') MODELS = [ ("deepseek_v4_flash", "deepseek_v4_flash_fusion_reference.json", "DS"), ("deepseek_v4_flash_0731", "deepseek_v4_flash_0731_fusion_reference.json", "DS"), ("deepseek_v4_pro", "deepseek_v4_pro_fusion_reference.json", "DS"), ("glm_51", "glm_51_fusion_reference.json", "GLM"), ("glm_52", "glm52_vectron_fusion_reference.json", "GLM"), ("glm_53", "glm53_fusion_reference.json", "GLM"), ("kimi_k2_6", "kimi_k2_6_fusion_reference.json", "Kimi"), ("kimi_k2_7code", "kimi_k2_7code_fusion_reference.json", "Kimi"), ("kimi_k3", "kimi_k3_fusion_reference.json", "Kimi"), ] FAMILY = {n: f for n, _, f in MODELS} refs = {} for n, rf, _ in MODELS: refs[n] = load_reference(f"{R}/{rf}")["cells"] def dist_of(path): records = [json.loads(l) for l in open(path)] d = build_d_normalized(records) return distributions_by_cell(d) live = {n: dist_of(f"{BFD}/{n}/raw_answers.jsonl") for n, _, _ in MODELS} extra = {} try: extra["glm_53#rerun"] = dist_of(f"{BFD}/glm_53/rerun/raw_answers.jsonl") except FileNotFoundError: pass names = [n for n, _, _ in MODELS] results = {} for lname, dist in {**live, **extra}.items(): results[lname] = {} for rname, ref_cells in refs.items(): entries, mean_jsd = compare_cells(dist, ref_cells) results[lname][rname] = (mean_jsd, len(entries)) print("JSD 矩阵(行=实测模型,列=参考,越低越像):") print("live\\ref".ljust(22) + "".join(n[:13].rjust(14) for n in names)) for lname in results: print(lname[:21].ljust(22) + "".join(f"{results[lname][n][0]:.3f}".rjust(14) if results[lname][n][0] is not None else "—".rjust(14) for n in names)) print() print("=" * 100) correct, total, confusions = 0, 0, [] print(f"{'模型':22s} {'own':>6s} {'top1 匹配':22s} {'判定':6s} {'最佳同族':22s} {'同族JSD':>8s} {'间距':>8s}") for lname in live: valid = {r: v[0] for r, v in results[lname].items() if v[0] is not None} if not valid: continue best = min(valid, key=valid.get) own = valid[lname] total += 1 ok = best == lname correct += ok if not ok: confusions.append((lname, best, round(own, 3), round(valid[best], 3))) sibs = [r for r in valid if FAMILY[r] == FAMILY[lname] and r != lname] if sibs: bs = min(sibs, key=valid.get) print(f"{lname:22s} {own:6.3f} {best:22s} {'✓' if ok else '✗混淆':6s} " f"{bs:22s} {valid[bs]:8.3f} {valid[bs] - own:+8.3f}") else: print(f"{lname:22s} {own:6.3f} {best:22s} {'✓' if ok else '✗混淆':6s} {'(无同族)':22s}") print() print(f"【top-1 正确率】{correct}/{total} = {correct / total * 100:.0f}%") print(f"【混淆数】{len(confusions)}") for c in confusions: print(f" 混淆: {c[0]} 被认成 {c[1]} (own={c[2]}, conf={c[3]})") if "glm_53#rerun" in results: valid = {r: v[0] for r, v in results["glm_53#rerun"].items() if v[0] is not None} best = min(valid, key=valid.get) print(f"【稳定性】glm_53 第二独立样本 top1={best} " f"{'✓ 与首跑一致' if best == 'glm_53' else '✗ 不一致'} " f"(own={valid['glm_53']:.3f}, 次优={sorted(valid.values())[1]:.3f})")