标签:
原文作者:Rohit Mundra, Richard Socher
原文翻译:@熊杰(jie.xiong.cs@gmail.com) && @王昱森(ethanwang92@outlook.com) && @范筑军老师( fanzhj@mail.sysu.edu.cn) && @OWEN(owenj1989@126.com)
内容校正:寒小阳 && 龙心尘
时间: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具有更强大的能力,所以即使名字很接近,两者之间倒没有那么多的相似之处。
简单说来,神经元其实就是一个取
其中
为了运算的一致性和精简性,我们也可以把权重向量和偏差变量写到一个
你们看出来了,就是把偏移量放作权重的一部分
下图2是这种神经元的一个直观一点的解释:
一点小总结:神经元呢,可以看做神经网络的基本组成功能单元,有多种多样功能(就是对输入做不同非线性变换)的神经元,它们共同去帮助整个神经网络形成非线性切分的能力。
刚才看完1个神经元的情况了,也知道它在做的非线性变换(输入到输出的运算)是什么,现在咱们拓展一下,看看对于一组输入
我们分别用
式子多了看着有点乱,咱们设定一下以下的数学标记,简化简化在神经网络中的公式:
其中,
这样咱们的二元逻辑回归的激励输出就可以写成:
那这些激励输出到底是干嘛的呢,有什么物理含义? 一种理解方式是, 每个神经元都是对输入向量一个不同角度的处理加工, 提取输入向量的某一部分信息(比如图像数据中的纹理、颜色、轮廓,或者文本信息中的词性、时态等等)。然后这些信息会被用到分类任务中去,为决策提供依据。
上一节咱们讨论了如何将一个向量
“Museums in Paris are amazing”
我们要来判断这里的中心词”Paris”是不是个命名实体。在这种情况下, 我们不止要知道这个词窗内哪些词向量出现过,可能也需要知道他们之间的相互作用。 比如说,可能只有在”Museums”出现在第1个位置,”in”出现在第二个位置的时候,Paris才是命名实体。如果你直接把词向量丢给Softmax函数, 这种非线性的决策是很难做到的。所以我们需要用1.2中讨论的方法对输入的变量进行非线性的处理加工(神经元产出非线性激励输出),再把这些中间层的产物输入到Softmax函数中去。 这样我们可以用另一个矩阵
维度分析: 如果我们用4维词向量表示这些词,且用一个
整个运算的过程(逐级的)大概是如下这个样子:
跟大多数机器学习模型一样,神经网络也需要一个优化目标,一个用来衡量模型好坏的度量。优化算法在做的事情呢,通常说来就是找到一组权重,来最优化目标或者最小化误差。这里我们讨论一个比较流行的度量,叫做最大化间隔目标函数。直观的理解就是我们要保证被正确分类的样本分数要高于错误分类的样本得分。
继续用之前的例子,如果我们把一个正确标记的词窗 “Museums in Paris are amazing”(这里Paris是命名实体)的得分记做
于是,我们的目标函数就是要最大化
因此,我们修改优化目标为 ︰
我们可以把这个
在这一节中我们来讨论一下,当1.4节中的目标函数
这里我们讨论的是一个只有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 。
一起来看看反向传播吧: 假设目标函数
我们可以看到这个梯度最终可以简化为
译者注:这里所谓的反向传播误差
δ(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_{ij}^{(k)},我们知道它的误差梯度为 \delta_{i}^{(k+1)}a_{j}^{(k)} ,这里\mathbf{W}^{(k)} 即为把\mathbf{a}^{(k)} 映射到\mathbf{z}^{(k+1)} 上的矩阵。 于是我们可以把误差信息对于整个矩阵\mathbf{W}^{(k)} 的梯度表示成以下形式:
\Delta_{W^{(k)}}= \begin{bmatrix} \delta_{1}^{(k+1)}a_{1}^{(k)} & \delta_{1}^{(k+1)}a_{2}^{(k)} & \cdots
\\ \delta_{2}^{(k+1)}a_{1}^{(k)} & \delta_{2}^{(k+1)}a_{2}^{(k)} & \cdots
\\ \vdots & \vdots & \ddots \end{bmatrix}
= \delta^{(k+1)}a^{(k)T}
于是,我们可以把这个矩阵形式的梯度写成(从下一层)反向传播过来的误差和(从这一层)参与到前向计算中的激励输出的外积。
咱们接着看如何向量化的计算 \mathbf{\delta}^{(k)}。参考上面的图8,\delta_{j}^{(k)} = f‘(z_{j}^{(k)})\sum_{i} \delta_{i}^{(k+1)} W_{ij}^{(k)}。
这可以很容易推广到矩阵形式
\mathbf{\delta}^{(k)} = f‘(\mathbf{z}^{(k)})\circ(\mathbf{W}^{(k)T}\mathbf{\delta}^{(k+1)})
在上式中 \circ 表示元素对应位相乘(即Hadamard积 \circ: \Bbb R^{N}\times \Bbb R^{N} \rightarrow \Bbb R^{N} )
计算效率: 我们知道,在很多科学计算软件中,像Matlab,Python(用NumPy/SciPy 包),向量化计算的效率远高于对每个元素逐个进行计算。所以,才实际操作中,我们尽可能的采用向量化的方式来训练参数。同时,我们在反向传播中应该尽量避免不必要的重复计算。比如说 \mathbf{\delta}^{(k)}的计算直接和 \mathbf{\delta}^{(k+1)}相关。 于是我们要保证在我们用 \mathbf{\delta}^{(k+1)}更新 \mathbf{W}^{(k)}的时候, 我们存下 \mathbf{\delta}^{(k+1)}的值用来下一步计算 \mathbf{\delta}^{(k)}。以此类推,我们在(k-1),\dots,(1) 上 我们重复这样的步骤,这种递归过程将使整个反向传播更加有效。
前面的部分讨论了神经网络的技术原理,理论和实践结合起来才能发挥大作用,现在咱们介绍一些神经网络在实际应用中常见的技巧和窍门。
我们已经介绍了如何用微积分计算神经网络模型中参数的误差梯度。现在我们介绍另一种不使用误差反向传播,而近似估计梯度的方法:
f‘(\theta)\approx\frac{J(\theta^{(i+)})-J(\theta^{(i-)})}{2\epsilon}
其中,\theta^{(i+)}=\theta+\epsilon\times e_i
从微分的定义来看,上述公式显然是正确的,但是怎么将其应用到求解误差梯度呢?对于一个给定的数据集,当我们正向扰动参数\theta的第i个元素时(可以简单理解成\theta加上一个极小的正数),咱们基于前向传导可以计算出误差项J(\theta^{(i+)})。同理,当我们负向扰动参数\theta的第i个元素时,咱们基于前向传导可以计算出新的误差项J(\theta^{(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
以下为页边注
梯度检验:其实一般情况下,解析梯度是一个更快的梯度求解方法,不过容易出错,而梯度检验是个很好的比较解析梯度和数值型梯度的方法。数值型梯度可以用下述公式去计算:
f‘(\theta)\approx\frac{J(\theta^{(i+)})-J(\theta^{(i-)})}{2\epsilon}
其中,J(\theta^{(i+)})和J(\theta^{(i-)})可以通过正向和负向微调\theta后两次前向传导来计算得到,这种方法的代码实现可以参阅Snippet 2.1。
以上为页边注
像大多数分类器一样,神经网络也容易产生过拟合,这会导致其在验证集和测试集上的结果并不一定那么理想。为了解决这个问题,简单一点咱们可以应用L2正则化,加上正则化项的损失函数J_R可以通过下述公式来计算:
J_R=J+\lambda\sum_{i=1}^L\|W^{(i)}\|_F
在上述公式中,\|W^{(i)}\|_F是矩阵W^{(i)}的F范数(frobenius norm),\lambda是用于在加权和目标函数中进行正则化的相对权重。加上这个正则化项,意在通过作用到损失的平方来惩罚那些在数值上特别大的权重(译者注:也就是让权重的分配更均匀一些)
。这样一来,目标函数(也就是分类器)的随意度(译者注:也就是可用于拟合的复杂度)
就被降低了,约束了拟合函数的假设空间,因此减少了发生过拟合的可能性。施加这样一种约束条件可以用先验贝叶斯思想来理解,即最优的权重分配是所有权重都接近0。你想知道有多接近?对啦,这正是\lambda所控制的——大的\lambda会倾向于使所有权重都趋于0。值得注意的是,偏移量b不会被正则化,也不会被计算入上述的损失项(试着想想为什么?)。
前面的内容里,我们已经讨论过了包含sigmoid神经元(sigmoidal neurons)来实现非线性分类的神经网络算法,然而在许多应用中,使用其他激励(激活)函数(activation functions)可以设计出更好的神经网络。这里列举了一些常用选择的函数表达式和梯度定义,它们是可以和上文讨论过的sigmoid函数(sigmoidal functions)互相替代的。
Sigmoid:这是通常拿来做例子的函数,我们已经讨论过它,其激励(激活)函数\sigma为:
\sigma(z)=\frac{1}{1+exp(-z)}
其中, \sigma(z)\in(0,1)
\sigma(z)的梯度为:
\sigma‘(z)=\frac{-exp(-z)}{1+exp(-z)}=\sigma(z)(1-\sigma(z))
以下为页边注
图9:Sigmoid非线性的响应
以上为页边注
Tanh:tanh函数是除了sigmoid函数之外的另一种选择,在实际中,它的收敛速度更快。tanh函数与sigmoid函数最主要的不同是tanh函数的输出结果在-1和1之间,而sigmoid函数的输出结果在0和1之间。
tanh(z)=\frac{exp(z)-exp(-z)}{exp(z)+exp(-z)}=2\sigma(2z)-1
其中, tanh(z)\in(-1,1)
tanh(z)的梯度为:
tanh‘(z)=1-\left(\frac{exp(z)-exp(-z)}{exp(z)+exp(-z)}\right)^2=1-tanh^2(z)
以下为页边注
图10:tanh非线性的响应
以上为页边注
Hard Tanh:hard tanh(硬双曲余弦正切)函数在有些时候要优于tanh函数,因为它在计算上更为简便。然而当z大于1时,hard tanh函数会在数值上形成饱和(译者注:即恒等于1)。hard tanh的激活函数为:
hard tanh(z)=\begin{cases} -1 & \text {: $z$ < -1} \\ z & \text{: -1$\leq z\leq$1} \\ 1 & \text{: $z$ > 1} \end{cases}
其微分也可以用分段函数来表达:
hard tanh‘(z)=\begin{cases} 1 & \text{: -1$\leq z\leq$1} \\ 0 & \text{: otherwise} \end{cases}
以下为页边注
图11:hard tanh非线性的响应
以上为页边注
Soft Sign:Soft Sign函数是另一个可以被用来替代Tanh函数的非线性函数,因为它也不会像硬限幅函数(hard clipped functions)那样过早饱和。其函数表达式为:
softsign(z)=\frac{z}{1+\mid z\mid}
其微分表达式为:
softsign‘(z)=\frac{sgn(z)}{(1+z)^2}
其中sgn()是符号函数,即根据z的符号返回+1或-1。
以下为页边注
图12:soft sign非线性的响应
以上为页边注
ReLU:ReLU(修正线性单元,Recti?ed Linear Unit)函数是激活函数的一个流行选择,因为即使对特别大的z,它也不会饱和,并且已经发现它在计算机视觉应用中非常好用。其函数表达式为:
rect(z) = max(z,0)
其微分表达式为:
rect‘(z)=\begin{cases} 1 & \text{: z>0} \\ 0 & \text{: otherwise} \end{cases}
以下为页边注
图13:ReLU非线性的响应
以上为页边注
Leaky ReLU:对于非正数的z,传统设计上的ReLU单元不会回传误差——而leaky ReLU修正了这一点,使得z是负数时,很小的误差也会反向传播回传回去。其函数表达式为:
leaky(z)=max(z,k\times z)
其中,0<k<1
因此其微分表达式可以被表示为:
leaky‘(z)=\begin{cases} 1 & \text{: z>0} \\ k & \text{: otherwise} \end{cases}
以下为页边注
图14:leaky ReLU非线性的响应
以上为页边注
在《理解训练深层前馈神经网络的困难(Understanding the Difficulty of Training Deep Feedforward Neural Networks)》(2010)一文中,Xavier等人研究了不同权重和偏差的初始化方案对训练动力(training dynamics)的影响。实证研究结果表明,对于sigmoid和tanh激活单元,当矩阵的权重W\in\mathbb R^{n^{(l+1)}\times n^{(l)}}以均匀分布在以下值域范围内被随机初始化时,有着更低的错误率和更快的收敛速度:
W\sim U\left[-\sqrt{\frac{6}{n^{(l)}+n^{(l+1)}}},\sqrt{\frac{6}{n^{(l)}+n^{(l+1)}}}\right]
其中,n^{(l)}是W关联的输入单元的数量(fan-in),n^{(l+1)}是W关联的输出单元的数量(fan-out)。
在这种参数初始化方案里,偏差项(b)被初始化为0。这种方法的目的是维持跨层的激活方差和反向传播梯度方差。如果不初始化,梯度方差(包含大量修正信息)一般会随层间反向传播而很快衰减。
模型最优化的过程中,参数更新的速度可以通过学习速率来控制。比如下面的梯度下降公式中,\alpha是学习速率:
\theta^{new}=\theta^{old}-\alpha \nabla _\theta J_t (\theta)
看到公式以后你可能会认为\alpha越大收敛速度会越快,事实上并不是这样哦。学习速率过大甚至可能会导致损失函数的不收敛,因为有时候因为太激进,参数的迭代步伐太大,一不小心跨过了凸优化的极小值,如图15所示。在非凸模型中(我们大多数时候遇到的),大学习速率的结果是不可预测的,但出现损失函数不收敛的可能性是非常高的。所以一定要慎重哦。
以下为页边注
图15:从上图可以看出,有时候学习率太大,更新的参数w_2反倒跨过了最低点,朝着误差增大的方向挪动了。
以上为页边注
那怎么办呢?一个简单的方案就是,初始化一个比较小的学习速率,谨慎地在参数空间内迭代和调整以避免模型不收敛。同时,我们还可以固定模型中所有参数的学习速率,而不是为模型中所有参数设定不同的学习速率。
深度学习系统训练阶段通常最耗时耗资源,一些研究也试图应用一些新的方法来设置学习速率。例如,Ronan Collobert通过取神经元n^{(l)}输入单元数的平方根的倒数来把权重W_{ij}(W\in\mathbb R^{n^{(l+1)}\times n^{(l)}})的学习速率进行标准化。另一种方法是允许学习速率随着时间而减小,如:
\alpha(t)=\frac{\alpha_0 \tau}{max(t,\tau)}
在上述方案中,\alpha_0是一个可调参数,代表起始学习速率。\tau也是一个可调参数,代表学习速率应该开始降低的时间。实践中,这种方法相当有效。下个部分,我们会讨论另一种方法,即不需要手动调节学习速率的自适应梯度下降法。
AdaGrad是标准随机梯度下降法(SGD)的一种实现,但是有一个关键的区别:每个参数的学习速率是不同的。参数的学习速率取决于该参数梯度更新的历史情况,更新的历史越稀疏,就应该使用更大的学习速率加快更新。换句话说,那些在过去未被更新的参数更有可能在现在获得更高的学习速率。其形式如下:
\theta_{t,i}=\theta_{t-1,i}-\frac{\alpha}{\sqrt{\sum_{\tau=1}^{t}\scr g_{\tau,i}^2}}\scr g_{t,i}
其中,\scr{g}_{t,i} = \frac{\partial}{\partial \theta_i^t} J_t(\theta)
对应上述公式我们可以看到,在这种算法中,如果梯度历史的方均根(RMS)非常低,学习速率会比较高。算法的实现如下:
# Assume the gradient dx and parameter vector x cache += dx**2
x += - learning_rate * dx / np.sqrt(cache + 1e-8)
深度学习与自然语言处理(3)_斯坦福cs224d Lecture 3
标签:
原文地址:http://blog.csdn.net/han_xiaoyang/article/details/51711134