CNN眼中的世界:利用Keras解释CNN的滤波器

目录

  • 原文信息
  • 使用Keras探索卷积网络的滤波器
  • 可视化所有的滤波器
  • Deep Dream(nightmare)
  • 愚弄神经网络
  • 革命尚未成功,同志仍需努力

原文信息

本文地址:http://blog.keras.io/how-convolutional-neural-networks-see-the-world.html

本文作者:Francois Chollet

本文的翻译版最先由我发布在Keras中文文档,为了方便各位网友,特将此文搬移到CSDN。

使用Keras探索卷积网络的滤波器

本文中我们将利用Keras观察CNN到底在学些什么,它是如何理解我们送入的训练图片的。我们将使用Keras来对滤波器的激活值进行可视化。本文使用的神经网络是VGG-16,数据集为ImageNet。本文的代码可以在github找到

VGG-16又称为OxfordNet,是由牛津视觉几何组(Visual Geometry Group)开发的卷积神经网络结构。该网络赢得了ILSVR(ImageNet)2014的冠军。时至今日,VGG仍然被认为是一个杰出的视觉模型——尽管它的性能实际上已经被后来的Inception和ResNet超过了。

Lorenzo Baraldi将Caffe预训练好的VGG16和VGG19模型转化为了Keras权重文件,所以我们可以简单的通过载入权重来进行实验。该权重文件可以在这里下载。国内的同学需要自备梯子。(这里是一个网盘保持的vgg16:http://files.heuritech.com/weights/vgg16_weights.h5赶紧下载,网盘什么的不知道什么时候就挂了。)

首先,我们在Keras中定义VGG网络的结构:

from keras.models import Sequential
from keras.layers import Convolution2D, ZeroPadding2D, MaxPooling2D

img_width, img_height = 128, 128

# build the VGG16 network
model = Sequential()
model.add(ZeroPadding2D((1, 1), batch_input_shape=(1, 3, img_width, img_height)))
first_layer = model.layers[-1]
# this is a placeholder tensor that will contain our generated images
input_img = first_layer.input

# build the rest of the network
model.add(Convolution2D(64, 3, 3, activation=‘relu‘, name=‘conv1_1‘))
model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(64, 3, 3, activation=‘relu‘, name=‘conv1_2‘))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))

model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(128, 3, 3, activation=‘relu‘, name=‘conv2_1‘))
model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(128, 3, 3, activation=‘relu‘, name=‘conv2_2‘))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))

model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(256, 3, 3, activation=‘relu‘, name=‘conv3_1‘))
model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(256, 3, 3, activation=‘relu‘, name=‘conv3_2‘))
model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(256, 3, 3, activation=‘relu‘, name=‘conv3_3‘))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))

model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(512, 3, 3, activation=‘relu‘, name=‘conv4_1‘))
model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(512, 3, 3, activation=‘relu‘, name=‘conv4_2‘))
model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(512, 3, 3, activation=‘relu‘, name=‘conv4_3‘))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))

model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(512, 3, 3, activation=‘relu‘, name=‘conv5_1‘))
model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(512, 3, 3, activation=‘relu‘, name=‘conv5_2‘))
model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(512, 3, 3, activation=‘relu‘, name=‘conv5_3‘))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))

# get the symbolic outputs of each "key" layer (we gave them unique names).
layer_dict = dict([(layer.name, layer) for layer in model.layers])

注意我们不需要全连接层,所以网络就定义到最后一个卷积层为止。使用全连接层会将输入大小限制为224×224,即ImageNet原图片的大小。这是因为如果输入的图片大小不是224×224,在从卷积过度到全链接时向量的长度与模型指定的长度不相符。

下面,我们将预训练好的权重载入模型,一般而言我们可以通过model.load_weights()载入,但这里我们只载入一部分参数,如果使用该方法的话,模型和参数形式就不匹配了。所以我们需要手工载入:

import h5py

weights_path = ‘vgg16_weights.h5‘

f = h5py.File(weights_path)
for k in range(f.attrs[‘nb_layers‘]):
    if k >= len(model.layers):
        # we don‘t look at the last (fully-connected) layers in the savefile
        break
    g = f[‘layer_{}‘.format(k)]
    weights = [g[‘param_{}‘.format(p)] for p in range(g.attrs[‘nb_params‘])]
    model.layers[k].set_weights(weights)
f.close()
print(‘Model loaded.‘)

下面,我们要定义一个损失函数,这个损失函数将用于最大化某个指定滤波器的激活值。以该函数为优化目标优化后,我们可以真正看一下使得这个滤波器激活的究竟是些什么东西。

现在我们使用Keras的后端来完成这个损失函数,这样这份代码不用修改就可以在TensorFlow和Theano之间切换了。TensorFlow在CPU上进行卷积要块的多,而目前为止Theano在GPU上进行卷积要快一些。

from keras import backend as K

layer_name = ‘conv5_1‘
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])

注意这里有个小trick,计算出来的梯度进行了正规化,使得梯度不会过小或过大。这种正规化能够使梯度上升的过程平滑进行。

根据刚刚定义的函数,现在可以对某个滤波器的激活值进行梯度上升。

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时,这个操作大概只要几秒。

然后我们可以提取出结果,并可视化:

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)

这里是第5卷基层第0个滤波器的结果:

可视化所有的滤波器

下面我们系统的可视化一下各个层的各个滤波器结果,看看CNN是如何对输入进行逐层分解的。

第一层的滤波器主要完成方向、颜色的编码,这些颜色和方向与基本的纹理组合,逐渐生成复杂的形状。

可以将每层的滤波器想为基向量,这些基向量一般是过完备的。基向量可以将层的输入紧凑的编码出来。滤波器随着其利用的空域信息的拓宽而更加精细和复杂,

可以观察到,很多滤波器的内容其实是一样的,只不过旋转了一个随机的的角度(如90度)而已。这意味着我们可以通过使得卷积滤波器具有旋转不变性而显著减少滤波器的数目,这是一个有趣的研究方向。

令人震惊的是,这种旋转的性质在高层的滤波器中仍然可以被观察到。如Conv4_1

Deep Dream(nightmare)

另一个有趣的事儿是,如果我们把刚才的随机噪声图片替换为有意义的照片,结果就变的更好玩了。这就是去年由Google提出的Deep Dream。通过选择特定的滤波器组合,我们可以获得一些很有意思的结果。如果你对此感兴趣,可以参考Keras的例子

Deep Dream和Google的博客Google blog post(墙)

愚弄神经网络

如果我们添加上VGG的全连接层,然后试图最大化某个指定类别的激活值呢?你会得到一张很像该类别的图片吗?让我们试试。

这种情况下我们的损失函数长这样:

layer_output = model.layers[-1].get_output()
loss = K.mean(layer_output[:, output_index])

比方说我们来最大化输出下标为65的那个类,在ImageNet里,这个类是蛇。很快,我们的损失达到了0.999,即神经网络有99.9%的概率认为我们生成的图片是一条海蛇,它长这样:

不太像呀,换个类别试试,这次选喜鹊类(第18类)

OK,我们的网络认为是喜鹊的东西看起来完全不是喜鹊,往好了说,这个图里跟喜鹊相似的,也不过就是一些局部的纹理,如羽毛,嘴巴之类的。那么,这就意味着卷积神经网络是个很差的工具吗?当然不是,我们按照一个特定任务来训练它,它就会在那个任务上表现的不错。但我们不能有网络“理解”某个概念的错觉。我们不能将网络人格化,它只是工具而已。比如一条狗,它能识别其为狗只是因为它能以很高的概率将其正确分类而已,而不代表它理解关于“狗”的任何外延。

革命尚未成功,同志仍需努力

所以,神经网络到底理解了什么呢?我认为有两件事是它们理解的。

其一,神经网络理解了如何将输入空间解耦为分层次的卷积滤波器组。其二,神经网络理解了从一系列滤波器的组合到一系列特定标签的概率映射。神经网络学习到的东西完全达不到人类的“看见”的意义,从科学的的角度讲,这当然也不意味着我们已经解决了计算机视觉的问题。想得别太多,我们才刚刚踩上计算机视觉天梯的第一步。

有些人说,卷积神经网络学习到的对输入空间的分层次解耦模拟了人类视觉皮层的行为。这种说法可能对也可能不对,但目前未知我们还没有比较强的证据来承认或否认它。当然,有些人可以期望人类的视觉皮层就是以类似的方式学东西的,某种程度上讲,这是对我们视觉世界的自然解耦(就像傅里叶变换是对周期声音信号的一种解耦一样自然)【译注:这里是说,就像声音信号的傅里叶变换表达了不同频率的声音信号这种很自然很物理的理解一样,我们可能会认为我们对视觉信息的识别就是分层来完成的,圆的是轮子,有四个轮子的是汽车,造型炫酷的汽车是跑车,像这样】。但是,人类对视觉信号的滤波、分层次、处理的本质很可能和我们弱鸡的卷积网络完全不是一回事。视觉皮层不是卷积的,尽管它们也分层,但那些层具有皮质列的结构,而这些结构的真正目的目前还不得而知,这种结构在我们的人工神经网络中还没有出现(尽管乔大帝Geoff Hinton正在在这个方面努力)。此外,人类有比给静态图像分类的感知器多得多的视觉感知器,这些感知器是连续而主动的,不是静态而被动的,这些感受器还被如眼动等多种机制复杂控制。

下次有风投或某知名CEO警告你要警惕我们深度学习的威胁时,想想上面说的爸。今天我们是有更好的工具来处理复杂的信息了,这很酷,但归根结底它们只是工具,而不是生物。它们做的任何工作在哪个宇宙的标准下都不够格称之为“思考”。在一个石头上画一个笑脸并不会使石头变得“开心”,尽管你的灵长目皮质会告诉你它很开心。

总而言之,卷积神经网络的可视化工作是很让人着迷的,谁能想到仅仅通过简单的梯度下降法和合理的损失函数,加上大规模的数据库,就能学到能很好解释复杂视觉信息的如此漂亮的分层模型呢。深度学习或许在实际的意义上并不智能,但它仍然能够达到几年前任何人都无法达到的效果。现在,如果我们能理解为什么深度学习如此有效,那……嘿嘿:)

@fchollet, 2016年1月

时间: 2024-11-14 04:20:40

CNN眼中的世界:利用Keras解释CNN的滤波器的相关文章

利用keras搭建CNN进行mnist数据集分类

当接触深度学习算法的时候,大家都很想自己亲自实践一下这个算法,但是一想到那些复杂的程序,又感觉心里面很累啊,又要学诸如tensorflow.theano这些框架.那么,有没有什么好东西能够帮助我们快速搭建这个算法呢?当然是有咯!,现如今真不缺少造轮子的大神,so,我强烈向大家推荐keras,Keras是一个高层神经网络API,Keras由纯Python编写而成并基Tensorflow或Theano.Keras为支持快速实验而生,能够把你的idea迅速转换为结果. 具体keras的安装与使用,请参

对比学习用 Keras 搭建 CNN RNN 等常用神经网络

Keras 是一个兼容 Theano 和 Tensorflow 的神经网络高级包, 用他来组件一个神经网络更加快速, 几条语句就搞定了. 而且广泛的兼容性能使 Keras 在 Windows 和 MacOS 或者 Linux 上运行无阻碍. 今天来对比学习一下用 Keras 搭建下面几个常用神经网络: 回归 RNN回归 分类 CNN分类 RNN分类 自编码分类 它们的步骤差不多是一样的: [导入模块并创建数据] [建立模型] [定义优化器] [激活模型] [训练模型] [检验模型] [可视化结果

【深度学习】简单地利用keras做车标识别

一次简简单单的实验课的内容而已. 首先把给出的样本素材放缩的32*32的大小,这部分可以用Python的批处理和opencv中的放缩函数resize()来做,在此我就不列出代码了. 列举出一部分放缩好的图片. 然后在利用keras简历卷积神经网络的模型,在做此实验之前,电脑要配置好Python+Theano+Keras的环境. #生成一个model def __CNN__(testdata,testlabel,traindata,trainlabel): model = Sequential()

tlcl-从shell眼中看世界echo

从shell眼中看世界 echo-显示一行文本 当输入一个命令,bash在执行命令之前,会对输入的字符做展开处理. 路径展开 echo * shell将*展开为当前目录下的文件名称,会打印出当前目录下的文件名称, echo d*       打印出当前目录下所有以d开头的文件 echo *d       打印出当前目录下所有以d结尾的文件 ~展开 echo ~        打印出当前用户的家目录信息 echo ~test    打印出test用户的家目录信息 算术表达式展开 echo $((e

《一个程序员眼中的世界》 -- 个人介绍

<一个程序员眼中的世界>  --  个人介绍 写在前面 写在前面的是对我个人的介绍,个人的教育.工作.成长经历决定了我的想法及看法,没必要,我没打算当一个专家,只是一个小小的程序员,芸芸众生中的一员,各位看客如果有其他不同的看法,尽管提出来,赞同的共勉,质疑的讲出你的看法,有道理我亦欣然接受,辱骂的我只能置之不理了. 教育背景 用现在的话说我是一个纯种的“屌丝”,也做这“屌丝”的代表职业----程序猿,如果说教育背景真是说不出口,不是985.更不是211,所以我不说体制教育(没脸说,避弱就强也

阿里王坚: 用数据改变世界而非解释世界

阿里王坚: 用数据改变世界而非解释世界 阿里巴巴技术委员会主席王坚在2019年中国电子政务论坛上发表演讲.他表示,现在智慧城市建设有很多的误区.但与很多顶尖公司用数据解释这个世界不同,浙江政府是从数据多跑一次开始第一个用数据在改变世界. 王坚表示,很多智慧城市花了那么多钱,但是并没有达到想要的结果.根源在于有几个认知上的错误.“社会发展到今天,城市不完成一次数字化,怎么支撑产业化发展,甚至数字政府”.“ 传统信息化建设都是从无到有,不断加杆子和机器,该付几次钱该付几次钱”. 王坚提出一个让老百姓

用keras作CNN卷积网络书本分类(书本、非书本)

本文介绍如何使用keras作图片分类(2分类与多分类,其实就一个参数的区别...呵呵) 先来看看解决的问题:从一堆图片中分出是不是书本,也就是最终给图片标签上:“书本“.“非书本”,简单吧. 先来看看网络模型,用到了卷积和全连接层,最后套上SOFTMAX算出各自概率,输出ONE-HOT码,主要部件就是这些,下面的nb_classes就是用来控制分类数的,本文是2分类: from keras.models import Sequential from keras.layers.core impor

DeepLearning (五) 基于Keras的CNN 训练cifar-10 数据库

数据库介绍 开发工具 网络框架 训练结果 训练要点 激活函数 Dropout 的作用 训练代码 [原创]Liu_LongPo 转载请注明出处[CSDN]http://blog.csdn.net/llp1992 数据库介绍 Cifar-10是由Hinton的两个大弟子Alex Krizhevsky.Ilya Sutskever收集的一个用于普适物体识别的数据集. Cifar-10由60000张32*32的RGB彩色图片构成,共10个分类.50000张训练,10000张测试(交叉验证).这个数据集最

实战keras——用CNN实现cifar10图像分类

原文:https://blog.csdn.net/zzulp/article/details/76358694 import keras from keras.datasets import cifar10 from keras.models import Sequential from keras.layers import Dense, Dropout, Activation, Flatten from keras.layers import Conv2D, MaxPooling2D num