文章目录
在介绍下降方法之前,我们需要先看一些预备的知识。
预备知识
我们假设目标函数在下水平集
S
S
S上是强凸的,这是指存在
m
>
0
m > 0
m>0,使得
∇
2
f
(
x
)
⪰
m
I
\nabla^2 f(x) \succeq mI
∇2f(x)⪰mI
对于任意
x
x
x成立。
注意,这个广义不等式,是指
∇
2
f
(
x
)
−
m
I
\nabla^2 f(x) - mI
∇2f(x)−mI半正定,即,
∇
2
f
(
x
)
\nabla^2 f(x)
∇2f(x)的最小特征值大于等于
m
m
m。
对于
x
,
y
∈
S
x, y\in S
x,y∈S,我们有广义泰勒展开:
f
(
y
)
=
f
(
x
)
+
∇
T
(
y
−
x
)
+
1
2
(
y
−
x
)
T
∇
2
f
(
z
)
(
y
−
x
)
f(y)=f(x)+\nabla^{T}(y-x)+\frac{1}{2}(y-x)^{T}\nabla^{2}f(z)(y-x)
f(y)=f(x)+∇T(y−x)+21(y−x)T∇2f(z)(y−x)
z
∈
[
x
,
y
]
z \in [x,y]
z∈[x,y]
利用强凸性假设,可以推得不等式(同时可知,
S
S
S是有界的):
f
(
y
)
≥
f
(
x
)
+
∇
f
(
x
)
T
(
y
−
x
)
+
m
2
∥
y
−
x
∥
2
2
f(y) \ge f(x) + \nabla f(x)^{T}(y-x) + \frac{m}{2}\|y-x\|_2^2
f(y)≥f(x)+∇f(x)T(y−x)+2m∥y−x∥22
通过,不等式右边是关于
y
y
y的二次凸函数,令其关于
y
y
y的导数等于零,可以得到该二次函数的最优解
y
~
=
x
−
(
1
/
m
)
∇
f
(
x
)
\widetilde{y} = x-(1/m)\nabla f(x)
y
=x−(1/m)∇f(x),所以
f
(
y
)
≥
f
(
x
)
−
1
2
m
∥
∇
f
(
x
)
∥
2
2
f(y) \ge f(x) - \frac{1}{2m}\|\nabla f(x)\|_2^2
f(y)≥f(x)−2m1∥∇f(x)∥22
x
∗
x^*
x∗是
f
(
x
)
f(x)
f(x)的全局最优解,
p
∗
=
f
(
x
∗
)
p^*=f(x^*)
p∗=f(x∗),因为上述不等式对于任意
y
y
y都成立,所以:
p
∗
≥
f
(
x
)
−
1
2
m
∥
∇
f
(
x
)
∥
2
2
p^* \ge f(x) - \frac{1}{2m} \|\nabla f(x)\|_2^2
p∗≥f(x)−2m1∥∇f(x)∥22
由此可见,任何梯度足够小的点都是近似最优解。由于,
∥
∇
f
(
x
)
∥
2
≤
(
2
m
ϵ
)
1
/
2
⇒
f
(
x
)
−
p
∗
≤
ϵ
\|\nabla f(x)\|_2 \le (2m\epsilon)^{1/2} \Rightarrow f(x) - p^* \le \epsilon
∥∇f(x)∥2≤(2mϵ)1/2⇒f(x)−p∗≤ϵ
我们可以将其解释为次优性条件。
因为
S
S
S有界,而
∇
2
f
(
x
)
\nabla^2 f(x)
∇2f(x)的最大特征值是
x
x
x在
S
S
S上的连续函数,所以它在
S
S
S上有界,即存在常数
M
M
M使得:
∇
2
f
(
x
)
⪯
M
I
\nabla^2 f(x) \preceq MI
∇2f(x)⪯MI
关于Hessian矩阵的这个上界,意味着,对任意的
x
,
y
∈
S
x, y \in S
x,y∈S:
f
(
y
)
≤
f
(
x
)
+
∇
f
(
x
)
T
(
y
−
x
)
+
M
2
∥
y
−
x
∥
2
2
f(y) \le f(x) + \nabla f(x)^T(y-x) + \frac{M}{2} \|y-x\|_2^2
f(y)≤f(x)+∇f(x)T(y−x)+2M∥y−x∥22
同样,可以得到:
p
∗
≤
f
(
x
)
−
1
2
M
∥
∇
f
(
x
)
∥
2
2
p^* \le f(x) - \frac{1}{2M} \|\nabla f(x)\|_2^2
p∗≤f(x)−2M1∥∇f(x)∥22
注意:
m
I
⪯
∇
2
f
(
x
)
⪯
M
I
mI \preceq \nabla^2 f(x) \preceq MI
mI⪯∇2f(x)⪯MI
κ
=
M
/
m
\kappa = M/m
κ=M/m为矩阵
∇
2
f
(
x
)
\nabla^2 f(x)
∇2f(x)的条件数的上界。通常,
κ
\kappa
κ越小(越接近1),梯度下降收敛的越快。这个条件会在收敛性分析中反复用到。
下降方法
由凸性知:
∇
f
(
x
(
k
)
)
T
(
y
−
x
(
k
)
)
≥
0
\nabla f(x^{(k)})^T (y-x^{(k)}) \ge 0
∇f(x(k))T(y−x(k))≥0意味着
f
(
y
)
≥
f
(
x
(
k
)
)
f(y) \ge f(x^{(k)})
f(y)≥f(x(k)),因此一个下降方法中的搜索方向必须满足:
∇
f
(
x
(
k
)
)
T
Δ
x
(
k
)
<
0
\nabla f(x^{(k)})^T \Delta x^{(k)} < 0
∇f(x(k))TΔx(k)<0
我们并没有限定下降方向
Δ
x
\Delta x
Δx必须为逆梯度方向,事实上这种选择也仅仅是局部最优的策略。
所以算法是如此的:
停止准则通常根据次优性条件,采用
∥
∇
f
(
x
)
∥
≤
η
\|\nabla f(x)\| \le \eta
∥∇f(x)∥≤η,其中
η
\eta
η是小正数。
梯度下降方法的算法如下:
精确直线搜索
精确直线搜索需要我们求解下面的单元的优化问题:
t
=
a
r
g
m
i
n
s
≥
0
f
(
x
+
s
Δ
x
)
t = argmin_{s \ge 0} f(x+s \Delta x)
t=argmins≥0f(x+sΔx)
因为问题是一元的,所以相对来说比较简单,可以通过一定的方法来求解该问题,特殊情况下,可以用解析方法来确定其最优解。
收敛性分析
在收敛性分析的时候,我们选择
Δ
x
:
=
−
∇
f
(
x
)
\Delta x := -\nabla f(x)
Δx:=−∇f(x)。
我们定义:
f
~
(
t
)
=
f
(
x
−
t
∇
f
(
x
)
)
\widetilde{f}(t)=f(x-t\nabla f(x))
f
(t)=f(x−t∇f(x)),同时,我们只考虑满足
x
−
t
∇
f
(
x
)
∈
S
x-t\nabla f(x) \in S
x−t∇f(x)∈S的
t
t
t。通过预备知识,我们容易得到下面的一个上界:
对上述不等式俩边同时关于
t
t
t求最小,左边等于
f
~
(
t
e
x
a
c
t
)
\widetilde{f}(t_{exact})
f
(texact),右边是一个简单的二次型函数,其最小解为
t
=
1
/
M
t=1/M
t=1/M,因此我们有:
f
(
x
+
)
=
f
~
(
t
e
x
a
c
t
)
≤
f
(
x
)
−
1
M
∥
∇
(
f
(
x
)
)
∥
2
2
f(x^{+}) = \widetilde{f}(t_{exact}) \le f(x) - \frac{1}{M} \|\nabla(f(x))\|_2^2
f(x+)=f
(texact)≤f(x)−M1∥∇(f(x))∥22
从该式俩边同时减去
p
∗
p^*
p∗,我们得到
f
(
x
+
)
−
p
∗
≤
f
(
x
)
−
p
∗
−
1
M
∥
∇
f
(
x
)
∥
2
2
f(x^+)-p^* \le f(x) - p^* - \frac{1}{M} \|\nabla f(x)\|_2^2
f(x+)−p∗≤f(x)−p∗−M1∥∇f(x)∥22
又
∥
∇
f
(
x
)
∥
2
2
≥
2
m
(
f
(
x
)
−
p
∗
)
\|\nabla f(x)\|_2^2 \ge 2m(f(x)-p^*)
∥∇f(x)∥22≥2m(f(x)−p∗),可以断定:
f
(
x
+
)
−
p
∗
≤
(
1
−
m
/
M
)
(
f
(
x
)
−
p
∗
)
f(x^+) -p ^* \le (1-m/M)(f(x)-p^*)
f(x+)−p∗≤(1−m/M)(f(x)−p∗)
重复进行,可以看出,
f
(
x
+
)
−
p
∗
≤
(
1
−
m
/
M
)
k
(
f
(
x
)
−
p
∗
)
f(x^+) -p ^* \le (1-m/M)^{k}(f(x)-p^*)
f(x+)−p∗≤(1−m/M)k(f(x)−p∗)
收敛性分析到这为止,更多内容翻看凸优化。
回溯直线搜索
回溯直线搜索,不要求每次都减少最多,只是要求减少足够量就可以了。其算法如下:
回溯搜索从单位步长开始,按比例逐渐减少,直到满足停止条件
f
(
x
+
t
Δ
x
)
≤
f
(
x
)
+
α
t
∇
f
(
x
)
T
Δ
x
f(x+t\Delta x) \le f(x) + \alpha t \nabla f(x)^T \Delta x
f(x+tΔx)≤f(x)+αt∇f(x)TΔx。
最后的结果
t
t
t满足
t
≥
m
i
n
{
1
,
β
t
0
}
t \ge min\{1, \beta t_0 \}
t≥min{1,βt0}。
在实际计算中,我们首先用 β \beta β乘 t t t直到 x + t Δ x ∈ d o m f x+t\Delta x \in dom f x+tΔx∈domf,然后才开始检验停止准则是否成立。
参数 α \alpha α的正常取值在0.01 和 0.3 之间表示我们可以接受的 f f f的减少量在基于线性外推预测的减少量的 1 % 1\% 1%和 1 % 1\% 1%之间。参数 β \beta β的正常取值在 0.1(对应非常粗糙的搜索)和 0.8(对应于不太粗糙的搜索)之间。
收敛性分析
我们先证明,只要
0
≤
t
≤
1
/
M
0 \le t \le 1/M
0≤t≤1/M,就能满足回溯停止条件:
f
~
(
t
)
≤
f
(
x
)
−
α
t
∥
∇
f
(
x
)
∥
\widetilde{f}(t) \le f(x) - \alpha t \|\nabla f(x)\|
f
(t)≤f(x)−αt∥∇f(x)∥
首先,注意到:
0
≤
t
≤
1
/
M
⇒
−
t
+
M
t
2
2
≤
−
t
/
2
0 \le t \le 1/M \Rightarrow -t + \frac{Mt^2}{2} \le -t/2
0≤t≤1/M⇒−t+2Mt2≤−t/2
由于
α
<
1
/
2
\alpha < 1/2
α<1/2(这也是为什么我们限定
α
<
1
/
2
\alpha < 1/2
α<1/2的原因),所以可以得到:
因此,回溯直线搜索将终止于
t
=
1
t=1
t=1或者
t
≥
β
/
M
t\ge \beta/M
t≥β/M。故:
f
(
x
+
)
≤
f
(
x
)
−
min
{
α
,
(
β
α
/
M
)
}
∥
∇
f
(
x
)
∥
2
2
f(x^+) \le f(x) - \min \{\alpha, (\beta \alpha / M) \} \|\nabla f(x)\|_2^2
f(x+)≤f(x)−min{α,(βα/M)}∥∇f(x)∥22
俩边减去
p
∗
p^*
p∗,再结合
∥
∇
f
(
x
)
∥
2
2
≥
2
m
(
f
(
x
)
−
p
∗
)
\|\nabla f(x)\|_2^2 \ge 2m(f(x)-p^*)
∥∇f(x)∥22≥2m(f(x)−p∗)可导出:
f
(
x
+
)
−
p
∗
≤
(
1
−
min
{
2
m
α
,
2
β
α
m
/
M
}
)
k
(
f
(
x
)
−
p
∗
)
f(x^+)-p^* \le (1-\min \{2m\alpha, 2 \beta \alpha m/M \})^{k}(f(x) - p^*)
f(x+)−p∗≤(1−min{2mα,2βαm/M})k(f(x)−p∗)
数值试验
f ( x ) = 1 2 ( x 1 2 + γ x 2 2 ) f(x)=\frac{1}{2}(x_1^2+\gamma x_2^2) f(x)=21(x12+γx22)
我们选取初始点为 ( γ , 1 ) , γ = 10 (\gamma, 1),\gamma=10 (γ,1),γ=10
下图是精确直线搜索:
下图是回溯直线搜索, α = 0.4 , β = 0.7 \alpha=0.4, \beta=0.7 α=0.4,β=0.7可以看出来,每一次的震荡的幅度比上面的要大一些。
f ( x ) = e x 1 + 3 x 2 − 0.3 + e x 1 − 3 x 2 − 0.3 + e − x 1 − 0.1 f(x)=e^{x_1+3x_2-0.3}+e^{x_1-3_x2-0.3}+e^{-x_1-0.1} f(x)=ex1+3x2−0.3+ex1−3x2−0.3+e−x1−0.1
下面采用的是回溯直线搜索,
α
=
0.4
,
β
=
0.7
\alpha=0.4,\beta=0.7
α=0.4,β=0.7,初始点为
(
7
,
3
)
(7,3)
(7,3)
初始点为
(
−
7
,
−
3
)
(-7, -3)
(−7,−3)
α
=
0.2
,
β
=
0.7
\alpha=0.2,\beta=0.7
α=0.2,β=0.7
代码
import numpy as np
class GradDescent:
"""
梯度下降方法
"""
def __init__(self, f, x):
assert hasattr(f, "__call__"), \
"Invalid function {0}".format(f)
self.__f = f
self.x = x
self.y = self.__f(x)
self.__process = [(self.x, self.y)]
@property
def process(self):
"""获得梯度下降过程"""
return self.__process
def grad1(self, update_x, error=1e-5):
"""精确收缩算法
update_x: 用来更新x的函数,这个我们没办法在这里给出
error: 梯度的误差限,默认为1e-5
"""
assert hasattr(update_x, "__call__"), \
"Invalid function {0}".format(update_x)
error = error if error > 0 else 1e-5
while True:
x = update_x(self.x)
if (x - self.x) @ (x - self.x) < error:
break
else:
self.x = x
self.y = self.__f(self.x)
self.__process.append((self.x, self.y))
def grad2(self, gradient, alpha, beta, error=1e-5):
"""回溯直线收缩算法
gradient: 梯度需要给出
alpha: 下降的期望值 (0, 0.5)
beta:每次更新的倍率 (0, 1)
error: 梯度的误差限,默认为1e-5
"""
assert hasattr(gradient, "__call__"), \
"Invalid gradient"
assert 0 < alpha < 0.5, \
"alpha should between (0, 0.5), but receive {0}".format(alpha)
assert 0 < beta < 1, \
"beta should between (0, 1), but receive {0}".format(beta)
error = error if error > 0 else 1e-5
def search_t(alpha, beta):
t = 1
t_old = 1
grad = -gradient(self.x)
grad_module = grad @ grad
while True:
newx = self.x + t * grad
newy = self.__f(newx)
if newy < self.y - alpha * t * grad_module:
return t_old
else:
t_old = t
t = t_old * beta
while True:
t = search_t(alpha, beta)
x = self.x - t * gradient(self.x)
if (t * gradient(self.x)) @ (t * gradient(self.x)) < error:
break
else:
self.x = x
self.y = self.__f(self.x)
self.__process.append((self.x, self.y))
r = 10.
def f(x):
vec = np.array([1., r], dtype=float)
return 0.5 * (x ** 2) @ vec
def f2(x):
x0 = x[0]
x1 = x[1]
return np.exp(x0+3*x1-0.1) \
+ np.exp(x0-3*x1-0.3) \
+ np.exp(-x0-0.1)
def gradient2(x):
x0 = x[0]
x1 = x[1]
grad1 = np.exp(x0+3*x1-0.1) \
+ np.exp(x0-3*x1-0.3) \
- np.exp(-x0-0.1)
grad2 = 3 * np.exp(x0+3*x1-0.1) \
-3 * np.exp(x0-3*x1-0.3)
return np.array([grad1, grad2])
def update(x):
t = -(x[0] ** 2 + r ** 2 * x[1] ** 2) \
/ (x[0] ** 2 + r ** 3 * x[1] ** 2)
x0 = x[0] + t * x[0]
x1 = x[1] + t * x[1] * r
return np.array([x0, x1])
def gradient(x):
diag_martix = np.diag([1., r])
return x @ diag_martix
x_prime = np.array([7, 3.], dtype=float)
ggg = GradDescent(f2, x_prime)
#ggg.grad1(update)
ggg.grad2(gradient2, alpha=0.2, beta=0.7)
process = ggg.process
import matplotlib.pyplot as plt
import matplotlib.path as mpath
fig, ax = plt.subplots(figsize=(10, 6))
w1 = 4
w2 = 10
Y, X = np.mgrid[-w1:w1:300j, -w2:w2:300j]
U = X
V = r * Y
ax.streamplot(X, Y, U, V)
process_x = list(zip(*process))[0]
print(process_x)
path = mpath.Path(process_x)
x0, x1 = zip(*process_x)
ax.set_xlim(-8, 8)
ax.set_ylim(-4, 4)
ax.plot(x0, x1, "go-")
plt.show()