Deep Learning(深度学习)之(十)神经网络在机器学习分类任务中的应用

来源:互联网 发布:淘宝在哪里查消费总额 编辑:程序博客网 时间:2024/04/30 15:17

关键词:神经网络,正向计算,反向传播,神经元,最大化间隔损失,梯度检验, 参数的哈维初始化, 学习速率, ADAGRAD(自适应梯度法)

这是斯坦福CS224d深度学习与自然语言处理的第3课,这节课先会介绍单层和多层神经网络和它们在机器学习分类任务中的应用, 接着介绍如何利用反向传播算法来训练这些神经网络模型(在这个方法中,我们将利用偏导数的链式法则来层层更新神经元参数)。在给出神经网络以及这些算法严谨的数学定义后,介绍了训练神经网络的一些实用的技巧和窍门,比如,神经元(非线性激励),梯度检验,参数的Xavier初始化方法,学习速率,ADAGRAD(自适应梯度法)等。最后,我们在神经网络模型的基础上来建立语言模型

1 神经网络:基础

我们在前面的课程中提到,由于大部分数据并非线性可分,线性分类模型在这些数据上的分类效果略显尴尬,而非线性的分类模型通常能取得更好的效果。 如下图1所示,神经网络模型就是这样一类具备非线性决策边界的分类器。 从图上我们可以看到神经网络生成了非线性判定边界,来对2类样本做分类,那咱们一起来看看,它是怎么做到的呢。


 
图 1 ︰ 在图上可以看到神经网络产生的非线性判定边界,更好地把2类样本点分隔开了。这就是神经网络的强大之处(不纠结原始样本的分布状况)。 

一点小历史 ︰ 神经网络是受生物学启发的分类器,因此它也常被称为人工神经网络(ANN),以区别于生物学上的神经网络。事实上,人类的神经网络复杂性高很多,也比ANN具有更强大的能力,所以即使名字很接近,两者之间倒没有那么多的相似之处。

1.1 神经元

简单说来,神经元其实就是一个取n个输入,并产生单一输出的通用计算单元。每层神经元通过不同的参数(也称权重)产生不同的输出结果(通常情况下同一层神经元的输入值是相同的,只是权重不同)。比较常见的神经元(激励函数)是”Sigmoid函数”,也叫作”二项逻辑回归”单元。这种神经元,对于输入的 n维向量,通过与n维的权重向量 w和一个偏差标量b做组合运算,输出一个标量a(咱们也把它叫做标量激活结果),具体的运算过程如下公式所示: 


a=11+exp((wTx+b))

其中w是权重,b是偏移量,x是输入 

为了运算的一致性和精简性,我们也可以把权重向量和偏差变量写到一个n+1维的向量里,得到上式的一个等价形式: 


a=11+exp([wT b][x 1])

你们看出来了,就是把偏移量放作权重的一部分 

下图2是这种神经元的一个直观一点的解释: 


 
图 2 ︰ 这就是所谓的”二元逻辑回归”神经元,输入向量 x 的各个元素被 w 中对应的权重缩放后求和,加上偏移量b(数学上可以看做对对输入x做线性的变换)之后放进”Sigmond函数”。 

一点小总结:神经元呢,可以看做神经网络的基本组成功能单元,有多种多样功能(就是对输入做不同非线性变换)的神经元,它们共同去帮助整个神经网络形成非线性切分的能力。

1.2 单层神经元

刚才看完1个神经元的情况了,也知道它在做的非线性变换(输入到输出的运算)是什么,现在咱们拓展一下,看看对于一组输入x,一层神经元(其实就是多个神经元)的变换和处理情况,基本的结构,就如下图3所示。


 
图 3 ︰ 传说中神经网络的一层(多个”二元逻辑回归”神经元),它们的输入是一致的,都是x。 

我们分别用{w(1),,w(m)}{b1,bm}{a1,am} 来表示m个神经元的权重向量,偏移量以及激励输出,则有一下的结果:

a1=11+exp(w(1)Tx+b1))


am=11+exp(w(m)Tx+bm))

式子多了看着有点乱,咱们设定一下以下的数学标记,简化简化在神经网络中的公式:

σ(z)=11+exp(z1)11+exp(zm)

b=b1bmRm

W=w(1)Tw(m)TRm×n

其中, 

z=Wx+b

这样咱们的二元逻辑回归的激励输出就可以写成: 

a1am=σ(z)=σ(Wx+b)

那这些激励输出到底是干嘛的呢,有什么物理含义? 一种理解方式是, 每个神经元都是对输入向量一个不同角度的处理加工, 提取输入向量的某一部分信息(比如图像数据中的纹理、颜色、轮廓,或者文本信息中的词性、时态等等)。然后这些信息会被用到分类任务中去,为决策提供依据。

1.3 前向计算

上一节咱们讨论了如何将一个向量xRn输给神经网络中的一层(一组(m个)二元回归神经元)进而得到他们的激励输出 aRm。 也简单提了一下这么做的意义,为了加深理解,咱们还是用命名实体识别(NER)的例子来直观解释一下这个过程吧。看这样一个例子:

“Museums in Paris are amazing”

我们要来判断这里的中心词”Paris”是不是个命名实体。在这种情况下, 我们不止要知道这个词窗内哪些词向量出现过,可能也需要知道他们之间的相互作用。 比如说,可能只有在”Museums”出现在第1个位置,”in”出现在第二个位置的时候,Paris才是命名实体。如果你直接把词向量丢给Softmax函数, 这种非线性的决策是很难做到的。所以我们需要用1.2中讨论的方法对输入的变量进行非线性的处理加工(神经元产出非线性激励输出),再把这些中间层的产物输入到Softmax函数中去。 这样我们可以用另一个矩阵URm+1,与激励输出结果运算生成得分(当然,这里是未归一化的),从而进一步用于分类任务:

s=UTa=UTf(Wx+b)

公式中的f是前面提到的做非线性变化的激励函数(激活函数)。

维度分析: 如果我们用4维词向量表示这些词,且用一个5词窗口作为输入(就像上面这个例子),那输入的变量就是xR20. 如果在隐藏层中使用8个sigmoid神经元,并且由其激励输出生成1个得分,我们就有WR20bR8UR8×1sR

整个运算的过程(逐级的)大概是如下这个样子: 
z=Wx+b 
a=σ(z) 
s=UTa


 
图4: 这张图描述了一个前向计算网络如何运算得到输出值

 1.4 最大化间隔目标函数

跟大多数机器学习模型一样,神经网络也需要一个优化目标,一个用来衡量模型好坏的度量。优化算法在做的事情呢,通常说来就是找到一组权重,来最优化目标或者最小化误差。这里我们讨论一个比较流行的度量,叫做最大化间隔目标函数。直观的理解就是我们要保证被正确分类的样本分数要高于错误分类的样本得分。

继续用之前的例子,如果我们把一个正确标记的词窗 “Museums in Paris are amazing”(这里Paris是命名实体)的得分记做 s, 而错误标记的词窗“Not all Museums in Paris”(这里Paris不是命名实体)的得分记作sc (c表示这个词窗”corrupt”了)

于是,我们的目标函数就是要最大化 (ssc) 或者最小化scs。 但是,我们要对这个目标函数稍作修改,让他只有在 sc>s=>scs>0 的时候才计算这个函数的值。因为当正确标记的词窗得分比错误标记的词窗得分高的时候,我们认为是满足要求的,并没有误差,我们只关心错误标记的词窗比正确标记的词窗得分高了多少,它代表了误差的程度。 于是,我们的目标函数在sc>s的时候取值scs,其余时候取值为0现在,优化目标变成: 

minimizeJ=max(scs,0)

但是这个优化函数还不稳妥,因为它缺乏一个用来保证安全划分的间隔。我们希望那些被正确标记的词窗得分不仅要比错误标记的词窗得分高,还希望至少高出一个取值为正的间隔Δ。 换句话说, 我们希望在 
ssc<Δ的时候就开始计算误差值,而不是等到ssc<0

因此,我们修改优化目标为 ︰ 

minimizeJ=max(Δ+scs,0)

我们可以把这个Δ的取值定为1,在学习的过程中,模型其他的权重参数自动会进行相应的缩放,而并不会影响最终分类模型的精度。如果你想了解更多细节的话,可以去读一下支持向量机中关于函数间隔和几何间隔(functional and geometric margins)的内容。所以最后我们定义了以下形式的目标函数,作为在训练集中所有词窗上求最优化的目标:

minimizeJ=max(1+scs,0)

1.5 反向传播训练法(未向量化的逐元素形式)

在这一节中我们来讨论一下,当1.4节中的目标函数J取值为正的时候, 怎么来训练模型中的各个参数。 如果这个目标函数的取值是0, 那我们已经不再需要更新参数的取值了。 一般来讲,我们通过可以通过梯度下降法来更新参数(或者一些变种,像随机梯度下降/SGD)。这样就需要每个参数的梯度的信息来实现下面的更新过程: 

θ(t+1)=θ(t)αΔθ(t)J

反向传播这种方法利用导数的链式法则来计算损失函数(正向计算求得)在每个模型参数上的梯度。为了进一步理解上述内容, 我们来看下图5所示的这样一个简单的网络: 


 
图5:这是一个 4-2-1的神经网络,第k层神经网络上的第j个神经元上的输入值是z(k)j,输出的激励输出值为a(k)j 

这里我们讨论的是一个只有1个隐藏层,1个单独的输出单元的神经网络。 我们先来统一以下标记:

  • xi 是神经网络的输入。
  • s是神经网络的输出。
  • 神经网络的每一层(包括输入层和输出层)都有神经元来进行输入和输出。 第k层神经网络上的第j个神经元上的输入值是z(k)j,输出的激励输出值为a(k)j
  • 我们把反向传播到z(k)j上的误差记为δ(k)j
  • 第1层指的是输入层而不是第一个隐藏层。对于输入层,我们有xj=z(1)j=a(1)j
  • W(k) 是把k层的激活子输出值映射到k+1层输入值的转换矩阵。于是,把这个一般化的标记用在1.3节例子中就有了 W(1)=W 以及 W(1)=U 。

一起来看看反向传播吧: 假设目标函数 J=(1+scs) 取正值,我们希望更新权重参数W(1)14(如图5及图6所示),我们注意到这里W(1)14只在计算z(2)1 和 a(2)1时出现。这一点对于理解反向传播很重要-参数的反向传播梯度只被那些在正向计算中用到过这个参数的值所影响。 a(2)1在之后的正向计算中和 W(2)1相乘进而参与到分类得分的计算中。我们从最大化边界损失的形式看到: 

Js=Jsc=1

于是,为了简化计算, 我们这里可以只考虑JW(1)ij。于是 
Js=Jsc=1 
这样, 我们可以对sW(1)ij化简如下: 
sW(1)ij=W(2)a(2)W(1)ij=W(2)ia(2)iW(1)ij=W(2)ia(2)iW(1)ij 
W(2)ia(2)iW(1)ij=W(2)ia(2)iz(2)iz(2)iW(1)ij 
=W(2)if(z(2)i)z(2)iz(2)iW(1)ij 
=W(2)if(z(2)i)z(2)iW(1)ij 
=W(2)if(z(2)i)W(1)ij(b(1)i+a(1)1W(1)i1+a(1)2W(1)i2+a(1)3W(1)i3+a(1)4W(1)i4) 
=W(2)if(z(2)i)W(1)ij(b(1)i+ka(1)kW(1)ik) 
=W(2)if(z(2)i)a(1)j 
=δ(2)ia(1)j

我们可以看到这个梯度最终可以简化为δ(2)ia(1)j 这样一个形式。 这里δ(2)i 就是反向逆推到第2层上第i个神经元的误差。 a(1)j则与Wij相乘后输入到第2层上第i个神经元的计算中。

译者注:这里所谓的反向传播误差δ(k)i其实就是最终的目标函数对于第k层上第i个激励输出值z(k)i的导数。当我们要求目标函数关于Wk1ij的导数时, 因为第k层上只有z(k)i的计算涉及到Wk1ij, 所以可以把z(k)i写成关于Wk1ij的函数,接着利用导数的链式法则,得到目标函数关于Wk1ij的导数。误差δ(k)k层传播到k1层的过程就等价于求目标函数高1阶的导数,这一步同样可以由偏导数的链式法则得到。


 
图6:这个子网络描述的是当我们更新 W(1)ij时所用到的神经网络的有关部分 

我们以图6为例子,从”误差分配/分散”的角度来诠释一下反向传播。比如说我们如果要更新 W(1)14

  1. 我们从a(3)1上的误差信息1开始进行逆向传播。
  2. 我们把这个误差乘以把z(3)1映射到a(3)1的神经元的局部梯度上。在这里这个梯度恰好也等于1 (有兴趣可以自己去算一下)。所以我们得到δ(3)1=1
  3. 于是,这个误差信息1已经传到了z(3)1上。 我们需要把这个误差分配到上一层a(2)1上去。(这里我们不关心2层上的其他激励输出,因为他们的计算不涉及到W(1)14)
  4. 分配到a(2)1上的误差为(z(3)1 上的误差 =δ(3)1)×W(2)1。 于是传播到a(2)1上的误差为W(2)1这里记作W(2)11比较合适
  5. 像第2步一样,我们通过把a(2)1上的误差乘以把z(2)1映射到a(2)i的神经元的局部梯度上,来把误差传播到z(2)1上。这里这个梯度是f(z(2)1)
  6. 于是z(2)1上的误差就等于f(z(2)1)W(2)1。记作δ(2)1
  7. 最后,我们要把这个误差乘以参与前向计算的a(4)1 从而把误差信息分配到 W(1)14上。
  8. 于是,目标函数关于Wk1ij的导数就有了a(4)1f(z(2)1)W(2)1这样的形式。

我们可以看到,我们从哪个角度出发,最后得到的结果都是一样的。所以对于反向传播我们既可以从链式法则的角度来理解,也可以从误差分配/分散的角度来理解。

偏移量的更新 偏移量(如b(1)1)在计算下一层神经元输入值z(2)1时,与其他权重参数在数学形式上是等价的,只不过更他相乘的是常量1。所以,对于第k层上第i个神经元偏移量的梯度就是δ(k)i。比方说,如果我们在上面的例子中,要更新的是b(1)1而不是W(1)14, 那它的梯度就是f(z(2)1)W(2)1

从 δ(k)δ(k1)反向传播的一般化步骤:

  1. 我们从z(k)i上的误差信息δ(k)i(即第k层的第i个神经元)开始,见图7。
  2. 我们通过把δ(k)i乘以路径上的权重W(k1)ij来把这个误差反向传播到上一层的激励输出a(k1)j上。
  3. 于是,激励输出a(k1)j收到了误差信息δ(k)iW(k1)ij
  4. 不过a(k1)j在前向计算中,参与到下一层中多个神经元的计算中。比如k层上第m个神经元上的误差信息也将通过类似第3步中的方式传递到a(k1)j上。
  5. 这样,a(k1)j收到的误差信息就会变成δ(k)iW(k1)ij+δ(k)mW(k1)mj
  6. 因为第k层上有很多个神经元,a(k1)j收到的误差信息可以写成一般化的形式iδ(k)iW(k1)ij
  7. 这样 a(k1)j就收到关于它的所有误差信息了,于是我们通过乘以这个神经元上的局部梯度f(z(k1)j)来把误差信息传播 到 z(k1)j上。
  8. 最终,到达 z(k1)j上的误差信息就是 z(k1)jiδ(k)iW(k1)ij,记作δ(k1)j


 
图7:从 δ(k)δ(k1)反向传播 

1.6 反向传播训练(向量化的形式)

我们前面介绍了如何计算模型中每个参数的梯度。这里我们要讨论如何把这些计算向量化及矩阵化(高效很多)。 
对于权重参数 W(k)ij,我们知道它的误差梯度为δ(k+1)ia(k)j,这里W(k)即为把a(k)映射到z(k+1)上的矩阵。 于是我们可以把误差信息对于整个矩阵W(k)的梯度表示成以下形式: 

ΔW(k)=δ(k+1)1a(k)1δ(k+1)2a(k)1δ(k+1)1a(k)2δ(k+1)2a(k)2=δ(k+1)a(k)T

于是,我们可以把这个矩阵形式的梯度写成(从下一层)反向传播过来的误差和(从这一层)参与到前向计算中的激励输出的外积。


 
图 8︰从 δ(k)δ(k1)的误差传播 

咱们接着看如何向量化的计算 δ(k)。参考上面的图8,δ(k)j=f(z(k)j)iδ(k+1)iW(k)ij。 
这可以很容易推广到矩阵形式 
δ(k)=f(z(k))(W(k)Tδ(k+1)) 
在上式中 表示元素对应位相乘(即Hadamard积 :RN×RNRN )

计算效率: 我们知道,在很多科学计算软件中,像Matlab,Python(用NumPy/SciPy 包),向量化计算的效率远高于对每个元素逐个进行计算。所以,才实际操作中,我们尽可能的采用向量化的方式来训练参数。同时,我们在反向传播中应该尽量避免不必要的重复计算。比如说 δ(k)的计算直接和 δ(k+1)相关。 于是我们要保证在我们用 δ(k+1)更新 W(k)的时候, 我们存下δ(k+1)的值用来下一步计算 δ(k)。以此类推,我们在(k1),,(1) 上 我们重复这样的步骤,这种递归过程将使整个反向传播更加有效。

2 神经网络:技巧和窍门

前面的部分讨论了神经网络的技术原理,理论和实践结合起来才能发挥大作用,现在咱们介绍一些神经网络在实际应用中常见的技巧和窍门。

2.1 梯度检验

我们已经介绍了如何用微积分计算神经网络模型中参数的误差梯度。现在我们介绍另一种不使用误差反向传播,而近似估计梯度的方法:

f(θ)J(θ(i+))J(θ(i))2ϵ

其中,θ(i+)=θ+ϵ×ei

从微分的定义来看,上述公式显然是正确的,但是怎么将其应用到求解误差梯度呢?对于一个给定的数据集,当我们正向扰动参数θ的第i个元素时(可以简单理解成θ加上一个极小的正数),咱们基于前向传导可以计算出误差项J(θ(i+))。同理,当我们负向扰动参数θ的第i个元素时,咱们基于前向传导可以计算出新的误差项J(θ(i))。因此,其实通过做两次前向运算,我们就可以根据上面的公式估计出任何给定参数的梯度。当然了,其实只做一次前向传导所需要的运算量也不小了,所以在估计梯度时,这种方法比较耗时,但是,在用于验证反向传播的实现时,这种方法很赞,也用得很多。

梯度检验的简单实现可以参照下述方式:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">eval_numerical_gradient</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(f, x)</span>:</span>  <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"""  a naive implementation of numerical gradient of f at x  - f should be a function that takes a single argument  - x is the point (numpy array) to evaluate the gradient  at  """</span>  fx = f(x) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># evaluate function value at original point</span>  grad = np.zeros(x.shape)  h = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.00001</span>  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># iterate over all indexes in x</span>  it = np.nditer(x, flags=[’multi_index’],                   op_flags=[’readwrite’])  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">while</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> it.finished:    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># evaluate function at x+h</span>    ix = it.multi_index    old_value = x[ix]    x[ix] = old_value + h <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># increment by h</span>    fxh = f(x) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># evaluate f(x + h)</span>    x[ix] = old_value <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># restore to previous value (very important!)</span>  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># compute the partial derivative</span>  grad[ix] = (fxh - fx) / h <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># the slope</span>  it.iternext() <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># step to next dimension</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> grad</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li></ul>

梯度检验:其实一般情况下,解析梯度是一个更快的梯度求解方法,不过容易出错,而梯度检验是个很好的比较解析梯度和数值型梯度的方法。数值型梯度可以用下述公式去计算:

f(θ)J(θ(i+))J(θ(i))2ϵ

其中,J(θ(i+))J(θ(i))可以通过正向和负向微调θ后两次前向传导来计算得到,这种方法的代码实现可以参阅Snippet 2.1。

2.2 正则化

像大多数分类器一样,神经网络也容易产生过拟合,这会导致其在验证集和测试集上的结果并不一定那么理想。为了解决这个问题,简单一点咱们可以应用L2正则化,加上正则化项的损失函数JR可以通过下述公式来计算:

JR=J+λi=1LW(i)F

在上述公式中,W(i)F是矩阵W(i)的F范数(frobenius norm),λ是用于在加权和目标函数中进行正则化的相对权重。加上这个正则化项,意在通过作用到损失的平方来惩罚那些在数值上特别大的权重(译者注:也就是让权重的分配更均匀一些)。这样一来,目标函数(也就是分类器)的随意度(译者注:也就是可用于拟合的复杂度)就被降低了,约束了拟合函数的假设空间,因此减少了发生过拟合的可能性。施加这样一种约束条件可以用先验贝叶斯思想来理解,即最优的权重分配是所有权重都接近0。你想知道有多接近?对啦,这正是λ所控制的——大的λ会倾向于使所有权重都趋于0。值得注意的是,偏移量b不会被正则化,也不会被计算入上述的损失项(试着想想为什么?)。

2.3 神经单元

前面的内容里,我们已经讨论过了包含sigmoid神经元(sigmoidal neurons)来实现非线性分类的神经网络算法,然而在许多应用中,使用其他激励(激活)函数(activation functions)可以设计出更好的神经网络。这里列举了一些常用选择的函数表达式和梯度定义,它们是可以和上文讨论过的sigmoid函数(sigmoidal functions)互相替代的。

Sigmoid:这是通常拿来做例子的函数,我们已经讨论过它,其激励(激活)函数σ为:

σ(z)=11+exp(z)

其中, σ(z)(0,1)

σ(z)的梯度为: 

σ(z)=exp(z)1+exp(z)=σ(z)(1σ(z))
 
图9:Sigmoid非线性的响应 

Tanh:tanh函数是除了sigmoid函数之外的另一种选择,在实际中,它的收敛速度更快。tanh函数与sigmoid函数最主要的不同是tanh函数的输出结果在-1和1之间,而sigmoid函数的输出结果在0和1之间。

tanh(z)=exp(z)exp(z)exp(z)exp(z)2σ(2z)1

其中, tanh(z)(1,1) 
tanh(z)的梯度为: 

tanh(z)=1(exp(z)exp(z)exp(z)exp(z))2=1tanh2(z)

以下为页边注 


 
图10:tanh非线性的响应 

以上为页边注

Hard Tanh:hard tanh(硬双曲余弦正切)函数在有些时候要优于tanh函数,因为它在计算上更为简便。然而当z大于1时,hard tanh函数会在数值上形成饱和(译者注:即恒等于1)。hard tanh的激活函数为:

hardtanh(z)=1z1z < -1: -1z1z > 1

其微分也可以用分段函数来表达:

hardtanh(z)={10: -1z1: otherwise

以下为页边注 


 
图11:hard tanh非线性的响应 

以上为页边注

Soft Sign:Soft Sign函数是另一个可以被用来替代Tanh函数的非线性函数,因为它也不会像硬限幅函数(hard clipped functions)那样过早饱和。其函数表达式为:

softsign(z)=z1+z

其微分表达式为:

softsign(z)=sgn(z)(1+z)2

其中sgn()是符号函数,即根据z的符号返回11

以下为页边注 


 
图12:soft sign非线性的响应 

以上为页边注

ReLU:ReLU(修正线性单元,Rectified Linear Unit)函数是激活函数的一个流行选择,因为即使对特别大的z,它也不会饱和,并且已经发现它在计算机视觉应用中非常好用。其函数表达式为:

rect(z)=max(z,0)

其微分表达式为:

rect(z)={10: z>0: otherwise

以下为页边注 


 
图13:ReLU非线性的响应 

以上为页边注

Leaky ReLU:对于非正数的z,传统设计上的ReLU单元不会回传误差——而leaky ReLU修正了这一点,使得z是负数时,很小的误差也会反向传播回传回去。其函数表达式为:

leaky(z)=max(z,k×z)

其中,0<k<1 
因此其微分表达式可以被表示为:

leaky(z)={1k: z>0: otherwise

以下为页边注 


 
图14:leaky ReLU非线性的响应 

以上为页边注

2.4 Xavier参数初始化

在《理解训练深层前馈神经网络的困难(Understanding the Difficulty of Training Deep Feedforward Neural Networks)》(2010)一文中,Xavier等人研究了不同权重和偏差的初始化方案对训练动力(training dynamics)的影响。实证研究结果表明,对于sigmoid和tanh激活单元,当矩阵的权重WRn(l+1)×n(l)以均匀分布在以下值域范围内被随机初始化时,有着更低的错误率和更快的收敛速度:

WU6n(l)+n(l+1),6n(l)+n(l+1)

其中,n(l)W关联的输入单元的数量(fan-in),n(l1)W关联的输出单元的数量(fan-out)。

在这种参数初始化方案里,偏差项(b)被初始化为0。这种方法的目的是维持跨层的激活方差和反向传播梯度方差。如果不初始化,梯度方差(包含大量修正信息)一般会随层间反向传播而很快衰减。

2.5 学习速率

模型最优化的过程中,参数更新的速度可以通过学习速率来控制。比如下面的梯度下降公式中,α是学习速率:

θnew=θoldαθJt(θ)

看到公式以后你可能会认为α越大收敛速度会越快,事实上并不是这样哦。学习速率过大甚至可能会导致损失函数的不收敛,因为有时候因为太激进,参数的迭代步伐太大,一不小心跨过了凸优化的极小值,如图15所示。在非凸模型中(我们大多数时候遇到的),大学习速率的结果是不可预测的,但出现损失函数不收敛的可能性是非常高的。所以一定要慎重哦。

以下为页边注 


 
图15:从上图可以看出,有时候学习率太大,更新的参数w2反倒跨过了最低点,朝着误差增大的方向挪动了。 

以上为页边注

那怎么办呢?一个简单的方案就是,初始化一个比较小的学习速率,谨慎地在参数空间内迭代和调整以避免模型不收敛。同时,我们还可以固定模型中所有参数的学习速率,而不是为模型中所有参数设定不同的学习速率。

深度学习系统训练阶段通常最耗时耗资源,一些研究也试图应用一些新的方法来设置学习速率。例如,Ronan Collobert通过取神经元n(l)输入单元数的平方根的倒数来把权重WijWRn(l+1)×n(l))的学习速率进行标准化。另一种方法是允许学习速率随着时间而减小,如:

α(t)=α0τmax(t,τ)

在上述方案中,α0是一个可调参数,代表起始学习速率。τ也是一个可调参数,代表学习速率应该开始降低的时间。实践中,这种方法相当有效。下个部分,我们会讨论另一种方法,即不需要手动调节学习速率的自适应梯度下降法。

2.6 使用AdaGrad进行次梯度优化

AdaGrad是标准随机梯度下降法(SGD)的一种实现,但是有一个关键的区别:每个参数的学习速率是不同的。参数的学习速率取决于该参数梯度更新的历史情况,更新的历史越稀疏,就应该使用更大的学习速率加快更新。换句话说,那些在过去未被更新的参数更有可能在现在获得更高的学习速率。其形式如下:

θt,i=θt1,iαtτ=1g2τ,igt,i

其中,gt,i=θtiJt(θ)

对应上述公式我们可以看到,在这种算法中,如果梯度历史的方均根(RMS)非常低,学习速率会比较高。算法的实现如下:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># Assume the gradient dx and parameter vector x cache += dx**2</span>x += - learning_rate * dx / np.sqrt(cache + <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1e-8</span>)</code>
0 0
原创粉丝点击