Keras中可视化卷积层的类激活热力图
C小C 2018-12-26 17:05:12 5546 收藏 15
展开
【时间】2018.12.26
【题目】Keras中可视化卷积层的类激活热力图
概述
本文是对《Deep Learning with python》一书中第5.4.3节Keras中可视化类激活的热力图的整理与总结,参考链接:https://blog.csdn.net/einstellung/article/details/82858974
一、什么是类激活图(CAP)?
类激活图(CAM,class activation map)是与特定输出类别相关的二维分数网格,对于输入图像的每个位置进行计算,它表示每个位置对该类别的重要程度。
二、类激活图可视化
类激活图可视化有助于了解一张图像的哪一部分让卷积神经网络做出了最终的分类决策。这有助于对卷积神经网络的决策过程进行调试,特别是分类错误的情况下。同时,这种方法可以定位图像中的特定目标。这里,我们介绍的具体实现方式是“Grad-CAM:visual explanations from deep networks via gradiaent-based localization',它是在2017年提出来的,具体实现方法是:给定一张输入图像,对于一个卷积层的输出特征图,用类别相对于每一个通道的梯度对这个特征图中的每个通道进行加权。
三、使用VGG16演示类激活图可视化
3.1 加载预训练的VGG-16:
from keras.applications.vgg16 import VGG16
model = VGG16(weights='imagenet')
model.summary()
【VGG-16】模型结构如下:最后输出的是1000维的向量,表示属于1000个类别的概率
3.2 载入输入图像及预处理
如图所示,这是两只非洲象的图片。我们将这张图片转换为VGG16能够读取的格式:大小为224X224的图像,这些训练图像都根据keras.applications.vgg16.preprocess_input函数中的内置的规则进行预处理。因此,我们需要加载图像,将其大小调整为224X224,然后将其转化为float32格式的Numpy张量,并应用这些预处理规则。
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input, decode_predictions
import numpy as np
img_path = '/Users/fchollet/Downloads/creative_commons_elephant.jpg'
img = image.load_img(img_path, target_size=(224, 224)) # 大小为224*224的Python图像库图像
x = image.img_to_array(img) # 形状为(224, 224, 3)的float32格式Numpy数组
x = np.expand_dims(x, axis=0) # 添加一个维度,将数组转化为(1, 224, 224, 3)的形状批量
x = preprocess_input(x) #按批量进行预处理(按通道颜色进行标准化)
这时,可以在图像上运行预训练的VGG16网络,并将预测向量解码为我们可以读的形式。
【代码】
preds = model.predict(x)
print('Predicted:', decode_predictions(preds, top=3)[0])
【运行结果】:
Predicted: [(‘n02504458’, ‘African_elephant’, 0.90942144),
(‘n01871265’, ‘tusker’, 0.08618243),
(‘n02504013’, ‘Indian_elephant’, 0.0043545929)]
对这个图像预测的前三个类别分别是:
非洲象:92.5%的概率
长牙动物:7%的概率
印度象:0.4%的概率
网络认为预测向量中最大激活的元素对应是“非洲象”类别的元素,索引编号386
np.argmax(preds[0])
386
3.3. 应用Grad-CAM算法
african_elephant_output = model.output[:, 386] # 预测向量中的非洲象元素
last_conv_layer = model.get_layer('block5_conv3') # block5_conv3层的输出特征图,它是VGG16的最后一个卷积层
grads = K.gradients(african_elephant_output, last_conv_layer.output)[0] # 非洲象类别相对于block5_conv3输出特征图的梯度
pooled_grads = K.mean(grads, axis=(0, 1, 2)) # 形状是(512, )的向量,每个元素是特定特征图通道的梯度平均大小
iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]]) # 这个函数允许我们获取刚刚定义量的值:对于给定样本图像,pooled_grads和block5_conv3层的输出特征图
pooled_grads_value, conv_layer_output_value = iterate([x]) # 给我们两个大象样本图像,这两个量都是Numpy数组
for i in range(512):
conv_layer_output_value[:, :, i] *= pooled_grads_value[i] # 将特征图数组的每个通道乘以这个通道对大象类别重要程度
heatmap = np.mean(conv_layer_output_value, axis=-1) # 得到的特征图的逐通道的平均值即为类激活的热力图
【注意】
K.gradients(y,x)用于求y关于x 的导数(梯度),(y和x可以是张量tensor也可以是张量列表,形如 [tensor1, tensor2, …, tensorn]),返回的是一个张量列表,列表长度是张量列表y的长度,列表元素是与x具有一样shape的张量。在本例中,grads = K.gradients(african_elephant_output, last_conv_layer.output)[0],african_elephant_output是(1,)张量,last_conv_layer.output是(1,7,7,512)张量,所以返回的是只有一个元素的(1,7,7,512)张量列表,grads取了列表中唯一一个元素。
3.4 热力图后处理
为了便于可视化,我们需要将热力图标准化到0~1范围内,如下。
heatmap = np.maximum(heatmap, 0) # heatmap与0比较,取其大者
heatmap /= np.max(heatmap)
plt.matshow(heatmap)
plt.show()
【结果】
3.5 将热力图与原始图叠加,实现可视化
最后,我们可以用OpenCV来生成一张图像,将原始图像叠加在刚刚得到的热力图上
import cv2
img = cv2.imread(img_path) # 用cv2加载原始图像
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('/Users/fchollet/Downloads/elephant_cam.jpg', superimposed_img) # 将图像保存到硬盘
【注意】cv2.applyColorMap()是opencv中的伪彩色函数,用于画色度图(colarmap),具体可看:opencv中伪彩色applyColorMap函数
【最终结果】
四、代码汇总
from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input, decode_predictions
from keras import backend as K
import numpy as np
import cv2
model = VGG16(weights='imagenet')
model.summary()
img_path = '/Users/fchollet/Downloads/creative_commons_elephant.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)
african_elephant_output = model.output[:, 386]
last_conv_layer = model.get_layer('block5_conv3')
grads = K.gradients(african_elephant_output, last_conv_layer.output)[0]
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):
conv_layer_output_value[:, :, i] *= pooled_grads_value[i]
heatmap = np.mean(conv_layer_output_value, axis=-1)
heatmap = np.maximum(heatmap, 0) # heatmap与0比较,取其大者
heatmap /= np.max(heatmap)
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('/Users/fchollet/Downloads/elephant_cam.jpg', superimposed_img)
————————————————
版权声明:本文为CSDN博主「C小C」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/C_chuxin/java/article/details/85265082