keras CNN卷积核可视化,热度图教程

1. 前言

卷积神经网络(Convolutional Neural Network,简称CNN)是将原始数据作为输入,通过一系列卷积操作和池化操作得到高层次的抽象特征表示,并进行全连接之后输出分类结果的一种神经网络结构。在实际应用中,CNN被广泛用于图像分类、物体检测、人脸识别等领域。本文将介绍如何使用Keras库对CNN卷积核进行可视化,并在此基础上讲解如何生成热度图,以此增强我们对卷积神经网络的理解。

2. CNN卷积核可视化

2.1 卷积神经网络

在介绍CNN卷积核可视化之前,我们先来回顾一下CNN的基本结构。CNN主要由卷积层、池化层和全连接层构成,其中卷积层是最核心的部分。卷积层中的每个神经元都看作一个线性分类器,它对输入的局部数据(也就是感受野)进行卷积操作得到一个输出。换句话说,卷积层的每个神经元都可以看作是一个滤波器,它尝试寻找输入中的某些特定特征。下面是一个三层CNN的结构示意图:

其中,卷积层输出的是一个三维矩阵,它有宽、高、深度三个维度,分别表示卷积核在宽、高两个方向上的移动步长和输入数据的深度。而池化层则是对卷积层输出的特征图进行降维处理,减少模型参数的数量,从而加快计算速度并防止过拟合。最后是全连接层,它的作用是将前面所有层的输出压缩成一个一维向量,并且学习将这个向量映射到不同类别的概率。

2.2 可视化方法

在卷积神经网络中,每个卷积层都会包含若干个卷积核。这些卷积核就是用来寻找图像中的不同特征的,比如边缘、纹理、颜色等等。卷积核的形状可以是任意的,但通常都是正方形或者长方形。它由若干个滤波器组成,每个滤波器都可以检测出图像中的某个特定特征。在CNN中,我们可以将卷积核看作是一个空间特征提取器,它学习了对输入数据的一种表达方式。

然而,卷积核的内容是非常难以理解的。因为卷积核不同的滤波器所检测的特征是不同的,而卷积结果却很难直观地理解。因此,我们需要一种方法来可视化卷积核中所学到的特征。

目前主要有两种方法可以对卷积核进行可视化:

直接输出卷积核中的权重值

使用梯度下降的方法,找到一张能够激活某一个特定卷积核的图像,并将其显示出来

这里我们将使用第二种方法来对卷积核进行可视化。我们可以使用Keras提供的函数,根据某一个给定的卷积核的激活值来生成一张输入图片,使得该卷积核对于该图片的响应最大。具体来说,我们需要定义一个损失函数,使得最小化这个损失函数的过程可以生成一张和某一个给定卷积核最匹配的图像。这种方法的优点是可以直观地表示出卷积核中所学习到的特征。

下面是一个用于找到最能激活某一个特定卷积核的图像的函数实现:

from keras import backend as K

def generate_pattern(model, layer_name, filter_index, size=150):

layer_output = model.get_layer(layer_name).output

loss = K.mean(layer_output[:, :, :, filter_index])

grads = K.gradients(loss, model.input)[0]

grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)

iterate = K.function([model.input], [loss, grads])

input_img_data = np.random.random((1, size, size, 3)) * 20 + 128.

step = 1.

for i in range(40):

loss_value, grads_value = iterate([input_img_data])

input_img_data += grads_value * step

img = input_img_data[0]

return img

函数传入参数为神经网络模型、层名称、目标滤波器的编号和生成图片的大小。我们先创建一个损失函数,该损失函数的值等于目标卷积层中某一个滤波器的激活值。接着对于该损失函数求导,这里我们采用L2正则化来防止图像过度噪声化。这一步得到的梯度向量就是输入图像中对于该滤波器激活值的相对重要性。最后,我们通过一定步数的梯度下降来最大化这个损失函数,得到一张最能激活目标滤波器的图像。

我们同样可以可视化某一个卷积层中的所有滤波器,下面是一个可视化函数的实现:

def visualize_layer(model, layer_name, size=150, n_cols=16, margin=5):

n_filters = model.get_layer(layer_name).output_shape[-1]

n_rows = n_filters // n_cols

results = np.zeros((n_rows * size + (n_rows - 1) * margin,

n_cols * size + (n_cols - 1) * margin,

3))

for row in range(n_rows):

for col in range(n_cols):

filter_index = row * n_cols + col

if filter_index < n_filters:

filter_img = generate_pattern(model, layer_name, filter_index, size=size)

filter_img -= filter_img.mean()

filter_img /= (filter_img.std() + 1e-5)

filter_img *= 0.1

filter_img += 0.5

filter_img = np.clip(filter_img, 0, 1)

x = row * size + row * margin

y = col * size + col * margin

results[x:x + size, y:y + size, :] = filter_img

plt.figure(figsize=(20, 20))

plt.imshow(results)

plt.show()

函数传入参数为神经网络模型、目标层的名称、生成图片的大小、每一行列数和图片之间的间隙大小。该函数的核心是在一个二维的图像空间中绘制所有的滤波器。对于每一个滤波器,我们都使用前面介绍的方法生成一张最能激活该滤波器的图像,并把它们排列在一个矩阵中展示出来。

下面我们来看一下在Keras中如何可视化卷积核。我们先下载并加载一个图片数据集:

from keras.applications import VGG16

from keras.preprocessing.image import load_img, img_to_array

import matplotlib.pyplot as plt

model = VGG16(weights='imagenet', include_top=False)

img_path = 'image.jpg'

img = load_img(img_path, target_size=(224, 224))

img_tensor = img_to_array(img)

img_tensor = np.expand_dims(img_tensor, axis=0)

img_tensor = preprocess_input(img_tensor)

我们使用了Keras的一个经典模型VGG16。然后加载一张我们要展示的图片,并将其转换成一个张量方便模型处理。

接着我们可以调用之前编写的可视化函数来生成图片:

visualize_layer(model, 'block1_conv1')

函数传入参数为我们刚才加载的模型,目标层的名称。然后我们就可以看到生成的图片了:

在这个图片中,我们可以看到第一层卷积核所学习到的一些特征,比如垂直边缘、水平边缘、对角边缘等等。这些特征都是在神经网络的训练过程中自动学习到的,而不是通过手动的方式设计得到的。

3. 热度图生成

3.1 热度图介绍

在分类问题中,神经网络经常会输出类别的概率分布。然而,这些概率分布通常并不是直接可视化的。为了直观地表示这些概率分布,我们需要使用一种被称为热度图(Heatmap)的可视化方式。热度图一般是一张二维的图像,在该图像中每个像素的颜色表示该位置对应的数值大小。

举个例子来说,我们现在有一个模型能够预测出不同区域的犯罪率,我们可以利用生成的热度图来增强我们对这些数据的理解。

下面的代码展示了如何生成一个热度图:

from keras.preprocessing import image

img_path = 'image.jpg'

img = load_img(img_path, target_size=(224, 224))

x = img_to_array(img)

x = np.expand_dims(x, axis=0)

x = preprocess_input(x)

preds = model.predict(x)

class_idx = np.argmax(preds[0])

class_output = model.output[:, class_idx]

last_conv_layer = model.get_layer('block5_conv3')

grads = K.gradients(class_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 /= np.max(heatmap)

plt.matshow(heatmap)

plt.show()

函数传入参数与可视化函数类似,不过这里我们只传入了一个目标层名称。然后我们通过计算最后一个卷积层的梯度和输出的平均值,得到了对于目标类别的梯度权重。接着,我们将权重权衡到最后一个卷积层的输出上,并对它们求取平均值,最后得到的就是热度图。

我们可以在原始图像上叠加热度图,用颜色的深浅来表示不同地方的重要程度。下面的代码展示了如何得到一个叠加了热度图的图片:

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('heatmap.jpg', superimposed_img)

我们使用OpenCV库读入图片并将热度图缩放到与原始图片相同的大小。接着将热度图进行颜色映射以便于可视化,并将它们合并到原始图片上。

下面是生成的热度图示例,其中红色表示该区域为目标类别所在区域,颜色深浅表示该区域对目标类别的重要程度:

3.2 可视化特定类别的热度图

我们可以使用上面的热度图生成方式来可视化目标类别的热度图。下面的代码展示了如何只可视化某一个特定类别的热度图:

def heat_map(model, img_path, label, size=224):

img = load_img(img_path, target_size=(size, size))

x = img_to_array(img)

x = np.expand_dims(x, axis=0)

x = preprocess_input(x)

preds = model.predict(x)

class_idx = np.argmax(preds[0])

if label is None:

label = class_idx

class_output = model.output[:, label]

last_conv_layer = model.get_layer('block5_conv3')

grads = K.gradients(class_output, last_conv_layer.output)[0]

pooled_grads = K.mean(grads, axis=(