梯度类算法:梯度类算法本质是使用函数的一阶导数信息选取下降方向 d k d^{k} dk,这其中最基本的算法是梯度下降法,也即直接选择负梯度作为下降方向 d k d^{k} dk,此外还有BB方法,是一种梯度法的变形,虽然理论性质目前仍不完整,但由于它有优秀的数值表现,也是在实际应用中使用较多的一种算法
梯度下降法(Gradient descent,GD):使用梯度下降法寻找函数极小值时,会沿着当前点对应梯度(或近似梯度)的反方向 d d d所规定的步长 α \alpha α内进行迭代搜索。当然如果沿着梯度正方向搜索,就会接近函数的局部最大值,此时对应梯度上升法
我们经常会用下山这个例子来理解梯度下降法:现在你可以想象自己站在一座高山上的某一位置,需要下山,那么此时最快的下山策略就是环顾四周、哪里最陡峭就沿着哪个方向下山,每到一个新的地方后再次执行这个策略
可以看出梯度下降有时得到的是局部最优解,如果损失函数是凸函数,梯度下降法得到的解就是全局最优解
因此,梯度下降法公式为
θ i = θ i − α ∂ J ( θ 0 , θ 1 , . . . , θ n ) ∂ θ i \theta_{i}=\theta_{i}-\alpha \frac{\partial J(\theta_{0},\theta_{1},...,\theta_{n})}{\partial \theta_{i}} θi=θi−α∂θi∂J(θ0,θ1,...,θn)
梯度下降法求解步骤:如下
import numpy as np
import torch
from sympy import *
# 计算梯度函数
def cal_grad_funciton(function):
res = []
x = []
x.append(Symbol('x1'))
x.append(Symbol('x2'))
for i in range(len(x)):
res.append(diff(function, x[i]))
return res
# 定义目标函数
def function_define():
x = []
x.append(Symbol('x1'))
x.append(Symbol('x2'))
# y = 2 * (x[0] - x[1] ** 2) ** 2 + (1 + x[1]) ** 2
# Rosenbrock函数
y = 100 * (x[1] - x[0] ** 2) ** 2 + (1 - x[0]) ** 2
return y
# 计算函数返回值
def cal_function_ret(function, x):
res = function.subs([('x1', x[0]), ('x2', x[1])])
return res
# 计算梯度
def cal_grad_ret(grad_function, x):
res = []
for i in range(len(x)):
res.append(grad_function[i].subs([('x1', x[0]), ('x2', x[1])]))
return res
def norm(x):
return sqrt(np.dot(x, x))
def gradient_descent(function, grad_function, x):
k = 1
d = cal_grad_ret(grad_function, x)
d = np.dot(-1, d).tolist()
alpha = 0.01
while norm(d) > 0.00001 and k < 1000:
d = cal_grad_ret(grad_function, x)
d = np.dot(-1, d).tolist()
# armijo_goladstein准则选择步长
# alpha = armijo_goldstein(function, grad_function, x, d)
# armijo_wlofe准则选择步长
# alpha = armijo_wlofe(function, grad_function, x, d)
x = np.add(x, np.dot(alpha, d))
k = k + 1
# 返回最小值点,函数最小值和迭代次数
return x, cal_function_ret(function, x), k
if __name__ == '__main__':
function = function_define()
grad_function = cal_grad_funciton(function)
print("函数为:", function)
print("梯度函数为:", grad_function)
x0 = [-10, 10]
Min, MinValue, Iterator = gradient_descent(function, grad_function, x0)
print("最小值点为:", Min)
print("最小值为:", MinValue)
print("迭代次数为:", Iterator)
全梯度下降算法(FGD):计算训练集所有样本误差,对其求和再取平均值作为目标函数。权重向量沿其梯度相反的方向移动,从而使当前目标函数减少得最多。因为在执行每次更新时,需要在整个数据集上计算所有的梯度,所以速度会很慢,同时,其在整个训练数据集上计算损失函数关于参数θ的梯度
θ = θ − η ⋅ ∇ θ J ( θ ) \theta = \theta -\eta \cdot \nabla_{\theta}J(\theta) θ=θ−η⋅∇θJ(θ)
随机梯度下降算法(SGD):由于FGD每迭代更新一次权重都需要计算所有样本误差,而实际问题中经常有上亿的训练样本,故效率偏低,且容易陷入局部最优解,因此提出了随机梯度下降算法。其每轮计算的目标函数不再是全体样本误差,而仅是单个样本误差,即每次只代入计算一个样本目标函数的梯度来更新权重,再取下一个样本重复此过程,直到损失函数值停止下降或损失函数值小于某个设定的阈值。此过程简单,高效,通常可以较好地避免更新迭代收敛到局部最优解。其迭代形式为
θ = θ − η ⋅ ∇ θ J ( θ ; x ( i ) ; y ( i ) ) \theta = \theta -\eta \cdot \nabla_{\theta}J(\theta;x^{(i)};y^{(i)}) θ=θ−η⋅∇θJ(θ;x(i);y(i))
小批量梯度下降算法:小批量梯度下降算法是FG和SG的折中方案,在一定程度上兼顾了以上两种方法的优点。每次从训练样本集上随机抽取一个小样本集,在抽出来的小样本集上采用FGD迭代更新权重。被抽出的小样本集所含样本点的个数称为batch_size
,通常设置为2的幂次方,更有利于GPU加速处理
batch_size=1
,则变成了SGDbatch_size=n
,则变成了FGD
θ = θ − η ⋅ ∇ θ J ( θ ; x ( i : i + n ) ; y ( i : i + n ) ) \theta = \theta -\eta \cdot \nabla_{\theta}J(\theta;x^{(i:i+n)};y^{(i:i+n)}) θ=θ−η⋅∇θJ(θ;x(i:i+n);y(i:i+n))
随机梯度下降算法(SGD):当问题的条件数很大,也即问题比较病态时,梯度下降法的收敛性质会受到很大影响。Barzilai-Borwein方法是一种特殊的梯度法,经常比一般的梯度法有着更好的效果,从形式上来看,BB方法的下降方向仍然是点 x k x^{k} xk处的负梯度方向,但是步长 a k a_{k} ak并不是直接由线搜索算法给出的
考虑下降法格式
x k + 1 = x k − α k ∇ f ( x k ) x^{k+1}=x^{k}-\alpha_{k}\nabla f(x^{k}) xk+1=xk−αk∇f(xk)
这种格式也可以写成
x k + 1 = x k − D k ∇ f ( x k ) x^{k+1}=x^{k}-D^{k}\nabla f(x^{k}) xk+1=xk−Dk∇f(xk)
其中 D k = α k I D^{k}=\alpha_{k}I Dk=αkI。BB方法选取的 α k \alpha_{k} αk是如下两个最优问题之一的解
其中引入记号
容易验证问题①和②的解分别为
α B B 1 k = d e f ( s k − 1 ) T y k − 1 ( y k − 1 ) T y k − 1 \alpha^{k}_{BB1}\mathop{=}\limits^{def}\frac{(s^{k-1})^{T}y^{k-1}}{(y^{k-1})^{T}y^{k-1}} αBB1k=def(yk−1)Tyk−1(sk−1)Tyk−1
α B B 2 k = d e f ( s k − 1 ) T s k − 1 ( s k − 1 ) T y k − 1 \alpha^{k}_{BB2}\mathop{=}\limits^{def}\frac{(s^{k-1})^{T}s^{k-1}}{(s^{k-1})^{T}y^{k-1}} αBB2k=def(sk−1)Tyk−1(sk−1)Tsk−1
由此可得到BB方法的两种迭代格式
下图给出了BB方法的一种框架