Калибровка и предвзятость судей на основе LLM
Предвзятость к позиции, предвзятость к многословию и способы их снижения в запросах для судьи.
«Калибровка и предвзятость судей на основе LLM» — бесплатный урок AI Prompt Engineering на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Prompt Engineering, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Prompt Engineering содержит 4 уроков всего.
Почему важна калибровка судьи
Судья на основе LLM, который систематически оценивает определённый тип ответа выше, чем он заслуживает, создаёт вводящие в заблуждение результаты оценивания. Вы можете выпустить более слабую модель, потому что судье понравился её многословный стиль, а не её реальное качество.
Калибровка означает, что оценки судьи точно отражают реальное качество. Калиброванный судья согласуется с людьми-оценщиками в измеримой доле случаев и не отдаёт систематического предпочтения какому-либо атрибуту, не связанному с качеством.
Позиционное смещение: подробный разбор
Позиционное смещение — самое сильное и наиболее изученное смещение у судей на основе LLM. При попарном сравнении судьи предпочитают первый вариант в 60–65% случаев независимо от качества. Это равносильно монете, которая выпадает орлом в 60% случаев, — на большом числе сравнений эффект становится значимым.
Смещение возникает потому, что LLM обучаются генерировать продолжения: увидев сначала «Ответ A:», они заранее склоняются к A ещё до того, как прочитают B.
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 разные модели в роли судей для оцениваний с высокими последствиями
- Явно задайте опорные уровни оценок, чтобы предотвратить их завышение
- Проведите калибровку по оценкам людей на репрезентативной выборке
- Записывайте и отслеживайте доли побед по метке модели, чтобы обнаруживать дрейф
- Добавьте вывод в структурированном формате, чтобы оценки не скрывались в произвольном тексте
Журналы аудита и объяснимость
Калиброванный оценщик также должен быть объяснимым. Если оценщик присваивает ответу оценку 4/5, вы должны иметь возможность понять почему: какие критерии выполнены, а какие — нет.
Требование возвращать JSON с оценками по каждому критерию и кратким обоснованием создаёт естественный журнал аудита. Заинтересованные стороны могут проверить, почему конкретные ответы получили именно такие оценки, а вы — выявить повторяющиеся закономерности среди низких оценок.
Проверка знаний: снижение смещения в пользу собственного стиля
Какая стратегия снижения смещения MOST непосредственно устраняет смещение в пользу собственного стиля у оценщиков на основе LLM?
Итоги: калибровка и смещения у оценщиков на основе LLM
У оценщиков на основе LLM есть четыре основных систематических смещения: позиционное смещение (предпочтение первого варианта), смещение в пользу многословия (предпочтение более длинных ответов), предпочтение собственного стиля и завышение оценок (скопление оценок в диапазоне 4–5 из 5). Для каждого смещения используйте отдельную меру: меняйте порядок вариантов при позиционном смещении, добавляйте инструкции против излишней многословности при смещении в пользу многословия, используйте разные модели-оценщики при предпочтении собственного стиля и применяйте рубрики с опорными примерами при завышении оценок. Калибруйте оценщика по человеческим оценкам на размеченном наборе данных и измеряйте корреляцию Пирсона. Отслеживайте доли побед по меткам с течением времени, чтобы выявлять возникающие закономерности смещения до того, как они исказят ваш конвейер оценки.
Часто задаваемые вопросы
Урок «Калибровка и предвзятость судей на основе LLM» бесплатный?
Да — полный текст урока «Калибровка и предвзятость судей на основе LLM» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Prompt Engineering, подпишись на CoddyKit PRO. Курс AI Prompt Engineering содержит 4 уроков всего.
Чему я научусь в уроке «Калибровка и предвзятость судей на основе LLM»?
Предвзятость к позиции, предвзятость к многословию и способы их снижения в запросах для судьи. Ты практикуешь AI Prompt Engineering с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Prompt Engineering?
Предыдущий опыт не требуется. AI Prompt Engineering на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Калибровка и предвзятость судей на основе LLM»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Prompt Engineering?
Да. Каждый урок AI Prompt Engineering включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Оценка результатов LLM с помощью LLM
- Запросы для оценки по критериям
- Сравнительная оценка: A и B
- Калибровка и предвзятость судей на основе LLM