【Deep Learning with Python】C5 热力图-感兴趣区域可视化
什么用以及是什么
这个比较难以理解,尤其是实现部分的代码,因为需要对卷积核有更进一步的理解。
- 目的
- 我想知道这么多个filter(通道,卷积核,下文不做区分),哪几个更重要,我想知道它们对于结果的贡献
- 我想知道输入的一个数据案例中哪一部分更加重要
- 原理简图
- 输入对每一个通道进行求导,得到输出对各个通道梯度,可以视为通道对结果的贡献权重
- 让所有通道 * 权重,权重越大,通道输出就会放大,反之,通道输出就会缩小
- 全通道叠加,得到14 * 14的统一通道,通道上每个位置都是各个通道的加权和
基本步骤就是上述,其实主要是理解两点:
- 想知道某个东西对结果的影响权重,那就对它求导就可以,导数的意义就在这里
- 得到各个通道的权重,让所有通道输出加权和即可
代码和注释
下面代码中关键的输出都注释了,理解需要。
african_elephant_output = model.output[:, 386]
# <tf.Tensor 'strided_slice_2:0' shape=(?,) dtype=float32>
# model.output <tf.Tensor 'predictions/Softmax:0' shape=(?, 1000) dtype=float32>
last_conv_layer = model.get_layer('block5_conv3')
from keras import backend as K
# 求最终预测值对卷积层最后一层输出的导数, 因为输出是一个列表,所以去列表化
grads = K.gradients(african_elephant_output, last_conv_layer.output)[0]
# grads = <tf.Tensor 'gradients_2/block5_pool/MaxPool_grad/MaxPoolGrad:0' shape=(?, 14, 14, 512) dtype=float32>
# 这和求导对象的大小是一样的,可以理解为,对512个通道(filter)求导
# african_elephant_output 可以说是一个数
# 求得512个通道各自的梯度平均值
pooled_grads = K.mean(grads, axis=(0, 1, 2))
# grads = <tf.Tensor 'gradients_2/block5_pool/MaxPool_grad/MaxPoolGrad:0' shape=(?, 14, 14, 512) dtype=float32>
# pooled_grads = <tf.Tensor 'Mean:0' shape=(512,) dtype=float32>
# 求平均后从上面变为下面,其实就是求512个通道导数的均值
# Multiplies each channel in the feature-map array by“how important this channel is” with regard to the“elephant” class
# 其实就是对512个通道求导,然后该通道越重要,导数越大,这个导数相当于该层次的权重
# 下面这么一乘,越重要的会越大,越不重要的只会越小,相当于对各个channel按梯度的结果加权
for i in range(512):
conv_layer_output_value[:, :, i] *= pooled_grads_value[i]
# 已经得到了加权后的层次输出,现在对14*14上的512个值求平均
# ps mean的用法,不需要哪几个维度就输入哪几个维度
heatmap = np.mean(conv_layer_output_value, axis=-1)
# conv_layer_output_value (14, 14, 512)
# heatmap.shape (14, 14)
import matplotlib.pyplot as plt
# x = max(0, x)
heatmap = np.maximum(heatmap, 0)
# 除最大值归一化
heatmap /= np.max(heatmap)
plt.matshow(heatmap)
# 把上述得到的heatmap拉升,和原图合成,用到了openCV库
import cv2
img = cv2.imread(img_path)
heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))
heatmap = np.uint8(255 * heatmap)
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
superimposed_img = heatmap * 0.4 + img
cv2.imwrite('elephant_cam.jpg', superimposed_img)
注意,上述将一个14*14的合成filter,直接拉升到原图中,这是可行的,说明一个问题——卷积核虽然是每次处理局部,但最终结果确是针对全局。
我觉得其中最重要的是,我能够知道模型抽取了哪些的特征,哪些特征更重要,分别多重要,即每个特征的权重。