File size: 6,984 Bytes
20981bf 3ffa31d 20981bf | 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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | #!/usr/bin/env python3
"""比较 csv 里多种来源的 position / score 的 F1 指标。
读取 golden_set_with_estimated.csv(带 estimated_position 列),
对以下 4 套 position / score 各算:
- 默认阈值 F1(rank<106 / score>=44.72)
- 扫阈值最优 F1
- AUC
来源:
1. estimated_position (embedding top-100 mean rank)
2. position_gpt_4.1 / score_gpt_4.1
3. position_gemini_2.5_flash / score_gemini_2.5_flash
用法:
python3 compare_positions.py
python3 compare_positions.py --csv /path/to/golden_set_with_estimated.csv
"""
import argparse
import json
from pathlib import Path
import numpy as np
import pandas as pd
DEFAULT_CSV = "/mnt/bn/tns-algo-ue-my/biaowu/aipf_dm_metric/example/yss_ruler_eval/data/aipf_golden_set.csv"
def metrics(preds, gts):
tp = int(((preds == 1) & (gts == 1)).sum())
fp = int(((preds == 1) & (gts == 0)).sum())
tn = int(((preds == 0) & (gts == 0)).sum())
fn = int(((preds == 0) & (gts == 1)).sum())
p = tp/(tp+fp) if tp+fp else 0.0
r = tp/(tp+fn) if tp+fn else 0.0
f = 2*p*r/(p+r) if p+r else 0.0
a = (tp+tn)/len(preds)
return tp, fp, tn, fn, p, r, f, a
def best_threshold(scores, gts):
"""scores 越大越严重时调用;返回 (f1, thr, p, r)"""
valid = ~np.isnan(scores)
s, g = scores[valid], gts[valid]
cands = sorted(set(s.tolist()))
best = (-1.0, None, None, None)
for c in cands:
preds = (s >= c).astype(int)
_, _, _, _, p, r, f, _ = metrics(preds, g)
if f > best[0]:
best = (f, c, p, r)
return best
def auc(scores, gts):
valid = ~np.isnan(scores)
s, g = scores[valid], gts[valid]
if len(set(g.tolist())) < 2:
return float("nan")
try:
from sklearn.metrics import roc_auc_score
return roc_auc_score(g, s)
except ImportError:
return float("nan")
def parse_args():
p = argparse.ArgumentParser()
p.add_argument("--csv", default="golden_set_with_estimated.csv")
p.add_argument("--positive-label", default="Y")
p.add_argument("--boundary-rank", type=int, default=106)
p.add_argument("--boundary-score", type=float, default=44.72)
p.add_argument("--output-md", default="position_comparison.md")
return p.parse_args()
def main():
args = parse_args()
print(f"[1] read csv: {args.csv}")
df = pd.read_csv(args.csv, keep_default_na=True)
print(f" {len(df)} rows, cols: {len(df.columns)}")
print(f" label dist: {df['label'].value_counts().to_dict()}")
gts = df["label"].astype(str).str.upper().eq(args.positive_label.upper()).astype(int).values
# 4 套预测的"分数"(统一成"越大越严重"以便扫阈值)
sources = [] # (name, score_array, default_threshold, default_pred_fn)
if "estimated_position" in df.columns:
pos = pd.to_numeric(df["estimated_position"], errors="coerce").values
sources.append((
"embedding estimated_position",
-pos, # 取负 → 越大越严重
-args.boundary_rank, # 默认阈值也取负
"rank<106",
))
if "position_gpt_4.1" in df.columns:
pos = pd.to_numeric(df["position_gpt_4.1"], errors="coerce").values
sources.append(("LLM gpt_4.1 position", -pos, -args.boundary_rank, "rank<106"))
if "score_gpt_4.1" in df.columns:
sc = pd.to_numeric(df["score_gpt_4.1"], errors="coerce").values
sources.append(("LLM gpt_4.1 score", sc, args.boundary_score, "score>=44.72"))
if "position_gemini_2.5_flash" in df.columns:
pos = pd.to_numeric(df["position_gemini_2.5_flash"], errors="coerce").values
sources.append(("LLM gemini_2.5_flash position", -pos, -args.boundary_rank, "rank<106"))
if "score_gemini_2.5_flash" in df.columns:
sc = pd.to_numeric(df["score_gemini_2.5_flash"], errors="coerce").values
sources.append(("LLM gemini_2.5_flash score", sc, args.boundary_score, "score>=44.72"))
if not sources:
raise SystemExit("没有可用的 position / score 列")
# ---- 算指标 ----
rows = []
for name, s, default_thr, default_desc in sources:
# 默认阈值
valid = ~np.isnan(s)
preds = np.zeros_like(s, dtype=int)
preds[valid] = (s[valid] >= default_thr).astype(int)
tp, fp, tn, fn, prec, rec, f1, acc = metrics(preds, gts)
# 扫最优
best_f1, best_thr, best_p, best_r = best_threshold(s, gts)
a = auc(s, gts)
rows.append({
"source": name,
"default": default_desc,
"n_valid": int(valid.sum()),
"TP": tp, "FP": fp, "TN": tn, "FN": fn,
"P": round(prec, 4),
"R": round(rec, 4),
"F1": round(f1, 4),
"Acc": round(acc, 4),
"best F1": round(best_f1, 4),
"best thr": best_thr,
"AUC": round(a, 4) if not np.isnan(a) else None,
})
out_df = pd.DataFrame(rows)
print("\n" + out_df.to_string(index=False))
# ---- 写 markdown ----
with open(args.output_md, "w", encoding="utf-8") as f:
f.write("# Position / Score 来源对比\n\n")
f.write(f"数据:`{args.csv}` ({len(df)} rows)\n\n")
f.write(f"GT:`label == '{args.positive_label}'` → 1\n\n")
f.write(f"默认阈值:rank<{args.boundary_rank} 或 score≥{args.boundary_score}\n\n")
f.write("## 完整对比\n\n")
# 不依赖 tabulate,手写 markdown 表格
cols = list(out_df.columns)
f.write("| " + " | ".join(cols) + " |\n")
f.write("|" + "|".join(["---"] * len(cols)) + "|\n")
for _, row in out_df.iterrows():
f.write("| " + " | ".join(str(v) for v in row.tolist()) + " |\n")
f.write("\n\n## 关键观察\n\n")
# F1 排名
sorted_rows = sorted(rows, key=lambda r: -r["F1"])
f.write(f"- 默认阈值下 F1 最高:**{sorted_rows[0]['source']}** ({sorted_rows[0]['F1']})\n")
f.write(f"- 默认阈值下 F1 最低:**{sorted_rows[-1]['source']}** ({sorted_rows[-1]['F1']})\n")
# AUC 排名
valid_rows = [r for r in rows if r["AUC"] is not None]
if valid_rows:
sorted_rows2 = sorted(valid_rows, key=lambda r: -r["AUC"])
f.write(f"- AUC 最高(分布可分性):**{sorted_rows2[0]['source']}** ({sorted_rows2[0]['AUC']})\n")
# embedding vs LLM gap
emb = next((r for r in rows if r["source"].startswith("embedding")), None)
gem = next((r for r in rows if "gemini" in r["source"] and "score" in r["source"]), None)
if emb and gem:
f.write(f"- embedding 估的 F1 比 Gemini 低 **{(gem['best F1']-emb['best F1']):.4f}**(best F1 对比)\n")
print(f"\n[written] {args.output_md}")
if __name__ == "__main__":
main()
|