总结自《Python 深度学习》(François Chollet)第5章。

可视化卷积神经网络的过滤器

想要观察卷积神经网络学到的过滤器,另一种简单的方法是显示每个过滤器所响应的视觉模式。这可以通过在输入空间中进行梯度上升来实现:从空白输入图像开始,将梯度下降应用于卷积神经网络输入图像的值,其目的是让某个过滤器的响应最大化。得到的输入图像是选定过滤器具有最大响应的图像。

我们需要构建一个损失函数,其目的是让某个卷积层的某个过滤器的值最大化;然后,我们要使用随机梯度下降来调节输入图像的值,以便让这个激活值最大化。例如,对于在 ImageNet 上预训练的 VGG16 网络,其 block3_conv1 层的第 0 个过滤器激活的损失如下所示:

from tensorflow.keras.applications import VGG16
from tensorflow.keras import backend as K

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

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]

Tensorflow 2 中已经不再支持 tf.gradients,因此直接运行上述代码可能报错,需要在文件开头加入如下代码:

import tensorflow as tf
tf.compat.v1.disable_eager_execution()

梯度标准化技巧

为了让梯度下降过程顺利进行,可以将梯度张量除以其 L2 范数(所有值平方的均值的平方根)来标准化,这确保了输入图像的更新大小始终位于相同的范围。

grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5) # 加上一个小常数,防止分母为0

下面的代码定义了一个方法,给定输入图像,计算损失张量和梯度张量的值:

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

import numpy as np
loss_value, grads_value = iterate([np.zeros((1, 150, 150, 3))])

iterate 是一个函数,它将一个 numpy 张量(model.input)转换为两个 numpy 张量组成的列表([loss, grads])。

随机梯度下降

"""初始输入图像为一张带有噪声的灰度图像"""
input_img_data = np.random.random((1, 150, 150, 3)) * 20 + 128.

"""每次梯度更新的步长"""
step = 1

"""运行40次梯度上升"""
for i in range(40):
    loss_value, grads_value = iterate([input_img_data])
    input_img_data += grads_value * step # 沿着损失最大化的方向更新

我们需要对得到的张量进行一些处理,将其转换为可显示的图像:

def deprocess_image(x):
    """对张量做标准化,使其均值为0,标准差为0.1"""
    x -= x.mean()
    x /= (x.std() + 1e-5)
    x *= 0.1
    
    """裁切到[0, 1]"""
    x += 0.5
    x = np.clip(x, 0, 1)
    
    """裁切到[0, 255]"""
    x *= 255
    x = np.clip(x, 0, 255).astype('uint8')
    return x

下面我们将上面所提到的所有操作合并到一个函数里,该函数将某层的某个过滤器的激活最大化:

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)

我们看看经过梯度上升之后的输入图像:

import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10))
plt.imshow(generate_pattern('block3_conv1', 0))

在这里插入图片描述
这个过滤器的响应似乎是波尔卡点图案。

波尔卡圆,点一般是同一大小、同一种颜色的圆点以一定的距离均匀地排列而成。波尔卡这个名字,来源于一种名叫波尔卡的东欧音乐,倒不是说这些图案像跳动的音符,而是有一段时间很多波尔卡音乐的唱片封套都是以波尔卡圆点图案来装饰的。

—— 百度百科

我们也可以将每个卷积块第一层的前 64 个过滤器都可视化:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这些过滤器可视化包含神经网络的层如何观察世界的很多信息:卷积神经网络中每一层都学习一组过滤器,以便将其输入表示为过滤器的组合。这类似于傅里叶变换将信号分解为一组余弦函数的过程。随着层数的加深,卷积神经网络中的过滤器变得越来越复杂,越来越精细:

  • 模型第一层的过滤器对应简单的方向边缘和颜色
  • 第二个 block 中的第一层的过滤器对应边缘和颜色组合而成的简单纹理
  • 更高层的过滤器类似于自然图像中的纹理:羽毛、眼睛、树叶等

References

《Python 深度学习》,François Chollet.

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐