几种常见梯度优化方法
优化算法是机器学习领域的重要内容,本文介绍几种常见的无约束的优化算法。关于无约束问题优化方法的一般讨论请参考此文。
梯度下降法 (Gradient Descent)
凡是接触过机器学习、强化学习、凸优化等领域的读者都会对梯度下降法非常熟悉。鉴于此原因,这里也不做详细介绍,仅给出基本方法。关于梯度下降在凸优化中的应用请参考此文。
学过数学的人都知道,函数的梯度方向表示了函数增长速度最快的方向,那么和它相反的方向就可以看作是函数值减少速度最快的方向。就机器学习模型优化问题而言,当目标被设定为求解目标函数的最小值的时候,只要朝着梯度下降的方向前进,就能不断接近最优值。
这里给出一种最简单的方法——固定步长方法:
def gd(x_start, step, g):
x = x_start\
for i in range(20):
grad = g(x)
x -= grad * step
print '[ Epoch {0} ] grad = {1}, x = {2}'.format(i,grad,x)
if abs(grad) < 1e-6:
break
return x
这里 x 保存当前优化过程中的参数值,g 是待优化函数 f(x) 的导数,step 是步长。步长的选择十分重要,步长太小导致收敛速度太慢;而步长太大则导致不收敛问题。关于调整步长目前已经有成熟的方法,这里不作细致讨论。
动量法 (Momentum)
动量代表了优化过程中累计的能量,它将在后面的优化过程中持续作用,推动目标值前进。动量会以一定的形式衰减。基于上一节的梯度下降方法,这里给出动量法的简单实现:
def momentum(x_start, step, g,discount=0.7):
x = np.array(x_start,dtype = 'floast64')
pre_grad = np.zeros_like(x)
for i in range(50):
grad = g(x)
pre_grad = pre_grad * discount + grad * step
x -= pre_grad
print '[ Epoch {0} ] grad = {1}, x = {2}'.format(i,grad,x)
if abs(grad) < 1e-6:
break
return x
可以看出,代码中多了 pre_grad。这个变量用于存储历史动量的积累,每一轮都会乘以一个打折量 discount 。动量法可以帮助目标值穿越“狭窄山谷”形状的优化曲面,从而达到最终的最优点。很多时候,目标函数在不同维度上的“陡峭”程度差别很大,固定步长的方法可在每一个维度上以相同的步长进行迭代,这就导致某些维度上更新乏力,而某些维度上大量进行无用功。
使用了动量后,历史的更新会以衰减的形式不断作用在这些方向上,反复做的无用功可以相互抵消,而更新不足的方向则在加强。然而,动量优化也存在一些问题,前几轮迭代累计不足,目标值在反复做无用功的方向震荡更大,带来更强烈的抖动。又有科研人员发明了解决方法——Nesterov算法。我们对上述代码稍加改造:
import numpy as np
def nesterov(x_start, step, g, discount = 0.7):
x = np.array(x_start,dtype = 'floast64')
pre_grad = np.zeros_like(x)
for i in range(50):
x_future = x - step * discount * pre_grad
grad = g(x_future)
pre_grad = pre_grad * discount + grad
x -= pre_grad * step
print '[ Epoch {0} ] grad = {1}, x = {2}'.format(i,grad,x)
if abs(grad) < 1e-6:
break
return x
可以看出,这里用 x_future 存储下一步的变量,然后计算更新后的优化点的梯度。这是与动力法的主要区别。想象一个场景,优化点累积到了某个抖动方向后的梯度后,对动量算法来说,虽然梯度指向累计梯度相反的方向,但是刚开始动量不够大,所以优化点还是会在累计的方向上前进一小段。对 Nesterov 方法来说,如果在该方向前进一小段,则梯度中指向累积梯度相反的方向的量会大很多,最终两个方向梯度抵消,反而使抖动方向的量迅速减少。
共轭梯度法(Conjugate Gradient)
共轭梯度法是用来求解正定二次规划的一种优化方法,它具有二次终止性。前面的讨论中,我们并没有给出优化问题的具体形式,本节我们首先将待优化问题形式定义为:
约束与共轭
梯度下降法的每一步都朝着局部最优的方向前进,但是几轮迭代它前进的方向可能非常相似,因为这个方向没有更新完,未来还会存在这个方向的残差。于是,我们对优化提出了更高要求——能不能再选中优化方向和步长上更新智能,每朝一个方向走,就把这个方向走到极致(极致就是再往前走就会远离目标)。我们引入一个误差变量
我们令 表示第 轮的更新方向,可以得到:
最优步长
共轭梯度法属于线搜索的一种,类似于梯度下降,优化过程分两步:
- 确定优化方向;
- 确定优化步长。
这里我们先来介绍如何确定优化步长,优化方向的确定留到后面。假设当前参数为 :
Gram-Schmidt方法
下面我们讨论如何确定优化方向。我们要解决的主要问题是如何让优化方向和误差正交。由于每一次优化后,剩下的误差和本次的优化方向正交(共轭正交),所以可以看出每一个优化方向都是彼此正交的。现在问题变成了如何构造这些正交的方向。
Gram-Schmidt 方法是线性代数中常见的一种向量正交化方法。这个算法的大意是在 维空间中输入 个线性无关的向量,输出是 个相互正交的向量,也就是我们最终想要的向量组合。假设输入的 个向量为 输出的 个向量为 , 它的具体做法如下:
- 对于第一个向量,保持不变:;
- 对于第二个向量,去掉其与第一个向量共线的部分:;
- 对于第三个向量,去掉其与第一、二个向量共线的部分:;
- 对于第个向量,去掉其与前 个向量共线的部分:
如何确定这些 呢?利用共轭正交的性质有:
共轭梯度
到这里,还有两个问题待解决:
- 要用什么向量构造这些正交向量?
- 随着向量数量的增加,我们需要计算的参数越来越多。这个计算量能不能减少呢?
解决上述两个问题的方法就是——用每一步的梯度构建这些正交向量,同时利用梯度的特点减少计算量。这便是共轭梯度法名称的由来。
最后我们来解决第二个问题。要解决此问题,我需要证明三个推论,这里我们依次证明。
推论一:第 步计算的梯度 和前 步的更新 正交。
证明:最优点 到初始参数值 的距离为 。
推论二:第 步计算的梯度 和前 步的梯度 正交。
证明:我们可以直接利用推论一证明推论二。注意这里的 相当于Gram-Schmidt方法中提到的输入 。
此时我们发现,每一步的梯度与此前的梯度正交,因为我们能用梯度作为线性无关的向量组,从而组成共轭正交的向量组。
推论三:第 步计算的梯度 和前 步的更新 正交。
证明:
所以可以看出,使用 Gram-Schmidt 方法可以保证更新方向共轭正交,对于第 轮优化,我们只计算 即可,其它 均是0。
下面来简单看下这个算法的实现:
import numpy as np
def cg(f_Ax,b,cg_iters=10,callback = None,verbose=False,residual_tol=1e-10):
p = b.copy()
r = b.copy()
x = np.zeros_like(b)
rdotr = r.dot(r)
for i in range(cg_iters):
z = f_Ax(p)
v = rdotr / p.dot(z)
x += v*p
r -= v*z
newrdotr = r.dot(r)
mu = newrdotr/rdotr
p = r + mu*p
rdotr = newrdotr
if rdotr < residual_tol:
break
return x
代码中用到了残差 ,真正的优化方向是 ,优化步长是 。
自然梯度法(Natural Gradient)
当优化问题的两个坐标轴的尺度差异比较大时,使用一个统一的学习率会产生问题:某一个坐标轴可能会发散。尺度不同在优化问题中很难避免,虽然每一轮迭代对参数的更新量相差不多,但他们对模型的影响完全不同。梯度下降法针对的目标是参数,我们更新的目标也是参数,而优化的真正目标是找到最优的模型。每一轮迭代中,对参数的改进和对模型的改进是不同的。梯度下降法只考虑了在梯度方向上对参数进行更新,并没有考虑模型层面的更新,因此就会出现模型更新不均匀的现象。
自然梯度法不简单地使用学习率对参数更新进行量化,而是对模型效果进行量化。假设我们的待优化模型为 ,其中参数为 。按照梯度下降法的思想,我们定义每一轮的迭代都要解决这样一个子问题:
Fisher信息矩阵
KL散度可以变成熵和交叉熵的运算:
为了进一步简化,我们引入Fisher信息矩阵。我们首先定义 Score函数 为对数似然函数的一阶导数:
自然梯度法的目标公式
通过前面的推演,我们的目标函数变为:
感谢冯超——《强化学习精要:核心算法与TensorFlow实现》(电子工业出版社)
上一篇: MVC5之表单集合数据自动绑定到对象属性(集合)中
下一篇: 利用热门事件营销,分分钟引爆大流量