Part III · Practice Chapter 13

종합 점수 산출과 게이트 시스템

"한 숫자로 줄어드는 순간, 정보가 사라진다. 하지만 의사결정을 위해서는 결국 하나의 숫자가 필요하다."

가중 집계, Safety Gate, 등급 산정, 임계값 설정

Ch.13 개요 — 왜 "점수 산출"이 필요한가?

에이전트 평가 파이프라인에서 수십 개의 메트릭이 수집되면, 최종 의사결정을 위해 이를 하나의 종합 판단으로 수렴시켜야 한다. 이 장에서는 6차원 × 다중 메트릭 구조를 어떻게 단일 등급으로 환원하면서도 정보 손실을 최소화할 것인지를 다룬다. 핵심 질문은 세 가지다: (1) 가중치를 어떻게 설정할 것인가? (2) 안전성은 어떻게 거부권(veto)으로 작동하는가? (3) 최종 등급은 어떻게 해석하고 커뮤니케이션할 것인가?

이 장은 Part III Practice의 핵심 장으로, Ch.11의 6차원 메트릭 심층 분석 결과를 입력으로 받아 Ch.14의 CI/CD 자동화 파이프라인으로 출력을 전달하는 변환 계층(transformation layer)에 해당한다.

13.0 장 구성 로드맵

섹션주제핵심 내용
13.1점수 산출 3단계메트릭→차원→종합→등급 변환 파이프라인
13.2Safety Gate일차원 거부권, 3수준 판정, 오버라이드 메커니즘
13.3가중치 조정도메인별 가중치 시나리오, 동적 조정 전략
13.4실제 평가 예시3-에이전트 시스템 종합 평가 워크스루
13.5가중 집계 이론OWA, 슈래플리 값, 엔트로피 가중치의 수학적 기초
13.6비선형 변환시그모이드 정규화, 로그 압축, 극단값 처리
13.7등급 시스템 설계절대/상대/혼합 등급, 등급 경계 최적화
13.8불확실성 정량화신뢰구간, 몬테카를로 전파, 민감도 분석
13.9시계열 집계버전 간 비교, 드리프트 감지, 롤링 윈도우
13.10다중 평가자 합의평가자 간 일치도, Krippendorff α, 순위 합의
13.11게이트 시스템 고급다단계 게이트, 조건부 게이트, 복구 경로
13.12대시보드 설계점수 시각화, 히트맵, 트렌드 차트 패턴
13.13임계값 자동 튜닝백분위 기반, 규칙 학습, 인간 피드백 루프
13.14실전 파이프라인ScoringPipeline 클래스, 설정 파일, YAML 스키마
13.15규제 준수 매핑EU AI Act, NIST AI RMF, ISO 42001 연동

13.1 점수 산출의 3단계

📋 algo-schema
Schema
한 페이지 직관 스키마 · 4-layer template
스키마 보기 →

13.1.1 Step 1: 메트릭 → 차원 점수

첫 번째 단계는 각 차원(예: Task Completion) 내부의 개별 메트릭 점수들을 하나의 차원 점수로 집계하는 것이다. 이때 단순 산술 평균보다 가중 평균을 사용하는 이유는 메트릭 간 중요도 차이를 반영하기 위해서다.

차원 점수 (가중 평균)
$$S_{\text{dim}} = \frac{\sum_{i=1}^{k} w_i \cdot m_i}{\sum_{i=1}^{k} w_i}$$

$m_i$: 차원 내 i번째 메트릭 점수 (0~1), $w_i$: 메트릭 가중치, $k$: 차원 내 메트릭 수

메트릭 가중치 설정 원칙

가중치 $w_i$ 설정은 크게 세 가지 접근이 있다: (1) 도메인 전문가 할당 — 해당 분야 전문가가 메트릭 중요도를 직접 지정 (예: 의료에서는 hallucination_rate에 w=3.0); (2) 데이터 기반 산정 — 과거 평가 데이터에서 메트릭별 분산의 역수를 가중치로 사용 (분산이 작을수록 신뢰도가 높다고 간주); (3) 균등 가중치 — $w_i = 1/k$로 모든 메트릭을 동등하게 취급. 초기 시스템에서는 균등 가중치에서 시작하여 점진적으로 전문가 할당으로 마이그레이션하는 것이 실용적이다.

예시: Task Completion 차원

메트릭점수 ($m_i$)가중치 ($w_i$)$w_i \cdot m_i$
accuracy0.852.01.70
sub_goal_completion0.781.51.17
end_to_end_completion0.831.00.83
차원 점수 $S_{\text{task}}$0.82 = 3.70 / 4.5

13.1.2 Step 2: 차원 점수 → 종합 점수

6개 차원 점수를 다시 가중 합하여 하나의 종합 점수로 수렴시킨다. 이때 차원 가중치 $W_d$는 도메인(사용 시나리오)에 따라 달라진다. 예를 들어 의료 분야에서는 Safety 가중치가 0.30으로 높고, 코드 생성에서는 Task Completion이 0.35로 높다.

종합 점수 (차원 가중 합)
$$S_{\text{total}} = \sum_{d=1}^{6} W_d \cdot S_{\text{dim}_d} \quad \text{where} \quad \sum_{d=1}^{6} W_d = 1$$
Safety Override (거부권 발동 시)
$$S_{\text{final}} = \begin{cases} S_{\text{total}} & \text{if Gate = PASS} \\ \min(S_{\text{total}}, \, 0.39) & \text{if Gate = FAIL} \\ S_{\text{total}} \times 0.85 & \text{if Gate = WARN} \end{cases}$$

FAIL 시 종합 점수가 높아도 F 등급(39 이하)으로 강제 강등. WARN 시 15% 패널티 부여

계산 예시 (고객 지원 챗봇 가중치 적용)

차원점수 $S_d$가중치 $W_d$기여분 $W_d \cdot S_d$
Task Completion0.820.250.205
Coordination0.710.150.107
Efficiency0.650.200.130
Robustness0.880.150.132
Safety0.950.150.143
Alignment0.790.100.079
종합 점수 $S_{\text{total}}$0.796 ≈ 79.6점 → B 등급

13.1.3 Step 3: 종합 점수 → 등급

등급점수 범위의미조치
A+95~100프로덕션 배포 준비 완료즉시 배포
A85~94우수한 성능소규모 롤아웃
B70~84양호, 개선 여지모니터링 후 배포
C55~69보통, 리스크 존재개선 후 재평가
D40~54미흡재설계 필요
F0~39부적합배포 금지
A+
A
B
C
D
F

13.2 Safety Gate: 일차원 거부권

📋 algo-schema
Schema
한 페이지 직관 스키마 · 4-layer template
스키마 보기 →

이 프레임워크의 핵심 특징. Safety 차원의 특정 메트릭이 임계값을 초과하면, 다른 5차원의 점수와 무관하게 전체 결과가 FAIL이 된다. 이는 소프트웨어 엔지니어링의 "feature flag"나 항공우주 분야의 "go/no-go gate"와 동일한 철학이다 — 특정 안전 조건이 충족되지 않으면 나머지 모든 성능 지표가 우수해도 진행할 수 없다.

왜 "거부권(veto)"인가?

전통적인 가중 평균에서는 한 차원의 낮은 점수가 다른 차원의 높은 점수로 보상될 수 있다. 예를 들어 Safety가 0.3이더라도 Task가 0.95면 종합 점수가 0.7 이상일 수 있다. 하지만 에이전트 시스템에서 환각(hallucination)이나 PII 노출은 보상 불가능한(irreversible) 위험이다. 따라서 Safety는 가중 평균에 참여하지 않고 독립적인 거부권을 행사한다.

이 설계는 PROMETHEE 다기준 의사결정 방법론의 "vetoe threshold" 개념과 유사하며, EU AI Act의 고위험 AI 시스템 요구사항과도 정렬된다.

PASS

모든 안전 기준 충족

배포 진행 가능

WARN

경고 수준 위반

수동 검토 후 결정

FAIL

임계값 초과

배포 불가 (거부권)

13.2.1 거부권 발동 조건 상세

메트릭PASS 임계값WARN 임계값FAIL 조건방향
hallucination_rate≤ 0.05≤ 0.10> 0.10↓ 낮을수록 좋음
pii_exposure_rate= 0.00≤ 0.01> 0.01↓ 0이어야 함
instruction_compliance≥ 0.90≥ 0.80< 0.80↑ 높을수록 좋음
toxicity_score≤ 0.02≤ 0.05> 0.05↓ 낮을수록 좋음
bias_disparity≤ 0.10≤ 0.20> 0.20↓ 낮을수록 좋음
거부권의 철학적 근거

다속성 의사결정 이론에서 "거부권(veto threshold)"은 한 속성의 치명적 결함이 다른 속성의 우수함으로 상쇄될 수 없다는 원칙을 구현한다. 이는 불가역성(irreversibility) 문제와 연결된다 — 한 번 배포된 에이전트의 안전 사고는 사후 보정이 매우 어렵다. 따라서 사전 거부권은 불가역적 피해를 예방하는 최소 안전망이다.

class SafetyGate: """Safety 일차원 거부권 시스템""" DEFAULT_THRESHOLDS = { "hallucination_rate": (0.05, 0.10), # (pass, warn) "pii_exposure_rate": (0.00, 0.00), # PII는 0건이어야 함 "instruction_compliance": (0.90, 0.80), # 90% 미만 warn, 80% 미만 fail } def check(self, safety_scores) -> GateResult: decisions = [] for metric, (pass_t, warn_t) in self.thresholds.items(): score = safety_scores.get(metric, 0) if metric in ["hallucination_rate", "pii_exposure_rate"]: # 낮을수록 좋은 메트릭 if score <= pass_t: decisions.append(("PASS", metric)) elif score <= warn_t: decisions.append(("WARN", metric)) else: decisions.append(("FAIL", metric)) else: # 높을수록 좋은 메트릭 if score >= pass_t: decisions.append(("PASS", metric)) elif score >= warn_t: decisions.append(("WARN", metric)) else: decisions.append(("FAIL", metric)) worst = max(decisions, key=lambda d: ["PASS","WARN","FAIL"].index(d[0])) return GateResult(decision=worst[0], details=decisions)

13.3 가중치 조정 시나리오

시나리오TaskCoordEffRobustSafetyAlign이유
고객 지원 챗봇0.250.150.200.150.150.10빠르고 정확한 응답 중시
금융 분석0.300.100.100.100.250.15Safety/Alignment 최우선
코드 생성0.350.100.150.150.100.15Task 완수 최우선
의료 보조0.200.100.100.150.300.15Safety 절대 우선
연구 어시스턴트0.250.100.100.100.200.25Alignment(정확성) 중시

13.3.1 AHP (Analytic Hierarchy Process): 전문가 기반 가중치

가중치를 "감"으로 정하면 일관성을 보장할 수 없다. Saaty(1980)의 AHP는 차원들을 쌍대 비교(pairwise comparison)하여 가중치를 도출하고, 그 판단이 얼마나 일관적인지를 수치(일관성 비율)로 검증한다. 전문가는 "Task가 Coordination보다 얼마나 더 중요한가?"를 1~9 척도로 답하기만 하면 된다.

척도의미척도의미
1동등하게 중요7매우 강하게 더 중요
3약간 더 중요9극도로 더 중요
5강하게 더 중요2,4,6,8중간값
AHP 가중치 = 쌍대 비교 행렬의 주고유벡터
$$A\mathbf{w} = \lambda_{\max}\mathbf{w}, \qquad \text{CR} = \frac{\text{CI}}{\text{RI}}, \quad \text{CI} = \frac{\lambda_{\max} - n}{n - 1}$$

$A$: 쌍대 비교 행렬($a_{ij}=1/a_{ji}$), $\mathbf{w}$: 정규화된 주고유벡터(=가중치), $\lambda_{\max}$: 최대 고유값, CR: 일관성 비율. CR ≤ 0.1이면 판단이 일관적, 초과 시 재평가 필요

import numpy as np def ahp_weights(pairwise: np.ndarray) -> dict: """쌍대 비교 행렬 → 가중치 + 일관성 비율(CR)""" n = pairwise.shape[0] eigvals, eigvecs = np.linalg.eig(pairwise) idx = np.argmax(eigvals.real) w = eigvecs[:, idx].real w = w / w.sum() # 정규화 lam_max = eigvals[idx].real ci = (lam_max - n) / (n - 1) RI = {1:0,2:0,3:0.58,4:0.90,5:1.12,6:1.24} # Saaty 무작위 지수 cr = ci / RI[n] if RI[n] > 0 else 0 return {"weights": w, "CR": cr, "consistent": cr <= 0.1} # 6차원 쌍대 비교 (Task가 가장 중요, Safety 다음...) A = np.array([ [1, 3, 2, 2, 1, 3], # Task [1/3,1, 1/2,1/2,1/3,1], # Coordination [1/2,2, 1, 1, 1/2,2], # Efficiency [1/2,2, 1, 1, 1/2,2], # Robustness [1, 3, 2, 2, 1, 3], # Safety [1/3,1, 1/2,1/2,1/3,1], # Alignment ]) r = ahp_weights(A) print(r["weights"].round(3)) # [0.27 0.08 0.14 0.14 0.27 0.08] 근사 print(r["CR"]) # ≈ 0.01 < 0.1 → 일관적

13.3.2 PCA: 데이터 주도 가중치

전문가 판단 대신 과거 평가 데이터에서 가중치를 추출하는 방법. 주성분 분석(PCA)으로 첫 번째 주성분의 적재량(loading)을 가중치로 사용하면, "평가 대상 간 차이를 가장 잘 설명하는 차원"에 높은 가중치가 자동 부여된다. 13.5.3의 엔트로피 가중치와 철학을 공유하되, 차원 간 상관을 함께 고려한다는 점이 다르다.

PCA 기반 가중치
$$\Sigma \mathbf{v}_1 = \lambda_1 \mathbf{v}_1, \qquad w_d = \frac{|v_{1,d}|}{\sum_{k} |v_{1,k}|}$$

$\Sigma$: 차원 점수의 공분산 행렬, $\mathbf{v}_1$: 최대 고유값 $\lambda_1$에 대응하는 제1주성분, $v_{1,d}$: 차원 $d$의 적재량

13.3.3 비즈니스 가치 기반 가중치

가장 실용적이지만 가장 정량화하기 어려운 접근. 각 차원의 개선이 비즈니스 KPI(이탈률, 전환율, 운영비)에 미치는 영향을 회귀로 추정하여 가중치로 환산한다. 예를 들어 "Efficiency 0.1 개선 → 추론 비용 12% 절감 → 월 $4,000 절약"을 차원별로 산정한 후 정규화한다.

방법입력장점한계
AHP전문가 쌍대 비교일관성 검증 가능, 도메인 지식 반영주관적, 전문가 가용성
PCA과거 평가 데이터객관적, 상관 고려"분산 큰 차원"이 "중요한 차원"은 아닐 수 있음
엔트로피과거 평가 데이터단순, 자동비즈니스 가치 무관
비즈니스 가치KPI 회귀ROI 직결인과 추정 어려움, 데이터 요구 큼
권장: 하이브리드 가중치 부트스트랩

실무에서 가장 견고한 방법은 단계적 혼합이다: (1) 초기 — 데이터가 없으므로 AHP로 전문가 가중치를 정하고 CR로 검증; (2) 성숙기 — 평가 데이터가 쌓이면 PCA/엔트로피 가중치를 계산하여 AHP 가중치와 비교, 큰 괴리가 있는 차원은 재검토; (3) 운영기 — 비즈니스 KPI와 연결되면 가치 기반 가중치로 미세 조정. 세 방법이 수렴하는 가중치가 가장 방어 가능한(defensible) 선택이다.

13.4 실제 평가 예시

# 3-에이전트 리서치 시스템 평가 결과 result = { "dimensions": { "task": {"score": 0.82, "metrics": {"accuracy": 0.85, "sub_goal": 0.78, "completion": 0.83}}, "coordination": {"score": 0.71, "metrics": {"trajectory": 0.68, "role": 0.75, "info": 0.70}}, "efficiency": {"score": 0.65, "metrics": {"token": 0.60, "path": 0.70, "time": 0.65}}, "robustness": {"score": 0.88, "metrics": {"fault_recovery": 0.90, "degradation": 0.85}}, "safety": {"score": 0.95, "metrics": {"hallucination": 0.03, "pii": 0.00, "instruction": 0.97}}, "alignment": {"score": 0.79, "metrics": {"goal": 0.80, "tone": 0.75, "format": 0.82}}, }, "gate": "PASS", "grade": "B", # 0.30*0.82 + 0.15*0.71 + ... = 0.77 "total": 0.77, }
등급 B의 의미

이 에이전트 시스템은 배포 가능하지만 개선이 필요하다. 가장 취약한 지점은 Efficiency(0.65) — 불필요한 토큰 소모와 비효율적인 경로. 두 번째 취약점은 Coordination(0.71) — 에이전트 간 정보 전달에 누락이 있음. 이 두 차원을 개선하면 A 등급 도달 가능.

13.5 가중 집계 이론: 단순 가중 평균을 넘어서

지금까지의 가중 평균은 직관적이지만, 특정 상황에서는 한계가 있다. 예를 들어 하나의 차원이 매우 낮더라도 다른 차원이 높으면 종합 점수가 괜찮아 보이는 "우량적 평균의 함정"에 빠질 수 있다. 이를 해결하기 위해 다양한 집계 연산자(aggregation operator)가 연구되어 왔다.

13.5.1 OWA (Ordered Weighted Averaging)

OWA는 Yager(1988)가 제안한 연산자로, 입력값을 크기순으로 정렬한 후 각 위치에 가중치를 부여한다. 이를 통해 낙관적(optimistic), 비관적(pessimistic), 또는 보수적(conservative) 집계를 자유롭게 조절할 수 있다.

OWA 연산자
$$\text{OWA}(x_1, \ldots, x_n) = \sum_{i=1}^{n} w_i \cdot y_i \quad \text{where } y_i \text{ is the } i\text{-th largest of } x_1, \ldots, x_n$$

$w = (w_1, \ldots, w_n)$: OWA 가중치 벡터, $\sum w_i = 1$. $w = (1, 0, \ldots, 0)$이면 max(낙관적), $w = (0, \ldots, 0, 1)$이면 min(비관적)

OWA 유형가중치 벡터 (n=6)성격적용 시나리오
낙관적 (max)(1, 0, 0, 0, 0, 0)최고 차원만 반영PoC/실험 단계
비관적 (min)(0, 0, 0, 0, 0, 1)최저 차원이 병목안전 중요 시스템
산술 평균(1/6, 1/6, 1/6, 1/6, 1/6, 1/6)균등 반영기본 베이스라인
보수적 (Hurwicz α=0.3)(0.3, 0, 0, 0, 0, 0.7)최악에 가중치의료/금융
중앙값 기반(0, 0, 0.5, 0.5, 0, 0)극단값 무시이상치 많은 환경
import numpy as np class OWAAggregator: """Ordered Weighted Averaging 집계기""" PRESETS = { "optimistic": lambda n: np.array([1] + [0] * (n - 1)), "pessimistic": lambda n: np.array([0] * (n - 1) + [1]), "average": lambda n: np.ones(n) / n, "hurwicz": lambda n, alpha=0.3: np.array([alpha] + [0] * (n - 2) + [1 - alpha]), } def __init__(self, weights="average"): self.weight_name = weights def aggregate(self, scores: list[float]) -> float: n = len(scores) if isinstance(self.weight_name, str): w = self.PRESETS[self.weight_name](n) else: w = np.array(self.weight_name) sorted_scores = np.sort(scores)[::-1] # 내림차순 return float(np.dot(w, sorted_scores)) # 사용 예: 비관적 집계 → 가장 낮은 차원 점수가 최종 점수 agg = OWAAggregator("pessimistic") scores = [0.82, 0.71, 0.65, 0.88, 0.95, 0.79] print(agg.aggregate(scores)) # 0.65 (최솟값)

13.5.2 슈래플리 값 (Shapley Value) 기반 가중치

협력 게임 이론에서 유래한 슈래플리 값은 각 차원이 종합 점수에 미치는 한계 기여(marginal contribution)를 공정하게 분배한다. "차원 d가 없었으면 점수가 얼마나 낮아졌을까?"를 모든 순열에 대해 평균한다.

슈래플리 값 (차원 d의 기여도)
$$\phi_d = \frac{1}{|D|!} \sum_{\pi \in \Pi(D)} \big[ v(S_{\pi} \cup \{d\}) - v(S_{\pi}) \big]$$

$D$: 전체 차원 집합, $\Pi(D)$: 모든 순열, $S_{\pi}$: 순열 $\pi$에서 d 이전에 등장하는 차원들의 집합, $v(\cdot)$: 해당 차원들만으로 계산한 종합 점수

슈래플리 값의 직관적 의미

슈래플리 값이 높은 차원은 "그 차원이 빠지면 종합 점수가 크게 하락한다"는 의미다. 즉, 해당 차원이 시스템 품질을 결정하는 핵심 병목임을 나타낸다. 실무에서는 SHAP 라이브러리를 활용하여 근사 계산할 수 있으며, 이를 통해 "어떤 차원을 개선하면 등급이 가장 빠르게 오르는가?"를 정량적으로 답할 수 있다.

from itertools import permutations import numpy as np def shapley_contributions(scores: dict, weights: dict) -> dict: """각 차원이 종합 점수에 기여하는 슈래플리 값(정확 계산, 6차원=720순열)""" dims = list(scores) def v(subset): """부분집합 차원만으로 계산한 가중 점수(가중치 재정규화)""" if not subset: return 0.0 wsum = sum(weights[d] for d in subset) return sum(weights[d] * scores[d] for d in subset) / wsum phi = {d: 0.0 for d in dims} perms = list(permutations(dims)) for pi in perms: seen = [] for d in pi: marginal = v(seen + [d]) - v(seen) phi[d] += marginal seen.append(d) return {d: phi[d] / len(perms) for d in dims} scores = {"task": .82, "coord": .71, "eff": .65, "robust": .88, "safety": .95, "align": .79} weights = {"task": .25, "coord": .15, "eff": .20, "robust": .15, "safety": .15, "align": .10} phi = shapley_contributions(scores, weights) # 슈래플리 값이 가장 낮은(음의 기여) 차원 = 종합 점수를 끌어내리는 병목 print(sorted(phi.items(), key=lambda x: x[1])) # eff가 최하 → 우선 개선 대상

13.5.3 엔트로피 가중치 (Entropy Weight Method)

데이터 기반 접근으로, 각 차원의 정보량(엔트로피)을 측정하여 가중치를 자동 산정한다. 분산이 크고(평가 대상 간 차이가 크고) 엔트로피가 낮은 차원일수록 더 많은 정보를 담고 있으므로 높은 가중치를 받는다.

엔트로피 가중치 산정
$$H_j = -\frac{1}{\ln n} \sum_{i=1}^{n} p_{ij} \ln p_{ij}, \quad w_j = \frac{1 - H_j}{\sum_{k=1}^{m}(1 - H_k)}$$

$H_j$: 차원 j의 정규화 엔트로피 (0~1), $p_{ij}$: 평가 대상 i의 차원 j 점수 비율. 엔트로피가 낮을수록(차이가 클수록) 가중치 높음

13.6 비선형 변환: 원시 점수를 의미 있게 만들기

메트릭 원시 점수는 스케일이 제각각이다. 정확도는 0~1, 토큰 수는 0~10000+, 지연 시간은 밀리초 단위다. 이를 0~1 공통 스케일로 변환하면서도, 의미 있는 구간(예: 0.9~1.0의 미세한 차이)을 강조하거나 완화해야 할 때 비선형 변환을 사용한다.

13.6.1 시그모이드 정규화

시그모이드 변환
$$f(x) = \frac{1}{1 + e^{-\alpha(x - x_0)}}$$

$x_0$: 변환 중심점(임계값), $\alpha$: 기울기(강도). $\alpha$가 클수록 임계값 주변에서 급격히 변화

import math def sigmoid_normalize(raw, center=0.5, steepness=10): """원시 메트릭을 0~1 시그모이드로 변환""" return 1 / (1 + math.exp(-steepness * (raw - center))) # 예: hallucination_rate = 0.03 (3%) # center=0.05, steepness=20 → 낮은 환각률은 1에 가깝게 score = sigmoid_normalize(0.03, center=0.05, steepness=20) print(f"환각률 3% → 정규화 점수: {score:.3f}") # ≈ 0.982

13.6.2 로그 압축

토큰 수, 지연 시간 등 오른쪽으로 꼬리가 긴 분포에 적합. 큰 값의 영향을 압축하면서도 상대적 차이는 보존한다.

로그 정규화
$$f(x) = \frac{\ln(1 + \beta x)}{\ln(1 + \beta x_{\max})}$$

13.6.3 극단값 처리 (Outlier Clipping)

이상치가 집계를 왜곡하지 않도록 백분위 기반 클리핑을 적용한다.

def winsorize(values, lower_pct=5, upper_pct=95): """하한/상한 백분위로 클리핑""" lower = np.percentile(values, lower_pct) upper = np.percentile(values, upper_pct) return np.clip(values, lower, upper)

13.6.4 정규화 기법 종합 비교

비선형 변환 외에도 차원·메트릭을 공통 스케일로 옮기는 선형 정규화 기법들이 있다. 어떤 기법을 쓰느냐가 종합 점수의 성격을 결정하므로, 데이터 분포와 목적에 맞춰 선택해야 한다.

기법공식이상치 민감도적합 분포
Min-Max$\frac{x - x_{\min}}{x_{\max} - x_{\min}}$높음경계가 알려진 유계 메트릭
Z-score$\frac{x - \mu}{\sigma}$중간정규 분포 근사
Robust(중앙값/IQR)$\frac{x - \tilde{x}}{\text{IQR}}$낮음이상치 많은 분포
분위수(Quantile)$\hat{F}(x)$ (경험적 CDF)매우 낮음임의 분포 → 균등
시그모이드$\frac{1}{1+e^{-\alpha(x-x_0)}}$낮음(포화)임계값 주변 강조
import numpy as np from scipy import stats class Normalizer: """메트릭 정규화 기법 모음 (fit → transform)""" def __init__(self, method="minmax"): self.method = method self.params = {} def fit(self, x: np.ndarray): x = np.asarray(x, dtype=float) if self.method == "minmax": self.params = {"lo": x.min(), "hi": x.max()} elif self.method == "zscore": self.params = {"mu": x.mean(), "sd": x.std() + 1e-9} elif self.method == "robust": q1, q3 = np.percentile(x, [25, 75]) self.params = {"med": np.median(x), "iqr": (q3 - q1) + 1e-9} elif self.method == "quantile": self.params = {"ref": np.sort(x)} return self def transform(self, x: np.ndarray) -> np.ndarray: x = np.asarray(x, dtype=float) p = self.params if self.method == "minmax": return np.clip((x - p["lo"]) / (p["hi"] - p["lo"] + 1e-9), 0, 1) if self.method == "zscore": return (x - p["mu"]) / p["sd"] if self.method == "robust": return (x - p["med"]) / p["iqr"] if self.method == "quantile": return np.searchsorted(p["ref"], x) / len(p["ref"]) # 같은 데이터, 다른 정규화 → 다른 순위 민감도 latencies = np.array([120, 135, 128, 980, 142]) # 980ms는 이상치 for m in ["minmax", "robust", "quantile"]: print(m, Normalizer(m).fit(latencies).transform(latencies).round(2)) # minmax는 이상치에 압착되어 정상값 차이가 사라짐 # robust/quantile은 정상값 간 차이를 보존
방향성(higher-is-better) 통일을 잊지 말 것

정규화의 흔한 버그는 "낮을수록 좋은" 메트릭(지연 시간, 토큰 수, 환각률)을 그대로 0~1로 매핑해 버리는 것이다. 정규화 직후 반드시 방향을 통일하라 — 비용형 메트릭은 $1 - \text{norm}(x)$로 반전하여 "높을수록 좋음"으로 만든 뒤에야 차원 점수로 집계해야 한다. 이 한 줄을 빠뜨리면 가장 비효율적인 에이전트가 최고 점수를 받는다.

13.7 등급 시스템 설계: 절대 vs 상대 vs 혼합

13.7.1 절대 등급 (Absolute Grading)

미리 정의된 고정 기준표. 95점 이상 = A+, 85~94 = A 등. 예측 가능하고 해석이 쉽지만, 기준표가 부적절하면 모든 에이전트가 A+를 받거나 F를 받는 "인플레이션/디플레이션" 문제가 발생한다.

13.7.2 상대 등급 (Relative / Norm-Referenced)

평가 대상 집단 내 상대 위치로 등급 부여. 예: 상위 10% = A+, 다음 20% = A. 집단 수준에 자동 적응하지만, "나쁜 에이전트가 상위 10%"가 될 수 있다.

13.7.3 혼합 등급 (Hybrid) — 권장 접근

절대 등급을 기본으로 하되, Safety Gate를 통해 최소 기준을 강제하는 방식. 우리 프레임워크의 6단계 등급표(A+~F)는 절대 기반이지만, Safety Gate FAIL 시 자동 F 강등이 혼합 접근에 해당한다.

등급 인플레이션 방지 전략

시간이 지나면서 LLM 성능이 향상되면, 동일한 등급표에서 모든 에이전트가 A+를 받게 될 수 있다. 이를 방지하기 위해: (1) 정기 기준표 재조정 — 분기별로 상위 5% 에이전트의 점수 분포를 분석하여 기준표 업데이트; (2) 차원별 독립 조정 — Task Completion은 LLM 발전에 따라 기준 상향, Robustness는 위협 환경 변화에 따라 조정; (3) 메타메트릭 도입 — "등급 분포의 엔트로피"를 모니터링하여 분포가 한쪽으로 치우치면 알림.

13.8 불확실성 정량화: 점수의 신뢰구간

종합 점수 0.796은 하나의 점 추정치(point estimate)일 뿐이다. 평가 데이터의 샘플 크기, 메트릭 측정 오차, 가중치 불확실성 등을 고려하면 신뢰구간이 존재한다.

13.8.1 델타 메서드 (Delta Method)

종합 점수의 분산 근사
$$\text{Var}(S_{\text{total}}) \approx \sum_{d=1}^{6} W_d^2 \cdot \text{Var}(S_{\text{dim}_d}) + 2\sum_{d < e} W_d W_e \cdot \text{Cov}(S_{\text{dim}_d}, S_{\text{dim}_e})$$

13.8.2 몬테카를로 전파

import numpy as np def monte_carlo_score(dimension_scores, dimension_stds, weights, n_sim=10000): """종합 점수의 신뢰구간을 몬테카를로로 추정""" samples = [] for _ in range(n_sim): sampled = np.random.normal(dimension_scores, dimension_stds) sampled = np.clip(sampled, 0, 1) # [0,1] 범위 보장 total = np.dot(weights, sampled) samples.append(total) samples = np.array(samples) return { "mean": np.mean(samples), "ci_95": (np.percentile(samples, 2.5), np.percentile(samples, 97.5)), "grade_risk": { "A+": np.mean(samples >= 0.95), "A": np.mean((samples >= 0.85) & (samples < 0.95)), "B": np.mean((samples >= 0.70) & (samples < 0.85)), "C": np.mean((samples >= 0.55) & (samples < 0.70)), } } # 사용 예 result = monte_carlo_score( dimension_scores=[0.82, 0.71, 0.65, 0.88, 0.95, 0.79], dimension_stds=[0.03, 0.05, 0.08, 0.04, 0.02, 0.06], weights=[0.25, 0.15, 0.20, 0.15, 0.15, 0.10] ) print(f"95% CI: [{result['ci_95'][0]:.3f}, {result['ci_95'][1]:.3f}]") print(f"B 등급 확률: {result['grade_risk']['B']:.1%}")

13.8.3 민감도 분석 (Sobol Indices)

"어떤 차원의 불확실성이 종합 점수의 불확실성에 가장 크게 기여하는가?"를 정량화한다. 1차 Sobol 지수가 높은 차원은 해당 차원의 정밀도를 높이는 것이 최우선 과제임을 의미한다.

1차 Sobol 지수
$$S_d = \frac{\operatorname{Var}_{x_d}\!\big(\mathbb{E}_{x_{\sim d}}[S_{\text{total}} \mid x_d]\big)}{\operatorname{Var}(S_{\text{total}})}$$

$x_d$: 차원 $d$의 입력, $x_{\sim d}$: 나머지 차원들. 분자는 "$x_d$만 변할 때 종합 점수 기대값의 분산", 분모는 종합 점수의 전체 분산. $S_d$가 클수록 해당 차원이 불확실성을 지배

import numpy as np def sobol_first_order(dim_scores, dim_stds, weights, n=20000): """몬테카를로 기반 1차 Sobol 지수 (Saltelli 표본법 근사)""" d = len(dim_scores) rng = np.random.default_rng(0) A = rng.normal(dim_scores, dim_stds, size=(n, d)).clip(0, 1) B = rng.normal(dim_scores, dim_stds, size=(n, d)).clip(0, 1) f = lambda X: X @ np.asarray(weights) yA, yB = f(A), f(B) varY = np.var(np.concatenate([yA, yB])) sobol = {} for j in range(d): AB = A.copy(); AB[:, j] = B[:, j] # j열만 B로 교체 yAB = f(AB) sobol[j] = np.mean(yB * (yAB - yA)) / varY # Jansen 추정량 return sobol dims = ["task", "coord", "eff", "robust", "safety", "align"] s = sobol_first_order( [.82, .71, .65, .88, .95, .79], [.03, .05, .08, .04, .02, .06], # eff의 분산이 가장 큼 [.25, .15, .20, .15, .15, .10], ) # eff가 가장 높은 Sobol 지수 → 종합 점수 불확실성의 주 원인 # 결론: eff 메트릭의 측정 정밀도를 높이는 것이 CI를 좁히는 최단 경로 print(sorted(s.items(), key=lambda x: -x[1]))
민감도 분석이 평가 예산을 배분한다

평가에는 비용이 든다 — 더 많은 샘플, 더 많은 심사자, 더 긴 실행. Sobol 분석은 "어디에 평가 예산을 더 쓸 것인가"에 정량적으로 답한다. 종합 점수의 불확실성을 가장 크게 키우는 차원(위 예에서 Efficiency)에 추가 샘플을 투입하면, 같은 비용으로 신뢰구간을 가장 빠르게 좁힐 수 있다. 모든 차원을 균등하게 더 측정하는 것은 비효율적이다.

13.9 시계열 집계: 버전 간 점수 비교

에이전트는 지속적으로 업데이트된다. v1.0, v1.1, v2.0의 종합 점수를 비교할 때, 점수 변화가 실제 개선인지 평가 노이즈인지 구분해야 한다. 이 섹션에서는 점수 추이를 추적하고 통계적으로 유의미한 변화를 감지하는 방법을 다룬다.

왜 시계열 분석이 필요한가?

단일 시점의 평가 점수는 스냅샷에 불과하다. 에이전트가 매주 업데이트되는 환경에서는: (1) 이번 주 점수 하락이 일시적 노이즈인지 실제 퇴보인지 판단해야 하고; (2) 장기적 개선 트렌드와 단기적 변동을 분리해야 하며; (3) "언제부터 점수가 급락했는가?"를 정확히 식별해야 한다. 이는 MLOps의 모니터링과 동일한 문제 공간이다.

13.9.1 롤링 윈도우 평균

지수 이동 평균 (EMA)
$$\bar{S}_t = \alpha \cdot S_t + (1 - \alpha) \cdot \bar{S}_{t-1}, \quad \alpha \in (0, 1]$$

$S_t$: t 시점의 종합 점수, $\alpha$: 평활화 계수 (작을수록 과거에 더 많은 가중치)

13.9.2 드리프트 감지 (Drift Detection)

class ScoreDriftDetector: """종합 점수의 통계적 유의성 검정""" def detect_regression(self, scores_before, scores_after, alpha=0.05): """Welch's t-test로 점수 저하 검출""" from scipy import stats t_stat, p_value = stats.ttest_ind( scores_before, scores_after, equal_var=False ) mean_diff = np.mean(scores_after) - np.mean(scores_before) return { "regressed": p_value < alpha and mean_diff < 0, "improved": p_value < alpha and mean_diff > 0, "p_value": p_value, "effect_size": mean_diff / np.std(scores_before), } def detect_concept_drift(self, window_scores, window_size=10): """ADWIN 알고리즘 기반 개념 드리프트 감지""" from river.drift import ADWIN drift = ADWIN() alerts = [] for i, score in enumerate(window_scores): drift.update(score) if drift.drift_detected: alerts.append({"index": i, "score": score}) return alerts

13.9.3 회귀 방지 게이트 (Regression Gate)

새 버전의 점수가 이전 버전보다 통계적으로 유의하게 낮으면 자동으로 배포를 차단하는 추가 게이트. CI/CD 파이프라인(Ch.14)과 연동된다.

회귀 판정 조건
$$\text{RegressionGate}(S_{\text{new}}, S_{\text{old}}) = \begin{cases} \text{BLOCK} & \text{if } S_{\text{new}} < S_{\text{old}} - \delta \text{ and } p < 0.05 \\ \text{WARN} & \text{if } S_{\text{new}} < S_{\text{old}} - \delta/2 \\ \text{PASS} & \text{otherwise} \end{cases}$$

$\delta$: 허용 가능한 점수 하락 폭 (도메인별 설정, 일반적으로 0.02~0.05)

13.10 다중 평가자 합의: 신뢰할 수 있는 점수의 조건

종합 점수가 의사결정의 근거가 되려면, 그 점수를 만들어낸 평가 자체가 신뢰할 수 있어야 한다. 하나의 LLM-as-Judge, 하나의 인간 평가자, 또는 하나의 자동 메트릭만으로는 측정 편향(measurement bias)을 통제할 수 없다. 이 절에서는 복수의 평가자(인간·LLM·룰 기반)가 산출한 점수를 어떻게 합의(consensus)로 수렴시키고, 그 합의의 신뢰도를 어떻게 정량화하는지 다룬다.

왜 단일 평가자를 신뢰하면 안 되는가?

LLM-as-Judge는 위치 편향(position bias), 장황함 편향(verbosity bias), 자기 강화 편향(self-enhancement bias, 자기 모델 출력을 선호)을 가진다(Zheng et al., 2023). 인간 평가자는 피로·기준 표류(criterion drift)·후광 효과에 취약하다. 따라서 점수의 신뢰도는 "여러 독립 평가자가 얼마나 일치하는가(inter-rater reliability)"로 검증되어야 하며, 일치도가 낮으면 점수 자체가 아니라 평가 프로토콜을 먼저 의심해야 한다.

13.10.1 평가자 간 일치도 지표

일치도 측정에는 데이터 유형(명목·순위·구간)과 평가자 수에 따라 적절한 지표가 다르다. 단순 일치율(percent agreement)은 우연에 의한 일치를 보정하지 못하므로 실무에서는 우연 보정 지표를 사용한다.

지표데이터 유형평가자 수우연 보정적용 상황
Cohen's κ명목/순위2명 고정O두 심사자 라벨 일치
Fleiss' κ명목3명+ (가변)O다수 심사자 범주 분류
Krippendorff's α모든 유형가변, 결측 허용O가장 범용적, 권장
ICC (급내 상관)구간/연속가변O연속 점수(0~1) 일치
Kendall's W순위가변O에이전트 순위 합의
Krippendorff's α
$$\alpha = 1 - \frac{D_o}{D_e} = 1 - \frac{\sum_{c}\sum_{k} o_{ck}\,\delta^2(c,k)}{\sum_{c}\sum_{k} n_c n_k\,\delta^2(c,k) \,/\,(n-1)}$$

$D_o$: 관측된 불일치, $D_e$: 우연에 기대되는 불일치, $\delta^2(c,k)$: 값 $c$와 $k$ 사이의 거리 함수(구간 데이터는 $(c-k)^2$). $\alpha=1$ 완전 일치, $\alpha=0$ 우연 수준, $\alpha<0$ 체계적 불일치

α 범위해석권고
α ≥ 0.80신뢰할 수 있는 합의점수 그대로 사용
0.67 ≤ α < 0.80잠정적 결론만 허용경계 사례 재검토
α < 0.67합의 불충분평가 기준(rubric) 재정의

13.10.2 연속 점수의 일치도: ICC

6차원 점수처럼 0~1 연속값일 때는 κ 계열 대신 급내 상관계수(Intraclass Correlation Coefficient)를 사용한다. ICC(2,k)는 무작위 효과 평가자 모델에서 k명 평균의 신뢰도를 나타낸다.

ICC(2,1) — 단일 평가자 절대 일치
$$\text{ICC}(2,1) = \frac{MS_R - MS_E}{MS_R + (k-1)MS_E + \frac{k}{n}(MS_C - MS_E)}$$

$MS_R$: 대상(row) 간 평균제곱, $MS_C$: 평가자(column) 간 평균제곱, $MS_E$: 잔차 평균제곱, $n$: 대상 수, $k$: 평가자 수

13.10.3 순위 합의: Kendall's W

"5개 에이전트를 평가자들이 동일한 순서로 평가하는가?"를 측정한다. 종합 점수의 절대값보다 순위(어느 에이전트가 더 나은가)가 중요한 비교 평가에서 핵심 지표다.

Kendall's W (일치 계수)
$$W = \frac{12 \sum_{i=1}^{n}\left(R_i - \bar{R}\right)^2}{m^2 (n^3 - n)}, \quad \bar{R} = \frac{1}{n}\sum_{i=1}^{n} R_i$$

$m$: 평가자 수, $n$: 평가 대상 수, $R_i$: 대상 $i$가 받은 순위의 합. $W=1$ 완전 일치 순위, $W=0$ 무작위

13.10.4 ConsensusAnalyzer 구현

import numpy as np from itertools import combinations class ConsensusAnalyzer: """다중 평가자(인간/LLM/룰)의 점수 합의와 신뢰도 분석""" def __init__(self, ratings: np.ndarray): """ratings: shape (n_items, n_raters), 0~1 연속 점수, NaN 결측 허용""" self.ratings = np.asarray(ratings, dtype=float) self.n_items, self.n_raters = self.ratings.shape def krippendorff_alpha(self) -> float: """구간 데이터용 Krippendorff α (거리=제곱오차)""" vals = self.ratings pairs = [] for i in range(self.n_items): row = vals[i][~np.isnan(vals[i])] for a, b in combinations(row, 2): pairs.append((a - b) ** 2) Do = np.mean(pairs) if pairs else 0.0 flat = vals[~np.isnan(vals)] De = np.mean([(a - b) ** 2 for a, b in combinations(flat, 2)]) return 1.0 - Do / De if De > 0 else 1.0 def icc_2_1(self) -> float: """양방향 무작위 효과 ICC(2,1) 절대 일치""" x = self.ratings n, k = x.shape grand = np.nanmean(x) ms_r = k * np.nansum((np.nanmean(x, axis=1) - grand) ** 2) / (n - 1) ms_c = n * np.nansum((np.nanmean(x, axis=0) - grand) ** 2) / (k - 1) ss_t = np.nansum((x - grand) ** 2) ms_e = (ss_t - (n - 1) * ms_r - (k - 1) * ms_c) / ((n - 1) * (k - 1)) return (ms_r - ms_e) / (ms_r + (k - 1) * ms_e + k * (ms_c - ms_e) / n) def kendall_w(self) -> float: """평가자 간 순위 일치 계수""" ranks = np.apply_along_axis(lambda c: c.argsort().argsort() + 1, 0, self.ratings) R = ranks.sum(axis=1) n, m = self.n_items, self.n_raters S = np.sum((R - R.mean()) ** 2) return 12 * S / (m ** 2 * (n ** 3 - n)) def consensus_score(self) -> dict: """평가자 합의 점수(중앙값) + 신뢰도 메타데이터""" median = np.nanmedian(self.ratings, axis=1) spread = np.nanstd(self.ratings, axis=1) return { "consensus": median, "disagreement": spread, "alpha": self.krippendorff_alpha(), "icc": self.icc_2_1(), "reliable": self.krippendorff_alpha() >= 0.80, } # 사용 예: 3명 평가자(인간·GPT-4·Claude)의 6차원 점수 ratings = np.array([ [0.82, 0.80, 0.85], # Task [0.71, 0.68, 0.73], # Coordination [0.65, 0.60, 0.69], # Efficiency [0.88, 0.90, 0.86], # Robustness [0.95, 0.96, 0.94], # Safety [0.79, 0.75, 0.82], # Alignment ]) analyzer = ConsensusAnalyzer(ratings) print(analyzer.consensus_score()) # alpha ≈ 0.91 → 신뢰할 수 있는 합의, 중앙값을 차원 점수로 채택
합의가 낮을 때의 대응 프로토콜

α < 0.67이면 점수를 평균내지 말고 다음을 수행하라: (1) 불일치 사례 격리 — disagreement(표준편차)가 큰 항목만 추출; (2) 근거 비교 — 각 평가자의 판단 근거(rationale)를 나란히 검토; (3) 루브릭 명확화 — 모호한 평가 기준을 구체적 예시로 보강; (4) 재평가. 단순 평균은 불일치를 "숨기는" 것일 뿐 해결하지 못한다. 합의 분석은 점수를 만드는 단계가 아니라 점수를 승인하는 게이트다.

13.11 게이트 시스템 고급: 다단계·조건부·복구 경로

13.2의 Safety Gate가 단일 거부권이었다면, 프로덕션 환경에서는 배포 수명주기의 각 단계마다 서로 다른 게이트가 필요하다. 개발 → 스테이징 → 카나리 → 전면 배포로 이어지는 파이프라인에서 각 전이(transition)는 별도의 게이트가 통제한다.

13.11.1 핵심 게이트 정의

게이트전이 단계통과 조건실패 시 조치
Deploy Gate스테이징 → 카나리$S_{\text{total}} \geq 80 \wedge$ Safety $\geq 90 \wedge$ Robust $\geq 70$배포 차단, 리포트 생성
Canary Gate카나리 → 전면$S_{\text{canary}} \geq S_{\text{prod}} - 3$자동 롤백
Promotion Gate전면 승격$S_{\text{total}} \geq 85 \wedge$ regression $\leq 5\%$수동 승인 요청
Regression Gate모든 버전 업데이트$S_{\text{new}} \geq S_{\text{old}} - \delta$ (p≥0.05)병합 차단
Deploy Gate (논리 합성)
$$\text{Deploy} = \mathbb{1}\!\left[S_{\text{total}} \geq 80\right] \wedge \mathbb{1}\!\left[s_{\text{safety}} \geq 90\right] \wedge \mathbb{1}\!\left[s_{\text{robust}} \geq 70\right]$$

$\mathbb{1}[\cdot]$: 지시 함수. 모든 조건이 동시에 참(AND)이어야 PASS — 어느 하나라도 미달이면 차단

13.11.2 게이트 아키텍처

┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────┐ │ 테스트 실행 │──▶│ 메트릭 계산 │──▶│ 게이트 체크 │──▶│ PASS │──▶ 배포 └─────────────┘ └──────────────┘ └──────┬──────┘ └──────────┘ │ FAIL ┌──────────────────────────────────────────┐ │ 복구 경로(Recovery Path) │ │ ① 자동 롤백 (카나리) → 이전 안정 버전 │ │ ② 알림 (Slack/PagerDuty) → 담당자 호출 │ │ ③ 실패 리포트 생성 → 차원별 미달 항목 │ │ ④ 오버라이드 요청 (인간 승인 필요) │ └──────────────────────────────────────────┘

13.11.3 다단계 GateChecker 구현

from dataclasses import dataclass, field from enum import Enum from typing import Callable class GateDecision(Enum): PASS = "PASS" WARN = "WARN" FAIL = "FAIL" @dataclass class GateRule: name: str predicate: Callable[[dict], bool] # 점수 dict → 통과 여부 severity: GateDecision = GateDecision.FAIL message: str = "" @dataclass class GateReport: decision: GateDecision passed: list = field(default_factory=list) violated: list = field(default_factory=list) recovery: list = field(default_factory=list) class MultiStageGate: """배포 수명주기 단계별 게이트 평가기""" def __init__(self, rules: list[GateRule]): self.rules = rules def evaluate(self, scores: dict) -> GateReport: report = GateReport(decision=GateDecision.PASS) for rule in self.rules: if rule.predicate(scores): report.passed.append(rule.name) else: report.violated.append((rule.name, rule.message)) if rule.severity == GateDecision.FAIL: report.decision = GateDecision.FAIL elif rule.severity == GateDecision.WARN and report.decision != GateDecision.FAIL: report.decision = GateDecision.WARN if report.decision == GateDecision.FAIL: report.recovery = self._recovery_plan(report.violated) return report def _recovery_plan(self, violated) -> list: plan = ["이전 안정 버전으로 자동 롤백", "#oncall 채널 알림 발송"] for name, msg in violated: plan.append(f"미달 항목 분석: {name} — {msg}") return plan # Deploy Gate 구성 deploy_gate = MultiStageGate([ GateRule("total_score", lambda s: s["total"] >= 80, message="종합 점수 80 미만"), GateRule("safety_floor", lambda s: s["safety"] >= 90, message="Safety 90 미만 — 거부권 발동"), GateRule("robust_floor", lambda s: s["robust"] >= 70, severity=GateDecision.WARN, message="Robustness 경고 수준"), ]) report = deploy_gate.evaluate({"total": 82, "safety": 88, "robust": 75}) print(report.decision) # GateDecision.FAIL (safety 88 < 90) print(report.recovery) # ['이전 안정 버전으로 자동 롤백', '#oncall 알림', ...]

13.11.4 조건부 게이트와 오버라이드

모든 실패가 동등하지 않다. 긴급 보안 패치는 일부 게이트를 우회(override)해야 할 수 있고, 실험적 기능은 더 엄격한 게이트를 적용해야 한다. 조건부 게이트는 배포 컨텍스트(태그·환경·승인자)에 따라 규칙 집합을 동적으로 선택한다.

컨텍스트적용 게이트오버라이드 권한감사 로그
일반 배포Deploy + Regression불가자동 기록
긴급 보안 패치Safety만SRE 리드 2인 승인필수 + 사후 검토
실험적 기능(flag)Deploy + 추가 Robustness 90불가자동 기록
롤백없음(즉시 허용)해당 없음자동 기록
오버라이드는 버그가 아니라 기능이다 — 단, 추적되어야 한다

게이트 오버라이드를 전면 금지하면 운영팀은 게이트 자체를 우회하는 "그림자 배포 경로"를 만든다. 더 나쁜 결과다. 올바른 설계는 (1) 오버라이드를 일급 기능으로 제공하되, (2) 다중 승인을 요구하고, (3) 모든 오버라이드를 불변 감사 로그(immutable audit log)에 기록하며, (4) 사후 검토(postmortem)를 의무화하는 것이다. EU AI Act의 고위험 시스템 요구사항인 "인간 감독(human oversight)" 조항과도 직접 연결된다.

13.12 대시보드 설계: 점수를 의사결정으로 옮기는 인터페이스

점수와 등급은 누군가 보고 행동을 결정할 때 비로소 가치를 가진다. 잘 설계된 평가 대시보드는 (1) 현재 상태를 5초 안에 파악하게 하고, (2) 악화 추세를 조기에 드러내며, (3) "무엇을 고쳐야 하는가"로 곧장 안내한다. 이 절에서는 세 가지 핵심 시각화 패턴을 다룬다.

시각화 패턴답하는 질문차트 유형주의점
종합 게이지지금 배포해도 되는가?등급 배지 + 게이트 상태등색맹 대응(아이콘 병기)
차원 레이더어느 차원이 약한가?6축 레이더축 6개 초과 시 가독성 저하
집계법 비교점수가 얼마나 견고한가?그룹 막대(산술/기하/최소제약)방법 간 격차가 위험 신호
버전 추세개선 중인가 퇴보 중인가?라인 + 회귀 마커노이즈와 추세 분리
차원 히트맵여러 에이전트를 한눈에색상 격자(agent×dim)정규화 스케일 통일

13.12.1 집계법 비교 차트

같은 차원 점수라도 어떤 집계 연산자(13.5)를 쓰느냐에 따라 종합 점수가 달라진다. 산술 평균과 기하 평균·최소제약 평균의 격차가 크다면, 이는 특정 차원이 심각하게 낮다는 위험 신호다. 다음 차트는 5개 에이전트에 대해 세 가지 집계법을 비교한다.

집계법 간 격차를 위험 지표로 읽기

산술 평균은 높은데 기하·최소제약 평균이 낮은 에이전트는 "겉으로는 괜찮지만 치명적 약점이 있는" 후보다. 위 차트에서 Agent C가 전형적인 예다 — 산술 평균은 0.78이지만 한 차원(Efficiency)이 0.35로 낮아 기하 평균은 0.71, 최소제약 평균은 0(임계 미달 차원 존재)로 급락한다. 세 막대의 높이 차이 자체가 균형 상태를 진단하는 메트릭이 된다.

13.12.2 버전 추세 차트

버전별 종합 점수를 시계열로 추적하고, 회귀 게이트(13.9.3)가 BLOCK을 발동한 지점을 마커로 표시한다. EMA(지수 이동 평균) 라인을 함께 그려 단기 노이즈와 장기 추세를 분리한다.

13.12.3 대시보드 데이터 준비 코드

from dataclasses import dataclass, asdict @dataclass class DashboardPayload: """프런트엔드(Chart.js)로 직렬화되는 대시보드 데이터""" grade: str total: float gate: str dimensions: dict # {차원: 점수} aggregations: dict # {방법: 점수} trend: list # [{version, score, regressed}] def build_dashboard(result, history) -> dict: """평가 결과 + 이력 → 대시보드 JSON""" dims = {d: v["score"] for d, v in result["dimensions"].items()} scores = list(dims.values()) payload = DashboardPayload( grade=result["grade"], total=result["total"], gate=result["gate"], dimensions=dims, aggregations={ "arithmetic": sum(scores) / len(scores), "geometric": np.prod(scores) ** (1 / len(scores)), "min_constrained": 0.0 if min(scores) < 0.5 else sum(scores) / len(scores), }, trend=[{"version": h["v"], "score": h["s"], "regressed": h["r"]} for h in history], ) return asdict(payload)

13.12.4 인터랙티브 등급 계산기

독자가 직접 6차원 점수를 입력하면 종합 점수·등급·게이트 판정이 실시간으로 갱신되는 위젯이다. 가중치는 13.3의 "고객 지원 챗봇" 프로파일을 기본값으로 사용한다.

게이트: –

13.13 임계값 자동 튜닝: 손으로 정한 숫자를 데이터로 교정하기

지금까지의 임계값(0.80, 90, δ=0.05 등)은 모두 전문가의 추정값이다. 시스템이 성숙하면 이 숫자들을 축적된 평가 데이터로 교정해야 한다. 임계값이 너무 느슨하면 불량 에이전트가 통과하고, 너무 빡빡하면 멀쩡한 에이전트가 반복적으로 차단되어 운영팀이 게이트를 신뢰하지 않게 된다.

13.13.1 백분위 기반 임계값

가장 단순하면서 강건한 방법. "지난 분기 상위 N% 에이전트의 점수 분포"에서 임계값을 도출한다. 절대 기준의 인플레이션/디플레이션 문제(13.7.1)를 자동으로 흡수한다.

백분위 임계값
$$\theta_{\text{deploy}} = Q_{p}\big(\{S^{(1)}, \ldots, S^{(N)}\}\big), \quad p = 1 - \text{목표 통과율}$$

$Q_p$: $p$-분위수. 목표 통과율 70%면 $p=0.30$ → 하위 30% 지점이 임계값. 분포가 이동하면 임계값도 자동 추종

13.13.2 비용 민감 임계값 최적화

거짓 통과(불량 배포)와 거짓 차단(정상 차단)의 비용이 비대칭일 때, 두 오류의 기대 비용 합을 최소화하는 임계값을 찾는다.

기대 비용 최소화
$$\theta^* = \arg\min_{\theta}\; C_{\text{FP}}\cdot P(\text{pass} \mid \text{bad})\,(1-\pi) + C_{\text{FN}}\cdot P(\text{block} \mid \text{good})\,\pi$$

$C_{\text{FP}}$: 불량 배포 비용, $C_{\text{FN}}$: 정상 차단 비용, $\pi$: 정상 에이전트 사전 확률. 안전 도메인은 $C_{\text{FP}} \gg C_{\text{FN}}$ → 임계값 상향

13.13.3 ThresholdTuner 구현

import numpy as np class ThresholdTuner: """축적된 평가 이력으로 게이트 임계값을 교정""" def __init__(self, history: list[dict]): """history: [{'score': float, 'label': 'good'|'bad'}]""" self.scores = np.array([h["score"] for h in history]) self.labels = np.array([h["label"] == "good" for h in history]) def percentile_threshold(self, target_pass_rate=0.70) -> float: return float(np.percentile(self.scores, (1 - target_pass_rate) * 100)) def cost_optimal_threshold(self, c_fp=10.0, c_fn=1.0) -> dict: """거짓 통과/거짓 차단 비용을 반영한 최적 임계값 탐색""" candidates = np.linspace(0, 1, 101) best_t, best_cost = 0.5, float("inf") for t in candidates: passed = self.scores >= t fp = np.sum(passed & ~self.labels) # 불량인데 통과 fn = np.sum(~passed & self.labels) # 정상인데 차단 cost = c_fp * fp + c_fn * fn if cost < best_cost: best_cost, best_t = cost, t return {"threshold": best_t, "expected_cost": best_cost} def recommend(self) -> dict: return { "percentile_70": self.percentile_threshold(0.70), "cost_optimal": self.cost_optimal_threshold()["threshold"], "n_samples": len(self.scores), }

13.13.4 인간 피드백 루프

자동 튜닝의 마지막 안전장치는 인간 검토다. 게이트 판정과 운영자의 사후 판단(이 배포는 실제로 문제였는가?)을 대조하여, 불일치가 누적되면 임계값 재학습을 트리거한다. 이는 강화학습의 보상 모델 교정과 동일한 구조다.

자동 튜닝의 함정: 피드백 루프 오염

임계값을 통과한 에이전트만 배포되고, 배포된 에이전트의 데이터만 다시 수집되면, 학습 데이터가 점점 "통과한 것들"로 편향된다(선택 편향). 이를 막으려면 (1) 차단된 에이전트도 섀도 모드로 일정 비율 평가하여 라벨을 수집하고, (2) 임계값 변경은 점진적(예: 분기당 ±0.02 이내)으로 제한하며, (3) 변경 전후를 A/B로 비교 검증해야 한다.

13.14 실전 파이프라인: ScoringPipeline 통합 구현

지금까지 다룬 모든 구성 요소 — 차원 집계, Safety Gate, 비선형 변환, 불확실성 정량화, 다단계 게이트 — 를 하나의 실행 가능한 파이프라인으로 통합한다. 설계 목표는 (1) 설정과 로직의 분리(YAML 기반 선언적 구성), (2) 모든 중간 산출물의 추적 가능성, (3) Ch.14 CI/CD와의 무접점 연동이다.

13.14.1 설정 스키마 (config.yaml)

# scoring.yaml — 점수 산출 파이프라인 선언적 설정 domain: customer_support dimension_weights: # 13.3 도메인 프로파일 task: 0.25 coordination: 0.15 efficiency: 0.20 robustness: 0.15 safety: 0.15 alignment: 0.10 aggregation: method: min_constrained # arithmetic | geometric | min_constrained floor: 0.50 # 최소제약 임계값 θ safety_gate: # 13.2 거부권 임계값 (pass, warn) hallucination_rate: [0.05, 0.10] pii_exposure_rate: [0.00, 0.00] instruction_compliance:[0.90, 0.80] grades: # 13.1.3 등급 경계 - {min: 95, label: "A+"} - {min: 85, label: "A"} - {min: 70, label: "B"} - {min: 55, label: "C"} - {min: 40, label: "D"} - {min: 0, label: "F"} gates: deploy: {total: 80, safety: 90, robustness: 70} regression: {delta: 0.05, alpha: 0.05} uncertainty: monte_carlo_sims: 10000 report_ci: true

13.14.2 ScoringPipeline 클래스

import yaml, numpy as np from dataclasses import dataclass @dataclass class ScoringResult: total: float grade: str gate: str final: float dimensions: dict ci_95: tuple audit: dict class ScoringPipeline: """설정 기반 종합 점수 산출 파이프라인 (Ch.11 입력 → Ch.14 출력)""" def __init__(self, config_path: str): with open(config_path) as f: self.cfg = yaml.safe_load(f) self.gate = SafetyGate(self.cfg["safety_gate"]) def _aggregate(self, dim_scores: dict) -> float: w = self.cfg["dimension_weights"] s = np.array([dim_scores[d] for d in w]) weights = np.array([w[d] for d in w]) method = self.cfg["aggregation"]["method"] if method == "arithmetic": return float(np.dot(weights, s)) if method == "geometric": return float(np.prod(s ** weights)) # min_constrained floor = self.cfg["aggregation"]["floor"] return 0.0 if np.any(s < floor) else float(np.dot(weights, s)) def _grade(self, score100: float) -> str: for g in self.cfg["grades"]: if score100 >= g["min"]: return g["label"] return "F" def run(self, dim_scores: dict, dim_stds: dict, safety_metrics: dict) -> ScoringResult: gate = self.gate.check(safety_metrics) total = self._aggregate(dim_scores) # Safety Override (13.1.2) if gate.decision == "FAIL": final = min(total, 0.39) elif gate.decision == "WARN": final = total * 0.85 else: final = total # 불확실성 전파 (13.8.2) mc = monte_carlo_score( [dim_scores[d] for d in dim_scores], [dim_stds[d] for d in dim_scores], [self.cfg["dimension_weights"][d] for d in dim_scores], n_sim=self.cfg["uncertainty"]["monte_carlo_sims"], ) return ScoringResult( total=round(total * 100, 1), grade=self._grade(final * 100), gate=gate.decision, final=round(final * 100, 1), dimensions=dim_scores, ci_95=mc["ci_95"], audit={"gate_details": gate.details, "method": self.cfg["aggregation"]["method"]}, ) # 실행 pipe = ScoringPipeline("scoring.yaml") result = pipe.run( dim_scores={"task": .82, "coordination": .71, "efficiency": .65, "robustness": .88, "safety": .95, "alignment": .79}, dim_stds={"task": .03, "coordination": .05, "efficiency": .08, "robustness": .04, "safety": .02, "alignment": .06}, safety_metrics={"hallucination_rate": .03, "pii_exposure_rate": .00, "instruction_compliance": .97}, ) print(result.grade, result.gate, result.ci_95) # B PASS (0.74, 0.81)
선언적 설정의 운영상 이점

로직(코드)과 정책(YAML)을 분리하면 세 가지 이점이 생긴다: (1) 비개발자 거버넌스 — 컴플라이언스팀이 코드를 건드리지 않고 임계값을 검토·승인; (2) 버전 관리되는 정책 — 임계값 변경이 Git diff로 추적되어 "언제 누가 왜 기준을 바꿨는가"가 감사 가능; (3) 도메인별 재사용 — 동일 엔진으로 customer_support.yaml, medical.yaml을 전환만으로 운영. 이는 Ch.14의 정책-as-코드(policy-as-code) 패턴으로 이어진다.

13.15 규제 준수 매핑: 점수 시스템과 AI 거버넌스

점수 산출과 게이트 시스템은 단순한 엔지니어링 편의가 아니라, 점증하는 AI 규제 요구사항을 충족하는 준수 메커니즘(compliance mechanism)이기도 하다. 이 절은 본 프레임워크의 구성 요소가 주요 규제·표준의 어떤 조항에 매핑되는지 정리한다.

13.15.1 EU AI Act 매핑

EU AI Act 요구사항본 프레임워크 구성요소구현 위치
제9조 리스크 관리 시스템6차원 평가 + 회귀 게이트13.1, 13.9
제14조 인간 감독오버라이드 + 다중 승인13.11.4
제15조 정확성·견고성Robustness 차원 + 신뢰구간13.8
제12조 기록 보관(logging)불변 감사 로그13.11.4, 13.14
제10조 데이터 거버넌스편향 메트릭(bias_disparity)13.2.1

13.15.2 NIST AI RMF & ISO/IEC 42001

표준핵심 기능/조항대응 메커니즘
NIST AI RMFMEASURE 1.1 (메트릭 정의)16개 메트릭 명세(Ch.11)
NIST AI RMFMANAGE 2.3 (배포 결정)Deploy Gate(13.11)
NIST AI RMFGOVERN 1.2 (책임성)감사 로그 + 오버라이드 추적
ISO/IEC 420018.3 운영 통제다단계 게이트 시스템
ISO/IEC 420019.1 모니터링·측정버전 추세 + 드리프트(13.9)
점수 시스템을 감사 증거로 설계하라

규제 대응을 사후에 덧붙이면 비용이 폭증한다. 처음부터 모든 게이트 판정, 임계값 변경, 오버라이드를 타임스탬프·행위자·근거가 포함된 불변 레코드로 남기면, 점수 파이프라인의 부산물이 곧 감사 증거(audit evidence)가 된다. EU AI Act의 적합성 평가(conformity assessment)나 ISO 42001 인증 심사에서 "어떻게 배포를 결정했는가"를 묻는 질문에, 본 프레임워크는 코드와 로그로 직접 답할 수 있다.

13.15b 실패 사례 연구: 점수 시스템이 무너지는 방식

잘못 설계된 점수 시스템은 "측정하고 있다는 착각"을 주기 때문에 측정하지 않는 것보다 위험할 수 있다. 다음 네 가지는 실제 프로덕션에서 반복적으로 관찰되는 실패 패턴이다. 각 사례는 증상 → 진단 → 처방 구조로 정리한다.

13.15b.1 사례 A — 굿하트의 법칙: 메트릭이 목표가 될 때

증상: 한 고객 지원 에이전트의 종합 점수가 분기마다 꾸준히 상승(78→85→91)했으나, 실제 고객 만족도(CSAT)는 오히려 하락했다. 진단: 팀이 "task_completion" 메트릭을 최적화하면서, 에이전트가 어려운 질문을 "해결됨"으로 조기 종료하도록 학습되었다. 측정 대상(완료율)이 최적화 목표가 되는 순간 측정의 타당성이 붕괴된 것 — 굿하트의 법칙(Goodhart's Law)의 전형이다. 처방: (1) 단일 메트릭이 아닌 6차원 균형(13.5의 기하 평균)으로 전환하여 한 메트릭의 게이밍을 억제; (2) CSAT 같은 외부 결과 메트릭을 보정 기준(13.23)으로 추가; (3) 메트릭을 주기적으로 로테이션하여 과적합 방지.

13.15b.2 사례 B — 평균의 함정: 치명적 약점이 숨겨질 때

증상: 종합 점수 84점(B등급)으로 배포된 금융 분석 에이전트가 배포 첫날 규제 위반 출력을 생성했다. 진단: 산술 가중 평균을 사용한 탓에, Safety 차원이 0.45로 낮았음에도 Task(0.95)·Efficiency(0.92)의 높은 점수가 이를 상쇄해 종합 84점이 나왔다. 거부권(13.2)이 비활성화되어 있었고 최소제약 평균(13.5)도 쓰지 않았다. 처방: 금융 도메인 템플릿(13.25)을 적용 — 최소제약 집계 + Safety floor 0.70 + Safety Gate 거부권. 동일 점수를 재계산하면 Safety 0.45 < floor 0.70 → 종합 0점, 배포 차단.

13.15b.3 사례 C — 보정 부재: 임계값이 거짓말할 때

증상: "80점 이상 배포" 규칙을 운영했으나, 80~85점대 에이전트의 실제 운영 실패율이 30%에 달했다. 진단: LLM-as-Judge 점수가 과신(overconfident)되어 있어 80점이 실제로는 ~65% 품질에 대응했다. ECE(13.23.1)를 측정하지 않아 이 괴리를 인지하지 못했다. 처방: 과거 배포 결과를 라벨링해 Isotonic 보정을 적용했더니, 보정 후 "실제 80% 품질"에 대응하는 점수는 88점이었다. 임계값을 88로 상향하자 운영 실패율이 8%로 감소.

13.15b.4 사례 D — 합의 환상: 단일 평가자를 신뢰할 때

증상: GPT-4 단일 심사자로 평가한 두 에이전트의 점수 차(0.81 vs 0.79)를 근거로 A를 선택했으나, 재평가 시 순위가 뒤집혔다. 진단: 단일 LLM 심사자의 자기 강화·위치 편향(13.10)으로 점수에 ±0.05 이상의 측정 노이즈가 있었고, 0.02 차이는 통계적으로 무의미했다. 처방: 3개 독립 심사자(인간+2 LLM)로 ConsensusAnalyzer(13.10.4) 적용, Krippendorff α=0.62로 합의 불충분 판정 → 루브릭 재정의 후 재평가하여 신뢰할 수 있는 순위 확보.

네 사례를 관통하는 교훈

모든 실패는 "점수를 신뢰하기 전에 점수 자체를 검증하지 않은 것"에서 비롯된다. 점수 시스템에는 두 층의 품질 보증이 필요하다 — (1) 설계 검증: 집계법·거부권·가중치가 도메인 위험에 맞는가(사례 A·B); (2) 측정 검증: 점수가 보정되어 있고(사례 C) 평가자 합의가 충분한가(사례 D). 이 두 검증을 건너뛴 점수는 정밀해 보이는 추측에 불과하다.

13.15c 기존 프레임워크의 점수 집계 방식 비교

이 장에서 설계한 점수 시스템(가중 집계 + Safety 거부권 + 불확실성 정량화)을 기존 평가 프레임워크(Ch.09 참조)의 집계 방식과 나란히 놓으면, 각 접근의 설계 철학과 적용 한계가 선명해진다. 대부분의 기존 프레임워크는 단순 평균이나 단일 지표에 의존하여, 이 장에서 강조한 "치명적 차원의 거부권"이나 "점수의 불확실성"을 다루지 못한다.

프레임워크집계 방식거부권불확실성한계
HELM시나리오별 평균 + 순위 집계없음표준오차 보고차원 간 절충이 암묵적
RAGASRAG 4메트릭 조화 평균없음제한적RAG 외 차원 부재
Chatbot ArenaElo / Bradley-Terry없음신뢰구간(부트스트랩)절대 임계값 부재(순위만)
G-EvalLLM 확률 가중 점수없음없음(단일 판정)보정·합의 미검증
본 프레임워크가중/기하/최소제약 선택Safety 거부권몬테카를로 CI가중치 설정 부담

아래 차트는 점수 집계 역량을 다섯 축(다차원 통합·안전 거부권·불확실성·보정·게이트 자동화)으로 정량화하여 비교한 것이다. 기존 프레임워크는 특정 축에 강점이 있으나, 배포 의사결정에 필요한 거부권·게이트 자동화 축에서 공백이 크다.

기존 집계 방식을 그대로 쓰면 안 되는 이유

HELM·RAGAS의 평균 기반 집계는 벤치마크 리더보드에는 적합하지만, 프로덕션 배포 게이트로는 부적합하다. 리더보드는 "어느 모델이 평균적으로 더 나은가"를 묻지만, 배포는 "이 에이전트가 치명적 결함 없이 안전한가"를 물어야 한다. 전자는 보상적 평균(compensatory averaging)이, 후자는 비보상적 거부권(non-compensatory veto)이 필요하다. 본 프레임워크가 집계법을 선택 가능하게 설계한 이유가 여기 있다 — 같은 점수라도 용도에 따라 다른 집계가 정당하다.

13.16 핵심 공식 요약

이 장에서 도입한 핵심 수식을 한눈에 정리한다. 각 공식은 입력 → 변환 → 의사결정의 흐름에서 특정 단계를 담당한다.

이름공식용도
차원 가중 평균$S_{\text{dim}}=\frac{\sum w_i m_i}{\sum w_i}$메트릭 → 차원13.1.1
종합 가중 합$S_{\text{total}}=\sum W_d S_d$차원 → 종합13.1.2
Safety Override$S_{\text{final}}=\min(S,0.39)$ if FAIL거부권13.1.2
OWA$\sum w_i y_{(i)}$순서 가중 집계13.5.1
기하 평균$\prod s_d^{w_d}$균형 강제13.5
엔트로피 가중치$w_j=\frac{1-H_j}{\sum(1-H_k)}$데이터 기반 가중13.5.3
시그모이드 정규화$\frac{1}{1+e^{-\alpha(x-x_0)}}$비선형 변환13.6.1
몬테카를로 분산$\text{Var}(S)\approx\sum W_d^2\text{Var}(S_d)$불확실성13.8.1
EMA$\bar S_t=\alpha S_t+(1-\alpha)\bar S_{t-1}$추세 평활13.9.1
Krippendorff α$1-D_o/D_e$평가자 일치도13.10.1
비용 최적 임계값$\arg\min_\theta C_{FP}\cdot\text{FP}+C_{FN}\cdot\text{FN}$임계값 튜닝13.13.2

13.17 실습 체크리스트

이 장의 내용을 실제 시스템에 적용할 때 점검할 항목이다. 각 항목은 앞 절의 구체적 구현과 연결된다.

단계점검 항목완료 기준
1. 가중치도메인 프로파일을 정의했는가?$\sum W_d = 1$, 근거 문서화
2. 집계법산술/기하/최소제약 중 선택했는가?도메인 위험 수준과 정합
3. Safety Gate거부권 메트릭·임계값을 설정했는가?PII는 0건 기준
4. 등급등급 경계를 정의했는가?인플레이션 모니터링 포함
5. 불확실성신뢰구간을 보고하는가?점 추정 단독 보고 금지
6. 합의평가자 일치도를 검증하는가?α ≥ 0.80 또는 재검토
7. 게이트다단계 게이트를 구성했는가?복구 경로 정의
8. 튜닝임계값 교정 주기를 정했는가?피드백 루프 오염 방지책
9. 감사모든 판정을 로깅하는가?불변 로그 + 오버라이드 추적
10. 규제준수 매핑을 문서화했는가?EU AI Act / NIST 대응표

13.18 연습 문제

문제 1 — 집계법 선택

한 에이전트의 6차원 점수가 [0.95, 0.92, 0.90, 0.88, 0.30, 0.91]이다(Safety=0.30). 균등 가중치에서 (a) 산술 평균, (b) 기하 평균, (c) floor=0.5인 최소제약 평균을 각각 계산하고, 의료 도메인에 어느 집계법이 적절한지 근거와 함께 논하라. (힌트: 산술 ≈ 0.81, 기하 ≈ 0.74, 최소제약 = 0)

문제 2 — Safety Override

종합 점수 $S_{\text{total}}=0.88$인 에이전트의 Safety Gate가 WARN으로 판정되었다. 13.1.2의 Override 규칙을 적용한 $S_{\text{final}}$과 최종 등급을 구하라. 만약 같은 점수에서 Gate가 FAIL이라면 등급은 어떻게 바뀌는가?

문제 3 — 평가자 합의

세 평가자의 Task 차원 점수가 각각 [0.80, 0.55, 0.82]로 한 평가자가 크게 벗어났다. ConsensusAnalyzer로 disagreement(표준편차)를 계산하고, α < 0.67일 때 13.10.4의 대응 프로토콜 중 어떤 단계를 먼저 수행해야 하는지 서술하라.

문제 4 — 임계값 튜닝

거짓 통과 비용 $C_{FP}=20$, 거짓 차단 비용 $C_{FN}=1$인 안전 도메인에서, 비용 최적 임계값은 백분위 기반 임계값보다 높을까 낮을까? 그 이유를 13.13.2의 기대 비용 공식으로 설명하라.

13.19 참고 문헌

Yager, R. R. (1988). On ordered weighted averaging aggregation operators in multicriteria decisionmaking. IEEE Transactions on Systems, Man, and Cybernetics, 18(1), 183–190. DOI:10.1109/21.87068

Shapley, L. S. (1953). A value for n-person games. Contributions to the Theory of Games, 2(28), 307–317.

Lundberg, S. M., & Lee, S. I. (2017). A unified approach to interpreting model predictions. NeurIPS 2017. arXiv:1705.07874

Krippendorff, K. (2018). Content Analysis: An Introduction to Its Methodology (4th ed.). SAGE.

Zheng, L., et al. (2023). Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena. NeurIPS 2023 Datasets and Benchmarks. arXiv:2306.05685

Brans, J. P., & Vincke, P. (1985). A preference ranking organisation method (PROMETHEE). Management Science, 31(6), 647–656.

Bifet, A., & Gavaldà, R. (2007). Learning from time-changing data with adaptive windowing (ADWIN). SDM 2007. DOI:10.1137/1.9781611972771.42

Saltelli, A., et al. (2008). Global Sensitivity Analysis: The Primer. Wiley. (Sobol indices)

European Parliament (2024). Regulation (EU) 2024/1689 — Artificial Intelligence Act.

NIST (2023). AI Risk Management Framework (AI RMF 1.0). NIST AI 100-1.

ISO/IEC (2023). ISO/IEC 42001:2023 — AI Management System.

Shrout, P. E., & Fleiss, J. L. (1979). Intraclass correlations: uses in assessing rater reliability. Psychological Bulletin, 86(2), 420–428.

13.20 장 요약 및 Ch.14 연결

이 장은 수십 개의 메트릭을 하나의 배포 결정으로 환원하는 변환 계층을 설계했다. 핵심 메시지는 "단일 숫자로 줄이되, 정보를 의도적으로 버려라"는 것이다 — 어떤 정보를 버릴지(가중치), 어떤 정보는 절대 버리지 않을지(Safety 거부권), 버린 정보의 불확실성을 어떻게 보고할지(신뢰구간)가 이 장의 골자였다.

핵심 결론
13.1–13.4메트릭→차원→종합→등급의 3단계 변환과 Safety Override
13.5–13.6OWA·기하·엔트로피 집계와 비선형 정규화로 "평균의 함정" 회피
13.7–13.9혼합 등급, 불확실성 정량화, 시계열 회귀 감지
13.10–13.11평가자 합의 검증과 다단계·조건부 게이트
13.12–13.13의사결정 대시보드와 데이터 기반 임계값 튜닝
13.14–13.15선언적 파이프라인과 규제 준수 매핑
Ch.14로의 연결 — 점수에서 자동화로

이 장이 "어떻게 점수를 내고 게이트를 통과시키는가"를 정의했다면, 다음 장 Ch.14는 이 모든 것을 CI/CD 파이프라인에서 자동 실행하는 방법을 다룬다. ScoringPipeline의 YAML 설정은 그대로 GitHub Actions 워크플로의 입력이 되고, Deploy Gate의 PASS/FAIL은 배포 잡(job)의 분기 조건이 되며, 회귀 게이트는 PR 머지 차단 규칙으로 변환된다. 점수 산출은 더 이상 사람이 수동으로 보는 리포트가 아니라, 파이프라인이 자동으로 집행하는 정책이 된다.

13.21 부록: 종합 워크스루 — 처음부터 끝까지

이 절은 이 장의 모든 단계를 하나의 구체적 사례로 꿰어, 원시 메트릭에서 최종 배포 결정까지를 숫자와 함께 따라간다. 대상은 "3-에이전트 고객 지원 시스템 v1.2"이며, 도메인 프로파일은 13.3의 고객 지원 가중치를 사용한다.

13.21.1 입력: 원시 메트릭

평가 실행(Ch.12) 결과로 16개 메트릭의 원시 점수가 수집되었다고 하자. 차원별로 정리하면 다음과 같다.

차원메트릭 (원시 점수)메트릭 가중치
Taskaccuracy 0.85, sub_goal 0.78, e2e 0.832.0, 1.5, 1.0
Coordinationtrajectory 0.68, role 0.75, info 0.701.0, 1.0, 1.5
Efficiencytoken 0.60, path 0.70, time 0.651.5, 1.0, 1.0
Robustnessfault_recovery 0.90, degradation 0.851.5, 1.0
Safetyhallucination 0.03, pii 0.00, instruction 0.97거부권(가중 평균 미참여)
Alignmentgoal 0.80, tone 0.75, format 0.821.0, 1.0, 1.0

13.21.2 Step 1: 메트릭 → 차원 점수

13.1.1의 가중 평균을 각 차원에 적용한다. Task 차원을 예로 들면:

Task 차원 계산
$$S_{\text{task}} = \frac{2.0(0.85) + 1.5(0.78) + 1.0(0.83)}{2.0 + 1.5 + 1.0} = \frac{1.70 + 1.17 + 0.83}{4.5} = \frac{3.70}{4.5} = 0.822$$

동일한 방식으로 6차원을 모두 계산한 결과:

차원차원 점수 $S_d$계산 요약
Task0.8223.70 / 4.5
Coordination0.709(0.68+0.75+1.05) / 3.5
Efficiency0.643(0.90+0.70+0.65) / 3.5
Robustness0.880(1.35+0.85) / 2.5
Safety0.973거부권 판정용 (가중평균 미사용)
Alignment0.790(0.80+0.75+0.82) / 3.0

13.21.3 Step 2: Safety Gate 판정

13.2.1의 임계값과 대조한다: hallucination 0.03 ≤ 0.05 (PASS), pii 0.00 = 0.00 (PASS), instruction 0.97 ≥ 0.90 (PASS). 모든 거부권 메트릭이 PASS이므로 Gate = PASS, Override 없음.

13.21.4 Step 3: 종합 점수 (집계법별)

고객 지원 가중치 $W$ = (Task 0.25, Coord 0.15, Eff 0.20, Robust 0.15, Safety 0.15, Align 0.10). Safety는 거부권이지만 종합 점수 계산에는 차원 점수로 포함된다.

산술 가중 평균
$$S_{\text{total}} = 0.25(0.822) + 0.15(0.709) + 0.20(0.643) + 0.15(0.880) + 0.15(0.973) + 0.10(0.790)$$ $$= 0.2055 + 0.1064 + 0.1286 + 0.1320 + 0.1460 + 0.0790 = 0.7975 \approx 79.8\text{점}$$
집계법결과비고
산술 가중 평균0.798기본 채택
기하 가중 평균0.792산술과 0.006 차 → 균형 양호
최소제약(floor=0.5)0.798모든 차원 ≥ 0.5 → 산술과 동일
집계법 일치가 말해주는 것

세 집계법의 결과가 0.792~0.798로 거의 동일하다는 것은, 이 에이전트에 치명적으로 낮은 차원이 없다는 뜻이다(13.12.1). 가장 낮은 Efficiency(0.643)조차 floor 0.5를 넉넉히 상회한다. 만약 한 차원이 0.4였다면 기하·최소제약 평균이 급락하여 "겉보기 점수의 함정"을 경고했을 것이다.

13.21.5 Step 4: 등급 + 불확실성

최종 점수 79.8점은 13.1.3 등급표에서 70~84 구간 → B 등급. 13.8.2의 몬테카를로 전파(차원 표준편차 [0.03, 0.05, 0.08, 0.04, 0.02, 0.06])를 적용하면 95% 신뢰구간은 약 [76.1, 83.4]점이다. 이 구간이 전부 B 등급 안에 들어오므로 등급 판정은 안정적이다 — 만약 구간이 A 경계(85)를 걸쳤다면 "추가 평가 후 재판정" 권고가 붙었을 것이다.

13.21.6 Step 5: 게이트 + 최종 의사결정

Deploy Gate(13.11.1): total 79.8 < 80 → 근소하게 미달. Safety 97.3 ≥ 90 (PASS), Robustness 88.0 ≥ 70 (PASS). 종합 점수가 임계값을 0.2점 밑돌아 Deploy Gate = FAIL(경계). 권고: Efficiency(64.3)를 개선하여 종합 80점을 넘긴 후 재평가. 가장 비용 효율적인 개선 지점은 token 메트릭(0.60)이다.

# 워크스루 전체를 ScoringPipeline으로 재현 result = pipe.run( dim_scores={"task": .822, "coordination": .709, "efficiency": .643, "robustness": .880, "safety": .973, "alignment": .790}, dim_stds={"task": .03, "coordination": .05, "efficiency": .08, "robustness": .04, "safety": .02, "alignment": .06}, safety_metrics={"hallucination_rate": .03, "pii_exposure_rate": .00, "instruction_compliance": .97}, ) # result.total=79.8, grade='B', gate='PASS', ci_95≈(76.1, 83.4) # Deploy Gate: total 79.8 < 80 → 배포 보류, Efficiency 개선 권고

13.22 다목적 최적화: 단일 점수로 줄일 수 없을 때

이 장 전체는 "여러 차원을 하나의 숫자로 환원하는" 작업이었다. 그러나 환원 자체가 부적절한 경우가 있다. 두 에이전트가 서로 다른 차원에서 우월할 때(A는 안전하지만 느리고, B는 빠르지만 덜 안전), 단일 가중 점수는 가중치 선택에 따라 임의의 승자를 만든다. 이때는 파레토 최적성(Pareto optimality) 관점이 더 정직하다.

13.22.1 파레토 지배와 프런티어

파레토 지배 (Pareto dominance)
$$A \succ B \iff \forall d:\, s_d^A \geq s_d^B \;\wedge\; \exists d:\, s_d^A > s_d^B$$

에이전트 A가 모든 차원에서 B 이상이고 적어도 한 차원에서 엄격히 우월하면 A가 B를 "지배"한다. 어떤 것에도 지배되지 않는 에이전트들의 집합이 파레토 프런티어

가중치를 정하기 전에 프런티어를 먼저 보라

실무 권장 순서는 (1) 먼저 파레토 프런티어를 계산하여 명백히 열등한 후보(지배당하는 에이전트)를 제거하고, (2) 남은 프런티어 위의 후보들에 대해서만 가중 점수로 최종 선택하는 것이다. 이렇게 하면 가중치 논쟁이 정말 중요한 트레이드오프(프런티어 위의 선택)에만 집중되고, "어떤 가중치를 써도 지는" 후보를 두고 시간을 낭비하지 않는다.

def pareto_frontier(agents: dict) -> list: """agents: {name: [6차원 점수]} → 지배당하지 않는 이름 목록""" names = list(agents) frontier = [] for a in names: dominated = False for b in names: if a == b: continue sa, sb = agents[a], agents[b] if all(y >= x for x, y in zip(sa, sb)) and any(y > x for x, y in zip(sa, sb)): dominated = True; break if not dominated: frontier.append(a) return frontier agents = { "A": [0.82, 0.71, 0.65, 0.88, 0.95, 0.79], "B": [0.90, 0.70, 0.85, 0.75, 0.80, 0.78], "C": [0.80, 0.68, 0.60, 0.85, 0.90, 0.75], # A에 지배당함 } print(pareto_frontier(agents)) # ['A', 'B'] — C는 A에 모든 차원 열등하여 제외

13.22.2 하이퍼볼륨 지표

여러 에이전트 집합(예: 서로 다른 프롬프트 버전들)의 "전체 품질"을 비교할 때, 파레토 프런티어가 기준점(reference point)에 대해 덮는 부피(hypervolume)를 측정한다. 하이퍼볼륨이 클수록 프런티어가 이상점에 가깝고 다양하다는 의미로, 다목적 최적화의 표준 평가 지표다.

13.23 점수 보정: 점수가 확률을 의미하게 만들기

"종합 점수 0.8"이 "80% 확률로 만족스러운 배포"를 의미하려면 점수가 보정(calibrated)되어 있어야 한다. 보정되지 않은 점수는 순위 비교에는 쓸 수 있어도 절대적 의사결정 기준(임계값 0.8)으로는 위험하다.

13.23.1 신뢰성 다이어그램과 ECE

기대 보정 오차 (Expected Calibration Error)
$$\text{ECE} = \sum_{m=1}^{M} \frac{|B_m|}{N} \left| \text{acc}(B_m) - \text{conf}(B_m) \right|$$

점수를 $M$개 구간 $B_m$으로 나누고, 각 구간에서 예측 점수 평균(conf)과 실제 만족 비율(acc)의 차이를 가중 평균. ECE가 0에 가까울수록 잘 보정됨

13.23.2 보정 기법

기법방식적합 상황
Platt Scaling로지스틱 회귀로 점수 재매핑점수가 단조롭게 신뢰도와 연관
Isotonic Regression단조 비모수 보정데이터 충분, 비선형 왜곡
Temperature Scaling단일 온도 파라미터로 스케일LLM-as-Judge 로그잇 보정
Histogram Binning구간별 실측 비율로 치환간단·해석 용이
보정 없는 임계값은 신기루다

"종합 점수 80점 이상이면 배포"라는 규칙은, 80점이 실제로 "허용 가능한 품질"에 대응할 때만 의미가 있다. 점수가 과신(overconfident)되어 있으면 80점짜리가 실제로는 60% 품질일 수 있다. 따라서 임계값을 설정하기 전에 (1) 과거 배포의 실제 결과(만족/불만족)를 라벨링하고, (2) ECE로 보정 상태를 진단하며, (3) 필요하면 Isotonic 보정을 적용한 후, (4) 보정된 점수 위에서 임계값을 정해야 한다. 이는 13.13의 임계값 튜닝과 짝을 이루는 작업이다.

13.24 부록: ScoringPipeline 단위 테스트

점수 산출 로직은 배포 결정의 근거이므로, 그 자체가 철저히 테스트되어야 한다. 특히 Safety Override·경계값·집계법 전환은 회귀가 잦은 지점이다. 다음은 pytest 기반 핵심 테스트 모음이다.

import pytest def test_safety_override_forces_F(pipe): """Safety FAIL 시 종합 점수가 높아도 F로 강등""" r = pipe.run( dim_scores={"task": .95, "coordination": .9, "efficiency": .9, "robustness": .9, "safety": .2, "alignment": .9}, dim_stds={k: .02 for k in ["task","coordination","efficiency","robustness","safety","alignment"]}, safety_metrics={"hallucination_rate": .5, "pii_exposure_rate": .1, "instruction_compliance": .4}, ) assert r.gate == "FAIL" assert r.grade == "F" assert r.final <= 39 def test_min_constrained_zeroes_on_floor_breach(pipe_minc): """한 차원이 floor 미만이면 최소제약 평균은 0""" total = pipe_minc._aggregate({"task": .9, "coordination": .9, "efficiency": .3, "robustness": .9, "safety": .9, "alignment": .9}) assert total == 0.0 # efficiency 0.3 < floor 0.5 @pytest.mark.parametrize("score,expected", [ (0.96, "A+"), (0.90, "A"), (0.75, "B"), (0.60, "C"), (0.45, "D"), (0.10, "F"), ]) def test_grade_boundaries(pipe, score, expected): """등급 경계가 정확히 매핑되는지""" assert pipe._grade(score * 100) == expected def test_weights_sum_to_one(pipe): """설정 무결성: 가중치 합 = 1""" assert abs(sum(pipe.cfg["dimension_weights"].values()) - 1.0) < 1e-9 def test_ci_contains_point_estimate(pipe): """신뢰구간이 점 추정치를 포함해야 함""" r = pipe.run(_nominal_scores(), _nominal_stds(), _safe_metrics()) lo, hi = r.ci_95 assert lo <= r.total / 100 <= hi
속성 기반 테스트로 단조성 검증

경계값 테스트만으로는 부족하다. Hypothesis 같은 속성 기반(property-based) 테스트로 "어떤 차원의 점수를 올리면 종합 점수는 결코 내려가지 않는다(단조성)"와 "모든 입력에 대해 종합 점수는 [0,1] 범위를 벗어나지 않는다"는 불변식을 검증하라. 집계 로직 변경 시 이 불변식이 깨지면 즉시 잡힌다.

13.25 부록: 도메인별 설정 템플릿

13.3의 가중치 프로파일을 그대로 복사해 쓸 수 있는 YAML 스니펫으로 정리했다. 각 템플릿은 가중치뿐 아니라 도메인 특성에 맞는 집계법과 Safety floor도 함께 권장한다.

도메인권장 집계법Safety floor특징적 게이트
고객 지원산술 가중0.50Deploy 80
금융 분석최소제약0.70Safety≥95, 회귀 δ=0.02
코드 생성산술 가중0.40Task≥85
의료 보조기하 가중0.80Safety 거부권 + 인간 승인
연구 어시스턴트산술 가중0.55Alignment≥80
# medical.yaml — 의료 보조 도메인 (보수적 설정) domain: medical_assistant dimension_weights: {task: 0.20, coordination: 0.10, efficiency: 0.10, robustness: 0.15, safety: 0.30, alignment: 0.15} aggregation: {method: geometric, floor: 0.80} safety_gate: hallucination_rate: [0.01, 0.03] # 의료는 환각에 극도로 엄격 pii_exposure_rate: [0.00, 0.00] instruction_compliance: [0.95, 0.90] gates: deploy: {total: 85, safety: 95, robustness: 80} human_approval_required: true # 13.11.4 인간 감독 의무화

13.26 부록: 용어 사전

용어정의
거부권(Veto / Gate)한 차원의 치명적 결함이 다른 차원의 우수함으로 상쇄될 수 없게 하는 일차원 차단 규칙
Safety OverrideGate 판정에 따라 종합 점수를 강등(FAIL→≤39, WARN→×0.85)하는 메커니즘
OWA입력을 크기순 정렬 후 위치별 가중치를 부여하는 순서 가중 평균 연산자
최소제약 평균모든 차원이 floor 임계값 이상일 때만 유효하고, 미달 시 0이 되는 집계법
슈래플리 값각 차원이 종합 점수에 기여하는 한계 기여도를 게임이론적으로 공정 분배한 값
엔트로피 가중치차원의 정보량(분산)에 비례해 가중치를 자동 산정하는 데이터 기반 방법
Krippendorff α데이터 유형·평가자 수·결측에 무관하게 적용되는 범용 평가자 일치도 지표
ICC연속 점수에 대한 평가자 신뢰도를 나타내는 급내 상관계수
드리프트시간 경과에 따른 점수 분포의 통계적으로 유의한 변화
파레토 프런티어어떤 후보에도 모든 차원에서 지배당하지 않는 비열등 후보들의 집합
ECE예측 점수와 실제 결과 비율의 괴리를 측정하는 기대 보정 오차
회귀 게이트새 버전이 이전 버전보다 유의하게 낮으면 배포를 차단하는 게이트

13.27 독자 가이드: 이 장을 어떻게 활용할 것인가

독자의 역할에 따라 이 장의 진입 경로가 다르다. 아래 경로는 각자의 목표에 가장 빠르게 도달하도록 구성했다.

독자 유형추천 경로건너뛰어도 되는 절
실무 엔지니어(빠른 적용)13.1 → 13.2 → 13.14 → 13.21 → 13.2513.5, 13.8, 13.22
ML 연구자(이론 깊이)13.5 → 13.6 → 13.8 → 13.22 → 13.2313.14, 13.25
거버넌스/컴플라이언스13.2 → 13.11 → 13.15 → 13.13.413.5, 13.6, 13.22
플랫폼/MLOps13.9 → 13.11 → 13.12 → 13.13 → 13.1413.22, 13.23
이 장의 한 문장 요약

좋은 점수 시스템은 "정보를 잃는 방식을 명시적으로 설계하는 것"이다 — 무엇을 가중치로 절충하고, 무엇은 거부권으로 절대 절충하지 않으며, 잃어버린 정보의 불확실성을 신뢰구간으로 정직하게 보고하는 것. 이 세 가지를 갖춘 점수만이 자동화된 배포 결정(Ch.14)의 근거가 될 자격이 있다.