현재 회사에서, 그리고 스터디에서 X-AI 를 적용하기 위해 기본 베이스 모델인 Shapley Value를 사용하고 있다.
근데,, 계속 에러가 나는 것 아닌가..!! 실패는 성공의 어머니. 왜 에러가 났는 지 그 이유를 하나하나 파고들어보자.
우선 Shapley Value 를 사용하기 위해
pip install shap
을 해준 후, Shap Value를 통해 각 Feature가 모델에 끼치는 영향을 알아보고자 했는데
🤦♀️다음과 같은 에러들이 나타났다.
1. cudnn RNN backward can only be called in training mode
2. Input and hidden tensors are not at the same device, found input tensor at cpu and hidden tensor at cuda:0
3. The size of tensor a (xx) must match the size of tensor b (yy) at non-singleton dimension 0
스트뤠스,,, ಥ_ಥ
🔍 왜 이와 같은 에러들이 나타났는 지 하나씩 살펴보자.
Shap 라이브러리는 다양한 Explainer 기능들을 제공해 주는데,
Explainer | 주 대상 모델 | 특징 | 장점 | 단점 |
TreeExplainer | XGBoost, LightGBM, Random Forest 등 트리 기반 모델 |
Tree 구조 최적화 이용 | 빠르고 정확 | 딥러닝/비트리 모델은 X |
GradientExplainer | TensorFlow, PyTorch 등 딥러닝 모델 |
Gradient 이용 | 빠름 | Gradient 기반이라 안정성 낮을 수 있음 |
DeepExplainer | TensorFlow, Keras 기반 DNN | 내부 레이어 탐색 | 정확한 backprop 기반 설명 | 속도 느림, 복잡함 |
KernelExplainer | 모든 모델 (black-box) | LIME 방식 유사, perturbation 기반 |
모델 종류 상관없이 사용 가능 |
느림, 대규모 데이터에 부적합 |
PartitionExplainer | 트리 기반 모델 중 일부 | Causal structure 반영 | 빠르고 직관적 | 제한된 모델만 |
LinearExplainer | 선형 모델 | 선형 SHAP 계산 | 매우 빠름, 이론적 해석 용이 | 비선형 모델은 불가 |
대표적으로 위 표와 같은 기능들이 있다.
나는 현재 RNN 계열 모델 사용하고 있기 때문에, Explainer 중 GradientExplainer 를 사용하여 Feature들의 영향성을 파악하는 중이다.
그럼 GradientExplainer는 어떤 방식으로 Feature의 영향성을 계산하는지,
GradientExplainer의 작동 원리를 살펴보자.
GradientExplainer 작동 원리
- 예측 함수 f(x) 에 대해, 입력 특성 x의 각 차원에 대해 기울기(gradient)를 계산하고, 이를 이용해 각 feature의 민감도와 중요도를 추정
- feature를 변경했을 때, 예측이 얼마나 바뀌는지를 확인하여, 예측 변화가 큰 feature를 "기여도가 크다"라고 간주
- 기울기 기반 설명이기 때문에, gradient vanishing/exploding 이슈가 있는 모델에서는 안정성이 낮아질 수 있음
** SHAP는 입력 특성 X를 변경했을 때, 값이 얼마나 변화하는 지를 확인하기 때문에, 종속 변수(Y, target) 값은 사용하지 않는다.
그럼 다시 에러 1번을 살펴보면,
✅ 에러 1번은 "cudnn RNN backward can only be called in training mode" 로 "cuDNN 기반 RNN의 backward 연산(=기울기 계산)은 'model.train()' 모드일 때만 가능하다" 라는 의미이다.
현재 나는 Pytorch로 모델을 만들고 있으며, Pytorch는 RNN 연산을 수행할 때, cnDNN 최적화를 사용하고 있다.
(cuDNN : CUDA Deep Neural Network library 로 GPU 연산을 빠르고 효율적으로 해주는 NVIDIA의 라이브러리)
그런데, model.eval() 상태에서는 dropout 비활성화, batchnorm 고정 등 추론 모드로 설정되기 때문에, cuDNN 내부에서 gradient 계산이 불가하다. 그런데, SAHP의 GradientExplainer는 backward 연산을 사용하기 때문에, model.eval() 상태에서는 gradient 계산이 안되는 것이 당연하다.
해서 나는 이 문제를 해결하기 위해
import torch
try:
torch.backends.cudnn.enabled = False
model.eval()
'''
Shap Explainer ...
'''
finally:
torch.backends.cudnn.enabled = True
처럼 코드를 구성하여, 에러를 해결하였다.
model.train() 을 사용하는 방법도 있지만, model.train()으로 해당 에러를 해결할 경우, backward는 가능할 수 있어도 Dropout, BatchNorm을 설정하였다면, 해당 기능들이 활성화 되어 SHAP 결과가 불안정해질 수 있기 때문에 사용하지 않았다.
✅ 에러 2. "Input and hidden tensors are not at the same device, found input tensor at cpu and hidden tensor at cuda:0"
이 에러는 모델과 입력이 같은 디바이스에 있어야 하는데, RNN 계열의 모델의 경우
model의 hidden state를 직접 초기화 한다면, 그 역시도 to(device)를 해줘야 하는데 안해줘서 생긴 문제다. ╮(╯-╰)╭
h0 = torch.zeros(num_layers, batch_size, hidden_size).to(device)
잊지 말자 device 설정.
✅ 에러 3. The size of tensor a (xx) must match the size of tensor b (yy) at non-singleton dimension 0
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
model.eval()
# background and input shape: (B, T, F)
X_background = torch.tensor(X_train[:50], dtype=torch.float).to(device)
X_test_sample = torch.tensor(X_test[:10], dtype=torch.float).to(device)
explainer = shap.GradientExplainer(model, X_background)
shap_values = explainer.shap_values(X_test_sample)
shap value를 구하는 코드가 위와 같다고 할 때, 에러 3이 날 수 있는 case는 다음과 같다.
case1)
SHAP의 GradientExplainer.shap_values()가 리턴하는 shap_values는 보통 (N, D) 형식이어야 하고,
x_valid_np.shape도 그와 같아야 하지만, RNN 계열 모델에서는 출력이 (N, T, D)나 예상치 못한 extra dimension이 붙는 경우가 많아서 broadcasting이 실패하는 경우가 발생한다.
보통 summary_plot() 에서 에러가 많이 발생하는데, 이에 대한 해결방법은
- 시계열 차원을 flatten 해서 summary plot에 출력 가능하도록 reshape
- 특정 time step 만 선택하여 시각화 (시간별 해석)
- 전체 feature 기여도를 평균하여 global importance 계산
case2)
위와 같은 모델 input이 있을 때, shap_values의 출력은 출력 차원 마다 따로 feature 기여도를 계산하기 때문에
한 차원이 더 붙어 (B, T, F, O)이 된다. (** model의 output size)
shap_val_mean = shap_values.mean(axis=-1)
이때는 위 코드처럼 평균을 사용하거나 하나하나 시각화 해서 보는 방법도 있다.
case3)
DataLoader BatchSize 설정 시, model 자체가 batch_first = False인데, 입력이 True 인 경우 모델과 통일
case4)
DataLoader 에서 drop_last = False 인 경우, True 로 변경
해당 에러를 수정하고 나서 결과를 확인했는데,,, 또 직면한 문제,,,
매 TEST마다 중요 Feature가 다르다..!!!
🔍 왜 SHAP 결과가 매번 바뀔까?
1. 훈련된 모델이 달라질 경우
- 모델이 완전히 똑같은 구조여도, 초기 가중치 또는 학습 도중의 난수로 다른 파라미터를 가질 수 있기 때문에, SHAP은 그 모델 기준으로 feature importance를 추정하므로 결과가 달라진다.
→같은 seed를 고정해도, optimizer 설정, 데이터 순서, dropout 등이 바뀌면 결과가 달라짐
2. 입력 데이터가 다를 경우
- 보통은 여러 input에 대해 SHAP 값을 구한 뒤, 평균해서 전체적인 경향을 보는 것이 일반적이라 한다.
3. GradientExplainer의 근본적 한계
- GradientExplainer는 local gradient 기반이라서 특정 샘플에 대해서만 매우 민감하게 반응하여 작은 input 변화에도 큰 importance 변화가 생긴다.
→ 이럴 때는 DeepExplainer(ReLU 계열) 또는 KernelExplainer(샘플 기반)를 고려
후.. 우선 에러만 고치고 결과 정리는 아직 못했는데, 수요일에 회사 가서 결과정리까지 해야짓..!!
'AI > DeepLearning' 카테고리의 다른 글
Inductive vs Transductive (0) | 2025.04.17 |
---|---|
[핵심 머신러닝] Shapley Value (Explainable AI) (0) | 2025.04.13 |
Clustering 평가 방법 (0) | 2024.07.09 |
[Pytorch] Seq2Seq Transformer 실습 (2) | 2024.02.04 |
[Pytorch] Transformer (2) | 2024.01.28 |