深度学习 Keras 1.6 将卷积网络(convnets)学习到的内容进行可视化

  1. 中间层激励(intermediate activations)的可视化

常听人说,深度学习的模型是一个黑盒子,非常难有一个直观的方式体现给我们它里面到底在干什么。虽然这样的说法对于某些类型的深度学习模型是真实的,但是对于卷积网络(convnets)模型来说绝对不是这样的。

卷积网络模型所学习到的特征非常适合用可视化的方式来呈现,它在很大程度上是因为卷积网络本来就是一个视觉概念的陈述。自 2013 年以来,已经开发了一系列的技术用于可视化。这里就不一一说明了,我们将挑几个容易使用并且用的多的技术来进行可视化:

  • 中间神经层的输出“中间层激励”(intermediate activations)。它有助于理解连续的卷积网络层如何转换他们的输入,并且让我们对单个卷积网络过滤器有一个初步了解。
  • 卷积网络过滤器。这有助于准确理解每个过滤器在卷积网络中接受到了什么样的视觉模式或概念。
  • 图像中各类被动激活后的热力图。这对于理解图像的哪一部分属于给定的类型是很有用的。

对于第一种方法:激励可视化(activation visualization)。我们将使用一个小的卷积网络(之前对猫与狗分类问题从头开始训练的结果)。至于其他两种方法,我们将使用上一届中介绍的 VGG16 模型。

中间层激励(intermediate activations)的可视化

中间层激励的可视化包括了显示由网络中的各种卷积和池化层输出的特征映射(feature maps)。

中间层激活(intermediate activations)的可视化包括显示网络中不同卷积层和池化层在给定输入(一层的输出通常被称为“激活”,即激活函数的输出)下输出的特征映射(feature maps)。这给出了一个关于输入如何被分解成网络学习的不同过滤器的视图。
我们想要可视化的这些特征地图有3个维度:宽度、高度和深度(通道)。每个通道编码相对独立的特征,因此将这些特征映射可视化的正确方法是将每个通道的内容独立地绘制成二维图像。
让我们从 《深度学习 Keras 1.4 如何训练小数据集》 中保存的模型开始:

运行如下程序

from keras.models import load_model
model = load_model('cats_and_dogs_small_2.h5')
model.summary()

------------ console ------------

Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 148, 148, 32)      896       
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 74, 74, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 72, 72, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 36, 36, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 34, 34, 128)       73856     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 17, 17, 128)       0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 15, 15, 128)       147584    
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 7, 7, 128)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 6272)              0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 6272)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 512)               3211776   
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 513       
=================================================================
Total params: 3,453,121
Trainable params: 3,453,121
Non-trainable params: 0
_________________________________________________________________

现在,我们在 测试集 中找一张图片作为输入:

img_path = 'data/cats_and_dogs_small/test/cats/cat.1700.jpg'

# 我們把圖像轉換成網絡要求的張量shape (4D 張量)
# (樣本數, 圖像高度, 圖像寬度, 圖像通道)
from keras.preprocessing import image
import numpy as np

img = image.load_img(img_path, target_size=(150, 150)) # 載入圖像並轉換大小為150x150
img_tensor = image.img_to_array(img) # 把影像物件轉換成 numpy ndarray物件

print("Origin img_tensor shape: ", img_tensor.shape)

img_tensor = np.expand_dims(img_tensor, axis=0) # 多增加一個維度來符合Keras Conv2D的要求

print("After reshape img_tensor shape: ", img_tensor.shape)

# 這個模型的輸入是有經過歸一化的前處理
# 所以我們也要進行相同的前處理
img_tensor /= 255. # 進行資料尺度(scale)的轉換

为了提取我们想要查看的特征映射(feature maps),我们将创建一个 Keras 模型,将一批图像作为输入,并输出所有卷积和池化层的激活。为此,我们将使用 Keras 类模型。模型使用两个参数进行实例化:一个输入张量(或输入张量列表),一个输出张量(或输出张量列表)。生成的类是一个 Keras 模型,就像你熟悉的顺序模型一样,将指定的输入映射到指定的输出。Model 类的不同之处在于它允许模型有多个输出,而不像顺序输出。有关 Model 类的更多信息,自己查去吧。

from keras import models

# Extracts the outputs of the top 8 layers:
layer_outputs = [layer.output for layer in model.layers[:8]]
# Creates a model that will return these outputs, given the model input:
activation_model = models.Model(inputs=model.input, outputs=layer_outputs)

我们现在有一个输入和八个输出,每个层激活一个输出。

当输入一个图像时,这个模型返回原始模型中层激活的值。这是我们第一次遇到多输出模型,到目前为止,我们所看到的模型都只有一个输入和一个输出。在一般情况下,一个模型可以有任意数量的输入和输出。

# This will return a list of 5 Numpy arrays:
# one array per layer activation
activations = activation_model.predict(img_tensor)

例如,这是我们的猫图像输入的第一个卷积层的激活:

first_layer_activation = activations[0]
print(first_layer_activation.shape)

------------ console ------------

(1, 148, 148, 32)

这是一张 148x148 的地图,有 32 个通道。让我们来看看第三个信道:

import matplotlib.pyplot as plt
plt.matshow(first_layer_activation[0, :, :, 3], cmap='viridis')
plt.show()

这个信道似乎编码一个对角边缘检测器。
让我们尝试第 30 个通道——但请注意,你自己的通道可能会有所不同,因为通过卷积层学习到的特定过滤器是不确定的。

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

这一个看起来像一个“亮绿点”(bright green dot)探测器,对于编码猫的眼睛非常有用!
现在,让我们画出网络中所有激活的完整可视化图。
我们将提取并绘制我们 8 张激活地图中的每个通道,并将结果堆叠在一个大图像张量中,通道并排堆叠(with channels stacked side by side)。

import keras

# These are the names of the layers, so can have them as part of our plot
layer_names = []
for layer in model.layers[:8]:
    layer_names.append(layer.name)

images_per_row = 16

# Now let's display our feature maps
for layer_name, layer_activation in zip(layer_names, activations):
    # This is the number of features in the feature map
    n_features = layer_activation.shape[-1]

    # The feature map has shape (1, size, size, n_features)
    size = layer_activation.shape[1]

    # We will tile the activation channels in this matrix
    n_cols = n_features // images_per_row
    display_grid = np.zeros((size * n_cols, images_per_row * size))

    # We'll tile each filter into this big horizontal grid
    for col in range(n_cols):
        for row in range(images_per_row):
            channel_image = layer_activation[0,
                                             :, :,
                                             col * images_per_row + row]
            # Post-process the feature to make it visually palatable
            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

    # Display the grid
    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()


这里有几点值得注意:

  • 第一层充当(acts as)各种边缘(various edge)检测器的集合。在这个阶段,激活仍然保留几乎所有的信息呈现在最初的图像中。
  • 随着我们越往高处走(As we go higher-up),这些活动变得越来越抽象,越来越难以直观地解释。他们开始编码更高层次的概念,如“猫耳”或“猫眼”。高层的演示所携带的关于图像视觉内容的信息越来越少,而与图像类别(the class of the image)相关的信息越来越多。
  • 激活的稀疏性(sparsity )随着图层的深度增加而增加:在第一层,所有的滤镜都是由输入图像激活的,但是在接下来的图层中越来越多的滤镜是空白的。这意味着过滤器编码的模式在输入图像中找不到。

我们刚刚证明了深度神经网络学习表示的一个非常重要的普遍特征:一层提取的特征随着层的深度而越来越抽象。更高层次的激活所携带的关于所看到的特定输入的信息越来越少,而关于目标的信息越来越多(在我们的例子中,是图像的类:猫或狗)。深神经网络有效地充当了信息蒸馏管道,与原始数据(在我们的例子中,篮板图片),并获得多次转换,这样无关紧要的信息过滤掉(如图像的特定的视觉外观)而有用的信息得到放大和精制(如图像的类)。
这类似于人类和动物感知世界的方式:在观察一个场景几秒钟后,人类可以记住其中有哪些抽象物体(如自行车、树),但却不记得这些物体的具体外观。事实上,如果你现在试图在脑海中画出一辆普通的自行车,你可能连一点点都画不出来,即使你一生中见过成千上万辆自行车。现在就试试吧:这种效果是绝对真实的。你的大脑已经学会将其视觉输入完全抽象,并将其转化为高级视觉概念,同时完全过滤掉不相关的视觉细节,这使得记住我们周围事物的实际样子变得异常困难。


转载请注明来源。 欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。 可以在下面评论区评论,也可以邮件至 sharlot2050@foxmail.com。

文章标题:深度学习 Keras 1.6 将卷积网络(convnets)学习到的内容进行可视化

字数:2.4k

本文作者:夏来风

发布时间:2021-02-04, 22:27:01

原始链接:http://www.demo1024.com/blog/ai-ml-keras-1.6/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。