Deep Learning Basics: Neural Networks, Optimization, and Normalization

Hyacehila

在开始之前

深度学习在思考问题的方式上和传统机器学习是有差异的,我们在这里需要逐渐理解深度学习是怎么思考我们的问题的。

一个案例和我们对他的思考

一个基本流程

以视频的点击次数预测为例介绍下机器学习的运作过程。

我们的目标是找一个函数,该函数的输入是后台的信息,输出是隔天这个频道会有的总观看的次数.

第1步,我们需要找到一个模型(model),他本质是一个含有未知的参数的函数,比如最基础的一个线性函数

y=b+wx1y=b+wx_1

第 2 个步骤是定义损失(loss),损失也是一个函数。这个函数的输入是模型里面的参数,输出代表着某一组参数带来的模型是否足够Fit我们的真实情况。

一般情况下,损失的定义应该是预测的值和真实值之间的差异,比如MSE MAE,当然如果y,y^y,\hat{y} 都是概率分布,我们也会选择交叉熵(cross entropy)

接下来进入机器学习的第 3 步:解一个最优化的问题。把未知的参数找一个数值出来,看代哪一个数值进去可以让损失 L 的值最小。梯度下降(gradient descent)最优化导论中的最速下降法是经常会使用优化的方法。

损失函数看似是预测值和真实值构成的函数,实际上预测值是被模型参数和训练样本影响的,因此他是模型参数的函数。因此存在求梯度的空间

考虑Sigmod逼近

当然,纯粹的时候线性模型逼近,在复杂的现实世界中很可能并不好用,常用的方法是用一群Sigmod函数的和来逼近任意的分段线性函数,而分段线性函数基本可以逼近任意函数,此时Sigmoid 的数量也是一个超参数

Sigmod函数形式为

y=c11+e(b+wx1)y=c\frac1{1+e^{-(b+wx_1)}}

其中cc b wb~w 是未知参数

我们也可以简写为

y=cσ(b+wx1)y=c\sigma(b+wx_1)

其中σ\sigma 就代表 Sigmoid 函数

特别的,我们可以让 Sigmoid 函数有更多的自变量,或者说深度学习领域的特征,得到下面的Sigmoid 函数

y=cσ(b+w1x1+w2x2+w3x3+...)y=c\sigma(b+w_1x_1+w_2x_2+w_3x_3+...)

一般情况下,我们会用 θ\theta 来代指所有的需要优化的参数,其中Sigmoid的数量作为超参数而不参与优化,此时我们的损失函数记作 L(θ)L(\theta) 是多元函数,依旧可以用梯度下降进行最优化问题的求解

一点细节

实现上有个细节的问题,实际使用梯度下降的时候。会把 N 笔数据随机分成一个一个的批量(batch)。每个批量里面有 B 笔数据(称为batch size)。

本来是把所有的数据拿出来算一个损失,现在只拿一个批量里面的数据出来算一个损失。 每一个Batch我们都会更新一次参数

把所有的批量都看过一次,称为一个回合(epoch),每一次更新参数叫做一次更新。

ReLU

其实还可以对模型做更多的变形,我们原本考虑用Sigmod函数来逼近真实情况,实际上他也可以看作两个修正线性单元(Rectified Linear Unit,ReLU)的加总,他的公式为

cmax(0,b+wx1)c*\max(0,b+wx_1)

在机器学习里面,Sigmoid 或 ReLU 称为激活函数(activation function)。当然还有其他常见的激活函数,但 Sigmoid 跟 ReLU 是最常见的激活函数

深度学习

正如我们已经在 机器学习导论与监督学习中的神经网络里面介绍的结构一样,我们一般把Sigmoid 或 ReLU 称为神经元(neuron),很多神经元构成的网络结构称为神经网络(neural network) 每一排称为一个隐藏层(hidden layer) 很多的隐藏层就“深”,这套技术称为深度学习

实践方法论

这里是我们在真实的处理一个问题的时候,会遇到的问题以及一些处理问题的方法。

模型偏差

现实世界的数据代表的问题异常复杂,模型的结构过于简单,灵活性太低,导致拟合效果比较差

解决方法:增加模型的复杂灵活性,选择更复杂的传统模型或者深度学习模型,引入更多的特征

优化问题

梯度下降法非常常用,但是问题也很多,可能会卡在局部最小值的地方,因此产生的优化不足会导致拟合效果较差

模型偏差和优化不足都会导致拟合效果差,想要区分出两者的差异往往需要很多经验,如果小模型能做到比复杂模型更好的效果,那很可能是优化没有做到位。需要改进优化方法

过拟合

拟合效果好,泛化效果差,往往意味着过拟合,这往往是因为模型过于灵活,而训练数据覆盖情况不足导致的

处理过拟合有下面的思路

  • 增加训练数据,或者说依靠数据增强来获取更多的训练数据
  • 限制模型灵活性,核心在于减少需要优化的参数
  • 减少特征
  • 早停(early stopping)、正则化(regularization)丢弃法(dropout)

不匹配

不匹配是因为训练数据和测试数据的本质分布形式不同导致的预测严重错误

一般的过拟合可以用搜集更多的数据来克服,但是不匹配是指训练集跟测试集的分布不同,训练集再增加其实也没有帮助了

是否遇到不匹配的问题需要看研究人员对数据本身的理解,了解训练集跟测试集的产生方式,才可能判断是否有不匹配的问题

深度学习基础

本章介绍了深度学习常见的概念,这一章将是研究后面各种深度学习的基础知识

神经网络模型与深度学习的产生

机器学习导论与监督学习中的神经网络

全局最小与局部极小

全局最小值和局部最小值的问题是最优化问题中非常常见的一个部分;所有基于梯度实现搜索的算法都不能实现直接寻找全局最小

而基于梯度下降进行搜索是大部分神经网络的优化方法的常态,因此我们研究了一些补救策略

  • 多组不同参数值初始化多个神经网络,按标准方法训练后,取其中误差最小的解作为最终参数(也就是最优化中最常见的方法)
  • 使用 “模拟退火“ 模拟退火在每一步都以一定的概率接受比当前解更差的结果 也就是非最优解 有助于我们跳出局部最小
  • 使用随机梯度下降 它为梯度下降增加了随机因素 哪怕陷入局部最小也可以跳出
  • 遗传算法

局部极小值与鞍点

局部极小值与鞍点问题回应了我们在前文“实践方法论”里的优化问题中提出的一个常见问题:随着参数不断更新,训练的损失不会再下降, 但是我们对这个损失仍然不满意,深层网络没有做得更好也就是优化有问题

临界点及其种类

在以前的优化问题中,我们的基本猜想是优化到参数对损失的微分为零的时刻,此时基于梯度下降的算法就不能继续优化参数降低损失了,训练也就结束了

对于梯度为0的问题,我们最常见的位置是局部最小值点和局部最大值点,不过由于我们的下降方向选择,一般深度学习都会收敛到局部最小值点。

实际上,损失不是只在局部极小值的梯度是零,还有其他可能会让梯度是零的点,比如鞍点(saddle point)。鞍点其实就是梯度是零且区别于局部极小值和局部极大值的点,一个经典的例子是马鞍面的中心点。

当收敛到鞍点的时候,梯度下降算法不能帮助我们继续优化损失,但是周围明显是存在损失更低的点的,只要逃离鞍点,就能能让损失更低,而收敛到局部极小值点的时候,我们并没有什么好的降低损失方法,

判断临界值种类的方法

判断一个临界点到底是局部极小值还是鞍点需要知道损失函数的形状。可是怎么知道损失函数的形状?网络本身很复杂,用复杂网络算出来的损失函数显然也很复杂。

不过我们可以考虑对损失函数的某点进行泰勒展开得到局部的近似损失函数,在参数组θ\theta^{\prime} 附近展开有

L(θ)L(θ)+(θθ)Tg+12(θθ)TH(θθ).L(\boldsymbol{\theta}) \approx L\left(\boldsymbol{\theta}^{\prime}\right)+\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right)^{\mathrm{T}} \boldsymbol{g}+\frac{1}{2}\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right)^{\mathrm{T}} \boldsymbol{H}\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right) .

其中ggHH分别是梯度与海森矩阵(Hessian matrix) 存储一阶和二阶微分

在所有的梯度为0的点,近似结果变为

L(θ)L(θ)+12(θθ)TH(θθ).L(\boldsymbol{\theta}) \approx L\left(\boldsymbol{\theta}^{\prime}\right)+\frac{1}{2}\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right)^{\mathrm{T}} \boldsymbol{H}\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right) .

我们可以根据后面一部分(θθ)TH(θθ)\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right)^{\mathrm{T}} \boldsymbol{H}\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right) 来判断θ\theta^{\prime}附近的误差表面(error surface)的形状,因而确定这点究竟是损失函数的局部极小值、局部极大值,还是鞍点。

我们用vv代替θθ\theta - \theta^{\prime} 给出下面的结论有

  • 如果对所有 vv 都有 vTHv>0v^{T}Hv > 0 它是局部极小值
  • 如果对所有 vv 都有 vTHv<0v^{T}Hv < 0 它是局部极大值
  • 如果时而vTHv>0v^{T}Hv > 0 时而 vTHv<0v^{T}Hv < 0 则是鞍点

实际上,这一结论可以直接简化为用HH的特征值来判断,前面的三种情况分别对应于

  • HH的特征值全为正
  • HH的特征值全为负
  • HH的特征值有正有负

逃离鞍点的方法

我们首先来介绍一种简单的情况,我们需要优化的函数非常的简单,可以直接计算出明确的矩阵HH 那么我们只需要计划矩阵的特征值就可以判断是否处于鞍点。于此同时 HH 还指出了参数可以更新的方向 也就是矩阵HH的负特征向量的方向

从这个角度来看,鞍点似乎并没有那么可怕。但实际上,我们几乎不会真的把海森矩阵算出来,因为海森矩阵需要算二次微分,计算这个矩阵的运算量非常大,还要把它的特征值跟特征向量找出来,所以几乎没有人用这个方法来逃离鞍点。还有一些其他逃离鞍点的方法的运算量都比要算海森矩阵小很多。

现在我们介绍一条基于经验的规律:在训练一个参数量很大的神经网络的时候,局部极小值并没有那么常见。多数的时候,我们训练到一个梯度很小的地方,参数不再更新,往往只是遇到了鞍点

也就是说,局部最小值往往并不存在,我们不必逃离执着鞍点,专注于降低损失就足够了

批量和动量

我们在前面说过,在实践中损失的计算并不每次都使用全部数据 实现上仍然沿用前文提到的批量思想:训练时会把数据随机分成一个个 batch,每个 batch 计算一次损失并更新参数;所有 batch 都看过一次,称为一个 epoch。

实际上,在每一个回合开始之前,我们都会重新划分批量,也就是说,每个回合的批量的数据都不一样。以此来提升训练的效果避免可能的过拟合以及利用并行运算加速训练

批量大小对梯度下降法的影响

先看下两个最极端的情况

  • 不用批量,批量大小(batch size)为训练数据的大小,这种使用全批量(full batch)的数据来更新参数的方法即批量梯度下降法(Batch Gradient Descent,BGD)。此时模型必须把 20 笔训练数据都看完,才能够计算损失和梯度,参数才能够更新一次。
  • 批量大小等于 1,此时使用的方法即随机梯度下降法(Stochastic Gra-dient Descent,SGD),也称为增量梯度下降法。用一笔数据算出来的损失相对带有更多噪声,因此其更新的方向一般是是曲曲折折的 。

相比随机梯度下降,批量梯度下降每次更新更稳定、更准确。但是随机梯度下降的梯度上引入了随机噪声,因此在非凸优化问题中,其相比批量梯度下降更容易逃离局部最小值。

虽然我们刚才说进行批量梯度下降需要更大的迭代计算量,但是考虑到目前GPU提供的加速运算。大批量耗时不一定比小批量更长。

实际上 在 batch size 较小的时候,要“跑”完一个回合,花的时间是更大的。假设训练数据只有 60000 笔,批量大小设 1,要 60000 个更新才能“跑”完一个回合;如果批量大小等于 1000,60 个更新才能“跑”完一个回合,计算梯度的时间差不多。但60000 次更新跟 60 次更新比起来,其时间的差距量就非常大了。

实际上,小的批量也对测试有帮助。假设有一些方法(比如调大的批量的学习率)可以把大的批量跟小的批量训练得一样好。实验结果发现小的批量在测试的时候会是比较好的 目前的解释是小批量带来的随机性帮助我们跳出了一些损失函数剧烈变换的峡谷,进入了一个损失的“盆地”

动量法

动量法(momentum method)是另外一个可以对抗鞍点或局部最小值的方法。

我们的思想是,在传统的梯度下降方法中,我们根据当前点的梯度决定下一步优化的方向,而动量法不是只往梯度的反方向来移动参数,而是根据梯度的反方向加上前一步移动的方向决定移动方向。我们认为这种移动的 “惯性” 能让我们脱开鞍点和局部最小的影响,从而进入损失的“盆地”

我们用mm表示移动 gg表示梯度,就可以给出动量法的下降结构,其中 μ\mu 是学习率,λ\lambda 是前一个方向的权重参数

m0=0m1=ηg0m2=ληg0ηg1...\begin{array}{l} \boldsymbol{m}_{0}=0 \\ \boldsymbol{m}_{1}=-\eta \boldsymbol{g}_{0} \\ \boldsymbol{m}_{2}=-\lambda \eta \boldsymbol{g}_{0}-\eta \boldsymbol{g}_{1} \\ ... \end{array}

自适应学习率

关于自适应学习率

临界点其实不一定是在训练一个网络的时候会遇到的最大的障碍。

有时候我们发现,损失函数的梯度(用其范数衡量)依旧很大,但是损失并不继续下降,我们没有困在某些梯度为0的点,但是无法继续更新参数降低损失了。这往往是因为学习率较大,导致我们卡在某个点。也就是参数在一个 “山谷” 的两端不断循环,但无法进入“谷底”

这和一般的训练情况并不相符,由于深度学习模型参数非常多,往往会在梯度还很大的时候,损失就已经降了下去,我们不需要到小梯度点就可以停止训练了。

我们可以试着把学习率设小一点,学习率决定了更新参数的时候的步伐,学习率设太大,步伐太大就无法慢慢地滑到山谷里面。 但是,并不是处处都是山谷,过小的学习率一般无法再让训练前进。

综上,在梯度下降里面,所有的参数都是设同样的学习率,这显然是不够的,应该要为每一个参数定制化学习率,即引入自适应学习率(adaptive learning rate) 的方法,给每一个参数不同的学习率。

如果在某一个方向上,梯度的值很小,非常平坦,我们会希望学习率调大一点;如果在某一个方向上非常陡峭,坡度很大,我们会希望学习率可以设得小一点。

AdaGrad

AdaGrad(Adaptive Gradient)是典型的自适应学习率方法,其能够根据梯度大小自动调整学习率。AdaGrad 可以做到梯度比较大的时候,学习率就减小,梯度比较小的时候,学习率就放大。

这是我们传统的参数更新方式

θt+1iθtiηgti\boldsymbol{\theta}_{t+1}^{i}\leftarrow\boldsymbol{\theta}_{t}^{i}-\eta\boldsymbol{g}_{t}^{i}

现在要有一个随着参数定制化的学习率,即把原来学习率η\eta变成ησti\frac\eta{\sigma_t^i}

θt+1iθtiησtigti\theta_{t+1}^i\leftarrow\theta_t^i-\frac\eta{\sigma_t^i}\boldsymbol{g}_t^i

这个学习率同时随着参数ii和迭代次数tt变换 现在我们的学习率变得参数相关(parameter dependent)

参数相关的一个常见的类型是算梯度的均方根(root mean square)。参数的更新过程为

θ1iθ0iησ0ig0i\theta_1^i\leftarrow\theta_0^i-\frac\eta{\sigma_0^i}\boldsymbol{g}_0^i

其中θ0i\boldsymbol{\theta}_0^i是初始化参数。而σ0i\sigma_0^i的计算过程为

σ0i=(g0i)2=g0i\sigma_0^i=\sqrt{\left(\boldsymbol{g}_0^i\right)^2}=\begin{vmatrix}\boldsymbol{g}_0^i\end{vmatrix}

其中g0ig_0^i是梯度。将σ0i\sigma_0^i的值代入更新的公式可知g0iσ0i\frac{g_0^i}{\sigma_0^i}的值是+1或-1。第一次在更新参数, 从θ0i\boldsymbol{\theta}_0^i更新到θ1i\boldsymbol{\theta}_1^i的时候,要么是加上η\eta,要么是减掉η\eta,跟梯度的大小无关,这个是第一步的情况。

第二次更新参数过程为

θ2iθ1iησ1ig1i\boldsymbol{\theta}_{2}^{i}\leftarrow\boldsymbol{\theta}_{1}^{i}-\frac{\eta}{\sigma_{1}^{i}}\boldsymbol{g}_{1}^{i}

其中

σ1i=12[(g0i)2+(g1i)2]\sigma_1^i=\sqrt{\frac{1}{2}\left[\left(\boldsymbol{g}_0^i\right)^2+\left(\boldsymbol{g}_1^i\right)^2\right]}

梯度的均方根用于约束学习率μ\mu 这就是这个方法名字的由来

同样的操作可以一直重复下去

θt+1iθtiησtigtiσti=1t+1i=0t(gti)2\boldsymbol{\theta}_{t+1}^i\leftarrow\boldsymbol{\theta}_t^i-\frac\eta{\sigma_t^i}\boldsymbol{g}_t^i\quad\sigma_t^i=\sqrt{\frac1{t+1}\sum_{i=0}^t\left(\boldsymbol{g}_t^i\right)^2}

这样的算法实现了:梯度较大的时候自适应降低学习率,梯度较小的时候增加学习率,保证了每次整体优化的步长有限

RMSProp

同一个参数需要的学习率,也会随着时间而改变。RMSProp(Root Mean Squared propagation)就是为了处理这个问题。他让不同时间算出的梯度有着不一样的重要性

RMSprop 没有论文,Geoffrey Hinton 在 Coursera 上开过深度学习的课程,他在他的课程里面讲了 RMSprop,如果要引用,需要引用对应视频的链接。

RMSprop 第一步跟 Adagrad 的方法是相同的,即

σ0i=(g0i)2=g0i\sigma_0^i=\sqrt{\left(\boldsymbol{g}_0^i\right)^2}=\begin{vmatrix}\boldsymbol{g}_0^i\end{vmatrix}

第二步更新过程为

θ2iθ1iησ1ig1iσ1i=α(σ0i)2+(1α)(g1i)2\boldsymbol{\theta}_2^i\leftarrow\boldsymbol{\theta}_1^i-\frac\eta{\sigma_1^i}\boldsymbol{g}_1^i\quad\sigma_1^i=\sqrt{\alpha\left(\sigma_0^i\right)^2+\left(1-\alpha\right)\left(\boldsymbol{g}_1^i\right)^2}

其中α\alpha 是一个 0-1 之间的超参数。在 RMSprop 里面,可以自己调整现在的新算出梯度的重要性。α\alpha 越小 新算出的梯度就越重要。

后面的若干步骤为

θt+1iθtiησtigtiσti=α(σt1i)2+(1α)(gti)2\boldsymbol{\theta}_{t+\mathbf{1}}^i\leftarrow\boldsymbol{\theta}_t^i-\frac\eta{\sigma_t^i}\boldsymbol{g}_t^i\quad\sigma_t^i=\sqrt{\alpha\left(\sigma_{t-1}^i\right)^2+\left(1-\alpha\right)\left(\boldsymbol{g}_t^i\right)^2}

RMSprop 可以很快地“踩刹车”。根据最新的梯度情况尽快调整学习率,无论是增大还是缩写

Adam

最常用的优化的策略或者优化器(optimizer)是Adam(Adaptive moment estimation)Adam 可以看作 RMSprop 加上动量,其使用动量作为参数更新方向,并且能够自适应调整学习率。

PyTorch 里面已经写好了 Adam 优化器,这个优化器里面有一些超参数需要人为决定,但是往往用 PyTorch 预设的参数就足够好了。

学习率调度

我们前面的学习率调整算法都无法避免一个问题,各个方向的学习率都要被前面的若干次梯度影响,虽然RMSProb算法强化了目前梯度的作用,但是将学习率调整到合适的程度仍旧需要时间,这些计算时间和耗费的计算资源实际上全部浪费掉了

通过学习率调度 (learning rate scheduling) 可以解决这个问题。之前的学习率调整方法中η\eta是一个固定的值,而在学习率调度中η\eta跟时间有关,如下所示。

θt+1iθtiηtσtigti\boldsymbol{\theta}_{t+1}^i\leftarrow\boldsymbol{\theta}_t^i-\frac{\eta_t}{\sigma_t^i}\boldsymbol{g}_t^i

学习率调度中最常见的策略是学习率衰减(learning rate decay),也称为学习率退火(learning rate annealing)。随着参数的不断更新,让η\eta越来越小,帮助我们在后面的精细优化部分更快的到达目标精度

除了学习率下降以外,还有另外一个经典的学习率调度的方式———预热。预热的方法是让学习率先变大后变小,至于变到多大、变大的速度、变小的速度是超参数。残差网络,BERT 和 Transformer 的训练都使用了预热。

关于预热的核心 当我们使用 Adam、RMSprop 或 AdaGrad 时,需要计算σ\sigma。而σ\sigma是一个统计的结果。从σ\sigma可知某一个方向的陡峭程度。统计的结果需要足够多的数据才精准,一开始统计结果σ\sigma是不精准的。一开始学习率比较小是用来探索收集一些有关误差表面的情报,先收集有关σ\sigma的统计数据,等σ\sigma统计得比较精准以后,再让学习率慢慢爬升。

预热可参考 Adam 的进阶版———RAdam

优化总结

经过前面多个小节的内容,我们终于算是把优化问题介绍的基本清楚了,我们从最原始的梯度下降,进化到这一个版本。

θt+1iθtiηtσtimti\boldsymbol{\theta}_{t+1}^{i}\leftarrow\boldsymbol{\theta}_{t}^{i}-\frac{\eta_{t}}{\sigma_{t}^{i}}\boldsymbol{m}_{t}^{i}

这个版本里面有动量mtim_{t}^{i} 前文“批量和动量”部分,其不是顺着某个时刻算出的梯度方向来更新参数,而是把过去所有算出梯度的方向做一个加权总和当作更新的方向。

然后使用自适应学习率方法帮助我们进行优化,并且加入了学习率调度

这个是目前优化的完整的版本,这种优化器除了 Adam 以外,还有各种变形。不过他们都使用不同的方式修正动量 自适应学习率 以及进行 学习率调度

特别的,动量法和自适应学习率都考虑过去的梯度,但是动量法还考虑他们的方向,而自适应学习率更加侧重于研究其大小

分类

分类与回归是深度学习最常见的两种问题。虽然我们在机器学习中已经介绍过他们的区别了,不过在深度学习中由于需要选择激活函数和损失函数进行优化,整体的思路会有所不同,这里进行介绍

当我们进行一个分类问题的时候,一般使用 类似 One-hot 编码之类的方式将 target 改为一个向量,那最基本的思想就是将网络扩展为原来的三倍,将输出一个数值的方法重复三遍。

带有 softmax 的分类

很显然,前面的方法听起来不像是一个很好的方法,毕竟原来的ReLU和Sigmod函数都无法保证输出 0 1 的二值变量。因此我们一般会选择将网络输出的数进行一层 Softmax 计算公式如下

yi=exp(yi)jexp(yi)y_i'=\frac{\exp(y_i)}{\sum_j\exp(y_i)}

我们首先指数化将原始的数据变为正的,然后归一化让大的值跟小的值的差距更大,得到 0-1 之间的变量

只有两个类的时候,sigmoid 和 softmax 是等价的(我们在Logistic 回归中实际上就是在使用sigmoid)。不过二分类问题在深度学习中研究的比较少,因此Softmax更为常用

分类损失

现在我们来看看分类问题如何设置一个合适的损失函数。

在进行Softmax变换以后的数实际上已经很接近0-1了,直接使用MSE和MAE等传统指标实际上就是可行的,不过交叉熵损失在分类问题中更常用,如下,其中yiy_i是真实值 yiy_i^{\prime} 是经过Softmax变换的预测值

e=iyilnyie=-\sum_iy_i\ln y_i^{\prime}

最小化交叉熵其实就是最大化似然(maximize likelihood)

在分类问题中,选择交叉熵作为损失函数有利于我们优化过程的进行。选均方误差的时候,如果没有好的优化器,有非常大的可能性会训练不起来,那么选择Adam之类的优化器自动调大学习率,也会优化的更困难一些

批量归一化

归一化思想

如果误差表面很崎岖,它比较难训练。能不能直接改误差表面的地貌,“把山铲平”,让它变得比较好训练呢?批量归一化(Batch Normalization BN)就是其中一个“把山铲平”的想法。

现在让我们来思考一个偏向本质的问题,难以训练的误差表面究竟是怎么产生的。崎岖的误差表面本质上是小的参数扰动导致大的误差变动,而我们在传统统计学就处理这样的问题——归一化 大小相差悬殊的自变量在线性回归模型中产生大小相差悬殊的回归系数。后面我们要讨论的就是 特征归一化(feature normalization)

考虑深度学习

由于深度学习模型是一个层网状结构,他的归一化也自然的产生了一些其他问题。

虽然我们已经对训练的初始数据xx进行了归一化,但是在通过一层网络W1W_1以后,我们得到的zz没有进行归一化,这会导致训练下一层W2W_2的时候产生一些困难。

因此我们建议对zz再次进行归一化(归一化形式的选择取决于激活函数),一般习惯将归一化放到激活函数之前,然后把激活函数激活后的结果再传给网络的下一层

特别的:训练是以batch进行的,因此进行的归一化也是batch范围的归一化,这意味着我们需要一个稍大的 batch size 保证分布的近似性

在做批量归一化的时候,往往还会做如下操作:

z^i=γz~i+β\hat{\boldsymbol{z}}^i=\gamma\odot\tilde{\boldsymbol{z}}^i+\beta

其中,\odot代表逐元素的相乘。β,γ\beta,\boldsymbol{\gamma} 可以想成是网络的参数,需要另外再被学习出来。

为什么要加上β\boldsymbol{\beta}γ\boldsymbol{\gamma}呢?

如果做归一化以后,z~\tilde{\boldsymbol{z}}的平均值一定是0,如果平均值是0的话,这会给网络一些限制,这个限制可能会带来负面的影响,所以需要把β,γ\beta,\boldsymbol{\gamma}加回去,让网络隐藏层的输出平均值不是0。让网络学习β,γ\boldsymbol{\beta},\boldsymbol{\gamma}来调整一下输出的分布,从而来调整z^\hat{\boldsymbol{z}}的分布。

批量归一化是为了要让每一个不同的维度的范围相同,如果把γ\gammaβ\beta加进去,这样不同维度的分布,其范围不会又都不一样了吗?

有可能,但是实际上在训练的时候,γ\boldsymbol{\gamma}的初始值都设为1,所以γ\boldsymbol{\gamma}值都为 1 的向量。β\beta是值全部都是0的向量,即零向量。所以让网络在一开始训练的时候,每一个维度的分布,是比较接近的,也许训练到后来,已经训练够长的一段时间,已经找到一个比较好的误差表面,走到一个比较好的地方以后,再把γ,β\gamma,\beta慢慢地加进去,所以加了γ,β\gamma,\beta的批量归一化,往往对训练是有帮助的。

测试时的批量归一化

以上说的都是训练的部分,测试有时候又称为推断(inference)。批量归一化在测试的时候,会有什么样的问题呢?在测试的时候,我们一次会得到所有的测试数据,继续分批量进行就不合适了 。

实际上,批量归一化在测试的时候,并不需要做什么特别的处理,PyTorch 已经处理好了。在训练的时候,如果有在做批量归一化,每一个批量计算出来的μ,σ\mu,\sigma ,都会拿出来算移动平均 (moving average)。假设现在有各个批量计算出来的μ1,μ2,μ3,,μt\boldsymbol{\mu}^1,\boldsymbol{\mu}^2,\boldsymbol{\mu}^3,\cdots\cdots,\boldsymbol{\mu}^t,则可以计算移动平均

μˉpμˉ+(1p)μt\bar{\boldsymbol{\mu}}\leftarrow p\bar{\boldsymbol{\mu}}+(1-p)\boldsymbol{\mu}^t

其中,μˉ\bar{\boldsymbol{\mu}}μ\boldsymbol{\mu}的个平均值,pp是因子,这也是一个常数,这也是一个超参数,也是需要调的那种。

在 PyTorch 里面,pp设 0.1。计算滑动平均来更新μ\mu的平均值。最后在测试的时候,就不用算批量里面的μ\muσ\sigma了。因为测试的时候,在真正应用上也没有批量,就可以就直接拿μˉ\bar{\mu}σˉ\bar{\boldsymbol{\sigma}} ,也就是μ,σ\boldsymbol{\mu},\boldsymbol{\sigma}在训练的时候,得到的移动平均来取代原来的μ\boldsymbol{\mu}σ\boldsymbol{\sigma}, 这就是批量归一化在测试的时候的运作方式。

批量归一化的作用

有论文可以支持我们前面介绍的核心 批量归一化可以改误差表面的地貌,让他更加平滑 因此我们可以选择更大的学习率(原始是五倍 十倍甚至更多) 从而增加训练的效率

虽然文献认为最后的训练的收敛位置是一样的,但是训练效率的提升可以节约训练的资源,节约时间,因此批量归一化还是很有意义的。

当然,批量归一化不是唯一的归一化,还有很多的归一化方法,他们在结果上都是在处理误差表面情况,不过具体的思想差距很大,大部分都是文章作者的一种偶然发现。

神经网络,多层感知机MLP,深度学习

神经元模型

神经网络是由具有适应性的简单单元组成的广泛并行互连的网络,它的组织能够模拟生物神经系统对真实世界物体所作出的交互反应;这是我们此时给出的对神经网络的定义.

事实上 神经网络并不是机器学习领域的分支 它在生物学的研究的基础要比机器学习早得多;我们这里介绍的神经网络是神经网络学习 是机器学习和神经网络交叉的产物,也是深度学习在产生之前的基础理论.

神经网络中最基本的成分是神经元(neuron)模型;它接受多个相连神经元发出的数据 并且给出输出; 最基本的神经元模型是MP神经元模型. 神经元接收到来自几个其他神经元传递过来的输入信号,这些输入信号通过带权重的连接(connection)进行传递,神经元接收到的总输入值将与神经元的阈值进行比较,然后通过“激活函数”(activation function)处理以产生神经元的输出.

把许多个这样的神经元按一定的层次结构连接起来,就得到了神经网络. 在数学上,神经网络就是一个包含的非常多参数的数学模型,接受输入,给出输出.

感知机与多层网络

感知机(Perceptron)由两层神经元组成,输入层接收外界输入信号后传递给输出层,输出层是则是一个神经元. 选定合适的激活函数 给定训练数据集 我们就可以学习得到阈值和权重,训练后的模型就可以用于其他工作.

需注意的是 ,感知机只有输出层神经元进行激活函数处理 ,即只拥有一层功能神经元 (functional neuron),其学习能力非常有限 事实上 从数学角度可以证明 感知机无法处理任何非线性可分的问题.

要解决非线性可分问题,需考虑使用多层功能神经元 在输入神经元和输出神经元中增加中间层 它也有着激活函数 可以在训练中寻找合适的权重.

最经典的多层神经网络结构的每层神经元与下一层神经元全互连,神经元之间不存在同层连接,也不存在跨层连接. 这样的神经网络结构通常称为“多层前馈神经网络“ (multi-layer feedforward neuralnetworks , MLP) 也是我们最基本的一种神经网络结构.

误差逆传播算法(BP)

多层网络的学习能力比单层感知机强得多 训练它也是一个问题 下面我们来介绍神经网络的学习算法 误差逆传播(error BackPropagation,简称BP ) 算法就是其中最杰出的代表.

在现实中 大多数的神经网络都由BP算法进行训练 它不仅仅可以用于我们前面介绍的多层前馈神经网络 还可以用于很多神经网络的训练 不过BP神经网络一般特指由BP算法训练的多层前馈神经网络(MLP).

现在 我们详细介绍BP算法 给定训练数据集DD 每一个样本包括dd个输入和ll个输出 因此 我们建立一个拥有dd 个输入神经元, ll个输出神经元、qq个隐层神经元的多层前馈网络结构 激活函数全部选取为Sigmod函数

对于任意一个训练例 网络导致的均方误差为

Ek=12j=1l(y^jkyjk)2.E_k=\frac{1}{2}\sum_{j=1}^{l}(\hat{y}_j^k-y_j^k)^2.

我们需要学习的参数有 输入层到隐藏层的d×qd\times q 个权值 隐藏层到输出层的q×lq\times l 个权值 q+lq+l个神经元的阈值 想要一次优化这么多参数显然不太现实 BP 是一个迭代学习算法,在迭代的每一轮中采用广义的感知机学习规则对参数进行更新估计 每个参数的更新形式如下

vv+Δv.v\leftarrow v+\Delta v.

BP 算法基于梯度下降(gradient descent)策略,以目标的负梯度方向对参数进行调整 我们随便取一个隐藏层到输出层的连接权作为例子来推导 给定误差EkE_k 学习率η\eta

Δwhj=ηEkwhj\Delta w_{hj}=-\eta\frac{\partial E_{k}}{\partial w_{hj}}

根据影响的连接链条 我们可以给出

Ekwhj=Eky^jky^jkβjβjwhj\frac{\partial E_{k}}{\partial w_{hj}}=\frac{\partial E_{k}}{\partial\hat{y}_{j}^{k}}\cdot\frac{\partial\hat{y}_{j}^{k}}{\partial\beta_{j}}\cdot\frac{\partial\beta_{j}}{\partial w_{hj}}

因此 有

βjwhj=bh.\frac{\partial\beta_{j}}{\partial w_{hj}}=b_{h}. gj=Eky^jky^jkβj=(y^jkyjk)f(βjθj)=y^jk(1y^jk)(yjky^jk).\begin{aligned} g_{j}& =-\frac{\partial E_{k}}{\partial\hat{y}_{j}^{k}}\cdot\frac{\partial\hat{y}_{j}^{k}}{\partial\beta_{j}} \\ &=-(\hat{y}_{j}^{k}-y_{j}^{k})f^{\prime}(\beta_{j}-\theta_{j}) \\ &=\hat{y}_{j}^{k}(1-\hat{y}_{j}^{k})(y_{j}^{k}-\hat{y}_{j}^{k}). \end{aligned}

我们就能给出BP算法权重的更新公式为

Δwhj=ηgjbh.\Delta w_{hj}=\eta g_{j}b_{h}.

同理我们可以给出其他参数的更新公式;

学习率控制着算法迭代的更新步长 过大容易产生振荡 过小容易导致太慢的收敛速度 选择合适的学习率是训练中一个值得考虑的问题;

前面的算法介绍基于一个样例的最小化MSE实现 如果我们一次读取全部的训练数据集 就可以优化全局MSE 但是这样会拖慢训练速度 尤其是在训练数据集太大的时候,实际上对于NN化的模型,我们一般采用分批训练的方式

有数学理论证明:只需一个包含足够多神经元的隐层,多层前馈网络就能以任意精度逼近任意复杂度的连续函数.然而,如何设置隐层神经元的个数仍是个未决问题,实际应用中通常靠“试错法”(trial-by-error)调整

由于其强大的表示能力,BP 神经网络经常遭遇过拟合;我们要么划分训练和测试集 在测试集误差没有继续降低就终止训练 要么增加正则项 在目标函数中惩罚网络复杂度 后者的一个简单例子为

E=λ1mk=1mEk+(1λ)iwi2,E=\lambda\frac1m\sum_{k=1}^{m}E_{k}+(1-\lambda)\sum_{i}w_{i}^{2},

深度学习

理论上来说,参数越多的模型复杂度越高、 “容量”(capacity)越大,这意味着它能完成更复杂的学习任务.但一般情形下,复杂模型的训练效率低,易陷入过拟合,因此难以受到人们青睐.

而随着云计算、大数据时代的到来,计算能力的大幅提高可缓解训练低效性,训练数据的大幅增加则可降低过拟合风险,因此,以 “深度学习”(deep learning)为代表的复杂模型开始受到人们的关注.

典型的深度学习模型就是很深层的神经网络;从增加模型复杂度的角度来看,增加隐层的数目显然比增加隐层神经元的数目更有效, 因为增加隐层数不仅增加了拥有激活函数的神经元数目,还增加了激活函数嵌套的层数.

下面我们用两个小节来简单的讨论一下深度学习领域的两个小问题,他们将是我们在展开关于深度学习的详细介绍的基础.

全连接前馈神经网络FFN

全连接是表示参数是密集连接的,所谓的前馈神经网络(FeedForward network,FFN)是两个线形变换层中间夹一层激活层组成的.

FeedForward通过线性变换和非线性激活函数,先将数据映射到高纬度的空间再映射到低纬度的空间,提取了更深层次的特征;通过激活函数引入非线性变换,增强模型对复杂模式的拟合能力。

全连接前馈神经网络FFN,在结构上和多层感知机MLP其实是同样的形成逻辑,但是FFN更加强调这是一个大型网络中的Module,是用于增强模型最复杂模式拟合能力的Module,而MLP则强调我们使用了一个单独的网络.

在Transformer模型中,FFN层占据了绝大部分的参数.注意力层的QKVO矩阵反而没有占据那么多参数量,虽然他们是注意力机制的核心. 这其实体现了一个关键的问题,FFN由于需要向高维空间映射,实际上会带来很大的参数消耗.

残差连接

ResNet(残差神经网络)在深度学习历史上是里程碑式的模型,在ResNet之前深度学习的模型一般都是在20-30层之间,但是在ResNet出现之后,把深度学习的模型层数提高到一百层以上,甚至可以向1000层扩展.

ResNet主要解决深层网络的退化问题,并不是为了解决过拟合的问题,也不是为了解决梯度消失和梯度爆炸的问题,这个问题已经可以通过批量归一化得到解决,而是当你的网络层次进一步加深以后,它的表现还不如浅层网络,这就叫做退化(Degradation)

与传统的机器学习相比,深度学习的关键特征在于网络层数更深、非线性转换(激活)、自动特征提取和特征转换。非线性转换是关键目标,它将数据映射到高纬空间以便于更好的完成“数据分类”。随着网络深度的不断增大,所引入的激活函数也越来越多,数据被映射到更加离散的空间,此时已经难以让数据回到原点.

残差网络的核心思想是:每个附加层都应该更容易地包含原始函数作为其元素之一。如果我们能将新添加的层训练成恒等映射,那么就不会发生退化的现象,与此同时新层可以带来新的识别可能,从而带来更好的效果.

残差连接从实现来看其实是非常简单的,原本的神经网络接受xx作为输入,经过多层的全连接与激活(比如经过一个FFN)后,输出F(x)F(x),那么我们后面的反向传播的过程就是拟合这个函数FF.

在使用残差连接后,我们的输出变为了F(x)+xF(x)+x 交给后面的激活等网络结构处理. 为了保证残差连接的维度,我们需要引入一些宽度为1的卷积层.在残差块(使用了残差连接的一个最小Module)中,输入可通过跨层数据线路更快地向前传播。从而带来更好的性能.

关于深度学习Essence的问题

深度学习DL都是过参数化的,也就是 Over- Parametrization 的,但是依旧能够成功,违背了原始机器学习的一些理论假设,暂时还缺乏新的理论解释,

深度学习的优化是一个明显的非凸优化问题 (non convex) 但是他使用很简单的优化方法(Adam,SGD等只利用梯度的方法) 往往在实践中可以收敛到很好的结果.

这与现在的随机化初始技术(避免了局部无法跳出),神经网络过参数化(超高维空间避免了局部最优可能),梯度消失(ReLU,Adam,Resnet,Batch Normal,Layer Normal共同解决了). 鞍点(SGD解决了) Loss surface 在这些原因影响下,让一阶优化方法就可以找到近乎全局的最优

  • Title: Deep Learning Basics: Neural Networks, Optimization, and Normalization
  • Author: Hyacehila
  • Created at : 2024-09-02 14:28:33
  • Link: https://hyacehila.github.io//blog/2024/09/02/deep-learning-basics/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments