LeNet-5 神经网络模型分析及其 TensorFlow 实现

一、LeNet-5 简介

LeNet-5 是 Yann Lecun 于1998提出的神经网络架构,更是卷积神经网络的开山鼻祖,虽然该网络模型仅有 7 层神经网络结构,但在 MNIST 数据集上的识别精度高达 99.2%,是卷积神经网络首次在数字图像识别领域的成功运用。

但是需要说明的有几点:

(1)LeNet-5 主要采用 tanh 和 sigmoid 作为非线性激活函数,但是目前 relu 对卷积神经网络更有效

(2)LeNet-5 采用平均池化作为下采样操作,但是目前最大池化操作被更广泛地采用

(3)LeNet-5 网络最后一层采用 Gaussian 连接层,用于输出 0~9 这 10 个类别中的一类,但是目前分类器操作已经被 softmax 层取代

LeNet-5 网络结构示意图

二、LeNet-5 网络结构

第一层:卷积层

卷积核尺寸:(长, 宽) = (5, 5)

卷积核深度:6(卷积核种类,可以简单理解经过卷积操作变换后得到的特征图 feature maps 的通道数)

卷积操作的步长:(长, 宽) = (1, 1)

边缘填充方式:padding = 'VALID'(不使用0填充边缘,得到的特征图 feature maps 的尺寸小于输入图像的尺寸)

输入数据的尺寸:(长, 宽) = (32, 32)

输出特征图 feature maps 的尺寸:(长, 宽) = (28, 28),不使用 0 填充边缘时输出特征图的尺寸计算方式为:(输入数据的长或宽 - 卷积核的长或宽 + 1) / 步长 = (32 - 5 + 1) / 1 = 28, 取上整为 28

输出节点的数量:28 * 28 * 6 = 4704(即下一层网络输入层的节点个数,对应输出特征图 feature maps 的尺度)

训练参数的数量:(5 * 5 * 1 + 1) * 6 = 156(每个卷积核有 5 * 5 个权重和 1 个偏置,共 6 种卷积核)

卷积层连接的数量:(28 * 28 * 6) * (5 * 5 + 1) = 122304

第二层:平均池化层

池化操作的区域尺寸:(长, 宽) = (2, 2)

池化操作的深度:6(池化操作不改变深度 / 通道数)

池化操作的步长:(长, 宽) = (2, 2)

边缘填充方式:padding = 'VALID'

输入数据的尺寸:(长, 宽) = (28, 28)

输出特征图 feature maps 的尺寸:(长, 宽) = (14, 14),(输入数据的长或宽 - 池化区域的长或宽 + 1) / 步长 = (28 - 2 + 1) / 2 = 13.5,取上整为 14

输出节点的数量:14 * 14 * 6

第三层:卷积层

卷积核尺寸:(长, 宽) = (5, 5)

卷积核深度:16

卷积操作的步长:(长, 宽) = (1, 1)

边缘填充方式:padding = 'VALID'

输入数据的尺寸:(长, 宽) = (14, 14)

输出特征图 feature maps 的尺寸:(长, 宽) = (28, 28),不使用 0 填充边缘时输出特征图的尺寸计算方式为:(输入数据的长或宽 - 卷积核的长或宽 + 1) / 步长 = (14 - 5 + 1) / 1 = 10,取上整为10

输出节点的数量:10* 10 * 16 = 1600

训练参数的数量:(5 * 5 * 6 + 1) * 16 = 2416(每个卷积核有 5 * 5 个权重和 1 个偏置,共 6 种卷积核)

卷积层连接的数量:(10 * 10 * 16) * (5 * 5 + 1) = 41600

第四层:平均池化层

池化操作的区域尺寸:(长, 宽) = (2, 2)

池化操作的深度:16

池化操作的步长:(长, 宽) = (2, 2)

边缘填充方式:padding = 'VALID'

输入数据的尺寸:(长, 宽) = (10, 10)

输出特征图 feature maps 的尺寸:(长, 宽) = (5, 5),(输入数据的长或宽 - 池化区域的长或宽 + 1) / 步长 = (10 - 2 + 1) / 2 = 4.5,取上整为 5

输出节点的数量:5 * 5 * 16

第五层:全连接层

输入数据的尺度:(长, 宽, 通道数) = (5, 5, 16)

输出节点的数量:120

训练参数的数量:(5 * 5 * 16) * 120 + 120 = 48120 (连接权重个数 (5 * 5 * 16) * 120, 偏置个数 120)

第六层:全连接层

输入节点个数:120

输出节点的数量:84

训练参数的数量:120 * 84 + 84 = 10164 (连接权重个数 120 * 84, 偏置个数 84)

第七层:高斯连接层(Gaussian Connections)(最后一层是分类器,目前主要通过 softmax 分类层代替)

输入节点个数:84

输出节点的数量:10

训练参数的数量:84* 10 + 10 = 850 (连接权重个数 84 * 10, 偏置个数 10)

三、LeNet-5 网络实现代码

1、环境

TensorFlow API r1.12

CUDA 9.2 V9.2.148

cudnn64_7.dll

Python 3.6.3

Windows 10、Mac

##################################################################################################
# 原始的 LeNet 网络复现 Tensorflow代码
# 尽量接近最原始的 LeNet 网络,但也有些许不同之处
# 1998 [LeNet-5] Gradient-Based Learning Applied to Document Recognition(Proceedings of the IEEE)
# LeNet-5 共 7 层
# 第一层:卷积层
# 第二层:平均池化层
# 第三层:卷积层
# 第四层:平均池化层
# 第五层:全连接层
# 第六层:全连接层
# 第七层:Gaussian连接层(以下代码通过全连接层 + softmax 代替实现)
# 卷积核在 TensorFlow 称为过滤器(filter)
# 卷积核的维度:卷积核的长、卷积核的宽、输入通道数、卷积后的输出通道数
# 池化核的维度:批量、高度、宽度、深度 / 通道
# Tensorboard 保存在当前路径下的 summary文件夹,命令行中运行:tensorboard --logdir=="./summary/"
# 浏览器中查看相关变量:http://localhost:6006
##################################################################################################

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

class LeNet5_Origin(object):
    def __init__(self):
        self.batch_size = 100   # 每个批次的数据量
        self.image_height = 32   # 输入图片的高度
        self.image_width = 32   # 输入图片的宽度
        self.image_channel = 1   # 输入图片的深度/通道数
        self.epoch_num = 1000   # 训练的轮次
        self.lr = 0.001   # 优化器的学习率

    def build_model(self, images):
        # 第一层:卷积层
        with tf.variable_scope("conv1"):
            conv1_weights = tf.get_variable(name="conv1_w", shape=[5, 5, 1, 6], initializer=tf.truncated_normal_initializer(mean=0.0, stddev=1.0))
            conv1_bias = tf.get_variable(name="conv1_b", shape=[6], initializer=tf.constant_initializer(value=0.1))
            conv1 = tf.nn.conv2d(input=images, filter=conv1_weights, strides=[1, 1, 1, 1], padding="VALID")
            conv1 = tf.nn.bias_add(value=conv1,bias=conv1_bias)
            relu1 = tf.nn.relu(features=conv1)

        # 第二层:平均池化层
        with tf.variable_scope("pool2"):
            pool2 = tf.nn.avg_pool(value=relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="VALID")

        # 第三层:卷积层
        with tf.variable_scope("conv3"):
            conv3_weights = tf.get_variable(name="conv3_w", shape=[5, 5, 6, 16], initializer=tf.truncated_normal_initializer(mean=0.0, stddev=1.0))
            conv3_bias = tf.get_variable(name="conv3_b", shape=[16], initializer=tf.constant_initializer(value=0.1))
            conv3 = tf.nn.conv2d(input=pool2, filter=conv3_weights, strides=[1, 1, 1, 1], padding="VALID")
            conv3 = tf.nn.bias_add(value=conv3, bias=conv3_bias)
            relu3 = tf.nn.relu(features=conv3)

        # 第四层:平均池化层
        with tf.variable_scope("pool4"):
            pool4 = tf.nn.avg_pool(value=relu3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="VALID")

        pool4_shapes = pool4.get_shape().as_list()
        batch_size = pool4_shapes[0]
        fc5_nodes_num = pool4_shapes[1]*pool4_shapes[2]*pool4_shapes[3]
        reshape_pool4 = tf.reshape(tensor=pool4,shape=[batch_size, fc5_nodes_num])

        # 第五层:全连接层
        with tf.variable_scope("fc5"):
            fc5_weights = tf.get_variable(name="w5", shape=[fc5_nodes_num, 120], initializer=tf.truncated_normal_initializer(mean=0.0, stddev=0.1))
            fc5_bias = tf.get_variable(name="b5", shape=[120], initializer=tf.constant_initializer(value=0.1))
            fc5 = tf.nn.relu(features=(tf.matmul(a=reshape_pool4, b=fc5_weights) + fc5_bias))

        # 第六层:全连接层
        with tf.variable_scope("fc6"):
            fc6_weights = tf.get_variable(name="w6", shape=[120, 84], initializer=tf.truncated_normal_initializer(mean=0.0, stddev=0.1))
            fc6_bias = tf.get_variable(name="b6", shape=[84], initializer=tf.constant_initializer(value=0.1))
            fc6 = tf.nn.relu(features=(tf.matmul(a=fc5, b=fc6_weights) + fc6_bias))

        # 第七层:Gaussian Connection输出层(输出0~9这10个类别中的一个,目前已有 softmax 层实现)
        with tf.variable_scope("softmax"):
            fc7_weights = tf.get_variable(name="w7", shape=[84, 10], initializer=tf.truncated_normal_initializer(mean=0.0, stddev=0.1))
            fc7_bias = tf.get_variable(name="b7", shape=[10], initializer=tf.constant_initializer(value=0.1))
            fc7 = tf.matmul(a=fc6, b=fc7_weights) + fc7_bias
            fc7 = tf.nn.softmax(logits=fc7)
        return fc7

    def train(self):
        mnist = input_data.read_data_sets('./mnist_data/', one_hot=True)

        images_holder = tf.placeholder(dtype=tf.float32, shape=[self.batch_size, self.image_height, self.image_width, self.image_channel], name="x")
        labels_holder = tf.placeholder(dtype=tf.float32, shape=[self.batch_size, 10], name="y")
        label_predict = self.build_model(images_holder)

        loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=tf.argmax(labels_holder,1), logits=label_predict)
        # loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=tf.argmax(labels_holder,1), logits=label_predict)
        correct_predict = tf.equal(tf.argmax(label_predict, 1), tf.argmax(labels_holder, 1))
        accuracy = tf.reduce_mean(input_tensor=tf.cast(x=correct_predict, dtype=tf.float32))

        train_op = tf.train.GradientDescentOptimizer(learning_rate=self.lr).minimize(loss=loss)

        tf.summary.histogram(name='loss', values=loss)
        tf.summary.scalar(name="accuracy", tensor=accuracy)
        merged = tf.summary.merge_all()

        init_op = tf.global_variables_initializer()

        saver = tf.train.Saver(max_to_keep=1)

        with tf.Session() as sess:
            sess.run(init_op)

            writer = tf.summary.FileWriter(logdir="./summary/", graph=sess.graph)

            for i in range(self.epoch_num):
                batch_images, batch_labels = mnist.train.next_batch(self.batch_size)
                batch_images = tf.reshape(tensor=batch_images, shape=[self.batch_size, 28, 28, 1])
                batch_images = tf.image.resize_images(images=batch_images,size=(32,32))

                sess.run(train_op, feed_dict={images_holder:batch_images.eval(), labels_holder:batch_labels})
                accuracy_result = sess.run(accuracy, feed_dict={images_holder: batch_images.eval(), labels_holder: batch_labels})

                summary_result = sess.run(fetches=merged, feed_dict={images_holder: batch_images.eval(), labels_holder: batch_labels})
                writer.add_summary(summary=summary_result, global_step=i)

                saver.save(sess=sess, save_path="./models/lenet-5.ckpt", global_step=i+1)

                # print(accuracy_result)


if __name__ == "__main__":
    model = LeNet5_Origin()
    model.train()

Tensorboard 图

Tensorboard Scalar 观察 accuracy

Tensorboard Histgram 观察 loss

参考文献:

[1] LeCun Y, Bottou L, Bengio Y, et al. Gradient-based learning applied to document recognition[J]. Proceedings of the IEEE, 1998, 86(11): 2278-2324.

[2] 《TensorFlow 实战 Google 深度学习框架》第 6 章 图像识别与卷积神经网络

猜你喜欢

转载自blog.csdn.net/sdnuwjw/article/details/86034302
今日推荐