Kalibracja i uprzedzenia sędziów LLM
Uprzedzenie pozycji, uprzedzenie na rzecz rozwlekłości oraz sposoby ograniczania ich w promptach sędziego.
Kalibracja i uprzedzenia sędziów LLM to bezpłatna lekcja AI Prompt Engineering na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Prompt Engineering, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Prompt Engineering zawiera 4 lekcji w sumie.
Dlaczego kalibracja sędziego ma znaczenie
Sędzia LLM, który systematycznie ocenia jeden typ odpowiedzi wyżej, niż na to zasługuje, prowadzi do mylących wyników ewaluacji. Mogą Państwo wdrożyć gorszy model, ponieważ sędzia preferował jego rozwlekły styl, a nie rzeczywistą jakość.
Kalibracja oznacza, że oceny sędziego dokładnie odzwierciedlają rzeczywistą jakość. Skalibrowany sędzia zgadza się z oceniającymi ludźmi w mierzalnym stopniu i nie faworyzuje systematycznie żadnej cechy niezwiązanej z jakością.
Stronniczość pozycji: szczegółowa analiza
Stronniczość pozycji jest najsilniejszą i najlepiej zbadaną formą stronniczości sędziów LLM. W porównywaniu parami sędziowie preferują pierwszą opcję w 60–65% przypadków, niezależnie od jakości. Jest to odpowiednik monety, która wypada orłem w 60% rzutów — przy dużej skali ma to istotne znaczenie.
Ta stronniczość występuje, ponieważ LLM-y są trenowane do generowania kontynuacji — zobaczenie najpierw „Response A:” ukierunkowuje je na A, zanim przeczytają 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 biasStronniczość związana z długością odpowiedzi: szczegółowa analiza
Stronniczość związana z długością odpowiedzi sprawia, że sędziowie preferują dłuższe odpowiedzi, nawet gdy krótsze są trafniejsze i bardziej kompletne. Badania pokazują, że w porównaniach parami sędziowie LLM 1,5–2 razy częściej uznają dłuższe odpowiedzi za „lepsze”, przy porównywalnej jakości treści.
Ta stronniczość prawdopodobnie wynika z danych treningowych, w których oceniający ludzie również utożsamiają długość z jakością.
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)')Stronniczość preferowania własnego stylu: szczegółowa analiza
Sędziowie Claude oceniają średnio wyżej odpowiedzi wygenerowane przez Claude. Sędziowie GPT-4 oceniają wyżej odpowiedzi wygenerowane przez GPT-4. Wykazano to w wielu niezależnych badaniach.
Mechanizm jest następujący: każdy model ma charakterystyczny styl — strukturę zdań, schematy wyrażania zastrzeżeń i dobór słownictwa. Ten sam model rozpoznaje własny styl i go preferuje.
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?')Ograniczanie 1: zamiana kolejności
Najskuteczniejszy pojedynczy sposób ograniczenia stronniczości pozycji polega na dwukrotnym przeprowadzeniu każdego porównania parami, z zamienioną kolejnością, i wykorzystaniu wyłącznie spójnych wyników. Należy zaimplementować to jako standardową funkcję, przez którą przechodzą wszystkie ewaluacje.
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}')Ograniczanie 2: wielu zróżnicowanych sędziów
Stronniczość preferowania własnego stylu można ograniczyć, korzystając z wielu różnych modeli jako sędziów i agregując ich werdykty. Gdy Claude, GPT-4 i Gemini są zgodne, wynik jest znacznie bardziej wiarygodny niż werdykt pojedynczego modelu.
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}')Ograniczanie 3: instrukcje przeciwdziałające rozwlekłości
Należy bezpośrednio poinstruować sędziego, aby obniżał oceny za rozwlekłość i nagradzał zwięzłość. Przeciwdziała to stronniczości związanej z długością odpowiedzi, która w przeciwnym razie sprawiałaby, że dłuższe odpowiedzi wydawałyby się lepsze.
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')Kalibracja względem oceniających ludzi
Złoty standard kalibracji sędziego polega na porównaniu ocen sędziego LLM z ocenami ludzi na zbiorze kalibracyjnym. Należy zmierzyć zgodność i zidentyfikować systematyczne rozbieżności.
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 correlationWykrywanie wzorców systematycznej stronniczości
Należy analizować wyniki sędziego dla różnych kategorii odpowiedzi, aby wykrywać systematyczną stronniczość. Czy sędzia konsekwentnie ocenia wyżej odpowiedzi pochodzące od jednego modelu? Czy obniża oceny odpowiedzi dotyczących określonych tematów?
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')Lista kontrolna ograniczania stronniczości
Lista kontrolna, którą należy zastosować przed wdrożeniem sędziego LLM na produkcji:
- Przeprowadzać wszystkie porównania parami dwa razy, zamieniając kolejność
- Uwzględnić w rubryce wyraźne instrukcje przeciwdziałające rozwlekłości
- W ewaluacjach wysokiej stawki używać co najmniej 2 różnych modeli jako sędziów
- Wyraźnie zakotwiczać poziomy ocen, aby zapobiegać ich zawyżaniu
- Kalibrować wyniki względem oceniających ludzi na reprezentatywnej próbie
- Rejestrować i monitorować współczynniki zwycięstw według etykiety modelu, aby wykrywać zmiany
- Dodawać uporządkowany JSON, aby zapobiegać ukrywaniu ocen w tekście swobodnym
Ścieżki audytu i wyjaśnialność
Skalibrowany model oceniający musi być również wyjaśnialny. Jeśli przyzna odpowiedzi ocenę 4/5, powinni Państwo móc prześledzić, dlaczego tak się stało — które kryteria zostały spełnione, a które nie.
Wymaganie, aby model oceniający zwracał kod JSON z ocenami dla poszczególnych kryteriów i krótkim uzasadnieniem, tworzy naturalną ścieżkę audytu. Interesariusze mogą sprawdzić, dlaczego konkretne odpowiedzi otrzymały dane oceny, a Państwo mogą wykrywać powtarzające się wzorce wśród niskich ocen.
Sprawdzenie wiedzy: ograniczanie samopreferencji
Jaka strategia ograniczania błędu NAJBEZPOŚREDNIEJ odnosi się do stronniczości samopreferencji wśród modeli LLM oceniających?
Podsumowanie: kalibracja i stronniczość modeli LLM oceniających
Modele LLM oceniające wykazują cztery główne systematyczne rodzaje stronniczości: stronniczość pozycji (preferowanie pierwszej opcji), stronniczość na rzecz rozwlekłości (preferowanie dłuższych odpowiedzi), samopreferencję (preferowanie własnego stylu) oraz zawyżanie ocen (koncentrowanie ocen na poziomie 4–5/5). Każdy z tych problemów należy ograniczać w konkretny sposób: zamieniać kolejność opcji w przypadku stronniczości pozycji, dodawać instrukcje przeciwdziałające rozwlekłości w przypadku stronniczości na rzecz rozwlekłości, używać zróżnicowanych modeli oceniających w przypadku samopreferencji oraz stosować zakotwiczone rubryki w przypadku zawyżania ocen. Należy kalibrować model oceniający względem ocen wystawionych przez ludzi na oznaczonym zbiorze danych i mierzyć korelację Pearsona. Warto monitorować współczynniki wygranych według etykiety w czasie, aby wykrywać pojawiające się wzorce stronniczości, zanim zniekształcą one Państwa potok ewaluacji.
Często zadawane pytania
Czy lekcja „Kalibracja i uprzedzenia sędziów LLM” jest bezpłatna?
Tak — pełny tekst „Kalibracja i uprzedzenia sędziów LLM” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Prompt Engineering, przejdź na CoddyKit PRO. Kurs AI Prompt Engineering zawiera 4 lekcji w sumie.
Co nauczysz się w „Kalibracja i uprzedzenia sędziów LLM”?
Uprzedzenie pozycji, uprzedzenie na rzecz rozwlekłości oraz sposoby ograniczania ich w promptach sędziego. Ćwiczysz AI Prompt Engineering z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AI Prompt Engineering?
Nie wymagamy żadnego doświadczenia. AI Prompt Engineering w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Kalibracja i uprzedzenia sędziów LLM”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AI Prompt Engineering?
Tak. Każda lekcja AI Prompt Engineering zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Używanie LLM do oceny wyników LLM
- Prompty oceniania oparte na rubrykach
- Ocenianie porównawcze: A kontra B
- Kalibracja i uprzedzenia sędziów LLM