《Python深度学习》《卷积神经网络的可视化》精读
对于大多数深度学习模型,模型学到的表示都难以用人类可以理解的方式提取和呈现。但对于卷积神经网络来说,我们可以很容易第提取模型学习到的表示形式,并以此加深对卷积神经网络模型运作原理的理解。
这篇文章的内容参考了《python深度学习》的《卷积神经网络可视化》的内容,可以说是对其中内容的提炼,代码书中都有,其他博客有很多都提供了,因此我尽可能不把代码放进来,而是把内容关注在概念上。
这里主要介绍三种可视化形式:
- 可视化卷积神经网络的中间输出
- 可视化卷积神经网络的过滤器(或者说可视化卷积神经网络过滤器所匹配的图像)
- 可视化图像中类激活的热力图
可视化卷积神经网络的中间输出
首先导入一个训练好的简单卷积神经网络模型,该模型主要用于猫狗图像的二分类,模型的结构如下所示。
然后下面是用于实验的图片,我们将把这张图片作为输入传入到神经网络,并获取到8个中间层的特征图输出:
大概过程可以用下图表述:
我们先关注第一个中间层的第一张特征图。
第一层能够获取到32张 148*148 的矩阵,把其中第一个矩阵作为图片格式输出一下,能够得到下图表示,可以发现第一张特征图主要是关注猫的面部特征。(绿色越深的位置表示了特征图提取特征的位置)
然后我们把八个层的所有特征图都输出一遍:
最后得出结论:
- 在卷积神经网络的第一层,特征图几乎保留了原始图像的所有信息,除了个别图片只提取了猫的某个部位(比如第二排第一个的特征几乎只提取了猫耳朵的信息)
- 随着层数的加深,特征图变得越来越模糊抽象。图像变得模糊是因为我们对原图像进行了最大池化,丢掉了一些几乎无用的像素点。特征图因此也开始关注更加局部的特征,比如尖耳朵、圆脑袋、五官的分布位置等。
可视化卷积神经网络的过滤器
以深度卷积神经网络模型VGG16为例,第7个卷积层有256个3*3*3的过滤器,每个过滤器都应该存在一个最佳匹配的图像,或者说像素特征。
这里说明一下为什么选择VGG16,我自己尝试过采用自建的模型进行可视化,但由于自建模型卷积层数较低,而较低层的卷积核表述的信息很多都是颜色图像和一些简单纹理,并不是一个好的例子,因此采用较深的VGG16
我们可以通过梯度上升的方式来计算出近似最佳匹配的图像矩阵。整个计算过程如下图表述:
可以看一下初始点位X0的图像表示:
我们先关注一下第一个卷积核经过40次梯度下降所匹配的输入图像特征:
似乎 block3_conv1 的第一个卷积核响应的特征是波卡尔点图案,我们再分别输出三个卷积块的前60个卷积核所匹配的输入图像:
block1_conv1
block2_conv1
block3_conv1
block4_conv1
可以发现随着层数加深,过滤器所匹配的图像变得越来越复杂,表述的形状也越来越精细。
模型的第一层和第二层大多包含一些简单的方向边缘和颜色以及一些纹理,到了第三层,纹理变得更加精细丰富,而第四层则表示了类似于自然图像中的纹理:羽毛、眼睛、树叶等。
可视化类激活的热力图
可视化类激活的热力图能够在图像上标注特定的位置,以得知神经网络通过图像的哪个部分来进行决策。
比如下面这张热力图则表示神经网络对大象的分类中,是通过大象头部的特征进行了决策。
这种方式还可以用于调试神经网络的决策过程,比如神经网络分类出错了,可以通过这种方式去看看,神经网络是因关注了什么特征而导致了出错。
热力图显示的原理在书中解释的非常简略,这里我再进行一个详细的补充。
上述过程描述的是单张特征图的计算过程,但我们有512张特征图,因此整个过程还需要执行512次,拿到一个14*14*512的热力图处理后的特征图集合。
最终我们需要获取到一张14*14的热力图,因此我们对上述的14*14*512的特征图向量进行加权平均
这里简单放一下处理过程的代码:
# 拿到非洲象的后验概率输出,也就是Y
african_elephant_output = model.output[:, 386]
# 拿到最后一个卷积层的14*14*512特征图向量X
last_conv_layer = model.get_layer('block5_conv3')
# 求grads=dY/dX
grads = K.gradients(african_elephant_output, last_conv_layer.output)[0]
# 对grads进行加权平均
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])
# 分别对512层特征图都乘上对应grads的加权值
for i in range(512):
conv_layer_output_value[:, :, i] *= pooled_grads_value[i]
# 加权平均获取到14*14的热力图
heatmap = np.mean(conv_layer_output_value, axis=-1)
然后我们看一下处理之前的加权平均显示的feature_map:
处理之后的heat_map
可以发现,颜色鲜艳的区域变得更少更集中。然后通过opencv把heat_map的格式套在原图上,就能够得到对应位置的热力图了。