附:课程链接
第七讲.卷积神经网络
7.2lenet5代码讲解
由于个人使用Win7系统,并未完全按照课程所讲,以下记录的也基本是我的结合课程做的Windows系统+PyCharm操作。且本人有python基础,故一些操作可能简略。并未完全按照网课。
记住编写代码时,除注释内容外,字符均使用英文格式。
一、Lenet神经网络结构为:
Lenet5神经网络是LeCun等人在1998年提出的。
Lenet5使用了6个5×5×1的核对输入的32×32×1的图片进行卷积计算,卷积计算后的结果通过激活函数,由于不使用零填充、步长为1,用此公式:
计算得到输出尺寸为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的图片转换成一维数组,喂入全连接网络,计算分类评估值。
上述全过程如下:
二、对Lenet神经网络微调,以适应mnist数据集
mnist数据集存放的是28×28×1的灰度图片,我们把Lenet5进行更改,让其匹配28×28×1的输入图片。
微调操作:
微调后的结构:
三、代码
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:
运行mnist_lenet5_test.py:
【BUG:
在运行mnist_lenet5_backward.py时出现了这样的bug:err, “a Variable name or other graph key that is missing”)
解决方案
(我这里报错的原因是原来的文件夹内训练好的模型都还没删除,太多了,删掉以后就能正常运行了)】
至此,第七讲结束。