본문 바로가기

Deep Learning/Vision

Image Classification < Basic To Transfer > - (3)

Convolution Network Visualization 


Grad-CAM : Visual Explanaitions from Deep Networks via Gradient based Localization

 딥러닝 모델은 흔히 Black Box 모델이라고 일컬어진다. 모델은 가중치를 학습하는 방식으로 진행이되기때문에 학습한 표현에서 어떤것이 분류, 회귀에 어떠한 형태로 기여하는지 알기가 어렵기 때문이다. 이러한 한계점은 모델에 대한 일반인들의 신뢰성을 저하시킬수 있으며 명확한 결정요인을 파악하기가 어렵다.

 하지만 2016년 'Grad-CAM : Visual Explanaitions from Deep Networks via Gradient based Localization' 이라는 논문이 발표되면서 Network의 중간층에 대한 시각화와 최종 분류결정요인에 대한 시각화가 가능해졌다. 간단히 논문에서 사용한 방법에 대해서 설명하자면 'Grad-CAM' 이라는 방법을 사용했다. 이 방법은 기존 모델의 구조에서 특별한 변형없이 성능을 유지한채로 어느정도 해석이가능한 시각화된 자료를 제공한다. Convolutional layer에서의 gradient를 활용해 Convolutional layer's feature maps과 곱연산하여 추가적으로 ReLU Activation function을 거친 값을 input이미지에 heat-map표현을 해주는 것으로 Grad-CAM을 표현할 수 있다. 

 


6. Filter Visualization

model = load_model('cats_and_dogs_small_finetuning.h5')
model.summary() 

Fine Tuning Model

# 출력할 샘플 이미지를 load
img_path = '/test/cats/cat.1700.jpg'

img = image.load_img(img_path, target_size=(150, 150))
img_tensor = image.img_to_array(img)
img_tensor = np.expand_dims(img_tensor, axis=0)
img_tensor /= 255.
# 상위 8개 층의 출력을 추출
# 입력에 대해 8개 층의 출력을 반환하는 모델생성
layer_outputs = [layer.output for layer in model.layers[0].layers[1:11]]
activation_model = models.Model(inputs=model.layers[0].layers[1].input, outputs=layer_outputs)

 전이학습을 이용한 모델 중 직접 Fine Tuning한, 성능이 가장 좋았던 모델을 불러온다. 불러오게되면 conv_base로 정의했던 vgg16 layer가 최하위에 위치하게된다. 이 중 상위 11개의 층에 해당하는 출력을 시각화하여 나타낼것이므로 해당 vgg16 layer에서 11개의 layer들을 추출하고 activation model을 생성한다.

# 각 층의 활성화마다 8개의 array로 이루어진 리스트를 반환
activations = activation_model.predict(img_tensor)

# Visualization
layer_names = []
for layer in model.layers[0].layers[1:11]:
    layer_names.append(layer.name)

images_per_row = 16

for layer_name, layer_activation in zip(layer_names, activations):
    n_features = layer_activation.shape[-1]

    # feature map size = (1, size, size, n_features)
    size = layer_activation.shape[1]

    # Grid 설정
    n_cols = n_features // images_per_row
    display_grid = np.zeros((size * n_cols, images_per_row * size))

    for col in range(n_cols):
        for row in range(images_per_row):
            channel_image = layer_activation[0, :, :, col * images_per_row + row]
            channel_image -= channel_image.mean()
            channel_image /= channel_image.std()
            channel_image *= 64
            channel_image += 128
            channel_image = np.clip(channel_image, 0, 255).astype('uint8')
            display_grid[col * size : (col + 1) * size,
                         row * size : (row + 1) * size] = channel_image

    scale = 1. / size
    plt.figure(figsize=(scale * display_grid.shape[1],
                        scale * display_grid.shape[0]))
    plt.title(layer_name)
    plt.grid(False)
    plt.imshow(display_grid, aspect='auto', cmap='viridis')

plt.show()

block1_conv1 layer
block2_conv1 layer
block3_conv1 layer / block3_pooling layer

 

 Layer가 깊어짐에 따라 고양이 사진이 사람이 식별하기 좀더 어려운 형태로 점차 변형되고있다. 특히 위 모델에서는 Max Pooling을 통해 이미지의 edge를 잡아내고 특성을 추출하는데 이에 따라 block1에서 block3로 갈수록 특정 필터에서는 귀, 눈, 코, 전체적인 이미지 실루엣 등의 특징적인 부분을 포착하여 필터에서 학습하고 있음을 보여준다. Max Pooling은 이러한 특징점 추출에 기여하고있다. 시각화에서 알 수 있듯이 CNN은 각각의 필터가 이미지의 고유한 특징적인 부분을 capture하는 방식으로 학습을 진행한다. 정리하자면, 층에서 추출한 특성은 층의 깊이를 따라 점점 더 추상적으로 학습된다. 상위 층의 활성화는 특정 입력에 관한 시각적 정보가 점점 줄어들고 target에 관한 정보가 점점 더 증가하는 방향으로 학습이 진행되는 것으로 보인다.

7. Class Activation Map (CAM)

from keras.applications.vgg16 import VGG16
from keras import backend as K
from keras.applications.vgg16 import preprocess_input, decode_predictions
import cv2

# Load VGG16 Model
model = VGG16(weights='imagenet')
# sample image
img_path = 'fox.jpg'
img = image.load_img(img_path, target_size=(224, 224))

# image to array
x = image.img_to_array(img)

# 차원을 추가, (1, 224, 224, 3) 크기로 변환 및 정규화
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
preds = model.predict(x)
print('Predicted:', decode_predictions(preds, top=3)[0])

 새로운 image인 여우 이미지에 대해 VGG16 모델은 red_fox를 1순위로 선정했으며 당시 확률은 0.68이 산출되었다.

# fox index
fox_index = model.output[:, 277]

# 마지막 layer인 block5_conv3 층의 특성 맵
last_conv_layer = model.get_layer('block5_conv3')

# block5_conv3의 feature map 출력에 대한 'red_fox' 클래스의 gradient
grads = K.gradients(fox_index, last_conv_layer.output)[0]
pooled_grads = K.mean(grads, axis=(0, 1, 2))

# pooled_grads와 block5_conv3의 feature map 출력
iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])
pooled_grads_value, conv_layer_output_value = iterate([x])

for i in range(512):
    conv_layer_output_value[:, :, i] *= pooled_grads_value[i]

# feature map에서 channel axis를 따라 평균하여 heatmap 산출
heatmap = np.mean(conv_layer_output_value, axis=-1)
import cv2

img = cv2.imread(img_path)
heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))
heatmap = np.uint8(255 * heatmap) # RGB format
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
imposed_img = heatmap * 0.4 + img
plt.imshow(imposed_img/255)

원본 / imposed_img

 모델의 마지막 Convolution Layer인 'block5_conv3' 를 back-propagation하여 얻어낸 gradient를 통해 feature map을 heatmap형태로 표현하고 이를 보기좋게 원본사진과 겹쳐서 표현하면 위와같은 사진이 산출된다. 즉 붉은 여우를 탐지하는 데에 있어서 해당 여우의 목부분과 특히 튀어나온 코부분이 분류에 상당히 큰 기여를 하고 있음이 드러난다. 

 


 

'Deep Learning > Vision' 카테고리의 다른 글

AutoEncoder  (0) 2021.06.21
Image Classification < Basic To Transfer > - (2)  (0) 2021.04.01
Image Classification < Basic To Transfer > - (1)  (0) 2021.03.27