这里,我们以优化下面函数为例:
z = 0.2 * x**2 + 9 * x + 0.5 * y ** 2 + 3 * y
这个函数是二元的,所以,梯度的维度是2,因此,梯度可以表示为数组
derivative_x = 0.4 * x + 9
derivative_y = y + 3
[derivative_x, derivative_y]
需要注意的是,x表示第一维度,y表示第二维度,z表示函数的值。
梯度下降
import math
def func(x, y):
z = 0.2 * x**2 + 9 * x + 0.5 * y ** 2 + 3 * y
return z
def get_derivative(x, y):
derivative_x = 0.4 * x + 9
derivative_y = y + 3
# 计算梯度的大小,用来判断迭代终止条件
norm = math.sqrt(derivative_x ** 2 + derivative_y ** 2)
return derivative_x, derivative_y, norm
step_size = 0.2
x = 1
y = 40
_, _, norm = get_derivative(x, y)
count = 0
while norm > 0.001:
z = func(x, y)
x1, y1, norm = get_derivative(x, y)
x = x - step_size * x1 # 更新第一维的值
y = y - step_size * y1 # 更新第二维的值
count += 1
print(count, z)
1 929.2
2 579.4148799999999
3 352.050802432
4 203.57403789844477
5 106.04038645804366
6 41.49563639780015
7 -1.6100912410862662
8 -30.718811874893383
9 -50.635813985910055
、、、
102 -105.7499946505782
103 -105.7499954722494
104 -105.74999616771186
105 -105.74999675635132
106 -105.74999725457577
107 -105.74999767627291
108 -105.74999803319739
109 -105.74999833529827
110 -105.74999859099646
111 -105.74999880741939
牛顿法
使用泰勒展开的二阶展开项,便得到下面的公式,从优化f(x)变成优化下面的函数了。
min { f ( x k ) + ∇ f ( x k ) T ( x − x k ) + 1 / 2 ( x − x k ) T ∇ 2 f ( x k ) ( x − x k ) } \left\{f(x^k) + \nabla f(x^k)^T(x-x^k) + 1/2(x-x^k)^T\nabla^2f(x^k)(x-x^k)\right\} { f(xk)+∇f(xk)T(x−xk)+1/2(x−xk)T∇2f(xk)(x−xk)}
这个式子是f(x)的二阶近似。因此,它的优点是在优化时使用到了二阶的信息(梯度下降至使用了一阶的),因此具有二阶收敛性,即更快的收敛速度。但是,代价便是计算量变大了,需要计算复杂的hasen矩阵。
- x 0 , e , k = 0 x^0, e, k = 0 x0,e,k=0
- if ∇ f ( x k ) ≤ e \nabla f(x^k)\leq e ∇f(xk)≤e
- d k = − [ ∇ 2 f ( x k ) ] − 1 ∇ f ( x k ) d^k = -[\nabla^2 f(x^k)]^{-1}\nabla f(x^k) dk=−[∇2f(xk)]−1∇f(xk) ,两矩阵相乘
- x k + 1 = x k + d k ; k = k + 1 x^{k+1} = x^{k} + d^k; k = k + 1 xk+1=xk+dk;k=k+1
import numpy as np
def func(x, y):
z = 0.2 * x**2 + 9 * x + 0.5 * y ** 2 + 3 * y
return z
def get_derivative(x, y):
derivative_x = 0.4 * x + 9
derivative_y = y + 3
return [derivative_x, derivative_y]
def getNorm(x, y):
derivative_x, derivative_y = get_derivative(x, y)
norm = math.sqrt(derivative_x ** 2 + derivative_y ** 2)
return norm
def hesen(x, y):
# 需要分别计算xx,xy,yx,yy的二次偏导数
return [[0.4, 0], [0, 1]]
hesen_matrix = hesen(x, y)
print(hesen_matrix)
inv = np.linalg.inv(hesen_matrix)
d_k = - (inv * get_derivative(x, y))
print(d_k)
derivative = np.array(get_derivative(x, y))
print(derivative)
d_k * derivative
x = 1
y = 40
count = 0
while getNorm(x, y) > 0.0001:
hesen_matrix = hesen(x, y)
inv = np.linalg.inv(hesen_matrix)
d_k = - (inv * np.array(get_derivative(x, y)))
x = x + d_k[0][0]
y = y + d_k[1][1]
count += 1
z = func(x, y)
print(count, z)
函数最优值为:-105.75,且只需要迭代一次。很明显,这个结果和梯度下降法一样。
其他
这里的代码仅仅是为了验证这两个优化算法的思想,对代码优雅性暂不做考虑。