python牛顿法与拟牛顿法_拟牛顿法实现(Python)

接着上篇写的

拟牛顿法其实也是很简单了。为什么要研究出这种方法呢,就是因为常规的牛顿法每次迭代更新x值时都要计算Hessian矩阵。这是个二阶导矩阵,当x的维度增大时,它的计算量也是成指数倍增加的。比如:

equation?tex=X%3D%5Bx_%7B1%7D%2C+x_%7B2%7D%2C+%5Ccdot+%5Ccdot+%5Ccdot%2C+x_%7Bn%7D%5D ,那么H矩阵的维度就是n*n。

所以,我们就想拿个一阶矩阵来近似于Hessian矩阵。过程如下图:

其实思想都很简单。DFP方法是用一个G正定矩阵来近似于Hessian的逆矩阵,而BFGS方法是用一个B正定矩阵来近似于Hessian矩阵。两个矩阵的更新表达式如下所示:

equation?tex=G_%7Bk%2B1%7D%3DG_%7Bk%7D%2B%5Cfrac%7B%5Cdelta_%7Bk%7D%5Ccdot+%5Cdelta_%7Bk%7D%5E%7B%5Ctau%7D%7D%7B%5Cdelta_%7Bk%7D%5E%7B%5Ctau%7D%5Ccdot+y_%7Bk%7D%7D-%5Cfrac%7BG_%7Bk%7D+%5Ccdot+y_%7Bk%7D+%5Ccdot+y_%7Bk%7D%5E%7B%5Ctau%7D+%5Ccdot+G_%7Bk%7D%7D%7By_%7Bk%7D%5E%7B%5Ctau%7D+%5Ccdot+G_%7Bk%7D+%5Ccdot+y_%7Bk%7D%7D

equation?tex=B_%7Bk%2B1%7D%3DB_%7Bk%7D%2B%5Cfrac%7By_%7Bk%7D+%5Ccdot+y_%7Bk%7D%5E%7B%5Ctau%7D%7D%7By_%7Bk%7D%5E%7B%5Ctau%7D%5Ccdot+%5Cdelta_%7Bk%7D%7D-%5Cfrac%7BB_%7Bk%7D+%5Ccdot+%5Cdelta_%7Bk%7D+%5Ccdot+%5Cdelta_%7Bk%7D%5E%7B%5Ctau%7D+%5Ccdot+B_%7Bk%7D%7D%7B%5Cdelta_%7Bk%7D%5E%7B%5Ctau%7D+%5Ccdot+B_%7Bk%7D+%5Ccdot+%5Cdelta_%7Bk%7D%7D

公式看上去都大同小异的,虽然式子有很多,但求起来都挺简单的。另外,BFGS方法是当前最流行的拟牛顿法,收敛的也更快。

下面是我的代码复现。

BFGS算法:

def bfgs_newton(f, x, iters):

"""

实现BFGS拟牛顿法

:param f: 原函数

:param x: 初始值

:param iters: 遍历的最大epoch

:return: 最终更新完毕的x值

"""

# 步长。设为1才能收敛,小于1不能收敛

learning_rate = 1

# 初始化B正定矩阵

B = np.eye(2)

x_len = x.shape[0]

# 一阶导g的第二范式的最小值(阈值)

epsilon = 1e-5

for i in range(1, iters):

g = jacobian(f, x)

if np.linalg.norm(g) < epsilon:

break

p = np.linalg.solve(B, g)

# 更新x值

x_new = x - p*learning_rate

print("第" + str(i) + "次迭代后的结果为:", x_new)

g_new = jacobian(f, x_new)

y = g_new - g

k = x_new - x

y_t = y.reshape([x_len, 1])

Bk = np.dot(B, k)

k_t_B = np.dot(k, B)

kBk = np.dot(np.dot(k, B), k)

# 更新B正定矩阵。完全按照公式来计算

B = B + y_t*y/np.dot(y, k) - Bk.reshape([x_len, 1]) * k_t_B / kBk

x = x_new

return x

算法的执行效果如下:

可以看到,在第23次就收敛了。

DFP算法:

def dfp_newton(f, x, iters):

"""

实现DFP拟牛顿算法

:param f: 原函数

:param x: 初始值

:param iters: 遍历的最大epoch

:return: 最终更新完毕的x值

"""

# 步长

learning_rate = 1

# 初始化B正定矩阵

G = np.eye(2)

x_len = x.shape[0]

# 一阶导g的第二范式的最小值(阈值)

epsilon = 1e-5

for i in range(1, iters):

g = jacobian(f, x)

if np.linalg.norm(g) < epsilon:

break

p = np.dot(G, g)

# 更新x值

x_new = x - p * learning_rate

print("第" + str(i) + "次迭代后的结果为:", x_new)

g_new = jacobian(f, x_new)

y = g_new - g

k = x_new - x

Gy = np.dot(G, y)

y_t_G = np.dot(y, G)

yGy = np.dot(np.dot(y, G), y)

# 更新G正定矩阵

G = G + k.reshape([x_len, 1]) * k / np.dot(k, y) - Gy.reshape([x_len, 1]) * y_t_G / yGy

x = x_new

return x

算法执行效率如下:

可以看到,执行到1000次也没有收敛。由此证明了BFGS算法的收敛性确实要优于DFP算法。

最后,我还要提一提矩阵与向量的相关计算问题。我在这块儿吃了一些苦头,这儿推荐一篇

完整版代码在

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值