人工智能实践(语言智能)
第8讲:LLM as Judge

实验8:为中英翻译任务搭 Judge 基线

从 rubric 到 prompt,再到偏差校正与人工对齐——端到端构建 LLM Judge

实验概述

本实验你将从零搭建一个可用的 LLM Judge,用于评估中英翻译质量。完成后你会有:

  • 一份可复用的翻译 Judge Prompt(含 rubric + few-shot + 结构化输出)
  • 一个跑在 200 对样本上的 Pairwise 评测脚本
  • 位置偏差校正后的结果
  • Judge 与人工标注的相关性分析(Spearman / Kendall)
项目详情
任务中英翻译质量评估
Judge 模型GPT-4o / Claude-3.5-Sonnet / Qwen3-Max(任选其一,推荐三选一对比)
被评模型2 个翻译模型(如 Qwen3-7B-SFT vs. GPT-3.5-turbo)
样本规模200 对翻译 + 50 对人工金标
范式Pairwise + Pointwise 双通道
预计时间3–4 小时(不含人工标注)

API 预算估算:Pairwise 200 对 × Swap 双跑 × 3 个 Judge ≈ 1,200 次调用。GPT-4o 约 20 元人民币,Qwen3-Max 约 5 元。可在 colab + OpenRouter 完成全部实验。

实验步骤

步骤 1:准备数据与定义 Rubric(30 分钟)

1.1 准备翻译数据

选取 WMT 或自建中英翻译测试集。推荐 Helsinki-NLP/opus-100zh-en 子集抽 200 条。

from datasets import load_dataset
import random

# 加载 opus-100 中英子集
ds = load_dataset("Helsinki-NLP/opus-100", "en-zh", split="test")
random.seed(42)
indices = random.sample(range(len(ds)), 200)
samples = [ds[i] for i in indices]

# 展示前 3 条
for s in samples[:3]:
    print(f"ZH: {s['translation']['zh']}")
    print(f"EN (ref): {s['translation']['en']}\n")

1.2 用两个模型生成待评翻译

from openai import OpenAI
# 伪代码 - 分别用 Model A 和 Model B 生成翻译
model_a = "qwen3-7b-sft-local"
model_b = "gpt-3.5-turbo"

def translate(model, zh_text):
    # 调用对应模型做中译英
    prompt = f"Translate the following Chinese to English:\n{zh_text}"
    return call_model(model, prompt)

pairs = []
for s in samples:
    zh = s["translation"]["zh"]
    ref = s["translation"]["en"]
    trans_a = translate(model_a, zh)
    trans_b = translate(model_b, zh)
    pairs.append({
        "zh": zh, "ref": ref,
        "trans_a": trans_a, "trans_b": trans_b
    })

1.3 写 Rubric

基于 8.2 技术 的翻译 Rubric,定义 4 个维度:

RUBRIC = """
评分维度(各 1-5 分):

1. Faithfulness 忠实度
   5 = 所有信息准确完整地传达
   4 = 核心信息正确,极少次要信息偏差
   3 = 核心信息正确,次要信息有遗漏或偏差
   2 = 主要信息部分错误或遗漏
   1 = 主要信息严重错误

2. Fluency 流畅度
   5 = 接近母语水平,自然流畅
   4 = 自然,极少不地道表达
   3 = 可读但存在语法或搭配问题
   2 = 可读性差,需反复理解
   1 = 母语者难以理解

3. Terminology 术语准确性
   5 = 所有术语准确且一致
   4 = 术语基本准确
   3 = 术语基本正确但不统一
   2 = 关键术语错译
   1 = 大多数术语错译

4. Style 文体匹配
   5 = 文体与原文完美匹配
   4 = 文体基本匹配
   3 = 文体大致合适但不精确
   2 = 文体有明显偏差
   1 = 文体完全错配
"""

步骤 2:设计并测试 Judge Prompt(45 分钟)

2.1 Pointwise Prompt(含 few-shot + CoT + 结构化输出)

POINTWISE_PROMPT = f"""你是一位专业的中英翻译质量评估员。

{RUBRIC}

请按以下步骤评估:
Step 1: 识别原文的关键信息点。
Step 2: 对照译文,逐条检查是否保留。
Step 3: 基于证据给出 4 个维度的分数。

**Few-shot 示例:**

原文:北京大学是中国顶尖的综合性大学。
译文:Peking University is one of China's top comprehensive universities.
评分:{{"faithfulness": 5, "fluency": 5, "terminology": 5, "style": 5, 
       "rationale": "完整准确;Peking University 为官方译名;自然流畅。"}}

原文:北京大学是中国顶尖的综合性大学。
译文:Beijing school is top in China.
评分:{{"faithfulness": 2, "fluency": 2, "terminology": 1, "style": 2,
       "rationale": "'school' 错译,漏译'综合性',用词口语化。"}}

---

**待评:**
原文:{{zh}}
译文:{{translation}}

仅输出 JSON,格式如上。
"""

2.2 Pairwise Prompt(含位置交换说明)

PAIRWISE_PROMPT = f"""你是一位专业的中英翻译质量评估员。

{RUBRIC}

比较以下两个译文(A 和 B),判断哪个翻译整体更好。
请先给出 4 个维度的简要分析,再给出最终判断。

原文:{{zh}}
译文 A:{{trans_a}}
译文 B:{{trans_b}}

仅输出 JSON:
{{"analysis": "...", "winner": "A" | "B" | "tie", "confidence": 0-1}}
"""

2.3 在 5 条样本上做烟测

import json

def parse_json(raw):
    raw = raw.strip().removeprefix("```json").removesuffix("```").strip()
    return json.loads(raw)

# 对 5 条样本跑 Pointwise
for p in pairs[:5]:
    raw = call_judge(POINTWISE_PROMPT.format(
        zh=p["zh"], translation=p["trans_a"]
    ))
    result = parse_json(raw)
    print(f"ZH: {p['zh']}")
    print(f"Trans A: {p['trans_a']}")
    print(f"Scores: {result}")
    print("-" * 60)

验证点

  • JSON 解析成功率应 ≥ 95%
  • 分数分布不应全都 4–5(说明 few-shot 没压缩分布)
  • rationale 应引用具体片段

步骤 3:跑 200 对 Pairwise 评测(60 分钟)

from collections import Counter
from tqdm import tqdm

def pairwise_judge(judge, zh, trans_a, trans_b):
    """单次 Pairwise 调用"""
    prompt = PAIRWISE_PROMPT.format(zh=zh, trans_a=trans_a, trans_b=trans_b)
    raw = call_judge(judge, prompt)
    result = parse_json(raw)
    return result["winner"]

# === 不带位置校正的原始评测 ===
raw_results = []
for p in tqdm(pairs):
    w = pairwise_judge("gpt-4o", p["zh"], p["trans_a"], p["trans_b"])
    raw_results.append(w)

print("原始结果:", Counter(raw_results))
# 可能看到:Counter({'A': 120, 'B': 70, 'tie': 10}) -> A 明显多,疑似位置偏差

步骤 4:位置偏差校正(30 分钟)

对每对样本再跑一次交换顺序,只采信两次一致的判决。

def robust_pairwise(judge, zh, trans_a, trans_b):
    """A/B 交换双跑"""
    v1 = pairwise_judge(judge, zh, trans_a, trans_b)  # 原顺序
    v2 = pairwise_judge(judge, zh, trans_b, trans_a)  # 交换
    
    # 映射到"实际胜者"
    # v1: A 表示 trans_a 在第一位获胜
    # v2: A 表示 trans_b 在第一位获胜(因为交换了)
    
    if v1 == "A" and v2 == "B":
        return "trans_a"  # 两次都选 trans_a
    elif v1 == "B" and v2 == "A":
        return "trans_b"  # 两次都选 trans_b
    elif v1 == v2 and v1 in ("A", "B"):
        return "position_bias"  # 两次都选同一位置
    else:
        return "tie"

# 跑校正版
robust_results = []
for p in tqdm(pairs):
    r = robust_pairwise("gpt-4o", p["zh"], p["trans_a"], p["trans_b"])
    robust_results.append(r)

print("校正后:", Counter(robust_results))
# 预期:Counter({'trans_a': 85, 'trans_b': 80, 'tie': 20, 'position_bias': 15})

计算位置偏差率

bias_rate = sum(1 for r in robust_results if r == "position_bias") / len(robust_results)
print(f"位置偏差率: {bias_rate:.2%}")
# 如果 > 15%,说明 Judge 存在显著位置偏差

步骤 5:人工标注 50 对作为金标(1.5–2 小时,独立完成)

从 200 对中随机抽 50 对,由至少 2 名标注员独立标注:

  1. 按同样 4 维度 rubric 给 trans_a 和 trans_b 各打 1-5 分
  2. 给出整体偏好(A / B / tie)

计算标注员间一致性(IAA),Cohen's κ 应 ≥ 0.5,否则需要校准 rubric。

# 示例:人工 gold 格式
human_gold = [
    {"pair_id": 0, "annotator_1": "A", "annotator_2": "A",
     "scores_a": [5,4,5,5], "scores_b": [3,3,3,4]},
    # ...
]

# 以多数投票聚合
def majority_vote(labels):
    c = Counter(labels).most_common()
    return c[0][0] if c[0][1] > 1 else "tie"

gold = [{"pair_id": g["pair_id"], 
         "gold": majority_vote([g["annotator_1"], g["annotator_2"]])}
        for g in human_gold]

步骤 6:与人工标注的相关性分析(45 分钟)

from scipy.stats import spearmanr, kendalltau
from sklearn.metrics import cohen_kappa_score

# 1. Pairwise 一致率 + Cohen's κ
gold_labels = [g["gold"] for g in gold]
judge_labels = [robust_results[g["pair_id"]] for g in gold]

# 归一化(position_bias 视为 tie)
judge_labels = ["tie" if l == "position_bias" else l for l in judge_labels]
judge_labels = [{"trans_a": "A", "trans_b": "B", "tie": "tie"}[l] for l in judge_labels]

agree = sum(1 for a, b in zip(gold_labels, judge_labels) if a == b) / len(gold)
kappa = cohen_kappa_score(gold_labels, judge_labels)

print(f"Pairwise 一致率: {agree:.2%}")
print(f"Cohen's κ: {kappa:.3f}")

# 2. Pointwise Spearman & Kendall(若同时跑了 Pointwise)
#    假设 judge_scores_a 和 gold_scores_a 是 50 条的 overall 分
rho, _ = spearmanr(judge_scores_a, gold_scores_a)
tau, _ = kendalltau(judge_scores_a, gold_scores_a)
print(f"Spearman ρ: {rho:.3f}")
print(f"Kendall τ:  {tau:.3f}")

目标

指标可用良好优秀
Cohen's κ≥ 0.4≥ 0.6≥ 0.8
Spearman ρ≥ 0.5≥ 0.7≥ 0.85
Pairwise 一致率≥ 70%≥ 80%≥ 90%

步骤 7:对比分析与报告(30 分钟)

跑 GPT-4o / Claude-3.5 / Qwen3-Max 三个 Judge,对比:

  • 位置偏差率
  • 与人工的 κ
  • 对 Model A vs. B 的胜率
judges = ["gpt-4o", "claude-3.5-sonnet", "qwen3-max"]
comparison = {}
for j in judges:
    results = run_pipeline(j, pairs, swap=True)
    comparison[j] = compute_metrics(results, gold)

# 表格展示
print_table(comparison)

只跑一个 Judge(如 GPT-4o),对比 不校正 vs. Swap 校正

配置A 胜率B 胜率Cohen's κ
不校正???
Swap 校正???

验证"位置校正是否提升与人类的一致性"。

对比 Reference-free vs. Reference-based(把 opus-100en 作为参考塞进 Prompt)。

观察:

  • Ref-based 的分数是否更稳定?
  • Self-preference 是否减弱(让 GPT-4o 评自己 vs. 让 GPT-4o 评 Qwen)?

交付物清单

  • rubric.md:最终版翻译 Rubric
  • judge_prompt.txt:最终版 Pointwise + Pairwise Prompt
  • results.json:200 对样本的原始 + 校正后判决
  • human_gold.json:50 对人工双标金标
  • metrics.md:包含以下 5 个数字
    • 位置偏差率
    • Pairwise 与人工一致率
    • Cohen's κ
    • Spearman ρ(若跑了 Pointwise)
    • Kendall τ
  • 2 页书面分析
    • 你的 Judge 达到了哪个可用水平?
    • 位置偏差在多大程度上影响结果?
    • Swap 校正带来了多少提升?
    • Ref-based vs. Ref-free 的取舍
    • 如果生产上线,你会如何配置(Judge 选型、抽检频率、兜底策略)?

加分项

  1. 冗长偏差诊断:人为把 Model A 的输出加长 30%(加冗余但不加信息),观察 Judge 分数变化。
  2. Length-Controlled 回归:按 AlpacaEval 2.0 LC 思路,拟合 sLC=srawβlog(len)s_{LC} = s_{\text{raw}} - \beta \log(\text{len})
  3. Judge 集成:用 3 个 Judge 投票,对比单 Judge 与 ensemble 的 κ。
  4. 错误类型统计:把 Judge 的 rationale 归类为 MQM 错误类别(Accuracy / Fluency / Terminology / Style),看哪种错误最常见。
  5. 对抗样本:构造"漂亮但错"(流畅但关键信息错)和"朴素但对"(粗糙但全对),测 Judge 能否识别。

时间管理

  • 步骤 1–2:1 小时 15 分
  • 步骤 3–4:1 小时 30 分
  • 步骤 5(独立):1.5–2 小时
  • 步骤 6–7:1 小时 15 分
  • 总计:约 5–6 小时(含人工标注)