0Pricing
AI Prompt Engineering · 课时

LLM 评审中的校准与偏差

位置偏差、冗长偏差,以及如何在评审提示词中减轻这些偏差。

LLM 评审中的校准与偏差 是 CoddyKit 上的免费 AI Prompt Engineering 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Prompt Engineering 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Prompt Engineering 课程共包含 4 节课。

评审模型校准为何重要

如果 LLM 评审模型系统性地给某类回答打出高于其应得水平的分数,就会产生误导性的评估结果。您可能会因为评审模型偏好某个模型的冗长风格,而不是其实际质量,错误地发布一个更差的模型。

校准意味着评审模型的分数能够准确反映真实质量。经过校准的评审模型会以可量化的比例与人类评分者达成一致,并且不会系统性地偏好任何与质量无关的单一属性。

位置偏差:深入分析

位置偏差是 LLM 评审模型最强、研究最多的偏差。在成对比较中,无论质量如何,评审模型有 60%—65% 的时间偏好第一个选项。这相当于一枚有 60% 概率正面朝上的硬币,在大规模场景下具有统计显著性。

这种偏差存在的原因是 LLM 经过训练要生成后续内容——先看到“回答 A:”会在它读到 B 之前使其倾向于 A。

import anthropic

client = anthropic.Anthropic(api_key='sk-ant-...')

def measure_position_bias(question, n_pairs=20):
    """
    Measure position bias by comparing IDENTICAL responses.
    If both responses are the same, wins should be 50/50.
    Any deviation from 50/50 is pure position bias.
    """
    response = 'Machine learning is a subset of AI that learns from data.'
    first_wins = 0

    for _ in range(n_pairs):
        prompt = (
            f'Which response is better?\nQ: {question}\n'
            f'Response A: {response}\n'
            f'Response B: {response}\n'
            f'Reply with A or B.'
        )
        r = client.messages.create(
            model='claude-opus-4-5',
            max_tokens=5,
            messages=[{'role': 'user', 'content': prompt}]
        )
        if 'A' in r.content[0].text:
            first_wins += 1

    bias = first_wins / n_pairs
    print(f'First-position win rate with IDENTICAL responses: {bias:.0%}')
    print(f'Expected (no bias): 50%')
    print(f'Measured bias: {(bias - 0.5) * 100:+.0f}%')
    return bias

冗长偏差:深入分析

冗长偏差会使评审模型偏好较长的回答,即使较短的回答更加准确和完整。研究表明,在控制内容质量后,在成对比较中,较长回答被评为“更好”的频率是较短回答的 1.5—2 倍。

这种偏差可能源于这样的训练数据:人类评分者也会将篇幅与质量混为一谈。

import anthropic

client = anthropic.Anthropic(api_key='sk-ant-...')

def measure_verbosity_bias(question, correct_answer):
    """
    Compare a concise correct answer against a verbose one with filler.
    A good judge should prefer the concise version or call it a tie.
    """
    concise = correct_answer
    verbose = (
        f'That is a great question! I am happy to help. '
        f'{correct_answer} '
        f'I hope this comprehensive explanation addresses all your needs. '
        f'Please feel free to ask if you need any further clarification!'
    )

    for label, resp in [('Concise', concise), ('Verbose', verbose)]:
        prompt = (
            f'Rate this response 1-5 for quality.\n'
            f'Q: {question}\nA: {resp}\n'
            f'Return only a number 1-5.'
        )
        r = client.messages.create(
            model='claude-opus-4-5',
            max_tokens=5,
            messages=[{'role': 'user', 'content': prompt}]
        )
        score = r.content[0].text.strip()
        print(f'{label} response score: {score}')
        print(f'  ({len(resp.split())} words)')

自我偏好偏差:深入分析

克劳德评审模型平均会给克劳德生成的回答更高评分。GPT-4 评审模型会给 GPT-4 生成的回答更高评分。多项独立研究已经证明了这一点。

其作用机制是:每个模型都有独特的风格,包括句子结构、模糊限定表达模式和词汇选择。同一个模型能够识别并偏好自身的风格。

import anthropic
import openai

anthropic_client = anthropic.Anthropic(api_key='sk-ant-...')
openai_client = openai.OpenAI(api_key='sk-...')

def test_self_preference(question):
    # Generate one response per model
    claude_answer = anthropic_client.messages.create(
        model='claude-opus-4-5', max_tokens=100,
        messages=[{'role': 'user', 'content': question}]
    ).content[0].text

    gpt_answer = openai_client.chat.completions.create(
        model='gpt-4o', max_tokens=100,
        messages=[{'role': 'user', 'content': question}]
    ).choices[0].message.content

    judge_prompt = (
        f'Which response is better?\nQ: {question}\n'
        f'Response A: {claude_answer}\n'
        f'Response B: {gpt_answer}\n'
        f'Reply: A or B'
    )

    # Claude judges the comparison
    claude_verdict = anthropic_client.messages.create(
        model='claude-opus-4-5', max_tokens=5,
        messages=[{'role': 'user', 'content': judge_prompt}]
    ).content[0].text.strip()

    print(f'Claude judge verdict: {claude_verdict}')
    print('(A=Claude response, B=GPT response)')
    print('Self-preference: did Claude prefer its own response?')

缓解措施 1:交换顺序

缓解位置偏差最有效的单一方法,是对每次成对比较运行两遍并交换顺序,只使用结果一致的比较结果。请将其实现为一个标准函数,让所有评估都通过该函数。

import anthropic
import json

client = anthropic.Anthropic(api_key='sk-ant-...')

def debiased_compare(question, response_a, response_b, label_a='A', label_b='B'):
    def single_pass(first, second, first_label, second_label):
        prompt = (
            f'Q: {question}\n\n'
            f'{first_label}: {first}\n\n'
            f'{second_label}: {second}\n\n'
            f'Which is better? Reply with {first_label}, {second_label}, or TIE.'
        )
        r = client.messages.create(
            model='claude-opus-4-5',
            max_tokens=10,
            messages=[{'role': 'user', 'content': prompt}]
        )
        return r.content[0].text.strip()

    # Pass 1: A first
    p1 = single_pass(response_a, response_b, label_a, label_b)
    # Pass 2: B first — but labels stay the same (we just swap presentation order)
    p2 = single_pass(response_b, response_a, label_b, label_a)

    # Normalize p2: if judge said label_b in pass 2, that means they preferred first-shown
    # Need to map back: if p2 = label_b, the 'first' won; if p2 = label_a, the 'second' won
    if p1 == p2 and p1 != 'TIE':
        return p1, 'Consistent result'
    else:
        return 'TIE', f'Inconsistent: pass1={p1}, pass2={p2}'

winner, reason = debiased_compare(
    'What is Docker?',
    'Docker is a containerization platform.',
    'Docker allows you to package applications into containers for consistent deployment.'
)
print(f'{winner}: {reason}')

缓解措施 2:使用多个不同的评审模型

使用多个不同模型作为评审模型并汇总它们的判断结果,可以减少自我偏好偏差。当克劳德、GPT-4 和双子座模型都达成一致时,结果比任何单个模型的判断更值得信赖。

import anthropic
import openai

anthropic_client = anthropic.Anthropic(api_key='sk-ant-...')
openai_client = openai.OpenAI(api_key='sk-...')

def multi_model_pairwise(question, response_a, response_b):
    results = {}

    # Judge 1: Claude
    r1 = anthropic_client.messages.create(
        model='claude-opus-4-5', max_tokens=5,
        messages=[{'role': 'user', 'content':
            f'Q: {question}\nA: {response_a}\nB: {response_b}\n'
            f'Which is better? Reply A, B, or TIE.'
        }]
    )
    results['Claude'] = r1.content[0].text.strip()

    # Judge 2: GPT-4o
    r2 = openai_client.chat.completions.create(
        model='gpt-4o', max_tokens=5,
        messages=[{'role': 'user', 'content':
            f'Q: {question}\nA: {response_a}\nB: {response_b}\n'
            f'Which is better? Reply A, B, or TIE.'
        }]
    )
    results['GPT4o'] = r2.choices[0].message.content.strip()

    print(f'Claude judge: {results["Claude"]}')
    print(f'GPT-4o judge: {results["GPT4o"]}')

    # Aggregate: majority vote
    votes = list(results.values())
    if votes.count('A') >= 2: return 'A'
    if votes.count('B') >= 2: return 'B'
    return 'TIE'

final = multi_model_pairwise(
    'Explain recursion.',
    'A function that calls itself.',
    'Recursion is when a function solves a problem by solving a smaller version of the same problem.'
)
print(f'Final verdict: {final}')

缓解措施 3:避免冗长的指令

请直接指示评审模型惩罚冗长并奖励简洁。这可以抵消冗长偏差,否则该偏差会让较长的回答显得更好。

ANTI_VERBOSITY_JUDGE = (
    'Evaluate this response. Apply these corrections for known judge biases:\n\n'
    'VERBOSITY CORRECTION: Do NOT rate a response higher simply because it is longer. '
    'A concise, accurate 20-word answer is better than a 200-word answer that says the same thing. '
    'Actively penalize unnecessary padding, filler phrases like "Great question!", '
    'and repetition.\n\n'
    'LENGTH PENALTY: If the response contains introductory filler, closing remarks, '
    'or restates the question, subtract 1 point from your score.\n\n'
    'Question: {question}\n'
    'Response: {response}\n\n'
    'Score 1-5 (apply verbosity correction above):'
)

# This instruction significantly reduces verbosity inflation in practice
print('Anti-verbosity instructions reduce the length-quality conflation')

根据人类评分者进行校准

评审模型校准的金标准,是在校准集上将您的 LLM 评审模型分数与人类评分者分数进行比较。请衡量一致程度,并找出系统性偏差。

import anthropic
import json
from scipy import stats  # pip install scipy

client = anthropic.Anthropic(api_key='sk-ant-...')

def calibrate_judge(calibration_set):
    """
    calibration_set: list of dicts with:
    {'question': str, 'response': str, 'human_score': float}
    """
    llm_scores = []
    human_scores = []

    for item in calibration_set:
        prompt = (
            f'Rate this response 1-5.\n'
            f'Q: {item["question"]}\nA: {item["response"]}\n'
            f'Return only a number.'
        )
        r = client.messages.create(
            model='claude-opus-4-5',
            max_tokens=5,
            messages=[{'role': 'user', 'content': prompt}]
        )
        try:
            llm_score = float(r.content[0].text.strip())
        except ValueError:
            llm_score = 3.0  # Default on parse error

        llm_scores.append(llm_score)
        human_scores.append(item['human_score'])

    correlation, p_value = stats.pearsonr(llm_scores, human_scores)
    print(f'LLM-Human correlation: {correlation:.3f} (p={p_value:.4f})')
    print(f'LLM mean score: {sum(llm_scores)/len(llm_scores):.2f}')
    print(f'Human mean score: {sum(human_scores)/len(human_scores):.2f}')
    return correlation

检测系统性偏差模式

请分析您的评审模型在不同回答类别下的输出,以检测系统性偏差。评审模型是否始终给来自某个模型的回答更高分?是否会惩罚涉及某些主题的回答?

import json
from collections import defaultdict

def analyze_judge_bias(log_file='pairwise_log.jsonl'):
    """
    Analyze pairwise logs to detect systematic bias.
    """
    wins_by_label = defaultdict(int)
    total_by_label = defaultdict(int)

    with open(log_file) as f:
        for line in f:
            entry = json.loads(line)
            winner = entry['winner']
            label_a = entry['label_a']
            label_b = entry['label_b']

            if winner == 'A':
                wins_by_label[label_a] += 1
            elif winner == 'B':
                wins_by_label[label_b] += 1

            total_by_label[label_a] += 1
            total_by_label[label_b] += 1

    print('Win rate by model:')
    for label in sorted(total_by_label):
        total = total_by_label[label]
        wins = wins_by_label[label]
        print(f'  {label}: {wins}/{total} = {wins/total:.0%}')

    # Flag if any model wins >60% — likely systematic bias
    for label in total_by_label:
        rate = wins_by_label[label] / total_by_label[label]
        if rate > 0.65 or rate < 0.35:
            print(f'WARNING: {label} win rate {rate:.0%} suggests systematic bias')

偏差缓解检查清单

在将 LLM 评审模型部署到生产环境之前,请使用以下检查清单:

  • 对所有成对比较运行两遍并交换顺序
  • 在评分标准中加入明确的避免冗长指令
  • 对于高风险评估,至少使用 2 个不同模型作为评审模型
  • 明确锚定分数等级,以防止评分膨胀
  • 根据具有代表性的样本与人类评分者进行校准
  • 按模型标签记录并监控胜率,以检测漂移
  • 添加结构化 JSON 输出,防止分数隐藏在自由文本中

审计轨迹与可解释性

经过校准的评审器还必须具备可解释性。如果评审器给某个响应打出 4/5 分,您应该能够追溯其中的原因——哪些标准得到了满足,哪些没有达标。

要求评审器返回包含各项标准得分和简短理由的 JSON,就能自然形成审计轨迹。相关方可以查看特定响应为何得到这样的分数,您也可以发现低分中反复出现的模式。

知识检查:缓解自偏好

哪种缓解策略最直接地应对 LLM 评审器中的自偏好偏差?

回顾:LLM 评审器的校准与偏差

LLM 评审器主要存在四种系统性偏差:位置偏差(偏好第一项)、冗长度偏差(偏好更长的响应)、自偏好(偏好自身的风格)以及评分膨胀(分数集中在 4–5/5)。请针对每种偏差采取具体措施:通过交换顺序缓解位置偏差,添加反冗长度指令缓解冗长度偏差,使用多样化的模型评审器缓解自偏好,并使用锚定式评分标准缓解评分膨胀。请在带标签的数据集上,将评审器与人类评分员进行校准,并测量皮尔逊相关系数。按标签持续监控胜率,以便在新出现的偏差模式扭曲评估流程之前及时发现它们。

常见问题解答

「LLM 评审中的校准与偏差」课时是免费的吗?

是的 — 「LLM 评审中的校准与偏差」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Prompt Engineering 课程的其余内容,请升级到 CoddyKit PRO。 AI Prompt Engineering 课程共包含 4 节课。

「LLM 评审中的校准与偏差」这节课中我会学到什么?

位置偏差、冗长偏差,以及如何在评审提示词中减轻这些偏差。 你通过在浏览器中直接运行的动手代码来练习 AI Prompt Engineering,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 AI Prompt Engineering 需要有经验吗?

无需任何先前经验。CoddyKit 上的 AI Prompt Engineering 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「LLM 评审中的校准与偏差」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 AI Prompt Engineering 课中编写并运行代码吗?

能。每节 AI Prompt Engineering 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 LLM 评估 LLM 输出
  2. 基于评分标准的评分提示词
  3. 比较式评审:A 与 B
  4. LLM 评审中的校准与偏差
← 返回 AI Prompt Engineering