Last active
August 22, 2018 04:35
-
-
Save you359/3874531b11f337ceeecda3a5174501b8 to your computer and use it in GitHub Desktop.
Class Activation Map
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from keras.applications.resnet50 import ResNet50 | |
from keras.applications.resnet50 import preprocess_input, decode_predictions | |
from keras.preprocessing import image | |
import keras.backend as K | |
import numpy as np | |
import cv2 | |
def load_image(path, target_size=(224, 224)): | |
x = image.load_img(path, target_size=target_size) | |
x = image.img_to_array(x) | |
x = np.expand_dims(x, axis=0) | |
x = preprocess_input(x) | |
return x | |
def generate_cam(img_tensor, model, class_index, last_conv): | |
""" | |
generate cam(class activation map) | |
:param img_tensor: CNN model의 입력으로 사용될 image tensor | |
:param model: 사용할 CNN model | |
- softmax(+ dense) 이전의 layer가 GlobalAveragePooling2D이어야 함 | |
- dense(fc) layer로 구성되면 안됨 -> VGGNet과 같은 모델은 Dense layer를 GAP로 변경 후 재학습(re-train)시킨 후 적용 | |
:param class_index: cam을 얻고자 하는 class index | |
:param last_conv: GAP 이전의 마지막 Conv layer 이름 | |
:return: | |
cam: class activation map | |
- shape는 last_conv layer의 feature map과 동일 | |
""" | |
model_input = model.input | |
model_output = model.layers[-1].output | |
# f_k(x, y) : 마지막 conv layer의 출력 feature map | |
f_k = model.get_layer(last_conv).output | |
# model의 입력에 대해서, | |
# 마지막 conv layer의 출력(f_k) 계산 | |
get_output = K.function([model_input], [f_k]) | |
[last_conv_output] = get_output([img_tensor]) | |
# batch size가 포함되어 shape가 (1, width, height, k)이므로 | |
# (width, height, k)로 shape 변경 | |
# 여기서 width, height는 마지막 conv layer인 f_k feature map의 width와 height를 의미함 | |
last_conv_output = last_conv_output[0] | |
# softmax(+ dense) layer와 GAP layer 사이의 weight matrix에서 | |
# class_index에 해당하는 class_weight_k(w^c_k) 계산 | |
# ex) w^2_1, w^2_2, w^2_3, ..., w^2_k | |
class_weight_k = model.layers[-1].get_weights()[0][:, class_index] | |
# 마지막 conv layer의 출력 feature map(last_conv_output)과 | |
# class_index에 해당하는 class_weight_k(w^c_k)를 k에 대응해서 weighted sum을 구함 | |
# feature map(last_conv_output)의 (width, height)로 초기화 | |
cam = np.zeros(dtype=np.float32, shape=last_conv_output.shape[0:2]) | |
for k, w in enumerate(class_weight_k): | |
cam += w * last_conv_output[:, :, k] | |
return cam | |
if __name__ == "__main__": | |
img_width = 224 | |
img_height = 224 | |
model = ResNet50(weights='imagenet') | |
print(model.summary()) | |
img_path = '../image/elephant.jpg' | |
img = load_image(path=img_path, target_size=(img_width, img_height)) | |
preds = model.predict(img) | |
predicted_class = preds.argmax(axis=1)[0] | |
# decode the results into a list of tuples (class, description, probability) | |
# (one such list for each sample in the batch) | |
print("predicted top1 class:", predicted_class) | |
print('Predicted:', decode_predictions(preds, top=1)[0]) | |
# Predicted: [(u'n02504013', u'Indian_elephant', 0.82658225), (u'n01871265', u'tusker', 0.1122357), (u'n02504458', u'African_elephant', 0.061040461)] | |
cam = generate_cam(img, model, predicted_class, 'res5c_branch2c') | |
## 최대값이 1이 되도록 normalize | |
cam = cam / cam.max() | |
cam = cam * 255 | |
print(cam) | |
cam = cv2.resize(cam, (img_width, img_height)) | |
cam = np.uint8(cam) | |
img = cv2.imread(img_path) | |
img = cv2.resize(img, (img_width, img_height)) | |
cv_cam = cv2.applyColorMap(cam, cv2.COLORMAP_JET) | |
fin = cv2.addWeighted(cv_cam, 0.7, img, 0.3, 0) | |
cv2.imshow('cam', cv_cam) | |
cv2.imshow('image', img) | |
cv2.imshow('cam on image', fin) | |
cv2.waitKey() | |
cv2.destroyAllWindows() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment