Explainable AI (XAI)¶
개요¶
| 항목 | 내용 |
|---|---|
| 분류 | Machine Learning / Interpretability / Trustworthy AI |
| 핵심 개념 | 모델의 예측 근거를 인간이 이해할 수 있도록 설명하는 기법 |
| 동기 | 블랙박스 모델의 신뢰성, 디버깅, 규제 준수 |
| 관련 분야 | Fairness, Accountability, Transparency (FAT), Mechanistic Interpretability |
| 주요 라이브러리 | shap, lime, captum, alibi, InterpretML |
XAI 분류 체계¶
분류 축¶
XAI 분류
============================================================
[범위] [대상] [시점]
Global vs Local Model-agnostic Post-hoc
vs vs
전체 모델 행동 Model-specific Intrinsic (ante-hoc)
[출력 형태]
Feature Attribution | Rule-based | Example-based | Concept-based
분류 기준 비교¶
| 기준 | 범주 | 설명 | 예시 |
|---|---|---|---|
| 범위 | Global | 모델 전체 행동 패턴 | PDP, ALE, Global SHAP |
| Local | 개별 예측 설명 | LIME, Local SHAP, Grad-CAM | |
| 대상 | Model-agnostic | 어떤 모델에든 적용 | SHAP, LIME, PDP |
| Model-specific | 특정 모델 구조 활용 | TreeSHAP, Grad-CAM, Attention | |
| 시점 | Post-hoc | 학습 완료 후 사후 설명 | SHAP, LIME |
| Intrinsic | 모델 자체가 해석 가능 | 선형 회귀, 결정 트리, GAM |
Model-Agnostic 방법¶
1. SHAP (SHapley Additive exPlanations)¶
| 항목 | 내용 |
|---|---|
| 논문 | Lundberg & Lee, "A Unified Approach to Interpreting Model Predictions" (NeurIPS 2017) |
| 이론 기반 | Shapley value (협조 게임 이론, 1953) |
| 핵심 성질 | 유일하게 효율성, 대칭성, 더미, 가법성을 모두 만족하는 기여도 할당 |
Shapley Value 정의:
- \(N\): 전체 특성 집합
- \(S\): \(j\)를 제외한 특성의 부분집합
- \(f(S)\): 특성 집합 \(S\)만 사용할 때의 모델 출력 기대값
SHAP의 가법 모델:
- \(z' \in \{0, 1\}^M\): 특성 포함/제외 이진 벡터
- \(\phi_0 = E[f(X)]\): 기저값 (데이터 전체 평균 예측)
- 효율성: \(\sum_{j=0}^{M} \phi_j = f(x)\)
SHAP 변형 비교¶
| 변형 | 적용 모델 | 시간 복잡도 | 정확도 | 특징 |
|---|---|---|---|---|
| KernelSHAP | 모든 모델 | O(2^M) -> 샘플링 근사 | 근사 | LIME + Shapley 결합, 모델 호출 횟수 많음 |
| TreeSHAP | 트리 앙상블 | O(TLD^2) | 정확 | 트리 구조 활용 다항시간 정확 계산 |
| DeepSHAP | 딥러닝 | O(forward pass) | 근사 | DeepLIFT + Shapley, 역전파 기반 |
| LinearSHAP | 선형 모델 | O(M) | 정확 | 계수에서 직접 계산 |
| PermutationSHAP | 모든 모델 | O(M * n_samples) | 근사 | 순열 기반 Shapley 근사 |
TreeSHAP의 핵심: - 일반 Shapley: O(2^M) 부분집합 열거 필요 - TreeSHAP: 트리의 경로 구조를 활용하여 O(TLD^2)로 단축 - T: 트리 수, L: 리프 수, D: 트리 깊이 - 조건부 기대값(Interventional vs Observational) 방식 선택 가능
2. LIME (Local Interpretable Model-agnostic Explanations)¶
| 항목 | 내용 |
|---|---|
| 논문 | Ribeiro et al., "Why Should I Trust You?" (KDD 2016) |
| 핵심 아이디어 | 예측 주변에서 해석 가능한 대리 모델(surrogate)을 학습 |
수학적 정의:
- \(f\): 원본 블랙박스 모델
- \(g\): 해석 가능한 대리 모델 (선형 모델, 결정 트리 등)
- \(\pi_x\): \(x\) 주변의 근접도 커널 (가까울수록 높은 가중치)
- \(\Omega(g)\): 복잡도 페널티
알고리즘:
LIME 알고리즘
==============================================
입력: 블랙박스 모델 f, 설명할 인스턴스 x
출력: 특성 기여도
1. x 주변에서 교란 샘플 생성
z_i ~ 특성별 독립 분포 (연속: 가우시안, 범주: 빈도)
2. 각 교란 샘플에 대해 f(z_i) 계산
3. 거리 기반 가중치 계산
w_i = exp(-D(x, z_i)^2 / sigma^2)
4. 가중 선형 회귀 학습
g = argmin sum(w_i * (f(z_i) - g(z_i))^2)
5. g의 계수 -> 특성 기여도
==============================================
LIME vs SHAP 비교:
| 항목 | LIME | SHAP |
|---|---|---|
| 이론 기반 | 지역 대리 모델 | Shapley value (게임 이론) |
| 일관성 | X (커널/샘플링에 의존) | O (공리적 보장) |
| 속도 | 빠름 (샘플 수 제어) | 느림 (KernelSHAP) / 빠름 (TreeSHAP) |
| 안정성 | 낮음 (실행마다 결과 변동) | 높음 (수렴 보장) |
| Global 설명 | 제한적 (SP-LIME) | 자연스러운 확장 |
3. Partial Dependence Plot (PDP)¶
- \(x_S\): 관심 특성, \(x_C\): 나머지 특성
- 관심 특성 값을 고정하고 나머지를 데이터에서 주변화(marginalize)
- Global 방법: 모델의 평균적 반응을 시각화
한계: - 특성 간 상관관계 무시 (비현실적 조합 생성) - 이질적 효과 평균화 (상호작용 숨김)
4. ALE (Accumulated Local Effects)¶
- PDP의 상관관계 문제를 해결
- 조건부 기대값을 사용하여 현실적인 데이터 분포 반영
- 국소 효과를 누적하여 전체 효과 파악
PDP vs ALE:
| 항목 | PDP | ALE |
|---|---|---|
| 상관 특성 | 편향됨 | 비편향 |
| 계산 방식 | 주변화 (marginalization) | 조건부 미분 누적 |
| 해석 | 평균 예측값 | 평균 대비 효과 |
| 속도 | 느림 (n번 예측) | 빠름 (구간별 평균) |
5. Permutation Importance¶
- \(s\): 원래 성능 점수
- \(s_{j,k}^{\text{perm}}\): \(j\)번째 특성을 순열한 후의 성능
- 특성 \(j\)가 중요하면 순열 후 성능이 크게 하락
주의사항: - 상관된 특성: 순열이 비현실적 조합 생성 -> 과소평가 가능 - 대안: Conditional Permutation Importance
Model-Specific 방법¶
1. Grad-CAM (Gradient-weighted Class Activation Mapping)¶
| 항목 | 내용 |
|---|---|
| 논문 | Selvaraju et al. (ICCV 2017) |
| 적용 | CNN (이미지 분류) |
| 출력 | 클래스별 히트맵 |
수식:
가중치: $\(\alpha_k^c = \frac{1}{Z} \sum_i \sum_j \frac{\partial y^c}{\partial A_{ij}^k}\)$
히트맵: $\(L_{\text{Grad-CAM}}^c = \text{ReLU}\left(\sum_k \alpha_k^c A^k\right)\)$
- \(A^k\): 마지막 합성곱 층의 \(k\)번째 특성맵
- \(y^c\): 클래스 \(c\)에 대한 로짓
- ReLU: 양의 영향만 시각화
2. Integrated Gradients¶
| 항목 | 내용 |
|---|---|
| 논문 | Sundararajan et al. (ICML 2017) |
| 적용 | 미분 가능한 모든 모델 |
| 특징 | 완전성(Completeness) 공리 만족 |
수식:
- \(x'\): 기준선 (baseline, 보통 영벡터)
- 기준선에서 입력까지의 경로를 따라 그래디언트를 적분
- 완전성: \(\sum_j \text{IG}_j(x) = f(x) - f(x')\)
3. Attention Visualization¶
Transformer의 Attention 가중치를 설명으로 사용:
| 장점 | 한계 |
|---|---|
| 직관적 시각화 | Attention != Explanation (Jain & Wallace, 2019) |
| 추가 계산 불필요 | 다층 Attention의 해석 어려움 |
| 토큰 간 관계 파악 | 그래디언트 기반 방법과 불일치 사례 |
Attention Rollout: - 다층 Attention 가중치를 곱하여 입력까지의 정보 흐름 추적 - 잔차 연결(skip connection) 고려
Global vs Local Explanation¶
Global Explanation Local Explanation
(모델 전체 행동) (개별 예측 설명)
+---------------------------+ +---------------------------+
| "이 모델은 전반적으로 | | "이 고객의 이탈 확률이 |
| 소득과 연령이 가장 | | 높은 이유는 최근 3개월 |
| 중요한 특성이다" | | 사용량이 70% 감소했기 |
| | | 때문이다" |
| 방법: PDP, ALE, Global | | |
| SHAP, PI | | 방법: LIME, Local SHAP, |
+---------------------------+ | Grad-CAM, IG |
+---------------------------+
| 항목 | Global | Local |
|---|---|---|
| 목적 | 모델 동작 원리 파악 | 개별 의사결정 근거 제시 |
| 대상 사용자 | 데이터 사이언티스트, 감사자 | 최종 사용자, 규제 기관 |
| 계산 비용 | 높음 (전체 데이터) | 낮음 (단일 인스턴스) |
| 활용 | 모델 디버깅, 편향 감지 | 대출 거절 사유, 진단 근거 |
Counterfactual Explanations¶
정의¶
"최소한의 변경으로 예측 결과를 바꾸려면 어떤 특성을 어떻게 바꿔야 하는가?"
- \(y'\): 원하는 목표 클래스
- \(d\): 거리 함수 (L1, L2, Gower 등)
예시¶
현재 상태: 대출 거절 (f(x) = 0)
소득: 35,000
부채: 15,000
신용등급: 620
재직기간: 2년
반사실: 대출 승인 (f(x') = 1)
소득: 35,000 (변경 없음)
부채: 10,000 (5,000 감소)
신용등급: 620 (변경 없음)
재직기간: 2년 (변경 없음)
=> "부채를 5,000만 줄이면 대출이 승인됩니다"
주요 방법¶
| 방법 | 특징 |
|---|---|
| Wachter et al. (2017) | 최초의 반사실 설명 프레임워크 |
| DiCE (Mothilal et al., 2020) | 다양한 반사실 집합 생성 |
| FACE (Poyiadzi et al., 2020) | 데이터 매니폴드 위의 실현 가능한 반사실 |
| GrowingSpheres | 점진적 반경 확장으로 탐색 |
좋은 반사실의 조건¶
- 유효성: 목표 클래스로 전환
- 근접성: 원본과 최소한의 차이
- 희소성: 변경 특성 수 최소화
- 실현가능성: 물리적/논리적으로 가능한 변경
- 인과적 타당성: 인과 구조를 위반하지 않는 변경
Concept-based Explanations¶
TCAV (Testing with Concept Activation Vectors)¶
| 항목 | 내용 |
|---|---|
| 논문 | Kim et al. (ICML 2018) |
| 핵심 | 인간이 이해하는 고수준 개념으로 설명 |
| 예시 | "이 이미지가 얼룩말로 분류된 것은 '줄무늬' 개념 때문" |
알고리즘:
TCAV 알고리즘
==============================================
1. 개념 데이터 수집
- 양성 예시: "줄무늬" 패턴이 있는 이미지
- 음성 예시: 무작위 이미지
2. CAV (Concept Activation Vector) 학습
- 활성화 공간에서 선형 분류기 학습
- 분류 초평면의 법선 벡터 = CAV
3. TCAV 점수 계산
TCAV_k^c = |{x: S_c(f_l(x), y_k) > 0}| / |X_k|
S_c = grad(h_k(f_l(x))) . v_c
(개념 방향으로의 기울기 성분)
4. 통계 검정
- 무작위 개념 대비 유의성 검정
==============================================
- 장점: 특성이 아닌 개념 수준의 설명
- 한계: 개념 데이터 수동 수집 필요, 선형 분리 가능 가정
LLM 해석과 Mechanistic Interpretability¶
Mechanistic Interpretability 개요¶
LLM 내부 메커니즘을 역공학하여 "어떻게" 작동하는지 이해:
| 접근법 | 설명 | 대표 연구 |
|---|---|---|
| Probing | 내부 표현에 분류기를 학습하여 정보 존재 확인 | Belinkov (2022) |
| Circuit Analysis | 특정 행동을 담당하는 최소 하위 네트워크 식별 | Olsson et al. (2022) |
| Sparse Autoencoders | 중첩된(superposed) 특성을 분리 | Cunningham et al. (2023) |
| Logit Lens | 중간 레이어의 출력을 어휘 공간으로 투영 | nostalgebraist (2020) |
| Activation Patching | 활성화를 교체하여 인과적 역할 검증 | Meng et al. (2022) |
| Representation Engineering | 표현 수준에서 모델 행동 제어 | Zou et al. (2023) |
Superposition과 Sparse Autoencoders¶
Superposition 문제:
===================================
뉴런 하나에 여러 특성이 중첩
뉴런 1: [특성A 0.7] + [특성B 0.3]
뉴런 2: [특성A 0.2] + [특성C 0.8]
뉴런 3: [특성B 0.5] + [특성C 0.5]
|
v Sparse Autoencoder로 분리
SAE 특성 1: [특성A] (순수)
SAE 특성 2: [특성B] (순수)
SAE 특성 3: [특성C] (순수)
===================================
- SAE는 모델의 은닉 표현을 더 큰 차원의 희소 공간으로 사영
- Anthropic (2024): Claude에서 수백만 개의 해석 가능한 특성 발견
- 안전성 연구의 핵심 도구
전통 XAI vs Mechanistic Interpretability¶
| 항목 | 전통 XAI | Mechanistic Interpretability |
|---|---|---|
| 질문 | "왜 이런 예측?" | "어떤 메커니즘으로 작동?" |
| 수준 | 입력-출력 관계 | 내부 계산 과정 |
| 대상 | 범용 ML 모델 | 주로 신경망/LLM |
| 출력 | 특성 기여도, 규칙 | 회로, 특성, 알고리즘 |
| 목적 | 신뢰/규제 준수 | 안전성/정렬(alignment) |
규제 요구사항¶
EU AI Act (2024)¶
| 위험 등급 | 설명 | XAI 요구사항 |
|---|---|---|
| Unacceptable | 사회 점수 시스템 등 | 사용 금지 |
| High-risk | 채용, 신용, 의료, 사법 | 설명 가능성 필수, 인간 감독 |
| Limited | 챗봇, 감정 인식 | AI 사용 고지 의무 |
| Minimal | 스팸 필터 등 | 의무 없음 |
High-risk AI에 대한 구체적 요구: - 투명성: 사용자가 출력을 해석할 수 있어야 함 - 인간 감독: 자동화된 의사결정에 대한 인간 개입 보장 - 기술 문서: AI 시스템의 작동 방식 문서화
금융 규제¶
| 규제 | 관할 | 핵심 요구 |
|---|---|---|
| SR 11-7 (Fed) | 미국 | 모델 리스크 관리, 모델 검증 필수 |
| ECOA / Reg B | 미국 | 신용 거절 시 구체적 사유 제공 의무 |
| GDPR Art. 22 | EU | 자동화된 의사결정에 대한 설명 권리 |
| EBA Guidelines | EU | ML 모델의 해석 가능성 요구 |
금융에서 XAI 실무: - 신용 평가: 거절 사유 상위 4개 특성 제공 (SHAP 활용) - 이상 거래 탐지: 경고 발생 근거 설명 - 보험 심사: 보험료 산정 근거의 투명성
방법별 비교표¶
| 방법 | 범위 | 모델 의존성 | 충실도 | 안정성 | 속도 | 이론적 보장 |
|---|---|---|---|---|---|---|
| SHAP (Kernel) | Local/Global | Agnostic | 높음 | 높음 | 느림 | Shapley 공리 |
| TreeSHAP | Local/Global | Tree only | 정확 | 높음 | 빠름 | Shapley 공리 |
| DeepSHAP | Local/Global | DL only | 근사 | 중간 | 빠름 | 근사적 |
| LIME | Local | Agnostic | 중간 | 낮음 | 빠름 | 없음 |
| PDP | Global | Agnostic | 중간 | 높음 | 중간 | 없음 |
| ALE | Global | Agnostic | 높음 | 높음 | 빠름 | 없음 |
| Grad-CAM | Local | CNN only | 중간 | 높음 | 빠름 | 없음 |
| Integrated Gradients | Local | 미분가능 | 높음 | 높음 | 중간 | 완전성 공리 |
| Permutation Imp. | Global | Agnostic | 중간 | 중간 | 느림 | 없음 |
| Counterfactual | Local | Agnostic | 높음 | 낮음 | 중간 | 없음 |
| TCAV | Global | DL | 높음 | 중간 | 중간 | 통계 검정 |
실무 코드 예시¶
SHAP을 이용한 종합 분석¶
import shap
import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_openml
# -------------------------------------------------------
# 데이터 준비 (Adult Income)
# -------------------------------------------------------
data = fetch_openml("adult", version=2, as_frame=True)
df = data.frame.dropna()
# 수치형 특성만 사용 (간략화)
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
target = "class" # <=50K, >50K
X = df[numeric_cols].drop(columns=[target], errors="ignore")
y = (df[target] == ">50K").astype(int)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# -------------------------------------------------------
# 모델 학습
# -------------------------------------------------------
model = GradientBoostingClassifier(
n_estimators=200, max_depth=4, learning_rate=0.1, random_state=42
)
model.fit(X_train, y_train)
# -------------------------------------------------------
# TreeSHAP
# -------------------------------------------------------
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)
# 개별 예측 설명 (Local)
idx = 0
print(f"Prediction: {model.predict_proba(X_test.iloc[[idx]])[0]}")
print(f"Base value: {explainer.expected_value:.4f}")
print(f"SHAP values for instance {idx}:")
for col, sv in zip(X_test.columns, shap_values[idx]):
if abs(sv) > 0.01:
print(f" {col}: {sv:+.4f} (value={X_test.iloc[idx][col]:.1f})")
# Global 특성 중요도 (평균 절대 SHAP)
global_importance = pd.DataFrame({
"feature": X_test.columns,
"mean_abs_shap": np.abs(shap_values).mean(axis=0),
}).sort_values("mean_abs_shap", ascending=False)
print("\nGlobal Feature Importance:")
print(global_importance.to_string(index=False))
# -------------------------------------------------------
# SHAP 상호작용 (Interaction Values)
# -------------------------------------------------------
shap_interaction = explainer.shap_interaction_values(X_test[:100])
# shape: (n_samples, n_features, n_features)
print(f"\nInteraction shape: {np.array(shap_interaction).shape}")
LIME을 이용한 텍스트 분류 설명¶
import lime
import lime.lime_text
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
# -------------------------------------------------------
# 텍스트 분류기 학습
# -------------------------------------------------------
texts = [
"This movie was great and I loved every minute",
"Terrible film, waste of time and money",
"Brilliant acting and wonderful storyline",
"Boring plot, bad acting, do not recommend",
"An absolute masterpiece of cinema",
"The worst movie I have ever watched",
] * 50 # 반복으로 충분한 데이터 확보
labels = [1, 0, 1, 0, 1, 0] * 50
pipe = Pipeline([
("tfidf", TfidfVectorizer(max_features=1000)),
("clf", LogisticRegression(max_iter=200)),
])
pipe.fit(texts, labels)
# -------------------------------------------------------
# LIME 설명
# -------------------------------------------------------
explainer = lime.lime_text.LimeTextExplainer(class_names=["negative", "positive"])
text_to_explain = "The movie had great acting but a terrible plot"
exp = explainer.explain_instance(
text_to_explain,
pipe.predict_proba,
num_features=6,
num_samples=500,
)
print(f"Text: {text_to_explain}")
print(f"Prediction: {pipe.predict_proba([text_to_explain])[0]}")
print("\nLIME Explanation:")
for feature, weight in exp.as_list():
direction = "positive" if weight > 0 else "negative"
print(f" '{feature}': {weight:+.4f} ({direction})")
Captum을 이용한 Integrated Gradients (PyTorch)¶
import torch
import torch.nn as nn
from captum.attr import (
IntegratedGradients,
LayerGradCam,
visualization,
)
import numpy as np
# -------------------------------------------------------
# 간단한 분류 모델
# -------------------------------------------------------
class SimpleNet(nn.Module):
def __init__(self, input_dim: int, hidden_dim: int = 64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, 2),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.net(x)
# 모델 준비
input_dim = 10
model = SimpleNet(input_dim)
model.eval()
# 더미 데이터
x = torch.randn(1, input_dim, requires_grad=True)
baseline = torch.zeros(1, input_dim)
# -------------------------------------------------------
# Integrated Gradients
# -------------------------------------------------------
ig = IntegratedGradients(model)
attributions, delta = ig.attribute(
x,
baselines=baseline,
target=1, # 클래스 1에 대한 기여도
n_steps=200, # 적분 스텝 수
return_convergence_delta=True,
)
print("Input:", x.detach().numpy().round(3))
print("Attributions:", attributions.detach().numpy().round(4))
print(f"Convergence delta: {delta.item():.6f}") # 0에 가까워야 함
# 완전성 검증
pred_diff = model(x)[0, 1].item() - model(baseline)[0, 1].item()
attr_sum = attributions.sum().item()
print(f"f(x) - f(baseline) = {pred_diff:.4f}")
print(f"Sum of attributions = {attr_sum:.4f}")
print(f"Difference (should be ~0): {abs(pred_diff - attr_sum):.6f}")
# -------------------------------------------------------
# Grad-CAM (CNN 예시)
# -------------------------------------------------------
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 16, 3, padding=1)
self.relu1 = nn.ReLU()
self.conv2 = nn.Conv2d(16, 32, 3, padding=1)
self.relu2 = nn.ReLU()
self.pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Linear(32, 10)
def forward(self, x):
x = self.relu1(self.conv1(x))
x = self.relu2(self.conv2(x))
x = self.pool(x)
x = x.view(x.size(0), -1)
return self.fc(x)
cnn = SimpleCNN()
cnn.eval()
img = torch.randn(1, 3, 32, 32)
# Grad-CAM on conv2 layer
grad_cam = LayerGradCam(cnn, cnn.conv2)
cam_attr = grad_cam.attribute(img, target=5)
print(f"\nGrad-CAM attribution shape: {cam_attr.shape}")
print(f"Grad-CAM min: {cam_attr.min():.4f}, max: {cam_attr.max():.4f}")
SHAP + XGBoost 실무 파이프라인¶
import shap
import xgboost as xgb
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
# -------------------------------------------------------
# 고객 이탈 예측 예시 (합성 데이터)
# -------------------------------------------------------
np.random.seed(42)
n = 5000
df = pd.DataFrame({
"tenure_months": np.random.randint(1, 72, n),
"monthly_charges": np.random.uniform(20, 100, n),
"total_charges": np.random.uniform(100, 5000, n),
"num_complaints": np.random.poisson(1.5, n),
"contract_type": np.random.choice([0, 1, 2], n), # month/year/2year
"usage_decline_pct": np.random.uniform(-20, 50, n),
})
# 이탈 확률 생성 (실제 관계 내장)
logit = (
-2
- 0.05 * df["tenure_months"]
+ 0.02 * df["monthly_charges"]
+ 0.5 * df["num_complaints"]
+ 0.03 * df["usage_decline_pct"]
- 0.8 * (df["contract_type"] == 2).astype(float)
)
df["churn"] = (np.random.uniform(0, 1, n) < 1 / (1 + np.exp(-logit))).astype(int)
features = [c for c in df.columns if c != "churn"]
X_train, X_test, y_train, y_test = train_test_split(
df[features], df["churn"], test_size=0.2, random_state=42
)
# -------------------------------------------------------
# XGBoost 학습
# -------------------------------------------------------
model = xgb.XGBClassifier(
n_estimators=200, max_depth=4, learning_rate=0.1,
use_label_encoder=False, eval_metric="logloss",
random_state=42,
)
model.fit(X_train, y_train)
auc = roc_auc_score(y_test, model.predict_proba(X_test)[:, 1])
print(f"AUC: {auc:.4f}")
# -------------------------------------------------------
# TreeSHAP 분석
# -------------------------------------------------------
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)
# 1) Global 중요도
print("\n=== Global Feature Importance (mean |SHAP|) ===")
importance = pd.DataFrame({
"feature": features,
"importance": np.abs(shap_values).mean(axis=0),
}).sort_values("importance", ascending=False)
print(importance.to_string(index=False))
# 2) 개별 고객 설명
customer_idx = 10
customer = X_test.iloc[customer_idx]
pred_prob = model.predict_proba(X_test.iloc[[customer_idx]])[0, 1]
print(f"\n=== Customer {customer_idx} ===")
print(f"Churn probability: {pred_prob:.3f}")
print(f"Base rate: {explainer.expected_value:.3f}")
print("Feature contributions:")
contributions = sorted(
zip(features, shap_values[customer_idx], customer[features]),
key=lambda x: abs(x[1]),
reverse=True,
)
for feat, sv, val in contributions:
if abs(sv) > 0.01:
direction = "increases" if sv > 0 else "decreases"
print(f" {feat}={val:.1f}: {sv:+.3f} ({direction} churn risk)")
# 3) 규제 대응: 상위 4개 거절 사유 자동 생성
def generate_decline_reasons(shap_vals, feature_names, top_k=4):
"""신용 거절 등 의사결정 사유를 SHAP 기반으로 생성"""
sorted_idx = np.argsort(-np.abs(shap_vals))
reasons = []
for i in sorted_idx[:top_k]:
feat = feature_names[i]
sv = shap_vals[i]
reasons.append({
"feature": feat,
"shap_value": round(sv, 4),
"direction": "negative" if sv > 0 else "positive",
})
return reasons
reasons = generate_decline_reasons(shap_values[customer_idx], features)
print(f"\nTop decline reasons:")
for i, r in enumerate(reasons, 1):
print(f" {i}. {r['feature']} (impact: {r['shap_value']:+.4f}, {r['direction']})")
한계와 발전 방향¶
현재 한계¶
- 설명의 충실도: Post-hoc 설명이 모델의 실제 메커니즘을 정확히 반영하지 않을 수 있음
- 안정성: LIME 등 교란 기반 방법은 실행마다 결과 변동
- 상관 특성: 다중공선성이 있을 때 기여도 할당이 임의적
- 스케일: 대규모 모델(LLM)에 대한 XAI 적용은 계산적으로 비실용적
- 사용자 이해: 기술적 설명이 비전문가에게는 여전히 불투명
발전 방향¶
- 인과적 설명 (Causal XAI): Shapley value를 인과 그래프 기반으로 확장
- 대화형 설명: 사용자가 질문하면 맞춤 설명 제공 (LLM + XAI)
- 자연어 설명: SHAP 값을 자연어로 자동 변환
- 인증된 설명: 설명의 충실도를 수학적으로 보장
- Mechanistic Interpretability 확장: LLM 내부 회로 분석을 통한 근본적 이해
- 다중 이해관계자 설명: 전문가/비전문가/규제기관 각각에 맞는 설명 수준