您好,欢迎访问三七文档
CNN眼中的世界CNN眼中的世界:利用Keras解释CNN的滤波器文章信息本文地址:本文作者:FrancoisChollet使用Keras探索卷积网络的滤波器本文中我们将利用Keras观察CNN到底在学些什么,它是如何理解我们送入的训练图片的。我们将使用Keras来对滤波器的激活值进行可视化。本文使用的神经网络是VGG-16,数据集为ImageNet。本文的代码可以在github找到VGG-16又称为OxfordNet,是由牛津视觉几何组(VisualGeometryGroup)开发的卷积神经网络结构。该网络赢得了ILSVR(ImageNet)2014的冠军。时至今日,VGG仍然被认为是一个杰出的视觉模型——尽管它的性能实际上已经被后来的Inception和ResNet超过了。LorenzoBaraldi将Caffe预训练好的VGG16和VGG19模型转化为了Keras权重文件,所以我们可以简单的通过载入权重来进行实验。该权重文件可以在这里下载。国内的同学需要自备梯子。(这里是一个网盘保持的vgg16:赶紧下载,网盘什么的不知道什么时候就挂了。)首先,我们在Keras中定义VGG网络的结构:fromkeras.modelsimportSequentialfromkeras.layersimportConvolution2D,ZeroPadding2D,MaxPooling2Dimg_width,img_height=128,128#buildtheVGG16networkmodel=Sequential()model.add(ZeroPadding2D((1,1),batch_input_shape=(1,3,img_width,img_height)))first_layer=model.layers[-1]#thisisaplaceholdertensorthatwillcontainourgeneratedimagesinput_img=first_layer.input#buildtherestofthenetworkmodel.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)))#getthesymbolicoutputsofeach'key'layer(wegavethemuniquenames).layer_dict=dict([(layer.name,layer)forlayerinmodel.layers])注意我们不需要全连接层,所以网络就定义到最后一个卷积层为止。使用全连接层会将输入大小限制为224×224,即ImageNet原图片的大小。这是因为如果输入的图片大小不是224×224,在从卷积过度到全链接时向量的长度与模型指定的长度不相符。下面,我们将预训练好的权重载入模型,一般而言我们可以通过model.load_weights()载入,但这里我们只载入一部分参数,如果使用该方法的话,模型和参数形式就不匹配了。所以我们需要手工载入:importh5pyweights_path='vgg16_weights.h5'f=h5py.File(weights_path)forkinrange(f.attrs['nb_layers']):ifk=len(model.layers):#wedon'tlookatthelast(fully-connected)layersinthesavefilebreakg=f['layer_{}'.format(k)]weights=[g['param_{}'.format(p)]forpinrange(g.attrs['nb_params'])]model.layers[k].set_weights(weights)f.close()print('Modelloaded.')下面,我们要定义一个损失函数,这个损失函数将用于最大化某个指定滤波器的激活值。以该函数为优化目标优化后,我们可以真正看一下使得这个滤波器激活的究竟是些什么东西。现在我们使用Keras的后端来完成这个损失函数,这样这份代码不用修改就可以在TensorFlow和Theano之间切换了。TensorFlow在CPU上进行卷积要块的多,而目前为止Theano在GPU上进行卷积要快一些。fromkerasimportbackendasKlayer_name='conv5_1'filter_index=0#canbeanyintegerfrom0to511,asthereare512filtersinthatlayer#buildalossfunctionthatmaximizestheactivation#ofthenthfilterofthelayerconsideredlayer_output=layer_dict[layer_name].outputloss=K.mean(layer_output[:,filter_index,:,:])#computethegradientoftheinputpicturewrtthislossgrads=K.gradients(loss,input_img)[0]#normalizationtrick:wenormalizethegradientgrads/=(K.sqrt(K.mean(K.square(grads)))+1e-5)#thisfunctionreturnsthelossandgradsgiventheinputpictureiterate=K.function([input_img],[loss,grads])注意这里有个小trick,计算出来的梯度进行了正规化,使得梯度不会过小或过大。这种正规化能够使梯度上升的过程平滑进行。根据刚刚定义的函数,现在可以对某个滤波器的激活值进行梯度上升。importnumpyasnp#westartfromagrayimagewithsomenoiseinput_img_data=np.random.random((1,3,img_width,img_height))*20+128.#rungradientascentfor20stepsforiinrange(20):loss_value,grads_value=iterate([input_img_data])input_img_data+=grads_value*step使用TensorFlow时,这个操作大概只要几秒。然后我们可以提取出结果,并可视化:fromscipy.miscimportimsave#utilfunctiontoconvertatensorintoavalidimagedefdeprocess_image(x):#normalizetensor:centeron0.,ensurestdis0.1x-=x.mean()x/=(x.std()+1e-5)x*=0.1#clipto[0,1]x+=0.5x=np.clip(x,0,1)#converttoRGBarrayx*=255x=x.transpose((1,2,0))x=np.clip(x,0,255).astype('uint8')returnximg=input_img_data[0]img=deprocess_image(img)imsave('%s_filter_%d.png'%(layer_name,filter_index),img)这里是第5卷基层第0个滤波器的结果:可视化所有的滤波器下面我们系统的可视化一下各个层的各个滤波器结果,看看CNN是如何对输入进行逐层分解的。第一层的滤波器主要完成方向、颜色的编码,这些颜色和方向与基本的纹理组合,逐渐生成复杂的形状。可以将每层的滤波器想为基向量,这些基向量一般是过完备的。基向量可以将层的输入紧凑的编码出来。滤波器随着其利用的空域信息的拓宽而更加精细和复杂,可以观察到,很多滤波器的内容其实是一样的,只不过旋转了一个随机的的角度(如90度)而已。这意味着我们可以通过使得卷积滤波器具有旋转不变性而显著减少滤波器的数目,这是一个有趣的研究方向。令人震惊的是,这种旋转的性质在高层的滤波器中仍然可以被观察到。如Conv4_1DeepDream(nightmare)另一个有趣的事儿是,如果我们把刚才的随机噪声图片
本文标题:CNN眼中的世界
链接地址:https://www.777doc.com/doc-4209283 .html