卷积神经网络的可视化(基于keras)

在通常的认知中,神经网络的模型是一个“黑盒”,即模型学到的内容很难用人能够理解的方式来提取和表现,虽然对于某些类型的深度学习模型来说,这种表述部分正确,但对卷积神经网络来说绝对不是这样,卷积神经网络学到的表示非常适合可视化,很大程度上是因为它们是视觉概念的表示。到现在为止,人们开发了多种技术来对这些表示进行可视化和理解,这里介绍3种最容易理解也是最有效的方法。

  1. 可视化卷积神经网络的中间输出(中间激活):有助于理解卷积神经网络的连续层如何变换输入,也有助于初步了解卷积神经网络的每个滤波器的含义神经网络。
  2. 可视化卷积神经网络的过滤器:有助于准确理解卷积神经网络中每个过滤器所接受的视觉模式或视觉概念。
  3. 可视化图像中类激活的热图:帮助了解图像的哪个部分被识别为属于某个类,来自
    相反,可以定位图像中的对象。

1 可视化卷积神经网络的中间输出

可视化中间激活是指对于给定的输入,显示网络中每个卷积和池化层的输出的特征图(一层的输出通常称为该层的激活,即激活函数的输出)。这使我们能够看到输入如何分解为网络学习的不同过滤器。我们希望在三个维度上可视化特征图:宽度、高度和深度(通道)。每个通道对应一个相对独立的特征,因此可视化这些特征图的正确方法是将每个通道的内容绘制为二维图像。
我们使用这篇博客保存的模型来进行中间输出的可视化:keras深度学习之猫狗分类二(数据增强)
加载模型并打印其网络架构:

if __name__=='__main__':
    #加载保存的模型
    model=models.load_model('cats_and_dogs_1.h5')
    model.summary()
``

```bash
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d (Conv2D)              (None, 148, 148, 32)      896
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 74, 74, 32)        0
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 72, 72, 64)        18496
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 36, 36, 64)        0
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 34, 34, 128)       73856
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 17, 17, 128)       0
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 15, 15, 128)       147584
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 7, 7, 128)         0
_________________________________________________________________
flatten (Flatten)            (None, 6272)              0
_________________________________________________________________
dropout (Dropout)            (None, 6272)              0
_________________________________________________________________
dense (Dense)                (None, 256)               1605888
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 257
=================================================================
Total params: 1,846,977
Trainable params: 1,846,977
Non-trainable params: 0
_________________________________________________________________

显示第一层输出的通道图像:

from tensorflow.keras.preprocessing.image import ImageDataGenerator
from PIL import Image
from tensorflow.keras import layers
from tensorflow.keras import models
from tensorflow.keras import optimizers
import matplotlib.pyplot as plt
from tensorflow.keras.preprocessing import image as kimage

if __name__=='__main__':
    #加载保存的模型
    model=models.load_model('cats_and_dogs_1.h5')
    model.summary()

    #加载一张猫的图像
    img=kimage.load_img(path='./dataset/training_set/cats/cat.1700.jpg',target_size=(150,150))
    img_tensor=kimage.img_to_array(img)
    img_tensor=img_tensor.reshape((1,)+img_tensor.shape)
    img_tensor/=255.
    plt.imshow(img_tensor[0])
    plt.show()

    #提取前8层的输出
    layer_outputs=[layer.output for layer in model.layers[:8]]
    activation_model=models.Model(inputs=model.input,outputs=layer_outputs)

    #以预测模式运行模型 activations包含卷积层的8个输出
    activations=activation_model.predict(img_tensor)
    print(activations[0].shape)#(1, 148, 148, 32)

    first_layer_activation = activations[0]
    plt.matshow(first_layer_activation[0, :, :, 9], cmap='viridis')
    plt.show()

原图是:
卷积神经网络的可视化(基于keras)
第一层的输入第9个通道的特征图为:
卷积神经网络的可视化(基于keras)
可视化每个中间激活的所有通道

from tensorflow.keras.preprocessing.image import ImageDataGenerator
from PIL import Image
from tensorflow.keras import layers
from tensorflow.keras import models
from tensorflow.keras import optimizers
import matplotlib.pyplot as plt
from tensorflow.keras.preprocessing import image as kimage
import numpy as np

if __name__=='__main__':
    #加载保存的模型
    model=models.load_model('cats_and_dogs_1.h5')
    model.summary()

    #加载一张猫的图像
    img=kimage.load_img(path='./dataset/training_set/cats/cat.1700.jpg',target_size=(150,150))
    img_tensor=kimage.img_to_array(img)
    img_tensor=img_tensor.reshape((1,)+img_tensor.shape)
    img_tensor/=255.
    plt.imshow(img_tensor[0])
    plt.show()

    #提取前8层的输出
    layer_outputs=[layer.output for layer in model.layers[:8]]
    activation_model=models.Model(inputs=model.input,outputs=layer_outputs)

    #以预测模式运行模型 activations包含卷积层的8个输出
    activations=activation_model.predict(img_tensor)
    print(activations[0].shape)#(1, 148, 148, 32)

    first_layer_activation = activations[0]
    plt.matshow(first_layer_activation[0, :, :, 9], cmap='viridis')
    plt.show()
    #清空当前图像
    #plt.clf()

    #将每个中间激活的所有通道可视化
    layer_names = [] 
    for layer in model.layers[:8]:
        layer_names.append(layer.name)
        images_per_row = 16
        for layer_name, layer_activation in zip(layer_names, activations): 
            n_features = layer_activation.shape[-1] 
            size = layer_activation.shape[1] 
            n_cols = n_features // images_per_row 
            display_grid = np.zeros((size * n_cols, images_per_row * size))
            for col in range(n_cols): 
                for row in range(images_per_row):
                    channel_image = layer_activation[0,:, :,col * images_per_row + row]
                    channel_image -= channel_image.mean() 
                    channel_image /= channel_image.std()
                    channel_image *= 64
                    channel_image += 128
                    channel_image = np.clip(channel_image, 0, 255).astype('uint8')
                    display_grid[col * size : (col + 1) * size, 
                    row * size : (row + 1) * size] = channel_image
        
        scale = 1. / size
        plt.figure(figsize=(scale * display_grid.shape[1],
        scale * display_grid.shape[0]))
        plt.title(layer_name)
        plt.grid(False)
        plt.imshow(display_grid, aspect='auto', cmap='viridis')
        plt.show()
        

每个通道的显示图像如下:
第1个:
卷积神经网络的可视化(基于keras)
第2个:
卷积神经网络的可视化(基于keras)
第3个:
卷积神经网络的可视化(基于keras)
第4个:
卷积神经网络的可视化(基于keras)
第5个:
卷积神经网络的可视化(基于keras)
第6个:
卷积神经网络的可视化(基于keras)
第7个:
卷积神经网络的可视化(基于keras)
第8个:
卷积神经网络的可视化(基于keras)
从上面显示的各种卷积层和池化层的输出特征图,我们可以得到以下信息:

  1. 第一层是各种边缘检测器的集合。在这个阶段,激活几乎保留了原始图像中的所有信息。
  2. 随着层的深入,激活变得更加抽象,更难直观理解。它们开始代表更高层次的概念,如“猫耳朵”和“猫眼”。层越深,关于图像视觉内容的信息就越少,而关于其表示中的类别的信息就越多。
  3. 激活的稀疏度(sparsity)随着层数的加深而增大。在第一层里,所有过滤器都被输入图像激活,但在后面的层里,越来越多的过滤器是空白的。也就是说,输入图像中找不到这些过滤器所编码的模式。

深度神经网络有一个重要普遍特征:随着层数的加深,层所提取的特征变得越来越抽象。更高的层激活包含关于特定输入的信息越来越少,而关于目标的信息越来越多(本例中即图像的类别:猫或狗)。深度神经网络可以有效地作为信息蒸馏管道(information distillation pipeline),输入原始数据(本例中是 RGB 图像),反复对其进行变换,将无关信息过滤掉(比如图像的具体外观),并放大和细化有用的信息(比如图像的类别)。

2 可视化神经网络的过滤器

查看卷积神经网络学习的过滤器的另一种简单方法是显示每个过滤器响应的视觉模式。这可以通过输入空间中的梯度上升来实现:从空白输入图像开始,对卷积神经网络输入图像的值应用梯度下降,目标是最大化某个滤波器的响应。生成的输入图像是所选滤波器响应最大的图像。
过程很简单:我们需要构建一个损失函数,其目的是最大化卷积层的过滤器的值;然后,我们需要使用随机梯度下降来调整输入图像的值,使这个激活值最大化。
在这里我们使用在ImageNet上训练的vgg16网络模型进行可视化滤波器。
通过如下代码可以查看block3_conv1 层第 0 个过滤器响应的是波尔卡点(polka-dot)图案。

from tensorflow.keras.applications import VGG16
from tensorflow.keras import backend as K
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
tf.compat.v1.disable_eager_execution()

#为过滤器的可视化定义损失张量
model = VGG16(weights='imagenet',
              include_top=False)

model.summary()

layer_name = 'block3_conv1'
filter_index = 0

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)

#给定numpy输入值,得到numpy输出值
iterate = K.function([model.input], [loss, grads])

#通过随机梯度下降让损失最大化
input_img_data = np.random.random((1, 150, 150, 3)) * 20 + 128.
step = 1. 
for i in range(40):
    loss_value, grads_value = iterate([input_img_data])
    input_img_data += grads_value * step

#将张量转换为有效图像的实用函数
def deprocess_image(x):
    x -= x.mean() 
    x /= (x.std() + 1e-5)
    x *= 0.1
    x += 0.5 
    x = np.clip(x, 0, 1)
    # x *= 255 
    # x = np.clip(x, 0, 255)
    # x/=255.
    return x

#生成过滤器可视化的函数
#构建一个损失函数,将该层第 n 个过滤器的激活最大化
def generate_pattern(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 deprocess_image(img)

#block3_conv1 层第 0 个过滤器响应的是波尔卡点(polka-dot)图案
plt.imshow(generate_pattern('block3_conv1', 0))
plt.show()

卷积神经网络的可视化(基于keras)
接下来我们把多个卷积层的每个层前64个过滤器的模式显示出来,代码如下:

from tensorflow.keras.applications import VGG16
from tensorflow.keras import backend as K
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
tf.compat.v1.disable_eager_execution()

#为过滤器的可视化定义损失张量
model = VGG16(weights='imagenet',
              include_top=False)

model.summary()

layer_name = 'block3_conv1'
filter_index = 0

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)

#给定numpy输入值,得到numpy输出值
iterate = K.function([model.input], [loss, grads])

#通过随机梯度下降让损失最大化
input_img_data = np.random.random((1, 150, 150, 3)) * 20 + 128.
step = 1. 
for i in range(40):
    loss_value, grads_value = iterate([input_img_data])
    input_img_data += grads_value * step

#将张量转换为有效图像的实用函数
def deprocess_image(x):
    x -= x.mean() 
    x /= (x.std() + 1e-5)
    x *= 0.1
    x += 0.5 
    x = np.clip(x, 0, 1)
    # x *= 255 
    # x = np.clip(x, 0, 255)
    # x/=255.
    return x

#生成过滤器可视化的函数
#构建一个损失函数,将该层第 n 个过滤器的激活最大化
def generate_pattern(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 deprocess_image(img)

#block3_conv1 层第 0 个过滤器响应的是波尔卡点(polka-dot)图案
# plt.imshow(generate_pattern('block3_conv1', 0))
# plt.show()

# 生成某一层中所有过滤器响应模式组成的网格
#查看如下5个层的过滤器模式
layer_names=['block1_conv1','block2_conv1','block3_conv1','block4_conv1','block5_conv1']
for layer_name in layer_names:
    #显示通道中的前64个滤波器
    size = 64
    margin = 5
    results = np.zeros((8 * size + 7 * margin, 8 * size + 7 * margin, 3)) 
    for i in range(8): 
        for j in range(8): 
            filter_img = generate_pattern(layer_name, i + (j * 8), size=size) 
            horizontal_start = i * size + i * margin 
            horizontal_end = horizontal_start + size
            vertical_start = j * size + j * margin
            vertical_end = vertical_start + size
            results[horizontal_start: horizontal_end,vertical_start: vertical_end, :] = filter_img
    plt.figure(figsize=(20, 20)) 
    plt.imshow(results)
    plt.show()

block1_conv1层过滤器模式为:
卷积神经网络的可视化(基于keras)

block2_conv1层过滤器模式为:
卷积神经网络的可视化(基于keras)

block3_conv1层过滤器模式为:
卷积神经网络的可视化(基于keras)

block4_conv1层过滤器模式为:
卷积神经网络的可视化(基于keras)

block5_conv1层过滤器模式为:
卷积神经网络的可视化(基于keras)
这些过滤器可视化了有关卷积神经网络各层如何看待世界的大量信息:卷积神经网络中的每一层都学习一组过滤器,以便将其输入表示为过滤器的组合。这类似于傅里叶变换将信号分解为一组余弦函数的过程。随着层数的增加,卷积神经网络中的过滤器变得更加复杂和精细。

  1. 模型第一层(block1_conv1)的过滤器对应简单的方向边缘和颜色(还有一些是彩色边缘)。
  2. block2_conv1 层的过滤器对应边缘和颜色组合而成的简单纹理。
  3. 高级过滤器类似于自然图像中的纹理:羽毛、眼睛、树叶等。

3 可视化类激活的热力图

我还想介绍另一种可视化方法,它有助于了解图像的哪一部分使卷积神经网络做出最终分类决策。这有助于调试卷积神经网络的决策过程,尤其是在错误分类的情况下。此方法还可以定位图像中的特定对象。
这种通用的技术叫作类激活图(CAM,class activation map)可视化,它是指对输入图像生成类激活的热力图。类激活热力图是与特定输出类别相关的二维分数网格,对任何输入图像的每个位置都要进行计算,它表示每个位置对该类别的重要程度。举例来说,对于输入到猫狗分类卷积神经网络的一张图像,CAM 可视化可以生成类别“猫”的热力图,表示图像的各个部分与“猫”的相似程度,CAM 可视化也会生成类别“狗”的热力图,表示图像的各个部分与“狗”的相似程度。
我们将使用的具体实现方式是“Grad-CAM: visual explanations from deep networks via gradient-based localization” 这篇论文中描述的方法。这种方法非常简单:给定一张输入图像,对于一个卷积层的输出特征图,用类别相对于通道的梯度对这个特征图中的每个通道进行加权。直观上来看,理解这个技巧的一种方法是,你是用“每个通道对类别的重要程度”对“输入图像对不同通道的激活强度”的空间图进行加权,从而得到了“输入图像对类别的激活强度”的空间图。
预处理模型的输入图像,这里是非洲象图像:
卷积神经网络的可视化(基于keras)
预测代码如下:

from tensorflow.keras.preprocessing import image
from tensorflow.keras.applications.vgg16 import preprocess_input, decode_predictions
from tensorflow.keras.applications import VGG16
import numpy as np

img_path = './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)

model = VGG16(weights='imagenet')

preds = model.predict(x)
print('Predicted:', decode_predictions(preds, top=3)[0])
print(np.argmax(preds[0]))

预测结果为:

Predicted: [('n02504458', 'African_elephant', 0.90988594), ('n01871265', 'tusker', 0.085724816), ('n02504013', 'Indian_elephant', 0.00434713)]

为该图像预测的前三个类别是:

  • 非洲象(African elephant,92.5% 的概率)
  • 长牙动物(tusker,7% 的概率)
  • 印度象(Indian elephant,0.4% 的概率)

网络识别出图像中包含数量不确定的非洲象。预测向量中被最大激活的元素是对应“非洲象”类别的元素,索引编号为 386。

为了展示图像中哪些部分最像非洲象,我们来使用 Grad-CAM 算法。
测试图片的“非洲大象”类激活热图,代码为:

from tensorflow.keras.preprocessing import image
from tensorflow.keras.applications.vgg16 import preprocess_input,decode_predictions
import numpy as np
from tensorflow.keras.applications.vgg16 import VGG16
import matplotlib.pyplot as plt
from tensorflow.keras import backend as K
import cv2
import tensorflow as tf
tf.compat.v1.disable_eager_execution()

model = VGG16(weights='imagenet')   # 包含最后的全连接层


img_path = '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)

preds = model.predict(x)
print('predicted: ', decode_predictions(preds, top=3)[0])
print(np.argmax(preds[0]))

elephant_output = model.output[:, 386]
last_conv_layer = model.get_layer('block5_conv3')
grads = K.gradients(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 /= np.max(heatmap)
plt.matshow(heatmap)
plt.show()

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)

生成的图像是:
卷积神经网络的可视化(基于keras)

卷积神经网络的可视化(基于keras)

这种可视化方法回答了两个重要问题:

  • 为什么网络认为这张图片包含一头非洲象?
  • 图片中的非洲象在哪里?
    尤其值得注意的是,小象耳朵的激活强度非常强,这可能是网上查到的非洲象和印度象的区别。
    相同。

版权声明:本文为博主Keras深度学习原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/qq_37781464/article/details/122946523

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2022年2月18日 下午4:01
下一篇 2022年2月18日 下午4:24

相关推荐