《人工智能实践:Tensorflow笔记》听课笔记25_7.2lenet5代码讲解

附:课程链接
第七讲.卷积神经网络
7.2lenet5代码讲解

由于个人使用Win7系统,并未完全按照课程所讲,以下记录的也基本是我的结合课程做的Windows系统+PyCharm操作。且本人有python基础,故一些操作可能简略。并未完全按照网课。

记住编写代码时,除注释内容外,字符均使用英文格式。

一、Lenet神经网络结构为:

Lenet5神经网络是LeCun等人在1998年提出的。
Lenet5使用了6个5×5×1的核对输入的32×32×1的图片进行卷积计算,卷积计算后的结果通过激活函数,由于不使用零填充、步长为1,用此公式:
《人工智能实践:Tensorflow笔记》听课笔记25_7.2lenet5代码讲解
计算得到输出尺寸为28。
因为用了6个核,所以输出深度是6。之后又用2×2的核对28×28×6的图片进行池化操作、步长为2,又用公式求得输出尺寸为14。
(可见通过池化操作减少了75%的操作,池化操作不改变输入图片的深度。)所以输出是14×14×6。
使用16个5×5×6的核(核的深度要和输入的深度一致,所以核的深度是6),这个5×5×6的核对输入图片14×14×6进行卷积计算,卷积计算的结果通过激活函数。由于用了16个5×5×6的核,输出的深度等于使用卷积核的个数,是10×10×16。再用2×2的核对输入的10×10×16的图片进行池化操作,池化操作对每个深度分别操作。池化不改变图片的深度,输出是5×5×16的图片,最后把5×5×16的图片转换成一维数组,喂入全连接网络,计算分类评估值。
上述全过程如下:
《人工智能实践:Tensorflow笔记》听课笔记25_7.2lenet5代码讲解
二、对Lenet神经网络微调,以适应mnist数据集

mnist数据集存放的是28×28×1的灰度图片,我们把Lenet5进行更改,让其匹配28×28×1的输入图片。
微调操作:
《人工智能实践:Tensorflow笔记》听课笔记25_7.2lenet5代码讲解
微调后的结构:
《人工智能实践:Tensorflow笔记》听课笔记25_7.2lenet5代码讲解
三、代码
Lenet神经网络在mnist数据集上的实现,主要分为三个部分:前向传播过程(mnist_lenet5_forward.py)、反向传播过程(mnist_lenet5_backward.py)、测试过程(mnist_lenet5_test.py)。

1、前向传播过程
在前向传播过程中,搭建了神经网络。

"""
    前向传播
"""
import tensorflow as tf

#在前向传播中,首先定义了神经网络的相关参数
IMAGE_SIZE = 28 #mnist数据集每张图片分辨率,也就是横向和纵向的边长都是28个像素点
NUM_CHANNELS = 1    #是灰度图,所以通道数是1
CONV1_SIZE = 5  #第一层卷积核大小为5
CONV1_KERNEL_NUM = 32   #第一层核个数为32
CONV2_SIZE = 5  #第二层卷积核大小为5
CONV2_KERNEL_NUM = 64   #第二层卷积核个数为64
FC_SIZE = 512   #第一层全连接网络有512个神经元
OUTPUT_NODE = 10    #第二层全连接网络有10个神经元,对应了十分类的输出

"""
    权重w生成函数,与之前一样
"""
def get_weight(shape,regularizer):  #shape表示生成张量的维度,regularizer表示正则化权重
    w = tf.Variable(tf.truncated_normal(shape,stddev=0.1))   #生成去掉过大偏离点的正太分布随机数
    if regularizer != None: tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(regularizer)(w))
    #如果使用正则化(regularizer != None),则把每一个w的正则化计入到总losses
    return w

"""
    偏置b生成函数,与之前一样
"""
def get_bias(shape):    #shape表示生成张量的维度
    b = tf.Variable(tf.zeros(shape))   #生成初始值为0的偏置b
    return b

"""
    卷积层计算函数
"""
def conv2d(x,w):
    return tf.nn.conv2d(x,w,strides=[1,1,1,1],padding = 'SAME') #给出输入图片x、所用卷积核w
    #x是对输入的描述,是个四阶张量。一阶给出一次喂入多少张图片,也就是batch,二、三阶分别给出图片行、列分辨率,四阶给出输入的通道数。
    #w是对卷积核的描述,也是个四阶张量。一、二阶分别给出图片行、列分辨率。三阶是通道数,四阶是有多少个卷积核。
    #strides为卷积核滑动步长,二、三阶表示滑动步长都是1。
    #使用零填充,所以padding = 'SAME'。

"""
    最大池化层计算函数
"""
def max_pool_2x2(x):
    return tf.nn.max_pool(x,ksize = [1,2,2,1],strides=[1,2,2,1],padding = 'SAME')
    #x是对输入的描述,是个四阶张量。一阶给出一次喂入多少张图片,也就是batch,二、三阶分别给出图片行、列分辨率,四阶给出输入的通道数。
    #池化层大小ksize是2*2的。
    #strides二、三阶表示滑动步长都是1。
    #使用零填充,所以padding = 'SAME'。


"""
    搭建神经网络,描述从输入到输出的数据流
"""
def forward(x,train,regularizer):
    conv1_w = get_weight([CONV1_SIZE,CONV1_SIZE,NUM_CHANNELS,CONV1_KERNEL_NUM],regularizer) #初始化第一层卷积核
    conv1_b = get_bias([CONV1_KERNEL_NUM])  #初始化第一层偏置
    conv1 = conv2d(x,conv1_w)   #执行卷积计算,输入是x,卷积核是初始化的conv1_w
    relu1 = tf.nn.relu(tf.nn.bias_add(conv1,conv1_b))   #对卷积后的输出conv1添加偏置,通过激活函数
    pool1 = max_pool_2x2(relu1) #将激活后的输出进行最大池化

    conv2_w = get_weight([CONV2_SIZE,CONV2_SIZE,CONV1_KERNEL_NUM,CONV2_KERNEL_NUM],regularizer) #第二层卷积核的深度等于上层卷积核的个数
    conv2_b = get_bias([CONV2_KERNEL_NUM])
    conv2 = conv2d(pool1,conv2_w)   #第二层的输入是上一层的输出pool1
    relu2 = tf.nn.relu(tf.nn.bias_add(conv2,conv2_b))   #通过激活函数
    pool2 = max_pool_2x2(relu2) #通过最大池化。pool2是第二层卷积的输出。需要把它从三维张量变为二维张量

    pool_shape = pool2.get_shape().as_list()    #得到pool2输出矩阵的维度存入list()中
    nodes = pool_shape[1] * pool_shape[2] * pool_shape[3]
    #提取特征的长度(pool_shape[1]),提取特征的宽度(pool_shape[2]),提取特征的深度(pool_shape[3])
    #这三个相乘就是所有特征点的个数
    reshaped = tf.reshape(pool2,[pool_shape[0],nodes])  #pool_shape[0]是一个batch的值。
    #将pool2表示成batch行所有特征点作为个数列的二维形状,喂入全连接网络中

    #通过第一层全连接网络
    fc1_w = get_weight([nodes,FC_SIZE],regularizer)
    fc1_b = get_bias([FC_SIZE])
    fc1 = tf.nn.relu(tf.matmul(reshaped,fc1_w) + fc1_b) #把上层的输出reshaped乘以本层线上的权重,加上偏置,过激活函数
    if train: fc1 = tf.nn.dropout(fc1,0.5) #如果是训练阶段,则对该层输出使用50%的dropout

    #通过第二层全连接网络
    fc2_w = get_weight([FC_SIZE,OUTPUT_NODE],regularizer)   #初始化第二层全连接网络的w
    fc2_b = get_bias([OUTPUT_NODE]) #初始化第二层全连接网络的b
    y = tf.matmul(fc1,fc2_w) + fc2_b    #上层的输出和本层线上的权重相乘,加上偏置,得到输出y

    return y

2.反向传播过程
在反向传播过程中,基本与之前的代码相同。只增加了对输入数据的形状调整。

"""
    反向传播
"""
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_lenet5_forward
import os   #os模块
import numpy as np

BATCH_SIZE = 100    #定义每轮喂入神经网络多少张图片
LEARNING_RATE_BASE = 0.005    #最开始的学习率
LEARNING_RATE_DECAY = 0.99  #学习率衰减率
REGULARIZER = 0.0001    #正则化系数
STEPS = 50000   #共训练多少轮
MOVING_AVERAGE_DECAY = 0.99 #滑动平均衰减率
MODEL_SAVE_PATH = "mnist_model" #模型的保存路径
MODEL_NAME = "mnist_model"  #模型保存的文件名
train_num_examples = 60000

#对于反向传播而言,要训练网络参数
def backward(mnist):    #在backward函数中读入mnist
    """
        x,y_是定义的占位符,指定参数为浮点型。由于卷积层输入为四阶张量,
        x的一阶表示每轮喂入的图片数量,  二、三阶分别表示图片的行、列分辨率,
        四阶表示输入的通道数
    """
    x = tf.placeholder(tf.float32, [
        BATCH_SIZE,
        mnist_lenet5_forward.IMAGE_SIZE,
        mnist_lenet5_forward.IMAGE_SIZE,
        mnist_lenet5_forward.NUM_CHANNELS])  #给x占位
    y_ = tf.placeholder(tf.float32, [None, mnist_lenet5_forward.OUTPUT_NODE])    #给y_占位
    y = mnist_lenet5_forward.forward(x,True,REGULARIZER)   #True表示训练参数时使用dropout操作
    global_step = tf.Variable(0,trainable=False)    #给轮数计数器赋初值,设定为不可训练

    ce = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))  # 实现softmax和交叉熵的协同使用
    cem = tf.reduce_mean(ce)
    loss = cem + tf.add_n(tf.get_collection('losses'))  #调用包含正则化的损失函数loss

    #定义指数衰减学习率
    learning_rate = tf.train.exponential_decay(
        LEARNING_RATE_BASE,
        global_step,
        train_num_examples / BATCH_SIZE,
        LEARNING_RATE_DECAY,
        staircase=True)

    #定义训练过程
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)

    #定义滑动平均
    ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    ema_op = ema.apply(tf.trainable_variables())
    with tf.control_dependencies([train_step, ema_op]):
        train_op = tf.no_op(name='train')

    #实例化saver
    saver = tf.train.Saver() #tf.compat.v1.train.Saver

    #在with结构中初始化所有变量
    with tf.Session() as sess:
        init_op = tf.global_variables_initializer()
        sess.run(init_op)

        ckpt = tf.train.get_checkpoint_state(MODEL_SAVE_PATH)
        if ckpt and ckpt.model_checkpoint_path:
            saver.restore(sess, ckpt.model_checkpoint_path)

        #在for循环中迭代STEPS轮
        for i in range(STEPS):
            xs, ys = mnist.train.next_batch(BATCH_SIZE)  #每次读入BATCH_SIZE组图片和标签
            #用np.reshape实现了从数据集中取出的xs进行reshape操作
            reshaped_xs = np.reshape(xs,(
            BATCH_SIZE,
            mnist_lenet5_forward.IMAGE_SIZE,
            mnist_lenet5_forward.IMAGE_SIZE,
            mnist_lenet5_forward.NUM_CHANNELS))
            _,loss_value,step = sess.run([train_op,loss,global_step],feed_dict={x:reshaped_xs,y_:ys})
            #用sess.run把喂入神经网络的x做了同样的修改
            if i % 100 == 0:   #每100轮打印loss值
                print("After %d training steps(s),loss on training batch is %g."%(step,loss_value))
                saver.save(sess,os.path.join(MODEL_SAVE_PATH,MODEL_NAME),global_step=global_step)   #保存模型到当前会话

def main():
    mnist = input_data.read_data_sets("mnist_work/", one_hot=True)
    #mnist = input_data.read_data_sets("./data/", one_hot=True)
    backward(mnist)

if __name__ == '__main__':
    main()

3、测试程序
在测试程序中,会输出识别准确率,也仅对x的形状进行了调整。由于测试是使用训练好的网络,所以不适用dropout,所有神经元都要参加运算。

import time #为了延时,导入了time模块
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_lenet5_forward
import mnist_lenet5_backward
import numpy as np

TEST_INTERVAL_SECS = 5  #定义程序循环的间隔时间是5s

def test(mnist):
    with tf.Graph().as_default() as g:  #绘制计算图中的节点
        #给输入图像x和y_占位
        x = tf.placeholder(tf.float32, [
            mnist.test.num_examples,
            mnist_lenet5_forward.IMAGE_SIZE,
            mnist_lenet5_forward.IMAGE_SIZE,
            mnist_lenet5_forward.NUM_CHANNELS])
        y_ = tf.placeholder(tf.float32, [None, mnist_lenet5_forward.OUTPUT_NODE])
        #用前向传播过程计算出y的值
        y = mnist_lenet5_forward.forward(x,False,None)

        #实例化可还原滑动平均的saver,这样所有参数在会话中北加载时会被赋值为各自的滑动平均值
        ema = tf.train.ExponentialMovingAverage(mnist_lenet5_backward.MOVING_AVERAGE_DECAY)
        ema_restore = ema.variables_to_restore()
        saver = tf.train.Saver(ema_restore)

        #计算正确率
        correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

        while True:
            with tf.Session() as sess:
                #加载训练好的模型ckpt,也就是把滑动平均值赋给各个参数
                ckpt = tf.train.get_checkpoint_state(mnist_lenet5_backward.MODEL_SAVE_PATH)
                #如果已有ckpt模型则恢复
                if ckpt and ckpt.model_checkpoint_path:
                    #恢复会话
                    saver.restore(sess,ckpt.model_checkpoint_path)

                    #恢复轮数
                    global_step = int(ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1])
                    reshaped_x = np.reshape(mnist.test.images, (
                    mnist.test.num_examples,
                    mnist_lenet5_forward.IMAGE_SIZE,
                    mnist_lenet5_forward.IMAGE_SIZE,
                    mnist_lenet5_forward.NUM_CHANNELS))

                    #计算准确率
                    accuracy_score = sess.run(accuracy,feed_dict = {x:reshaped_x,y_:mnist.test.labels})
                    #打印提示
                    print("After %s training step(s),test accuracy = %g"%(global_step,accuracy_score))

                #如果没有模型
                else:
                    print('No checkpoint file found')   #模型不存在提示
                    return

            time.sleep(TEST_INTERVAL_SECS)

def main():
    mnist = input_data.read_data_sets("mnist_work/", one_hot=True)
    test(mnist)

if __name__ == '__main__':
    main()

我们运行一下代码,看一下使用卷积之后的效果。
运行mnist_lenet5_backward.py:
《人工智能实践:Tensorflow笔记》听课笔记25_7.2lenet5代码讲解
运行mnist_lenet5_test.py:
《人工智能实践:Tensorflow笔记》听课笔记25_7.2lenet5代码讲解

【BUG:
在运行mnist_lenet5_backward.py时出现了这样的bug:err, “a Variable name or other graph key that is missing”)
解决方案
(我这里报错的原因是原来的文件夹内训练好的模型都还没删除,太多了,删掉以后就能正常运行了)】

至此,第七讲结束。

附:助教的Tensorflow笔记7

上一篇:c++: rvalue, prvalue, lvalue, glvalue


下一篇:cifar数据集的预处理和训练