|
|
|
|
|
|
| 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 {}
|
|
|
| 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
|
|
|
|
|
| m = LETTER_RE.search(t)
|
| if m:
|
| return m.group(1).upper()
|
|
|
|
|
| m2 = re.match(r"(?i)^\s*([ABCD])\s*[\)\.\::]\s*", t)
|
| if m2:
|
| return m2.group(1).upper()
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
| 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()
|
|
|
| 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")
|
|
|
| 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
|
|
|
| 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()
|
|
|