Tensorflow深度学习笔记4 卷积神经网络(CNN)介绍和简单示例(LeNet-5)

本文内容来自《Tensorflow深度学习算法原理与编程实战》第七章

卷积

不再详细介绍卷积运算的数学原理。在实际运用中,卷积运算的形式为:
在这里插入图片描述
其中,I为输入二位网络数据,K为卷积核。

概念:感受野

在处理图像这样的高维度输入时,让每个神经元都与前一层中的所有神经元进行全连接是不现实的。相反,我们让每个神经元只与输入数据的一个局部区域连接。该连接的空间大小叫做神经元的感受野(receptive field),它的尺寸是一个超参数(其实就是滤波器的空间尺寸)。在深度方向上,这个连接的大小总是和输入量的深度相等。需要再次强调的是,我们对待空间维度(宽和高)与深度维度是不同的:连接在空间(宽高)上是局部的,但是在深度上总是和输入数据的深度一致。
在这里插入图片描述

以下为卷积运算的三个特性

稀疏连接

与最简单的全连接网络有所不同,卷积神经网络有稀疏连接的特性。具体表现为需要的参数数量少,这通过限制核的大小远小于输入的大小实现的。
好处主要是降低计算的复杂度,提高泛化效果。

参数共享

相同参数被应用于一个模型的多个函数中。也就是核的每一个参数都将被应用于输入的每一位置。这样同样能够显著的降低参数的数量。

平移等变

当改变输入,输出也以同样的方式改变。
等变指的是,对于一个函数fx,有f(g(x))=g(f(x))
由于有参数共享机制,平移等变是很容易证明的。

多卷积核

不同的卷积核是用于提取不同的特征时使用的。当在一张图像中需要提取多种特征时,不同特征对应的卷积核是不相同的。

在Tensorflow官方文档中,卷积核被称作过滤器(Filer)。
实现函数(的一种):

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

Args:
input: A Tensor. type必须是以下几种类型之一: half, float32, float64.
应当是四维矩阵,第一维度的参数对应输入的batch,二,三维度则为这一batch的图片宽高,第四维度为图片深度。
注意,当接受的图像不是来自于输入时,深度应当为上一层输出矩阵的深度。
filter: A Tensor. type和input必须相同
同样的四维矩阵,每一维度分别为:宽,高,数据深度,过滤器深度。
strides: A list of ints.一维,长度4, 在input上切片采样时,每个方向上的滑窗步长,必须和format指定的维度同阶。第2,3个参数为移动步长。
padding: A string from: “SAME”, “VALID”. SAME表示开启全0填充,反之不开启。
data_format: An optional string from: “NHWC”, “NCHW”, 默认为"NHWC"。
指定输入输出数据格式,默认格式为"NHWC", 数据按这样的顺序存储:
[batch, in_height, in_width, in_channels]
也可以用这种方式:“NCHW”, 数据按这样的顺序存储:
[batch, in_channels, in_height, in_width]
name: 操作名,可选.
Returns:
A Tensor. type与input相同
Given an input tensor of shape [batch, in_height, in_width, in_channels]
and a filter / kernel tensor of shape
[filter_height, filter_width, in_channels, out_channels]

代码示范如下:

import tensorflow as tf
import numpy as np

#使用numpy工具初始化一个名为M的数组,形状为2x3,数据类型为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("biase", [1], initializer = tf.constant_initializer(1))


x = tf.placeholder('float32', [1,None, None,1])

#conv2d()函数实现了卷积层前向传播的算法。
#这个函数的第一个参数为当前层的输入矩阵,注意这个矩阵应该是一个四维矩阵,
#代表第一个维度的参数对应一个输入batch。如果在输入层,input[0, , , ]表示第一张图片,
#input[1, , , ]表示第二张图片,等等。函数第二个参数是卷积层的权重,第三个参数为
#不同维度上的步长。虽然第三个参数提供的是一个长度为4 的数组,
#但是第一个和最后一个数字要求一定是1,这是因为卷积层的步长只对矩阵的长和宽有效。
#最后一个参数是填充(padding的方法,有SAME或VALID 两种选择,
#其中SAME 表示添加全0填充,VALID表示不添加。
#函数原型conv2d(input,filter,strids,padding,us_cudnn_on_gpu_,data_format,name)
conv = tf.nn.conv2d(x, filter_weight, strides=[1, 1, 1, 1], padding="SAME")

#bias_add()函数具有给每一个节点加上偏置项点功能。这里不能直接使用加法的原因是
#矩阵上不同位置上的节点都需要加上同样的偏置项。因为过滤器深度为1,
#故偏置项只有一个数,结果为3x4的矩阵中每一个值都要加上这个偏置项。
#原型bias_add(value,bias,data_format,name)
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)

池化

汇总平面某一位置及其相邻位置的特征信息,一般有最大池化和平均池化。
基于一个事实,特征图的相邻区域极有可能的相似的,所以可以运用池化操作减小特征图的大小。
在以上卷积操作的基础上加上池化层,代码如下所示:

import tensorflow as tf
import numpy as np

M = np.array([[[-2],[2],[0],[3]],
              [[1],[2],[-1],[2]],
              [[0],[-1],[1],[0]]],dtype="float32").reshape(1, 3, 4, 1)
filter_weight = tf.get_variable("weights",[2, 2, 1, 1],
    initializer = tf.constant_initializer([[2, 0],[-1, 1]]))
biases = tf.get_variable('biases', [1], initializer = tf.constant_initializer(1))
x = tf.placeholder('float32', [1, None, None, 1])
conv = tf.nn.conv2d(x, filter_weight, strides=[1, 1, 1, 1], padding="SAME")
add_bias = tf.nn.bias_add(conv, biases)

#max_pool()函数实现了最大池化层的前向传播过程
#原型为max_pool(value,strides,padding,data_format,name)
#参数value为输入数据,strides为提供了步长信息,padding提供了是否使用全0填充。
#平均池化函数为:tf.nn.avg_pool
pool = tf.nn.max_pool(add_bias, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")

with tf.Session() as sess:
    tf.global_variables_initializer().run()
    M_conv = sess.run(add_bias, feed_dict={x: M})
    M_pool = sess.run(pool, feed_dict={x: M})
    print(" after average pooled: \n", M_pool)
    '''输出内容
    after average pooled:
    [[[[7.]
       [5.]]
      [[1.]
       [3.]]]]
    '''

实现卷积神经网络的简单例子

基本框架介绍

输入层,卷积层,池化层,全连层,softmax层
卷积,池化,softmax的作用都已经介绍过,在此不再赘述。
全连层的作用是在得到足够抽象的特征后,完成分类任务。
卷积层和池化层之间,一般需要连接一个非线性变化整流操作,比如ReLU。
为了加深对整体网络的理解,下面我将对卷积神经网络进行一个整体的推导。
首先,假设我们需要做的是一个手写数字的卷积神经网络(也就是minst啦),输入是三通道彩色图像,维度为 32 ∗ 32 ∗ 3 32*32*3 32323
需要经过两次卷积-池化处理,卷积核的维度为 5 ∗ 5 ∗ 3 5*5*3 553(这说明这是一个三维维卷积核),步长为1,个数为6个,无padding操作,这一层的输出为conv1,那么这一层的输出维度就为 28 ∗ 28 ∗ 6 ( 计 算 公 式 为 32 − 5 + 1 ) 28*28*6(计算公式为32-5+1) 28286325+1。然后我们经过一个步长均为2的 2 ∗ 2 2*2 22最大池化层,可以使宽度和高度缩小一半,于数输出为 14 ∗ 14 ∗ 6 14*14*6 14146
再经过一个f=5,s=1,p=0,16个的卷积核,输出为 10 ∗ 10 ∗ 16 10*10*16 101016,再经过一个相同的池化层,最终得到的输出为 5 ∗ 5 ∗ 16 5*5*16 5516.
这个时候,剩余的元素只剩 5 ∗ 5 ∗ 16 = 400 5*5*16=400 5516=400个了,已经很少了。现在需要做的就是利用全连接层进行分类。先把剩余的元素平铺成一个列向量,假设下一层的元素数量是120个,那这一层的权重矩阵就为 400 ∗ 120 400*120 400120的。经过几层全连接层后,得到一个10层的输出,然后再连接一个softmax单元,就组成了一个完整的卷积神经网络。具体的流程在下图也有展示。
在这里插入图片描述
而卷积神经网络中的反向传播推导,暂时省略不写,可以参考这个博客: 卷积神经网络(CNN)反向传播算法.

代码示范:LeNet-5

网络全貌

LeNet-5 是 Yann LeCun 等人在1998年设计的用于手写数字识别的卷积神经网络,当年美国大多数银行就是用它来识别支票上面的手写数字的,它是早期卷积神经网络中最有代表性的实验系统之一。
在这里插入图片描述
LeNet-5是一个相对比较简单的神经网络,只包含8层,输入层-2次卷积-池化操作-3个全连接层-输出层,也就是上文举例计算的网络结构。值得注意的是,第三层至第四层的卷积操作并不是一对一的,而具有固定的连接关系。
该网路与当下流行的网络的主要区别在于,激活函数选择了sigmoid而不是ReLU,以及output层选择的是径向基函数,而不是softmax函数。
具体每层的介绍如下:

C1层

属性:卷积层 卷积核大小 5*5

输入:32*32

输出特征图大小: 28 ∗ 28 ( 32 − 5 + 1 ) 28*28 (32-5+1) 2828325+1

卷积核种类(特征图个数): 6

神经元数量: 28 ∗ 28 ∗ 6 28*28*6 28286

可训练参数: 6*(55+1)(6个特征图,每个特征图含一个滤波器55个参数,和一个偏置参数)

连接数: 6 ∗ ( 5 ∗ 5 + 1 ) ∗ 28 ∗ 28 6*(5*5+1)*28*28 655+12828

S2层

属性:下采样层 采样区域大小 2*2

采样方式: 2*2区域的4个值相加,乘以一个可训练参数,再加上一个偏置参数,结果通过Sigmoid非线性化

输出特征图大小: 14*14

采样种类(特征图个数): 6

神经元数量: 6 ∗ 14 ∗ 14 6*14*14 61414

可训练参数: 6 ∗ ( 1 + 1 ) 6*(1+1) 61+1(采样的权重+一个偏置参数)

连接数: 6 ∗ ( 2 ∗ 2 + 1 ) ∗ 14 ∗ 14 6*(2*2+1)*14*14 622+11414

C3层

属性: 卷积层 卷积核大小 5*5

输出特征图大小: 10*10

卷积核种类(特征图个数): 16

可训练参数:C3跟S2并不是全连接的,具体连接方式是: C3的前6个特征图以S2中3个相邻的特征图子集为输入。接下来6个特征图以S2中4个相邻特征图子集为输入。然后的3个以不相邻的4个特征图子集为输入。最后一个将S2中所有特征图为输入,对应如下:

在这里插入图片描述

这样算,可训练参数 = 6 ∗ ( 3 ∗ 5 ∗ 5 + 1 ) + 6 ∗ ( 4 ∗ 5 ∗ 5 + 1 ) + 3 ∗ ( 4 ∗ 5 ∗ 5 + 1 ) + 1 ∗ ( 6 ∗ 5 ∗ 5 + 1 ) = 1516 6*(3*5*5+1)+6*(4*5*5+1)+3*(4*5*5+1)+1*(6*5*5+1)=1516 6355+1+6455+1+3455+1+1655+1=1516

连接数: 10 ∗ 10 ∗ 1516 10*10*1516 10101516

S4层

属性:下采样层 采样区域大小 2*2

采样方式: 2*2区域的4个值相加,乘以一个可训练参数,再加上一个偏置参数,结果通过Sigmoid非线性化

输出特征图大小: 5*5

采样种类(特征图个数): 16

神经元数量: 16 ∗ 5 ∗ 5 16*5*5 1655

可训练参数: 16 ∗ ( 1 + 1 ) 16*(1+1) 161+1(采样的权重+一个偏置参数)

连接数: 16 ∗ ( 2 ∗ 2 + 1 ) ∗ 5 ∗ 5 = 2000 16*(2*2+1)*5*5=2000 1622+155=2000

C5层

属性:卷积层 卷积核大小 5*5

输入:5*5

输出特征图大小: 1 ∗ 1 ( 5 − 5 + 1 ) 1*1 (5-5+1) 1155+1

卷积核种类(特征图个数): 120

神经元数量: 120 ∗ 1 120*1 1201

可训练参数: 120 ∗ ( 5 ∗ 5 ∗ 16 + 1 ) = 48120 120*(5*5*16+1)=48120 1205516+1=48120

连接数: 120 ∗ ( 5 ∗ 5 ∗ 16 + 1 ) = 48120 120*(5*5*16+1)=48120 1205516+1=48120

F6

属性: 全连接层

输入: 120维的向量

计算方式:计算输入向量和权重向量之间的点积,再加上一个偏置,结果通过sigmoid函数

可训练参数: 84 ∗ ( 120 + 1 ) = 10164 84*(120+1)= 10164 84120+1=10164

Gaussian Connections
LeNet-5最后一步是Gaussian Connections,采用了RBF函数(即径向欧式距离函数),计算输入向量和参数向量之间的欧式距离。目前已经被Softmax取代。

Tensorlow实现

import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import time

time_start=time.time()

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

batch_size = 100
learning_rate = 0.01
learning_rate_decay = 0.99
max_steps = 30000

def hidden_layer(input_tensor,regularizer,avg_class,resuse):
    #创建第一个卷积层,得到特征图大小为32@28x28
    with tf.variable_scope("C1-conv",reuse=resuse):
        conv1_weights = tf.get_variable("weight", [5, 5, 1, 32],
                             initializer=tf.truncated_normal_initializer(stddev=0.1))
        conv1_biases = tf.get_variable("bias", [32], initializer=tf.constant_initializer(0.0))

        conv1 = tf.nn.conv2d(input_tensor, conv1_weights, strides=[1, 1, 1, 1], padding="SAME")
        relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_biases))

    #创建第一个池化层,池化后的结果为32@14x14
    with tf.name_scope("S2-max_pool",):
        pool1 = tf.nn.max_pool(relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")

    # 创建第二个卷积层,得到特征图大小为64@14x14。注意,第一个池化层之后得到了32个
    # 特征图,所以这里设输入的深度为32,我们在这一层选择的卷积核数量为64,所以输出
    # 的深度是64,也就是说有64个特征图
    with tf.variable_scope("C3-conv",reuse=resuse):
        conv2_weights = tf.get_variable("weight", [5, 5, 32, 64],
                                     initializer=tf.truncated_normal_initializer(stddev=0.1))
        conv2_biases = tf.get_variable("bias", [64], initializer=tf.constant_initializer(0.0))
        conv2 = tf.nn.conv2d(pool1, conv2_weights, strides=[1, 1, 1, 1], padding="SAME")
        relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_biases))

    #创建第二个池化层,池化后结果为64@7x7
    with tf.name_scope("S4-max_pool",):
        pool2 = tf.nn.max_pool(relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
        #get_shape()函数可以得到这一层维度信息,由于每一层网络的输入输出都是一个batch的矩阵,
        #所以通过get_shape()函数得到的维度信息会包含这个batch中数据的个数信息
        #shape[1]是长度方向,shape[2]是宽度方向,shape[3]是深度方向
        #shape[0]是一个batch中数据的个数,reshape()函数原型reshape(tensor,shape,name)
        shape = pool2.get_shape().as_list()
        nodes = shape[1] * shape[2] * shape[3]    #nodes=3136
        reshaped = tf.reshape(pool2, [shape[0], nodes])

    #创建第一个全连层
    with tf.variable_scope("layer5-full1",reuse=resuse):
        Full_connection1_weights = tf.get_variable("weight", [nodes, 512],
                                      initializer=tf.truncated_normal_initializer(stddev=0.1))
        #if regularizer != None:
        tf.add_to_collection("losses", regularizer(Full_connection1_weights))
        Full_connection1_biases = tf.get_variable("bias", [512],
                                                     initializer=tf.constant_initializer(0.1))
        if avg_class ==None:
            Full_1 = tf.nn.relu(tf.matmul(reshaped, Full_connection1_weights) + \
                                                                   Full_connection1_biases)
        else:
            Full_1 = tf.nn.relu(tf.matmul(reshaped, avg_class.average(Full_connection1_weights))
                                                   + avg_class.average(Full_connection1_biases))

    #创建第二个全连层
    with tf.variable_scope("layer6-full2",reuse=resuse):
        Full_connection2_weights = tf.get_variable("weight", [512, 10],
                                      initializer=tf.truncated_normal_initializer(stddev=0.1))
        #if regularizer != None:
        tf.add_to_collection("losses", regularizer(Full_connection2_weights))
        Full_connection2_biases = tf.get_variable("bias", [10],
                                                   initializer=tf.constant_initializer(0.1))
        if avg_class == None:
            result = tf.matmul(Full_1, Full_connection2_weights) + Full_connection2_biases
        else:
            result = tf.matmul(Full_1, avg_class.average(Full_connection2_weights)) + \
                                                  avg_class.average(Full_connection2_biases)
    return result



x = tf.placeholder(tf.float32, [batch_size ,28,28,1],name="x-input")
y_ = tf.placeholder(tf.float32, [None, 10], name="y-input")

regularizer = tf.contrib.layers.l2_regularizer(0.0001)

y = hidden_layer(x,regularizer,avg_class=None,resuse=False)

training_step = tf.Variable(0, trainable=False)
variable_averages = tf.train.ExponentialMovingAverage(0.99, training_step)
variables_averages_op = variable_averages.apply(tf.trainable_variables())

average_y = hidden_layer(x,regularizer,variable_averages,resuse=True)

cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
cross_entropy_mean = tf.reduce_mean(cross_entropy)

loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))

learning_rate = tf.train.exponential_decay(learning_rate,
                                 training_step, mnist.train.num_examples /batch_size ,
                                 learning_rate_decay, staircase=True)

train_step = tf.train.GradientDescentOptimizer(learning_rate). \
    minimize(loss, global_step=training_step)

with tf.control_dependencies([train_step, variables_averages_op]):
    train_op = tf.no_op(name='train')
crorent_predicition = tf.equal(tf.arg_max(average_y,1),tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(crorent_predicition,tf.float32))
with tf.Session() as sess:
    tf.global_variables_initializer().run()
    for i in range(max_steps):
        if i %1000==0:
            x_val, y_val = mnist.validation.next_batch(batch_size)
            reshaped_x2 = np.reshape(x_val, (batch_size,28,28, 1))
            validate_feed = {x: reshaped_x2, y_: y_val}

            validate_accuracy = sess.run(accuracy, feed_dict=validate_feed)
            print("After %d trainging step(s) ,validation accuracy"
                  "using average model is %g%%" % (i, validate_accuracy * 100))
            time_end = time.time()
            print('time cost', time_end - time_start, 's')
            time_start = time.time()

        x_train, y_train = mnist.train.next_batch(batch_size)

        reshaped_xs = np.reshape(x_train, (batch_size ,28,28,1))
        sess.run(train_op, feed_dict={x: reshaped_xs, y_: y_train})


time_end=time.time()
print('time cost',time_end-time_start,'s')
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值