ROMA / eval /reactive /streamingbench /eval_sqa.py
Houssem0's picture
ROMA + GH200 reproducible Docker layer
e5c09aa verified
Raw
History Blame Contribute Delete
4.85 kB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import re
from pathlib import Path
from typing import Dict, Optional, Tuple
from tqdm import tqdm
IN_PATH = "sqa.jsonl"
LETTER_RE = re.compile(r"(?i)(?:^|[\s::\(\[\{<]+)([ABCD])(?:[\s\.\)::\]\}>]|$)")
def parse_options(question_text: str) -> Dict[str, str]:
"""
从 question 字符串中解析 (A) ... (B) ... (C) ... (D) ... 的选项文本
返回 {'A': '...', 'B': '...', ...}(已 strip)
"""
if not question_text:
return {}
# 允许 (A) 或 A) 或 A. 等常见形式;用 non-greedy 拉到下一个选项或结尾
pat = re.compile(
r"(?is)(?:\(\s*([ABCD])\s*\)|^\s*([ABCD])[\)\.])\s*(.*?)\s*(?=(?:\(\s*[ABCD]\s*\)|^\s*[ABCD][\)\.]|\Z))",
re.MULTILINE
)
opts: Dict[str, str] = {}
for g1, g2, body in pat.findall(question_text):
k = (g1 or g2).upper()
if k and k not in opts:
opts[k] = " ".join(body.strip().split())
return opts
def normalize_text(s: str) -> str:
return " ".join((s or "").strip().lower().split())
def extract_choice_from_pred(pred_text: str, options: Dict[str, str], gt_choice: Optional[str]) -> Optional[str]:
"""
1) 优先从 pred_text 里抽 A/B/C/D(支持 A, A., A:..., (A) 等)
2) 若没抽到,尝试用“包含选项文本/gt文本”进行匹配(复述内容也算对上)
"""
if pred_text is None:
return None
t = pred_text.strip()
if not t:
return None
# 1) 直接抽字母
m = LETTER_RE.search(t)
if m:
return m.group(1).upper()
# 额外:有些模型会输出 "A)" / "A." 开头
m2 = re.match(r"(?i)^\s*([ABCD])\s*[\)\.\::]\s*", t)
if m2:
return m2.group(1).upper()
# 2) 文本包含匹配:如果 pred 复述了某个选项内容,则映射回该选项字母
norm_pred = normalize_text(t)
if options:
# 先匹配最长选项(避免短句误匹配)
items = sorted(options.items(), key=lambda kv: len(kv[1]), reverse=True)
for k, opt_text in items:
norm_opt = normalize_text(opt_text)
if norm_opt and (norm_opt in norm_pred or norm_pred in norm_opt):
return k
# 3) 兜底:如果 pred 直接把 gt 的文本重复出来(有些数据 gt.text)
# 这里不需要完整等于,包含即可(同样走字符串归一)
# 注意:gt_choice 不一定有对应文本,所以只能靠 options 或者 pred 自己的字母形式
return None
def get_gt_choice(obj: dict) -> Optional[str]:
gt = obj.get("gt")
if isinstance(gt, dict):
c = gt.get("choice")
if isinstance(c, str) and c.strip():
return c.strip().upper()
# 兼容 answer 字段
ans = obj.get("answer")
if isinstance(ans, dict):
c = ans.get("choice")
if isinstance(c, str) and c.strip():
return c.strip().upper()
return None
def get_pred_text(obj: dict) -> Optional[str]:
pred = obj.get("prediction")
# 常见:prediction 是 list[{"time":..., "text":...}],取最后一个
if isinstance(pred, list) and pred:
last = pred[-1]
if isinstance(last, dict):
t = last.get("text")
return t if isinstance(t, str) else None
if isinstance(last, str):
return last
# 兜底:prediction 是 str
if isinstance(pred, str):
return pred
return None
def main():
path = Path(IN_PATH)
total = 0
correct = 0
skipped = 0
with path.open("r", encoding="utf-8") as f:
for line in tqdm(f, desc="Scoring", unit="lines"):
line = line.strip()
if not line:
continue
try:
obj = json.loads(line)
except json.JSONDecodeError:
skipped += 1
continue
gt_choice = get_gt_choice(obj)
pred_text = get_pred_text(obj)
q = obj.get("question", "")
options = parse_options(q) if isinstance(q, str) else {}
pred_choice = extract_choice_from_pred(pred_text or "", options, gt_choice)
if not gt_choice or gt_choice not in {"A", "B", "C", "D"}:
skipped += 1
continue
total += 1
if pred_choice == gt_choice:
correct += 1
acc = (correct / total) if total else 0.0
print(f"File: {IN_PATH}")
print(f"Total (scored): {total}")
print(f"Correct: {correct}")
print(f"Accuracy: {acc:.4f} ({acc*100:.2f}%)")
print(f"Skipped (empty/invalid/no-gt/bad-json): {skipped}")
if __name__ == "__main__":
main()