卷积神经网络(Convolutional Neural Network, CNN)是一种前馈神经网络,它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出色表现。通过卷积、池化、激活等操作的配合,卷积神经网络能够较好的学习到空间上关联的特征。以VGG16为例,我们将利用Keras观察CNN到底在学些什么,它是如何理解我们送入的训练图片的。
github:https://github.com/xiaochus/VisualizationCNN
参考:Keras,deep-learning-with-python-notebooks
主要的四种可视化模式:
1. 卷积核输出的可视化,即可视化卷积操作后的结果,帮助理解卷积核的作用。
2. 卷积核的可视化,对卷积核本身进行可视化,对卷积核学习到的行为进行解释。
3. 类激活图可视化,通过热度图,了解图像分类问题中图像哪些部分起到了关键作用,同时可以定位图像中物体的位置。
4. 特征可视化,与第一种方法类似,但是输出的不再是卷积层的激活值,而是使用反卷积与反池化来可视化输入图像的激活特征。
环境
- Python 3.6
- Keras 2.2.2
- Tensorflow-gpu 1.8.0
- OpenCV 3.4
卷积输出可视化
卷积输出可视化可以通过将不同的卷积层设置为输出层来实现,如下所示:
def conv_output(model, layer_name, img):
"""Get the output of conv layer.
Args:
model: keras model.
layer_name: name of layer in the model.
img: processed input image.
Returns:
intermediate_output: feature map.
"""
# this is the placeholder for the input images
input_img = model.input
try:
# this is the placeholder for the conv output
out_conv = model.get_layer(layer_name).output
except:
raise Exception('Not layer named {}!'.format(layer_name))
# get the intermediate layer model
intermediate_layer_model = Model(inputs=input_img, outputs=out_conv)
# get the output of intermediate layer model
intermediate_output = intermediate_layer_model.predict(img)
return intermediate_output[0]
我们对四个block下不同的卷积层输出进行了可视化处理,可视化结果如下所示。可以看出浅层的卷积层获得的特征数量还比较多,特征数据与原始的图像数据很接近。随着层数越深,得到的有效特征越来越少,特征也变得越来越抽象。
conv_block1_conv1 conv_block2_conv2 conv_block3_conv3 conv_block4_conv3卷积核可视化
解释CNN模型的另一个简单方法是显示每个卷积核响应的视觉模式, 卷积核可视化通过输入空间中的梯度上升来完成。
卷积核可视化的过程:我们要定义一个损失函数,这个损失函数将用于最大化某个指定滤波器的激活值。以该函数为优化目标优化后,后我们将使用随机梯度下降来调整输入图像的值,以便最大化该激活值。
def conv_filter(model, layer_name, img):
"""Get the filter of conv layer.
Args:
model: keras model.
layer_name: name of layer in the model.
img: processed input image.
Returns:
filters.
"""
# this is the placeholder for the input images
input_img = model.input
# get the symbolic outputs of each "key" layer (we gave them unique names).
layer_dict = dict([(layer.name, layer) for layer in model.layers[1:]])
try:
layer_output = layer_dict[layer_name].output
except:
raise Exception('Not layer named {}!'.format(layer_name))
kept_filters = []
for i in range(layer_output.shape[-1]):
loss = K.mean(layer_output[:, :, :, i])
# compute the gradient of the input picture with this loss
grads = K.gradients(loss, input_img)[0]
# normalization trick: we normalize the gradient
grads = utils.normalize(grads)
# this function returns the loss and grads given the input picture
iterate = K.function([input_img], [loss, grads])
# step size for gradient ascent
step = 1.
# run gradient ascent for 20 steps
fimg = img.copy()
for j in range(40):
loss_value, grads_value = iterate([fimg])
fimg += grads_value * step
# decode the resulting input image
fimg = utils.deprocess_image(fimg[0])
kept_filters.append((fimg, loss_value))
# sort filter result
kept_filters.sort(key=lambda x: x[1], reverse=True)
return np.array([f[0] for f in kept_filters])
可视化结果如下所示。可以看出最浅层的卷积核倾向于学习点、颜色等基础特征;接下来开始学习到线段、边缘等特征。层数越深,学习到的特征就越具体越抽象。
filter_block1_conv1 filter_block2_conv2 filter_block3_conv3 filter_block4_conv3类激活图可视化
类激活图(CAM)可视化,包括在输入图像上产生类激活的热图。 类激活热图是与特定输出类相关联的分数的2D网格,针对任何输入图像中的每个位置计算,指示每个位置相对于所考虑的类的重要程度。例如,给定一个图像输入我们的“猫与狗”之一,类激活图可视化允许我们为类“猫”生成热图,指示图像的猫状不同部分是如何,同样对于“狗”类,表示图像的狗状不同部分。换句话时候,假设最后一层卷积层有512个卷积核,我想了解这512个卷积核对该图片是”猫”分别投了几票。投票越多的卷积核,就越确信图片是“猫”,因为它们提取到的特征趋向猫的特征。
CAM的原理:它包括在给定输入图像的情况下获取卷积层的输出特征图,并通过目标类相对于通道的梯度对该特征图中的每个通道进行加权。简单的讲,我们获取到最后一个卷积层的卷积输出以及目标类别神经元相对于每一个通道的梯度,使用这个梯度对卷积核的每一个通道进行加权处理,最后对通道求均值并且对重要度进行归一化处理。
def output_heatmap(model, last_conv_layer, img):
"""Get the heatmap for image.
Args:
model: keras model.
last_conv_layer: name of last conv layer in the model.
img: processed input image.
Returns:
heatmap: heatmap.
"""
# predict the image class
preds = model.predict(img)
# find the class index
index = np.argmax(preds[0])
# This is the entry in the prediction vector
target_output = model.output[:, index]
# get the last conv layer
last_conv_layer = model.get_layer(last_conv_layer)
# compute the gradient of the output feature map with this target class
grads = K.gradients(target_output, last_conv_layer.output)[0]
# mean the gradient over a specific feature map channel
pooled_grads = K.mean(grads, axis=(0, 1, 2))
# this function returns the output of last_conv_layer and grads
# given the input picture
iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])
pooled_grads_value, conv_layer_output_value = iterate([img])
# We multiply each channel in the feature map array
# by "how important this channel is" with regard to the target class
for i in range(conv_layer_output_value.shape[-1]):
conv_layer_output_value[:, :, i] *= pooled_grads_value[i]
# The channel-wise mean of the resulting feature map
# is our heatmap of class activation
heatmap = np.mean(conv_layer_output_value, axis=-1)
heatmap = np.maximum(heatmap, 0)
heatmap /= np.max(heatmap)
return heatmap
可视化结果如下所示。可以看出对于这张猫的图片,头部耳朵贡献了最重要的特征,其次就是爪子,最后是身体部分。
heatmap特征可视化
特征可视化主要使用目标卷积层的输出作为输入,利用反卷积、反池化、反激活等方法得到反向操作的结果,用以验证显示各层提取到的特征图。由于Keras还没有提供反池化的方法,这里提供一些相关的论文与工具作为参考。
paper:
Visualizing and Understanding Convolutional Networks
Understanding Neural Networks Through Deep Visualization
tools:
DeepVis Toolbox
tf_cnnvis
网络的整个过程,从右边开始:输入图片→卷积→Relu→最大池化→得到结果特征图→反池化→Relu→反卷积。
对于反卷积过程,这里不用进行训练而是采用卷积过程转置后的滤波器(参数一样,把参数矩阵水平和垂直方向翻转了一下)。
网友评论