tensorflow实现多层感知机

在前面的博客中我们已经讨论过softmax实现分类的例子,该模型最大的特点是简单易用,但是拟合能力不强。它和传统意义上的神经网络的最大区别是没有隐含层。

对于神经网络来说,引入非线性隐含层后,理论上只要隐含节点足够多,即使只有一个隐含层的神经网络也可以拟合任意函数。同时隐含层越多,越容易拟合复杂函数。理论表明,为了拟合复杂函数需要的隐含节点的数目,基本上随着隐含层的数量增加呈现指数下降的趋势。这也是深度学习的特点之一,层数越深,概念越抽象,需要的隐含节点就越少。

对于神经来说,一般有两种实现方式:
1)设计隐含层少,但节点数多的神经网络(宽而浅的神经网络);
2)设计隐含层多,但每层节点数目少的神经网络(窄而深的神经网络)。
相对来说,第二种的设计方法使用居多。但是使用层数较深的神经网络往往会遇到很多问题,比如过拟合参数难以调试梯度弥散等等。下面我们简单介绍一下这三个问题,以及解决的方法。

过拟合,是指该模型在训练集上的准确率升高,但是在测试集上的准确率反而下降,这意味着泛化能力不好。具体的过拟合现象可以参考前面讲述的博客
过拟合的解决思路:为了解决这个问题,Hinton提出了一个简单但是非常有效的方法——dropout。它的大致思路为:在训练时,将神经网络某一层的输出节点数据随机丢弃一部分。可以理解为我们随机把一张图片50%的点删掉(即随机将50%的点变成黑的的点),此时人可以识别出这张图像的类别,同理机器也可以识别出。该方法的实质是等于创造出了很多随机样本,通过增加样本量、减少特征数量来防止过拟合。 Dropout也可以看做bagging方法,可以理解为每次丢弃的节点数据是对特征的一种采样。也就是相当于我们训练了一个ensemble的神经网络的模型,对每个特征都做特征采样,只不过没有训练多个神经网络模型,只有一个融合的神经网络。

参数难以调试,尤其对于随机梯度下降(SGD)的参数,对SGD设置不同的学习速率,最后得到的结果可能差异巨大。神经网络通常不是一个凸优化的问题,它处处充满的局部最优。同时SGD本身也不是一个稳定的算法,结果可能会在最优解附近波动,而不同的学习速率可能导致神经网络落入截然不同的局部最优之中。但是对于神经网络来说,可能有很多个局部最优解都可以达到比较好的分类效果,而全局最优反而容易是过拟合的解。
对于学习率设置的解决措施:对于SGD,一开始我们可能希望学习率大一些,可以加速收敛,但是训练后期又希望学习速率可以小一些,这样可以比较稳定的落入一个局部最优解。
解决方法: 不同的机器学习问题所需要的学习速率也不太好设置,需要反复的调试,因此就有像Adagrad、Adam、Adadelta等(这些方法在tensorflow中有对应的函数)自适应的方法可以减轻调试参数的负担。对于这些优化算法我们使用它默认的参数设置就可以取得一个比较好的效果。

梯度弥散(Gradient Vanishment),梯度弥散的问题很大程度上是来源于激活函数的“饱和”。因为在后向传播的过程中仍然需要计算激活函数的导数,所以一旦输出落入函数的饱和区,它的梯度将变得非常小。 使用反向传播算法传播梯度的时候,随着传播深度的增加,梯度的幅度会急剧减小,会导致浅层神经元的权重更新非常缓慢,不能有效学习。这样一来,深层模型也就变成了前几层相对固定,只能改变最后几层的浅层模型。
sigmoid函数:在ReLU激活函数出现之前,神经网络训练全部使用sigmoid作为激活函数。非线性的sigmoid函数在信号的特征空间映射上,对中央区的信号增益较大,对两侧区的信号增益小。在训练神经网络时,可以将重要特征置于中央区,将非重要特征置于两侧区。关于sigmoid的详细介绍可以参照原来的博客
sigmoid函数的问题:但是当神经网络层数较多时,sigmoid函数在反向传播中梯度值会逐渐减小,经过多层的传递之后会呈指数级急剧减小,因此梯度值在传递到前面几层时就变得非常小,这时候根据训练数据的反馈来更新参数将会变得非常缓慢,基本上起不了多大的作用。
解决方法: ReLU的出现解决了梯度弥散的问题。ReLU是一个简单的非线性函数 y = m a x ( 0 , x ) . ReLU可以很好的传递梯度,经过多层的反向传播,梯度依旧不会大幅减小,所以非常适合训练很深的神经网络。ReLU从正面解决了梯度弥散的问题,而不需要通过无监督的逐层训练初始化权重来绕行。

ReLU与Sigmoid函数相比,主要包括三个变化:
1)单侧抑制;
2)相对宽阔的兴奋边界;
3)稀疏激活性。
值得注意的是,对于神经网络来说,我们习惯于将隐含层的激活函数从sigmoid函数变为ReLU来提高训练速度以及模型的准确率。在网络的输出层一般还是使用sigmoid函数,因为它最接近于概率输出的分布。

那么多层感知机中的隐含层有什么作用呢?
隐含层一个代表性的功能是解决XOR问题。没有隐含层的神经网络是线性的,只能解决线性可分的问题,当引入隐含层并使用非线性激活函数时,神经网络可以解决非线性问题。同时神经网络的隐含层越多,就可以对原有的特征进行的越抽象,模型的拟合能力就越强。

下面,我们使用tensorflow实现了在使用一个隐含层情况下,使用神经网络对MNIST数据集实现分类。同时针对神经网络上面常见的问题,我们采用了Dropout、Adagrad以及ReLU等方法。

# -*- coding: utf-8 -*-
"""
Created on Tue Mar 20 11:20:23 2018

@author: wangyule
"""

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# 读取数据
mnist = input_data.read_data_sets('MNIST_data/', one_hot = True)

in_units = 784  #输入层节点数量
h1_units = 300  #隐含层节点数量

x = tf.placeholder(tf.float32, [None, in_units])    #定义输入x的placeholder
keep_prob = tf.placeholder(tf.float32)  #定义dropout的比率

#定义权重以及偏置
W1 = tf.Variable(tf.truncated_normal([in_units, h1_units], stddev=0.1))
b1 = tf.Variable(tf.zeros([h1_units]))
W2 = tf.Variable(tf.zeros([h1_units, 10]))
b2 = tf.Variable(tf.zeros([10]))

#定义网络结构
hidden1 = tf.nn.relu(tf.add(tf.matmul(x, W1), b1))  #激活函数使用ReLU
hidden1_drop = tf.nn.dropout(hidden1, keep_prob)    #在隐含层使用Dropout
y = tf.nn.softmax(tf.add(tf.matmul(hidden1, W2), b2))   #使用softmax分类

#定义损失函数以及确定使用损失函数最小化的优化算法Adagrad
y_ = tf.placeholder(tf.float32, [None, 10])
cross_enerty = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), \
                              reduction_indices=[1]))
train_step = tf.train.AdagradOptimizer(0.3).minimize(cross_enerty)

#定义会话以及初始化全部变量
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()

#共使用3000个batch,每个batch100个样本进行训练模型
for i in range(3000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    train_step.run({x: batch_xs, y_: batch_ys, keep_prob: 0.75})

#计算模型的精度   
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

#对测试集进行精度计算
print(accuracy.eval({x: mnist.test.images, y_: mnist.test.labels, \
                     keep_prob: 1.0}))

1. W1 = tf.Variable(tf.truncated_normal([in_units, h1_units], stddev=0.1))
上述权重初始化的方式为正太分布的一种,称为截断的正太分布。因为模型使用的激活函数是ReLU,所以需要使用正太分布给参数加一点噪声,来打破完全对称并且避免0梯度(也就是说一般使用ReLU激活函数时,一般使用正太分布的权重初始化,而对于sigmoid函数,在0附近最敏感、梯度最大,可以将参数的初始化置为0)。常见的随机初始化的方法有:
这里写图片描述

2. hidden1_drop = tf.nn.dropout(hidden1, keep_prob)
在隐含层使用Dropout,随机将一部分节点置为0,keep_prob参数为不置为0的节点比例。在训练时应该小于1,用以制造随机性,防止过拟合;在测试时应该等于1,即使用全部的特征来预测样本的类别。

与Softmax Regression相比,加入隐含层的MLP性能提高不少。因为没有隐含层的oftmax Regression只能直接从图像的像素点推断是哪个数字,没有特征抽象的过程。而MLP依靠隐含层,则可以组合出高阶特征,之后将这些高阶特征或者说组件在组合成数字,就实现了精准的匹配和分类。

但是使用全连接的神经网络(Fully Connected Network, FCN, MLP的另一种说法)也是有局限性的,即使我们使用很深的网络、很多的隐含层节点、很大的迭代次数,也很难在MNIST数据集上精度达到99%以上,所以下篇博客将学习一下卷积神经网络CNN,它可以达到99%以上的精度。

猜你喜欢

转载自blog.csdn.net/wyl1813240346/article/details/79853511