ConvNet 학습 시각화 (히트맵 시각화)

머신러닝과 딥러닝 · 2020. 1. 7. 19:12

히트맵 시각화

이미지의 어느 부분이 컨브넷의 최종 분류 결정에 기여하는지 이해하는데 유용하다.

 

분류에 실수가 있는 경우, 컨브넷의 결정과정을 디버깅하는데 도움이 된다.

이미지에 특정 물체가 있는 위치를 파악하는데도 사용이 된다.

 

이러한 종류의 기법을 일반적으로 클래스 활성화 맵(Class Activation Map, CAM) 시각화라고 한다.

 

강아지 vs 고양이 컨브넷에 한 이미지를 주입하면 CAM 시각화는

 

고양이 클래스에 대한 히트맵을 이미지에서 고양이와 비슷한 부분을 알려준다.

강아지 클래스에 대한 히트맵은 이미지에서 강아지와 비슷한 부분을 알려준다.

 

(Grad-CAM : Visual Explannations from Deep Networks via Gradient-based Localization에서 사용된 기법이다.)

 

이 방법은

 

입력 이미지가 주어지면 합성곱 층에 있는 특성 맵의 출력을 추출한다.

그 다음, 특성 맵의 모든 출력 채널에 대한 클래스의 그래디언트 평균을 곱한다.

 

직관적으로 이해하면, '입력 이미지가 각 채널을 활성화하는 정도'에 대한 공간적인 맵을

'클래스에 대한 각 채널의 중요도'로 가중치를 부여하여

'입력 이미지가 클래스를 활성화하는 정도'에 대한 공간적인 맵을 만드는 것이다.

 

사용한 모델의 구조는 다음과 같다.

 

이미지를 읽고 

모델의 입력 형식에 맞게 변경해준다.

from keras.preprocessing import image
import numpy as np

img_path      = "./test1/1010.jpg"
img           = image.load_img(img_path, target_size=(150, 150))
img_tensor    = image.img_to_array(img)
img_tensor_4d = np.expand_dims(img_tensor, axis=0)
img_tensor_4d /= 255.

print(img_tensor.shape)
print(img_tensor_4d.shape)

# (150, 150, 3)
# (1, 150, 150, 3)

이미지를 출력하면 다음과 같은 강아지 사진을 볼 수 있다.

import matplotlib.pyplot as plt

plt.imshow(img_tensor_4d[0])
plt.show()

 

이미지에서 가장 강아지 같은 부위를 시각화하기 위해 Grad-CAM을 사용한다.

Grad-CAM은 아래와 같다.

from keras import backend as K

last_conv_layer = model.get_layer('conv2d_24')
# 모델의 마지막 합성곱 층인 conv2d_24층의 특성 맵

grads           = K.gradients(model.output, last_conv_layer.output)[0]
# conv2d_24의 특성 맵 출력에 대한 강아지 클래스의 그래디언트

pooled_grads    = K.mean(grads, axis=(0,1,2))
# 특성 맵 채널별 그래디언트 평균값이 담긴 (128,) 크기의 벡터

iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0], last_conv_layer.output])
# 샘플 이미지가 주어졌을 때 방금 전 정의한 pooled_grads와 conv2d_24의 특성 맵 출력을 구한다.

pooled_grads_value, conv_layer_output_value, output = iterate([img_tensor_4d])
# 강아지가 있는 샘플 이미지를 넣고 2개의 Numpy 배열을 얻는다.

print(pooled_grads_value.shape, conv_layer_output_value.shape, output.shape)

for i in range(128):
    conv_layer_output_value[:,:,i] *= pooled_grads_value[i]
    # 강아지 클래스에 대한 채널의 중요도를 특성 맵 배열의 채널에 곱한다.
    
heatmap = np.mean(conv_layer_output_value, axis=-1)
# 만들어진 특성 맵에서 채널 축을 따라 평균한 값이 클래스 활성화의 히트맵이다.

heatmap = np.maximum(heatmap,0)
heatmap /= np.max(heatmap)

plt.matshow(heatmap)
plt.show()

 

테스트 사진에 대한 강아지 클래스 활성화 히트맵

OpenCV를 사용하여 위의 히트맵에 원본이미지를 겹친 이미지를 보자.

import cv2

img                = cv2.imread(img_path)

heatmap_resize     = cv2.resize(heatmap, (img.shape[1], img.shape[0]))
# heatmap을 원본 이미지 크기에 맞게 변경한다.

heatmap_rgb_format = np.uint8(255 * heatmap_resize)
# heatmap을 RGB 포맷으로 변환한다.

heatmap_conversion = cv2.applyColorMap(heatmap_rgb_format, cv2.COLORMAP_JET)
# 히트맵으로 변환한다.

superimposed_img = heatmap_conversion * 0.4 + img
# 0.4는 히트맵의 강도이다.

cv2.imwrite('./cam.jpg', superimposed_img)
# 디스크에 이미지를 저장한다.