总结自《Python 深度学习》(François Chollet)第5章。
类激活图(CAM, class activation map),是指对输入图像生成类激活的热力图。类激活图是与特定输出类别相关的二维分数网络,对任何输入图像的每个位置都要进行计算,它表示每个位置对该类别的重要程度。它有助于了解一张图像的哪一部分让卷积神经网络做出了最终的分类决策,还可以定位图像中的特定目标。
我们将使用 Grad-CAM 算法来实现类激活。这种方法非常简单,给定一张输入图像,对于一个卷积层的输出的特征图,用类别相对于通道的梯度对这个特征图中的每个通道进行加权。直观上来看,我们是在用 每个通道对类别的重要程度 对 输入图像对不同通道的激活强度的空间图进行加权,从而得到了输入图像对类别的激活强度的空间图。
加载带有预训练权重的 VGG16 网络
from keras.applications.vgg16 import VGG16
model = VGG16(weights='imagenet')
下图是一只二哈。我们需要将这张图像转换为 VGG16 模型能够读取的格式:模型在大小为 224 × 224 的图像上进行训练,这些训练图像都根据 keras.applications.vgg16.preprocess_input
函数中内置的规则进行预处理。
预处理输入图像
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input, decode_predictions
import numpy as np
img_path = '../input/cats-and-dogs-small/catsANDdogs_small/train/dogs/dog.297.jpg'
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
现在我们可以在图像上运行预训练的 VGG16 网络,并将其预测向量解码为人类可读的格式:
preds = model.predict(x)
print('Predicted:', decode_predictions(preds, top=3)[0])
"""
Out: [('n02110185', 'Siberian_husky', 0.68843895),
('n02109961', 'Eskimo_dog', 0.29772884),
('n02110063', 'malamute', 0.013509828)]
"""
对这张图像预测的前三个类别分别为:
- 哈士奇,68.8% 的概率
- 爱斯基摩犬,30% 的概率
- 马拉摩特雪橇犬,1.3% 的概率
且二哈对应的编号为 250:
np.argmax(preds[0])
应用 Grad-CAM 算法
import tensorflow as tf
tf.compat.v1.disable_eager_execution()
from tensorflow.keras import backend as K
husky_output = model.output[:, 250]
last_conv_layer = model.get_layer('block5_conv3')
"""husky类别对于block5_conv3输出特征图的梯度"""
grads = K.gradients(husky_output, last_conv_layer.output)[0]
"""形状为(512,)的向量,每个元素是特定特征图通道的梯度平均大小"""
pooled_grads = K.mean(grads, axis=(0, 1, 2))
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):
"""将特征图组的每个通道乘以这个通道对husky类别的重要程度"""
conv_layer_output_value[:, :, i] *= pooled_grads_value[i]
heatmap = np.mean(conv_layer_output_value, axis=-1)
为了便于可视化,我们还需将热力图标准化到 0~1 范围内。结果如下图所示:
import matplotlib.pyplot as plt
heatmap = np.maximum(heatmap, 0)
heatmap /= np.max(heatmap)
plt.matshow(heatmap)
将热力图与原始图像叠加
import cv2
img = cv2.imread(img_path)
heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))
heatmap = np.uint8(255 * heatmap) # 将热力图转换为RGB格式
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
superimposed_img = heatmap * 0.4 + img # 0.4为热力图强度因子
cv2.imwrite('./heatmap.png', superimposed_img)
因为二哈与背景的颜色较为接近,结果图片在背景也有很多激活区域。大家可以自己换图片进行尝试。
References
《Python 深度学习》,François Chollet.