From 3cfc143df3923057a1a18f6c6ff2f9cfd21fdee4 Mon Sep 17 00:00:00 2001 From: CyberZHG <853842+CyberZHG@users.noreply.github.com> Date: Thu, 1 Oct 2020 23:52:13 +0800 Subject: [PATCH] Add Grad-CAM --- README.md | 45 +++++++++++++++- README.zh-CN.md | 46 +++++++++++++++- demo/grad_cam.py | 55 +++++++++++++++++++ keras_conv_vis/gradient.py | 79 +++++++++++++++++++++++----- requirements-dev.txt | 1 + samples/cat_grad-cam_irrelevant.jpg | Bin 0 -> 28838 bytes samples/cat_grad-cam_relevant.jpg | Bin 0 -> 32232 bytes tests/test_get_gradient.py | 11 +++- 8 files changed, 222 insertions(+), 15 deletions(-) create mode 100644 demo/grad_cam.py create mode 100644 samples/cat_grad-cam_irrelevant.jpg create mode 100644 samples/cat_grad-cam_relevant.jpg diff --git a/README.md b/README.md index 5c0667a..1461e48 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,11 @@ pip install git+https://github.com/cyberzhg/keras-conv-vis ``` +The codes only work when eager execution is enabled. + ## Guided Backpropagation -See [the paper](https://arxiv.org/pdf/1412.6806.pdf) and [demo](./demo/guided_backpropagation.py). +See [the paper](https://arxiv.org/pdf/1412.6806.pdf) and [demo](https://github.com/CyberZHG/keras-conv-vis/blob/master/demo/guided_backpropagation.py). ```python import keras @@ -47,3 +49,44 @@ visualization = Image.fromarray(gradient) | Gradient | | | Deconvnet without Pooling Switches | | | Guided Backpropagation | | + + +## Grad-CAM + +See [the paper](https://arxiv.org/abs/1610.02391) and [demo](https://github.com/CyberZHG/keras-conv-vis/blob/master/demo/grad_cam.py). + +```python +import keras +import numpy as np +import matplotlib.pyplot as plt +from PIL import Image + +from keras_conv_vis import split_model_by_layer, get_gradient, Categorical + +model = keras.applications.MobileNetV2() +# Split the model at the last convolutional layer and compute the intermediate result +head, tail = split_model_by_layer(model, 'Conv_1') +last_conv_output = head(inputs) +# Computer the gradient for the convolution +gradient_model = keras.models.Sequential() +gradient_model.add(tail) +gradient_model.add(Categorical(284)) # 284 is the siamese cat in ImageNet +gradients = get_gradient(gradient_model, last_conv_output) + +# Calculate Grad-CAM +gradient = gradients.numpy()[0] +gradient = np.mean(gradient, axis=(0, 1)) +grad_cam = np.mean(last_conv_output.numpy()[0] * gradient, axis=-1) +grad_cam = grad_cam * (grad_cam > 0).astype(grad_cam.dtype) + +# Visualization +grad_cam = (grad_cam - np.min(grad_cam)) / (np.max(grad_cam) - np.min(grad_cam) + 1e-4) +heatmap = plt.get_cmap('jet')(grad_cam, bytes=True) +heatmap = Image.fromarray(heatmap[..., :3], mode='RGB') +heatmap = heatmap.resize((original_image.width, original_image.height), resample=Image.BILINEAR) +visualization = Image.blend(original_image, heatmap, alpha=0.5) +``` + +| Input | Relevant CAM | Irrelevant CAM| +|:-:|:-:|:-:| +| | | | diff --git a/README.zh-CN.md b/README.zh-CN.md index 52a6bcc..b9417d0 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -13,9 +13,11 @@ pip install git+https://github.com/cyberzhg/keras-conv-vis ``` +只在启用eager execution的情况下可以使用。 + ## Guided Backpropagation -参考[论文](https://arxiv.org/pdf/1412.6806.pdf)和[样例](./demo/guided_backpropagation.py). +参考[论文](https://arxiv.org/pdf/1412.6806.pdf)和[样例](./demo/guided_backpropagation.py)。 ```python import keras @@ -47,3 +49,45 @@ visualization = Image.fromarray(gradient) | 梯度 | | | Deconvnet without Pooling Switches | | | Guided Backpropagation | | + + +## Grad-CAM + + +参考[论文](https://arxiv.org/abs/1610.02391)和[样例](https://github.com/CyberZHG/keras-conv-vis/blob/master/demo/grad_cam.py)。 + +```python +import keras +import numpy as np +import matplotlib.pyplot as plt +from PIL import Image + +from keras_conv_vis import split_model_by_layer, get_gradient, Categorical + +model = keras.applications.MobileNetV2() +# 将模型从最后一个卷积处分开,计算出中间结果 +head, tail = split_model_by_layer(model, 'Conv_1') +last_conv_output = head(inputs) +# 给最后一个卷积计算梯度 +gradient_model = keras.models.Sequential() +gradient_model.add(tail) +gradient_model.add(Categorical(284)) # ImageNet第284类是暹罗猫 +gradients = get_gradient(gradient_model, last_conv_output) + +# 计算Grad-CAM +gradient = gradients.numpy()[0] +gradient = np.mean(gradient, axis=(0, 1)) # 根据梯度计算每一层输出的权重 +grad_cam = np.mean(last_conv_output.numpy()[0] * gradient, axis=-1) # 对卷积输出进行加权求和 +grad_cam = grad_cam * (grad_cam > 0).astype(grad_cam.dtype) # Grad-CAM输出需要经过ReLU + +# 可视化 +grad_cam = (grad_cam - np.min(grad_cam)) / (np.max(grad_cam) - np.min(grad_cam) + 1e-4) +heatmap = plt.get_cmap('jet')(grad_cam, bytes=True) +heatmap = Image.fromarray(heatmap[..., :3], mode='RGB') +heatmap = heatmap.resize((original_image.width, original_image.height), resample=Image.BILINEAR) +visualization = Image.blend(original_image, heatmap, alpha=0.5) +``` + +| Input | Relevant CAM | Irrelevant CAM| +|:-:|:-:|:-:| +| | | | diff --git a/demo/grad_cam.py b/demo/grad_cam.py new file mode 100644 index 0000000..8726528 --- /dev/null +++ b/demo/grad_cam.py @@ -0,0 +1,55 @@ +import os + +from PIL import Image +import numpy as np +import matplotlib.pyplot as plt + +from keras_conv_vis import split_model_by_layer, get_gradient, Categorical +from keras_conv_vis.backend import keras + +CLASS_CAT = 284 +CLASS_GUITAR = 546 + +# Load an image +current_path = os.path.dirname(os.path.realpath(__file__)) +sample_path = os.path.join(current_path, '..', 'samples') +image_path = os.path.join(sample_path, 'cat.jpg') +original_image = Image.open(image_path) +image = original_image.resize((224, 224)) +inputs = np.expand_dims(np.array(image).astype(np.float) / 255.0, axis=0) +inputs = inputs * 2.0 - 1.0 + + +def process(target_class, + cmap='jet', + alpha=0.5): + # Build model and get gradients + model = keras.applications.MobileNetV2() + head, tail = split_model_by_layer(model, 'Conv_1') + last_conv_output = head(inputs) + gradient_model = keras.models.Sequential() + gradient_model.add(tail) + gradient_model.add(Categorical(target_class)) + gradients = get_gradient(gradient_model, last_conv_output) + + # Calculate Grad-CAM + gradient = gradients.numpy()[0] + gradient = np.mean(gradient, axis=(0, 1)) + grad_cam = np.mean(last_conv_output.numpy()[0] * gradient, axis=-1) + grad_cam = grad_cam * (grad_cam > 0).astype(grad_cam.dtype) + + # Visualization + grad_cam = (grad_cam - np.min(grad_cam)) / (np.max(grad_cam) - np.min(grad_cam) + 1e-4) + heatmap = plt.get_cmap(cmap)(grad_cam, bytes=True) + heatmap = Image.fromarray(heatmap[..., :3], mode='RGB') + heatmap = heatmap.resize((original_image.width, original_image.height), resample=Image.BILINEAR) + return Image.blend(original_image, heatmap, alpha=alpha) + + +for target_class in [CLASS_CAT, CLASS_GUITAR]: + visualization = process(target_class) + cat_name = 'relevant' + if target_class != CLASS_CAT: + cat_name = 'irrelevant' + save_name = f'cat_grad-cam_{cat_name}.jpg' + visualization.save(os.path.join(sample_path, save_name)) diff --git a/keras_conv_vis/gradient.py b/keras_conv_vis/gradient.py index dc3abd4..e2af4ce 100644 --- a/keras_conv_vis/gradient.py +++ b/keras_conv_vis/gradient.py @@ -5,7 +5,7 @@ from .backend import keras -__all__ = ['Categorical', 'get_gradient'] +__all__ = ['Categorical', 'get_gradient', 'split_model_by_layer'] class Categorical(keras.layers.Layer): @@ -27,27 +27,82 @@ def get_config(self): return dict(list(base_config.items()) + list(config.items())) -def get_gradient(model: keras.models.Model, +def get_gradient(model: Union[keras.models.Model, List[keras.models.Model]], inputs: Union[np.ndarray, tf.Tensor, List[Union[np.ndarray, tf.Tensor]]], targets: Optional[Union[tf.Tensor, List[tf.Tensor]]] = None): + """Get the gradient of input, weights, of intermediate outputs. + + :param model: The keras model. + :param inputs: The batched input data. + :param targets: The default is the input tensor. + :return: The gradients of the targets. + """ + models = model + if not isinstance(model, list): + models = [model] if not isinstance(inputs, list): inputs = [inputs] for i, input_item in enumerate(inputs): if isinstance(input_item, np.ndarray): inputs[i] = tf.convert_to_tensor(input_item) + if len(inputs) == 1: + inputs = inputs[0] + input_targets = targets if targets is None: - target_tensors = inputs - else: - target_tensors = [] - if not isinstance(targets, list): - targets = [targets] - for target in targets: - target_tensors.append(target) + targets = [] + if not isinstance(targets, list): + targets = [targets] + model_output = inputs with tf.GradientTape() as tape: - for target in target_tensors: + for target in targets: tape.watch(target) - model_output = model(inputs) - gradients = tape.gradient(model_output, target_tensors) + for i, model in enumerate(models): + if input_targets is None: + targets.append(model_output) + tape.watch(model_output) + model_output = model(model_output) + gradients = tape.gradient(model_output, targets) if len(gradients) == 1: gradients = gradients[0] return gradients + + +def split_model_by_layer(model: keras.models.Model, + layer_cut: Union[str, keras.layers.Layer]): + """Split a model into two parts. + + :param model: The keras model. + :param layer_cut: The layer whose output will be cut. The layer must be a cut point, + a.k.a., the output edge is a bridge. + :return: The two models. + """ + if isinstance(layer_cut, str): + layer_cut = model.get_layer(layer_cut) + head = keras.models.Model(model.inputs, layer_cut.output) + meet = False + mappings, depends = {}, set() + for layer in model.layers: + if layer_cut is layer: + meet = True + mappings[layer.name] = keras.layers.Input(layer.output.shape[1:]) + depends.add(layer.name) + continue + if not meet: + continue + inputs = layer.input + if not isinstance(inputs, list): + inputs = [inputs] + new_inputs = [] + for input_tensor in inputs: + name = input_tensor.name.rsplit('/')[0] + depends.add(name) + new_inputs.append(mappings[name]) + if not isinstance(layer.input, list): + new_inputs = new_inputs[0] + mappings[layer.name] = layer(new_inputs) + outputs = [] + for layer in model.layers: + if layer.name in mappings and layer.name not in depends: + outputs.append(mappings[layer.name]) + tail = keras.models.Model(mappings[layer_cut.name], outputs) + return head, tail diff --git a/requirements-dev.txt b/requirements-dev.txt index 1f22a48..dcd634e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,3 +6,4 @@ nose pycodestyle coverage Pillow +matplotlib diff --git a/samples/cat_grad-cam_irrelevant.jpg b/samples/cat_grad-cam_irrelevant.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d9b067cca0b16af12863919834df0c0212d0db29 GIT binary patch literal 28838 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

S}@sA2ryw4aExHWn+Z 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 U3NZ?B;8TW%$@H3$W8WVCFFq$wRsaA1 literal 0 HcmV?d00001 diff --git a/samples/cat_grad-cam_relevant.jpg b/samples/cat_grad-cam_relevant.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9358e1cc7ee80cef1f45022a149cdd8ab27ac496 GIT binary patch literal 32232 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¥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 literal 0 HcmV?d00001 diff --git a/tests/test_get_gradient.py b/tests/test_get_gradient.py index 31c93be..e2f87a3 100644 --- a/tests/test_get_gradient.py +++ b/tests/test_get_gradient.py @@ -2,7 +2,7 @@ import numpy as np -from keras_conv_vis import get_gradient, Categorical +from keras_conv_vis import get_gradient, Categorical, split_model_by_layer from keras_conv_vis.backend import keras @@ -17,3 +17,12 @@ def test_get_gradient(self): get_gradient(gradient_model, np.random.random((1, 224, 224, 3))) get_gradient(gradient_model, np.random.random((1, 224, 224, 3)), targets=model.get_layer('bn_Conv1').trainable_weights[0]) + + def test_cut_model(self): + model = keras.applications.MobileNetV2() + head, tail = split_model_by_layer(model, 'block_5_add') + gradient_model = keras.models.Sequential() + gradient_model.add(tail) + gradient_model.add(Categorical(7)) + gradients = get_gradient([head, gradient_model], np.random.random((1, 224, 224, 3))) + self.assertEqual(2, len(gradients))