2편에서 7가지 편향을 측정했습니다. 3편은 그것들을 고치는 법입니다. 중요한 전제: "더 좋은 프롬프트"는 해결책이 아닙니다. 2024년 arXiv:2604.23178은 9가지 완화 전략을 5개 판사, 3개 벤치마크, 225개 통제 케이스에서 체계적으로 비교했습니다. 결론은 명확합니다. 편향 완화는 프롬프트가 아니라 기계적 절차로 해결합니다. 각 편향마다 다른 처방이 필요하고, 조합할 때 비용 대비 효율이 달라집니다. 편향별 완화 전략을 코드와 함께, 그리고 실제로 얼마나 효과가 있는지 수치와 함께 정리합니다.
3편이 다루는 것 → arXiv:2604.23178의 S1~S8 전략 체계 — 비용·효과 매핑 → 편향 7종 각각에 대한 완화 전략 코드 → Length-Controlled Win Rate (Dubois 2024) — GLM 기반 사후 보정 → Cross-family 판사 선택 — Self-preference 완화의 가장 실용적 방법 → CyclicJudge — 라운드로빈 순환으로 분산 최소화 → PREPAIR — Pointwise 분석 + Pairwise 판정 결합 → 조합 전략의 비용·효과 트레이드오프 — 언제 무엇을 쓸지
완화 전략 프레임워크 — S1~S8
"Judging the Judges"(arXiv:2604.23178, 2026년 4월)는 편향 완화 전략을 체계적으로 코드화했습니다. 이 분류를 기준으로 편향별 전략을 정리합니다.
전략 설명 API 호출 비용 주요 효과
| S1 | Position Swap | 2× | 위치 편향 제거 |
| S2 | Same-family 앙상블 (temp 변화) | 3× | 분산 감소 |
| S3 | Cross-family 앙상블 | 3× | Self-preference 제거 |
| S4 | 구조화 루브릭 (5차원, 각 1~10) | 1× | 스타일 편향 부분 완화 |
| S5 | CoT (단계적 추론 의무화) | 1× | 피상적 판단 방지 |
| S6 | Reference-guided (참조 답안) | 1× | 사실 정확성 편향 완화 |
| S7 | S3 + S4 + S5 + S6 결합 | 3× | 최대 효과 |
| S8 | S1 + S4 + S5 결합 (Budget) | 2× | 실용적 최선 |
핵심 발견: S8(Position Swap + CoT + 구조화 루브릭, 비용 2×)이 가장 실용적인 고효과 조합입니다. Claude를 판사로 쓸 때 S8은 스타일 편향까지 거의 제거했습니다. S7은 최대 효과지만 비용이 3×이므로 고위험 평가에 한정합니다.
1. Position Bias 완화 — S1: Position Swap
위치 편향은 현세대 프론티어 모델에서 ≤0.04로 사실상 소멸했지만, 코드 평가 같은 특정 도메인에서는 여전히 10% 이상 정확도가 이동합니다. 구현 비용이 낮으므로 일단 기계적으로 적용합니다.
from anthropic import Anthropic
import json
client = Anthropic()
JUDGE_SYSTEM = "당신은 두 AI 응답의 품질을 공정하게 비교하는 전문 평가자입니다."
PAIRWISE_TEMPLATE = """[질문]
{question}
[응답 {la}]
{ra}
[응답 {lb}]
{rb}
{cot_instruction}
JSON으로만 출력:
{{"winner": "{la}"|"{lb}"|"tie", "confidence": "high"|"medium"|"low",
"reasoning": "2-3문장 근거"}}"""
COT_INSTRUCTION = """평가 전 다음 단계를 따르세요:
1단계: 응답 {la}의 강점과 약점을 분석합니다.
2단계: 응답 {lb}의 강점과 약점을 분석합니다.
3단계: 어떤 기준으로 더 나은지 결정합니다.
4단계: 최종 판정을 내립니다."""
def s8_judge(
question: str,
response_a: str,
response_b: str,
judge_model: str = "claude-opus-4-7",
use_cot: bool = True,
) -> dict:
"""
S8 전략: Position Swap + CoT + 구조화 판단
비용 2× API 호출
"""
def _eval(la, ra, lb, rb):
cot = COT_INSTRUCTION.format(la=la, lb=lb) if use_cot else ""
prompt = PAIRWISE_TEMPLATE.format(
question=question,
la=la, ra=ra, lb=lb, rb=rb,
cot_instruction=cot,
)
raw = client.messages.create(
model=judge_model, max_tokens=512,
system=JUDGE_SYSTEM,
messages=[{"role": "user", "content": prompt}]
).content[0].text.strip()
return json.loads(raw)
# AB 순서
result_ab = _eval("A", response_a, "B", response_b)
# BA 순서 (레이블 그대로, 응답만 교체)
result_ba = _eval("A", response_b, "B", response_a)
# BA 결과를 원래 기준으로 역변환
ba_winner_original = {
"A": "B", # BA에서 A 이김 = 원래 B(response_b)가 이긴 것
"B": "A", # BA에서 B 이김 = 원래 A(response_a)가 이긴 것
"tie": "tie",
}.get(result_ba["winner"], "tie")
# 양쪽 일치 여부 확인
ab_winner = result_ab["winner"]
consistent = ab_winner == ba_winner_original
return {
"winner": ab_winner if consistent else "tie",
"consistent": consistent,
"ab_result": result_ab,
"ba_result": result_ba,
"strategy": "S8",
"api_calls": 2,
"note": "불일치 → 자동 Tie 처리" if not consistent else None,
}
효과 수치: arXiv:2604.23178에서 Claude + S8 조합은 스타일 편향을 포함한 전반적 편향을 거의 제거했습니다. 위치 편향 0.04 → 사실상 0.
2. Verbosity Bias 완화 — 3가지 접근
Verbosity Bias 완화에는 세 레벨이 있습니다. 프롬프트 수준 → 사후 통계 보정 → 평가 구조 변경. 효과는 뒤로 갈수록 강하고 복잡도도 높아집니다.
레벨 1: 루브릭 지시 (가장 간단)
VERBOSITY_AWARE_RUBRIC = """
응답을 평가할 때 다음 원칙을 반드시 따르세요:
**길이에 대한 지침:**
- 응답의 길이 자체는 품질 지표가 아닙니다
- 동일한 정보를 더 짧게 전달하는 응답이 동등하거나 더 나을 수 있습니다
- 불필요한 반복, 자명한 설명, 내용 없는 머리말은 감점 요인입니다
- "정보 밀도"(토큰당 유용한 정보량)를 기준으로 평가하세요
**평가 기준:**
1. 정확성 (1-5): 사실 오류 없음
2. 관련성 (1-5): 질문 핵심 충족
3. 완전성 (1-5): 중요 내용 누락 없음
4. 간결성 (1-5): 불필요한 내용 없이 명확히 전달
5. 유용성 (1-5): 실제 도움이 되는 내용
주의: 간결성과 완전성은 서로 다른 차원입니다.
짧아도 완전할 수 있고, 길어도 불완전할 수 있습니다.
"""
레벨 2: 정보 밀도 점수화
def information_density_score(
response: str,
base_score: float,
length_penalty_threshold: int = 500, # 단어 수
penalty_rate: float = 0.02, # 초과 100단어당 -0.02점
) -> float:
"""
긴 응답에 길이 페널티 적용.
단, 짧은 응답에 보너스를 주면 안 됨 — Verbosity만 패널티.
"""
word_count = len(response.split())
if word_count > length_penalty_threshold:
excess_words = word_count - length_penalty_threshold
penalty = (excess_words / 100) * penalty_rate
return max(1.0, base_score - penalty)
return base_score
레벨 3: Length-Controlled Win Rate (Dubois 2024)
AlpacaEval 2.0에서 도입된 방법입니다. 길이 차이를 공변량으로 포함한 GLM을 적합해, "같은 길이였다면 어느 쪽이 이겼을까"를 추정합니다. Chatbot Arena와의 Spearman 상관이 0.94 → 0.98로 향상됐습니다.
import numpy as np
from sklearn.linear_model import LogisticRegression
def compute_length_controlled_winrate(
comparisons: list[dict],
# comparisons: [{"winner": "A"|"B"|"tie",
# "len_a": int, "len_b": int}, ...]
) -> dict:
"""
Dubois et al. 2024 방법 간략 구현.
GLM으로 길이 차이를 통제한 실제 품질 기반 승률 추정.
"""
# 타이 제외
valid = [c for c in comparisons if c["winner"] != "tie"]
if len(valid) < 10:
return {"error": "충분한 비교 데이터 없음 (최소 10개 필요)"}
# 피처: 길이 차이 (A 길이 - B 길이)
X = np.array([[c["len_a"] - c["len_b"]] for c in valid])
# 레이블: A 승리 = 1, B 승리 = 0
y = np.array([1 if c["winner"] == "A" else 0 for c in valid])
# GLM (로지스틱 회귀)
model = LogisticRegression()
model.fit(X, y)
# Length-Controlled Win Rate: 길이 차이 = 0일 때의 승률
# (같은 길이라고 가정했을 때 A가 이길 확률)
lc_winrate = model.predict_proba([[0]])[0][1]
# 원래 win rate (보정 전)
raw_winrate = y.mean()
# 길이 편향 크기 추정
# 길이가 100단어 증가할 때 승률 변화
delta_100 = (
model.predict_proba([[100]])[0][1]
- model.predict_proba([[0]])[0][1]
)
return {
"raw_winrate": round(raw_winrate, 4),
"length_controlled_winrate": round(lc_winrate, 4),
"winrate_correction": round(lc_winrate - raw_winrate, 4),
"length_bias_per_100_words": round(delta_100, 4),
"n_comparisons": len(valid),
"interpretation": (
"길이 편향 강함 (>0.05 보정)" if abs(lc_winrate - raw_winrate) > 0.05
else "길이 편향 약함"
),
}
주의: Length-Controlled Win Rate는 pairwise 비교가 충분히 쌓인 뒤 사후 보정에 쓰는 방법입니다. 실시간 단일 평가에는 적용할 수 없습니다.
3. Self-preference Bias 완화 — Cross-family 판사 선택
가장 직접적이고 효과적인 방법은 생성 모델과 다른 패밀리의 모델을 판사로 쓰는 것입니다.
# 패밀리 정의
MODEL_FAMILIES = {
"anthropic": ["claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5-20251001"],
"openai": ["gpt-5.5", "gpt-5-mini"],
"google": ["gemini-3.5-flash", "gemini-3.1-pro"],
"meta": ["llama-3.3-70b", "llama-3.1-8b"],
"xai": ["grok-4.3"],
}
def _get_family(model_id: str) -> str:
mid = model_id.lower()
for family, models in MODEL_FAMILIES.items():
if any(m.split("-")[0] in mid for m in models):
return family
if family in mid:
return family
return "unknown"
def select_cross_family_judge(
generator_model: str,
available_judges: list[str],
preferred_capability: str = "high", # "high" | "medium"
) -> str:
"""
생성 모델과 다른 패밀리의 판사 중 가장 적합한 것 선택.
"""
gen_family = _get_family(generator_model)
# 같은 패밀리 제외
eligible = [
j for j in available_judges
if _get_family(j) != gen_family and _get_family(j) != "unknown"
]
if not eligible:
raise ValueError(
f"가용 판사({available_judges})가 모두 생성 모델({generator_model})과 "
f"같은 패밀리({gen_family})입니다. "
f"다른 패밀리의 판사를 추가하세요."
)
# 능력 기준 정렬 (고성능 모델 우선)
HIGH_CAPABILITY = ["claude-opus", "gpt-5.5", "gemini-3.5-flash"]
if preferred_capability == "high":
for cap_model in HIGH_CAPABILITY:
for judge in eligible:
if cap_model in judge.lower():
return judge
return eligible[0]
# S3: Cross-family 앙상블
def s3_cross_family_ensemble(
question: str,
response: str,
generator_model: str,
n_judges: int = 3,
) -> dict:
"""
생성 모델과 다른 패밀리 3개 모델로 앙상블 평가.
"""
gen_family = _get_family(generator_model)
# 다른 패밀리에서 판사 선발
judge_pool = [
m for family, models in MODEL_FAMILIES.items()
for m in models
if family != gen_family
]
judges = judge_pool[:n_judges]
scores = {}
for judge in judges:
score = pointwise_judge_single(question, response, judge)
scores[judge] = score
values = list(scores.values())
return {
"ensemble_mean": round(sum(values) / len(values), 2),
"ensemble_std": round(float(np.std(values)), 2),
"individual_scores": scores,
"judges_used": judges,
"generator_family_excluded": gen_family,
"api_calls": n_judges,
}
효과: Self-preference가 10~25% 점수 인플레이션을 유발하는 반면, Cross-family 판사는 이 편향을 구조적으로 제거합니다. 앙상블(S3)은 비용 3×이지만 분산도 함께 줄어 고위험 평가에 적합합니다.
4. Style/Format Bias 완화 — 내용 격리 + 포맷 지시 제거
스타일 편향(0.76~0.92)이 가장 강력한 편향입니다. 두 가지 방향으로 완화합니다.
방향 1: 응답 포맷 표준화 (평가 전 처리)
import re
def strip_formatting(text: str) -> str:
"""
평가 전 마크다운 포맷 제거.
내용은 보존하되 시각적 구조는 제거.
주의: 이 방법은 코드 블록이 포함된 응답에는 부적합.
"""
# 마크다운 헤더 제거 (## Title → Title)
text = re.sub(r'^#{1,6}\s+', '', text, flags=re.MULTILINE)
# 볼드/이탤릭 제거 (**bold** → bold)
text = re.sub(r'\*{1,3}([^*]+)\*{1,3}', r'\1', text)
# 불릿 포인트를 일반 문장으로 (- item → item)
text = re.sub(r'^\s*[-*+]\s+', '', text, flags=re.MULTILINE)
# 번호 목록 제거 (1. item → item)
text = re.sub(r'^\s*\d+\.\s+', '', text, flags=re.MULTILINE)
# 연속된 빈 줄 정리
text = re.sub(r'\n{3,}', '\n\n', text)
return text.strip()
def format_neutralized_judge(
question: str,
response_a: str,
response_b: str,
judge_model: str = "claude-opus-4-7",
strip_format: bool = True,
) -> dict:
"""
포맷 중립화 후 평가.
코드가 없는 응답에 사용.
"""
ra = strip_formatting(response_a) if strip_format else response_a
rb = strip_formatting(response_b) if strip_format else response_b
return s8_judge(question, ra, rb, judge_model)
방향 2: 루브릭에서 포맷을 명시적으로 제외
STYLE_NEUTRAL_SYSTEM = """당신은 AI 응답의 내용 품질만을 평가하는 전문가입니다.
**절대 평가하지 말 것:**
- 마크다운 포맷 (헤더, 불릿, 볼드)
- 응답의 시각적 구조
- 응답의 길이
- 글쓰기 스타일이나 어조
**오직 평가할 것:**
- 사실적 정확성
- 질문에 대한 실질적 답변 여부
- 정보의 유용성과 완전성
- 논리적 타당성
포맷이 화려해도 내용이 없으면 낮은 점수를 주세요.
포맷이 없어도 내용이 좋으면 높은 점수를 주세요."""
5. Bandwagon/Authority Bias 완화 — 익명화 + 명시적 차단
import re
def anonymize_response(response: str) -> str:
"""
응답에서 출처·권위 신호 제거.
"""
patterns = [
# 모델 이름 언급 제거
r'\b(GPT|Claude|Gemini|Llama|Grok|ChatGPT|Copilot)\b',
# 인용 형식 (저자 et al., 2024) → [인용됨]
r'\([A-Z][a-z]+ et al\.,?\s*\d{4}\)',
# URL 제거
r'https?://\S+',
# "연구에 따르면", "전문가들은" 같은 권위 신호
r'(연구에 따르면|전문가들은|학계에서는|[0-9]+%의 사람들이)',
]
result = response
for pattern in patterns:
result = re.sub(pattern, '[익명화됨]', result, flags=re.IGNORECASE)
return result
ANTI_BANDWAGON_SYSTEM = """응답을 평가할 때 다음 규칙을 반드시 따르세요:
1. 인용이나 참고문헌의 존재 여부는 무시하세요
2. "많은 사람들이 동의한다"는 식의 주장은 근거로 인정하지 마세요
3. 권위 있는 출처 언급은 내용이 아닙니다 — 실질적 주장을 평가하세요
4. 확신에 찬 어조가 정확성을 의미하지 않습니다
5. 사실 확인이 불가능한 주장은 불확실한 것으로 간주하세요
응답의 내용 자체만을 근거로 판단하세요."""
6. Calibration Drift 완화 — 버전 고정 + 월별 재검증
import hashlib
import json
from datetime import datetime, timedelta
class JudgeContract:
"""
판사 설정을 버전 관리하는 불변 계약.
(judge_model_id, rubric_version, prompt_hash) 트리플렛이 고정돼야
점수의 시계열 비교가 유의미해집니다.
"""
def __init__(
self,
model_id: str,
rubric: str,
system_prompt: str,
rubric_version: str = "v1.0",
):
self.model_id = model_id
self.rubric = rubric
self.system_prompt = system_prompt
self.rubric_version = rubric_version
self.prompt_hash = self._hash_prompt()
self.created_at = datetime.utcnow().isoformat()
def _hash_prompt(self) -> str:
content = self.system_prompt + self.rubric
return hashlib.sha256(content.encode()).hexdigest()[:12]
def fingerprint(self) -> str:
"""이 판사 설정의 고유 식별자"""
return f"{self.model_id}:{self.rubric_version}:{self.prompt_hash}"
def to_metadata(self) -> dict:
return {
"judge_model": self.model_id,
"rubric_version": self.rubric_version,
"prompt_hash": self.prompt_hash,
"fingerprint": self.fingerprint(),
"created_at": self.created_at,
}
def recalibration_check(
contract: JudgeContract,
golden_set: list[dict],
judge_fn,
baseline_kappa: float,
last_calibrated: datetime,
kappa_drop_threshold: float = 0.1,
days_threshold: int = 60,
) -> dict:
"""
재캘리브레이션 필요 여부 판단.
golden_set: [{"question": str, "response": str, "human_score": int}, ...]
"""
from sklearn.metrics import cohen_kappa_score
days_elapsed = (datetime.utcnow() - last_calibrated).days
current_scores = [judge_fn(s["question"], s["response"]) for s in golden_set]
human_scores = [s["human_score"] for s in golden_set]
current_kappa = cohen_kappa_score(
[round(s) for s in human_scores],
[round(s) for s in current_scores],
)
kappa_drop = baseline_kappa - current_kappa
mean_shift = abs(
sum(current_scores) / len(current_scores)
- sum(human_scores) / len(human_scores)
)
triggers = []
if days_elapsed >= days_threshold:
triggers.append(f"주기 도달 ({days_elapsed}일 경과)")
if kappa_drop > kappa_drop_threshold:
triggers.append(f"κ 하락 ({kappa_drop:.3f})")
if mean_shift > 2.0:
triggers.append(f"평균 이동 ({mean_shift:.1f}점)")
if current_kappa < 0.5:
triggers.append(f"κ 임계치 미달 ({current_kappa:.3f})")
return {
"contract_fingerprint": contract.fingerprint(),
"current_kappa": round(current_kappa, 3),
"baseline_kappa": round(baseline_kappa, 3),
"kappa_drop": round(kappa_drop, 3),
"mean_shift": round(mean_shift, 2),
"days_since_calibration": days_elapsed,
"recalibration_needed": len(triggers) > 0,
"triggers": triggers,
"alert_level": (
"critical" if current_kappa < 0.4 or mean_shift > 5
else "warning" if triggers
else "ok"
),
}
7. Rubric Instability 완화 — 균형 순열
루브릭 항목 순서 편향을 제거하는 방법입니다. 아이템이 N개일 때 모든 순열을 평균내거나, 대표 순열들을 순환합니다.
from itertools import permutations
import random
def balanced_rubric_evaluation(
question: str,
response: str,
rubric_items: list[str],
judge_fn_with_rubric, # (q, r, rubric_order) → score
n_permutations: int = 4, # 전체 순열이 너무 많으면 샘플링
) -> dict:
"""
rubric 순서를 무작위로 섞어 복수 평가 후 평균.
arXiv:2602.02219의 Balanced Permutation 방법.
"""
all_perms = list(permutations(rubric_items))
# 항목이 많으면 랜덤 샘플링
if len(all_perms) > n_permutations:
sampled_perms = [rubric_items] + random.sample(all_perms, n_permutations - 1)
else:
sampled_perms = all_perms
results = []
for perm in sampled_perms:
score = judge_fn_with_rubric(question, response, list(perm))
results.append({"rubric_order": list(perm), "score": score})
scores = [r["score"] for r in results]
return {
"balanced_score": round(sum(scores) / len(scores), 2),
"score_variance": round(float(np.var(scores)), 3),
"rubric_instability": round(max(scores) - min(scores), 2),
"permutation_results": results,
"n_permutations_tested": len(sampled_perms),
}
8. CyclicJudge — 라운드로빈 순환 앙상블
단일 판사의 분산을 줄이는 가장 효율적인 방법으로 arXiv:2603.01865에서 제안됐습니다. 여러 판사를 라운드로빈으로 순환시키면, 단순 다수결 앙상블보다 낮은 분산을 동일 예산에서 달성합니다.
from itertools import cycle
class CyclicJudge:
"""
여러 판사를 라운드로빈으로 순환해 평가 분산 최소화.
arXiv:2603.01865 (CyclicJudge) 구현.
"""
def __init__(self, judges: list[str]):
"""
judges: 순환할 판사 모델 리스트
가능하면 다른 패밀리에서 선택 (Cross-family 효과 결합)
"""
self.judges = judges
self._cycle = cycle(judges)
self._call_count = {j: 0 for j in judges}
def next_judge(self) -> str:
"""다음 순서 판사 반환"""
judge = next(self._cycle)
self._call_count[judge] += 1
return judge
def evaluate_batch(
self,
evaluations: list[dict], # [{"question": str, "response": str}, ...]
judge_fn, # (question, response, judge_model) → score
) -> list[dict]:
"""
배치 평가: 각 샘플에 순환 판사 할당.
총 판사 호출 = len(evaluations) × 1 (단일 판사보다 저렴)
단, 판사 다양성으로 분산은 앙상블과 유사하게 감소.
"""
results = []
for eval_item in evaluations:
judge = self.next_judge()
score = judge_fn(
eval_item["question"],
eval_item["response"],
judge,
)
results.append({
**eval_item,
"score": score,
"judge_model": judge,
})
return results
def usage_stats(self) -> dict:
return {
"total_calls": sum(self._call_count.values()),
"calls_per_judge": self._call_count,
"balance_ratio": min(self._call_count.values()) / max(self._call_count.values())
if max(self._call_count.values()) > 0 else 1.0,
}
# 사용 예시
cyclic = CyclicJudge(judges=[
"claude-opus-4-7", # Anthropic
"gpt-5.5", # OpenAI
"gemini-3.5-flash", # Google
])
batch_results = cyclic.evaluate_batch(
evaluations=[
{"question": "...", "response": "..."},
{"question": "...", "response": "..."},
# ...
],
judge_fn=lambda q, r, m: pointwise_judge_single(q, r, m),
)
CyclicJudge의 장점: 배치 평가에서 단일 판사와 동일한 비용(1×)으로 앙상블에 준하는 분산 감소를 달성합니다. 모든 평가에 같은 판사를 쓸 때 발생하는 자기 패밀리 편향이 순환으로 분산됩니다.
전략 선택 가이드 — 비용 vs 효과
평가 유형별 권장 전략:
[일상적 품질 모니터링 - 비용 최소화]
→ S4 (구조화 루브릭) + Cross-family 판사 선택
→ CyclicJudge로 배치 처리
→ 비용: 1×, 효과: 중간
[A/B 테스트 / 모델 교체 결정 - 균형]
→ S8 (Position Swap + CoT + 루브릭)
→ Cross-family 판사
→ 비용: 2×, 효과: 높음
→ arXiv:2604.23178에서 권장하는 실용적 최선
[RLHF 선호 데이터 수집 - 높은 품질 요구]
→ S7 (S3 + S4 + S5 + S6)
→ Length-Controlled Win Rate 사후 보정
→ 비용: 3×, 효과: 최대
[긴급 인시던트 조사 - 단발성 고신뢰]
→ S3 Cross-family 앙상블 3개 판사
→ 황금 세트 즉시 재캘리브레이션
→ 비용: 3×, 신뢰도: 최대
[대규모 배치 스크리닝 - 처리량 우선]
→ Distilled Judge (4편에서 상세 다룸)
→ 비용: ~1/50×, 효과: 보통
✅ 3편 정리 — 완화 전략 매핑
편향 1순위 완화 2순위 완화 비용
| Position | S1 Position Swap | S8 결합 | 2× |
| Verbosity | 루브릭 지시 + 간결성 차원 | LC Win Rate | 1× / 사후 |
| Self-preference | Cross-family 판사 | S3 앙상블 | 1× / 3× |
| Style/Format | 포맷 중립화 + 명시적 지시 | S4 구조화 루브릭 | 1× |
| Bandwagon/Authority | 익명화 + Anti-bandwagon 지시 | S6 Reference | 1× |
| Calibration Drift | JudgeContract 버전 고정 | 60일 재캘리브레이션 | — |
| Rubric Instability | 균형 순열 평균 | 루브릭 순서 고정 | N× |
황금 룰: 편향 완화에 "만능 해결책"은 없습니다. 편향마다 다른 처방이 필요하고, 가장 실용적인 기본값은 S8 + Cross-family 판사입니다. 이 두 가지만 적용해도 현재 알려진 주요 편향의 대부분을 의미 있게 줄일 수 있습니다.
LLM-as-a-Judge 완전정리 시리즈 — 완결
- ✅ 1편 — 왜 기존 지표는 죽었고, 세 패러다임은 무엇인가 https://cell-devlog.tistory.com/265
- ✅ 2편 — 판사는 어디서 거짓말하나: 7가지 편향 해부 https://cell-devlog.tistory.com/266
- ✅ 3편 — 편향 잡는 법: Position Swap부터 Cross-family까지 https://cell-devlog.tistory.com/267
- ✅ 4편 — G-Eval vs Prometheus 2 vs PAJAMA vs Themis https://cell-devlog.tistory.com/268
- ✅ 5편 — 판사를 평가하기: Cohen's κ, Bradley-Terry, 황금 세트 설계 https://cell-devlog.tistory.com/269
- ✅ 6편 — 프로덕션 파이프라인: 샘플링·CI 게이트·캘리브레이션 주기 https://cell-devlog.tistory.com/270
- ✅ 7편 — 한계와 대안: LLM 판사가 절대 못 하는 것들 https://cell-devlog.tistory.com/271