一.原理

CNN 由许多神经网络层组成。卷积和池化这两种不同类型的层通常是交替的。网络中每个滤波器的深度从左到右增加。最后通常由一个或多个全连接的层组成:

cnn 卷积神经网络源码 cnn卷积神经网络实例_池化

Convnets 背后有三个关键动机:局部感受野、共享权重和池化。

局部感受野

如果想保留图像中的空间信息,那么用像素矩阵表示每个图像是很方便的。然后,编码局部结构的简单方法是将相邻输入神经元的子矩阵连接成属于下一层的单隐藏层神经元。这个单隐藏层神经元代表一个局部感受野。当然,可以通过重叠的子矩阵来编码更多的信息。例如,假设每个子矩阵的大小是 5×5,并且将这些子矩阵应用到 28×28 像素的 MNIST 图像。然后,就能够在下一隐藏层中生成 23×23 的局部感受野。事实上,在触及图像的边界之前,只需要滑动子矩阵 23 个位置。

此操作名为“卷积”,此类网络也因此而得名。

理解卷积的一个简单方法是考虑作用于矩阵的滑动窗函数

cnn 卷积神经网络源码 cnn卷积神经网络实例_池化_02

步幅可以是 1 或大于 1。大步幅意味着核的应用更少以及更小的输出尺寸,而小步幅产生更多的输出并保留更多的信息。滤波器的大小、步幅和填充类型是超参数,可以在训练网络时进行微调。

共享权重和偏置

假设想要从原始像素表示中获得移除与输入图像中位置信息无关的相同特征的能力。一个简单的直觉就是对隐藏层中的所有神经元使用相同的权重和偏置。通过这种方式,每层将从图像中学习到独立于位置信息的潜在特征。

在 TensorFlow 中,如果想添加一个卷积层,可以这样写:

tf.nn.conv2d(unput,filter,strides,padding,use_cudnn_on_gpu=None,data_format=None,name=None)

 参数说明如下:

  • input:张量,必须是 half、float32、float64 三种类型之一。
  • filter:张量必须具有与输入相同的类型。
  • strides:整数列表。长度是 4 的一维向量。输入的每一维度的滑动窗口步幅。必须与指定格式维度的顺序相同。
  • padding:可选字符串为 SAME、VALID。要使用的填充算法的类型。
  • use_cudnn_on_gpu:一个可选的布尔值,默认为 True。
  • data_format:可选字符串为 NHWC、NCHW,默认为 NHWC。指定输入和输出数据的数据格式。使用默认格式 NHWC,数据按照以下顺序存储:[batch,in_height,in_width,in_channels]。或者,格式可以是 NCHW,数据存储顺序为:[batch,in_channels,in_height,in_width]。
  • name:操作的名称(可选)。

池化层

假设我们要总结一个特征映射的输出。我们可以使用从单个特征映射产生的输出的空间邻接性,并将子矩阵的值聚合成单个输出值,从而合成地描述与该物理区域相关联的含义。

一个简单而通用的选择是所谓的最大池化算子,它只是输出在区域中观察到的最大输入值。在 TensorFlow 中,如果想要定义一个大小为 2×2 的最大池化层,可以这样写:

tf.nn.max_pool(value,ksize,strides,padding,data_format='NHWC',name=None)

参数说明如下:

  • value:形状为 [batch,height,width,channels] 和类型是 tf.float32 的四维张量。
  • ksize:长度 >=4 的整数列表。输入张量的每个维度的窗口大小。
  • strides:长度 >=4 的整数列表。输入张量的每个维度的滑动窗口的步幅。
  • padding:一个字符串,可以是 VALID 或 SAME。
  • data_format:一个字符串,支持 NHWC 和 NCHW。
  • name:操作的可选名称。

cnn 卷积神经网络源码 cnn卷积神经网络实例_卷积_03

另一个选择是平均池化,它简单地将一个区域聚合成在该区域观察到的输入值的平均值。简而言之,所有池化操作只不过是给定区域的汇总操作。

CNN 基本上是几层具有非线性激活函数的卷积,以及将池化层应用于卷积的结果。每层应用不同的滤波器(成百上千个)。理解的关键是滤波器不是预先设定好的,而是在训练阶段学习的,以使得恰当的损失函数被最小化。已经观察到,较低层会学习检测基本特征,而较高层检测更复杂的特征,例如形状或面部。

请注意,由于有池化层,靠后的层中的神经元看到的更多的是原始图像,因此它们能够编辑前几层中学习的基本特征。
CNN 在时间维度上对音频和文本数据进行一维卷积和池化操作,沿(高度×宽度)维度对图像进行二维处理,沿(高度×宽度×时间)维度对视频进行三维处理。对于图像,在输入上滑动滤波器会生成一个特征图,为每个空间位置提供滤波器的响应。换句话说,一个 ConvNet 由多个滤波器堆叠在一起,学习识别在图像中独立于位置信息的具体视觉特征。这些视觉特征在网络的前面几层很简单,然后随着网络的加深,组合成更加复杂的全局特征。

二.三维卷积神经网络预测MNIST数字

这个深层网络由两个带有 ReLU 和 maxpool 的卷积层以及两个全连接层组成

from __future__ import division,print_function
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.examples.tutorial.mnist import input_data

minst=input_data.read_data_sets("MNIST_data/",one_hot=True)

#parameters
learning_rate=0.001
training_iters=500
batch_size=128
display_step=10
n_input=484
n_classes=10
dropout=0.85

x=tf.placeholder(tf.float32,[None,n_input])
y=tf.placeholder(tf.float32,[None,n_classes])
keep_prob=tf.placeholder(tf.float32)

def conv2d(x,W,b,strides=1):
    x=tf.nn.conv2d(x,W,strides=[1,strides,strides,1],padding='SAME')
    x=tf.nn.bias_add(x,b)
    return tf.nn.relu(x)

def maxpool2d(x,k=2):
    return tf.nn.max_pool(x,ksize=[1,k,k,1],strides=[1,k,k,1],padding='SAME')

def conv_net(x,weights,biases,dropout):
    x=tf.reshape(x,shape=[-1,28,28,1])
    conv1=conv2d(x,weights['wc1'],biases['bc1'])
    conv1=maxpool2d(conv1,k=2)
    conv2=conv2d(conv1,weights['wc2'],biases['bc2'])
    conv2=maxpool2d(conv2,k=2)
    #reshape conv2 output to match the input of fully connected layer
    fc1=tf.reshape(conv2,{-1,weights['wd1'].get_shape().as_list()[0]})
    fc1=tf.add(tf.matmul(fc1,weights['wd1'],biases['bd1']))
    fc1=tf.nn.relu(fc1)
    fc1=tf.nn.dropout(fc1,dropout)
    out=tf.add(tf.matmul(fc1,weights['out'],biases['out']))
    return out

定义网络层的权重和偏置。第一个 conv 层有一个 5×5 的卷积核,1 个输入和 32 个输出。第二个 conv 层有一个 5×5 的卷积核,32 个输入和 64 个输出。全连接层有 7×7×64 个输入和 1024 个输出,而第二层有 1024 个输入和 10 个输出对应于最后的数字数目。所有的权重和偏置用 randon_normal 分布完成初始化:

weigts={
    'wc1':tf.Variable(tf.random_normal([5,5,1,32])),
    'wc2':tf.Variable(tf.random_normal([5,5,32,64])),
    'wd1':tf.Variable(tf.random_normal([7*7*64,1024])),
    'out':tf.Variable(tf.random_normal([1024,n_classes])),
}
biases={
    'bc1':tf.Variable(tf.random_normal([32])),
    'bc2':tf.Variable(tf.random_normal([64])),
    'bd1':tf.Variable(tf.random_normal([1024])),
    'out':tf.Variable(tf.random_normal([n_classes])),
}

建立一个给定权重和偏置的 convnet。定义基于 cross_entropy_with_logits 的损失函数,并使用 Adam 优化器进行损失最小化。优化后,计算精度:

pred=conv_net(x,weights,biases,keep_prob)
cost=tf.reduxe_mean(tf.nn.softmax_cross_entropy_with_logirs(logits=pred,labels=y))
optimizer=tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
correct_prediction=tf.equal(tf.argmax(pred,1),tf.argmax(y,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf,float32))
init=tf.global_variables_initializer()

启动计算图并迭代 training_iterats次,其中每次输入 batch_size 个数据进行优化。请注意,用从 mnist 数据集分离出的 mnist.train 数据进行训练。每进行 display_step 次迭代,会计算当前的精度。最后,在 2048 个测试图片上计算精度,此时无 dropout。

train_loss=[]
train_acc=[]
test_acc=[]
with tf.Session() as sess:
    sess.run(init)
    step=1
    while step<=traing_iters:
        batch_x,batch_y=mnist.train.next_batch(batch_size)
        sess.run(optimizer,feed_dict={x:bacth_x,y:batch_y,keep_prob:dropout})
        if step%display_step==0:
            loss_train,acc_train=sess.run([cost,accuracy],feed_dict={x:bacth_x,y:batch_y,keep_prob:dropout})
            acc_test=sess.run(accuracy,feed_dict={x:mnist.test.images,y:mnist.test.labels,keep_prob:1.})
            train_loss.append(loss_train)
            train_acc.append(acc_train)
            test_acc.append(acc_test)
        step+=1

画出每次迭代的 Softmax 损失以及训练和测试的精度:

eval_indices=range(0,training_iters,display_step)
plt.plot(eval_indices,train_loss,'k-')
plt,title('softmax loss per iteration')
plt.xlabel('Iteration')
plt.ylabel('Softmax Loss')
plt.show()

plt.plot(eval_indices,train_acc,'k-',label='Train Set Accuracy')
plt.plot(eval_indices,test_acc,'r--',label='Test Set Accuracy')
plt,title('Train an Test Accuracy')
plt.xlabel('Iteration')
plt.ylabel('Accuracy')
plt.legend(loc='lower right')
plt.show()

cnn 卷积神经网络源码 cnn卷积神经网络实例_卷积神经网络_04

cnn 卷积神经网络源码 cnn卷积神经网络实例_池化_05

三.文本情感分析

如果将句子或文档表示为矩阵,则该矩阵与其中每个单元是像素的图像矩阵没有什么区别,矩阵的每一行都是一个表示文本的向量。当然,现在需要定义一个基本单位。一个简单方法是将基本单位表示为字符。另一种做法是将一个单词看作基本单位,将相似的单词聚合在一起,然后用表示符号表示每个聚合(有时称为聚类或嵌入)。位于相邻列中的两个单词最有可能具有某种相关性,这是文本矩阵与图像的主要差异。这里将使用 IMDb 数据集,收集 45000 个高度受欢迎的电影评论样本进行训练,并用 5000 个样本进行测试。TFLearn有从网络自动下载数据集的库,便于创建卷积网络,所以可以直接编写代码。

cnn 卷积神经网络源码 cnn卷积神经网络实例_卷积_06

导入 TensorFlow、tflearn 以及构建网络所需要的模块。然后导入 IMDb 库并执行独热编码和填充:

import tensorflow as tf
import tflearn
from tflearn.layers.core import import input_data,dropout,fully_connected
from tflearn.layers.conc import conv_1d,global_max_pool
from tflearn.layers.merge_ops import merge
from tflearn.layers.estimator import regression
from tflearn.data_utils import to_categorical,pad_sequences
from tflearn.datasets import imdb

加载数据集,用 0 填充整个句子至句子的最大长度,然后在标签上进行独热编码,其中两个数值分别对应 true 和 false 值。请注意,参数 n_words 是词汇表中单词的个数。表外的单词均设为未知。此外,请注意 trainX 和 trainY 是稀疏向量,因为每个评论可能仅包含整个单词集的一个子集。

train,test,_=imdb.load_data(path='imdb.pkl',n_words=10000,valid_portion=0.1)
trainX,trainY=train
testX,testY=test
trainX=pad_sequences(trainX,maxlen=100,value=0.)
testX=pad_sequences(testX,maxlen=100,value=0.)
trainY=to_categorical(trainY,nb_classes=2)
testY=to_categorical(testY,nb_classes=2)

为数据集中包含的文本构建一个嵌入。就目前而言,考虑这个步骤是一个黑盒子,它把这些词汇映射聚类,以便类似的词汇可能出现在同一个聚类中。请注意,在之前的步骤中,词汇是离散和稀疏的。通过嵌入操作,这里将创建一个将每个单词嵌入连续密集向量空间的映射。使用这个向量空间表示将给出一个连续的、分布式的词汇表示。如何构建嵌入,将在讨论RNN时详细讲解:

network=input_data(shape=[None,100],name='input')
network=tflearn.embedding(network,input_dim=10000,output_dim=128)

创建合适的卷积网络。这里有三个卷积层。由于正在处理文本,这里将使用一维卷积网络,这些图层将并行执行。每一层需要一个 128 维的张量(即嵌入输出),并应用多个具有有效填充的滤波器(分别为 3、4、5)、激活函数 ReLU 和 L2 regularizer。然后将每个图层的输出通过合并操作连接起来。接下来添加最大池层,以 50% 的概率丢弃参数的 dropout 层。最后一层是使用 softmax 激活的全连接层:

branch1=conv_1d(network,128,3,padding='valid',activation='relu',regularizer='L2')
branch2=conv_1d(network,128,4,padding='valid',activation='relu',regularizer='L2')
branch3=conv_1d(network,128,5,padding='valid',activation='relu',regularizer='L2')
network=merge([branch1,branch2,branch3],mode='concat',axis=1)
network=global_max_pool(network)
network=dropout(network,0.5)
nerwork=fully_connected(network,2,activation='softmax')

学习阶段使用 Adam 优化器以及 categorical_crossentropy 作为损失函数:

network=regression(network,optimizer='adam',learnig_rate=0.001.loss='categorical_crossentropy',name='target')

在训练中,采用 batch_size=32,观察在训练和验证集上达到的准确度。在通过电影评论预测情感表达时能够获得 79% 的准确性:

model=tflearn.DNN(network,tensorboard_verbose=0)
model.fit(trainX,trainY,n_epoch=5,shuffle=True,validation_set=(testX,testY),show_metric=True,bacth_size=32)

四.各种网络介绍

(1)VGG16和VGG19

cnn 卷积神经网络源码 cnn卷积神经网络实例_卷积_07

使用 VGG16 和一般的 DCNN 模型来进行特征提取。这段代码通过从特定图层中提取特征

from keras.applications.vgg16 import VGG16
from keras.models import Model
from keras.preprocessing import image
from keras.application.vgg16 import preprocess_input
import numpy as np

从网络中选择一个特定的图层,并获取输出的特征:

base_model=VGG16(weights='imagenet',include_top=True)
for i,layer in enumerate(base_model.layers):
    print(i,layer.name,layer.output_shape)
    
model=Model(input=base_model.input,output=base_model.get_layer('block4_pool').output)

 提取给定图像的特征,代码如下所示:

img_path='cat.jpg'
img=image.load_image(img_path,target_size=(224,224))
x=image.img_to_array(img)
x=np.expand_dims(x,axis=0)
x=preprocess_input(x)
features=model.predict(x)

(2)ResNet

ResNet(残差网络)这个网络是非常深的,可以使用一个称为残差模块的标准的网络组件来组成更复杂的网络(可称为网络中的网络),使用标准的随机梯度下降法进行训练。 与 VGG 相比,ResNet 更深,但是由于使用全局平均池操作而不是全连接密集层,所以模型的尺寸更小。

cnn 卷积神经网络源码 cnn卷积神经网络实例_卷积_08

(3)Inception

其主要思想是使用多个尺度的卷积核提取特征,并在同一模块中同时计算 1×1、3×3 和 5×5 卷积。然后将这些滤波器的输出沿通道维度堆叠并传递到网络中的下一层.

cnn 卷积神经网络源码 cnn卷积神经网络实例_池化_09

最近对深度神经网络的研究主要集中在提高精度上。具有相同精度的前提下,轻量化 DNN 体系结构至少有以下三个优点:

  1. 轻量化CNN在分布式训练期间需要更少的服务器通信。
  2. 轻量化CNN需要较少的带宽将新模型从云端导出到模型所在的位置。
  3. 轻量化CNN更易于部署在FPGA和其他有限内存的硬件上。