机器学习:神经网络

阅读量 ,评论量

前馈神经网络 (feed-forward nueral network)

前馈神经网络一般有两种,linear perceptron network 和 RBF network,该文主要叙述前一种,其学习规则是梯度下降法,是一种无约束的最优化算法。

NN示例代码

nn_net1

符号及标记

符号 含义
\(L\) 代价函数 / 损失函数 / 最优化目标函数
\(w\) “轴突”权重
\(z\) \(l\)层的带权输入 \(z^l=w^{l-1}a^{l-1}\) ,即从上个神经元轴突传到该神经元树突上的值
\(\sigma\) 神经元的激活函数
\(a\) 神经元的激活值
\(\delta\) 中间变量,称为某层的误差,专用于反向传播算法

注 1:零层为真实数据,无前传过程,自然没有所谓的误差 \(\delta^0\);因第零层的“轴突”上的权重 \(w^0\) 尚未学习成功而导致的第一层的神经元的带权输入 \(z^1\) 的误差,记为 \(\delta^1\)

注 2:每层“轴突”上的权重\(w\) 的行数为希望学到的模式数,列数为输入数据的维度。

注 3:做某一层的前传的时候,那一层的恒一神经元(用于模式中的偏置)才会被重新激活;当做为输出层时,该层的恒一神经元处于闭塞状态,是看不见的。当做某一层的反传的时候,是用该层神经元的误差\(\delta\)loss对上一层“轴突”权重的偏导,而该层的恒一神经元没有所谓的误差,上一层的恒一神经元却有“轴突”权重,自然也有偏导,故只有求“轴突”权重偏导的那一层的恒一神经元会被重新激活,其余均处于闭塞状态。

学习法则

\(y\)\((x_1,\dotsb,x_m)\) 线性相关,且采样没有噪声,则直接采\(m\)个样本点求解线性方程就能得到参数 \(\omega\) 的唯一解。为应对非线性相关的数据,采用迭代最优化(iterative optimization)的方法。

Ref:https://blog.csdn.net/heyongluoyao8/article/details/52478715

参数更新:梯度下降法

梯度是个向量,指函数变化增加最快的地方,故沿负梯度的方向便能到达函数的极小值处。

迭代优化的参数更新通式为: \[ w(t+1) \leftarrow w(t)+\alpha v(t) \quad , \alpha > 0 \] 其中参数的确定由降低 loss 函数的方法确定。对 loss 函数进行一阶泰勒展开: \[ L(w(t+1)) \approx L(w(t))+(w(t+1)-w(t))\nabla L(w(t)) \\ = L(w(t))+ \alpha v(t)^T \nabla L(w(t)) \] 要使 \(L(w(t+1))\) 最小,须使 $ v(t)^T L(w(t))$ 最小,故令 \(v(t)= -\nabla L(w(t))\) 。为使得学习率在陡的地方大,缓的地方小,令 \(\alpha \propto ||\nabla L(w(t))||\) ,从而得到梯度下降法的参数更新式: \[ w(t+1) \leftarrow w(t)-\alpha_0 \nabla L(w(t)) \] 其中 \(\alpha_0\) 称为 fixed learning rate,而真实的 learning rate \(\alpha\) 的大小随梯度的大小变化而变化。

参数更新2:牛顿下降法

loss 函数进行二阶泰勒展开: \[ L(w(t+1)) \approx L(w(t))+(w(t+1)-w(t))\nabla L(w(t))+\frac{(w(t+1)-w(t))^2}{2}\nabla^2 L(w(t)) \\ = L(w(t))+F(w(t+1)-w(t)) \] 为使\(L(w(t+1))\)最小,令\(F(w(t+1)-w(t))\)达到负的最小即可,即置导数为零得: \[ \frac{\partial F(w(t+1))-w(t))}{\partial w(t+1))-w(t)} = 0 \\ => w(t+1))-w(t) = \frac{- \nabla L(w(t))}{\nabla^2 L(w(t))} \] 对应参数迭代更新通式,\(v(t)=\frac{- \nabla L(w(t))}{\nabla^2 L(w(t))}=-H^{-1}\nabla L(w(t))\),其中H称为海森矩阵,为函数的二阶偏导方阵,可用SVD求伪逆以保证数值稳定性。

BFGS

海森矩阵计算费时,故有一些准牛顿算法来近似逼近,BFGS算法便是一种。BFGS算法通过迭代来逼近\(H^{-1}\)

image-20210715144427987

BFGS算法迭代过程中需要存储D矩阵,如果太大会导致可行性较差,可通过L-BFGS算法,以时间换空间的算法来节省内存。

参数更新3:坐标下降法

与梯度优化算法沿着梯度最速下降的方向寻找函数最小值不同,坐标下降法依次沿着坐标轴的方向最小化目标函数值。

注意:

求梯度:反向传播算法

\[ \delta^l=(w^l\delta^{l+1})*\sigma'_{z^l} \\ \frac{\partial L}{\partial w^{l-1}}=\frac{1}{n}\delta^{l}(a^{l-1})^T \]

在用矩阵编程计算梯度时,无需考虑具体矩阵乘积的细节和含义,在得到反向传播的标量表达式后,只需依照两条规则即可写出梯度的矩阵算式:

  1. 依据标量表达式确定算式的结构;
  2. 依据loss对该层参数偏导的形状调整矩阵的顺序和形状。

优化算法

GD

\[ w(t+1) \leftarrow w(t)-\alpha_0 \nabla L(w(t)) \]

Vanilla SGD

参数更新公式同GD,利用 Stochastic 的优势,以跳出局部最优点。

Asyn SGD

同步随机梯度下降法(Synchronous SGD)在优化的每轮迭代中,会等待所有的计算节点完成梯度计算,然后将每个工作节点上计算的随机梯度进行汇总、平均并上面的公式更新模型。之后,工作节点接收更新之后的模型,并进入下一轮迭代。由于Sync SGD要等待所有的计算节点完成梯度计算,因此好比木桶效应,Sync SGD的计算速度会被运算效率最低的工作节点所拖累。

异步随机梯度下降法(Asynchronous SGD)在每轮迭代中,每个工作节点在计算出随机梯度后直接更新到模型上,不再等待所有的计算节点完成梯度计算。因此,异步随机梯度下降法的迭代速度较快,也被广泛应用到深度神经网络的训练中。然而,Async SGD虽然快,但是用以更新模型的梯度是有延迟的,会对算法的精度带来影响。Async SGD的更新公式为: \[ w(t+\tau+1) \leftarrow w(t+\tau)-\alpha_0 \nabla L(w(t)) \] 对参数 \(w_{t+\tau}\) 更新时所使用的随机梯度是 \(\nabla L(w(t))\) ,相比SGD中应该使用的随机梯度 \(\nabla L(w(t+\tau))\) 产生了τ步的延迟。因而,我们称Async SGD中随机梯度为“延迟梯度”。

这种延迟梯度会影响模型收敛的准确性,并且这种现象随着机器数量的增加会越来越严重。

DC-ASGD

带延迟补偿的随机梯度下降法(ASGD with Delay Compensation)。ASGD中用的是 \(\nabla L(w(t))\) ,实际上应该用 \(\nabla L(w(t+\tau))\) ,那么能不能用 \(\nabla L(w(t))\) 来近似表示 \(\nabla L(w(t+\tau))\) 呢?将 \(\nabla L(w(t+\tau))\) 进行一阶泰勒展开: \[ \nabla L(w(t+\tau)) \approx \nabla L(w(t)) +(w(t+\tau)-w(t))\nabla^2 L(w(t)) \] 其中 \(w(t)\) 通过各work节点拉模型参数时缓存得到(即分work节点地保存模型参数,若共有5个节点,那么加上主版本的模型参数,ps应该共维护6份模型参数),主要难点就在于那个loss函数的二阶导了。根据费舍尔信息矩阵的定义,梯度的外积矩阵是Hessian矩阵的一个渐近无偏估计(点我看更多的推导细节),故: \[ \begin{align*} \nabla^2 L(w(t)) &\approx \nabla L(w(t)) \otimes (\nabla L(w(t)))^T \\ &\approx Diag(\lambda \nabla L(w(t)) \otimes (\nabla L(w(t)))^T) \\ &= \lambda \nabla L(w(t)) \odot \nabla L(w(t)) \end{align*} \] 由此代入SGD中: \[ \begin{align*} w(t+\tau+1) &\leftarrow w(t+\tau)-\alpha_0 \nabla L(w(t+\tau)) \\ &\approx w(t+\tau) - \alpha_0 (\nabla L(w(t)) +\lambda \nabla L(w(t)) \odot \nabla L(w(t))(w(t+\tau)-w(t))) \end{align*} \] 具体操作:各work节点在t步从ps中把模型参数w拉下来,并计算梯度 \(\nabla L(w(t))\) ,然后将节点编号和计算好的梯度推给ps,ps从缓存中拿到该节点对应的模型参数w(t),从主模型中拿到w(t+τ),计算得到w(t+τ+1),完成一轮更新。

Downpour SGD

参数服务器 + ASGD + AdaGrad 组成的优化算法。

AdaGrad

更新公式为: \[ w(t+\tau+1) \leftarrow w(t+\tau)- \frac{\alpha_0}{\epsilon + \sqrt{\sum_{i=0}^{t}(\nabla L(w(i)))^2}} \nabla L(w(t)) \] 其中, \(\alpha_0\) 为预设的固定学习率,随着不断的迭代,学习率越来越小,梯度延迟的影响也会越来越小。

另外,实验发现,该优化算法的不一致性带来的随机性,对非凸问题求解很有效:

  1. 异步随机梯度下降,导致的梯度延迟;
  2. ps独立更新不同部分的模型,导致的模型部分参数版本不一致;
  3. push和pull分别在不同线程独立运行,会导致什么不一致?

SGD-M

\[ \begin{align*} M(t) &= \alpha M(t-1) + (1-\alpha) \nabla L(w(t)) \\ w(t) &\leftarrow w(t-1) - \alpha_0 M(t) \end{align*} \]

通常,\(\alpha=0.9\),这意味着参数更新方向不仅由当前的梯度决定,也与此前累积的下降方向有关。这使得参数中那些梯度方向变化不大的维度可以加速更新,并减少梯度方向变化较大的维度上的更新幅度。由此产生了加速收敛和减小震荡的效果。

其本质为对梯度的滑动平均(指数加权平均,具体效果是一种时间衰减的效果),其原理如下:

expMoveAvg

注:

Adam

【官方解释】

Adam,即 Adaptive Moment Estimation,对SGD-M的自适应预估。 \[ \begin{align*} g_t &= \nabla L(w(t)) \\ M(t) &\leftarrow \beta_1 M(t-1) + (1-\beta_1)g_t \\ \hat{M}(t) &\leftarrow \frac{M(t)}{1 - \beta_1^t} \\ v_t &\leftarrow \beta_2 v_{t-1} + (1-\beta_2)g_t^2 \\ \hat{v}_t &\leftarrow \frac{v_t}{1 - \beta_2^t} \\ w_t &\leftarrow w_{t-1} - \frac{\alpha_0}{\sqrt {\hat{v}_t} + \epsilon} \hat{M}(t) \end{align*} \] \(\hat{M}(t)\) 是对 \(E(g_t)\) 的估计,衡量了训练时关于更新梯度的信息;\(\hat{v}_t\) 是对 \(E(g_t^2)\) 的估计,即参数的方差估计,衡量了模型参数的稳定性。

定义 \(\frac{\hat{M}(t)}{\sqrt{\hat{v}_t}}\) 为信噪比,可以理解为,信噪比越高,意味着对当前梯度走向越有把握,从而得到更大的步长。

在迭代过程中,其中 \(\beta_1^2\)\(\beta_2^2\) 会存在精度丢失的问题,可将公式做如下转换(以 \(\hat{v}_t\) 为例): \[ \begin{align*} g_t &= \nabla L(w(t)) \\ \hat{v}_t &\leftarrow \frac{v_t}{1 - \beta^t} \\ &\leftarrow \frac{\frac{v_t}{1 - \beta}}{\frac{1 - \beta^t}{1 - \beta}} \\ &\leftarrow \frac{\frac{\beta v_{t-1} + (1-\beta)g_t^2}{1 - \beta}}{\sum_{i=0}^{i=t}\beta^i} \end{align*} \] 故: \[ \begin{align*} d_t &\leftarrow \beta d_{t-1} + 1 \\ s_t &= \frac{v_t}{1 - \beta} \\ s_t &\leftarrow \beta \frac{v_{t-1}}{1-\beta} + g_t^2 \\ &\leftarrow \beta s_{t-1} + g_t^2 \\ \hat{v}_t &= \frac{s_t}{d_t} \\ \end{align*} \] 代入模型参数更新式中: \[ \begin{align*} d_t &\leftarrow \beta d_{t-1} + 1 \\ s_t &\leftarrow \beta s_{t-1} + g_t^2 \\ w_t &\leftarrow w_{t-1} - \frac{\alpha_0}{\sqrt {\hat{v}_t} + \epsilon} \hat{M}(t) \\ &\leftarrow w_{t-1} - \alpha_0 \hat{M}(t) \sqrt{\frac{1+\epsilon}{s_t/d_t +\epsilon}} \end{align*} \] 【另一种解释】

通过引入二阶动量放在学习率的分母上,来解决对于『经常更新但梯度不大』A 和『梯度接近于0不怎么更新』B,下一步A、B都有一个较大梯度回传时,B的更新量小于A的问题。相当于是 SGD-M 和 AdaGrad 两者的结合体 ,实现既能加速收敛(SGD-M)又能对稀疏的特征得到更大的更新。

对于经常更新的参数,我们已经积累了大量关于它的知识,不希望被单个样本影响太大,希望学习速率慢一些;对于偶尔更新的参数,我们了解的信息太少,希望能从每个偶然出现的样本身上多学一些,即学习速率大一些。所以我们需要一个东西去度量历史更新频率。考虑到对于梯度接近于0的参数自然不怎么更新,故我们可以用二阶动量来衡量滑动窗口内的历史更新频率。参数更新式如下: \[ \begin{align*} g_t &= \nabla L(w(t)) \\ M(t) &= \beta_1 M(t-1) + (1-\beta_1)g_t \\ v_t &= \beta_2 v_{t-1} + (1-\beta_2)g_t^2 \\ w_t &\leftarrow w_{t-1} - \frac{\alpha_0}{\sqrt v_t + \epsilon} M(t) \end{align*} \] 通常,\(\beta_1=0.9,\beta_2=0.999,\epsilon=10^{-8},\alpha_0=0.001\)

由于Adam中的学习率主要是由二阶动量控制的,这就可能在训练后期引起学习率的震荡,导致模型无法收敛。通常解决方法为修改二阶动量更新公式,从而使得各参数的学习率都单调递减: \[ v_t = \max ( \beta_2 v_{t-1} + (1-\beta_2)g_t^2, v_{t-1}) \] 但进而又会引入另一个问题:后期Adam的学习率太低,影响了有效的收敛。故通常的方法为:前期用Adam,享受Adam快速收敛的优势;后期切换到SGD,慢慢寻找最优解。

其实某种算法是否有效,主要看你的数据是否符合该算法的胃口,算法固然美好,数据才是根本。

自适应学习率

每个神经元入度的不同导致了流入不同神经元的“树突”权值的最佳学习率各不相同。当入度很大时,每个“树突”权值改变一点点,累积的改变量就很大了,很容易过量;而当入度很小时反之。所以一般采用一个全局学习率,然后根据对每个神经元各自做适当调整: \[ w+=-\epsilon g \frac{\partial L}{\partial w} \] 初始化局部 \(g=1\) ,如果下次该权值的梯度符号不变则增加 \(g+=0.05\) ,否则减小为 \(g*=0.95\)

注意:

  1. \(g\) 限制在某个合理的范围,比如[0.1, 10] 或 [.01, 100]。
  2. 使用 full-batch 或很大的 mini-batch,毕竟这样保证了梯度的符号不易受 mini-batch 的采样误差影响。
  3. 综合自适应学习率和动量更新法,以当前梯度和当前速度的符号来决策 \(g\) 的变化。

RMSProp:将梯度除以历史数量级

全局学习率之所以难选,主要是因为每个期望的最终的权值的数量级相差巨大。在 full batch 中,可以利用梯度的符号来替代权值的更新量,从而解决这个问题。RProp结合了“只用符号”和“自适应学习率”的思想,但它违反了 SGD 的中心思想(当学习率很小的时候,权值更新量其实是当前mini-batch的梯度和历史梯度的平均。举例来讲,假设某个权值在9个批次中的梯度是+0.1,在第10个批次中的梯度是-0.9,我们希望这个权值大致不变。),故不适用于 mini-batch。而RMSProp便融合了mini-batch的高效性、mini-batch间的有效平均和RProp的稳定性。

总结

常用的激活函数及其对应的 loss 函数

假设激活函数\(\sigma(z)=z\),则 \(L(\omega , b)=\frac{1}{n}\sum ||y- \{ \omega_{L-1}[\omega_{L-2}(...x)] \}||_2\),而我们平常所说的loss函数是与网络结构无关的“基础loss函数”。

线性激活函数与均方差 loss 函数

\[ \sigma(z)=z \\ L=\frac{1}{2n}\sum_x||a(x)-y(x)||^2 \]

sigmod 激活函数与交叉熵 loss 函数

\[ \sigma(z)=\frac{1}{1+e^{-z}} \\ L=-\frac{1}{n}\sum_x[y\ln a+(1-y)\ln (1-a)] \]

  1. 用交叉熵而非均方差的原因是为了解决神经元饱和问题(指某些神经元可能因权重初始化不当或者确实已经学到了很成熟的地步,而处于 \(\sigma'(z)\) 值很小的激活值位置,进而导致在该处的梯度几近为零的现象;而其后果是神经元激活值会维持很长一段时间的近似稳定状态,这在训练终期是代表收敛的好现象,但若是在训练初期(即误差比较大时), loss 下降会十分缓慢,严重拖慢收敛速度):使用交叉熵会使得采用 sigmod 激活的神经网络中的梯度表达式中的 \(\sigma'(z)\) 被约掉,从而解决了神经元饱和问题。

  2. 可根据激活函数和希望的梯度形式反推得到所需的loss函数,见神经网络与深度学习(Michael Nielsen)3.1.3节。

  3. 最小化交叉熵 loss 函数等价于最大化以 sigmod 为参数的对数似然,见数学——基础 信息论

softmax 激活函数与对数似然 loss 函数(right 损失函数)

\[ \sigma(z_i)=\frac{e^{z_i}}{\sum_ie^{z_i}} \\ L=-\frac{1}{n}\sum_x\ln a_I,(a_I为真实类别对应神经元的激活值) \]

  1. 因loss只求在真实类别神经元上的偏差,而激活函数的分母中所有神经元都包含,故\(\delta^L\)的求解分为两部分,求解过程如下: \[ \delta_i^L = - \frac{1}{a_I^L} \frac{\partial a_I^L}{\partial z_i^L} \\ a_I = \frac{e^{z_I}}{\sum_ie^{z_i}} \\ if \quad i \ne I: \frac{\partial a_I}{\partial z_i} = -a_ia_I, then \quad \delta_i^L = a_i; \\ if \quad i=I: \frac{\partial a_I}{\partial z_i} = a_I(1-a_I), then \quad \delta_i^L = a_I-1; \\ and, \quad y_i=\{^{0,i\ne I} _{1,i=I} \\ so, \quad \delta^L = a-y. \]

对该学习算法的一些理解

  1. 在迭代过程中,还会出现神经元饱和问题,从梯度公式的角度想,是\(\sigma'(z)\)\(a\)过小导致的梯度过小,从而引起学习缓慢的问题,解决方法便是构造合适的函数将梯度公式中的\(\sigma'(z)\)约掉;从网络的\(loss\)函数角度想,是寻找极低点时中途出现了原地踱步(小梯度)的情况,解决方法便是选用不同的激活函数与基础\(loss\)函数的搭配,从而得到形状更好的网络\(loss\)函数;从模式的可激活空间角度想,是样本\(x\)在模式\(\omega\)的激活空间上的分量值(带权输入)处于激活函数的平缓处(极低变化率)导致的模式\(\omega\)寻找进度迟缓,解决方法便是消除激活函数变化率对模式\(\omega\)的迭代寻找的影响。
  2. 对导数的理解:导数的2-范数越大,说明在该点越不稳定,或说在该点输入的波动会对网络的输出造成很大的影响,故亦可称导数为该点的不稳定度或不成熟度。即可将 \(\delta\) 理解为该神经元的不成熟度。

实际应用中遇到的问题

最优化问题:更新频率与幅度究竟应该为多大

泛化问题:如何防止过拟合?数据的两种噪音

神经网络调参

第一步

首先,要保证代码的 no bug,不仅指编译和运行时不会导致 core dump,还指神经网络可以确确实实地在拟合到一个可用的模型上,一般需要做如下工作:

第二步

然后开始做调参工作(测试集与训练集的loss最好同时保存,以便调参):

  1. 先固定batchsize为32(以装满显存为准),再将lr从1开始十倍十倍地往下降,一直降到loss震荡趋于缓和(如0.01),然后以该学习率为lr_base
  2. 以该lr_base去寻找收敛更快的batchsize,batchsize过小可能导致不收敛,batchsize过大可能导致收敛不到最优值,loss较高
  3. 在找到合适的batchsize后,以lr_base+sgd+momentum跑N多epoch
  4. 在loss收敛区占整个训练epoch的1/5后,选择某几个特定epoch下的模型去用0.1*lr_base继续训练,或者根据模型或任务的直觉做其他调整

优化

循环神经网络(recurrent nueral network)

参考文档

RNNs

其中 \[ a^1 = \sigma(W^0 x + V^1 a^1). \]

类别

RNNs

LSTM

LSTM

其中 \[ \begin{align} a_{t} &= f \odot a_{t-1} + i, \\ i &= \mathrm{linear\_sigmod} (h_{t-1} + x) \odot \mathrm{tanh} (h_{t-1} + x), \\ h_{t} &= \mathrm{linear\_sigmod} (h_{t-1} + x) \odot \mathrm{tanh} (a_{t}), \end{align} \] 即,遗忘门用于确定历史中的哪些信息需要保留,输入门用于确定当前输入中的哪些信息应当被添加进来,输出门用于更新该神经元的状态(包括激活值 a 和隐藏值 h )。

GRU