机器学习笔记(十三):TensorFlow实战五(经典卷积神经网络: LeNet -5 )

1 - 引言

之前我们介绍了一下卷积神经网络的基本结构——卷积层和池化层。通过这两个结构我们可以任意的构建各种各样的卷积神经网络模型,不同结构的网络模型也有不同的效果。但是怎样的神经网络模型具有比较好的效果呢?

下图展示了CNN的发展历程。

在这里插入图片描述

经过人们不断的尝试,诞生了许多有有着里程碑式意义的CNN模型。因此我们接下来会学习这些非常经典的卷积神经网络

  • LeNet -5
  • AlexNet
  • VGG
  • Inception
  • ResNet

2 - LeNet-5模型

LeNet-5模型是Yann LeCun教授于1998年在论文Gradient-Based Learning Applied to Document Recognition中提出的,它是第一个成功应用与数字识别问题的卷积神经网络。在MNIST数据集上,LeNet-5模型可以达到大约99.2%的正确率,LeNet-5模型如下图所示:
在这里插入图片描述

下面我们来详细介绍一个LeNet-5模型每一层的结构

2.1 第一层:卷积层

数据维数详细说明:

这一层的输入就是原始的图像像素,LeNet-5模型输入层为32X32X1的图像(只能识别灰度图像而不能识别彩色图像)。第一个卷积层的过滤器尺寸为5X5,深度为6(深度既是通道值),不使用padding,步长为1,因为没有使用padding,这一层的输出尺寸为32-5+1=28,深度为6。这一个卷积层总共有5x5x1x6+6=156个参数,其中6个为偏置项参数。本层卷积层总共有28x28x6x(5x5+1)=122304个连接

总结:

输入图片: 32 32 1 32*32*1

卷积核大小: 5 5 5*5

卷积核种类:6

输出featuremap大小: 28 28 32 5 + 1 = 28 28*28 (32-5+1)=28

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

可训练参数: 5 5 + 1 ) 6 5 5 = 25 u n i t b i a s 6 (5*5+1) * 6(每个滤波器5*5=25个unit参数和一个bias参数,一共6个滤波器)

连接数: 5 5 + 1 6 28 28 = 122304 (5*5+1)*6*28*28=122304

2.2 第二层:池化层

这一层的输入为第一层的输出,是一个28x28x6的节点矩阵。本层采用的过滤器大小为2x2,步长为2,所以输出矩阵为14x14x6。

输入: 28 28 6 28*28*6

采样区域: 2 2 2*2

采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid

采样种类:6

输出featureMap大小: 14 14 28 / 2 14*14(28/2)

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

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

S2中每个特征图的大小是C1中特征图大小的1/4。

详细说明:第一次卷积之后紧接着就是池化运算,使用$ 2*2$核 进行池化,于是得到了S2,6个 14 14 14*14 的 特征图(28/2=14)。S2这个pooling层是对C1中的 2 2 2*2 区域内的像素求和乘以一个权值系数再加上一个偏置,然后将这个结果再做一次映射。同时有5x14x14x6=5880个连接。

2.3 第三层:卷积层

本层的输入矩阵大小为14x14x6,使用的过滤器大小为5x5,深度为16。本层不使用padding,步长为1,所以输出节点为10x10x16。所以有5x5x6x16+16 = 2416个参数。10x10x16x(25+1)=41600个连接。

输入:14x14x6

卷积核大小: 5 5 5*5

卷积核种类:16

输出featureMap大小: 10 10 ( 14 5 + 1 ) 10*10 (14-5+1)

2.4 第四层:池化层

本层输入矩阵大小为10x10x16,采用的过滤器大小为2x2,步长为2,本层输出矩阵大小为5x5x16

2.5 第五层:卷积层

输入:5x5x16

卷积核大小:5*5

卷积核种类:120

输出featureMap大小:1*1(5-5+1)

可训练参数/连接:120*(1655+1)=48120

虽然LeNet-5模型的论文中将这一层成为卷积层,但是因为过滤器的大小就是5x5,所以和全连接层没有区别。

2.6 F6层-全连接层

本层的输入节点个数为120个,输出节点个数为84个,总共参数为120x84+84 = 10164个。

2.7 Output层-全连接层

本层输入节点为84个,输出节点个数为10个,总共参数为84x10+10 = 850个。

3 - TensorFlow实现LeNet-5模型

** mnist_train_LeNet_5.py文件:**

import os

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import  numpy as np
#加载mnist_inference.py中定义的常量和前向传播的函数。
import mnist_inference_LeNet_5

BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8 # 最开始的学习率
LEARNING_RATE_DECAY = 0.99 # 在指数衰减学习率的过程中用到
REGULARIZATION_RATE = 0.0001 # 描述模型复杂度的正则化项在损失函数中的系数
TRAINING_STEPS = 30000 # 训练轮数,注意,训练一个Batch就是一个step
MOVING_AVERAGE_DECAY = 0.99 # 滑动平均模型的衰减率,最后我会讲解滑动平均模型
#模型保存的路径和中文名
MODEL_SAVE_PATH = "/path/to/model/"
MODEL_NAME = "model.ckpt"

def train(mnist):
    # 定义输入输出placeholder。
    x = tf.placeholder(tf.float32,[
        BATCH_SIZE, #第一维表示一个batch中样例的个数
        mnist_inference_LeNet_5.IMAGE_SIZE, #第二维和第三维表示图片的尺寸
        mnist_inference_LeNet_5.IMAGE_SIZE,
        mnist_inference_LeNet_5.NUM_CHANNELS],  #第四维表示图片的深度,对于RBG格式的图片,深度为5
        name='x-input')

    y_ = tf.placeholder(tf.float32, [None, mnist_inference_LeNet_5.OUTPUT_NODE], name='y-input')

    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    # 直接使用mnist_inference.py中定义的前向传播过程
    y = mnist_inference_LeNet_5.inference(x,train, regularizer)
    global_step = tf.Variable(0, trainable=False)

    # 定义损失函数、学习率、滑动平均操作以及训练过程
    variable_averages = tf.train.ExponentialMovingAverage(
        MOVING_AVERAGE_DECAY, global_step
    )
    variable_averages_op = variable_averages.apply(
        tf.trainable_variables()
    )
    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_BASE,
        global_step,
        mnist.train.num_examples / BATCH_SIZE,
        LEARNING_RATE_DECAY
    )
    train_step = tf.train.GradientDescentOptimizer(learning_rate)\
                   .minimize(loss, global_step=global_step)
    with tf.control_dependencies([train_step, variable_averages_op]):
        train_op = tf.no_op(name='train')

    # 初始化TensorFlow持久化类
    saver = tf.train.Saver()
    with tf.Session() as sess:
        tf.global_variables_initializer().run()

        # 在训练过程中不再测试模型在验证数据上的表现,验证和测试的过程将会有一个独
        # 立的程序来完成。
        for i in range(TRAINING_STEPS):
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            xs = np.reshape(xs,(
                BATCH_SIZE,
                mnist_inference_LeNet_5.IMAGE_SIZE,
                mnist_inference_LeNet_5.IMAGE_SIZE,
                mnist_inference_LeNet_5.NUM_CHANNELS))

            _, loss_value, step = sess.run([train_op, loss, global_step],
                                           feed_dict={x: xs, y_: ys})
            # 每1000轮保存一次模型
            if i % 1000 == 0:
                # 输出当前的训练情况。这里只输出了模型在当前训练batch上的损失
                # 函数大小。通过损失函数的大小可以大概了解训练的情况。在验证数
                # 据集上正确率的信息会有一个单独的程序来生成
                print("After %d training step(s), loss on training "
                      "batch is %g." % (step, loss_value))
                # 保存当前的模型。注意这里给出了global_step参数,这样可以让每个
                # 被保存的模型的文件名末尾加上训练的轮数,比如“model.ckpt-1000”,
                # 表示训练1000轮之后得到的模型。
                saver.save(
                    sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME),
                    global_step=global_step
                )
# 主程序入口
def main(argv=None):
    # 声明处理MNIST数据集的类,这个类在初始化时会自动下载数据。
    mnist = input_data.read_data_sets("/path/to/MNIST_data", one_hot=True)
    train(mnist)

# TensorFlow提供的一个主程序入口,tf.app.run会调用上面定义的main函数
if __name__ == "__main__":
    tf.app.run()

mnist_inference_LeNet_5.py文件:

# -*- coding: utf-8 -*-
import tensorflow as tf

# 定义神经网络结构相关的参数
INPUT_NODE = 784
OUTPUT_NODE = 10

IMAGE_SIZE = 20
NUM_CHANNELS = 1
NUM_LABELS = 10

#第一层卷积层的尺寸和深度
CONV1_DEEP = 32
CONV1_SIZE = 5
#第二层卷积层的尺寸和深度
CONV2_DEEP = 64
CONV2_SIZE = 5
#全连接层的节点个数
FC_SIZE = 512

#定义卷积神经网络的前向传播过程。这里添加了一个新的参数train,用于区分训练过程和测试
#过程,在这个过程中将用到dropout方法(只在训练时使用)
def inference(input_tensor,train,regularizer):
    #通过不同的命名空间隔离不同层的变量,这可以让每一层中的变量名只需要
    #考虑在当前层的作用。而不需要担心重名的问题。
    with tf.variable_scope('layer1-conv1'):
        conv1_weights = tf.get_variable(
            "weight",[CONV1_SIZE,CONV1_SIZE,NUM_CHANNELS,CONV1_DEEP],
            initializer=tf.truncated_normal_initializer(stddev=0.1))
        conv1_biases = tf.get_variable(
            "bias",[CONV1_DEEP],initializer=tf.constant_initializer(0.0))
        #使用边长为5,深度为32的过滤器,过滤器移动的步长为1,且使用全零填充
        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))

    #实现第二层池化层的前向传播过程,这里选用最大池化层,池化层边长为2,
    #使用全0填充且移动的步长为2,这一层的输入时上一层的输出,也就是28x28x32
    #的矩阵,输出为14x14x32的矩阵
    with tf.name_scope('layer2-pool1'):
        pool1 = tf.nn.max_pool(
            relu1,ksize=[1,2,2,1],strides=[1,2,1,1],padding='SAME')

    #声明第三层卷积层的变量并实现前向传播过程,这一层的输入为14x14x32的矩阵
    #输出为14x14x64的矩阵
    with tf.variable_scope('layer3-conv2'):
        conv2_weights = tf.get_variable(
            "weight",[CONV2_SIZE,CONV2_SIZE,CONV1_DEEP,CONV2_DEEP],
            initializer=tf.truncated_normal_initializer(stddev=0.1))
        conv2_biases = tf.get_variable(
            "bias",[CONV2_DEEP],initializer=tf.constant_initializer(0.0))
        #使用边长为5,深度为64的过滤器,过滤器移动的步长为1,且使用全零填充
        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))

    #实现第四层池化层的前向传播过程,这一层和第二层的结构是一样的,这一层的输入为
    #14x14x64的矩阵,输出为7x7x64的矩阵。
    with tf.name_scope('layer4-pool2'):
        pool2 = tf.nn.max_pool(
            relu2,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
    #将第四层池化层的输出转化为第五层全连接的输入格式。第四层的输入为7x7x64的矩阵,
    #然而第五层全连接层需要的输入格式为向量,所以在这里需要将这个7x7x64的矩阵拉直成
    #一个向量,pool2.get_shape函数可以得到第四层输出矩阵的维度而不需要手工计算。
    #注意因为每一层神经网络的输入输出都为一个batch的矩阵,所以这里得到的维度也包含一个
    #batch中数据的个数

    pool_shape = pool2.get_shape().as_list()

    #计算将矩阵拉成向量之后的长度,这个长度就是矩阵长宽及深度的成绩,注意这里
    #pool_shape[0]为一个batch钟数据的个数。
    nodes = pool_shape[1]*pool_shape[2]*pool_shape[3]

    #通过tf.reshape函数将第四层的输出变成一个batch的向量。
    reshaped = tf.reshape(pool2,[pool_shape[0],nodes])

    #声明第五层全连接的变量并实现前向传播的过程,这一层的输入时拉直之后的一组向量
    #向量长度为3136,输出是一组长度为512的向量。这里我们引入了dropout的概念。
    #dropout会随机的将部分节点输出改为0,避免过拟合的问题,这种优化方法一般只在
    #全连接层而不是卷积层使用
    with tf.variable_scope('layer5-fc1'):
        fc1_weights = tf.get_variable(
            "weight",[nodes,FC_SIZE],initializer=tf.truncated_normal_initializer(stddev=0.1))
        #只有全连接层的权重需要加入正则化
        if regularizer !=None:
            tf.add_to_collection('losses',regularizer(fc1_weights))
        fc1_biases = tf.get_variable(
            "bias",[FC_SIZE],initializer=tf.constant_initializer(0.1))
        fc1 = tf.nn.relu(tf.matmul(reshaped,fc1_weights)+fc1_biases)
        if train: fc1 = tf.nn.dropout(fc1,0.5)

    #声明第六层全连接层的变量并实现前向传播过程,这一层为长度512的向量
    #输出一组长度为10的向量,并且输出通过sotfmax之后就得到了最后的分类结果
    with tf.variable_scope('layer6-fc2'):
        fc2_weights = tf.get_variable(
            "weight",[FC_SIZE,NUM_LABELS],initializer=tf.truncated_normal_initializer(stddev=0.1))
        if regularizer !=None:
            tf.add_to_collection('losses',regularizer(fc2_weights))
        fc2_biases = tf.get_variable(
            "bias",[NUM_LABELS],initializer=tf.constant_initializer(0.1))
        logit = tf.matmul(fc1,fc2_weights)+fc2_biases

    return logit

After 1 training step(s), loss on training batch is 3.2112.
After 1001 training step(s), loss on training batch is 0.231712.
After 2001 training step(s), loss on training batch is 0.182711.
·
·
·
After 27001 training step(s), loss on training batch is 0.0336458.
After 28001 training step(s), loss on training batch is 0.036755.
After 29001 training step(s), loss on training batch is 0.0390648.

猜你喜欢

转载自blog.csdn.net/HHH_ANS/article/details/84252294