GradCAM (Gradient-weighted Class Activation Mapping)
딥러닝은 이미지 분류, 객체 탐지, 이미지 생성 등 다양한 분야에서 뛰어난 성능을 보입니다.
CNN은 이러한 성과의 중심에 있으며, 복잡한 패턴을 학습하여 높은 정확도를 달성합니다.
그러나 CNN의 내재된 복잡성은 모델이 내린 결정을 이해하고 설명하는 것을 어렵게 만듭니다.
AI 시스템의 해석 가능성은 그 신뢰성을 높이고, 사용자가 시스템을 더 잘 이해할 수 있도록 돕습니다.
GradCAM은 Convolutional Neural Network(CNN)의 예측을 시각화하고 이해하는 데 사용되는 기법입니다.
GradCAM은 CNN이 특정 클래스에 대해 내리는 예측이 입력 이미지의 어떤 부분에 의해 영향을 받았는지 시각적으로 나타냅니다.
이 기법은 다음과 같은 단계를 통해 작동합니다:
- 이미지 입력
- CNN Forward Pass
- 특징 맵 및 예측 값 획득
- 예측 클래스에 대한 그래디언트 계산
- 특징 맵의 각 채널 가중치 계산
- 가중치를 적용한 특징 맵 계산
- Heatmap 생성
- Heatmap을 원본 이미지에 겹쳐 시각화
GradCAM 알고리즘의 예제는 다음과 같습니다.
우선 데이터를 불러옵니다.
import tensorflow as tf
from tensorflow.keras.applications import VGG19, ResNet50, ResNet10
import cv2
import matplotlib.pyplot as plt
import numpy as np
import GradCAM as gcam
img_path = './park_bench.png'
img = cv2.imread(img_path)
img = cv2.resize(img, (224,224))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
print(img.shape)
plt.imshow(img)
모델의 경우에는 tensorflow에서 정의해둔 ResNet50 아키텍처에 사전 학습된 ImageNet 가중치를 불러와서 테스트해 보겠습니다.
model = ResNet50(weights='imagenet')
model.summary()
# model.summary() 말고 이렇게도 layer name을 확인할 수 있습니다.
# for layer in model.layers:
# print(layer.name)
이제 GradCAM을 정의하고 제일 마지막 convolution layer가 찾은 feature 시각화하여 heatmap으로 나타내 봅시다.
g_cam = gcam.GradCAM(model)
heatmap = g_cam.return_color_heatmap(img)
heatmap = np.array(heatmap)
여기서 gcam은 제일 위에 import한 "import GradCAM as gcam" 모듈에서 가지고 왔습니다.
gcam 내부에 GradCAM class의 코드는 다음과 같습니다.
class GradCAM():
def __init__(self, model, last_conv_layer_name=''):
self.model = model
model_1, model_2 = self.model_12(model, last_conv_layer_name)
self.model_1 = model_1
self.model_2 = model_2
def preprocessing_img(self, img):
preprocessed_img = np.expand_dims(img, 0) / 255.0
return preprocessed_img
def model_12(self, model, last_conv_layer_name):
'''
모델을 두 개로 분할하는 메서드
model = 학습된 tensorflow 모델
last_conv_layer_name = 마지막 convolution layer의 레이어명 (model.summary()로 확인하거나 model.layers[1].name로 가져올 것)
'''
if last_conv_layer_name=='':
last_conv_layer_name = model.layers[-3].name
last_conv_layer = model.get_layer(last_conv_layer_name)
# 첫 번째 단계, 입력 이미지를 마지막 컨볼루션 층의 activation으로 매핑하는 모델을 만듭니다.
model_1 = tf.keras.Model(model.inputs, last_conv_layer.output)
# 두 번째 단계, 마지막 컨볼루션 층의 activation을 최종 클래스 예측으로 매핑하는 모델을 만듭니다.
model_input = tf.keras.Input(shape=last_conv_layer.output.shape[1:])
x_2 = model_input
x_2 = model.get_layer(model.layers[-2].name)(x_2)
x_2 = model.get_layer(model.layers[-1].name)(x_2)
model_2=tf.keras.Model(model_input, x_2)
return model_1, model_2
def make_gradcam_heatmap(self, img):
img = self.preprocessing_img(img)
with tf.GradientTape() as tape:
# 마지막 컨볼루션 층의 activation을 계산하고 tape가 이를 확인함.
last_conv_layer_output = self.model_1(img)
tape.watch(last_conv_layer_output)
# 클래스 예측을 계산.
preds = self.model_2(last_conv_layer_output)
top_pred_index = tf.argmax(preds[0])
top_class_channel = preds[:, top_pred_index]
# 마지막 컨볼루션 층의 출력 특징 맵에 대한 최상위 예측 클래스의 gradient.
grads = tape.gradient(top_class_channel, last_conv_layer_output)
# 이 벡터의 각 항목은 특정 특징 맵 채널에 대한 그라디언트의 평균 강도.
pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))
# 마지막 컨볼루션 층 출력의 각 채널에 대해
# "이 채널이 최상위 예측 클래스에 얼마나 중요한지"를 곱함.
last_conv_layer_output = last_conv_layer_output.numpy()[0]
pooled_grads = pooled_grads.numpy()
for i in range(pooled_grads.shape[-1]):
last_conv_layer_output[:, :, i] *= pooled_grads[i]
# 결과적인 특징 맵의 채널별 평균이 클래스 활성화 히트맵.
heatmap = np.mean(last_conv_layer_output, axis=-1)
# 시각화를 위해 히트맵을 0과 1 사이로 정규화.
heatmap = np.maximum(heatmap, 0) / np.max(heatmap)
return heatmap
def return_color_heatmap(self, img):
'''
img = model predict를 진행할 input image
'''
# GradCAM 히트맵을 생성.
heatmap = self.make_gradcam_heatmap(img)
# 히트맵을 [0, 255] 범위의 uint8 타입으로 변환.
heatmap =np.uint8(255*heatmap)
# 'jet' 컬러맵을 가져옴.
jet = cm.get_cmap("jet")
# 'jet' 컬러맵의 색상을 [0, 255] 범위의 배열로 변환.
color = jet(np.arange(256))[:,:3]
# 히트맵의 값을 'jet' 컬러로 변환.
color_heatmap = color[heatmap]
# 컬러 히트맵을 PIL 이미지로 변환.
color_heatmap = tf.keras.preprocessing.image.array_to_img(color_heatmap)
# 원본 이미지 크기로 리사이즈.
color_heatmap = color_heatmap.resize((img.shape[1], img.shape[0]))
# 다시 배열로 변환.
color_heatmap = tf.keras.preprocessing.image.img_to_array(color_heatmap)
# 컬러 히트맵을 원본 이미지 위에 덧씌움.
overlay_img= color_heatmap * 0.5 + img
# 최종 이미지를 PIL 이미지로 변환.
overlay_img = tf.keras.preprocessing.image.array_to_img(overlay_img)
return overlay_img
최종적으로 return_color_heatmap()을 사용하여 획득한 color heatmap 이미지는 다음과 같이 겹쳐서 나타낼 수있습니다.
alpha = 0.1
# 원본 이미지와 heatmap이미지 병합
merge_img = img * alpha + heatmap * (1-alpha)
merge_img = merge_img.astype(np.uint8)
plt.imshow(merge_img)
예제 코드는 아래 링크에서 확인하실 수 있습니다.
https://github.com/SaeByeolMun/GradCAM_tensorflow_keras