TensorFlow入门教程(5)卷积神经网络CNN

#
#作者:韦访
#博客:https://blog.csdn.net/rookie_wei
#微信:1007895847
#添加微信的备注一下是CSDN的
#欢迎大家一起学习
#

1、概述

第一讲我们用Softmax模型来训练MNIST数据集,得到模型的准确率为0.9175。这一讲我们来学习一个深度学习中应用非常非常广泛的卷积神经网络CNN,CNN是很多复杂的神经网络的基础。接下来我们首先会讲CNN的原理,然后再用CNN去训练MNIST,看看它的威力。

环境配置:

操作系统:Win10 64位

显卡:GTX 1080ti

Python:Python3.7

TensorFlow:1.15.0

 

2、为什么需要CNN

先来看看为什么需要CNN,或者说CNN有什么优点。

上图显示一个全连接神经网络的结构图,可以看到,使用全连接神经网络处理图像的最大问题在于全连接层的参数太多。参数增多除了导致计算速度减慢,还很容易导致过拟合问题,而卷积神经网络就可以有效的减少神经网络中的参数。

3、CNN结构示意图

上图是一个用于图像分类任务的卷积神经网络架构图,主要由5种结构组成:输入层、卷积层、池化层、全连接层、Softmax层。下面一一讲解每个层的作用。

输入层:输入层是整个神经网络的输入,在处理图像的卷积神经网络中,它一般代表了一张图片的像素矩阵。

卷积层:卷积层是一个卷积神经网络中最重要的部分。和传统全连接层络不同,卷积层中每一个节点的输入只是上一层神经网络的一小块,这一小块常用的大小有3×3或者5×5 。卷积层试图将神经网络中的每一小块进行更加深入地分析从而得到抽象程度更高的特征。一般来说,通过卷积层处理过的节点矩阵会变得更深,所以在上图中可以看到,经过卷积层之后的节点矩阵的深度会增加。

池化层:池化层神经网络不会改变三维矩阵的深度,但是它可以缩小矩阵的大小。池化操作可以认为是将一张分辨率较高的图片转化为分辨率较低的图片。通过池化层,可以进一步缩小最后全连接层中节点的个数,从而达到减少整个神经网络中参数的目的。

全连接层:经过多轮卷积层和池化层的处理之后,在卷积神经网络的最后一般会是有1到2个全连接层来给出最后的分类结果。在特征提取完成之后,仍然需要使用全连接层来完成分类任务。

Softmax层:Softmax层主要用于分类问题,通过Softmax层,可以得到当前样例属于不同种类的概率分布情况。

4、卷积层

上面只是大概讲了每个层的作用,现在我们来重点讲解卷积层和池化层的具体计算过程,先来看卷积层的。

前向传播

卷积层的前向传播过程就是通过将一个过滤器从神经网络当前层的左上角移动到右下角,并且在移动中计算每一个对应的单元矩阵得到。

上图展示了在3×3矩阵上使用2×2过滤器的卷积层前向传播过程,节点矩阵深度都为1。在这个过程中,首先将这个过滤器用于左上角子矩阵,然后移动到右上角矩阵,再到左下角矩阵,最后到右下角矩阵。过滤器每移动一次,可以计算得到一个值(当深度为K时会计算出K个值),将这些数值拼接成一个新的矩阵,就完成了一个卷积层前向传播的过程。

全零填充

如上图,为了避免尺寸变化,可以在当前层矩阵的边界上加入全0填充,这样可以使得卷积层前向传播结果矩阵的大小和当前层矩阵保持一致。

移动步长

除了使用全0填充,还可以通过设置过滤器移动的步长来调整结果矩阵的大小。上图为过滤器移动步长为2时,卷积层前向传播的过程。当宽和高的步长均为2时,过滤器每隔2步计算一次结果,所以得到的结果矩阵的宽和高也就都只有原来的一半。

6、使用两层卷积神经网络识别MNIST

接下来,我们使用TensorFlow来构建一个两层卷积神经网络来识别MNIST数据集中的手写数字。

加载MNIST数据集

首先,使用TensorFlow集成的接口来加载MNIST数据集,代码如下,

# coding: utf-8
import tensorflow.compat.v1 as tf
from tensorflow.examples.tutorials.mnist import input_data
import os

# 加载MNIST数据集
mnist = input_data.read_data_sets('mnist_data', one_hot=True)

创建占位符

创建占位符,用于临时存放 MNIST图片数据和标签,代码如下,

# 创建x占位符,用于临时存放MNIST图片的数据,
# [None, 784]中的None表示不限长度,而784则是一张图片的大小(28×28=784)
x = tf.placeholder(tf.float32, [None, 784])
# label 存的是实际图像的标签,即对应于每张输入图片实际的值
label = tf.placeholder(tf.float32, [None, 10])

将图片数据从向量转为二维矩阵

我们第二讲中已经说过,MNIST图片的存储方式并不是常见的的形式,而是一个向量,所以要将它转为二维矩阵的形式,

# 将图片从长度为784的向量重新还原为28×28的矩阵图片,
# 因为MNIST是黑白图片,所以深度为1,
# 第一个参数为-1,表示一维的长度不限定,这样就可以灵活设置每个batch的训练的个数了
x_image = tf.reshape(x, [-1, 28, 28, 1])

设计神经网络结构

这个网络结构比较简单,大致如下图所示,只不过我们用一个全连接层,

卷积层

先来看看我们封装了的卷积层函数,代码如下,

# 初始化过滤器
def weight_variable(shape):
    return tf.Variable(tf.truncated_normal(shape, stddev=0.1))

# 初始化偏置
def bias_variable(shape):
    return tf.Variable(tf.constant(0.1, shape=shape))

# 卷积运算
def conv2d(x, W):
    # strides表示每一维度滑动的步长,一般strides[0] = strides[3] = 1
    # 第四个参数可选"Same"或"VALID",“Same”表示边距使用全0填充
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 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")

# 卷积层
def conv_layer(input, filter_shape, bias_shape):
    W = weight_variable(filter_shape)
    b = bias_variable(bias_shape)
    # 使用conv2d函数进行卷积计算,然后再用ReLU作为激活函数
    h = tf.nn.relu(conv2d(input, W) + b)
    # 卷积以后再经过池化操作
    return max_pool_2x2(h)

TensorFlow封装了卷积操作,我们只要调用tf.nn.conv2d函数即可,经过卷积运算以后,再使用激活函数去线性化,然后再经过一个池化层。

全连接层

经过卷积层运算以后,再把它放到全连接层,代码如下,

# 全连接层
def dense(input, weight_shape, bias_shape, reshape):
    W = weight_variable(weight_shape)
    b = bias_variable(bias_shape)
    # 将输入数据还原成向量的形式
    h = tf.reshape(input, reshape)
    # 使用ReLU作为激活函数
    return tf.nn.relu(tf.matmul(h, W) + b)

需要说明的是,经过卷积层以后,我们得到的数据是一个矩阵形式,所以在经过全连接层前,要将数据从矩阵的形式又变回向量的形式。这里同样也需要使用激活函数。

Dropout

为了防止过拟合,我们还有经过dropout正则化,代码如下,

# dropout
def dropout(input):
    # 为了防止过拟合,使用dropout正则化
    keep_prob = tf.placeholder(tf.float32)
    return keep_prob, tf.nn.dropout(input, keep_prob)

Softmax

最后,经过Softmax输出图片属于各个类别的概率,代码如下,

# Softmax输出
def softmax(input, weight_shape, bias_shape):
    W = weight_variable(weight_shape)
    b = bias_variable(bias_shape)
    # 最后都要经过Softmax函数将输出转化为概率问题
    return tf.nn.softmax(tf.matmul(input, W) + b)

损失函数和优化器

接着就要定义损失函数和优化器了,代码如下,

# 定义损失函数和优化器
def optimizer(label, y):
    loss = tf.reduce_mean(-tf.reduce_sum(label * tf.log(y)))
    return tf.train.AdamOptimizer(1e-4).minimize(loss), loss

计算模型预测的准确率

最后计算模型预测的准确率,代码如下,

# 计算模型预测准确率
def accuracy(label, y):
    pred = tf.equal(tf.argmax(y, 1), tf.argmax(label, 1))
    return tf.reduce_mean(tf.cast(pred, tf.float32))

网络结构

上面都是我们将各个功能封装成的函数,下面来看看真正的网络结构,代码如下,

# 网络结构
def net(input, label):
    # 第一层卷积
    # 将过滤器设置成5×5×1的矩阵,
    # 其中5×5表示过滤器大小,1表示深度,因为MNIST是黑白图片只有一层。所以深度为1
    # 32表示我们要创建32个大小5×5×1的过滤器,经过卷积后算出32个特征图(每个过滤器得到一个特征图)
    c1 = conv_layer(input, [5, 5, 1, 32], [32])

    # 第二层卷积
    # 因为经过第一层卷积运算后,输出的深度为32,所以过滤器深度也为32,64是指经过第二层卷积运算以后的深度
    c2 = conv_layer(c1, [5, 5, 32, 64], [64])

    # 全连接层
    # 经过两层卷积后,图片的大小为7×7(第一层池化后输出为(28/2)×(28/2),
    # 第二层池化后输出为(14/2)×(14/2)),深度为64,
    # 这个全连接层中,使用1024个隐藏节点,所以权重W的尺寸为[7 * 7 * 64, 1024]
    f1 = dense(c2, [7 * 7 * 64, 1024], [1024], [-1, 7 * 7 * 64])

    # dropout
    keep_prob, h = dropout(f1)

    # Softmax
    y = softmax(h, [1024, 10], [10])

    # 定义损失函数和优化器
    op, loss = optimizer(label, y)

    # 计算预测准确率
    acc = accuracy(label, y)

    return acc, op, keep_prob

创建会话

现在万事俱备只欠东风了,接下来就是创建会话了,跟第二讲的内容一样,我直接上代码了,

# 搭建神经网络结构
acc, op, keep_prob, loss = net(x_image, label)
tf.summary.scalar('loss', loss)
tf.summary.scalar('accuracy', acc)


# 创建模型要保存的路径
saveFile = mkdir_saver()

saver = tf.train.Saver()

# 开始训练
with tf.Session() as sess:
    # 初始化所有变量
    sess.run(tf.global_variables_initializer())

    summary_op = tf.summary.merge_all()
    summary_writer = tf.summary.FileWriter("log/", sess.graph)

    # 训练两万次
    for i in range(20000):
        # 每次获取50张图片数据和对应的标签
        batch = mnist.train.next_batch(50)

        # 将数据传入神经网络,开始训练
        sess.run(op, feed_dict={x:batch[0], label:batch[1], keep_prob:0.5})
        # 每训练100次,我们打印一次训练的准确率
        if i % 100 == 0:
            train_accuracy = sess.run(acc, feed_dict={x: batch[0], label: batch[1], keep_prob: 1.0})
            print("step %d, training accuracy %g" % (i, train_accuracy))

            summary_str = sess.run(summary_op, feed_dict={x: batch[0], label: batch[1], keep_prob: 1.0})
            summary_writer.add_summary(summary_str, i)


    print ("end train, start testing...")

    # 训练结束后,我们使用测试集 mnist.test测试最后的准确率
    print("test accuracy %g" % sess.run(acc, feed_dict={x:mnist.test.images, label:mnist.test.labels, keep_prob:1.0}))

    # 最后,将训练结果保存,如果不保存我们这次训练结束后的结果也随着程序运行结束而释放了
    saver.save(sess, saveFile)

需要注意的是,我们这里使用了TensorBoard可视化工具,这个工具提供了tf.summary模块,通过tf.summary.scalar函数可以将标量的信息显示出来,一般用来话loss、accuary等。用tf.summary.merge_all()则可以将所有summary保存到磁盘,再通过tensorboard显示。通过tf.summary.FileWriter则可以指定一个文件来保存图。接下来,我们将上面代码跑起来。

运行结果

首先来看准确率,

比第二讲的91.75%识别率有了非常大幅度的提升,这就是卷积神经网络的威力。

再来看看怎么使用TensorBoard的可视化工具,可以看到当前文件夹下新生成了一个log子文件夹,

在cmd中,假设你已经cd到当前工程目录下,只需要执行下面命令即可,

tensorboard --logdir log

执行完上面的命令后,会看到如下提示信息,

将红色框起来的链接用浏览器打开,得到如下界面,

这就是TensorBoard的可视化界面,可以看到我们模型训练过程中的accuracy和loss以图表的形式很直观的显示出来了,点击GRAPHS,就可以直观看到我们整个神经网络图的设计结构,如下图所示,

大家自行对比右边的图的结构和我们代码的结构就很容易明白了,这个图要从下往上看。

7、完整代码

完整代码链接如下,

 

https://mianbaoduo.com/o/bread/YpaclZ0=

 

下一讲,我们来看看读取数据集之队列的基本概念和知识。

 

 

  • 8
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值