[XAI] LIME : Local Interpretable Model-agnostic Explanation

2024. 12. 1. 20:43머신러닝&딥러닝/Explainability

728x90

LIME은 복잡한 ML 모델의 explainability를 확보하기 위해 local approximation이라는 방법을 활용하는 기법이다. LIME은 model agnostic, 즉 모델의 종류와 상관없이 작동하며 다룰 수 있는 data modality 또한 다양하다. 본 포스팅에서는 Marco Tulio Ribeiro et al의 LIME: "Why Should I Trust You?" Explaining the Predictions of Any Classifier 을 리뷰해 보고자 한다.

 

우선, "모델을 신뢰할 수 있다"는 것은 무엇을 말할까? 모델의 예측값(prediction)을 신뢰 가능하다는 의미일 수도 있고, 모델 자체에 대한 신뢰일 수도 있다. 예측값에 대한 신뢰를 보기 위해서는 모델이 어떤 input 때문에 판단을 내렸는지, 모델에 대한 신뢰를 보기 위해서는 전반적으로 어떤 feature를 중요하게 보아야 한다.

 

 

이러한 "모델 신뢰도"를 정량적으로 확인할 수 있는 Explanation 을 제공하는 것을 LIME의 주요한 objective로 본다.

Fidelity - Interpretability Tradeoff

먼저, LIME을 이해하기 위해서는 Fidelity-Interpretability Tradeoff의 개념을 이해해야 한다. 모델의 fidelity, 혹은 정확도가 높아질수록 모델의 blackbox 성향은 짙어지며 interpretability는 떨어지게 된다. 아래와 같은 논문의 figure를 인용해 보면, 실제 전체 데이터에 대한 classification boundary가 분홍색과 하늘색 사이의 경계라고 해 보자. 이러한 boundary를 재현할 수 있는 비선형함수가 우리가 원하는 optimal fidelity를 가진 classifier일 것이다. 이러한 classifier는 global한 데이터 분포에 대한 interpretability는 현저히 떨어진다.

 

하지만, 이때 linear descision function(점선)을 우리의 분류 모델로 사용한다고 했을 때, 당연히 model의 global fidelity는 떨어지지만, 경계선 근처에 붉은 +와 푸른 ○로 이루어진 데이터에 대해서는 높은 fidelity (즉, local fidelity가 된다)를 보일 것이며 interpretability도 높아질 것이다. 이것이 LIME의 핵심 아이디어인데, 복잡한 model을 local apporoximation할 수 있는 surrogate model을 만들어서 interpretability를 높이자는 것이다.

 

LIME에서는 실제 모델 f를 설명할 수 있는 근사 모델 g를 설정한다. g는 Descision tree, linear model 등등이 될 수 있다. 이때 instance x에 대하여 explanation ξ(x)는 아래와 같이 정의된다. ξ(x)=argmin

\pi_x는 x의 locality function으로, \pi_x (z)의 값은 x와 z가 가까울수록 커진다. 또한, Omega는 g의 복잡도를 나타낸다. 이러한 loss function \mathcal{L}을 locality-aware loss라고 한다.

 

Sampling for Local Exploration

인스턴스 x에 대한 explanation을 위해, x를 "설명가능한" 인스턴스 x'로 바꿔야 한다. 논문에서는 이를 x의 feature들을 이진화하여 벡터로 나타내어 설명 가능한 인스턴스 x'로 바꾼다. 이러한 binary representation이 설명가능성을 나타낸 이유는, {0, 1}의 descrete한 값이 feature의 활성화를 나타내기 떄문이다. 또한, x'와 "local"한 데이터를 샘플링하기 위해서 x'의 nonzero element를 random selection한다. 예를 들어 x' = (1, 0, 1, 1, 0, 1)이면 z'로 (1, 0, 0, 0, 0, 0), (0, 0, 1, 1, 0, 0) 등을 고르는 것이다. 이러한 z'는 x'와의 유사도를 측정하기 위해 (즉, \pi_x 값을 결정하기 위해) proximity weighting이라는 과정을 거치는데, cosine/euclidean similarity 등을 통해 x'와 z'의 거리를 재고 이에 따른 가중치를 부여하는 것이다. (text에서는 cosine, image에서는 L2 distance로 하면 될 듯하다.)

 

Code Example

LIME의 코드를 저자 M.T.Ribiero의 github(https://github.com/marcotcr)을 통해 살펴보자. pip을 통해 lime을 받아주고, MNIST classifier의 explainability를 살펴 보자.

 

marcotcr - Overview

marcotcr has 31 repositories available. Follow their code on GitHub.

github.com

 

이미지에 대한 MNIST classifier를 scikit-learn으로 아래와 같이 만들고, fit까지 시켜 주자. (MNIST 불러오는 과정은 생략!)

from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import Normalizer

class PipeStep(object):
    """
    Wrapper for turning functions into pipeline transforms (no-fitting)
    """
    def __init__(self, step_func):
        self._step_func=step_func
    def fit(self,*args):
        return self
    def transform(self,X):
        return self._step_func(X)


makegray_step = PipeStep(lambda img_list: [rgb2gray(img) for img in img_list])
flatten_step = PipeStep(lambda img_list: [img.ravel() for img in img_list])

simple_rf_pipeline = Pipeline([
    ('Make Gray', makegray_step),
    ('Flatten Image', flatten_step),
    #('Normalize', Normalizer()),
    #('PCA', PCA(16)),
    ('RF', RandomForestClassifier())
                              ])
                              
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X_vec, y_vec,
                                                    train_size=0.55)
simple_rf_pipeline.fit(X_train, y_train)

 

다음으로 lime을 호출하여 explainer와 segmentor를 정의하자. 먼저 segmentor는 이미지를 superpixel로 쪼개어, superpixel하나가 하나의 binary feature로 작동하게 한다. 즉 알고리즘에 따르면 이미지 데이터 x를 binary representation x'로 변환해준다. 이러한 x'를 LIME 알고리즘으로 explain 하는 함수가 explainer이다. 

from lime import lime_image
from lime.wrappers.scikit_image import SegmentationAlgorithm
explainer = lime_image.LimeImageExplainer(verbose = False)
segmenter = SegmentationAlgorithm('quickshift', kernel_size=1, max_dist=200, ratio=0.2)

 

Explainer에서 surrogate model을 학습하고, 여기서 예측한 feature importance를 아래 코드와 같이 시각화할 수 있다.

%%time
explanation = explainer.explain_instance(X_test[0], 
                                         classifier_fn = simple_rf_pipeline.predict_proba, 
                                         top_labels=10, hide_color=0, num_samples=10000, segmentation_fn=segmenter)
                                         
temp, mask = explanation.get_image_and_mask(y_test[0], positive_only=True, num_features=10, hide_rest=False, min_weight = 0.01)
fig, (ax1, ax2) = plt.subplots(1,2, figsize = (8, 4))
ax1.imshow(label2rgb(mask,temp, bg_label = 0), interpolation = 'nearest')
ax1.set_title('Positive Regions for {}'.format(y_test[0]))
temp, mask = explanation.get_image_and_mask(y_test[0], positive_only=False, num_features=10, hide_rest=False, min_weight = 0.01)
ax2.imshow(label2rgb(3-mask,temp, bg_label = 0), interpolation = 'nearest')
ax2.set_title('Positive/Negative Regions for {}'.format(y_test[0]))

 

fig, m_axs = plt.subplots(2,5, figsize = (12,6))
for i, c_ax in enumerate(m_axs.flatten()):
    temp, mask = explanation.get_image_and_mask(i, positive_only=True, num_features=1000, hide_rest=False, min_weight = 0.01 )
    c_ax.imshow(label2rgb(mask,X_test[0], bg_label = 0), interpolation = 'nearest')
    c_ax.set_title('Positive for {}\nActual {}'.format(i, y_test[0]))
    c_ax.axis('off')

반응형