【Tensorflow】LeNet-5训练MNIST数据集

LeNet-5共有7层,不包含输入,每层都包含可训练参数;每个层有多个Feature Map,每个FeatureMap通过一种卷积滤波器提取输入的一种特征,然后每个FeatureMap有多个神经元。根据其他博客,利用MNIST训练LeNet-5网络,并做了测试。

目录

 

1.LeNet-5网络

2.LeNet-5代码

3.测试结果


代码地址

1.LeNet-5网络

1.1 网络结构

【Tensorflow】LeNet-5训练MNIST数据集_第1张图片

1.2 各层详解

  • 输入层

    输入图像尺寸归一化为32*32

  • C1层---卷积层

    输入:32*32

    kernel:5*5

    卷积核个数:6

    输出:28*28 [strip = 1,no padding,(32-5)/ 1 +1 = 28]

    神经元数量:28*28*6

    可训练参数:(5*5+1)*6(每个滤波器5*5=25个unit参数和一个bias参数,一共6个滤波器)

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

详细说明:对输入图像进行第一次卷积运算(使用 6 个大小为 5*5 的卷积核),得到6个C1特征图(6个大小为28*28的 feature maps, 32-5+1=28)。我们再来看看需要多少个参数,卷积核的大小为5*5,总共就有6*(5*5+1)=156个参数,其中+1是表示一个核有一个bias。对于卷积层C1,C1内的每个像素都与输入图像中的5*5个像素和1个bias有连接,所以总共有156*28*28=122304个连接(connection)。有122304个连接,但是我们只需要学习156个参数,主要是通过权值共享实现的。

  • S2层---下采样层(池化)

    输入:28*28

    kernel:2*2

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

    kernel个数:6

    输出:14*14(28/2 * 28/2)

    神经元数量:14*14*6

    可训练参数:2*6(和的权+偏置)

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

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

  • C3层---卷积层

    输入:14*14  S2中所有6个或者几个特征map组合

    kernel:5*5

    卷积核种类:16

    输出:10*10 [(14 - 5)/ 1 +1 = 10]

    C3中的每个特征map是连接到S2中的所有6个或者几个特征map的,表示本层的特征map是上一层提取到的特征map的不同组合

    存在的一个方式是:C3的前6个特征图以S2中3个相邻的特征图子集为输入。接下来6个特征图以S2中4个相邻特征图子集为输入。然后的3个以不相邻的4个特征图子集为输入。最后一个将S2中所有特征图为输入。

    则:可训练参数:6*(3*25+1)+6*(4*25+1)+3*(4*25+1)+(25*6+1)=1516【个人理解:3*5*5+1个bias】

    连接数:10*10*1516=151600

详细说明:第一次池化之后是第二次卷积,第二次卷积的输出是C3,16个10x10的特征图,卷积核大小是 5*5. 我们知道S2 有6个 14*14 的特征图,怎么从6 个特征图得到 16个特征图了? 这里是通过对S2 的特征图特殊组合计算得到的16个特征图。具体如下:

C3的前6个feature map(对应上图第一个红框的6列)与S2层相连的3个feature map相连接(上图第一个红框),后面6个feature map与S2层相连的4个feature map相连接(上图第二个红框),后面3个feature map与S2层部分不相连的4个feature map相连接,最后一个与S2层的所有feature map相连。卷积核大小依然为5*5,所以总共有6*(3*5*5+1)+6*(4*5*5+1)+3*(4*5*5+1)+1*(6*5*5+1)=1516个参数。而图像大小为10*10,所以共有151600个连接。

C3与S2中前3个图相连的卷积结构如下图所示:

上图对应的参数为 3*5*5+1,一共进行6次卷积得到6个特征图,所以有6*(3*5*5+1)参数。 为什么采用上述这样的组合了?论文中说有两个原因:1)减少参数,2)这种不对称的组合连接的方式有利于提取多种组合特征。

  • S4层---池化层

    输入:10*10

    kernel:2*2

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

    采样种类:16

    输出featureMap大小:5*5(10/2)

    神经元数量:5*5*16=400

    可训练参数:2*16=32(和的权+偏置)

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

详细说明:S4是pooling层,窗口大小仍然是2*2,共计16个feature map,C3层的16个10x10的图分别进行以2x2为单位的池化得到16个5x5的特征图。这一层有2x16共32个训练参数,5x5x5x16=2000个连接。连接的方式与S2层类似。

  • C5层---卷积层

    输入:5*5  S4层的全部16个单元特征map(与s4全相连)

    卷积核大小:5*5

    卷积核种类:120

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

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

详细说明:C5层是一个卷积层。由于S4层的16个图的大小为5x5,与卷积核的大小相同,所以卷积后形成的图的大小为1x1。这里形成120个卷积结果。每个都与上一层的16个图相连。所以共有(5x5x16+1)x120 = 48120个参数,同样有48120个连接。C5层的网络结构如下:

  • F6层---全连接层

    输入:1*1  C5 120维向量

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

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

详细说明:6层是全连接层。F6层有84个节点,对应于一个7x12的比特图,-1表示白色,1表示黑色,这样每个符号的比特图的黑白色就对应于一个编码。该层的训练参数和连接数是(120 + 1)x84=10164。ASCII编码图如下:

F6层的连接方式如下:

 

  • 输出层---全连接层

2.LeNet-5代码

2.1 部分注解

  •  
# padding mnist 28x28->32x32
def mnist_reshape_32(_batch):

    batch = np.reshape(_batch,[-1,28,28])
    num = batch.shape[0]
    batch_32 = np.array(np.random.rand(num,32,32),dtype=np.float32)
    for i in range(num):
        batch_32[i] = np.pad(batch[i],2,'constant',constant_values=0)

    return batch_32

MNIST数据集大小为28x28,LeNet-5网络输入图像全部归一化为32x32,本文没有修改网络结构,直接对MNIST数据pad为32x32.

 

  • 原文第五层输入是5*5*16的featuremap,该层卷积核为5x5,所以相当于一个全连接层。
  • loss中加入正则项,避免过拟合
 tf.add_to_collection('losses', _reg(fc5_weight))

2.2 完整代码

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

WIDTH = 32
HEIGHT = 32

training_epochs = 20000
training_batch = 32
display_step = 50
test_step = 100

mnist = input_data.read_data_sets('../MNIST_data/',one_hot=True)
x_test = mnist.test.images
y_test = mnist.test.labels

xs = tf.placeholder(tf.float32,[None,WIDTH,HEIGHT],name='x_data')
ys = tf.placeholder(tf.float32,[None,10],name='y_data')
keep_prob = tf.placeholder(tf.float32,name='keep_prob')

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

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

# x:输入 W:权重
def conv2d(x, W, padding):
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding=padding)

def relu_bias(x,bias,name):
    return tf.nn.relu(tf.nn.bias_add(x,bias),name=name)

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


def inference(_input,_reg):

    x_data = tf.reshape(_input,[-1,32,32,1])
    # 第一层:卷积层,过滤器的尺寸为5×5,深度为6,不使用全0补充,步长为1。
    # 尺寸变化:32×32×1->28×28×6
    with tf.variable_scope('Layer1_Conv'):
        conv1_weight = weight_variable([5,5,1,6],'conv1_weight')
        tf.summary.histogram('Layer1_Conv/weights',conv1_weight)
        conv1_bias = bias_variable([6],'conv1_bias')
        tf.summary.histogram('Layer1_Conv/biases', conv1_bias)
        conv1 = conv2d(x_data,conv1_weight,'VALID')
        conv1_relu = relu_bias(conv1,conv1_bias,'conv1_relu')
        tf.summary.histogram('Layer1_Conv/output', conv1_relu)

    # 第二层:池化层,过滤器的尺寸为2×2,使用全0补充,步长为2。
    # 尺寸变化:28×28×6->14×14×6
    with tf.name_scope('Layer2_Pooling'):
        pool2 = max_pool_2x2(conv1_relu)
        tf.summary.histogram('Layer2_Pooling/output', pool2)

    # 第三层:卷积层,过滤器的尺寸为5×5,深度为16,不使用全0补充,步长为1。
    # 尺寸变化:14×14×6->10×10×16
    with tf.variable_scope('Layer3_Conv'):
        conv3_weight = weight_variable([5, 5, 6, 16], 'conv3_weight')
        tf.summary.histogram('Layer3_Conv/weights', conv3_weight)
        conv3_bias = bias_variable([16], 'conv2_bias')
        tf.summary.histogram('Layer3_Conv/biases', conv3_bias)
        conv3 = conv2d(pool2, conv3_weight, 'VALID')
        conv3_relu = relu_bias(conv3, conv3_bias, 'conv3_relu')
        tf.summary.histogram('Layer3_Conv/output', conv3_relu)

    # 第四层:池化层,过滤器的尺寸为2×2,使用全0补充,步长为2。
    # 尺寸变化:10×10×6->5×5×16
    with tf.variable_scope('Layer4_Pooling'):
        pool4 = max_pool_2x2(conv3_relu)
        tf.summary.histogram('Layer4_Pooling/output', pool4)

    # 原文第五层是5*5的卷积层,因为输入是5*5*16的map,所以这里即相当于一个全连接层。
    # 5×5×16->1 x 400
    pool4_shape = pool4.get_shape().as_list()
    size = pool4_shape[1] * pool4_shape[2] * pool4_shape[3]
    pool4_reshape = tf.reshape(pool4,[-1,size])


    # 第五层:全连接层,nodes=5×5×16=400,400->120的全连接
    # 尺寸变化:比如一组训练样本为64,那么尺寸变化为64×400->64×120
    # 训练时,引入dropout,dropout在训练时会随机将部分节点的输出改为0,dropout可以避免过拟合问题。
    # 这和模型越简单越不容易过拟合思想一致,和正则化限制权重的大小,使得模型不能任意拟合训练数据中的随机噪声,以此达到避免过拟合思想一致。
    with tf.variable_scope('Layer5_FC'):

        fc5_weight = weight_variable([size,120],'fc5_weight')
        tf.summary.histogram('Layer5_FC/weights', fc5_weight)
        fc5_bias = bias_variable([120],'fc5_bias')
        tf.summary.histogram('Layer5_FC/biases', fc5_bias)
        if _reg != None:
            tf.add_to_collection('losses', _reg(fc5_weight))
        fc5 = tf.matmul(pool4_reshape,fc5_weight)
        fc5_relu = relu_bias(fc5,fc5_bias,'fc5_relu')
        fc5_relu = tf.nn.dropout(fc5_relu, keep_prob)
        tf.summary.histogram('Layer5_FC/output', fc5_relu)

    # 第六层:全连接层,120->84的全连接
    # 尺寸变化:比如一组训练样本为64,那么尺寸变化为64×120->64×84
    with tf.variable_scope('Layer6_FC'):

        fc6_weight = weight_variable([120, 84], 'fc6_weight')
        tf.summary.histogram('Layer6_FC/weights', fc6_weight)
        fc6_bias = bias_variable([84], 'fc6_bias')
        tf.summary.histogram('Layer6_FC/biases', fc6_bias)
        if _reg != None:
            tf.add_to_collection('losses', _reg(fc6_weight))
        fc6 = tf.matmul(fc5_relu, fc6_weight)
        fc6_relu = relu_bias(fc6, fc6_bias,'fc6_relu')
        fc6_relu = tf.nn.dropout(fc6_relu, keep_prob)
        tf.summary.histogram('Layer6_FC/output', fc6_relu)

    # 第七层:全连接层(近似表示),84->10的全连接
    # 尺寸变化:比如一组训练样本为64,那么尺寸变化为64×84->64×10。最后,64×10的矩阵经过softmax之后就得出了64张图片分类于每种数字的概率,
    # 即得到最后的分类结果。
    with tf.variable_scope('Layer7_FC'):

        fc7_weight = weight_variable([84, 10], 'fc7_weight')
        tf.summary.histogram('Layer7_FC/weights', fc7_weight)
        fc7_bias = bias_variable([10], 'fc7_bias')
        tf.summary.histogram('Layer7_FC/biases', fc7_bias)
        if _reg != None:
            tf.add_to_collection('losses', _reg(fc7_weight))
        result = tf.matmul(fc6_relu, fc7_weight) + fc7_bias
        tf.summary.histogram('Layer7_FC/output', result)
    return result

# padding mnist 28x28->32x32
def mnist_reshape_32(_batch):

    batch = np.reshape(_batch,[-1,28,28])
    num = batch.shape[0]
    batch_32 = np.array(np.random.rand(num,32,32),dtype=np.float32)
    for i in range(num):
        batch_32[i] = np.pad(batch[i],2,'constant',constant_values=0)

    return batch_32



regularizer = tf.contrib.layers.l2_regularizer(0.001)
y = inference(xs,regularizer)

cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y,labels=tf.argmax(ys,1))
with tf.name_scope('loss'):
    loss = tf.reduce_mean(cross_entropy) + tf.add_n(tf.get_collection('losses'))
    tf.summary.scalar('loss',loss)

with tf.name_scope('loss'):
    train_step = tf.train.GradientDescentOptimizer(.01).minimize(loss)
correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(ys,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

init = tf.global_variables_initializer()

x_test_32 = mnist_reshape_32(x_test)


with tf.Session() as sess:

    merged = tf.summary.merge_all()
    writer = tf.summary.FileWriter('logs/', sess.graph)

    sess.run(init)
    for i in range(training_epochs):
        batch_xs, batch_ys = mnist.train.next_batch(training_batch) # 28x28
        batch_xs_32 = mnist_reshape_32(batch_xs)

        sess.run(train_step,feed_dict={xs:batch_xs_32,ys:batch_ys,keep_prob:.3})

        if i % display_step == 0:
            print('---------------step:%d, training accuracy:%g--------------' % (i, sess.run(accuracy,feed_dict={
                xs: batch_xs_32, ys: batch_ys, keep_prob: 1.0})))
            rs = sess.run(merged,feed_dict={xs: batch_xs_32, ys: batch_ys, keep_prob: 1.0})
            writer.add_summary(rs,i)

        if i % test_step == 0:
            print("test accuracy %g" % sess.run(accuracy, feed_dict={
                xs: x_test_32, ys: y_test, keep_prob: 1.0}))

    print('---------------step:%d, training accuracy:%g--------------' % (i, sess.run(accuracy, feed_dict={
        xs: batch_xs_32, ys: batch_ys, keep_prob: 1.0})))

3.测试结果

网络结构图:

【Tensorflow】LeNet-5训练MNIST数据集_第2张图片

loss:

【Tensorflow】LeNet-5训练MNIST数据集_第3张图片

 

【Tensorflow】LeNet-5训练MNIST数据集_第4张图片 

~~还在入门学习中,水平有限,如有错误请指正,谢谢~~


参考链接:

https://blog.csdn.net/enchanted_zhouh/article/details/76855108

http://cuijiahua.com/blog/2018/01/dl_3.html

你可能感兴趣的:(Tensorflow学习笔记,TensorFlow学习记录)