Сравнительная оценка: A и B
Запросы для попарного сравнения, позволяющие ранжировать результаты без абсолютной оценки.
«Сравнительная оценка: A и B» — бесплатный урок AI Prompt Engineering на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Prompt Engineering, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Prompt Engineering содержит 4 уроков всего.
Что такое попарное оценивание (A против B)?
Попарное оценивание (также называемое сравнительным оцениванием) предоставляет судье два ответа на один и тот же вопрос и спрашивает, какой из них лучше. Вместо оценки одного ответа по шкале от 1 до 5 судья выносит относительное решение: A лучше, B лучше или это ничья.
Такой подход позволяет обойти некоторые виды смещения при абсолютном оценивании и часто даёт более надёжные рейтинги, чем абсолютные оценки отдельных ответов.
Базовый запрос для попарного оценивания
Самый простой запрос для попарного судьи спрашивает, какой ответ лучше и почему. Важно заставить судью сделать выбор — не позволяйте ему уклоняться от сравнения с помощью расплывчатого ответа «оба хороши».
import anthropic
import json
client = anthropic.Anthropic(api_key='sk-ant-...')
PAIRWISE_PROMPT = (
'Given the same question, compare these two responses and decide which is better.\n\n'
'Question: {question}\n\n'
'Response A:\n{response_a}\n\n'
'Response B:\n{response_b}\n\n'
'Which response is better? You must pick A, B, or TIE (use TIE only if '
'they are truly equal in all meaningful ways).\n\n'
'Return JSON: {{"winner": "A" or "B" or "TIE", '
'"reason": "<one sentence explaining why the winner is better>"}}'
)
def pairwise_judge(question, response_a, response_b):
prompt = PAIRWISE_PROMPT.format(
question=question,
response_a=response_a,
response_b=response_b
)
r = client.messages.create(
model='claude-opus-4-5',
max_tokens=150,
messages=[{'role': 'user', 'content': prompt}]
)
return json.loads(r.content[0].text)
result = pairwise_judge(
'What is machine learning?',
'Machine learning is a subset of AI.',
'Machine learning is a method of data analysis that automates model building.'
)
print(result)Случайное изменение порядка для уменьшения позиционного смещения
Позиционное смещение заставляет судей предпочитать первый вариант. Чтобы нейтрализовать его, выполняйте каждое сравнение дважды: сначала с A, затем с B. Засчитывайте победителя, только если оба порядка дают один и тот же результат. При расхождении объявляйте ничью или передавайте случай на проверку людям.
import anthropic
import json
import random
client = anthropic.Anthropic(api_key='sk-ant-...')
def debiased_pairwise_judge(question, response_a, response_b):
def single_comparison(first, second, first_label, second_label):
prompt = (
f'Question: {question}\n\n'
f'Response {first_label}:\n{first}\n\n'
f'Response {second_label}:\n{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()
# Run A-first
result_ab = single_comparison(response_a, response_b, 'A', 'B')
# Run B-first
result_ba = single_comparison(response_b, response_a, 'B', 'A')
if result_ab == 'A' and result_ba == 'A':
return 'A', 'Consistent: A wins in both orderings'
elif result_ab == 'B' and result_ba == 'B':
return 'B', 'Consistent: B wins in both orderings'
else:
return 'TIE', f'Inconsistent: {result_ab} then {result_ba} — position bias detected'
winner, reason = debiased_pairwise_judge(
'Explain a hash table.',
'A hash table maps keys to values.',
'A hash table is a data structure using a hash function to store key-value pairs for O(1) lookup.'
)
print(f'Winner: {winner} — {reason}')Попарное оценивание по нескольким критериям
Просите судью сравнивать ответы по конкретным измерениям, а не просто спрашивать, «какой из них в целом лучше». Попарное сравнение по отдельным измерениям даёт полезную информацию о том, почему предпочтение отдано одному ответу.
import anthropic
import json
client = anthropic.Anthropic(api_key='sk-ant-...')
DIMENSIONAL_PAIRWISE = (
'Compare Response A and Response B on each dimension.\n\n'
'Question: {question}\n\n'
'Response A: {response_a}\n\n'
'Response B: {response_b}\n\n'
'For each dimension, say A, B, or TIE:\n'
'1. ACCURACY: Which is more factually correct?\n'
'2. COMPLETENESS: Which answers the question more fully?\n'
'3. CLARITY: Which is easier to understand?\n'
'4. CONCISENESS: Which avoids unnecessary length?\n\n'
'Return JSON: {{"accuracy":"A/B/TIE", "completeness":"A/B/TIE", '
'"clarity":"A/B/TIE", "conciseness":"A/B/TIE", "overall":"A/B/TIE"}}'
)
def dimensional_compare(question, a, b):
r = client.messages.create(
model='claude-opus-4-5',
max_tokens=200,
messages=[{'role': 'user', 'content': DIMENSIONAL_PAIRWISE.format(
question=question, response_a=a, response_b=b
)}]
)
return json.loads(r.content[0].text)
result = dimensional_compare(
'How does HTTPS work?',
'HTTPS encrypts web traffic using SSL/TLS.',
'HTTPS secures HTTP using TLS. The browser and server perform a handshake, exchange certificates, and establish an encrypted channel for all data transfer.'
)
print(result)Построение турнира: круговая система
Если нужно сравнить более двух ответов, используйте круговой турнир: каждый ответ сравнивается с каждым другим ответом. Ответ с наибольшим числом побед занимает первое место.
from itertools import combinations
from collections import defaultdict
def round_robin_tournament(question, responses):
"""
responses: list of (label, text) tuples
Returns ranking by win count.
"""
wins = defaultdict(int)
ties = defaultdict(int)
# Every pair compared once
for (label_a, text_a), (label_b, text_b) in combinations(responses, 2):
winner, reason = debiased_pairwise_judge(question, text_a, text_b)
if winner == 'A':
wins[label_a] += 1
elif winner == 'B':
wins[label_b] += 1
else: # TIE
ties[label_a] += 1
ties[label_b] += 1
print(f'{label_a} vs {label_b}: {winner}')
# Rank by wins, then ties
ranking = sorted(
responses,
key=lambda x: (wins[x[0]], ties[x[0]]),
reverse=True
)
print('\nFinal ranking:')
for i, (label, _) in enumerate(ranking, 1):
print(f'{i}. {label}: {wins[label]} wins, {ties[label]} ties')
return rankingРейтинг Эло для непрерывного ранжирования
Для масштабного оценивания используйте рейтинги Эло вместо кругового турнира. Каждый ответ начинает с 1000 очков. После каждого сравнения победитель получает очки, а проигравший теряет их — пропорционально тому, насколько неожиданным был результат. После множества сравнений оценки Эло формируют надёжный общий рейтинг.
import math
def elo_update(rating_a, rating_b, winner, k=32):
"""
Update Elo ratings after a match.
winner: 'A' (A won), 'B' (B won), 'TIE' (draw)
Returns (new_rating_a, new_rating_b)
"""
expected_a = 1 / (1 + 10 ** ((rating_b - rating_a) / 400))
expected_b = 1 - expected_a
if winner == 'A':
score_a, score_b = 1, 0
elif winner == 'B':
score_a, score_b = 0, 1
else: # TIE
score_a, score_b = 0.5, 0.5
new_a = rating_a + k * (score_a - expected_a)
new_b = rating_b + k * (score_b - expected_b)
return new_a, new_b
# Simulate ratings for 4 model variants
ratings = {'ModelA': 1000, 'ModelB': 1000, 'ModelC': 1000, 'ModelD': 1000}
# After running many pairwise comparisons:
ratings['ModelA'], ratings['ModelB'] = elo_update(ratings['ModelA'], ratings['ModelB'], 'A')
ratings['ModelC'], ratings['ModelD'] = elo_update(ratings['ModelC'], ratings['ModelD'], 'TIE')
ranking = sorted(ratings.items(), key=lambda x: x[1], reverse=True)
for model, score in ranking:
print(f'{model}: {score:.0f}')Когда использовать попарное и абсолютное оценивание
Используйте попарное оценивание, когда:
- Вы хотите ранжировать несколько моделей или вариантов запросов
- Трудно определить пороги абсолютных оценок
- Вы сравниваете результаты, которые оба «хороши», но хороши по-разному
Используйте абсолютное оценивание (по системе критериев), когда:
- Вам нужен порог «пройдено/не пройдено» («достаточно ли хорош этот ответ?»)
- Для каждого запроса у вас есть только один ответ для оценивания
- Вам нужны оценки по отдельным критериям для отладки
Масштабное попарное оценивание на основе выборки
Полный перебор всех пар для N ответов требует N*(N-1)/2 сравнений — O(N^2). При большом N используйте случайную выборку: сравнивайте каждый ответ с K случайно выбранными соперниками, а не со всеми остальными. Это позволяет приблизительно восстановить настоящий рейтинг с гораздо меньшими затратами.
import random
from collections import defaultdict
def sampled_tournament(question, responses, k_opponents=5):
"""
Compare each response against k random opponents.
More efficient than full round-robin for large N.
"""
wins = defaultdict(int)
n = len(responses)
for i, (label, text) in enumerate(responses):
# Sample k random opponents (not self)
opponent_indices = random.sample(
[j for j in range(n) if j != i],
min(k_opponents, n - 1)
)
for j in opponent_indices:
opp_label, opp_text = responses[j]
winner, _ = debiased_pairwise_judge(question, text, opp_text)
if winner == 'A':
wins[label] += 1
elif winner == 'B':
wins[opp_label] += 1
ranking = sorted(responses, key=lambda x: wins[x[0]], reverse=True)
for i, (label, _) in enumerate(ranking, 1):
print(f'{i}. {label}: {wins[label]} wins')
return rankingИнтерпретация расхождений
Когда два порядка подачи дают разные результаты (A побеждает в порядке A–B, а B — в порядке B–A), это указывает на действительно пограничное сравнение, а не просто на позиционное смещение. Такие пограничные случаи заслуживают более глубокого анализа.
import anthropic
import json
client = anthropic.Anthropic(api_key='sk-ant-...')
def deep_analyze_tie(question, response_a, response_b):
"""When A vs B is a genuine tie, ask the judge to explain strengths of each."""
analysis_prompt = (
f'These two responses to the same question are closely matched in quality.\n\n'
f'Question: {question}\n\n'
f'Response A: {response_a}\n\n'
f'Response B: {response_b}\n\n'
f'Analyze both:\n'
f'1. What does A do better than B?\n'
f'2. What does B do better than A?\n'
f'3. For what audience or context would you prefer A?\n'
f'4. For what audience or context would you prefer B?'
)
r = client.messages.create(
model='claude-opus-4-5',
max_tokens=400,
messages=[{'role': 'user', 'content': analysis_prompt}]
)
return r.content[0].text
analysis = deep_analyze_tie(
'Explain async/await in Python.',
'Async/await allows non-blocking code execution.',
'Async/await lets you write asynchronous code that looks synchronous, '
'using the asyncio event loop to handle I/O without blocking.'
)
print(analysis[:300])Запись результатов попарного оценивания
Записывайте все результаты попарных сравнений, чтобы создать доступный для поиска журнал: какие ответы побеждали какие и при каких условиях. Эти данные ценны для регрессионных проверок и понимания улучшений моделей с течением времени.
import json
import datetime
def logged_pairwise(question, response_a, response_b,
label_a='A', label_b='B', log_file='pairwise_log.jsonl'):
winner, reason = debiased_pairwise_judge(question, response_a, response_b)
entry = {
'timestamp': datetime.datetime.utcnow().isoformat(),
'question': question[:100], # Truncate for storage
'label_a': label_a,
'label_b': label_b,
'winner': winner,
'reason': reason,
'response_a_length': len(response_a.split()),
'response_b_length': len(response_b.split()),
}
with open(log_file, 'a') as f:
f.write(json.dumps(entry) + '\n')
print(f'{label_a} vs {label_b}: {winner} — {reason}')
return winner
logged_pairwise(
'What is SQL?',
'SQL is a database query language.',
'SQL (Structured Query Language) is used to query and manage relational databases.',
label_a='ModelV1',
label_b='ModelV2'
)Попарное оценивание и абсолютное оценивание: компромиссы
Попарное оценивание и абсолютное оценивание по системе критериев дополняют друг друга, а не конкурируют. Используйте их вместе: абсолютное оценивание задаёт минимально приемлемый уровень качества, а попарное сравнение ранжирует кандидатов, соответствующих этому уровню.
Главный компромисс заключается в следующем: для N ответов попарное оценивание требует O(N^2) сравнений, а абсолютное — O(N). При большом масштабе становятся необходимыми попарное оценивание на основе выборки или рейтинги Эло.
Проверка знаний: устранение смещения при попарном оценивании
Как эффективнее всего уменьшить позиционное смещение при попарном оценивании с помощью LLM?
Повторение: сравнительное оценивание A против B
Попарное оценивание определяет, какой из двух ответов лучше, вместо того чтобы оценивать каждый по абсолютной шкале. Чтобы контролировать позиционное смещение, выполняйте каждое сравнение дважды, меняя порядок, и принимайте только согласующиеся результаты. Для N ответов используйте круговой турнир (все пары) при малом N или турниры на основе выборки при большом N. Рейтинги Эло формируют непрерывное ранжирование по множеству попарных сравнений. Используйте попарное оценивание по отдельным критериям (точность, полнота и ясность оцениваются независимо), чтобы получать полезную диагностическую информацию. Записывайте все результаты для регрессионных проверок и анализа тенденций в разных версиях моделей.
Часто задаваемые вопросы
Урок «Сравнительная оценка: A и B» бесплатный?
Да — полный текст урока «Сравнительная оценка: A и B» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Prompt Engineering, подпишись на CoddyKit PRO. Курс AI Prompt Engineering содержит 4 уроков всего.
Чему я научусь в уроке «Сравнительная оценка: A и B»?
Запросы для попарного сравнения, позволяющие ранжировать результаты без абсолютной оценки. Ты практикуешь AI Prompt Engineering с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Prompt Engineering?
Предыдущий опыт не требуется. AI Prompt Engineering на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Сравнительная оценка: A и B»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Prompt Engineering?
Да. Каждый урок AI Prompt Engineering включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Оценка результатов LLM с помощью LLM
- Запросы для оценки по критериям
- Сравнительная оценка: A и B
- Калибровка и предвзятость судей на основе LLM