拉格朗日对偶问题与神经网络

对于一个无约束优化问题,如果目标函数是一个凸函数(或凹函数),那么我们只需要求得梯度为0的点即可,极大似然估计其实就是一个凸优化的问题。

对于一个约束优化问题,如果目标函数和不等式约束函数都是凸函数,且等式约束为线性函数,那么KKT点就是原问题的极值点。

我们先来看一个约束优化问题(这里设定目标函数和不等式约束都是凸函数)

min f(x,y)

s.t.  y≤g(x)

该问题的拉格朗日函数为

L(x,y,λ)=f(x,y)+λ(y-g(x))

令t(x,y)=y-g(x)

KKT点就是:∇f(x,y)+λ∇t(x,y)=0,即为

上图中一圈一圈的圆是目标函数f(x,y)的等值线,中心点为函数值的最小点;红色的曲线为不等式约束y-g(x)≤0的部分,向左上是大于0的部分,右下是小于0的部分,红线本身是等于0的部分,那么我们知道右下和曲线本身的部分才是满足不等式约束的。带箭头的直线是梯度方向,蓝色的是目标函数各个点的梯度方向,红色的是不等式约束函数的梯度方向。

虽然圆的中心点最小,但它不在不等式满足的范围内,我们要的是在满足不等式的范围内找最小。这里只有当目标函数的梯度与不等式约束函数的梯度方向相反的时候才是原问题的极值点。也就是KKT条件中的∇f(x,y)+λ∇t(x,y)=0,只有它们方向相反的时候才可能相加为0,而其他的情况都不可能。虽然两个梯度的方向相反,但是大小却未必相同,所以λ是一个调节因子,调节到它们的大小刚好相等,这样才能相加刚好为0。而且λ也必须大于等于0,否则就会把不等式约束函数的梯度方向调节到与目标函数的梯度相同的方向,这样也无法等于0了。

第二个约束优化问题(目标函数和不等式约束都是凸函数)

min f(x),x\(R^n\)

s.t.  \(a_i^T\)x+\(b_i\)≤0,i=1,...,m

该问题的拉格朗日函数为

L(x,λ)=f(x)+\(\sum_{i=1}^m\)\(λ_i\)( \(a_i^T\)x+\(b_i\) )

之前的问题是一个单条件约束,这里是一个多条件约束,那么我们就不能只找一个条件约束的梯度来匹配目标函数的梯度

\(g_i\)(x)=  \(a_i^T\)x+\(b_i\) ,i=1,...,m

KKT点为:∇f(x)+\(\sum_{i=1}^m\)\(λ_i\)\(g_i\)(x)=0

这意味着我们需要将所有的条件约束的梯度结合起来与目标函数的梯度做一个方向相反的整合。

上图中的圆与之前一样,约束条件都是线性的,这里以m=5为例来说明,它们的约束范围为橙色的部分。我们可以看到圆的中心依然不在约束范围内,在约束范围内找最小,就是\(x^*\)这个点。\(x^*\)只属于\(g_α(x)\)\(g_β(x)\)这两条直线上,其他的三条直线是不起作用的。那么α,β属于有效指标集I,则\(g_α(x^*)\)=\(g_β(x^*)\)=0(见凸优化整理(三) 中的与切锥关系密切的两个集合),且∇f(\(x^*\))+\(λ_α\)\(g_α(x^*)\)+\(λ_β\)\(g_β(x^*)\)=0。虽然 \(g_α(x^*)\)=\(g_β(x^*)\)=0,但它们的梯度并不为0,见上图中两个橙色箭头的直线,蓝色箭头的直线为目标函数的梯度方向。那么两个约束条件函数的梯度方向经过\(λ_α\)\(λ_β\)的调节后通过平行四边形法则刚好与目标函数的梯度大小相等,方向相反,这里的\(λ_α\)\(λ_β\)都必须大于0。其他三个约束条件由于不起作用,\(g_i\)(\(x^*\))<0,iα,β,则它们对应的\(λ_i\)=0,iα,β,因为它们要满足KKT的互补松弛条件\(λ_i\) \(g_i\)(\(x^*\))=0。

这三个不起作用的约束条件函数的梯度从上图中可以看到,它们两两的交点梯度和都跟目标函数的梯度同向,不可能构成相反的方向达到相加为0的效果,所以它们的调节因子\(λ_i\)只能调节到0,以满足KKT条件∇f(x)+\(\sum_{i=1}^m\)\(λ_i\)\(g_i\)(x)=0。结合以上两点,所以必须满足\(λ_i\)≥0,如果\(λ_i\)=0,那么对应的约束条件\(g_i\)(x)是松弛的;如果\(λ_i\)>0,那么对应的约束条件 \(g_i\)(x)是紧致的。

当然如果上图的圆心就在约束范围内,那么所有的约束条件都是松弛的,因为最小点就在圆心上。

以上都是针对凸优化的,如果原问题的目标函数或者约束条件函数不是凸函数,那么我们就无法简单的获得它的最优解,此时我们需要将原问题转化成对偶问题,因为无论原问题是什么,它的对偶问题一定是一个凸问题,且原问题的解v(P)一定大于等于对偶问题的解v(D)。当然在强对偶的情况下,v(P)=v(D),但是强对偶的原问题P依然是凸问题,还需要满足Slater 条件。

从交叉熵说起

我们知道信息量的定义为(见信息论整理 中的信源不确定度的定义)

I(\(x_i\))=-log p(\(x_i\))

其中\(x_i\)表示一个整个事件X中可能发生的一个小事件,\(x_i\)∈X;I表示该事件\(x_i\)的信息量,p(\(x_i\))表示该事件发生占整个事件X的概率。

熵的定义为(见信息论整理 中的离散信源的熵)

H(X)=-\(\sum_{i=1}^n\)p(\(x_i\))log p(\(x_i\))

熵表示所有信息量的期望,亦代表信息总量的状态。n表示整个事件中会有多少种可能发生的小事件。总体就是每一个信息的信息量乘以它们发生的概率,再汇总

相对熵(KL散度)

同一个随机变量 x 有两个单独的概率分布 P(x) 和 Q(x),我们可以使用 KL 散度(Kullback-Leibler (KL) divergence)来衡量这两个分布的差异。

在机器学习中,P往往用来表示样本的真实分布,比如[1,0,0]表示当前样本属于第一类。Q用来表示模型所预测的分布,比如[0.7,0.2,0.1]。直观的理解就是如果用P来描述样本,那么就非常完美。而用Q来描述样本,虽然可以大致描述,但是不是那么的完美,信息量不足,需要额外的一些“信息增量”才能达到和P一样完美的描述。如果我们的Q通过反复训练,也能完美的描述样本,那么就不再需要额外的“信息增量”,Q等价于P。计算公式为

\(D_{KL}\)(p||q)=\(\sum_{i=1}^n\)p(\(x_i\))log \(p(x_i)\over q(x_i)\)

其中,n为事件的所有可能性。\(D_{KL}\)的值越小,表示q分布和p分布越接近。

交叉熵

对KL散度的公式进行变形,可得

\(D_{KL}\)(p||q)=\(\sum_{i=1}^n\)p(\(x_i\))log \(p(x_i)\over q(x_i)\)=\(\sum_{i=1}^n\)p(\(x_i\))log p(\(x_i\))- \(\sum_{i=1}^n\)p(\(x_i\))log q(\(x_i\))=-H(p(X))+(- \(\sum_{i=1}^n\)p(\(x_i\))log q(\(x_i\)))

等式的前一部分恰巧就是p的熵,等式的后一部分,就是交叉熵

H(p,q)= - \(\sum_{i=1}^n\)p(\(x_i\))log q(\(x_i\))

在机器学习中,我们需要评估label和predicts之间的差距,使用KL散度刚刚好,即\(D_{KL}\)(y||y^),由于KL散度中的前一部分−H(y)不变,故在优化过程中,只需要关注交叉熵就可以了。所以一般在机器学习中直接用用交叉熵做loss,评估模型。

设计损失函数的方法

最小二乘法

最小二乘法其实就是它字面意思(这里可以参考机器学习算法整理 中的简单线性回归)

min \(1\over 2\)\(\sum_{i=1}^n\)\((x_i-y_i)^2\)

最小化误差的平方,平方就是乘两次。它满足处处可微,且是一个凸函数。\(x_i\)是真实值,\(y_i\)是模型预测值;\(1\over 2\)是为了求导,化解掉平方。它是最简单最直观构建损失函数的方法,但是它有一个很大的缺陷。

如果样本分布不是线性的,那么我们的拟合函数也必然不是线性的,那么将无法使用最小二乘法。

极大似然估计法

本质上是去计算神经网络里面的概率模型的似然值(由真实实验得到的发生某些事情的概率),找到极大似然值(最有可能发生的概率)。(具体可以参考高等数学整理(三) 中的极大似然估计)

神经网络可以通过叠加感知机的方式去任意逼近一种概率模型的。我们以神经网络对猫的识别来说明。

上图中的左边是我们人可以清晰的划定所有猫的边界,它满足于一种概率分布,虽然我们并不知道这个概率分布是什么,但是我们可以假定它满足正态分布。上图中的右边是神经网络中的概率模型,它总是会出现各种各样的偏差,我们总是想尽量的让这个模型和我们人确定出来的猫一致。图中中间的部分黄色曲线是人确定出来的概率分布,绿色的曲线是神经网络的各种情况的概率分布。我们在对神经网络进行训练的过程其实就是调整绿色的曲线能够和黄色的曲线重合,而调整的参数就是NN(W,b),NN代表神经网络,W是系数,b是截距。

假设有n张图片,\(x_i\)是人工的标注,当然都是猫,那么\(x_i\)的值就都是1(概率)。W,b是神经网络的参数,所有的猫的图片通过神经网络输出预测结果为\(y_i\)

对于每一个\(x_i\)输出一个\(y_i\),它的条件概率就是P(\(x_i\)|\(y_i\));那么所有的\(x_i\)输出\(y_i\)的联合概率密度函数就是似然函数(见概率论整理(二) 中的相互独立的随机变量)

L=\(\prod_{i=1}^n\)P(\(x_i\)|\(y_i\))

我们知道,图片要么是猫,要么不是猫,它满足二项分布的独立重复贝努力实验,有(见概率论整理 中的二项分布)

 \(\prod_{i=1}^n\)P(\(x_i\)|\(y_i\))=\(\prod_{i=1}^n\)\(y_i^{x_i}\)\((1-y_i)^{1-x_i}\)

给右边的式子添加对数符号

log( \(\prod_{i=1}^n\)\(y_i^{x_i}\)\((1-y_i)^{1-x_i}\))=\(\sum_{i=1}^n\)log(\(y_i^{x_i}\)\((1-y_i)^{1-x_i}\))=\(\sum_{i=1}^n\)(\(x_i\)log \(y_i\)+(1-\(x_i\))log(1-\(y_i\)))

对此求最大值

max  ( \(\sum_{i=1}^n\)(\(x_i\)log \(y_i\)+(1-\(x_i\))log(1-\(y_i\))))

转化成求最小值就是

min -( \(\sum_{i=1}^n\)(\(x_i\)log \(y_i\)+(1-\(x_i\))log(1-\(y_i\))))

那么作为损失函数的就为

loss=- \(\sum_{i=1}^n\)(\(x_i\)log \(y_i\)+(1-\(x_i\))log(1-\(y_i\)))

现在我们再来对比一下交叉熵

H(p,q)= - \(\sum_{i=1}^n\)p(\(x_i\))log q(\(x_i\))

由于这里的\(x_i\)是代表同一个事件的,而在损失函数中,它有两个事件,要么是猫,要么不是猫,故这里的\(x_i\)相当于损失函数中两个事件的统一,则我们把 \(x_i\)log \(y_i\) 看成是这里p(\(x_i\))log q(\(x_i\))的一种情况,就是是猫的情况;而 (1-\(x_i\))log(1-\(y_i\))看成是p(\(x_i\))log q(\(x_i\))的不是猫的情况,故有

 - \(\sum_{i=1}^n\)p(\(x_i\))log q(\(x_i\))= - \(\sum_{i=1}^n\)(\(x_i\)log \(y_i\)+(1-\(x_i\))log(1-\(y_i\)))

通过上面的等式,我们可以知道,通过极大似然估计法就是得到了交叉熵作为损失函数的方法

梯度下降法

反向传播

梯度下降法之前已经写过很多了,具体可以参考高等数学整理(二) 中的方向导数与梯度下降算法以及凸优化整理(二) 中的最速下降法

这里我们主要讨论的是反向传播,反向传播是通过复合函数求导的链式法则,具体可见Tensorflow深度学习算法整理 中的神经网络反向传播。

这是一个神经网络的图,最后的J就是损失函数,比如之前说的交叉熵损失函数

这里的\(y^{(i)}\)是人工标注的真实概率,\(a^{[3]}\)是上一层神经网络正向传播计算下来的结果,,这里σ是激活函数。要让损失函数J最小,这是一个无约束优化问题,如果这是一个凸优化问题,那么我们都知道求梯度为0就可以了,但这个问题可能是非凸的。但不论是凸还是非凸,我们都可以找到一个平稳点,梯度下降法本身就是在求这个点。

那么无论整个神经网络有多么复杂,整个问题本身就是在对损失函数求得最优解,它跟我们求一个书面上的优化问题的最优解的计算没有本质区别。只不过将当前层的\(a^k\)由上一层的计算公式\(σ(W^ka^{k-1}+b^k)\)代入到损失函数中,此时这个损失函数写出来可能会非常的长。如果这个式子全部写出来了,那么求梯度为0的反向传播的意义也就非常明显了,它会把神经网络所有层的计算公式的梯度全部都要算出来才能求得最终的结果,这就要用到复合函数求导的链式法则。而神经网络的作用在于调节和分配不同特征结构(卷积神经网络对原始图像提取出来的趋势数据,具体可以参考卷积的意义)的权重,以达到最终产生的结果更有利于拟合真实的概率分布,最好的结果是能够使得神经网络推导出的结果能与真实的概率分布相同。

当然我们也需要考虑这样一个问题,那就是人工标注的概率就是自然界的真实概率吗?显然不是,我们只是把人工标注的概率近似自然概率。也就是说我们对样本的选取的时候需要满足样本的丰富性,使得样本空间本身更加丰富。

梯度下降法的优化

  • 随机梯度下降法

梯度下降法在实际应用中会存在着很大的局限,首先我们的算力(可以理解为GPU的显存)有限,如果需要训练的样本量非常巨大,那么GPU不可能一次性读取那么大的数据量读入显存中,于是就有了随机梯度下降法,也称为mini-batch方法。我们在实际算法中也会有一个batch_size值,也就是一次读取进显存的图片数量。我们注意到上面写的损失函数是这个样子的

它比我们之前写的损失函数多了一个\(1\over n\),n是样本数量,它其实就是求了一个平均值,也就是数学期望(见概率论整理(二) 中的数学期望)。期望是能够摆脱开样本数据,又能代表整个样本的数值。如果从期望的角度来考虑问题,那么计算的时候用了多少样本数据,是否覆盖了整个样本空间,就不再是一个关键性要素了。此时我们就不需要对整个数据进行一次性计算,而是从中挑选一部分数据集,分批次的去进行计算,这样也能达到同样的效果。

当然通过这种方式得到的极值点,与对全部数据进行一次性计算得到的极值点还是存在着一定的误差的。如果该问题是一个凸问题,那么误差就是

这里的k是迭代了k次,\(f(x^{(k)})\)是通过批次算法,也就是随机梯度下降法得到的极值点,\(f^*\)是真实的极值点,它们的差值的时间复杂度(时间复杂度的概念可以参考数据结构整理 中的时间复杂度的简单示例)为\(1\over \sqrt k\)这个量级。如果是强凸问题(,比凸函数的性质多了一个),那么误差为

它的收敛会更快。正常情况下,标准的梯度下降法会比随机梯度下降法的速度快,但是标准的梯度下降法再快也快不过\(1\over k\),也就是说标准梯度下降法计算了那么多的数据,提高的效果并不明显,不如直接使用随机梯度下降法。

在Pytorch中,torch.optim.SGD的常规用法就是这种随机梯度下降法。

torch.optim.SGD(model.parameters(), lr=0.1)
  • 牛顿法

我们在使用梯度下降法的时候其实是选取一个点的梯度,也就是这个点下降最快的方向,如果要将整个下降最优的路线描述出来,那就得是步长无限小,但是步长无限小,计算机是无法计算的,即便能计算,那么计算量也是非常巨大的。所以每一次迭代一定是有一个确定的步长的,有了这个步长,它就不会和最优的下降路线完全重合。

如上图所示,沿着A点梯度的方向经过一个步长下降到达了A',这个步长可能较大,以至于下降过程中越过了B点,但B点的梯度方向已经不再是A点的梯度方向。所以整体上这个下降方向一定不是最完美的。那么是否存在一个合适的步长,又非常贴近于完美下降呢?

在上图中,蓝色曲线是损失函数的函数图像,橙色直线是该函数图像上某一点(第一条竖虚线与横虚线的交点)的切线(一阶导数图像),该点梯度的反方向与第二条竖虚线的交点在横轴上经过的距离就是Δx,函数值下降的高度也就是Δy。绿色曲线是该点的二阶导数图像,是一个抛物线,在一定范围内,它的下降效果是比一阶导数要好的,但是超过这个范围就会变差,当它取到抛物线顶点的时候下降效果是最好的。如果以二阶导数的方式进行下降,那么这就是牛顿法(关于牛顿法的具体内容可以参考凸优化整理(二) 中的牛顿法)。

上图中蓝色的箭头方向是最完美的下降路径,对所有的损失函数的等高线都是梯度的反方向。红色箭头方向是梯度下降法的下降路径,由于学习率取值的不同,它会偏离于完美下降路径。绿色箭头方向是牛顿法的下降路径,它在效果上会比梯度下降法要好一些,会更加贴近于完美下降路径。但是由于牛顿法需要计算二阶导数,在多元函数中也就是海塞矩阵(有关海塞矩阵的内容可以参考凸优化整理 中的Hesse 矩阵 (海塞矩阵)),这个计算量是非常大的,每次都要去计算一个矩阵,这个在实际应用中是行不通的。牛顿法只是提供了一个理论上的方法,实用性较差。

  • 动量梯度下降法

特征图在神经网络中经过全连接层或者1*1卷积之后会由一个矩阵变成一个向量,这个向量的维度是非常高的,也就是说多元函数中的元非常多。如果使用牛顿法,那么这个海塞矩阵是一个巨大的矩阵,但同时也是说我们会使用这个向量的所有维度的信息。

上图中的圈是一个的损失函数的等值线,中心点是该损失函数的极值点。无论这个向量的维度有多么高,它可能只有一个分量的方向是逼近极值点的。图中的红色线段轨迹(从左到右)是一般梯度下降法的下降路线。我们可以看到在第一个线段中,它可以分为两个方向的分量(这里以二维向量来说明),一个是垂直分量,一个是水平分量。垂直分量的方向其实为下降不起太大的贡献,它只是在不断的震荡,而水平分量才是不断逼近极值点的正确方向。所以我们会考虑是不是不需要考虑向量的所有维度信息,只考虑那些对下降起作用的分量信息。

我们知道一般梯度下降法的公式是这个样子的

\(x_{t+1}\)=\(x_t\)-α∇f(\(x_t\))

\(x_t\)是一个向量,由某一个时刻的系数\((\)\(W_{1t},W_{2t},...,W_{nt}\)\()^T\)和偏置\(b_t\)组成。梯度∇f(\(x_t\))也是一个向量\((\)\(∂f(x_t)\over ∂W_{1t}\),\(∂f(x_t)\over ∂W_{2t}\),...,\(∂f(x_t)\over ∂W_{nt}\),\(∂f(x_t)\over ∂b_t\)\()^T\),上式可以拆分成

\((\)\(W_{1t}\)\(∂f(x_t)\over ∂W_{1t}\),\(W_{2t}\)\(∂f(x_t)\over ∂W_{2t}\),...,\(W_{nt}\)\(∂f(x_t)\over ∂W_{nt}\),\(b_t\)\(∂f(x_t)\over ∂b_t\)\()^T\)

\(W_{i(t+1)}\)=\(W_{it}\)\(∂f(x_t)\over ∂W_{it}\),i=1...n

Δ\(W_{i(t+1)}\)=\(W_{i(t+1)}\)-\(W_{it}\)\(∂f(x_t)\over ∂W_{it}\),i=1...n

本来有了 Δ\(W_{i(t+1)}\) \(∂f(x_t)\over ∂W_{it}\) ,直接用在参数上进行学习 \(W_{it}\)\(∂f(x_t)\over ∂W_{it}\) ,现在增加一个量

\(v_{t+1}\)=\(v_t\)+ Δ\(W_{i(t+1)}\)

这个v是一个累加量,从0开始,不断累加前面的不同时刻的梯度分量增量 Δ\(W_{i(t+1)}\) ,那么后一时刻真正的分量就为

 \(W_{i(t+1)}\)=\(W_{it}\)\(v_t\)

这里的\(v_t\)代表了前面所有步的Δ\(W_i\)的和。

这样之后,在上图中的绿色线段轨迹,它明显不同于红色线段轨迹,就是采用了这种动量梯度下降法。在垂直方向上,由于方向性的原因,会出现方向相反的情况,增量的累加会相互抵消,降低了它震动的幅度;而在水平方向上,由于它们始终方向一致,会出现加快向极值点靠近的情况。

但是这里也会有一个情况,那就是就算是动量梯度下降法也会有很多步,那么离当前步越远的之前的步骤的梯度的影响会越小,我们需要将其影响减弱,就有了

\(v_{t+1}\)\(v_t\)+ (1-ρ\(W_{i(t+1)}\)

\(W_{i(t+1)}\)=\(W_{it}\)\(v_t\)

这个ρ是一个权重,就相当于是对历史数据进行加权求和。距离当前越近的数据权重越大,距离当前越远的数据权重越小。这种加权方法称为指数加权移动平均法,它是数学上的一个常用方法。我们以ρ=0.9为例来说明

\(v_{t+1}\)=0.9\(v_t\)+ (1-0.9\(W_{i(t+1)}\)

       =0.1⋅\(0.9^0\) Δ\(W_{i(t+1)}\) +0.1⋅\(0.9^1\)Δ\(W_{it}\) +0.1⋅\(0.9^2\) Δ\(W_{i(t-1)}\) +...+0.1\(0.9^t\)Δ\(W_{i1}\)

上式中的第一个 0.1⋅\(0.9^0\) Δ\(W_{i(t+1)}\) 是当前的增量,不考虑衰减,所以\(0.9^0\)=1;再向前一步,考虑衰减,\(0.9^1\)=0.9,这里只衰减了0.1;再向前一步,\(0.9^2\)=0.81,那么衰减了0.19;那么越向前的步骤衰减越大,\(0.9^t\)是一个很小的数了,它所占的权重很低,几乎忽略不计,也就是几乎全部衰减了。

动量梯度下降法还有一些其他的功能,具体可以参考Tensorflow深度学习算法整理 中的神经网络训练优化。

在Pytorch中,动量梯度下降法依然是torch.optim.SGD,只不过要设定一个参数momentum来表示权重。

torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
  • Nesterov算法

对于梯度下降法,我们不仅可以借助于历史数据,还可以超前的去参考未来的数据。

在上图中,带箭头的蓝色轨迹线是完美下降路径。最左边的浅红色箭头是最上面的点的梯度方向,第三条浅绿色的箭头是该点的历史动量方向,那么经过梯度方向与历史的动量方向叠加,第二条浅红色的线段就是本次的真实下降路径,第二个点到第三个点的连线就是第二次真实下降路径,这就是动量梯度下降法。如果我们能够预知第二步到达的方向,那么我们在第一步的时候修正的幅度就加大,建立上图中深红色方向的下降方向,这就是Nesterov算法。

之前我们知道Δ\(W_{i(t+1)}\) \(∂f(x_t)\over ∂W_{it}\)\(v_{t+1}\)\(v_t\)+ (1-ρ\(W_{i(t+1)}\) \(W_{i(t+1)}\)=\(W_{it}\)\(v_t\)现在我们更新一下 Δ\(W_{i(t+1)}\) 

 Δ\(W_{i(t+1)}\) \(∂f(x_t)+γv_t\over ∂W_{it}\)

上式中的\(v_t\)是历史动量,对它求偏导,那么求的就不是第一个点的偏导,而是浅绿色线终点的点的偏导

即上图中红色点的梯度方向,为该点的红色箭头方向。我们将该方向平移到第一个点上

也就是上图中第一个点的第五条红色箭头方向,也就是 Δ\(W_{i(t+1)}\) 的方向。

最后该方向与动量梯度下降法的下降方向叠加,就成了上图中的深红色的下降方向,它比动量梯度下降法更接近完美下降方向。Nesterov算法是由一个俄罗斯数学家提出来的算法。

在Pytorch中,Nesterov算法依然是torch.optim.SGD,需要设定nesterov参数

torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9, nesterov=True)
  • AdaGrad方法

AdaGrad方法是针对学习率α的,使它能够自适应,自动调节。

如果学习率α是一个固定长度的话,那么可能会出现下面的情况。

明明损失函数已经快接近极值点了,但是由于学习率是固定长度的,它会走到极值点的另一面,由此造成在极值点周围震荡,无法恰好到达极值点。

我们知道在一般的梯度下降法中,有 \(W_{i(t+1)}\)=\(W_{it}\)\(∂f(x_t)\over ∂W_{it}\) ,那么AdaGrad方法是调整了这个学习率,变为

  \(W_{i(t+1)}\)=\(W_{it}\)-\(α\over \sqrt {s_{t+1}}+ε \)\(∂f(x_t)\over ∂W_{it}\)

其中\(s_{t+1}\)=\(s_t\)+\((\)\(∂f(x_t)\over ∂W_{it}\)\()^2\)

这个\(s_{t+1}\)是历史上所有梯度数据的平方和再开方。ε是一个极小值,是避免分母为0。同动量梯度下降法一样,它也是要看历史数据的,如果历史数据修改的越多,那么学习率α减小的就会越多。Ada是自适应的意思,Grad就是梯度。

上图中,彩色曲面是一个二元损失函数的函数曲面,紫色的轨迹是动量梯度下降法的下降路径。我们可以看到刚开始的时候由于惯性(梯度的不断累积)的作用下,沿着梯度的方向下降的很快,但是会冲过头,然后再掉头返回,而在极值点附近,它会不断的来回震荡,最终才到达极值点。白色的轨迹是AdaGrad方法的下降路径。它不是一开始就朝着梯度下降最快的路径进行移动,而是朝着一个长期来看更优的路径进行下降,由于在向下的方向梯度的平方变化比较快,而向左的方向梯度的平方变化比较慢,所以它不会朝着某一个方向快速的下降,而是朝着一个更中庸的方向下降。

AdaGrad方法在稀疏数据上训练效果特别好。我们知道数据维度,一个一个的维度就是特征,虽然我们输入到神经网络最开始的是一个原始的照片,但是经过神经网络的一层一层的隐藏层之后,到最后一层的隐藏层的感知机就是一个一个的特征。虽然神经网络识别出来的特征,我们人类不一定能理解,但依然是特征。如果某一个特征它对应的学习率比较大,那就代表了在训练过程中,对这个特征的调整比较大。如果学习率小,那就代表了在学习过程中,对这个特征的调整比较小。稀疏数据是指给了一个训练集(假如是二分类数据集),它包含的两种数据之间的不同更多的体现在特征的不同上,而不是某一个具体特征上的程度不同。

比如说猴跟人,要想把他们区分开,那就只需要去看哪个有尾巴,哪个没尾巴;或者说哪个身上有毛,哪个身上没毛。我们只需要判断某个特征到底是有还是没有,不用去具体的考虑某一个具体的特征的程度是多还是少。

上图的两个都是猫,第一个是英短,第二个是波斯猫。要区分这两种猫就不能依靠哪些特征有,哪些特征没有去区分了。最后判断的标准要看它们的毛到底是长还是短,或者颜色到底是蓝色还是白色这种程度上的划分。那么这种数据集就不是一个稀疏数据集。

对于稀疏数据来说,在某一个特征或者某一个维度上提供的数据没那么充分,此时使用梯度下降法,可能就会比较容易出现震荡的情况。如果使用的是AdaGrad方法,那么这个优势就会比较明显,正好可以去减少震荡。

随着维度的增加,遇到稀疏数据的可能性会越来越高。

上图是一个球体,球体的体积公式为\(V_3\)=\(4\over 3\)π\(r^3\),如果是一个单位圆,那么体积就为 \(4\over 3\)π。随着维度的增高,高维球的体积为

  1. 4维:\(V_4\)=\(π^2\over 2\)\(r^4\)
  2. 5维:\(V_5\)=\(8π^2\over 15\)\(r^5\)
  3. 6维:\(V_6\)=\(π^3\over 6\)\(r^6\)
  4. ...
  5. n维:\(V_n\)=\(π^{n\over 2}\over Γ(1+{n\over 2})\)   当n是奇数时为\(π^{n\over 2}\over \sqrt {π}{n!!\over 2^{(n+1)/2}}\)\(r^n\),当n是偶数时为\(π^{n\over 2}\over ({n\over 2})!\)\(r^n\)

随着维度的升高,体积的变化规律如下

根据上图,我们可以看到,当维度增加到一定程度之后,随着维度的增加,体积是在变小的,当维度趋于无穷大的时候,体积趋于0。我们知道球的内部是由一个一个的点组成的。在维度比较低的时候,点和点的不同可以区别于两个方面。

上图的A点和B点,它们是在同一个维度的,但是在这个维度上取值不同。但是A点和C点的维度不同(这里的C点是在纵面上)。在计算体积的时候,更多的是依赖同一个维度的取值不同,这样才能有纵深感。当维度升到很高的时候,一个球中的两个点更多的是维度的不同,而不是某一个维度上的取值不同,因此就失去了纵深,再去计算体积,就慢慢变小,直到没有,也就是趋于0。

当我们去训练神经网络的时候,肯定是希望维度越多越好,也就是特征越多越好。特征越多,我们去进行判断的标准也就越多。数据集就会越稀疏,使用AdaGrad方法就会越好。不过AdaGrad方法也有自己的问题。

如上图训练过程中的红色曲线,它先经历一个快速的变化期,到达一个平台区(上图中间部分的黑影区),在这个平台上的变化速度就会非常的慢。当它经过平台到达应该继续快速变化的区域,它的速度依然很慢,因为它要考虑所有的历史数据。

在Pytorch中,AdaGrad的使用方法是

torch.optim.Adagrad(model.parameters(), lr=0.01, lr_decay=0, weight_decay=0, initial_accumulator_value=0)
  • RMSprop方法

RMSprop方法是从AdaGrad方法改进而来,它类似于动量梯度下降法的改进版,只考虑当前比较近的那部分历史梯度,而不是所有的梯度累积。具体也是使用指数加权移动平均法。

  \(W_{i(t+1)}\)=\(W_{it}\)-\(α\over \sqrt {s_{t+1}}+ε \)\(∂f(x_t)\over ∂W_{it}\)

 \(s_{t+1}\)\(s_t\)+(1-β)\((\)\(∂f(x_t)\over ∂W_{it}\)\()^2\)

Pytorch中的使用方法为

torch.optim.RMSprop(model.parameters(), lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False)
  • Adam方法

Adam方法是我们训练神经网络模型最最最经常使用的方法,几乎达到了无脑用的地步。它是结合了RMSprop方法和动量梯度下降法。

 \(W_{i(t+1)}\)=\(W_{it}\)-\(α\over \sqrt {s_{t+1}}+ε \)\(v_t\)

\(s_{t+1}\)\(s_t\)+(1-β)\((\)\(∂f(x_t)\over ∂W_{it}\)\()^2\)

\(v_{t+1}\)\(v_t\)+ (1-ρ\(W_{i(t+1)}\)

 Δ\(W_{i(t+1)}\) \(∂f(x_t)\over ∂W_{it}\) 

Adam方法学习率没有那么敏感,一般设置为0.001,Pytorch中的使用方法为

torch.optim.Adam(model.parameters(), lr=0.001)

它还有两个默认参数

  1. betas (beta1, beta2): 表示 Adam 算法中两个动量参数。默认值为 (0.9, 0.999)。第一个是动量梯度下降法的,第二个是RMSprop方法的。
  2. eps (epsilon): 一个很小的值,用来维持数值稳定性。默认值为 1e-8。这个值就是RMSprop方法分母防止为0使用的。
  • NAdam方法

RMSprop方法能够和动量梯度下降法结合,自然也就可以和Nesterov算法结合,它的公式基本和Adam方法相同,只不过 Δ\(W_{i(t+1)}\) 发生了变化。

 \(W_{i(t+1)}\)=\(W_{it}\)-\(α\over \sqrt {s_{t+1}}+ε \)\(v_t\)

\(s_{t+1}\)\(s_t\)+(1-β)\((\)\(∂f(x_t)\over ∂W_{it}\)\()^2\)

\(v_{t+1}\)\(v_t\)+ (1-ρ\(W_{i(t+1)}\)

Δ\(W_{i(t+1)}\) \(∂f(x_t)+γv_t\over ∂W_{it}\)

Pytorch中的使用方法为

torch.optim.NAdam(model.parameters(), lr=0.001)

它同样有Adam的两个默认参数。

Sigmoid与Softmax

sigmoid函数的具体内容可以参考机器学习算法整理(三) 中的逻辑回归。一般在卷积神经网络中都会有一个激活函数ReLu,那为什么会使用ReLu而不是sigmoid呢?我们来看一下sigmoid的函数图像。

我们知道在进行神经网络训练的时候会有一个反向传播,是要计算每一层神经网络的梯度,如果使用的是sigmoid函数来做激活函数,如上图所示,该函数的绿色区域的部分的梯度很低,甚至在边缘趋近于0,那么随着神经网络层的加深就会造成梯度消失的可能性,这样就无法再继续反向传播。

如果换成ReLu函数,当大于0的时候,它的梯度处处相等,这样就可以避免梯度消失问题。但是ReLu函数的最大值是无穷大的,对于损失函数来说,一般指交叉熵,是基于概率的,也就是说它的输出值必须位于0到1之间的(概率的基本性质),ReLu函数是不具备这个特点的,所以在整个神经网络的最后一层使用sigmoid函数,之前都使用ReLu。不过我们知道sigmoid函数只能解决单一分类的问题,如果我们要进行多分类,就只能使用softmax了。

如果在我们的分类任务中,各种分类彼此间是互斥的,比如在猫狗分类中,要么是猫,要么是狗。虽然是二分类,但是此时还必须得使用softmax。但是如果是对同一个事物打标签,比如一个人,他可以有男人、父亲、公务员等多个标签,那么用softmax就不合适了,反而得使用sigmoid。

上图是一个一层感知机的神经网络图,黄色部分是原始图像,灰色的是两层隐藏层,蓝色的是输出的感知机。其中\(z^{[l](k)}\)是上一层隐藏层线性计算的输出值,它是一个标量,l代表的是层数,k是第几张图片;\(a^{[l](k)}\)\(z^{[l](k)}\)经过激活函数sigmoid得到的概率值;J(\(y^{(k)}\),\(a^{[l](k)}\))是损失函数,\(y^{(k)}\)是人工标注的标签。这是对于一个单一分类来说的。

但是对于多分类来说,它的输出层就不是一个感知机了,对于你有几个分类,它就有几个感知机,我们这里假定为i个。这里的\(z^{[l](k)}\)依然是上一层隐藏层线性计算的输出值,只不过它是一个向量,它有i个分量,每一个对应一个感知机。\(a^{[l](k)}\)是该向量经过激活函数得到的概率值,不过这里不能再使用sigmoid了。 J(\(y^{(k)}\),\(a^{[l](k)}\))是损失函数, \(y^{(k)}\)是人工标注的标签。

对于单层感知机的标签\(y^{(k)}\)是一个标量,只有一个值,要么是0,要么是1;多层感知机的标签\(y^{(k)}\)是一个向量,如上图中的红色部分。每一个向量的分量代表着一种分类,譬如说\(y_1^{(k)}\)代表猫,\(y_2^{(k)}\)代表狗,\(y_3^{(k)}\)代表牛....;这些分量具体的值就是后面的部分,如果是猫,只有\(y_1^{(k)}\)为1,其他都是0;如果是狗,只有\(y_2^{(k)}\)是1,其他都是0......。这就是我们说的Onehot独热编码,见特征工程和Kaggle赛题分析 中的Onehot。

既然\(y^{(k)}\)是一个向量,那么\(a^{[l](k)}\)自然也就是一个向量。向量里的每一个分量代表的是一个概率,那么这个分量的范围就是[0,1],且所有的分量和为1。

\(z^{[l](k)}\)的每一个分量都可能是从-∞到+∞的,我们需要将其投射到大于等于0的范围。

上图是以e为底的指数函数图像,它的定义域是-∞到+∞,值域是大于0的。那么我们使用\(e^{z^{[l](k)}}\)给原本属于-∞到+∞的各个分量给投射到大于0的数。当然这样投射之后无法达到小于等于1和全部分量和为1的条件。

我们将上面的\(e^{z^{[l](k)}}\)定义成t函数,即t(\(z^{[l](k)}\)),那么我们将\(z^{[l](k)}\)每一个分量经过t函数后的结果相加的和作为分母,而每一个分量经过t函数后的结果作为分子组成一个新的向量,就可以满足概率的所有需求——新的向量的分量的范围为[0,1],且所有的分量和为1,这就是我们经常使用softmax函数。当然经过了这样的变化,交叉熵损失函数也需要发生一定的变化。

H(p,q)= - \(\sum_{i=1}^n\)\(p^{(k)}(x_i)\)log \(q^{(k)}(x_i)\)

这个是单一分类的交叉熵损失函数,现在我们要对所有的类别都经过一次这种交叉熵损失计算,就有了

J=-\(\sum_{k=1}^m\)\(\sum_{i=1}^n\)\(p^{(k)}(x_i)\)log \(q^{(k)}(x_i)\)

这里的m就是分类数。

softmax与sigmoid的关系

首先sigmoid(\(z^{[l](k)}\))=\(1\over 1+e^{-z[l](k)}\)是sigmoid的定义式,经过变形得到\(e^{z[l](k)}\over e^{z[l](k)}+1\)=\(t\over t+1\),这里的t我们可以等价于softmax中的\(t_1\),1等价于softmax中的\(t_2\),所以sigmoid可以看成只有一种情况下的softmax,它虽然只有一种分类,但其实它是包含着两个概率的,第一个是以\(t_1\)为分子,第二个是以\(t_2\)为分子,只不过\(t_2\)这个分子是不会变化的。故而sigmoid和softmax其实是一回事,sigmoid对应的单一分类,而softmax对应的是多分类。

虽然sigmoid对应的是一种分类,但它有两个概率,我们来看一下对于单一感知机和双层感知机的不同。

通过上图,我们可以看到,无论是单层感知机还是双层感知机,它们都有\(t_1\)\(t_2\)两个分量。对于单层感知机来说,\(t_2\)是不会变化的,只有\(t_1\)会变;对于双层感知机来说,它的 \(t_1\)\(t_2\) 都会变。对于概率值\(p_1\)\(p_2\)在单层感知机中,它们只会随着\(t_1\)的变化而变,而在双层感知机中,它们会随着 \(t_1\)\(t_2\) 同时发生变化而发生改变。在交叉熵损失函数J中,在单层感知机中,\(y_1^{(k)}\)表示是猫的标签,(1-\(y_1^{(k)}\))表示不是猫的标签;在双层感知机中, \(y_1^{(k)}\)表示是猫的标签,\(y_2^{(k)}\)表示是狗的标签,虽然\(y_2^{(k)}\)也等于 (1-\(y_1^{(k)}\)),但在意义上已经完全不同。

在Pytorch中几种交叉熵损失函数的用法

  • torch.nn.CrossEntropyLoss()

该损失函数自带了softmax的操作,所以我们只需要将网络最终的输出向量传递给该损失函数就好了,不需要再手动计算一个softmax得到概率向量。

loss_func = torch.nn.CrossEntropyLoss()
outputs = net(images)
loss = loss_func(outputs, labels)

内部计算步骤:进行softmax操作,然后吧label进行oneshot编码,再进行交叉熵计算,求值返回。

  • torch.nn.BCELoss()

同CrossEntropyLoss()相比,该损失函数是做sigmoid操作的,但是它本身是不自带sigmoid函数的。

loss_func = torch.nn.BCELoss()
outputs = net(images)
m = torch.nn.Sigmoid()
outputs = m(outputs)
loss = loss_func(outputs, labels)
  • torch.nn.BCEWithLogitsLoss()  

同BCELoss()相比,它本身自带sigmoid函数。

loss_func = torch.nn.BCEWithLogitsLoss()
outputs = net(images)
loss = loss_func(outputs, labels)

这样BCELoss()和BCEWithLogitsLoss()的代码就是等价的。以上三种交叉熵损失函数的具体用法可以参考PyTorch技术点整理

最大熵

神经网络的主要作用是逼近任何一种概率模型,哪怕这种概率模型我们无法表达,也能逼近。具体做法就是去寻找交叉熵最小的概率模型。在逼近目标的时候有一个默认的前提假设,那就是交叉熵最小的那个概率模型与目标最接近。

但是还有另外一个前提假设,那就是我们一旦选择了softmax或者sigmoid的时候,最大熵就被默许了。

  • 最大熵原理

最大熵原理是一种选择随机变量统计特性最符合客观情况的准则,也称为最大信息原理。真实随机量的概率分布是很难测定的,一般只能测得其各种均值(如数学期望、方差等)或已知某些限定条件下的值(如峰值、取值个数等),符合测得这些值的分布可有多种、以至无穷多种,通常,其中有一种分布的熵最大。选用这种具有最大熵的分布作为该随机变量的分布,是一种有效的处理方法和准则。这种方法虽有一定的主观性,但可以认为是最符合客观情况的一种选择

熵代表的是随机变量的不确定性,熵最大的时候,随机变量最不确定,也就是随机变量最随机,对其行为做准确预测最困难。比方说八支球队参加世界杯,预测哪一支夺冠,那么最大熵下肯定是每支球队夺冠的概率都是\(1\over 8\)

如果我们直接使用最大熵来判断一张照片是不是猫,那其实没有太大的意义,因为根据最大熵原理,它是猫的概率为\(1\over 2\),不是猫的概率也是\(1\over 2\)。神经网络在利用最大熵的时候是在一些已知的事实的时候才去加以利用的,而不是无知的盲猜。这个已知的事实就是那个训练用的数据集,训练用的数据集并不是一个包罗万象的全集,它有一部分是已知的,还有很大一部分是未知的,这个未知的部分就需要使用最大熵来补齐了。

对于我们训练模型来说,首先需要拿到训练集,从中归纳出它背后隐藏的概率模型来,然后把这个概率模型的特征给挑出来,最后是让我们的目标概率模型也具有这些特征。我们知道数据集并不代表全集,全集的概率模型是不知道的,我们只知道了数据集的概率模型,这个时候就只能使用最大熵来预测全集了。这样我们获得的目标概率模型既能满足数据集的概率模型,又能使得未知的部分熵最大化。

  • 概率模型

我们一直在说的概率模型究竟指的是什么的概率模型,我认为应该是特征,也就是一张原始图像经过卷积神经网络后得到的特征向量,特征向量经过softmax后得到的概率向量,这个概率向量就是了概率模型。

现在我们假设这个概率模型满足正态分布。正态分布是最简单的概率分布。

它的期望是μ,方差是\(σ^2\)。期中(此处可以参考概率论整理(二) 中的数学期望方差)

μ=E[x]

\(σ^2\)=E[\((x-μ)^2\)]=E[\(x^2\)] - \(E^2\)[x] =  E[\(x^2\)] - \(μ^2\)  

这里的x是特征向量中每一个特征的特征值。我们可以看到在μ中,有x的一次方,在\(σ^2\)有x的二次方。

因为正态分布比较简单,我们可以将概率模型变的复杂一点,让其向左或向右有一定的倾斜。

这个倾斜的程度可以用偏度这个概念来描述。

在这里我们可以看到x出现了三次方。由此我们可以把矩的概念拿出来了,矩的概念可以参考概率论整理(二) 中的矩协方差矩阵

这里我们只看阶矩,即E[\(x^k\)],k=1,2,...,n;

如果是正态分布,那么只需要一阶矩和二阶矩就可以描述出来的。如果复杂一点,就需要三阶矩。如果更复杂,就通过四阶矩、五阶矩、六阶矩......也可以描述出来。

任何一个概率分布的特征都可以用这样一个向量来表现

数学上已经证明,任何一个概率分布都可以用一个特征函数表示出来

这里的\(e^{itx}\)是一个复数。一个特征函数和一个概率分布是一一对应的。将特征函数进行泰勒展开就是后面的样子。经过最终的变化,就可以看到1到n阶矩的形式。同时特征函数的最终展开式是阶矩向量的线性关系。如果我们知道了一堆样本数据,那么这个样本的一阶矩、二阶矩、三阶矩...一直到n阶矩,都是可以计算出来的,计算出来的结果就可以把这个概率分布给唯一的表达出来。

如果有两组数据,要想知道这两组数据是不是一样的,只需要去比较它们的一阶矩、二阶矩、三阶矩...是不是相同,如果相同就可以肯定这两组数据的概率分布是一样的。

我们知道特征函数\(φ_x\)(t)是\(e^{itx}\)的期望,即E[\(e^{itx}\)]=\(\int_{-∞}^{+∞}e^{itx}⋅f(x)dx\),其中f(x)是概率密度函数。(具体可参考概率论整理(二) 中的数学期望)

如果对这个概率密度函数f(x)做傅立叶变换的话,为F(t)=\(\int_{-∞}^{+∞}f(x)⋅e^{-itx}dx\)(傅立叶变换可以参考高等数学整理(三) 中的傅里叶变换)

这里我们可以看到这个傅立叶变换跟特征函数是共轭的,是一一对应的关系,而傅立叶变换和概率分布又可以做一一对应,于是概率分布和特征函数就能做一一对应了。

{{o.name}}
{{m.name}}

猜你喜欢

转载自my.oschina.net/u/3768341/blog/7184278