本文由斯坦福CS224n翻译整理而来
1. 神经网络基础知识
1.1 单个神经元
单个神经元是神经网络的基本单位,其主要接受一个n维的向量x,输出为一个激活函数的输出a
每个神经元均可拟合一种非线性的变化形势,上图采用的主要是基于sigmoid函数的神经元。神经元内部的主要参数为一个n维向量的参数 ω 和一个偏移量b。
每一个神经网络可以看作是同时运行多个逻辑回归
1.2 单层神经网络
如果将多个神经元组合在一起那么就形成了一个单层神经网络,其参数也将变成一个集合 {ω(1),⋯,ω(m)} 以及 {b(1),⋯,b(m)}
为了更加好的表示多个复杂的变量,利用矩阵标记的形式标记如下
1.3 前向计算算法(Feed-forward Computation)
以一个实体识别的例子介绍,考虑如下的实体识别任务
“Museums in Paris are amazing”
这里我们要识别Paris是否是一个命名实体。在这个例子当中,我们不仅仅需要了解中心词的上下文是否存在,因为只计算上下文是否存在并不能得到上下文的 全部信息,还有一些其它的联系可以用于改进分类,比如只有当in在第二个词的时候,Museums才会成为第一个词。这种非线性的关系无法通过直接利用Softmax函数得到,因此,需要多个神经元提取不同的特征。因此,我们可以用另一个矩阵 U∈ℝ 生成一个未经规范化的分数用于分类任务
1.4 最大边缘目标函数(Maximum Margin Objective Function)
同众多的机器学习方法相同,神经网络也需要目标函数,最为有效的目标函数是最大边缘目标函数,他的主要目的是让对的标签得到的分数尽可能高
利用我们之前的例子,我们有两个句子,分别为正确的和错误的标签
“Museums in Paris are amazing”(True)
“Not all Museums in Paris”(False)
我们假定正确的句子的分数为s,错误的句子的分数为 sc ,因此我们可以得到一个目标函数
为了进一步优化这个目标函数,创造了一个间隔的概念,即让正确和错误的标签分的更加的远,因此引入间隔 Delta 使得 s−sc<Δ 。因此得到一个新的目标函数
为了其他的参数不受到任何影响,所以 Δ 设定为1
1.5 bp算法(Backpropagation)[1]
基本设定如下
-
xi
为神经网络的输入,s为神经网络的输出
- 在每一层(包括输入层和输出层),第k层的第j个神经元接受输入
z(k)j
并输出
a(k)j
- 我们将在向后传导过程中在
z(k)j
处的错误为
δ(k)j
- 当k等于1时,指的是第一层而不是第一层隐层,对于输入层
xj=z(k)j=a(k)j
-
W(k)
是从k层到k+1层到转移矩阵
整个bp算法本质在于从目标函数出发,逐层计算每一个对于最终目标函数到误差所作出到贡献
只需要记住三个公式就可以
k-1层的错误是由k层错误传导而来,对于k层错误的加权是根据W的比例进行加权得到对于 a(k)i 的错误,最终加一个对激活函数对导数得到第k-1层的错误
上述的两个公式为W和b的参数的计算,均利用到了之前得到的 δ
为了更加简便的计算,引入矩阵形式的公式,分别如下
2. 神经网络实践技巧
2.1 梯度检查(Gradient Check)
这里主要介绍了一个梯度检查方法,主要用于对分析得到的梯度进行检查,其基本公式为
通过对一个参数分量做微小的调整,利用两次前向计算,可以得到对于梯度的模拟。利用这种方法进行梯度的检查。一个简单的实现为:
def eval_numerical_gradient(f, x):
"""
- f 为代价函数
- x 需要计算梯度的点
"""
fx = f(x) # 原始点的损失函数值
grad = np.zeros(x.shape)
h = 0.00001
# 遍历x的所有位置的数字
it = np.nditer(x, flags=[’multi_index’],
op_flags=[’readwrite’])
while not it.finished:
ix = it.multi_index
old_value = x[ix]
x[ix] = old_value + h #添加一个增量
fxh_left = f(x) #计算f(x + h)
x[ix] = old_value - h #减少一个增量
fxh_right = f(x) #计算f(x - h)
x[ix] = old_value #还原现场
#计算偏导数
grad[ix] = (fxh_left - fxh_right) / (2*h)
it.iternext() #跳到下一个维度
return grad
2.2 正则化
如同一般的机器学习方法一样,正则化的引入可以避免陷入过拟合的情况,我们可以对目标函数加入L2正则化参数
这里需要注意的是,偏移量b不参与到正则化项当中。L1正则同样也是一种正则化的方法,但是会导致参数的稀疏,使用没有L2频繁。
2.3 Dropout[2]
Dropout是一种非常有用的正则化方法。模型训练时,在每个前向后向的传导中,以
1−p
的概率随机丢弃一部分神经元;在测试时,利用整个网络进行预测。实验证明,这可以有效的避免了过拟合,并且获得更好的性能。关于这一技术的解释,一种比较直观的说法认为通过这种方式可以同时训练很多小网络并且在预测时取其均值。
在实际使用过程中,对于每一层得到的结果,以概率
p
保留每一个神经元的值,其余设置为0。这样,在BP的过程中,我们只传导这些保留神经元的梯度。最后,在预测的时候,利用整个网络进行预测。然而,dropout能顺利工作的关键一点在于每个神经元在测试时的期望输出应该同训练时相同。因此,在测试时,需要对每个神经元除以一个固定的值。
备注:这里查阅了一下其他的资料,在做rescale的时候,有两种选择:一种是测试阶段不同,在训练阶段每一个保留的神经元的输出除以
2.4 激活函数类型
常用对激活函数总结如下:
sigmoid
最为常用的函数
σ(z)=11+exp(−z)
σ′(z)=σ(z)(1−σ(z))Tanh
tanh函数是sigmoid函数的另一种变形,它被发现可以比sigmoid函数更加快速的收敛
tanh(z)=exp(z)−exp(−z)exp(z)+exp(−z)=2σ(2z)−1
tanh′(z)=1−tanh2(z)Hard tanh
这个函数比tanh函数的计算更为简便,因为作为一个分段线性函数,计算速度更加快速
handtanh(z)=⎧⎩⎨⎪⎪−1z1z<−1−1⩽z⩽1z>1
handtanh′(z)={11−1⩽z⩽1otherwiseSoft sign
该函数是类似于tanh函数的另一种非线性函数,相比于hard tanh,该函数变化更加柔和
softsign(z)=11+|z|
softsign′(z)=sgn(z)(1+z)2
其中,sgn为激活函数,根据z的符号返回+-1ReLU
ReLU是目前一种非常流行的选择,因为当z取很大也不会饱和
rect(z)=max(z,0)
rect′(z)={10z>0otherwiseLeaky ReLU
传统的ReLU函数对于非正的z不会传递一点误差,这种函数做了一定的调整,使得当z为负的时候依旧可以传递一点误差
最后两种比较常用于计算视觉当中,前面几种在NLP中应用较多
2.5 数据预处理
一些常见的数据预处理的手段有:
Mean Subtraction
零中心化(zero-center)是较为常见的一种数据预处理手段,即对于数据中的每一个维度减去对应的均值。在实际使用中,需要注意的是均值只通过训练集计算得到,在训练集、测试集和交叉验证集中均要减去这个向量。Normalization
这是另一个常用的预处理手段。对于输入的每一个特征维度,将其保持在相同的数据范围中。通过该处理,可以保证特征的重要程度相同。在实现的过程中,对于每个特征除以其对应的标准差,同上一种一样,标注差只通过训练集合计算得到。Whitening
这个使用相对上述两种并不是十分广泛。该方法希望将数据转化为一个具有单位协方差矩阵的数据,即特征之间线性无关且每个特征的方差为1。实现的过程为:首先通过零中心化处理数据 X 得到X′ ,随后进行SVD分解得到矩阵 U,S,V ;计算 UX′ 将数据投影到 U 所对应的线性空间;最终,对于得到的矩阵中的每个维度,除以其对应于S中的奇异值来合理控制数据的范围。(如果奇异值为0,那么可以除以一个比较小的值)
2.6 权重初始化
关于参数的初始化,经验研究表明在sigmoid和tanh激活函数中,当W处于如下的均匀分布时,会得到最好的收敛剂速度
其中, n(l) 为该W的输入节点个数, n(l+1) 为输出节点个数。偏移量被初始化为0。这一初始化,可以保证激活函数方差以及BP的梯度方差在层之间维持稳定。如果不使用这种初始化,梯度的方差会对着跨越层数的增多而下降。
2.7 学习率
两种研究成果
RONAN COLLOBERT
对于 Wij,W∈ℝn(l+1)×n(l) ,该参数的学习率为
α=1n(l)‾‾‾√decrease by time
学习率随着时间的变化而变化,有两种方式:
α(t)=α0e−kt
α(t)=α0τmax(t,τ)
其中, α 为一个可调整参数,代表最初的学习率; τ 也是一个可调整参数,代表学习旅开始下降的时间
2.8 动量更新
这是受物理力学概念影响得到的一种梯度下降的变种。
2.9 Adaptive Optimization Methods
AdaGrad是一种SGD的变种实现,同SGD的主要区别在于每个参数的学习率依赖于该参数过去的梯度更新历史。形式化表述为
从公式可以看出,如果历史的梯度的RMS非常低,那么学习率会非常高。一个简单的实现为
# Assume the gradient dx and parameter vector x
cache += dx**2
x += - learning_rate * dx / np.sqrt(cache + 1e-8)
另外两种种常用的学习率方式为RMSProp和Adam,他们的实现方式如下
# Update rule for RMS prop
cache = decay_rate * cache + (1 - decay_rate) * dx**2
x += - learning_rate * dx / (np.sqrt(cache) + eps)
# Update rule for Adam
m = beta1*m + (1-beta1)*dx
v = beta2*v + (1-beta2)*(dx**2)
x += - learning_rate * m / (np.sqrt(v) + eps)
RMSProp是AdaGrad的一个变种,不同的是,学习率不一定会单调变小。
Adam将RMSProp和动量更新的方式进行了融合,一般来说可以作为一个通用选择。
参考文献
[1] Williams D E R G E H R J, Hinton G E. Learning representations by back-propagating errors[J]. Nature, 1986, 323: 533-536.
[2] Srivastava N, Hinton G, Krizhevsky A, et al. Dropout: A simple way to prevent neural networks from overfitting[J]. The Journal of Machine Learning Research, 2014, 15(1): 1929-1958.