卷积神经网络(Convolutional neural network,CNN),是一多层的神经网络架构,是以类神经网络实现的深度学习,在许多实际应用上取得优异的成绩,尤其在影像对象识别的领域上表现优异。

传统的多层感知器(Multilayer Perceptron,MLP)可以成功的用来做影像识别,如之前所介绍,可以用来做MNIST 手写数字辨识,但是由于多层感知器(Multilayer Perceptron,MLP)在神经元之间采用全连接的方式(Full connectivity),当使用在较高分辨率或是较高维度的影像或数据时,便容易发生维度灾难而造成过度适合(Overfitting)

 例如,考虑MNIST的手写输入数据为28x28分辨率,考虑神经元全连接的方式
(Full connectivity),连接到第一个隐藏层神经元便需要28x28=784个权重(Weight)。如果考虑一RGB三信道色彩图像输入,如CIFAR-10为32x32x3的图像分辨率,连接到第一个隐藏层神经元便需32x32x3=3072个权重(Weight)。那么如果是200x200x3的图像分辨率,便需要120000个权重,而这只是连接到隐含层的第一个神经元而已,如果隐含层有多个神经元,那么连接权重的数量必然再倍数增加。

像这样子的网络架构,没有考虑到原始影像数据各像素(pixel)之间的远近,或是密集关系,只是一昧的将全部像素(pixel)当各个输入的特征值,连接到隐含层神经元,造成爆量增加的权重不仅是一种浪费,因为实际上不须这么多的权重,效果不仅没增加,只是增加了运算的负荷,而且很可能会造成过度适合(Overfitting)。而卷积神经网络(Convolutional neural network,CNN)被提出来后,可以有效的解决此一问题。

事实上卷积神经网络(Convolutional neural network,CNN)设计的目标就是用来处理以多数组型态表达的数据,如以RGB三信道表达的彩色图片。CNN和普通神经网络之间的一个实质差别在于,CNN是对原始图像直接做操作,而传统神经网络是人为的先对影像提取特征(例如灰阶化,二值化)才做操作。

CNN有三个主要的特点。

1.感知区域(Receptive field):可采用3维的图像数据(width,height,depth)与神经元连接方式,实际上也可以直接采用2维的图像数据,但隐含层内部的神经元只与原本图像的某一小块区域做链接。该区块我们称之为感知区域(Receptive field)

2.局部连结采样(Local connectivity):根据上述感知区域(Receptive field)的概念,CNN使用过滤器(filters)增强与该局部图形空间的相关性,然后堆栈许多这样子的层,可以达到非线性滤波的功能,且扩及全局,也就是允许网络架构从原图小区块的较好的特征值代表性,组合后变成大区块的特征值代表性。

3.共享权重(Shared weights):使用的过滤器(filters)是可以重复使用的,当在原始图像产生一特征图(Feature Map)时,其权重向量(weights vector)及偏误(bias)是共享的。这样可以确保 在该卷积层所使用的神经元会侦测相同的特征。并且即使图像位置或是有旋转的状态,仍然可以被侦测。

这三个特点使得CNN在图像辨识上有更好的效果。在实际操作上的三个基础分别是: 区域感知域(Local Receptive field),卷积(Convolutional ),池化(pooling)。

我们可以从下面图形化的实际操作来理解它的意思。

卷积层(Convolutional Layer )

下图1,2说明在卷积层的运作方式,假设原始影像为一32x32x3维度,我们可以任意给定一卷积核(filter),其卷积核的值即为权重(weight)。其大小可以为5x5x3,3为与原影像有一样的深度(RGB三信道),如果输入影像为一灰阶单信道色彩,那么卷积核大小5x5即可。卷积核大小3x3或5x5或7x7...等等可以自定义,通常为奇数维度。

接着与原图像相同的小区域计算点积,得到的一个值便是在新的feature Map的一个新元素值。卷积核会在原始的图像或者前一个图像(Map)按照Stride(步伐)的设定移动,直到扫描完全部图像区域,这样便会产生一个新的Feature Map。便是下图图3~图5所示,在这个步骤中产生单一新feature Map所使用的权重W,也就是卷积核是共享的,并且共享同一个偏误(bias),通常初始预设偏误(bias)可以为0。这样确保这一新的feature map侦测的是该原始图像同一特征。

这边要注意,上图3~5只是单纯说明当卷积操作时如何作点积取和,实际在CNN神经网络运算里,产生的新Feature Map里的单一元素便是连接到一个神经元做运算,故其运算应当如下公式,这就是一般神经网络向前传递的公式形式:

W便是kxk维卷积核,Xij为前一个图像的元素值,i,j为其index。

b为偏误(bias) 预设可以为0。

S()为一激励函数可以为Sgmoid或ReLu或其他函数,如下图7。

y为输出的值,便是产生一新feature Map上其中的一个元素值。

也就是说卷积层的每一个feature map是不同的卷积核在前一层图像(map)上作卷积并将对应元素累加后加一个偏误(bias),再使用激励函数如sigmod得到的。

另外要注意的是,卷积层要产生几个新的feature map个数是在网络架构初始化指定的,而要产生几个新的feature Map,便要使用相同数量的不同卷积核。例如下图8及图9所示,使用6个不同的卷积核便可产生6个feature Map。

而feature map的大小是由卷积核和上一层输入图像的大小决定的,假设上一层的图像大小是n*n、卷积核的大小是k*k,则该层的feature map大小是(n-k+1)*(n-k+1),比如上图3~5,从7*7的图像大小,产生3x3 feature map  (3=7-5+1),当然这是在预设stride=1的情状下。

<图6>Stride=1,卷积后产生一个Activation Map,也就是Feature Map。

<图7>常见激励函数

<图8>使用第二个不同的卷积核(Filter)产生第二个feature map

至此,我们来以下图9计算一下使用了多少参数(w权重及b偏误),及多少连接到神经元。原始图像大小为32x32,使用6个不同的5x5 卷积核(filter),产生了6个新feature map。产生的新featue map大小为28x28,因为32-5+1=28。一个新feature map所使用参数数量为:5x5+1=26,也就是5x5个W权重及1个偏误(bias),6个新feature map所使用参数数量为:6x(5x5+1)=156个参数,那么一个新feature map所须连接数量为:(5x5+1)x28x28=20384,也就是28x28个神经元各有(5x5+1)个权重及偏误连接。那么6个新feature map所须连接数量为(5x5+1)x28x28x6=122304。这个连接方式及数量其实便是LetNet-5第一个卷积层的状况。

有关卷积(Convolutional)的操作也可以参阅底下一般图像处理领域的操作。其操作本质上便
是对影像作滤波(filter)

OPENCV(7)--2D Convolution ,Image Filtering and Blurring (旋积,滤波与模糊)

另外使用不同的卷积核(kernel filter),便是对影像作处理找出影像特征,如找出"边","角点"。

OPENCV(9)--Image Gradients(图像梯度)

<图9>使用6个不同的卷积核(Filter),产生6个feature map

底下图10是用来说明不同的Stride设定,在卷积后,所产生的新Map尺寸便会不同。
例如原本图像为7x7(N=7),而且卷积核为3x3(F=3),那么当sride=1时,所卷积后所产生的新图像大小为5x5,计算方式(7-3)/1+1=5,如果stirde改成2,那么新产生的图像大小为3x3,计算方式为(7-3)/2+1=3。

<图10>

底下图11说明Padding的作法,原本的图像在经过卷积后,原本的大小必然会缩小,如原本7x7经由卷积核3x3及stride=1,卷积后变成了5x5大小。那么如何保持卷积后大小还能不变呢?

便是可以使用Padding的方式,Zero Pad便是先在原始图像外围先补0(注意一般在计算机图像计算里数值0代表黑色,这也就是会产生黑框),那么原本7x7影像便会成为9x9大小,这时再作卷积,(9-3)+1=7,便还是可以得到7x7原本的大小。

有关Pading的方式除了外围补0外也可以补1(如果是二值化影像便是白边),也可以补跟他相邻元素一样的值。其相关操作在一般的图像处理书籍或数据皆有相关说明,可再自行参阅。<图11>

池化层(Pooling Layer)

     池化层紧接在卷积层之后,是将前一层的输入信息作压缩。池化通常使用2x2核并且stride=2进行卷积。可以使用均值方式计算,但通常使用取最大值为较为普遍的作法,称之为Max Pooling.如下图12,13。下图12原本为28x28图像,经Pooling 后大小变为14x14,下图13解释了其Max pooling的操作方式。另外通常在这一层不再作计算权重及偏误以及激励函数。只单纯Max pooling。

 <图12>

 <图13>

CNN神经网络架构,最后仍有全连接层(Fully connected Layer),其操作其运算方式便如同一般的MLP方式。整个CNN神经网络仍然使用BP反向传递算法计算误差后更新权重。

 <图14>全连接层

底下我们简单实作一个简单的CNN网络,使用MNIST数据集,并使用Tensorflow来实现。
实际程序使用Python3.5及Tensorflow R1.0.1在win10环境。

首先如下图15,我们自定义一个CNN架构,在这个架构里,C1,C3的卷积层使用Padding的技术,故卷积后,尺寸大小不变。S2,及S4为pooling层,F5为一个1024神经元与S4作全连接,最后输出10 个数字类别的可能机率。

在Tensorflow 理定义padding='SAME',如下程序在卷积层会自动使用padding成一样尺寸。

def conv2d(img, w, b):

return tf.nn.relu(tf.nn.bias_add\

                      (tf.nn.conv2d(img, w,\

                                    strides=[1, 1, 1, 1],\

                                    padding='SAME'),b))

<图15>自定义一个CNN网络架构

底下为程序初始参数设定,及简单的解释主要程序。

 

# Parameters
learning_rate = 0.001
training_iters = 400000 #迭带400000次
batch_size = 128   #使用minibatch方式
display_step = 10

# Network Parameters
n_input = 784 # MNIST data input (img shape: 28*28)
#原本MNIST数据集为1x784,会被reshape回28x28,当作原始输入
n_classes = 10 # MNIST total classes (0-9 digits)
dropout = 0.75 # Dropout, probability to keep units

#dropout = 0.75意义为:为了减少过度适合(Overfitting)的问题,应用了丢弃(dropout)正则化技术。
#意旨在神经网络中丢弃一些连接的单元(输入,隐藏,和输出),决定丢弃那些神经元是随机
#的也可以用机率决定。

wc1 = tf.Variable(tf.random_normal([5, 5, 1, 32])) # 5x5 conv, 1 input, 32 outputs
#32代表第1个卷积层要产生32个新的feature map
wc2 = tf.Variable(tf.random_normal([5, 5, 32, 64])) # 5x5 conv, 32 inputs, 64 outputs
#64代表第2个卷积层要产生64个新的feature map
#5,5则是5x5的卷积核(filter权重)
wd1 = tf.Variable(tf.random_normal([7*7*64, 1024])) # fully connected, 7*7*64 inputs, 1024 outputs
wout = tf.Variable(tf.random_normal([1024, n_classes])) # 1024 inputs, 10 outputs (class prediction)
#1024则是全连接层的1024个神经元数量,n_classes=10,代表0-9的数字类别

# Convolution Layer
conv1 = conv2d(_X,wc1,bc1)
conv1 = max_pool(conv1, k=2)
conv1 = tf.nn.dropout(conv1,keep_prob)
#定义第1层卷积层及pooling层并使用dropout正则化

# Convolution Layer
conv2 = conv2d(conv1,wc2,bc2)
conv2 = max_pool(conv2, k=2)
conv2 = tf.nn.dropout(conv2, keep_prob)
#定义第2层卷积层及pooling层并使用dropout正则化

# Fully connected layer
dense1 = tf.reshape(conv2, [-1, wd1.get_shape().as_list()[0]]) # Reshape conv2 output to fit dense layer input
dense1 = tf.nn.relu(tf.add(tf.matmul(dense1, wd1),bd1)) # Relu activation
dense1 = tf.nn.dropout(dense1, keep_prob) # Apply Dropout
#定义F5层全连接层使用ReLu激励函数及dropout正则化

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=pred))
#计算成本函数的方式并使用softmax_cross_entropy_with_logits
#注意,在Tensorflow R1.0.1版,注意这两个参数(labels=y, logits=pred)的放法跟以前版本不同
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
#使用AdamOptimizer优化。

<图16>底下为训练结果及测试结果

附完整程序:

# Import MINST data
import input_data
#mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
import tensorflow as tf

# Parameters
learning_rate = 0.001
training_iters = 400000
batch_size = 128
display_step = 10

# Network Parameters
n_input = 784 # MNIST data input (img shape: 28*28)
n_classes = 10 # MNIST total classes (0-9 digits)
dropout = 0.75 # Dropout, probability to keep units

# tf Graph input
x = tf.placeholder(tf.float32, [None, n_input])
y = tf.placeholder(tf.float32, [None, n_classes])
keep_prob = tf.placeholder(tf.float32) #dropout (keep probability)

# Create model
def conv2d(img, w, b):
    return tf.nn.relu(tf.nn.bias_add\
                      (tf.nn.conv2d(img, w,\
                                    strides=[1, 1, 1, 1],\
                                    padding='SAME'),b))

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

# Store layers weight & bias

wc1 = tf.Variable(tf.random_normal([5, 5, 1, 32])) # 5x5 conv, 1 input, 32 outputs
wc2 = tf.Variable(tf.random_normal([5, 5, 32, 64])) # 5x5 conv, 32 inputs, 64 outputs
wd1 = tf.Variable(tf.random_normal([7*7*64, 1024])) # fully connected, 7*7*64 inputs, 1024 outputs
wout = tf.Variable(tf.random_normal([1024, n_classes])) # 1024 inputs, 10 outputs (class prediction)

bc1 = tf.Variable(tf.random_normal([32]))
bc2 = tf.Variable(tf.random_normal([64]))
bd1 = tf.Variable(tf.random_normal([1024]))
bout = tf.Variable(tf.random_normal([n_classes]))

# Construct model
_X = tf.reshape(x, shape=[-1, 28, 28, 1])

# Convolution Layer
conv1 = conv2d(_X,wc1,bc1)
# Max Pooling (down-sampling)
conv1 = max_pool(conv1, k=2)
# Apply Dropout
conv1 = tf.nn.dropout(conv1,keep_prob)

# Convolution Layer
conv2 = conv2d(conv1,wc2,bc2)
# Max Pooling (down-sampling)
conv2 = max_pool(conv2, k=2)
# Apply Dropout
conv2 = tf.nn.dropout(conv2, keep_prob)

# Fully connected layer
dense1 = tf.reshape(conv2, [-1, wd1.get_shape().as_list()[0]]) # Reshape conv2 output to fit dense layer input
dense1 = tf.nn.relu(tf.add(tf.matmul(dense1, wd1),bd1)) # Relu activation
dense1 = tf.nn.dropout(dense1, keep_prob) # Apply Dropout

# Output, class prediction
pred = tf.add(tf.matmul(dense1, wout), bout)

#pred = conv_net(x, weights, biases, keep_prob)
# Define loss and optimizer
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=pred))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)

# Evaluate model
correct_pred = tf.equal(tf.argmax(pred,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

# Initializing the variables
#init = tf.initialize_all_variables()
init =tf.global_variables_initializer()
# Launch the graph
with tf.Session() as sess:
    sess.run(init)
    step = 1
    # Keep training until reach max iterations
    while step * batch_size < training_iters:
        batch_xs, batch_ys = mnist.train.next_batch(batch_size)
        # Fit training using batch data
        sess.run(optimizer, feed_dict={x: batch_xs, y: batch_ys, keep_prob: dropout})
        if step % display_step == 0:
            # Calculate batch accuracy
            acc = sess.run(accuracy, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 1.})
            # Calculate batch loss
            loss = sess.run(cost, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 1.})
            print ("Iter " + str(step*batch_size) + ", Minibatch Loss= " + "{:.6f}".format(loss) + ", Training Accuracy= " + "{:.5f}".format(acc))
        step += 1
    print ("Optimization Finished!")
    # Calculate accuracy for 256 mnist test images
    print ("Testing Accuracy:", sess.run(accuracy, feed_dict={x: mnist.test.images[:1024], y: mnist.test.labels[:1024], keep_prob: 1.}))

授权转载自 http://arbu00.blogspot.jp/2017/03/2-tensorflowconvolutional-neural.html?m=1