머신러닝 편향 감지와 XAI 운영 설계

Fairlearn으로 편향을 진단·완화하고 SHAP와 LIME으로 예측 근거를 설명하는 머신러닝 거버넌스 운영 방법

2026-08-14 · 최초 발행 2024-04-29

편향과 설명 책임은 모델 배포 이후에도 남는다

머신러닝 윤리는 모델이 사용자와 사회에 미치는 영향을 통제하는 개발·운영 거버넌스 체계다. 공정성(Fairness), 투명성(Transparency), 책임성(Accountability), 안전성(Safety)을 보장하고, 데이터·모델·운영 단계에 걸친 위험을 미리 식별하고 완화하는 데 초점을 둔다.

편향은 데이터의 대표성 불균형, 라벨 편향, 피처 설계 편향 등에서 발생할 수 있다. 이를 판단할 때는 인구 통계학적 균형(Demographic Parity), 기회 균등(Equal Opportunity), 균등화된 기회(Equalized Odds) 같은 공정성 지표를 사용한다.

설명 가능성(XAI)은 모델 예측의 근거를 사람이 이해할 수 있는 형태로 제공하는 기법의 묶음이다. 설명 범위는 전역(Global)과 국소(Local)로, 구현 방식은 사후(Post-hoc) 설명과 내재적(Intrinsic) 설명으로 나뉜다.

Fairlearn은 공정성 지표 계산과 편향을 줄이는 알고리즘을 제공한다. SHAP은 셰이플리 값 기반으로 특성 기여도를 추정하며 전역·국소 설명을 모두 지원한다. LIME은 국소 선형 근사로 개별 예측을 설명하는 모델 불가지론적 접근이다. EU AI Act, GDPR 등 규제와의 연계 필요성도 함께 검토해야 하며, 최신 정보 확인이 필요하다.

진단부터 감사까지 이어지는 운영 흐름

민감 특성(Sensitive attribute) 단위로 슬라이싱(Slicing)하면 그룹별 예측률, 재현율, 정밀도의 차이를 확인할 수 있다. MetricFrame으로 여러 지표를 집계하고, 경향성(Trend)을 계속 관찰하면 운영 중 모델 표류(Model drift)도 감지할 수 있다.

편향 완화는 적용 위치에 따라 나뉜다. 데이터 단계에서는 재가중치(Reweighing)와 샘플 균형화로 편향을 낮춘다. 학습 단계에서는 Exponentiated Gradient와 GridSearch로 제약(Constraints)을 만족하는 모델을 찾는다. 배포 직전에 손댈 수 있는 후처리 방식으로는 ThresholdOptimizer를 사용해 그룹별 임계치를 조정할 수 있다.

설명 기법도 운영 조건에 맞춰 선택해야 한다. SHAP은 일관성과 가법성을 보장하고 전역·국소·상호작용 설명을 제공하지만, 대규모 데이터에서는 샘플링이나 근사가 필요하다. LIME은 빠른 국소 설명과 모델 불가지론적 사용이 장점이지만, 반복 실행 시 변동성을 관리해야 한다. 시드 고정과 K-LIME이 그 방법으로 제시된다.

설명 결과에는 민감 정보가 드러날 수 있다. 특성 마스킹과 k-익명성을 적용하고, 데이터 라인리지, 실험 추적(MLflow), 모델 카드(Model Card), 데이터 시트(Data Sheet)를 아티팩트로 관리한다. 승인 단계에서는 편향 지표 임계치, 설명 커버리지, AUC·정확도 등의 품질 기준을 함께 검증하며, 입력·결정·설명 로그를 남겨 감사 추적성과 재현 가능성을 확보한다.

판단이 사람에게 영향을 주는 영역

신용평가에서는 데이터 수집 후 Fairlearn으로 편향을 확인하고, 후처리 임계치 조정으로 완화한 뒤 SHAP 또는 LIME으로 설명을 제공하고 모니터링한다. 성별·연령 그룹의 승인율 격차와 TPR gap을 관리하며, 불이익 고객에게는 개별 사유를 고지한다.

채용 추천과 스크리닝에서는 특성 선정 가이드라인을 먼저 만들고 민감 특성의 프록시를 탐지한다. 이후 공정성 제약 아래에서 모델을 탐색하고 설명 기록을 남긴다. 학력·지역 프록시를 제거하고 설명 로그를 보존하는 방식은 차별 소송 리스크 완화와 연결된다.

의료 트리아지에서는 임상 동의와 윤리 검토 후 인종·연령에 따른 편향을 진단한다. 보수적 임계치를 설정하고 설명을 근거로 이의제기를 처리한다. 과소진단 위험을 줄이고, 설명을 임상의 보조 의사결정에 활용하는 흐름이다.

승인율 격차(Δselection rate)는 12%→3% 수준으로 줄이고, TPR gap은 0.10→0.02를 달성하는 사례가 가능하다(데이터 의존). 이때 AUC 감소는 0.3~2.0pp 내에서 관리하며 비용-효익 균형을 유지한다. 이러한 체계는 규제 대응력과 이해관계자 신뢰를 높이고, 운영 투명성·감사 가능성 및 내부 통제 성숙도를 강화한다.

배포 승인과 모니터링을 연결하는 구조

공정성 기준 충족미달임계 초과입력: 데이터·민감 특성 식별편향 감지: MetricFrame,그룹별 지표학습/배포 승인완화 전략 선택:전처리/학습/후처리재학습/임계치 조정설명 생성: SHAP/LIME감사 로그: 예측·설명·지표 저장모니터링: 드리프트/경보

Fairlearn, SHAP, LIME의 운용 차이

항목 Fairlearn SHAP LIME
성능(시간/자원) 지표 계산 가벼움, 완화 탐색은 중간~높음 트리/선형은 중간, 커널/대규모 시 높음 빠름, 지역 샘플 수에 비례
확장성 배치/파이프라인 연계 용이 샘플링·근사로 확장, 분산 필요 시 추가 작업 병렬화 쉬움, 대량 일괄 설명 적합
일관성 지표 정의 명확, 재현성 높음 이론적 일관성 보장(가법성) 시드·초모수에 민감, 변동성 존재
안정성 제약 충족 보장형 탐색 제공 수치 안정성 양호, 누락 특성에 민감 로컬 근사 품질에 의존, 잡음 영향
운영 편의 임계치 기반 정책화 용이 전역/국소 리포트 자동화 용이 개별 이의제기·사례 대응에 유리

Fairlearn으로 편향을 계산하고 완화하기

사전 조건은 Python 3.9+와 scikit-learn>=1.2, fairlearn>=0.8, shap>=0.45, lime>=0.2 패키지다.

pip install scikit-learn fairlearn shap lime

아래 예시는 데이터와 공정성 지표를 계산하고 Fairlearn으로 완화를 적용한다.

# 환경: Python 3.9+, scikit-learn 1.2+, fairlearn 0.8+
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
import numpy as np

from fairlearn.metrics import MetricFrame, selection_rate, true_positive_rate
from fairlearn.metrics import demographic_parity_difference, equalized_odds_difference
from fairlearn.reductions import ExponentiatedGradient, DemographicParity

RANDOM_STATE = 42
X, y = make_classification(
    n_samples=8000, n_features=12, n_informative=6, n_redundant=2,
    class_sep=1.0, weights=[0.55, 0.45], random_state=RANDOM_STATE
)

# 민감 특성 생성(예시): 첫 번째 특성 기준으로 그룹화하고, 한 그룹에 불리한 편향 주입
sensitive = (X[:, 0] > np.median(X[:, 0])).astype(int)  # 0/1 그룹
# 편향 주입: 그룹1에서 소수 클래스의 레이블 일부를 0으로 전환
mask = (sensitive == 1) & (y == 1)
flip_idx = np.random.RandomState(RANDOM_STATE).choice(np.where(mask)[0], size=int(mask.sum()*0.15), replace=False)
y_biased = y.copy()
y_biased[flip_idx] = 0

X_train, X_test, y_train, y_test, s_train, s_test = train_test_split(
    X, y_biased, sensitive, test_size=0.3, stratify=y_biased, random_state=RANDOM_STATE
)

# 기본 모델
base = LogisticRegression(max_iter=1000, n_jobs=None, random_state=RANDOM_STATE)
base.fit(X_train, y_train)
pred_base = base.predict(X_test)
proba_base = base.predict_proba(X_test)[:, 1]

# 공정성 지표
mf = MetricFrame(
    metrics={"selection_rate": selection_rate, "tpr": true_positive_rate},
    y_true=y_test, y_pred=pred_base, sensitive_features=s_test
)
dp_diff = demographic_parity_difference(y_test, pred_base, sensitive_features=s_test)
eo_diff = equalized_odds_difference(y_test, pred_base, sensitive_features=s_test)
auc_base = roc_auc_score(y_test, proba_base)

print("=== Baseline ===")
print("AUC:", round(auc_base, 4))
print("Group metrics:\n", mf.by_group)
print("Demographic Parity Diff:", round(dp_diff, 4))
print("Equalized Odds Diff:", round(eo_diff, 4))

# 완화: 학습 단계 제약(인구 통계학적 균형)
mitigator = ExponentiatedGradient(base, constraints=DemographicParity())
mitigator.fit(X_train, y_train, sensitive_features=s_train)
pred_mit = mitigator.predict(X_test)

proba_mit = np.clip(
    # 일부 감쇠기 구현체는 확률 제공이 제한적일 수 있음 → 예측만 비교
    # 운영에서는 ThresholdOptimizer 등 후처리를 병행
    proba_base, 0, 1
)
mf_mit = MetricFrame(
    metrics={"selection_rate": selection_rate, "tpr": true_positive_rate},
    y_true=y_test, y_pred=pred_mit, sensitive_features=s_test
)
dp_diff_mit = demographic_parity_difference(y_test, pred_mit, sensitive_features=s_test)
eo_diff_mit = equalized_odds_difference(y_test, pred_mit, sensitive_features=s_test)
auc_mit = roc_auc_score(y_test, proba_mit)

print("\n=== Mitigated (Demographic Parity) ===")
print("AUC(approx):", round(auc_mit, 4))
print("Group metrics:\n", mf_mit.by_group)
print("Demographic Parity Diff:", round(dp_diff_mit, 4))
print("Equalized Odds Diff:", round(eo_diff_mit, 4))

SHAP과 LIME으로 개별 예측 근거를 남기기

SHAP은 전역 중요도와 국소 설명을 함께 다룰 수 있다.

# 환경: shap 0.45+, scikit-learn 1.2+
from sklearn.ensemble import GradientBoostingClassifier
import shap
import numpy as np

gb = GradientBoostingClassifier(random_state=RANDOM_STATE)
gb.fit(X_train, y_train)

# Auto Explainer: 트리 모델 자동 인식
explainer = shap.Explainer(gb, X_train, feature_names=[f"f{i}" for i in range(X.shape[1])])
sv = explainer(X_test[:200])  # 계산 비용 절감을 위한 샘플링

# 전역 중요도 Top-5
mean_abs = np.abs(sv.values).mean(axis=0)
top_idx = np.argsort(mean_abs)[::-1][:5]
print("\n=== SHAP Global Top-5 Features ===")
for i in top_idx:
    print(f"Feature f{i}: {mean_abs[i]:.4f}")

# 국소 설명(첫 샘플)
i = 0
contrib = sorted(list(zip(explainer.feature_names, sv.values[i])), key=lambda x: abs(x[1]), reverse=True)[:5]
print("\n=== SHAP Local Explanation (sample 0) ===")
for name, val in contrib:
    print(f"{name}: {val:+.4f}")
print("Base value:", float(sv.base_values[i]))
print("Model output approx:", float(sv.base_values[i] + sv.values[i].sum()))

LIME은 특정 예측을 빠르게 해석할 때 사용할 수 있다.

# 환경: lime 0.2+
from lime.lime_tabular import LimeTabularExplainer
import numpy as np

class_names = ["neg", "pos"]
lime_exp = LimeTabularExplainer(
    training_data=X_train,
    feature_names=[f"f{i}" for i in range(X.shape[1])],
    class_names=class_names,
    discretize_continuous=True,
    random_state=RANDOM_STATE
)

# gb의 확률 예측을 사용
exp = lime_exp.explain_instance(X_test[0], gb.predict_proba, num_features=5)
print("\n=== LIME Local Explanation (sample 0) ===")
for feat, weight in exp.as_list():
    print(f"{feat}: {weight:+.4f}")

재현성을 위해 random_state를 고정하고 데이터 스냅샷을 보관한다. SHAP은 대규모 데이터에서 샘플링·요약(KMeans)을 적용한다. 민감 특성은 운영 입력에서 제외하되, 오프로 인과/프록시 진단에만 사용한다.

공정성·성능·개인정보 사이의 선택

공정성 제약을 강화하면 AUC·정확도가 소폭 하락할 수 있다. KPI는 다목적 최적화로 두고 파레토 전선을 분석한다.

SHAP은 일관성과 전역 보고서에 유리하지만 비용이 높다. LIME은 빠른 국소 설명과 이의제기 대응에 적합하지만, 시드 고정과 K-LIME으로 안정성을 보강해야 한다.

설명 결과 자체에도 재식별 위험이 있으므로 특성 단위 마스킹, 민감 특성 비공개, 접근 통제와 감사 로그가 필요하다. 배치 전에는 공정성 지표 임계, 성능 하한, 설명 커버리지 기준을 동시에 충족하는 승인 게이트를 두고, 배포 후에는 지표 드리프트 알림과 자동 롤백·재학습 워크플로우를 구성한다.

머신러닝 윤리공정성XAIFairlearnSHAPLIME