本节介绍:
牛顿法的基本思路是用二次函数来局部逼近目标函数 f 并解近似函数的极小点作为下一个迭代点,迭代公式
拟牛顿法的迭代思路是
对于正定矩阵 H0,H1,…,Hn 当目标函数为 n 维二次型时,他们必须满足
由以上可以看到, Hk 并不是唯一确定的因此,我们需要找到一种方式让 Hk 满足条件并且容易计算
下面讲两种算法,分别是DFP和BFGS
- 选择初始点 x0,对称正定矩阵H0
- 计算 gk , 如果 ||gk||<ϵ 停止迭代
- 计算 dk=−Hkgk
计算 αk=argminα≥0f(xk+αdk)
xk+1=xk+αkdk
Δxk=αkdk
Δgk=gk+1−gk
tmp=HkΔgk
Hk+1=Hk+ΔxkΔxTkΔxTkΔgk−tmptmpTΔgTkHkΔgk
这里得证明这样构造的 Hk 满足两个条件
定理11.3
用DFP算法求解 二次型问题时,Hessian matrix Q=QT,有,Hk+1Δgi=Δxi,0≤i≤k
因证明过程较为难写,故这里直接给出书上证明:
定理11.4
假定 gk≠0, 只要有 Hk 正定 那么 Hk+1 正定
书上说,DFP 算法处理某些较大的非二次型问题是容易被’卡’ 住,所以后面有四个大牛提出了一种BFGS算法解决这一问题
前面提到的 Hk 满足的第二个条件即 Hk+1Δgi=Δxi,0≤i≤k 是根据 Δgi=QΔxi , 构造的 Q−1 的近似矩阵 Hk , 同样,我们可以构造 Q 的近似矩阵 Bk+1 , 那么 Bk+1 满足
利用互补的概念,应用DFP算法中的更新公式我们可以得到 Bk 的更新公式
关于这个式子我们有如下引理
引理11.1
如果 A 非奇异, 列向量 u,v,s.t,1+vTA−1u≠0 , 那么其逆矩阵存在且
直接计算可证
因此应用两次引理,可得
BFGS在实践中较为常用scipy.optimize 里面也有实现.以下是我的实现
def dfp(fun, grad, x0, args=(), g_args=(), tol=1e-8, max_iter=5000):
h0 = np.eye(len(x0))
g_0 = grad(*((x0,) + g_args))
alpha = lambda a, x, d: fun(*((x + a * d,) + args))
for i in range(max_iter):
if is_stop(g_0, np.zeros(g_0.shape), tol):
break
d = -h0.dot(g_0)
alp = minimize_scalar(alpha, bounds=(0, 10000), args=(x0, d), method='brent', tol=1e-4)
alp = alp.x
x_next = x0 + alp * d
delta_x = (alp * d).reshape((len(x0), 1))
g_next = grad(*((x_next,) + g_args))
delta_g = g_next - g_0
delta_g = delta_g.reshape((delta_x.shape))
tmp = h0.dot(delta_g)
h0 = h0 + delta_x.dot(delta_x.T) / (delta_x.T.dot(delta_g)) - tmp.dot(tmp.T) / (delta_g.T.dot(tmp))
x0 = x_next
g_0 = g_next
return OptimizeResult({'nit': i, 'x': x0, 'jac': g_0, 'fun': fun(*((x0,) + args))})
def bfgs(fun, grad, x0, args=(), g_args=(), tol=1e-8, max_iter=5000):
h0 = np.eye(len(x0))
g_0 = grad(*((x0,) + g_args))
alpha = lambda a, x, d: fun(*((x + a * d,) + args))
for i in range(max_iter):
if is_stop(g_0, np.zeros(g_0.shape), tol):
break
d = -h0.dot(g_0)
alp = minimize_scalar(alpha, bounds=(0, 10000), args=(x0, d), method='brent', tol=1e-4)
alp = alp.x
x_next = x0 + alp * d
delta_x = (alp * d).reshape((len(x0), 1))
g_next = grad(*((x_next,) + g_args))
delta_g = g_next - g_0
delta_g = delta_g.reshape((delta_x.shape))
tmp = h0.dot(delta_g).dot(delta_x.T)
tmp1 = (1+delta_g.T.dot(h0).dot(delta_g)/(delta_g.T.dot(delta_x)))*delta_x.dot(delta_x.T)/(delta_x.T.dot(delta_g))
tmp2 = (tmp + tmp.T)/(delta_g.T.dot(delta_x))
h0 = h0 + tmp1 - tmp2
x0 = x_next
g_0 = g_next
return OptimizeResult({'nit': i, 'x': x0, 'jac': g_0, 'fun': fun(*((x0,) + args))})
DFP 与bfgs除了更新公式以外其他几乎完全一样
以下是在rosen 函数下的实验效果
bfgs result
fun: 6.0875493993460361e-23
jac: array([ -2.87637469e-10, 1.46505030e-10])
nit: 17
x: array([ 1., 1.])
dfp result
fun: 2.4549731009788525e-23
jac: array([ -1.82560633e-10, 9.29922805e-11])
nit: 17
x: array([ 1., 1.])
两次均优于共轭梯度算法.
在实际使用中我们用的最多的还是SGD这样的用梯度来优化的算法,因为可以看到二阶项的一个重要的弊端就是空间复杂度太高,需要存贮二阶项,这对于 n 维的数据来说,需要 n2 的内存
本文链接http://blog.csdn.net/Dylan_Frank/article/details/78287680