之前写了一篇基于minist数据集(手写数字0-9)的全连接层神经网络,识别率(85%)并不高,这段时间学习了一些卷积神经网络的知识又实践了一把, 识别率(96%左右)确实上来了 ,下面把我的学习过程记录下来以便加深理解也希望可以帮到大家伙儿。
一、准备工作
定义定义两个函数weight_variables(shape)、 bias_variables(shape)用来初始化权重偏置
# 定一个初始化权重的函数
def weight_variables(shape):
"""
初始化权重的函数
:param shape:
:return:
"""
w = tf.Variable(tf.random_normal(shape=shape, mean=0.0, stddev=1.0))
return w
# 定义一个初始化偏置的函数
def bias_variables(shape):
"""
初始化偏置的函数
:param shape:
:return:
"""
b = tf.Variable(tf.constant(0.0, shape=shape))
return b
二、自定义卷积模型
1. 老规矩先把位置占下
占位符这里就不赘述了、不明白的可以看上篇。
x = tf.placeholder(tf.float32, [None, 784])
y_true = tf.placeholder(tf.float32, [None, 10])
2. 确定第一卷积层Filter_1的数量跟形状
Filter是什么 是干啥的 下面我会在卷积层通过图片1详细说。
这里我指定Filter_1的形状为5×5×1×32,即长=5、 宽=5、 高=1、 个数=32。
ps:看到这部分人会有疑问,为什么Filter_1的形状是5×5×1×32?其实可以指定Filter_1的形状为任意形状,只要其长宽不超过数据集中图片的长跟宽。当然不要太小也别太大,不然训练效果就不好了。其实Filter的形状是卷积模型中可调整的参数之一,其形状直接影响训练效果。
3. 根据Filter形状确定第一卷积层的权重跟偏置
Filter_1的形状有了那么权重跟偏置的形状就确定下来了,我们直接调用上文写的初始化函数。
# 初始化权重(这里要清楚 权重是跟Filter的形状挂钩的,Filter的形状是什么偏置的形状就是什么)
w_conv1 = weight_variables([5, 5, 1, 32]) # 5行5列、1个通道、32个Filter
# 初始化偏置(有几个Filter就有几个偏置bias)
b_conv1 = bias_variables([32])
4.数据集的形状改变一下
[None,784]单位为张 即None张图片,一张图片的格式为28×28×1。
-1 在tensorflow中代表任意数或不确定数量。
# 对x(数据集)进行形状的改变。[None,784]=>[None,28,28,1]
x_reshape = tf.reshape(x, [-1, 28, 28, 1])
5. 卷积层(convolution)(第一层)
图一动图中四列分别为:
1、第一列三张表格代表一张彩色图的三个通道(RGB)
2、第二列为一个Filter
3、第三列为另一个Filter
4、第四列为卷积结果
对Filter的理解:
1、可以把Filter看作 过滤网,此过滤网可以有好几层,其层数由数据集中图片的通道数决定。如图1 由第一列可以看到此图片有三个通道,那么第二三列Filter(过滤网)的层数均为3层。
2、一个卷积网络会有多个卷积层,每一卷积层都对应若干个Filter,我们这把这个若干个Filter称作Filter组或是“滤芯”。其实Filter的形状是卷积模型中可调整的参数之一,其形状直接影响训练效果。
3、如图1 每个Filter都有自己的偏置bias,第一个Filter的偏置为1,第二个Filter的偏置为0.
卷积操作流程:
1、用Filter去扫描图像矩阵(图1第一列),这个扫描过程就是将Filter矩阵与扫描区域矩阵对应位置相乘然后相加。后再加上Filter对应的偏置bias,最后按行跟列排列,得到卷积的输出(如图中第四列)此过程即为卷积。
2、老规矩 在这还要给神经网络添加非线性功能,也就是添加激活函数。现在深度学习中用到的激活函数99%都是用ReLU。也就是将图1中第四列的值传到ReLU函数中作为ReLU函数的输入。至此卷积层搞定。
3、代码中的参数解析:①x_reshape:训练数据占位符,可以认为它就是我们输入的图片数据(x),因为在训练的过程中我们会把图像数据往这里面放。
②w_conv1:权重(w),我们可以认为这权重就是Filter。所以w的形状跟Filter的形状是一样的。
③strides:滑动步长,Filter在扫描图片矩阵的时候,其左右上下的滑动间隔。tensorflow里面要求strides的形状为1×4,其实在strides=[1, 1, 1, 1]这个数组中只有中间两个数值有用,第一个跟第四个数值都是默认的1。绝大部分Filter的滑动步长(strides)都是1。
④padding:填充标志,看到这里不知道小伙伴们有没有发现一个问题,图1中Filter的滑动步长为2,正好在横向扫描的时候扫描三次,若滑动步长为3,当Filter扫描两次后 我们发现后面还剩1列无法扫描(因为Filter大小为3×3,扫描两次后 图像矩阵中剩余未扫描的部分为3×1)。这时候我们有两种选择:一 剩余的一列我不要了(padding=”VALID“),二 在整个图像矩阵外面在添加两圈零(padding=“SAME”),来补充图像矩阵。
4、图中图像数据非常少,所以卷积后期输出矩阵的形状(行跟列的数量)都很好算,当图像数据非常大时 比如当图像矩阵为1920×1080时 通过人眼是看不出卷积操作后输出矩阵(图1第四行)的形状的。这里给出公式:
其中W、H为元图像矩阵的行跟列,F为Filter的大小(比如一个3×3的Filter,其大小为3),P为Padding的值(即当在图像矩阵外圈添加一圈零)p需要手动计算:比如数据宽为28 Filter宽为5,28除5取余为3(当取余数为0时 p=0),p=5-3=2,也就是在补充2列0。(28-5+2*2)/1 +1 = 28,所以卷积后的输出为[None,28,28,32],其中32 为输出矩阵数量。
width跟height为卷积操作后输出矩阵的列数跟行数
# 卷积操作:[None,28,28,1]=>[None,28,28,32]
x_relu1 = tf.nn.relu(tf.nn.conv2d(x_reshape, w_conv1, strides=[1, 1, 1, 1], padding="SAME") + b_conv1)
图1
5. 池化层(Pooling)(第一层)
池化操作理解:
池化操作跟卷积操作非常相似但池化操作简单的多。一般池化操作分三种(Max Pooling、Mean Pooling、Random Pooling)其中最大值池化Max Pooling、均值池化Mean Pooling最常用。池化操作相对比较简单这里不多赘述,可以参考图2、图3,也可以参考文章。
# 池化操作(2*2、strides=1、个数=32),[None,28,28,32]=>[None,14,14,32]
x_pool1 = tf.nn.max_pool(x_relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
图2
图3
6. 卷积层(convolution)(第二层)
原理与第一层卷积一样。其中Filter_2的 大小=5×5×64 , strides=1
#卷积层二 卷积:(filter_size=5*5*32、filter_number=64、strides=1) => 激活(tf.n.relu) => 池化2*2 strides=2
# 随即初始化权重[5,5,32,64]
w_conv2 = weight_variables([5, 5, 32, 64])
# 随机初始化偏置
b_conv2 = bias_variables([64])
# 卷积([None,14,14,32]->[None,14,14,64])=>激活
x_relu2 = tf.nn.relu(tf.nn.conv2d(x_pool1, filter=w_conv2, strides=[1, 1, 1, 1], padding="SAME") + b_conv2)
7. 池化层(Pooling)(第二层)
同池化层第一层,这里就不再叨叨了。
接下来流程: 全连接层=>优化(梯度下降)=>预测=>保存模型=>利用训练好的模型进行训练。这一系列流程在上篇已经详细介绍。
废话不多说直接上代码 ↓↓↓↓↓↓
三、源代码
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from tensorflow.contrib.slim.python.slim.nets.inception_v3 import inception_v3_base
FlAGS = tf.flags.FLAGS
tf.flags.DEFINE_integer("is_train", 0, "指定程序是预测还是训练")
def conv_fc():
# 获取数据
mnist = input_data.read_data_sets("./data/mnist/input_data/", one_hot=True)
# 专门定义一个模型函数,得出输出
x, y_true, y_predict, w_fc, b_fc = model()
# 求所有样本的损失、然后求平均值
with tf.variable_scope("soft_cross"):
# 求平均交叉熵损失(先经过softmax得到概率,后交叉熵拿损失,后求平均损失)
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_true, logits=y_predict))
# 梯度下降优化损失
with tf.variable_scope("optimizer"):
train_op = tf.train.GradientDescentOptimizer(0.0001).minimize(loss)
# 计算准确率
with tf.variable_scope("acc"):
# 分别拿到预测样本跟目标样本每一行的最大下标 ,若下表相等 返回1 不相等返回0
equal_list = tf.equal(tf.argmax(y_true, 1), tf.argmax(y_predict, 1))
# 准确率=算平均值 None个样本 [1,0,1,0,1,1,0,1,..............................]
accuracy = tf.reduce_mean(tf.cast(equal_list, tf.float32))
# 定一个初始化变量的op
init_op = tf.global_variables_initializer()
# 收集变量 (一维)单个数字值收集
tf.summary.scalar("losses", loss)
tf.summary.scalar("acc", accuracy)
# 收集变量 高纬度变量收集
tf.summary.histogram("weightes", w_fc)
tf.summary.histogram("biases", b_fc)
# 定义一个合并变量的操作operation
merged = tf.summary.merge_all()
# 定义一个saver用于保存模型
saver = tf.train.Saver()
correct = 0
# 开启会话运行训练
with tf.Session() as sess:
filewriter = tf.summary.FileWriter("./temp/summary/test_conv/", graph=sess.graph)
sess.run(init_op)
if FlAGS.is_train == 1:
# 循环进行训练
for i in range(100000):
# 取出真实存在的目标值和特征值
mnist_x, mnist_y = mnist.train.next_batch(50)
# 运行train_op训练
sess.run(train_op, feed_dict={x: mnist_x, y_true: mnist_y})
# 写入每一步训练的值
summary = sess.run(merged, feed_dict={x: mnist_x, y_true: mnist_y})
filewriter.add_summary(summary, i)
print("训练地%d步,准确率为:%f" % (i, sess.run(accuracy, feed_dict={x: mnist_x, y_true: mnist_y})))
# 保存模型
saver.save(sess, "./temp/ckpt_conv/fc_model")
else:
saver.restore(sess, "./temp/ckpt_conv/fc_model")
# 如果是0,则作出预测
for i in range(100):
# 每次测试一张图片
x_test, y_test = mnist.test.next_batch(1)
print("第%d张图片目标是%d,预测结果是:%d" % (
i,
tf.argmax(y_test, 1).eval(),
tf.argmax(sess.run(y_predict, feed_dict={x: x_test, y_true: y_test}), 1).eval()
))
if (tf.argmax(y_test, 1).eval() == tf.argmax(sess.run(y_predict, feed_dict={x: x_test, y_true: y_test}),
1).eval()):
correct += 1
print("100样本预测准确率:%f" % (correct / 100))
return None
def model():
"""
自定义的ziding卷积模型
:return:
"""
# 1、准备占位符 x [None,784] y_true[None,10]
with tf.variable_scope("data"):
x = tf.placeholder(tf.float32, [None, 784])
y_true = tf.placeholder(tf.float32, [None, 10])
# 2、卷积层一 卷积:(filter_size=5*5*1、filter_number=32、strides=1) => 激活(tf.n.relu) => 池化(2*2、strides=1、个数=32)
with tf.variable_scope("conv1"):
# 初始化权重(这里要清楚 权重是跟Filter的形状挂钩的,Filter的形状是什么偏置的形状就是什么)
w_conv1 = weight_variables([5, 5, 1, 32]) # 5行5列、1个通道、32个Filter
# 初始化偏置(有几个Filter就有几个偏置bias)
b_conv1 = bias_variables([32])
# 对x(数据集)进行形状的改变。[None,784]=>[None,28,28,1]
x_reshape = tf.reshape(x, [-1, 28, 28, 1])
# 卷积操作:[None,28,28,1]=>[None,28,28,32]
x_relu1 = tf.nn.relu(tf.nn.conv2d(x_reshape, w_conv1, strides=[1, 1, 1, 1], padding="SAME") + b_conv1)
# 池化操作(2*2、strides=1、个数=32),[None,28,28,32]=>[None,14,14,32]
x_pool1 = tf.nn.max_pool(x_relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
# 3、卷积层二 卷积:(filter_size=5*5*32、filter_number=64、strides=1) => 激活(tf.n.relu) => 池化2*2 strides=2
with tf.variable_scope("conv2"):
# 随即初始化权重[5,5,32,64]
w_conv2 = weight_variables([5, 5, 32, 64])
# 随机初始化偏置
b_conv2 = bias_variables([64])
# 卷积([None,14,14,32]->[None,14,14,64])=>激活
x_relu2 = tf.nn.relu(tf.nn.conv2d(x_pool1, filter=w_conv2, strides=[1, 1, 1, 1], padding="SAME") + b_conv2)
# 池化操作2*2 strides=2,[None,14,14,64]=>[None,7,7,64]
x_pool2 = tf.nn.max_pool(x_relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
# 4、全连接层[None,7,7,64]=>[None,7*7*64]*[7*7*64,10]+[10] = [None,10]
with tf.variable_scope("conv2"):
# 随机初始化权重和偏执
w_fc = weight_variables([7 * 7 * 64, 10])
b_fc = bias_variables([10])
# 修改形状 x_pool2[None,7,7,64]=>[None,7*7*64]
x_fc_reshape = tf.reshape(x_pool2, [-1, 7 * 7 * 64])
# 进行矩阵预算得出每个样本的10个结果
y_predict = tf.matmul(x_fc_reshape, w_fc) + b_fc
return x, y_true, y_predict, w_fc, b_fc
# 定一个初始化权重的函数
def weight_variables(shape):
"""
初始化权重的函数
:param shape:
:return:
"""
w = tf.Variable(tf.random_normal(shape=shape, mean=0.0, stddev=1.0))
return w
# 定义一个初始化偏置的函数
def bias_variables(shape):
"""
初始化偏置的函数
:param shape:
:return:
"""
b = tf.Variable(tf.constant(0.0, shape=shape))
return b
if __name__ == "__main__":
conv_fc()
四、效果一览
1、我这边训练练了10W次 用时15分钟
2、训练时电脑的运行状态。
3、用了1000张图片测试模型的可靠程度。如下图 准确率为94.9%