(六)TensorFlow实现卷积神经网络

       卷积运算具有3个重要特性:稀疏连接,参数共享和等变表示,卷积层通过这些特性来改善机器学习系统,

1 卷积运算的稀疏连接

        在全连接神经网络中,参数矩阵的每一个参数全部并且仅仅描述了一个输入单元与一个输出单元间的交互关系。这样做当输入的数据增多时,参数的数量也会变得巨大。

        卷积运算具有稀疏连接(Sparse Connectivity)的特性。这通过将核的大小限制为远小于输入的大小来达到。假设有m个输入和n个输出,那么运行一个矩阵乘法需要m*n个参数;如果我们限制每一个输出只连接到k个输入,那么稀疏的连接方式只需要 k*n 个参数。

        卷积运算的稀疏连接借鉴了感受神经感受野的概念,因为图像的空间联系也是局部的像素联系较为紧密,而距离较远的像素相关性则较弱,所以每一个神经元不需要接受全部像素点的信息,只需要接受相互关联的一小块区域内的像素点作为输入即可,之后将所有神经元接受到的局部信息在更高层进行综合,就可以得到全局的信息。

       通过稀疏连接,我们达到了减少权重参数数量的目的。减少权重参数数量有两个好处:一是降低计算的复杂度;二是过多的连接会导致严重的过拟合,减少连接数可以提升模型的泛化性。

2 卷积运算的参数共享

        参数共享是指相同的参数被用在一个模型的多个函数中,在全连接神经网络中,计算每一层的结果时,权重矩阵中的每一个元素只使用一次。然而,在卷积神经网络中,核的每一个元素会作用在输入的每一位置上。

        卷积运算中的参数共享机制会显著的降低参数的数量。相比于全连方式中每一神经元都需要学习一个单独的参数集合而言,在卷积运算中每一层神经元只需要学习一个卷积核大小的参数集合即可。

       例如,每一个隐藏单元都与图像中10*10的像素相连,也就是每一个隐藏单元都拥有独立的100个参数。假设隐藏单元是由卷积运算得到,那么每一个隐藏单元的参数都完全一样,参数数量发生了显著的降低。

       参数共享并不会改变前向传播的运行时间,它只是显著地把需要存储地权重参数数量降低至 k 个。得益于稀疏连接和权重共享,卷积运算在存储需求和统计效率方面极大优于稠密矩阵地乘法运算。

3 卷积运算地平移等变

       针对卷积操作,参数共享的机制使得神经网络对输入的平移具有等变的性质。具体地说,如果令函数 g 完成 对卷积操作的输入进行任意的平移,那么卷积函数对于 g 具有等变性。

       卷积的平移等变是一个很有用的性质。当处理图像数据时,这意味着卷积产生了一个二维映射来表明输入中某些存在的特征,如果我们移动输入中的某些对象,平移等变就意味着在输出中特征也会进行一定的移动。

4 多卷积核

       人眼识别物体的方式是从点、线和面开始,视觉神经元接受光信号后,每一个神经元只接受一个区域的信号,并提取出点和线的特征;然后将点和线的信号传递到后面一层的神经元再组合成高阶的特征,比如直线、拐角等;再继续抽象组合,得到一些图像,比如正方形、圆形等;之后再继续抽象组合,得到人脸中的眼睛、鼻子和嘴等五官;将这些组合得到的五官组成一张脸,就完成了生物识别的过程。

       使用卷积核的目的也是在于对图中基本特征的提取。如果我们只有一个卷积核,那就只能提取一种卷积核滤波的结果,即只能提取图片中的某一个特征。尽管图像中最基本的特征很少,但是很多情况下我们仍然希望多提取一些特征,这可以通过增加卷积核的数量来完成。

       产生一个卷积层所需要的卷积核的长和宽都是编写代码时人工指定的,较常用的卷积核长宽尺寸有3*3或5*5。上面所述的多卷积核通常称为卷积核的深度,卷积核的深度指的是卷积核的数量,这也是需要人工指定的参数。

5 卷积层的代码实现

       TensorFlow提供了一系列能够进行卷积运算的函数,其中 nn.conv2d()函数使用较频繁。

        conv2d()函数的第一个参数为当前卷积层的输入矩阵。注意该矩阵是一个四维矩阵,赋值格式是一个长度为4的列表,比如[1,32,32,3]。其中代表第一个维度的参数对应一个输入batch,例如在输入层 0 代表第一张图片,1代表第二张图片;代表第二个维度和第三个维度的参数对应这个batch的宽度和高度,如32,32 表示这张图片大小为 32*32; 代表第四个维度的参数对应这张图片的深度,如果图片是 RGB三通道彩色模式,则深度值为 3, 如果图片是单通道黑白模式,则深度值为 1。

        cov2d()函数的第二个参数为当前卷积层使用的卷积核。这也是一个四维矩阵,赋值的格式也是一个长度为 4 的列表,比如 [3,3,3,64]。列表中的前两个数值表示卷积核的大小,如3*3,第三个数值是输入的矩阵数据的深度,第四个数值是本层卷积核的深度。

        在卷积运算函数中,可以设置padding参数的值来使用是否开启全0填充。有ASME和VALID两种选择,当设置padding为SAME时表示添加全0填充,为VALID时表示不添加全0填充。在默认情况下,是不使用全0填充的。

        除了是否使用全0填充外,还可以通过设置过滤器移动的步长来调整结果矩阵的大小。卷积运算函数中的strides 参数能够指定不同维度上过滤器的移动的步长,赋值的格式是一个长度为4的列表。比如 [1,2,2,1] 表示长方向移动的步长为2,宽方向移动的步长为2。虽然提供的是一个长度为4的数组,但是第一个和最后一个数字要求一定是 1, 这是因为卷积层的步长只对矩阵的 长和宽有效。

       下面的程序实现了一个卷积层的前向传播过程,使用的输入数据是矩阵 I,滤波器是矩阵 fiter_weight, 卷积的过程中使用了全0填充并且步长是1。代码如下:

import tensorflow as tf
import numpy as np

#使用Numpy工具初始化一个名为M的数组,大小为4*4,数组类型为float32
#并使用Numpy的reshape()函数调整输入的格式
#注意,M不会被Tensorflow识别为张量
M = np.array([[[2],[1],[2],[-1]],[[0],[-1],[3],[0]],[[2],[1],[-1],[4]],
             [[-2],[0],[-3],[4]]], dtype="float32").reshape(1,4,4,1)
#通过get_variable()函数创建过滤器的权重变量,上面介绍了卷积层
#这里声明的参数变量是一个四维矩阵,前面两个维度代表了过滤器的尺寸,
#第三个维度表示当前层的深度,第四个维度表示过滤器的深度
filter_weight = tf.get_variable("weights",[2, 2, 1, 1],
                                initializer= tf.constant_initializer([[-1,4],[2,1]]))
#通过get_variable()函数创建过滤器的偏执项,代码中[1]表示过滤器的深度
#等于神经网络下一层的深度
biases = tf.get_variable("biases", [1], initializer = tf.constant_initializer(1))
x = tf.placeholder('float32', [1,None,None,1])

#使用conv2d()函数实现卷积层前向传播的算法
#这个函数的第一个参数为当前层的输入矩阵,注意这个矩阵应该是一个四维矩阵
conv = tf.nn.conv2d(x, filter_weight, strides=[1, 1, 1, 1], padding="SAME")

add_bias = tf.nn.bias_add(conv, biases)

init_op = tf.global_variables_initializer()
with tf.Session() as sess:
    init_op.run()
    M_conv = sess.run(add_bias, feed_dict={x: M})
    print ("M after convolution: \n", M_conv)

6 池化

       池化函数用来对卷积操作得到的特征映射结果进行处理。池化函数会讲平面内某一位置及其相邻位置的特征值进行统计汇总,并将汇总后的结果作为一位置及其相邻的位置特征值进行统计汇总,并将汇总后的结果作为这一位置在该平面内的值。例如,常见的最大池化函数会计算该位置及其相邻矩形区域内的最大值,并将这个最大值作为该位置的值。

       TensorFlow提供了几个封装有池化操作的函数,如实现最大池化的nn.max_pool()函数、实现平均池化的 nn.avg_pool()函数等,应用最多的是最大池化,因为它能凸显出一个区域内最明显的特征。在 nn.max_pool()函数中,首先需要传入当前层的单元矩阵,这个矩阵是一个四维矩阵,格式和 nn.conv2d()函数中的第一个参数一致。第二个参数为过滤器(池化核)的尺寸。虽然能够将其赋值为长度为4的一维数组,但是这个数组的第一个和最后一个数必须为1,在实际使用中使用最多的池化核尺寸为 2*2 或 3*3.

      nn.max_pool()函数第三个参数为步长。 和 nn.conv2d()函数相同的是,这个参数的第一维和最后一维也只能为1。nn.max_pool()函数的最后一个参数指定了是否使用全0填充。 和 nn.conv2d()函数相同的是,这个参数也只有两种取值——VALID或者 SAME,其中VALID表示不适用全0填充,SAME表示使用全0填充。

      在上面卷积运算的基础上,这里使用最大池化的方式进行处理,代码如下:

import tensorflow as tf
import numpy as np

#使用Numpy工具初始化一个名为M的数组,大小为4*4,数组类型为float32
#并使用Numpy的reshape()函数调整输入的格式
#注意,M不会被Tensorflow识别为张量
M = np.array([[[2],[1],[2],[-1]],[[0],[-1],[3],[0]],[[2],[1],[-1],[4]],
             [[-2],[0],[-3],[4]]], dtype="float32").reshape(1,4,4,1)
#通过get_variable()函数创建过滤器的权重变量,上面介绍了卷积层
#这里声明的参数变量是一个四维矩阵,前面两个维度代表了过滤器的尺寸,
#第三个维度表示当前层的深度,第四个维度表示过滤器的深度
filter_weight = tf.get_variable("weights",[2, 2, 1, 1],
                                initializer= tf.constant_initializer([[-1,4],[2,1]]))
#通过get_variable()函数创建过滤器的偏执项,代码中[1]表示过滤器的深度
#等于神经网络下一层的深度
biases = tf.get_variable("biases", [1], initializer = tf.constant_initializer(1))
x = tf.placeholder('float32', [1,None,None,1])

#使用conv2d()函数实现卷积层前向传播的算法
#这个函数的第一个参数为当前层的输入矩阵,注意这个矩阵应该是一个四维矩阵
conv = tf.nn.conv2d(x, filter_weight, strides=[1, 1, 1, 1], padding="SAME")

add_bias = tf.nn.bias_add(conv, biases)

#max_pool()实现了最大池化层前向传播的过程
pool = tf.nn.max_pool(add_bias, ksize=[1,2,2,1], strides=[1,2,2,1], padding="SAME")

init_op = tf.global_variables_initializer()
with tf.Session() as sess:
    init_op.run()
    M_conv = sess.run(add_bias, feed_dict={x: M})
    M_pool = sess.run(pool, feed_dict={x: M})
    print ("M after pooled: \n", M_pool)

          

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值