공정성을 측정하는 것은 AI 시스템의 편향을 탐지하고 완화하는 데 필수적입니다. 이 장에서는 공정성 지표의 수학적 기초, 적용 방법, 한계, 그리고 실제 구현에 대해 탐구합니다.
편향을 해결하기 전에 먼저 측정할 수 있어야 합니다. 공정성 지표는 AI 시스템이 서로 다른 그룹을 공평하게 대우하는지 평가하는 정량적 방법을 제공합니다. 그러나 공정성은 단일적이고 획일적인 개념이 아닙니다. 무엇이 공정한 대우를 구성하는지에 대한 여러 가지, 때로는 경쟁적인 정의가 존재합니다.
연구에 따르면 사소한 경우를 제외하고는 모든 공정성 기준을 동시에 만족시키는 것이 수학적으로 불가능합니다. 이는 실무자들이 특정 맥락과 사용 사례에 가장 적합한 공정성 정의에 대해 정보에 입각한 선택을 해야 함을 의미합니다.
그룹 공정성 지표는 서로 다른 인구통계학적 그룹 간의 결과를 비교합니다. 이러한 지표는 일반적으로 보호 속성으로 정의된 그룹 간의 통계적 균등성 또는 동등한 대우를 보장하는 데 초점을 맞춥니다.
인구통계학적 균등성은 모든 그룹에서 긍정적 예측의 비율이 동일할 것을 요구합니다. 즉, 의사결정이 보호 속성과 독립적이어야 합니다.
여기서 Ŷ는 예측이고 A는 보호 속성(예: 인종, 성별)입니다.
# 인구통계학적 균등성 구현
import numpy as np
def demographic_parity(y_pred, protected_attribute):
"""
인구통계학적 균등성 지표 계산
최소값 대 최대값의 긍정 비율 비율 반환
"""
groups = np.unique(protected_attribute)
positive_rates = {}
for group in groups:
mask = protected_attribute == group
positive_rate = np.mean(y_pred[mask])
positive_rates[group] = positive_rate
print(f"그룹 {group}: {positive_rate:.3f}")
rates = list(positive_rates.values())
parity_ratio = min(rates) / max(rates) if max(rates) > 0 else 1.0
print(f"\n인구통계학적 균등성 비율: {parity_ratio:.3f}")
print(f"상태: {'통과' if parity_ratio >= 0.8 else '실패'}")
return parity_ratio, positive_rates
# 사용 예
y_pred = np.array([1, 1, 0, 1, 0, 1, 0, 0, 1, 1])
protected = np.array(['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'])
ratio, rates = demographic_parity(y_pred, protected)
장점:
한계:
동등한 확률은 그룹 간에 진양성률(TPR)과 위양성률(FPR)이 모두 동일할 것을 요구합니다. 이는 모델의 오류가 동등하게 분산되도록 보장합니다.
# 동등한 확률 구현
def equalized_odds(y_true, y_pred, protected_attribute):
"""
각 그룹의 진양성률과 위양성률 계산하여
동등한 확률 평가
"""
groups = np.unique(protected_attribute)
results = {}
for group in groups:
mask = protected_attribute == group
# 진양성률
tp = np.sum((y_true[mask] == 1) & (y_pred[mask] == 1))
fn = np.sum((y_true[mask] == 1) & (y_pred[mask] == 0))
tpr = tp / (tp + fn) if (tp + fn) > 0 else 0
# 위양성률
fp = np.sum((y_true[mask] == 0) & (y_pred[mask] == 1))
tn = np.sum((y_true[mask] == 0) & (y_pred[mask] == 0))
fpr = fp / (fp + tn) if (fp + tn) > 0 else 0
results[group] = {'tpr': tpr, 'fpr': fpr}
print(f"그룹 {group}:")
print(f" TPR: {tpr:.3f}, FPR: {fpr:.3f}")
# 불일치 확인
tprs = [r['tpr'] for r in results.values()]
fprs = [r['fpr'] for r in results.values()]
tpr_disparity = max(tprs) - min(tprs)
fpr_disparity = max(fprs) - min(fprs)
print(f"\nTPR 불일치: {tpr_disparity:.3f}")
print(f"FPR 불일치: {fpr_disparity:.3f}")
print(f"상태: {'통과' if (tpr_disparity < 0.1 and fpr_disparity < 0.1) else '실패'}")
return results
# 사용 예
y_true = np.array([1, 1, 0, 1, 0, 0, 1, 0, 1, 0])
y_pred = np.array([1, 1, 0, 1, 0, 1, 1, 0, 0, 0])
protected = np.array(['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'])
results = equalized_odds(y_true, y_pred, protected)
장점:
한계:
동등한 기회는 동등한 확률의 완화된 버전으로, 그룹 간에 진양성률만 동일할 것을 요구합니다. 이는 위음성이 위양성보다 더 해로운 경우에 특히 관련이 있습니다.
# 동등한 기회 구현
def equal_opportunity(y_true, y_pred, protected_attribute):
"""
각 그룹의 진양성률 계산
"""
groups = np.unique(protected_attribute)
tprs = {}
for group in groups:
mask = protected_attribute == group
positives = y_true[mask] == 1
if positives.sum() == 0:
tprs[group] = None
print(f"그룹 {group}: 양성 샘플 없음")
continue
tp = np.sum((y_true[mask] == 1) & (y_pred[mask] == 1))
tpr = tp / positives.sum()
tprs[group] = tpr
print(f"그룹 {group} TPR: {tpr:.3f}")
# 불일치 계산
valid_tprs = [t for t in tprs.values() if t is not None]
if len(valid_tprs) > 1:
disparity = max(valid_tprs) - min(valid_tprs)
print(f"\nTPR 불일치: {disparity:.3f}")
print(f"상태: {'통과' if disparity < 0.1 else '실패'}")
return tprs
예측적 균등성은 그룹 간에 정밀도(양성 예측 값)가 동일할 것을 요구합니다. 이는 그룹 구성원 자격에 관계없이 긍정적 예측이 동일한 의미를 갖도록 보장합니다.
# 예측적 균등성 구현
def predictive_parity(y_true, y_pred, protected_attribute):
"""
각 그룹의 정밀도(PPV) 계산
"""
groups = np.unique(protected_attribute)
precisions = {}
for group in groups:
mask = protected_attribute == group
predicted_positive = y_pred[mask] == 1
if predicted_positive.sum() == 0:
precisions[group] = None
continue
tp = np.sum((y_true[mask] == 1) & (y_pred[mask] == 1))
precision = tp / predicted_positive.sum()
precisions[group] = precision
print(f"그룹 {group} 정밀도: {precision:.3f}")
# 불일치 확인
valid_prec = [p for p in precisions.values() if p is not None]
if len(valid_prec) > 1:
disparity = max(valid_prec) - min(valid_prec)
print(f"\n정밀도 불일치: {disparity:.3f}")
print(f"상태: {'통과' if disparity < 0.1 else '실패'}")
return precisions
개인 공정성은 그룹 공정성과 다른 접근 방식을 취합니다. 그룹을 비교하는 대신, 유사한 개인이 유사한 예측을 받아야 한다고 요구합니다.
개인 공정성의 원칙은 "유사한 개인은 유사하게 대우받아야 한다"고 명시합니다. 과제는 편향을 인코딩하지 않으면서 의미 있는 방식으로 "유사성"을 정의하는 것입니다.
# 예제: 개인 공정성 확인
from sklearn.metrics.pairwise import cosine_similarity
def check_individual_fairness(X, y_pred, threshold=0.9):
"""
유사한 개인이 유사한 예측을 받는지 확인
"""
# 쌍별 유사도 계산
similarities = cosine_similarity(X)
fairness_violations = 0
total_pairs = 0
for i in range(len(X)):
for j in range(i + 1, len(X)):
if similarities[i, j] > threshold:
total_pairs += 1
if y_pred[i] != y_pred[j]:
fairness_violations += 1
violation_rate = fairness_violations / total_pairs if total_pairs > 0 else 0
print(f"유사한 쌍: {total_pairs}")
print(f"공정성 위반: {fairness_violations}")
print(f"위반율: {violation_rate:.3f}")
return violation_rate
불균형 영향(부정적 영향이라고도 함)은 차별을 평가하는 데 사용되는 법적 개념입니다. "80% 규칙" 또는 "5분의 4 규칙"이 일반적으로 임계값으로 사용됩니다.
0.8(80%) 미만의 비율은 종종 불균형 영향의 증거로 간주됩니다.
# 불균형 영향 계산
def disparate_impact(y_pred, protected_attribute, reference_group):
"""
불균형 영향 비율 계산
참조 그룹은 일반적으로 다수 또는 유리한 그룹
"""
groups = np.unique(protected_attribute)
selection_rates = {}
for group in groups:
mask = protected_attribute == group
selection_rate = np.mean(y_pred[mask])
selection_rates[group] = selection_rate
reference_rate = selection_rates[reference_group]
print("선택률:")
for group, rate in selection_rates.items():
di_ratio = rate / reference_rate if reference_rate > 0 else 0
status = "통과" if di_ratio >= 0.8 else "실패"
print(f" {group}: {rate:.3f} (DI 비율: {di_ratio:.3f}) [{status}]")
return selection_rates
# 예제
y_pred = np.array([1, 1, 1, 1, 0, 1, 0, 0, 1, 0])
protected = np.array(['다수', '다수', '다수', '다수', '다수',
'보호', '보호', '보호', '보호', '보호'])
rates = disparate_impact(y_pred, protected, '다수')
하드 분류가 아닌 확률 점수를 출력하는 모델의 경우, 보정은 중요한 공정성 고려 사항입니다.
모델이 예측이 실제 확률을 반영하는 경우 보정됩니다. 그룹 보정은 이것이 각 인구통계학적 그룹에 대해 별도로 유지되어야 한다고 요구합니다.
# 그룹별 보정 확인
def calibration_by_group(y_true, y_score, protected_attribute, n_bins=10):
"""
각 그룹에 대해 별도로 보정 평가
"""
from sklearn.calibration import calibration_curve
groups = np.unique(protected_attribute)
for group in groups:
mask = protected_attribute == group
prob_true, prob_pred = calibration_curve(
y_true[mask],
y_score[mask],
n_bins=n_bins,
strategy='uniform'
)
print(f"\n그룹 {group} 보정:")
for i, (true_p, pred_p) in enumerate(zip(prob_true, prob_pred)):
print(f" 빈 {i+1}: 예측={pred_p:.3f}, 실제={true_p:.3f}")
return True
적절한 공정성 지표 선택은 응용 분야, 법적 요구 사항, 윤리적 고려 사항에 따라 달라집니다:
| 응용 분야 | 권장 지표 | 근거 |
|---|---|---|
| 형사 사법 | 동등한 확률, 동등한 기회 | 위양성과 위음성 모두 심각한 결과를 초래함 |
| 대출/신용 | 불균형 영향, 인구통계학적 균등성 | 법적 준수(ECOA), 동등한 접근 요구 사항 |
| 채용 | 불균형 영향, 동등한 기회 | 법적 준수(EEOC), 자격을 갖춘 후보자에 초점 |
| 의료 | 동등한 기회, 보정 | 놓친 진단 최소화, 신뢰할 수 있는 위험 점수 |
| 콘텐츠 추천 | 인구통계학적 균등성, 개인 공정성 | 동등한 노출, 개인화된 대우 |
실제로는 전체 그림을 얻기 위해 여러 공정성 지표를 평가해야 합니다:
# 포괄적인 공정성 보고서
def fairness_report(y_true, y_pred, protected_attribute):
"""
포괄적인 공정성 평가 생성
"""
print("=" * 60)
print("공정성 평가 보고서")
print("=" * 60)
# 1. 인구통계학적 균등성
print("\n1. 인구통계학적 균등성")
print("-" * 60)
dp_ratio, _ = demographic_parity(y_pred, protected_attribute)
# 2. 동등한 확률
print("\n2. 동등한 확률")
print("-" * 60)
eo_results = equalized_odds(y_true, y_pred, protected_attribute)
# 3. 동등한 기회
print("\n3. 동등한 기회")
print("-" * 60)
eop_results = equal_opportunity(y_true, y_pred, protected_attribute)
# 4. 예측적 균등성
print("\n4. 예측적 균등성")
print("-" * 60)
pp_results = predictive_parity(y_true, y_pred, protected_attribute)
# 요약
print("\n" + "=" * 60)
print("요약")
print("=" * 60)
print("위의 모든 지표를 검토하여 정보에 입각한 공정성 평가를 수행하십시오.")
print("다른 지표가 충돌할 수 있음을 기억하십시오. 특정 사용 사례와")
print("이해관계자 가치에 따라 선택하십시오.")
print("\n弘益人間 - AI가 모든 인류에게 공평하게 이익이 되도록 보장하십시오")
print("=" * 60)
return {
'demographic_parity': dp_ratio,
'equalized_odds': eo_results,
'equal_opportunity': eop_results,
'predictive_parity': pp_results
}