注意:这篇文章最初写于 2016 年 1 月。现在已经过时了。有关最新的替代方案,请参阅如何可视化卷积网络过滤器的示例,或查看我的书“Deep Learning with Python (2nd edition)”的第 9 章。
使用 Keras 探索卷积网络过滤器
在这篇文章中,我们将了解深度卷积神经网络 (convnets) 真正学到了什么,以及它们如何理解我们输入的图像。我们将使用 Keras 来可视化最大化在 ImageNet 上训练的 VGG16 架构不同层中的过滤器激活的输入。这篇文章中使用的所有代码都可以在Github 上找到。
VGG16(也称为 OxfordNet)是一种以开发它的牛津大学视觉几何组命名的卷积神经网络架构。它被用于赢得 2014 年 ILSVR(ImageNet)竞赛。尽管它在某种程度上已被 Inception 和 ResNet 等更新的进展所超越,但它至今仍被认为是一个优秀的视觉模型。
首先,让我们从在 Keras 中定义 VGG16 模型开始
from keras import applications
# build the VGG16 network
model = applications.VGG16(include_top=False,
weights='imagenet')
# get the symbolic outputs of each "key" layer (we gave them unique names).
layer_dict = dict([(layer.name, layer) for layer in model.layers])
请注意,我们只到最后一个卷积层——我们不包括全连接层。原因是添加全连接层会强制您对模型使用固定的输入大小(224x224,原始 ImageNet 格式)。通过仅保留卷积模块,我们的模型可以适应任意输入大小。
该模型加载一组在 ImageNet 上预先训练的权重。
现在让我们定义一个损失函数,它将寻求最大化特定层 (layer_name
) 中特定过滤器 (filter_index
) 的激活。我们通过 Keras 后端函数来做到这一点,这允许我们的代码在 TensorFlow 和 Theano 之上运行。
from keras import backend as K
layer_name = 'block5_conv3'
filter_index = 0 # can be any integer from 0 to 511, as there are 512 filters in that layer
# build a loss function that maximizes the activation
# of the nth filter of the layer considered
layer_output = layer_dict[layer_name].output
loss = K.mean(layer_output[:, :, :, filter_index])
# compute the gradient of the input picture wrt this loss
grads = K.gradients(loss, input_img)[0]
# normalization trick: we normalize the gradient
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
# this function returns the loss and grads given the input picture
iterate = K.function([input_img], [loss, grads])
一切都很简单。这里唯一的技巧是规范化输入图像像素的梯度,这可以避免非常小和非常大的梯度,并确保平滑的梯度上升过程。
现在我们可以使用我们定义的 Keras 函数来在输入空间中进行梯度上升,关于我们的过滤器激活损失
import numpy as np
# we start from a gray image with some noise
input_img_data = np.random.random((1, 3, img_width, img_height)) * 20 + 128.
# run gradient ascent for 20 steps
for i in range(20):
loss_value, grads_value = iterate([input_img_data])
input_img_data += grads_value * step
此操作在使用 TensorFlow 的 CPU 上需要几秒钟。
然后我们可以提取并显示生成的输入
from scipy.misc import imsave
# util function to convert a tensor into a valid image
def deprocess_image(x):
# normalize tensor: center on 0., ensure std is 0.1
x -= x.mean()
x /= (x.std() + 1e-5)
x *= 0.1
# clip to [0, 1]
x += 0.5
x = np.clip(x, 0, 1)
# convert to RGB array
x *= 255
x = x.transpose((1, 2, 0))
x = np.clip(x, 0, 255).astype('uint8')
return x
img = input_img_data[0]
img = deprocess_image(img)
imsave('%s_filter_%d.png' % (layer_name, filter_index), img)
结果
可视化所有过滤器!
现在是有趣的部分。我们可以使用相同的代码来系统地显示什么样的输入(它们不是唯一的)最大化了每一层中的每个过滤器,让我们可以清楚地看到卷积网络对其视觉空间的模块化-层次分解。
第一层基本上只编码方向和颜色。然后将这些方向和颜色过滤器组合成基本的网格和点纹理。这些纹理逐渐组合成越来越复杂的图案。
您可以将每一层中的过滤器视为一组向量,通常是过完备的,可用于以紧凑的方式对层的输入进行编码。当过滤器开始合并来自越来越大的空间范围的信息时,它们变得更加复杂。
一个值得注意的观察结果:许多过滤器是相同的,但旋转了某个非随机因子(通常为 90 度)。这意味着我们有可能通过找到一种使卷积过滤器旋转不变的方法,将卷积网络中使用的过滤器数量压缩很大一部分。我可以看到几种可以实现这一点的方法——这是一个有趣的研究方向。
令人震惊的是,即使对于相对高级的过滤器,例如block4_conv1
中的过滤器,旋转观察结果也成立。
在最高层(block5_conv2
、block5_conv3
)中,我们开始识别出与网络训练分类的对象中发现的纹理相似的纹理,例如羽毛、眼睛等。
卷积网络的梦
另一个有趣的事情是将这些过滤器应用于照片(而不是应用于嘈杂的全灰度输入)。这就是去年谷歌推广的 Deep Dreams 的原理。通过选择特定的过滤器组合而不是单个过滤器,您可以获得非常漂亮的结果。如果您对此感兴趣,您还可以查看 Keras 中的Deep Dream 示例,以及介绍该技术的Google 博文。
找到最大化特定类的输入
现在是别的东西——如果您在网络的末尾包含全连接层,并试图最大化网络特定输出的激活,会怎么样?您会得到一张看起来像该输出编码的类的图像吗?让我们试试看。
我们可以只定义这个非常简单的损失函数
model = applications.VGG16(include_top=True,
weights='imagenet')
loss = K.mean(model.output[:, output_index])
让我们将它应用于output_index = 65
(它是 ImageNet 中的海蛇类)。我们很快达到了 0.999 的损失,这意味着卷积网络有 99.9% 的置信度认为生成的输入是一条海蛇。让我们来看看生成的输入。
哦,好吧。看起来不像海蛇。当然那只是偶然,所以让我们再试一次output_index = 18
(喜鹊类)。
好吧。所以我们卷积网络对喜鹊的概念看起来一点也不像喜鹊——充其量,唯一的相似之处是在局部纹理的水平上(羽毛,也许是一两个喙)。这是否意味着卷积网络是糟糕的工具?当然不是,它们很好地服务于它们的用途。这意味着我们应该避免我们自然而然地将它们拟人化,并相信它们“理解”,比如说,狗的概念,或喜鹊的外观,仅仅是因为它们能够以高精度对这些物体进行分类。他们没有,至少在任何对我们人类有意义的程度上都没有。
那么他们真正“理解”的是什么呢?有两件事:首先,他们将视觉输入空间的分解理解为卷积过滤器的层次模块化网络;其次,他们理解某些过滤器组合与一组任意标签之间的概率映射。自然,这在任何人类意义上都不能算作“看”,而且从科学的角度来看,这当然不意味着我们在这一点上以某种方式解决了计算机视觉问题。不要相信炒作;我们只是站在一个非常高的梯子的第一步。
有人说,卷积网络学习到的视觉空间的层次模块化分解类似于人类视觉皮层所做的事情。这可能是真的,也可能不是真的,但没有强有力的证据支持这一点。当然,人们会期望视觉皮层学习类似的东西,因为它构成了我们视觉世界的“自然”分解(就像傅立叶分解是周期性音频信号的“自然”分解一样)。但过滤器的确切性质和层次结构,以及它们学习的过程,很可能与我们弱小的卷积网络没有什么共同之处。视觉皮层一开始就不是卷积的,虽然它是分层结构的,但这些层本身又结构成皮质柱,其确切用途尚不清楚——这是我们的人工网络中没有的特征(尽管 Geoff Hinton 正在研究它)。此外,视觉感知远不止静态图片的分类——人类的感知本质上是连续的和主动的,而不是静态的和被动的,并且与运动控制(例如眼跳)紧密相连。
下次当你听到一些风险投资家或大牌 CEO 出现在新闻中,警告你警惕我们最近在深度学习方面取得的进步所带来的生存威胁时,请想想这一点。今天,我们拥有比以往任何时候都更好的工具来绘制复杂的信息空间,这很棒,但归根结底,它们是工具,而不是生物,而且它们所做的任何事情都不能合理地算作“思考”。在石头上画一张笑脸并不能使它“快乐”,即使你的灵长类动物新皮层告诉你它是这样。
也就是说,可视化卷积网络学习到的东西是非常吸引人的——谁会想到,在足够大的数据集上进行简单梯度下降和合理的损失函数就足以学习到这个美丽的层次模块化模式网络,它设法解释了一个复杂的视觉空间?深度学习在任何真正意义上可能都不是智能,但它仍然比几年前任何人预期的都要好得多。现在,如果我们知道为什么就好了……;-)
@fchollet,2016 年 1 月