Path: blob/master/keras/resnet_cam/explainer.py
2595 views
import numpy as np1from scipy.ndimage import zoom2from keras.models import Model3from keras.applications.resnet50 import preprocess_input4from keras.preprocessing.image import load_img, img_to_array567class CAMExplainer:8"""CAM (Class Activation Map) Explainer"""910def __init__(self, model, target_size=(224, 224)):11self.model = model12self.target_size = target_size1314self.class_weights_ = None15self.resnet50_cam_layers_ = None1617def fit(self, img_path):18if self.class_weights_ is None:19self.resnet50_cam_layers_, self.class_weights_ = self._get_resnet50_cam_info()2021self.img_, keras_img = self._read_and_process_img(img_path)22self.cam_, self.predicted_class_ = self._create_cam(keras_img)23return self2425def plot(self):26import matplotlib.pyplot as plt27fig, ax = plt.subplots()2829ax.imshow(self.img_, alpha=0.5)30ax.imshow(self.cam_, cmap='jet', alpha=0.5)31plt.show()3233def _create_cam(self, img):34# before_gap_output will be of shape [1, 7, 7, 2048] for resnet5035before_gap_output, prediction = self.resnet50_cam_layers_.predict(img)36img_width = before_gap_output.shape[1]37img_height = before_gap_output.shape[2]38n_activation = before_gap_output.shape[3]3940predicted_class = np.argmax(prediction)41dominate_class_weight = self.class_weights_[:, predicted_class]4243# we resize the shape of the activation so we can perform a dot product with44# the dominated class weight45before_gap_output = np.squeeze(before_gap_output).reshape((-1, n_activation))46cam = np.dot(before_gap_output, dominate_class_weight).reshape((img_width, img_height))4748# we reshape it back to the target image size49# so we can overlay the class activation map on top of our image later50# order 1 = bi-linear interpolation was fast enough for this use-case51width_scale_ratio = self.target_size[0] // img_width52height_scale_ratio = self.target_size[1] // img_height53cam = zoom(cam, (width_scale_ratio, height_scale_ratio), order=1)54return cam, predicted_class5556def _read_and_process_img(self, img_path):57"""58Reads in a single image, resize it to the specified target size59and performs the same preprocessing on the image as the original60pre-trained model.61"""62img = load_img(img_path, target_size=self.target_size)63img_arr = img_to_array(img)6465# keras works with batches of images, since we only have 1 image66# here, we need to add an additional dimension to turn it into67# shape [samples, size1, size2, channels]68keras_img = np.expand_dims(img_arr, axis=0)6970# different pre-trained model preprocess the images differently71# we also preprocess our images the same way to be consistent72keras_img = preprocess_input(keras_img)73return img, keras_img7475def _get_resnet50_cam_info(self):76# we need the output of the activation layer right before the77# global average pooling (gap) layer and the last dense/softmax78# layer that generates the class prediction79before_gap_layer = self.model.layers[-4]80class_pred_layer = self.model.layers[-1]8182outputs = before_gap_layer.output, class_pred_layer.output83resnet50_cam_layers = Model(inputs=self.model.input, outputs=outputs)8485# only access the first element of weights, we won't be needing the bias term here86class_weights = class_pred_layer.get_weights()[0]87return resnet50_cam_layers, class_weights888990