人工智能实验四:深度学习算法及应用(DNN&CNN)

走远了吗. 提交于 2020-01-12 08:43:37

人工智能实验四报告:深度学习算法及应用

实验目的

  • 了解深度学习的基本原理
  • 能够使用深度学习开源工具识别图像中的数字
  • 了解图像识别的基本原理

实验要求

  • 解释深度学习原理
  • 对实验性能进行分析
  • 回答思考题

实验硬件

计算机

实验软件

软件:windows操作系统

应用软件:TensorFlow、PyCharm、Python、Google Colab

实验内容与步骤

安装开源深度学习工具设计并实现一个深度学习模型,它能够学习识别图像中的数字序列。使用数据训练它,可以使用人工合成的数据(推荐),或直接使用现实数据。

MNIST数据集

通过下面的代码我们可以对其中的图片有一个大致的把握:

import matplotlib.pyplot as plt

# 训练集
train_images = mnist.train.images
train_labels = mnist.train.labels

# 验证集
validation_images = mnist.validation.images
validation_labels = mnist.validation.labels

# 测试集
test_images = mnist.test.images
test_labels = mnist.test.labels

#打印
print(mnist.train.num_examples)
print(mnist.validation.num_examples)
print(mnist.test.num_examples)

im = mnist.train.images[1]
print(im.shape)
print(np.min(im),np.max(im))
im = im.reshape(28, 28)
print(im.shape)
plt.imshow(im, cmap='Greys')
plt.show()

可以知道,MNIST数据集中训练集大小为55000,验证集大小为5000,测试集大小为10000。每一张图片都是28*28大小,每个像素的值最小为0最大为1,0代表白色,1代表黑色,之间的小数表示不同程度的灰色。调用show函数可以绘出数据集中的图片。

具体实现

DNN(深度神经网络)

概述

神经网络是基于感知机的扩展,而DNN可以理解为有很多隐藏层的神经网络,DNN有时也叫做多层感知机。

DNN按不同层划分,内部神经网络层可分为三类:输入层、隐藏层、输出层,层与层之间是全连接的。

每一层的算法都是一样的。比如,对于包含一个输入层,一个输出层和三个隐藏层的神经网络,我们假设其权重矩阵分别为W1,W2,W3,W4W1,W2,W3,W4,每个隐藏层的输出分别是a1,a2,a3\vec{a_1},\vec{a_2},\vec{a_3},神经网络的输入为x\vec{x},神经网络的输入为y\vec{y},如下图所示:

则每一层的输出向量的计算可以表示为:
a1=f(W1x+b1)a2=f(W2a1+b2)a3=f(W3a2+b3)y=f(W4a3+b4) \vec{a_1}=f(W_1\cdot \vec{x}+b1)\\ \vec{a_2}=f(W_2\cdot \vec{a_1}+b2)\\ \vec{a_3}=f(W_3\cdot \vec{a_2}+b3)\\ \vec{y}=f(W_4\cdot \vec{a3}+b4)\\
现在,我们需要知道一个神经网络的每个连接上的权值和偏置是如何得到的。我们可以说神经网络是一个模型,那么这些权值就是模型的参数,也就是模型要学习的东西。

整体来讲,先给各层权重和偏置一个初始值,之后计算DNN的输出,求出损失代价,然后反向传播,朝着尽量减小损失的方向更新各层的权值和偏置。

好在TensorFlow为我们提供了丰富的API,所以我们要做的就是定义好自己的DNN(层数,各层的神经元数目),初始化、更新权值和偏置都调用函数来进行。

具体设计

在学习了TensorFlow的一些相关知识后,我这样设计了DNN:

  • 输入层有784个神经元(28*28),隐藏层1有500个神经元,隐藏层2和隐藏层3都有300个神经元,输出层有10个神经元(0-9十个数字)

  • 使用截断正态分布初始化权值,标准差为0.1

      一般来讲权重矩阵是K个N维向量。从直觉上来讲,如果这K个N维向量在N维空间中均匀分布在以原点为中心的N-1维单位超球面上,在随机性上应该是最好的。因为这样,这K个向量的夹角为均匀分布。此时问题变成了,如何在N-1维超球面上进行均匀采样。若对N维向量的每个分量进行N(0,1)的正态分布采样,生成K个N维向量,然后投影到单位超球面上,那么形成的K个N维向量在单位超球面上均匀分布。所以用正态分布初始化,再单位化,就可以达到这种效果。当然也可以不必单位化。

  • 将偏置初始化为常量0.1

  • 采用交叉熵(cross-entropy)损失代价函数而不是二次代价函数

    交叉熵代价函数:
    C=1nx[ylna+(1y)ln(1a)] C=-\frac{1}{n}\sum_{x}{[y\ln a+(1-y)\ln (1-a)]}

    其中C表示代价函数,x表示样本,y表示实际值,a表示输出值,n表示样本总数。

    a=σ(z)a=\sigma(z)z=WjXj+bz=\sum W_j*X_j+bσ(z)=σ(z)(1σ(z))\sigma'(z)=\sigma(z)(1-\sigma(z))
    Cwj=1nx(yσ(z)(1y)1σ(z))σwj=1nx(yσ(z)(1y)1σ(z))σ(z)xj=1nxσ(z)xjσ(z)(1σ(z))(σ(z)y)=1nxxj(σ(z)y) \frac{\partial C}{\partial w_j} =-\frac{1}{n}\sum_x{(\frac{y}{\sigma(z)}-\frac{(1-y)}{1-\sigma(z)})\frac{\partial \sigma}{\partial w_j}} \\ = -\frac{1}{n}\sum_x{(\frac{y}{\sigma(z)}-\frac{(1-y)}{1-\sigma(z)})\sigma'(z)x_j} \\ = \frac{1}{n}\sum_x{\frac{\sigma'(z)x_j}{\sigma(z)(1-\sigma(z))}(\sigma(z)-y)} \\ = \frac{1}{n}\sum_x{x_j(\sigma(z)-y)} \\
    Cb=1nx(σ(z)y) \frac{\partial C}{\partial b} =\frac{1}{n}\sum_x{(\sigma(z)-y)}
      可以看出,权值和偏置值的调整与σ(z)\sigma'(z)无关。另外,梯度公式中的σ(z)y\sigma(z)-y表示输出值与实际值的误差。所以当误差越大时,梯度就越大,参数WWbb的调整就越快,训练的速度也就越快。

      如果输出激活函数是线性的,那么二次代价就是一种合适的选择;如果输出激活函数是S型的,那么比较适合用交叉熵代价函数。

  • 采用Adam优化器

    Adam是从2个算法脱胎而来的:AdaGrad和RMSProp,它集合了2个算法的主要优点,同时也做了自己的一些创新,大概有这么几个优点:

    • 计算高效,方便实现,内存使用也很少。
    • 更新步长和梯度大小无关,只和alpha、beta_1、beta_2有关系。并且由它们决定步长的理论上限。
    • 对目标函数没有平稳要求,即loss function可以随着时间变化
    • 能较好的处理噪音样本,并且天然具有退火效果
    • 能较好处理稀疏梯度,即梯度在很多step处都是0的情况
  • 不断更新学习率

    将学习率初始化为0.001,之后每迭代一轮就将学习率降低一些,因为越到后面越接近收敛。

代码实现

import tensorflow.compat.v1 as tf
import input_data
import time

tf.disable_v2_behavior()

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

# 每个批次的大小
batch_size = 100  # 一次放入100张图片【可改】
# 计算一共有多少个批次
n_batch = mnist.train.num_examples // batch_size

# 定义两个Placeholder
x = tf.placeholder(tf.float32, [None, 784])  # 28*28=784
y = tf.placeholder(tf.float32, [None, 10])  # 28*28=784
keep_prob = tf.placeholder(tf.float32)
lr = tf.Variable(0.001, dtype=tf.float32)  # 学习率,初始值为0.001,后续会改变

# 创建一个深度神经网络
W1 = tf.Variable(tf.truncated_normal([784, 500], stddev=0.1))  # 截断正态分布初始化,标准差是0.1
b1 = tf.Variable(tf.zeros([500]) + 0.1)
L1 = tf.nn.tanh(tf.matmul(x, W1) + b1)
L1_drop = tf.nn.dropout(L1, keep_prob)  # 百分之多少的神经元在工作

# 隐藏层1
W2 = tf.Variable(tf.truncated_normal([500, 300], stddev=0.1))  # 截断正态分布初始化,标准差是0.1
b2 = tf.Variable(tf.zeros([300]) + 0.1)
L2 = tf.nn.tanh(tf.matmul(L1_drop, W2) + b2)
L2_drop = tf.nn.dropout(L2, keep_prob)  # 百分之多少的神经元在工作

W3 = tf.Variable(tf.truncated_normal([300, 300], stddev=0.1))  # 截断正态分布初始化,标准差是0.1
b3 = tf.Variable(tf.zeros([300]) + 0.1)
L3 = tf.nn.tanh(tf.matmul(L2_drop, W3) + b3)
L3_drop = tf.nn.dropout(L3, keep_prob)  # 百分之多少的神经元在工作

# 输出层
W4 = tf.Variable(tf.truncated_normal([300, 10], stddev=0.1))  # 截断正态分布初始化,标准差是0.1
b4 = tf.Variable(tf.zeros([10]) + 0.1)
prediction = tf.nn.softmax(tf.matmul(L3_drop, W4) + b4)

# 交叉熵损失函数(对于S型的激活函数,使用交叉熵损失函数效果好于二次代价损失函数)
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=prediction))
# 使用Adam优化器(优于随机梯度下降优化器GradientDescentOptimizer)
train_step = tf.train.AdamOptimizer(lr).minimize(loss)  # 以最小化loss为导向更新权值和偏置

# 初始化变量
init = tf.global_variables_initializer()

# 结果存放在一个布尔型列表中
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(prediction, 1))  # tf.argmax返回一维张量中最大的值所在的位置,tf.equal比较二者是否相等
# 求准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))  # cast函数把bool转化为32位float类型,再求平均值

# 定义会话
with tf.Session() as sess:
    start = time.clock()
    sess.run(init)
    for epoch in range(41):
        sess.run(tf.assign(lr, 0.001 * (0.95 ** epoch)))  # 学习率逐渐减小
        for batch in range(n_batch):  # 把所有图片训练一次
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)  # 100张100张地获取
            sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 1.0})  # 1.0相当于没有用dropout【可改为0.7,解决过拟合】
        Learning_rate = sess.run(lr)
        if epoch % 5 == 0:
            test_acc = sess.run(accuracy,
                                feed_dict={x: mnist.test.images, y: mnist.test.labels, keep_prob: 1.0})  # 测试集准确率
            train_acc = sess.run(accuracy,
                                 feed_dict={x: mnist.train.images, y: mnist.train.labels, keep_prob: 1.0})  # 训练集准确率
            print("Iter " + str(epoch) + ",Testing Accuracy " + str(test_acc) + ",Training Accuracy " + str(
                train_acc) + ",Learning Rate " + str(Learning_rate))
    end = time.clock()
    print("Time consuming: " + str(int((end - start) / 60)) + "min" + str(int((end - start) % 60)) + "sec.")

之所以迭代41次是因为经过多次测试,发现到41次正确率已经基本收敛了,没有必要再迭代下去。另外,如果要使用Google colab,那么将

import input_data
mnist = input_data.read_data_sets('mnist_data', one_hot=True)

改为

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

结果输出

本机

Google colab

不得不说Google colab上的GPU加速真的很可以。

结果分析

使用DNN训练的效果还是挺好的,基本上在迭代15-20次时测试集的识别准确率就可以达到98%了,并且在本机上训练时间为4min28sec,在可接受的范围内。

我尝试过增加迭代次数,迭代1001次结果如下:

这个DNN识别的准确率已经达到了极限:98.28%。

此外,这个模型基本不存在过拟合问题,虽然训练集准确率达到了99.7%,但是测试集准确率也达到了98.3%,相差并不是很大。这种情况下,如果使用dropout随机丢掉一部分神经元,反而会降低模型的准确率,还会增加模型训练的时间。

为啥我又用了CNN(卷积神经网络)?

全连接神经网络之所以不太适合图像识别任务,主要有以下几个方面的问题:

  • 参数数量太多 考虑一个输入1000*1000像素的图片(一百万像素,现在已经不能算大图了),输入层有1000*1000=100万节点。假设第一个隐藏层有100个节点(这个数量并不多),那么仅这一层就有(1000*1000+1)*100=1亿参数,这实在是太多了!我们看到图像只扩大一点,参数数量就会多很多,因此它的扩展性很差。
  • 没有利用像素之间的位置信息 对于图像识别任务来说,每个像素和其周围像素的联系是比较紧密的,和离得很远的像素的联系可能就很小了。如果一个神经元和上一层所有神经元相连,那么就相当于对于一个像素来说,把图像的所有像素都等同看待,这不符合前面的假设。当我们完成每个连接权重的学习之后,最终可能会发现,有大量的权重,它们的值都是很小的(也就是这些连接其实无关紧要)。努力学习大量并不重要的权重,这样的学习必将是非常低效的。
  • 网络层数限制 我们知道网络层数越多其表达能力越强,但是通过梯度下降方法训练深度全连接神经网络很困难,因为全连接神经网络的梯度很难传递超过3层。因此,我们不可能得到一个很深的全连接神经网络,也就限制了它的能力。

CNN就可以很好的解决上面的问题。

CNN(卷积神经网络)

概述

全连接神经网络相比,卷积神经网络的训练要复杂一些。但训练的原理是一样的:利用链式求导计算损失函数对每个权重的偏导数(梯度),然后根据梯度下降公式更新权重。训练算法依然是反向传播算法。

卷积神经网络的三个思路

  • 局部连接 这个是最容易想到的,每个神经元不再和上一层的所有神经元相连,而只和一小部分神经元相连。这样就减少了很多参数。
  • 权值共享 一组连接可以共享同一个权重,而不是每个连接有一个不同的权重,这样又减少了很多参数。
  • 下采样 可以使用Pooling来减少每层的样本数,进一步减少参数数量,同时还可以提升模型的鲁棒性。

对于图像识别任务来说,卷积神经网络通过尽可能保留重要的参数,去掉大量不重要的参数,来达到更好的学习效果。

大致认识

如图所示,一个卷积神经网络由若干卷积层Pooling层全连接层组成。我们可以构建各种不同的卷积神经网络,它的常用架构模式为:

INPUT -> [[CONV]*N -> POOL?]*M -> [FC]*K

也就是N个卷积层叠加,然后(可选)叠加一个Pooling层,重复这个结构M次,最后叠加K个全连接层。

三维的层结构

从图中我们可以发现卷积神经网络的层结构和全连接神经网络的层结构有很大不同。全连接神经网络每层的神经元是按照一维排列的,也就是排成一条线的样子;而卷积神经网络每层的神经元是按照三维排列的,也就是排成一个长方体的样子,有宽度高度深度

对于上图展示的神经网络,我们看到输入层的宽度和高度对应于输入图像的宽度和高度,而它的深度为1。接着,第一个卷积层对这幅图像进行了卷积操作(后面我们会讲如何计算卷积),得到了三个Feature Map。这里的"3"实际上,就是这个卷积层包含三个Filter,也就是三套参数,每个Filter都可以把原始输入图像卷积得到一个Feature Map,三个Filter就可以得到三个Feature Map。至于一个卷积层可以有多少个Filter,是可以自由设定的。也就是说,卷积层的Filter个数也是一个超参数。我们可以把Feature Map可以看做是通过卷积变换提取到的图像特征,三个Filter就对原始图像提取出三组不同的特征,也就是得到了三个Feature Map,也称做三个通道(channel)

继续观察,在第一个卷积层之后,Pooling层对三个Feature Map做了下采样,得到了三个更小的Feature Map。接着,是第二个卷积层,它有5个Filter。每个Filter都把前面下采样之后的3个Feature Map卷积在一起,得到一个新的Feature Map。这样,5个Filter就得到了5个Feature Map。接着,是第二个Pooling,继续对5个Feature Map进行下采样,得到了5个更小的Feature Map。

上图所示网络的最后两层是全连接层。第一个全连接层的每个神经元,和上一层5个Feature Map中的每个神经元相连,第二个全连接层(也就是输出层)的每个神经元,则和第一个全连接层的每个神经元相连,这样得到了整个网络的输出。

卷积层输出值的计算

假设有一个5*5的图像,使用一个3*3的filter进行卷积,想得到一个3*3的Feature Map,如下所示:

将filter盖在image左上角,刚好覆盖9个像素。之后将上下位置相同的像素相乘,最后将得到的9个数相加,结果填入feature map的第一行第一列。然后将filter向右平移一个像素,重复将上下位置相同的像素相乘,将得到的9个数相加,结果填入feature map第一行第二列。以此类推,可以填满整个feature map,这就是卷积的过程。

上面的计算过程中,步幅(stride)为1。步幅可以设为大于1的数。例如,当步幅为2时Feature Map计算如下:

注意到,当步幅设置为2的时候,Feature Map就变成2*2了。这说明图像大小、步幅和卷积后的Feature Map大小是有关系的。事实上,它们满足下面的关系:
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ W_2 & =(W_1-F+…
在上面两个公式中,W2W_2是卷积后Feature Map的宽度;W1W_1是卷积前图像的宽度;FF是filter的宽度;是Zero Padding数量,Zero Padding是指在原始图像周围补几圈0,如果的值是1,那么就补1圈0;SS步幅;是卷积后Feature Map的高度;H1H_1是卷积前图像的宽度。式1式2本质上是一样的。

以前面的例子来说,图像宽度W1=5W_1=5,filter宽度F=3F=3Zero PaddingP=0P=0,步幅S=2S=2,则:
W2=(W1F+2P)/S+1=(53+0)/2+1=2 W_2 =(W_1-F+2P)/S+1\\ =(5-3+0)/2+1\\ =2\\
说明Feature Map宽度是2。同样,我们也可以计算出Feature Map高度也是2。

前面还曾提到,每个卷积层可以有多个filter。每个filter和原始图像进行卷积后,都可以得到一个Feature Map。因此,卷积后Feature Map的深度(个数)和卷积层的filter个数是相同的。

以上就是卷积层的计算方法。这里面体现了局部连接权值共享:每层神经元只和上一层部分神经元相连(卷积计算规则),且filter的权值对于上一层所有神经元都是一样的。对于包含两个3*3*3的filter的卷积层来说,其参数数量仅有(3*3*3+1)*2=56个,且参数数量与上一层神经元个数无关。与全连接神经网络相比,其参数数量大大减少了。

Pooling(池化、下采样)层输出值的计算

Pooling层主要的作用是下采样,通过去掉Feature Map中不重要的样本,进一步减少参数数量。Pooling的方法很多,最常用的是Max PoolingMax Pooling实际上就是在n*n的样本中取最大值,作为采样后的样本值。下图是2*2 max pooling:

除了Max Pooing之外,常用的还有Mean Pooling——取各样本的平均值。

对于深度为D的Feature Map,各层独立做Pooling,因此Pooling后的深度仍然为D。

全连接层

计算方法和DNN一样。

具体设计

  • 输入图片大小为28*28,卷积层步幅为1,采样窗口为5*5,采用SAME PADDING(卷积不改变图片大小);池化层步幅为2,采样窗口为2*2,采用最大池化;

  • 该网络一共有两个卷积层,两个池化层,两个全连接层。卷积层1深度为32,即32个卷积核从1个平面抽取特征;卷积层2深度为64,即64个卷积核从32个平面抽取特征;池化层2输出后来到全连接层1,有512个神经元,之后是全连接层2,也是输出层,有10个神经元。具体顺序如下:

    输入层
    卷积层1
    池化层1
    卷积层2
    池化层2
    全连接层
    输出层

以一张图片为例:

卷积层1
池化层1
卷积层2
池化层2
28*28
28*28*32
14*14*32
14*14*64
7*7*64
  • 使用截断正态分布初始化权值,标准差为0.1

  • 将偏置初始化为常量0.1

  • 做完卷积后采用ReLU激活函数

    ReLU函数的定义是:f(x)=max(0,x)f(x)=max(0,x)

    图像:

    Relu函数作为激活函数,有下面几大优势:

    • 速度快 和sigmoid函数需要计算指数和倒数相比,relu函数其实就是一个max(0,x),计算代价小很多。

    • 减轻梯度消失问题 回忆一下计算梯度的公式=σδx\nabla=\sigma'\delta x。其中,σ\sigma'是sigmoid函数的导数。在使用反向传播算法进行梯度计算时,每经过一层sigmoid神经元,梯度就要乘上一个σ\sigma'。从下图可以看出,σ\sigma'函数最大值是1/4。因此,乘一个σ\sigma'会导致梯度越来越小,这对于深层网络的训练是个很大的问题。而relu函数的导数是1,不会导致梯度变小。当然,激活函数仅仅是导致梯度减小的一个因素,但无论如何在这方面relu的表现强于sigmoid。使用relu激活函数可以让你训练更深的网络。

    • 稀疏性 通过对大脑的研究发现,大脑在工作的时候只有大约5%的神经元是激活的,而采用sigmoid激活函数的人工神经网络,其激活率大约是50%。有论文声称人工神经网络在15%-30%的激活率时是比较理想的。因为relu函数在输入小于0时是完全不激活的,因此可以获得一个更低的激活率。

  • 采用交叉熵(cross-entropy)损失代价函数

  • 采用Adam优化器

  • 采用dropout,随机丢弃掉一半的神经元

代码实现

import tensorflow.compat.v1 as tf
import numpy as np
import input_data
import time
import matplotlib.pyplot as plt

tf.disable_v2_behavior()
# 读入数据
mnist = input_data.read_data_sets('mnist_data', one_hot=True)

# 每个批次的大小
batch_size = 100
# 共有多少批次
n_batch = mnist.train.num_examples // batch_size


# 初始化权值
def weight_init(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)  # 生成一个截断的正态分布
    return tf.Variable(initial)


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


# 卷积层
def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')  # 2d的卷积操作
    # stride[0]和stride[3]都为1,stride[1]代表x方向的步长,stride[2]代表y方向的步长


# 池化层(最大池化)
def max_pool_2x2(x):
    # ksize=[1,x,y,1]
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')


# 定义两个Placeholder
x = tf.placeholder(tf.float32, [None, 784])  # 28*28
y = tf.placeholder(tf.float32, [None, 10])

# 改变x的格式为四维向量[batch, in_height, in_width, in_channels]
x_image = tf.reshape(x, [-1, 28, 28, 1])

# 初始化第一个卷积层的权重和偏置
W_conv1 = weight_init([5, 5, 1, 32])  # 5*5的采样窗口,32个卷积核从1个平面抽取特征
b_conv1 = biase_init([32])  # 每一个卷积核一个偏置

# 把x_image和权值向量进行卷积,再加上偏置值,然后应用于relu激活函数
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
# 第一个池化层
h_pool1 = max_pool_2x2(h_conv1)

# 初始化第二个卷积层的权重和偏置
W_conv2 = weight_init([5, 5, 32, 64])  # 5*5的采样窗口,64个卷积核从32个平面抽取特征
b_conv2 = biase_init([64])  # 每个卷积核一个偏置

# 把h_pool1和权值向量进行卷积,再加上偏置,然后应用于relu激活函数
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
# 第二个池化层
h_pool2 = max_pool_2x2(h_conv2)

# 28*28的图片第一次卷积后还是28*28,第一次池化后变为14*14
# 第二次卷积后为14*14,第二次池化后变为7*7
# 经过上面操作后得到64张7*7平面

# 初始化第一个全连接层的权值
W_fc1 = weight_init([7 * 7 * 64, 1024])  # 上一层有7*7*64个神经元,全连接层有1024个神经元
b_fc1 = biase_init([1024])  # 1024个神经元

# 把池化层2的输出扁平化为1维
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
# 第一个全连接层的输出
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

# keep_prob用来表示神经元的使用率
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

# 初始化第二个全连接层
W_fc2 = weight_init([1024, 10])
b_fc2 = biase_init([10])

# 计算输出
prediction = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

# 交叉熵代价函数
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=prediction))
# 优化器:Adam
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
# 结果存放在一个布尔列表中
correct_prediction = tf.equal(tf.argmax(prediction, 1), tf.argmax(y, 1))  # argmax返回一维张量中最大值所在的位置
# 正确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))  # 转化为32位浮点类型

# 定义会话
with tf.Session() as sess:
    start = time.clock()

    sess.run(tf.global_variables_initializer())  # 初始化变量
    for epoch in range(21):
        for batch in range(n_batch):
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)
            sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 0.5})

        acc = sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels, keep_prob: 1.0})
        print("Iter" + str(epoch) + ",Testing Accuracy is " + str(acc))
    end = time.clock()
    print("Time consuming: " + str(int((end - start) / 60)) + "min" + str(int((end - start) % 60)) + "sec.")

结果输出

本机

Google Colab

Colab有GPU加速跑的比较快,所以我多迭代了几次。

结果分析

相比DNN,CNN训练出来的结果要好一些,迭代20次左右时测试集的准确率可以达到99%以上,迭代50次可达99.3%左右,但是CNN的训练时间也更长一些。

我在colab上迭代501次,大概迭代92次时,识别准确率第一次达到99.4%。另外我发现这个CNN识别的准确率最高可达99.49%,但却不是最后几次迭代出现的,甚至后面还会时不时的降到99.4%以下,说明存在着“抖动”。

思考题

深度算法参数的设置对算法性能的影响?

在我具体实现的过程中,有如下发现:

  • batch(每一批的大小),学习率,迭代次数,dropout的keep_prob大小都会影响算法的性能;

  • batch为50时比其他条件相同时batch为100效果要稍微好一些;

  • 学习率大了收敛速度快,但是会有抖动现象,学习率小了收敛速度又很慢;

  • 未收敛之前,错误率随着迭代次数的增加而减小,但越到后面越趋于稳定,所以迭代次数并不是越多越好,还要考虑时间耗费;

  • dropout小于1时,会增加模型训练的时间,但是一定程度上可以解决过拟合的问题;

  • 选择不同的权重和偏置初始化方法也会对算法性能造成影响;

  • 选择不同的代价函数也会对算法性能造成影响,比如本次实验选择交叉熵代价函数就优于二次代价函数;

  • 选择不同的优化器同样会对算法性能造成影响,比如本次实验选择Adam优化器就略优于GradientDescent优化器。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!