NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化

1 神经网络相关的基本概念

前馈神经网络:是一种最简单的神经网络,各神经元分层排列。每个神经元只与前一层的神经元相连。接收前一层的输出,并输出给下一层,各层间没有反馈。

网络层数、输入层、隐藏层、输出层、隐藏单元:下面以图为例,介绍这些名词的含义

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第1张图片

网络层数:一般是指设置或者搭建的模型有多少层。以上图为例,网络层为3。

输入层:一般指数据输入模型的一层,如图。

输出层:一般指模型的最后一层;

隐藏层:指除开输入层和输出层之外的中间层,如图;

隐藏单元:一般指隐藏层中的单元结构。

激活函数:一般指输入到输出之间函数,通过激活函数将上一层的输出作为下一层输入之前进行非线性变化,使模型不再是单一的线性变换。

2  用tensorflow构建简单的神经网络

此处以BOR函数为例,构建一个两层的神经网络,具体见demo:

# BOR函数的tensorflow
import tensorflow as tf
import numpy as np

D_input = 2 # 隐藏层单元
D_label = 1 # 输出单元
D_hidden = 2 # 隐藏层
lr=1e-3 # 学习率 
x = tf.placeholder(tf.float32, [None, D_input], name="x") # 给X占位符
t = tf.placeholder(tf.float32, [None, D_label], name="t") # 给t占位符 
# 计算第一层
W_h1 = tf.Variable(tf.truncated_normal([D_input, D_hidden], stddev=0.1), name="W_h") # 初始化第一层参数W
b_h1 = tf.Variable(tf.constant(0.1, shape=[D_hidden]), name="b_h") # 初始化第一层偏置项b
pre_act_h1 = tf.matmul(x, W_h1) + b_h1 # 第一层仿射变化
act_h1 = tf.nn.relu(pre_act_h1, name='act_h') # 第一层激活函数
# 计算第二层    
W_o = tf.Variable(tf.truncated_normal([D_hidden, D_label], stddev=0.1), name="W_o") # 初始化第二层参数W
b_o = tf.Variable(tf.constant(0.1, shape=[D_label]), name="b_o") # 初始化第二层偏置项b
pre_act_o = tf.matmul(act_h1, W_o) + b_o # 第二层仿射变化
y = tf.nn.relu(pre_act_o, name='act_y') # 预测值
# 损失函数    
loss=tf.reduce_mean((y-t)**2) 
# 优化模型   
train_step = tf.train.AdamOptimizer(lr).minimize(loss) # 最小化损失函数时的迭代更新
# 输入值
X=[[0,0],[0,1],[1,0],[1,1]] 
Y=[[0],[1],[1],[0]]
X=np.array(X).astype('int16')
Y=np.array(Y).astype('int16')

sess = tf.InteractiveSession() # 执行创建好的网络,创建session
tf.initialize_all_variables().run() # 初始化权重
# 训练函数
for i in range(1000):
    sess.run(train_step,feed_dict={x:X,t:Y})
pred = sess.run(y,feed_dict={x:X}) # 输出预测值

3  常见的激活函数

激活函数(activation function)运行时激活神经网络中某一部分神经元,将激活信息向后传入下一层的神经网络。激活函数不会改变输入数据的维度,即输入和输出数据的维度一致。激活函数包括平滑非线性的激活函数:sigmoid、tanh、elu、softplus、softsign;也包括连续但不处处可微的函数relu、relu6、crelu、relu_x,以及随机正则化函数dropout。

为什么在深度神经网络中要添加激活函数呢?

因为如果不使用激活函数,搭建的神经网络中,每一层节点的输入都是上一层的输出的线性函数,使得隐藏层的意义消失,网络的逼近能力就相当有限。因此,引入非线性函数作为激励函数,增加深层神经网络表达能力,能更好提取出输入数据中的特征,更好拟合模型。

常见的激活函数,先放一张图:

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第2张图片

如图所示,常见的激活函数有Sigmoid函数、tanh函数、ReLU函数、Leaky ReLU函数、Maxout函数及ELU函数。下面一一介绍这些函数。

3.1 平滑非线性的激活函数

(1)Sigmoid函数

Sigmoid函数的表达式为:\sigma (x)=\frac{1}{1+e^{-x}}

从函数的表达形式上面能够看出Sigmoid函数具有指数函数的形式,经过该函数的输出范围为(0,1),可以表示成概率,或者用于数据的归一化。

从图中Sigmoid函数的图像,可以看出x\rightarrow inf,\sigma (x)\rightarrow 1,x\rightarrow -inf,\sigma (x)\rightarrow 0。这既是Sigmoid的特点也是缺点。

缺点:

       1)在深度神经网络中梯度反向传递时导致梯度爆炸和梯度消失,其中梯度爆炸发生的概率非常小,但梯度消失发生的概率很大;

       2)Sigmoid输出在(0,1)之间,即非0均值,这导致了后一层的神经元将得到上一层输出的非0均值的信号作为输入。产生的一个结果是:如x>0,f=w^{T}x+b,那么对w求局部梯度则为正,这样在反向传播的过程中w要么往正方向更新,要么向负方向更新,导致一种捆绑的效果,使收敛缓慢。

(2)  tanh函数

tanh函数表达式:f(x)=\frac{e^{x}-e^{-x}}{e^{x}+e^{-x}}

tanh函数与sigmoid函数相比,输出均值为0,这使得收敛速度较sigmoid快,从而减少迭代次数。缺点是梯度消失和幂运算的问题。

(3)ELU(Exponential Linear Units)函数

ELU函数表达式:f(x)=\left\{\begin{matrix} x &if,x>0 \\ \alpha (e^{x}-1)&otherwise \end{matrix}\right.

ELU也是为解决ReLU存在的问题而提出。缺点在于计算量稍大。类似于Leaky ReLU,理论上虽然好于ReLU,但在实际使用中目前并没有好的证据ELU总是优于ReLU。

(4)softplus函数

softplus函数表达式:f(x)=ln(1+e^{x})

值域为(0,inf)

图像如图(左),与ReLU函数对比图(右):

                               NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第3张图片                               NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第4张图片

(5)softsign函数
函数表达式:f(x)=\frac{x}{1+|x|}

3.2 连续但不处处可微的激活函数

(1) ReLU函数

ReLU函数的表达式为:Relu=max(0,x)

ReLU函数本质为一个取最大值的函数,从Relu函数的图像易看出此函数并非全局可导。

优点:1)解决了梯度消失(gradient vanishing)问题(正区间);

           2)计算速度非常快,只需要判断输入是否大于0;

           3)收敛速度远快于sigmoid和tanh;

缺点:1)ReLU的输出不是0均值(zero-centered);

           2)Dead ReLU Problem,指某些神经元可能永远不会被激活;导致相应的参数永远不会被更新。有两个原因导致这种情况的产生:(1) 非常不行的参数初始化,这种情况比较少见;(2) learning rate太高导致在训练过程中参数更新太大,不幸使网络进入这种状态。解决方法是采用Vavier初始化方法,以及避免将learning rate设置太大或使用adagrad等自动调节learning rate的算法。

(2) Leaky ReLU 函数

Leaky ReLU函数(PReLU)的表达式:f(x)=max(\alpha x,x)

为解决Dead ReLU Problem,提出了将ReLU的前半段设为\alpha x而非0,通常\alpha =0.01。另外一种直观的想法是基于参数的方法,即即ParametricReLU:f(x)=max(\alpha x,x),其中\alpha可由反向传播学习出来。

(3) MaxOut函数

MaxOut函数的表达式一般为:f(x)=max(w_{1}^{T}+b_{1},w_{2}^{T}+b_{2})

3.3 tensorflow中调用

tf.nn.relu()
tf.nn.sigmoid()
tf.nn.tanh()
tf.nn.elu()
tf.nn.bias_add()
tf.nn.crelu()
tf.nn.relu6()
tf.nn.softplus()
tf.nn.softsign()
tf.nn.dropout()

更为详细使用见:tensorflow技术解析与实战

3.4  应用中如何选择合适的激活函数

1)深度学习往往需要大量时间来处理大量数据,模型的收敛速度是尤为重要的。所以,总体上来讲,训练深度学习网络尽量使用zero-centered数据 (可以经过数据预处理实现) 和zero-centered输出。所以要尽量选择输出具有zero-centered特点的激活函数以加快模型的收敛速度。
2)如果使用 ReLU,那么一定要小心设置 learning rate,而且要注意不要让网络出现很多 “dead” 神经元,如果这个问题不好解决,那么可以试试 Leaky ReLU、PReLU 或者 Maxout.

3)分类一般使用sigmoid。

参考资料:常用激活函数(激励函数)理解与总结

                  常见激活函数的总结

                  神经网络中的激活函数tanh sigmoid RELU softplus softmatx

4  Deep Learning中防止过拟合的方法

过拟合是指训练误差很小,泛化误差很大的情况,这种情况往往是因为模型过于复杂,使其记住了“训练样本”,然而其泛化误差却很高。常见的几种防过拟合的方法有:参数范数惩罚、数据集增强、bagging和其他集成方法、early stop、Dropout层、Batch Normalization。

4.1 参数范数惩罚

范数正则化是一种非常普遍的方法,也是最常用的方法,假如优化:

                                                                   min Obj(\theta )=L(y,f(x))+\alpha G(\theta )

其中L为经验风险,其为在训练样本上的误差,而G为对参数的惩罚,也叫结构风险。\alpha是平衡两者,如果太大则对应的惩罚越大,如果太小,甚至接近于0,则没有惩罚。

最常用的范数惩罚为L1,L2正则化,L1又被称为Lasso:

                                                                                    \left \| w \right \|_{1}=\left | w_{1} \right| +\left | w_2 \right |

即绝对值相加,其趋向于是一些参数为0.可以起到特征选择的作用.
L2正则化为:

                                                                                  \left \| w \right \|_{2}=\sqrt{w_{1}^{2}+w_{2}^{2}+...}

其趋向与,使权重很小.其又成为ridge.

4.2  数据增强

让模型泛化的能力更好的最好办法就是使用更多的训练数据进行训练,但是在实践中,我们拥有的数据是有限的,可以人为添加数据,如:

      1)从类别较小的数据集中随机抽取一部分重复生成;

      2)人为的创造一些假数据添加到训练集中;

4.3 early stop

     如图片所示,当随着模型的能力提升,训练集的误差会先减小再增大,这样可以提前终止算法减缓过拟合现象.

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第5张图片

     提前终止是一种很常用的缓解过拟合的方法,如在决策树的先剪枝的算法,提前终止算法,使得树的深度降低,防止其过拟合.

4.4  bagging 和其他集成方法

     其实bagging的方法是可以起到正则化的作用,因为正则化就是要减少泛化误差,而bagging的方法可以组合多个模型起到减少泛化误差的作用.

4.5 DropOut

    DropOut实现随机扔掉一部分神经元,使模型具有更好的泛化能力,一般设置为0.5。其官方文本的意思是:

"With probability keep_prob, outputs the input element scaled up by 1 / keep_prob, otherwise outputs 0. The scaling is so that the expected sum is unchanged."

做一个测试:

import tensorflow as tf

dropout = tf.placeholder(tf.float32)
x = tf.Variable(tf.ones([10, 10]))
y = tf.nn.dropout(x, dropout)

init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(init)

print sess.run(x)
print sess.run(y, feed_dict = {dropout: 0.5})
print sess.run(y, feed_dict = {dropout: 0.8})

输出的x如图:

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第6张图片

输出的y,dropout为0.5的情况:

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第7张图片

从x和y的输出可以发现,dropout后的值等于1/keep_prob;

输出的y,dropout为0.8的情况:

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第8张图片

从dropout为0.5和0.8进行比较y值易知:dropout为0.5时扔掉的单元数多于dropout为0.8时扔掉的数目。dropout设置为0.5时,模型的网络结构最多,设置为0.8时变化很小。(参考博客:dropout比率最好的设置为0.5,因为随机生成的网络结构最多)

关于过拟合问题,此处建议使用batch norm~~~

4.6 Batch Normalization

      在Google Inception V2中所采用,是一种非常有用的正则化方法,可以让大型的卷积网络训练速度加快很多倍,同事收敛后分类的准确率也可以大幅度的提高.
     BN在训练某层时,会对每一个mini-batch数据进行标准化(normalization)处理,使输出规范到N(0,1)的正太分布,减少了Internal convariate shift(内部神经元分布的改变),传统的深度神经网络在训练是,每一层的输入的分布都在改变,因此训练困难,只能选择用一个很小的学习速率,但是每一层用了BN后,可以有效的解决这个问题,学习速率可以增大很多倍.

     优点:

     1)没有它之前,需要小心的调整学习率和权重初始化,但是有了BN可以放心的使用大学习率,但是使用了BN,就不用小心的调参了,较大的学习率极大的提高了学习速度;

     2)Batchnorm本身上也是一种正则的方式,可以代替其他正则方式如dropout等;

     3)另外,batchnorm降低了数据之间的绝对差异,有一个去相关的性质,更多的考虑相对差异性,因此在分类任务上具有更好的效果。

tensorflow中调用:tf.nn.batch_normalization、tf.layers.batch_norm、tf.contrib.layers.batch_norm(slim),这三个函数的封装是逐个递进的,建议使用后面两个

关于tf.nn.batch_normalization的调用

import tensorflow as tf

# 代传入参数的定义
prev_layer = tf.Variable(tf.ones([10, 10]))
layer = tf.layers.dense(prev_layer, 5, use_bias=False, activation=None)
gamma = tf.Variable(tf.ones([5]))
beta = tf.Variable(tf.zeros([5]))
pop_mean = tf.Variable(tf.zeros([5]), trainable=False)
pop_variance = tf.Variable(tf.ones([5]), trainable=False)
epsilon = 1e-3

y = tf.nn.batch_normalization(layer, pop_mean, pop_variance, beta, gamma, epsilon)

init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(init)

print(sess.run(prev_layer))
print(sess.run(y))

prev_layer层的结果为:

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第9张图片

y层(经过batch_norm后的层)的结果为:

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第10张图片

(此处仅为如何使用tf.nn.batch_normalization进行测试。参考:使用tf.nn.batch_normalization函数实现Batch Normalization操作)

关于tf.layers.batch_norm的调用和tf.contrib.layers.batch_norm()的调用,见博客:Tensorflow系列:Batch-Normalization层

更为详细介绍Batch Normalization:基础 | batchnorm原理及代码详解

参考博客:深度学习防止过拟合的方法

5  Deep Learning中模型优化的方法

对于深度模型,常见的优化有:参数初始化策略,自适应学习率算法(梯度下降、AdaGrad、RMSProp、Adam,优化算法的选择),batch norm层、layer norm层。

5.1 参数初始化策略

为了让神经网络在训练过程中学习到有用的信息,需要参数更新时的梯度不为0.在一般的全连接网络中,参数更新的梯度和反向传播得到的状态梯度以及输入激活有关。那么参数初始化应该满足如下两个条件:

1)各层激活函数值不会出现饱和现象(对于sigmoid,tanh);

2)各层激活值不为0;

对于参数初始化:

1)标准初始化:通过对方差乘以一个系数确保每层神经元的输出具有相同的方差,提高训练收敛速度。标准均匀初始化方法保证了激活函数的输入值的均值为0,方差为常量的1/3,和网络的层数和神经元的数量无关。对于sigmoid激活函数来说,可以确保自变量处于有梯度的范围内。但需要注意对于sigmoid函数,其输出是大于零的,这违反了上面推导中关于E(x_{i})=0的假设。综上,标准化初始方法更适用于tanh激活函数。

2)Xavier初始化(glorot初始化)

glorot认为优秀的初始化应该使得各层的激活函数值和状态梯度的方差在传播过程中的方差保持一致。glorot假设DNN使用激活值关于0对称且在0处梯度为1的激活函数。

 参考博客:深度学习之参数初始化策略

5.2 自适应学习率算法

神经网络模型中,学习率是难以设置的超参数之一,因为它对模型的性能有显著的影响。损失通常高度敏感于参数空间中的某些方向,而不敏感于其他。动量算法可以在一定程度缓解这些问题,但这样做的代价是引入另一个超参数。于是基于一个简单的想法,如果损失对于某个给定模型参数偏导保持相同的符号,那么学习率应该增加。如果对于该参数的偏导变化了符号,那么学习率应减小。这种方法只能应用于全批量优化中。最近,提出了一些增量(或者基于小批量)的算法来自适应模型参数的学习率。

1)AdaGrad

AdaGrad 算法,如下图所示,独立地适应所有模型参数的学习率,缩放每个参数反比于其所有梯度历史平方值总和的平方根 (Duchi et al., 2011)。具有损失最大偏导的参数相应地有一个快速下降的学习率,而具有小偏导的参数在学习率上有相对较小的下降。净效果是在参数空间中更为平缓的倾斜方向会取得更大的进步。在凸优化背景中, AdaGrad 算法具有一些令人满意的理论性质。然而,经验上已经发现,对于训练深度神经网络模型而言, 从训练开始时积累梯度平方会导致有效学习率过早和过量的减小。 AdaGrad 在某些深度学习模型上效果不错,但不是全部。

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第11张图片

2) RMSProp

RMSProp算法修改AdaGrad以在非凸设定下效果更好,改变梯度积累为指数加权的移动平均。AdaGrad旨在应用于凸问题时快速收敛。当应用于非凸函数训练神经网络时,学习轨迹可能穿过了很多不同的结构,最终到达一个局部是凸形的区域。AdaGrad根据平方梯度的整个历史收缩学习率,可能使得学习率在达到这样的凸结构前变得太小了。RMSProp 使用指数衰减平均以丢弃遥远过去的历史,使其能够在找到凸碗状结构后快速收敛,它就像一个初始化于该碗状结构的 AdaGrad 算法实例。RMSProp 的标准形式如算法 8.5 所示,结合 Nesterov 动量的形式如算法 8.6 所示。相比于 AdaGrad,使用移动平均引入了一个新的超参数ρ,用来控制移动平均的长度范围。经验上, RMSProp 已被证明是一种有效且实用的深度神经网络优化算法。目前它是深度学习从业者经常采用的优化方法之一。

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第12张图片

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第13张图片

3)Adam

Adam是另一种学习率自适应的优化算法,如算法8.7所示。早期,Adam最好被看作结合RMSProp和具有一些重要区别的动量的变种。首先,ai在Adam中,动量直接并入了梯度一阶矩(指数加权)的估计。将动量加入RMSProp最直观的方法是将动量应用于缩放后的梯度。结合缩放的动量使用没有明确的理论动机。其次,Adam包括偏执修正,修正从远点初始化的一阶矩(动量项)和(非中心的)二阶矩的估计。RMSProp二阶矩估计可能在训练初期有很高的偏置Adam通常被认为对超参数的选择相当鲁棒,尽管学习率有时需要从建议的默认修改。

NLP_task5: 神经网络基本概念、激活函数、防止过拟合的方法、参数优化_第14张图片

自适应学习率来自博客:深度学习之自适应学习率算法

5.3 优化算法的选择

     1)数据量小,选择自适应学习速率的方法。

     2)RMSProp,Adadelta和Adam非常相似,在相同情况下表现很好;

     3)偏置校验让Adam的效果稍微比RMSProp好;

     4)进行过很好的参数调优的SGD+Momentum算法效果好于Adagrad/Adadelta;

参考博客:神经网络的优化算法选择

参考博客:深度学习—常见问题总结(调优方法)

5.4 batch norm层

         机器学习领域有个很重要的假设:IID独立同分布假设(统计学很多模型、算法都是严格要求基于独立同分布的),就是假设训练数据和测试数据是满足相同分布的,这是通过训练数据获得的模型能够在测试集获得好的效果的一个基本保障。BatchNorm就是基于这种思想,用于解决深度神经网络训练过程中使得每一层神经网络的输入保持相同分布。

        思想:让每个隐层节点的激活输入分布固定下来。因为深层神经网络在做非线性变换前的激活输入值,会随着网络深度加深或者在训练过程中,其分布逐渐发生偏移或变动(想想一些激活函数的非0均值情况),之所以训练收敛慢,一般是整体分布逐渐往非线性函数的取值区间的上下限两端靠近,所以这导致反向传播时低层神经网络的梯度消失,这即是训练深层神经网络收敛越来越慢的本质原因。BN就是通过一定的规范化手段,把每层神经网络任一神经元这个输入值的分布强行拉回到均值为0方差为1的标准正太分布。为保证激活函数对网络的表达能力,对于经过BN变换后的神经元,又进行了scale加上shift操作(y=scale*x+shift),找到一个线性和非线性的较号平衡点,既能享受非线性的较强表达能力的好处,又避免太靠非线性区两头使得网络收敛速度太慢。

       优点:1)极大提升了训练速度,收敛过程大大加快;2)能增加分类效果,一种解释使这是类似于Dropout的一种防止过拟合的正则化表达方式;3)调参过程简化,对初始化要求不高,可以使用大的学习率。

参考博客:【深度学习】深入理解Batch Normalization批标准化

BatchNorm原文:《Batch Normalization Accelerating Deep Network Training by Reducing Internal Covariate Shift》

博客“caffe中常用层: BatchNorm层详解”有关于BatchNorm的代码。

5.5 layer norm层

      Batch Normu虽在在DNN上取得了很不错的效果,但却难以应用于RNN.为解决这一难题,提出了Layer norm层。

     注意到一层的输出的改变会产生下一层输入的高度相关性改变,于是通过固定一层神经元的输入均值和方差来降低covariate shift的影响。

博客:BatchNormalization、LayerNormalization、InstanceNorm、GroupNorm、SwitchableNorm总结 对其进行了很好的总结,笔者在这里还需要阅读原文仔细阅读后,才能明白其中含义,故仅将其他博客放至此处,以便来者直接访问。

你可能感兴趣的:(nlp学习,神经网络,防止过拟合)