原文作者:Rohit Mundra, Richard Socher
原文翻译:@熊杰([email protected]) && @王昱森([email protected]) && @范筑军老师( [email protected]) && @OWEN([email protected])
内容校正:寒小阳 && 龙心尘
时间:2016年6月
出处:http://blog.csdn.net/han_xiaoyang/article/details/51711134
http://blog.csdn.net/longxinchen_ml/article/details/51711172
说明:本文为斯坦福大学CS224d课程的中文版内容笔记,已得到斯坦福大学课程@Richard Socher教授的授权翻译与发表
课堂笔记:第3课
关键词:神经网络,正向计算,反向传播,神经元,最大化间隔损失,梯度检验, 参数的哈维初始化, 学习速率, ADAGRAD(自适应梯度法)
这是斯坦福CS224d深度学习与自然语言处理的第3课,这节课先会介绍单层和多层神经网络和它们在机器学习分类任务中的应用, 接着介绍如何利用反向传播算法来训练这些神经网络模型(在这个方法中,我们将利用偏导数的链式法则来层层更新神经元参数)。在给出神经网络以及这些算法严谨的数学定义后,介绍了训练神经网络的一些实用的技巧和窍门,比如,神经元(非线性激励),梯度检验,参数的Xavier初始化方法,学习速率,ADAGRAD(自适应梯度法)等。最后,我们在神经网络模型的基础上来建立语言模型
我们发现notes最后语言模型这个部分被略去了,回头ppt里面扒出来补充给大家
我们在前面的课程中提到,由于大部分数据并非线性可分,线性分类模型在这些数据上的分类效果略显尴尬,而非线性的分类模型通常能取得更好的效果。 如下图1所示,神经网络模型就是这样一类具备非线性决策边界的分类器。 从图上我们可以看到神经网络生成了非线性判定边界,来对2类样本做分类,那咱们一起来看看,它是怎么做到的呢。
一点小历史 ︰ 神经网络是受生物学启发的分类器,因此它也常被称为人工神经网络(ANN),以区别于生物学上的神经网络。事实上,人类的神经网络复杂性高很多,也比ANN具有更强大的能力,所以即使名字很接近,两者之间倒没有那么多的相似之处。
简单说来,神经元其实就是一个取 n 个输入,并产生单一输出的通用计算单元。每层神经元通过不同的参数(也称权重)产生不同的输出结果(通常情况下同一层神经元的输入值是相同的,只是权重不同)。比较常见的神经元(激励函数)是”Sigmoid函数”,也叫作”二项逻辑回归”单元。这种神经元,对于输入的 n 维向量,通过与 n 维的权重向量 w 和一个偏差标量 b 做组合运算,输出一个标量 a (咱们也把它叫做标量激活结果),具体的运算过程如下公式所示:
其中 w 是权重, b 是偏移量, x 是输入
为了运算的一致性和精简性,我们也可以把权重向量和偏差变量写到一个 n+1 维的向量里,得到上式的一个等价形式:
你们看出来了,就是把偏移量放作权重的一部分
下图2是这种神经元的一个直观一点的解释:
一点小总结:神经元呢,可以看做神经网络的基本组成功能单元,有多种多样功能(就是对输入做不同非线性变换)的神经元,它们共同去帮助整个神经网络形成非线性切分的能力。
刚才看完1个神经元的情况了,也知道它在做的非线性变换(输入到输出的运算)是什么,现在咱们拓展一下,看看对于一组输入 x ,一层神经元(其实就是多个神经元)的变换和处理情况,基本的结构,就如下图3所示。
我们分别用 {w(1),⋯,w(m)} , {b1⋯,bm} 和 {a1⋯,am} 来表示 m 个神经元的权重向量,偏移量以及激励输出,则有一下的结果:
式子多了看着有点乱,咱们设定一下以下的数学标记,简化简化在神经网络中的公式:
其中,
这样咱们的二元逻辑回归的激励输出就可以写成:
那这些激励输出到底是干嘛的呢,有什么物理含义? 一种理解方式是, 每个神经元都是对输入向量一个不同角度的处理加工, 提取输入向量的某一部分信息(比如图像数据中的纹理、颜色、轮廓,或者文本信息中的词性、时态等等)。然后这些信息会被用到分类任务中去,为决策提供依据。
上一节咱们讨论了如何将一个向量 x∈ℝn 输给神经网络中的一层(一组( m 个)二元回归神经元)进而得到他们的激励输出 a∈ℝm 。 也简单提了一下这么做的意义,为了加深理解,咱们还是用命名实体识别(NER)的例子来直观解释一下这个过程吧。看这样一个例子:
“Museums in Paris are amazing”
我们要来判断这里的中心词”Paris”是不是个命名实体。在这种情况下, 我们不止要知道这个词窗内哪些词向量出现过,可能也需要知道他们之间的相互作用。 比如说,可能只有在”Museums”出现在第1个位置,”in”出现在第二个位置的时候,Paris才是命名实体。如果你直接把词向量丢给Softmax函数, 这种非线性的决策是很难做到的。所以我们需要用1.2中讨论的方法对输入的变量进行非线性的处理加工(神经元产出非线性激励输出),再把这些中间层的产物输入到Softmax函数中去。 这样我们可以用另一个矩阵 U∈ℝm+1 ,与激励输出结果运算生成得分(当然,这里是未归一化的),从而进一步用于分类任务:
维度分析: 如果我们用4维词向量表示这些词,且用一个 5 词窗口作为输入(就像上面这个例子),那输入的变量就是 x∈ℝ20 . 如果在隐藏层中使用8个sigmoid神经元,并且由其激励输出生成1个得分,我们就有 W∈ℝ20,b∈ℝ8,U∈ℝ8×1,s∈ℝ 。
整个运算的过程(逐级的)大概是如下这个样子:
z=Wx+b
a=σ(z)
s=UTa
跟大多数机器学习模型一样,神经网络也需要一个优化目标,一个用来衡量模型好坏的度量。优化算法在做的事情呢,通常说来就是找到一组权重,来最优化目标或者最小化误差。这里我们讨论一个比较流行的度量,叫做最大化间隔目标函数。直观的理解就是我们要保证被正确分类的样本分数要高于错误分类的样本得分。
继续用之前的例子,如果我们把一个正确标记的词窗 “Museums in Paris are amazing”(这里Paris是命名实体)的得分记做 s , 而错误标记的词窗“Not all Museums in Paris”(这里Paris不是命名实体)的得分记作 sc (c表示这个词窗”corrupt”了)
于是,我们的目标函数就是要最大化 (s−sc) 或者最小化 (sc−s) 。 但是,我们要对这个目标函数稍作修改,让他只有在 sc>s=>(sc−s)>0 的时候才计算这个函数的值。因为当正确标记的词窗得分比错误标记的词窗得分高的时候,我们认为是满足要求的,并没有误差,我们只关心错误标记的词窗比正确标记的词窗得分高了多少,它代表了误差的程度。 于是,我们的目标函数在 sc>s 的时候取值 (sc−s) ,其余时候取值为 0 现在,优化目标变成:
因此,我们修改优化目标为 ︰
我们可以把这个 Δ 的取值定为1,在学习的过程中,模型其他的权重参数自动会进行相应的缩放,而并不会影响最终分类模型的精度。如果你想了解更多细节的话,可以去读一下支持向量机中关于函数间隔和几何间隔(functional and geometric margins)的内容。所以最后我们定义了以下形式的目标函数,作为在训练集中所有词窗上求最优化的目标:
在这一节中我们来讨论一下,当1.4节中的目标函数 J 取值为正的时候, 怎么来训练模型中的各个参数。 如果这个目标函数的取值是0, 那我们已经不再需要更新参数的取值了。 一般来讲,我们通过可以通过梯度下降法来更新参数(或者一些变种,像随机梯度下降/SGD)。这样就需要每个参数的梯度的信息来实现下面的更新过程:
这里我们讨论的是一个只有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+sc−s) 取正值,我们希望更新权重参数 W(1)14 (如图5及图6所示),我们注意到这里 W(1)14 只在计算 z(2)1 和 a(2)1 时出现。这一点对于理解反向传播很重要-参数的反向传播梯度只被那些在正向计算中用到过这个参数的值所影响。 a(2)1 在之后的正向计算中和 W(2)1 相乘进而参与到分类得分的计算中。我们从最大化边界损失的形式看到:
我们可以看到这个梯度最终可以简化为 δ(2)i⋅a(1)j 这样一个形式。 这里 δ(2)i 就是反向逆推到第 2 层上第 i 个神经元的误差。 a(1)j 则与 Wij 相乘后输入到第 2 层上第 i 个神经元的计算中。
译者注:这里所谓的反向传播误差 δ(k)i 其实就是最终的目标函数对于第 k 层上第 i 个激励输出值 z(k)i 的导数。当我们要求目标函数关于 Wk−1ij 的导数时, 因为第 k 层上只有 z(k)i 的计算涉及到 Wk−1ij , 所以可以把 z(k)i 写成关于 Wk−1ij 的函数,接着利用导数的链式法则,得到目标函数关于 Wk−1ij 的导数。误差 δ(k) 从 k 层传播到 k−1 层的过程就等价于求目标函数高 1 阶的导数,这一步同样可以由偏导数的链式法则得到。
我们以图6为例子,从”误差分配/分散”的角度来诠释一下反向传播。比如说我们如果要更新 W(1)14 :
我们可以看到,我们从哪个角度出发,最后得到的结果都是一样的。所以对于反向传播我们既可以从链式法则的角度来理解,也可以从误差分配/分散的角度来理解。
偏移量的更新 偏移量(如 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) 到 δ(k−1) 反向传播的一般化步骤:
我们前面介绍了如何计算模型中每个参数的梯度。这里我们要讨论如何把这些计算向量化及矩阵化(高效很多)。
对于权重参数 W(k)ij ,我们知道它的误差梯度为 δ(k+1)ia(k)j ,这里 W(k) 即为把 a(k) 映射到 z(k+1) 上的矩阵。 于是我们可以把误差信息对于整个矩阵 W(k) 的梯度表示成以下形式:
于是,我们可以把这个矩阵形式的梯度写成(从下一层)反向传播过来的误差和(从这一层)参与到前向计算中的激励输出的外积。
咱们接着看如何向量化的计算 δ(k) 。参考上面的图8, δ(k)j=f′(z(k)j)∑iδ(k+1)iW(k)ij 。
这可以很容易推广到矩阵形式
δ(k)=f′(z(k))∘(W(k)Tδ(k+1))
在上式中 ∘ 表示元素对应位相乘(即Hadamard积 ∘:ℝN×ℝN→ℝN )
计算效率: 我们知道,在很多科学计算软件中,像Matlab,Python(用NumPy/SciPy 包),向量化计算的效率远高于对每个元素逐个进行计算。所以,才实际操作中,我们尽可能的采用向量化的方式来训练参数。同时,我们在反向传播中应该尽量避免不必要的重复计算。比如说 δ(k) 的计算直接和 δ(k+1) 相关。 于是我们要保证在我们用 δ(k+1) 更新 W(k) 的时候, 我们存下 δ(k+1) 的值用来下一步计算 δ(k) 。以此类推,我们在 (k−1),…,(1) 上 我们重复这样的步骤,这种递归过程将使整个反向传播更加有效。
前面的部分讨论了神经网络的技术原理,理论和实践结合起来才能发挥大作用,现在咱们介绍一些神经网络在实际应用中常见的技巧和窍门。
我们已经介绍了如何用微积分计算神经网络模型中参数的误差梯度。现在我们介绍另一种不使用误差反向传播,而近似估计梯度的方法:
其中, θ(i+)=θ+ϵ×ei
从微分的定义来看,上述公式显然是正确的,但是怎么将其应用到求解误差梯度呢?对于一个给定的数据集,当我们正向扰动参数 θ 的第i个元素时(可以简单理解成 θ 加上一个极小的正数),咱们基于前向传导可以计算出误差项 J(θ(i+)) 。同理,当我们负向扰动参数 θ 的第i个元素时,咱们基于前向传导可以计算出新的误差项 J(θ(i-)) 。因此,其实通过做两次前向运算,我们就可以根据上面的公式估计出任何给定参数的梯度。当然了,其实只做一次前向传导所需要的运算量也不小了,所以在估计梯度时,这种方法比较耗时,但是,在用于验证反向传播的实现时,这种方法很赞,也用得很多。
梯度检验的简单实现可以参照下述方式:
def eval_numerical_gradient(f, x):
"""
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
"""
fx = f(x) # evaluate function value at original point
grad = np.zeros(x.shape)
h = 0.00001
# iterate over all indexes in x
it = np.nditer(x, flags=[’multi_index’],
op_flags=[’readwrite’])
while not it.finished:
# evaluate function at x+h
ix = it.multi_index
old_value = x[ix]
x[ix] = old_value + h # increment by h
fxh = f(x) # evaluate f(x + h)
x[ix] = old_value # restore to previous value (very important!)
# compute the partial derivative
grad[ix] = (fxh - fx) / h # the slope
it.iternext() # step to next dimension
return grad
以下为页边注
梯度检验:其实一般情况下,解析梯度是一个更快的梯度求解方法,不过容易出错,而梯度检验是个很好的比较解析梯度和数值型梯度的方法。数值型梯度可以用下述公式去计算:
其中, J(θ(i+)) 和 J(θ(i−)) 可以通过正向和负向微调 θ 后两次前向传导来计算得到,这种方法的代码实现可以参阅Snippet 2.1。
以上为页边注
像大多数分类器一样,神经网络也容易产生过拟合,这会导致其在验证集和测试集上的结果并不一定那么理想。为了解决这个问题,简单一点咱们可以应用L2正则化,加上正则化项的损失函数 JR 可以通过下述公式来计算:
在上述公式中, ∥W(i)∥F 是矩阵 W(i) 的F范数(frobenius norm), λ 是用于在加权和目标函数中进行正则化的相对权重。加上这个正则化项,意在通过作用到损失的平方来惩罚那些在数值上特别大的权重(译者注:也就是让权重的分配更均匀一些)
。这样一来,目标函数(也就是分类器)的随意度(译者注:也就是可用于拟合的复杂度)
就被降低了,约束了拟合函数的假设空间,因此减少了发生过拟合的可能性。施加这样一种约束条件可以用先验贝叶斯思想来理解,即最优的权重分配是所有权重都接近0。你想知道有多接近?对啦,这正是 λ 所控制的——大的 λ 会倾向于使所有权重都趋于0。值得注意的是,偏移量 b 不会被正则化,也不会被计算入上述的损失项(试着想想为什么?)。
前面的内容里,我们已经讨论过了包含sigmoid神经元(sigmoidal neurons)来实现非线性分类的神经网络算法,然而在许多应用中,使用其他激励(激活)函数(activation functions)可以设计出更好的神经网络。这里列举了一些常用选择的函数表达式和梯度定义,它们是可以和上文讨论过的sigmoid函数(sigmoidal functions)互相替代的。
Sigmoid:这是通常拿来做例子的函数,我们已经讨论过它,其激励(激活)函数 σ 为:
其中, σ(z)∈(0,1)
σ(z) 的梯度为:
以下为页边注
图9:Sigmoid非线性的响应
以上为页边注
Tanh:tanh函数是除了sigmoid函数之外的另一种选择,在实际中,它的收敛速度更快。tanh函数与sigmoid函数最主要的不同是tanh函数的输出结果在-1和1之间,而sigmoid函数的输出结果在0和1之间。
其中, tanh(z)∈(-1,1)
tanh(z) 的梯度为:
以下为页边注
图10: tanh 非线性的响应
以上为页边注
Hard Tanh:hard tanh(硬双曲余弦正切)函数在有些时候要优于tanh函数,因为它在计算上更为简便。然而当z大于1时,hard tanh函数会在数值上形成饱和(译者注:即恒等于1)。hard tanh的激活函数为:
其微分也可以用分段函数来表达:
以下为页边注
图11:hard tanh非线性的响应
以上为页边注
Soft Sign:Soft Sign函数是另一个可以被用来替代Tanh函数的非线性函数,因为它也不会像硬限幅函数(hard clipped functions)那样过早饱和。其函数表达式为:
其微分表达式为:
其中 sgn() 是符号函数,即根据 z 的符号返回 +1 或 -1 。
以下为页边注
图12:soft sign非线性的响应
以上为页边注
ReLU:ReLU(修正线性单元,Rectified Linear Unit)函数是激活函数的一个流行选择,因为即使对特别大的 z ,它也不会饱和,并且已经发现它在计算机视觉应用中非常好用。其函数表达式为:
其微分表达式为:
以下为页边注
图13:ReLU非线性的响应
以上为页边注
Leaky ReLU:对于非正数的 z ,传统设计上的ReLU单元不会回传误差——而leaky ReLU修正了这一点,使得 z 是负数时,很小的误差也会反向传播回传回去。其函数表达式为:
其中, 0<k<1
因此其微分表达式可以被表示为:
以下为页边注
图14:leaky ReLU非线性的响应
以上为页边注
在《理解训练深层前馈神经网络的困难(Understanding the Difficulty of Training Deep Feedforward Neural Networks)》(2010)一文中,Xavier等人研究了不同权重和偏差的初始化方案对训练动力(training dynamics)的影响。实证研究结果表明,对于sigmoid和tanh激活单元,当矩阵的权重 W∈ℝn(l+1)×n(l) 以均匀分布在以下值域范围内被随机初始化时,有着更低的错误率和更快的收敛速度:
其中, n(l) 是 W 关联的输入单元的数量(fan-in), n(l+1) 是 W 关联的输出单元的数量(fan-out)。
在这种参数初始化方案里,偏差项( b )被初始化为0。这种方法的目的是维持跨层的激活方差和反向传播梯度方差。如果不初始化,梯度方差(包含大量修正信息)一般会随层间反向传播而很快衰减。
模型最优化的过程中,参数更新的速度可以通过学习速率来控制。比如下面的梯度下降公式中, α 是学习速率:
看到公式以后你可能会认为 α 越大收敛速度会越快,事实上并不是这样哦。学习速率过大甚至可能会导致损失函数的不收敛,因为有时候因为太激进,参数的迭代步伐太大,一不小心跨过了凸优化的极小值,如图15所示。在非凸模型中(我们大多数时候遇到的),大学习速率的结果是不可预测的,但出现损失函数不收敛的可能性是非常高的。所以一定要慎重哦。
以下为页边注
图15:从上图可以看出,有时候学习率太大,更新的参数 w2 反倒跨过了最低点,朝着误差增大的方向挪动了。
以上为页边注
那怎么办呢?一个简单的方案就是,初始化一个比较小的学习速率,谨慎地在参数空间内迭代和调整以避免模型不收敛。同时,我们还可以固定模型中所有参数的学习速率,而不是为模型中所有参数设定不同的学习速率。
深度学习系统训练阶段通常最耗时耗资源,一些研究也试图应用一些新的方法来设置学习速率。例如,Ronan Collobert通过取神经元 n(l) 输入单元数的平方根的倒数来把权重 Wij ( W∈ℝn(l+1)×n(l) )的学习速率进行标准化。另一种方法是允许学习速率随着时间而减小,如:
在上述方案中, α0 是一个可调参数,代表起始学习速率。 τ 也是一个可调参数,代表学习速率应该开始降低的时间。实践中,这种方法相当有效。下个部分,我们会讨论另一种方法,即不需要手动调节学习速率的自适应梯度下降法。
AdaGrad是标准随机梯度下降法(SGD)的一种实现,但是有一个关键的区别:每个参数的学习速率是不同的。参数的学习速率取决于该参数梯度更新的历史情况,更新的历史越稀疏,就应该使用更大的学习速率加快更新。换句话说,那些在过去未被更新的参数更有可能在现在获得更高的学习速率。其形式如下:
其中, gt,i=∂∂θtiJt(θ)
对应上述公式我们可以看到,在这种算法中,如果梯度历史的方均根(RMS)非常低,学习速率会比较高。算法的实现如下:
# Assume the gradient dx and parameter vector x cache += dx**2
x += - learning_rate * dx / np.sqrt(cache + 1e-8)