我是靠谱客的博主 贪玩草丛,最近开发中收集的这篇文章主要介绍tensorflow实现卷积神经网络,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

卷积神经网络的简介:

相对于传统的图像分类,卷积神经网络(Convolutional Neural Network, CNN)提取的特征能够达到很好的效果,同时不需要将特征提取和分类训练两个过程分开。

CNN的构成:
(1)输入层,在CNN中,输入层与全连接网络的输入层类似,是一张图像的像素矩阵。
(2)卷积层,它是CNN中最重要的组成部分,卷积层中每一个节点的输入只是上一层神经网络的一小块(局部感受野),这个小块尺寸(滤波器的尺寸)经常使用3*3或者5*5。卷积层试图将神经网络中的每个小块进行更加深入地分析从而得到更加抽象的特征。一般来说,通过卷积层处理过的节点矩阵深度会增加(也就是说使用滤波器的个数比上一层的feature map数量多)。下面我们详细介绍一下卷积层的实现过程。
下图显示了卷积层的结构,这个部分称为滤波器。滤波器可以将当前神经网络上的一个子节点矩阵(局部感受野)转化为下一层神经网络上的一个单位节点矩阵(指的是一个长和宽均为1,但是深度不限的节点矩阵)。
这里写图片描述
值得注意的是,在一个卷积层中,滤波器说处理的节点矩阵的长和宽都是由人工指定的,虽然节点矩阵是三维的,但过滤器的尺寸只需要两个维度(以RGB图像输入层和卷积层相连的结构为例,只需要指定滤波器尺寸的长和宽,另一个维度是由输入决定的,对于RGB图像,由于它有三个通道,所以第三个维度是3,对于灰度图像,它的第三个维度为1;如果卷积操作在中间层,则第三个维度取决于上一层feature map的个数),同时滤波器另一个需要人工设定的参数为处理得到单位节点矩阵的深度,这个设置成为滤波器的深度。注意的是,过滤器的尺寸指的是一个过滤器输入节点矩阵的大小,而深度值得是输出单位节点矩阵的深度(也就是当前层你想得到的feature map的数量)。
下面以一个例子来说明卷积操作的过程。假设将一个2*2*3的节点矩阵转化为1*1*5的单位节点矩阵。单位矩阵中的第i个节点的取值计算公式为:
这里写图片描述
其中, ax,y,z a x , y , z 为过滤器节点 (x,y,z) ( x , y , z ) 的取值, f f 为激活函数。下图显示了给定a,w0b0情况下,使用ReLU作为激活函数时 g(0) g ( 0 ) 的计算过程,同样方法可以计算 g(1)g(2)g(3)g(4) g ( 1 ) 、 g ( 2 ) 、 g ( 3 ) 、 g ( 4 ) 代表点积,也就是对应元素乘积的和。
这里写图片描述
卷积层结构的前向传播过程就是讲一个过滤器从神经网络当前层的左上角移动到右下角,并且在移动过程中计算每一个对应的单位矩阵得到的。在这个过程中,首先将这个过滤器用于左上角子矩阵,然后移动到左下角矩阵,再到右上角矩阵,最后到右下角矩阵。下图显示了过滤器在移动过程中计算得到的结果与新矩阵中节点的对应关系。
这里写图片描述
从上面的过程中我们能够看到,当使用的过滤器的尺寸不为1*1时,卷积后得到的矩阵的尺寸小于当前层矩阵的尺寸,为了避免尺寸的变化,我们可以将当前层的边界加入全零填充(zero-padding)。这样可以使得卷积层前向传播结果矩阵的大小与当前层矩阵保持一致。示意图如下:
这里写图片描述
除了使用全0填充,还可以通过设置过滤器的步长来调整结果矩阵的大小。下面显示了步长为2使用全0填充时,卷积层前向传播的过程:
这里写图片描述
卷积层需要人工设定的参数有:滤波器的尺寸、深度(想得到的feature map的个数)、是否使用全零填充、滤波器的步长。
(3)池化层,它可以有效地缩小矩阵的尺寸,从而减少最后全连接层中参数的数量,使用池化层既可以加快计算速度也有防止过拟合问题的作用。池化层需要人工设定的参数有滤波器的尺寸,是否使用全0填充以及滤波器的步长。
池化主要分为两类:最大池化和平均池化。其中最大池化选择矩阵窗口中最大的数值,该方式是使用最多的池化层结构,保留最显著的特征,并提高模型畸变容忍能力;另一种均值池化是选择窗口中所有像素的均值。它不会改变三维矩阵的深度,但是它可以缩小矩阵的大小,它可以看做将一张分辨率较高的图像转化为分辨率较低的图片。下图显示了3*3*2节点矩阵经过全0填充且步长为2的最大池化的过程:
这里写图片描述
(4)全连接层,在神经网络的最后一般会是由1到2个全连接层来给出最后的分类结果,我们可以将卷积层和池化层看做特征提取的过程,特征提取之后需要使用全连接层完成分类任务。
(5)softmax层,它主要用于分类问题,将当前样例转化为属于不同类别的概率分布情况。具体的softmax实现过程可以参考这篇博客。
CNN全部的过程流程图如下:
这里写图片描述

CNN的特点:
(1) 卷积的权值共享结构,可以大幅减少神经网络的参数数量,防止过拟合的同时又降低了神经网络模型的复杂度,同时权值共享还赋予了卷积网络对平移的容忍性。
(2) 局部连接,可以大幅减少神经网络的参数数量,防止过拟合的同时又降低了神经网络模型的复杂度。
(3) 池化层的降采样,进一步降低输出参数量,并赋予了对轻度形变的容忍性,提高了模型的泛化能力。

局部连接:
同时CNN采用视觉感受野的概念,也就是说每一个像素在空间上和周围的像素点有密切联系,然而对于距离太遥远的像素点不一定有什么关联。对于神经网络来说,每一个神经元不需要接收全部像素点的信息,只需要接收局部像素点作为输入,最后将所有这些神经元收到的局部信息综合起来就可以得到全局的信息。这种连接方式叫做局部连接。

权值共享:
我们的局部连接方式是卷积操作,也就是默认每一个隐含节点的参数数量都完全一样。所以不论图像大小,每个隐含节点参数的数量仅仅取决于卷积核的尺寸,参数量只跟卷积核的大小有关,这也就是我们所说的权值共享。
权值共享从直观上理解,可以使得图像的内容不受位置的影响。以MNIST手写体为例,无论数字“1”在左上角还是右下角,图片的种类都是不变的。因为在左上角和右下角使用的权值都是一样的,所以通过卷积层之后无论数字在图像的哪个位置,得到的结果都一样。
但是对于CNN来说,如果只使用一个卷积核,我们只能够提取出一种卷积核滤波的结果,这不是我们期望得到的结果,我们可以增加卷积核的数量来提取更多的特征。只要我们提供的卷积核的数量足够多,就能够让卷积层抽象出有效而丰富的高阶特征。每一个卷积核滤波得到的图像就是一类特征的映射,即feature map。一般来说,我们使用100个卷积核放在第一层就已经足够了。

下面我们通过一个具体的实例,来比较一下相对于全连接网络,CNN如何通过权值共享以及局部连接减少参数数量。
比如我们有一个1000像素*1000像素的图片,并且假定它是灰度图像,即只有一个颜色通道。
1. 如何通过局部连接减少参数数量?
下图显示了全连接和局部连接:
这里写图片描述
对于全连接网络,我们假设全连接隐含层的节点与图像像素数相同,即1000*1000=100万个,则上述一个全连接层所需要的参数数量为:100万(像素的数量)*100万(隐含节点的数量)=1万亿个,对于一个全连接层就需要这么多参数,所以需要我们减少参数的数量。
对于CNN采用局部连接方法,假设局部感受野的大小为10*10,同样采用隐含层节点数为100万,则需要的节点数量为10*10*100万=1亿个,相对于全连接,参数的数量缩小为原来的10000倍。
2. 如何通过共享权值减少参数数量?
通过上述可以看出,即使使用局部连接,参数的数量仍然很多,于是CNN采用了另一种策略——权值共享,所谓的权值共享也就是上述的100万个隐层节点全部使用相同的参数。经过上述局部链接可以使得参数的数量下降为1亿个,但是使用权值共享之后使用的参数的数量为10*10=100个,也就是感受野(滤波器)的大小,大大降低了参数的数量。为了获得更多的特征,我们一般不仅仅使用一个滤波器,可以使用多个滤波器提取出更加丰富的特征。

结论:通过局部连接和权值共享大大减少了参数的数量,同时卷积层参数的个数和图片的大小无关,它只和滤波器的尺寸、深度以及当前层节点矩阵的深度有关。值得注意的是,虽然需要训练的参数的数量减少了,但是隐含节点的数量并没有下降,隐含节点的数量只跟卷积的步长有关。如果步长为1,那么隐含节点的数量和输入的图像像素数量一致;如果步长为5,那么每5*5的像素才需要一个隐含节点,那么隐含节点的数量就是输入像素数量的1/25。

卷积的好处:
(1)不管图像尺寸如何,需要训练的权值数量只跟卷积核的大小、卷积核数量有关。可以使用非常少的参数来处理任意大小的图像。
(2)每一个卷积层提取的特征,在后面的层中都会抽象组合成更高阶的特征。而且多层抽象的卷积网络表达能力更强,效率更高,相比只是用一个隐含层提取全部高阶特征,反而可以节省大量的参数。

下面我们使用tensorflow实现使用两个卷积层加一个全连接层构建的一个简单但是有代表性的卷积网络。

# -*- coding: utf-8 -*-
"""
Created on Thu Mar 22 15:51:49 2018

@author: wangyule
"""

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("MNIST/", one_hot=True)
sess = tf.InteractiveSession()

def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

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

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

x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])
x_image = tf.reshape(x, [-1, 28, 28, 1])

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

W_fc1 = weight_variable([7*7*64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv), 
                                             reduction_indices=[1]))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

tf.global_variables_initializer().run()
for i in range(20000):
    batch = mnist.train.next_batch(50)
    if i%100 == 0:
        train_accuracy = accuracy.eval(feed_dict = {x: batch[0], y_: batch[1], 
                                                    keep_prob: 1.0})
        print("step %d, training accuracy %g" % (i, train_accuracy))
    train_step.run(feed_dict = {x: batch[0], y_: batch[1], keep_prob: 0.5})

print("test accuracy %g" % accuracy.eval(feed_dict = {x: mnist.test.images, 
                                                      y_: mnist.test.labels, 
                                                      keep_prob: 1.0}))

1.def weight_variable(shape):
因为后期多次用到权重初始化,所以将权重初始化封装成函数,该函数将权重初始化为截断的正太分布,关于截断正太分布的详细介绍可以看该博客。

2.def bias_variable(shape):
同样后期多次使用偏置初始化,也将其封装成函数,该函数将偏置增加一些小的正值0.1。因为我们使用ReLU激活函数,所以用此方法避免死亡节点。

3.def conv2d(x, W):
定义卷积操作,其中tf.nn.conv2d代表使用tensorflow实现2为卷积函数,x代表输入,W是卷积参数,比如W=[5,5,1,32]代表卷积核的尺寸为5*5;1代表只有一个通道(如果该层前一层是输入层,改数字代表输入图像的通道数,也就是对于灰度图像只有一个通道,彩色图像为三个通道;如果该层的上一层同样是卷积层,改数字代表上一层中的feature map的数量)。32代表卷积核的数量,也就是经过该卷积层获得多少个feature map图像。strides代表卷积模板移动的步长,都是1代表会不遗漏地划过图片中的每个点。padding代表边界处理的方式,SAME代表给边界进行全0填充和输入保持相同的尺寸,VALID代表不进行填充。

4.def max_pool_2x2(x):
定义池化操作,其中tf.nn.max_pool代表使用最大池化,ksize代表使用池化滤波器的尺寸,strides代表步长,padding指明填充方式。文中的设置方式为:使用池化滤波器的模板大小为2*2,将22的像素转化为1*1的像素,步长设置为横竖方向均为2。如果步长为1,则会得到一个尺寸不变的图片。

5.W_fc1 = weight_variable([7*7*64, 1024])
这里为全连接层设置参数,我们需要经过计算确定隐含层节点的数量。对于28*28的图像,经过一次卷积(不改变尺寸)和池化(将原来的尺寸,在长宽都缩减为原来的一般),将尺寸转化为14*14,由于文中使用了两层卷积和池化,所以最终的尺寸为7*7,而最后一层的卷积层形成64个feature map,所以在使用全连接是使用的参数的数量为7*7*64,并且设置第一个全连接层的隐含节点数为1024个。

6.h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
将卷积层得到的结果进行变形,变化为行(代表样本的数量,-1代表数量不确定)不确定,列为7*7*64的矩阵。

最后

以上就是贪玩草丛为你收集整理的tensorflow实现卷积神经网络的全部内容,希望文章能够帮你解决tensorflow实现卷积神经网络所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(68)

评论列表共有 0 条评论

立即
投稿
返回
顶部