本节使用 Python \text{Python} Python对共轭梯度法的精确搜索与非精确搜索进行示例。
本人数学水平与代码水平有限,欢迎小伙伴一起讨论~关联文章:
非标准二次型——这意味着:对应函数
f
(
x
)
=
x
T
Q
x
f(x)=xTQx
f
(
x
)
=
x
T
Q
x
Q
=
(
1
1
1
2
)
;
x
∈
R
2
f(x) = x^T \mathcal Q x \quad \mathcal Q = (1112)
的等值线为例,使用
Python
\text{Python}
Python做出基于
Wolfe Condition
\text{Wolfe Condition}
Wolfe Condition与精确搜索的共轭梯度法效果。完整代码见文章最下方,后面不再赘述。如果使用二元函数进行表示,可以表示为如下形式:
f
(
x
1
,
x
2
)
=
(
x
1
x
2
)
(
1
1
1
2
)
(
x
1
x
2
)
=
(
x
1
+
x
2
x
1
+
2
x
2
)
(
x
1
x
2
)
=
x
1
2
+
2
x
1
x
2
+
2
x
2
2
f(x1,x2)=(x1 x2)(1112)(x1x2)=(x1+x2x1+2x2)(x1x2)=x21+2x1x2+2x22
很明显:该函数中不仅包含平方项,并且包含交叉项。因而无法将
x
1
,
x
2
x_1,x_2
x1,x2进行独立表示。对于等值线函数表示如下:
基于
C
>
0
\mathcal C>0
C>0的不同取值,可以得到相应大小的等值线结果。
x
1
2
+
2
x
1
x
2
+
2
x
2
2
=
C
x_1^2 + 2 x_1 x_2 + 2x_2^2 = \mathcal C
x12+2x1x2+2x22=C
针对上述问题,这里的思路是:在给定等值线
C
\mathcal C
C的条件下,确定
x
1
x_1
x1的范围。判断
x
1
x_1
x1是否为边缘的条件是:将
x
1
,
C
x_1,\mathcal C
x1,C均视作常数,此时上述函数就是关于
x
2
x_2
x2的一元二次方程,只需要求解根判别公式:
Δ
=
b
2
−
4
a
c
≜
0
\Delta = b^2 - 4ac \triangleq 0
Δ=b2−4ac≜0即可找到该范围:
因为
Δ
≜
0
\Delta \triangleq 0
Δ≜0说明一元二次方程有唯一解,意味着随机变量
x
1
x_1
x1的正交基在该位置与函数图像相切,见下示例图:
其中
a
=
2
,
b
=
2
x
1
,
c
=
x
1
2
−
C
a = 2,b=2 x_1,c = x_1^2 - \mathcal C
a=2,b=2x1,c=x12−C,带入即可。基于该范围,范围边缘的
±
2
C
\pm \sqrt{2 \mathcal C}
±2C只有唯一解,范围内的其他点均对应两个不相同的解。使用求根公式:
−
b
±
b
2
−
4
a
c
2
a
−b±√b2−4ac2a
一次画半个椭圆~
对应代码表示如下:
import math
import matplotlib.pyplot as plt
def CalxLimits(C):
# 根判别式
return math.sqrt(2 * C),-1 * math.sqrt(2 * C)
# def f(x,y):
# 没有用到;只是描述一下这个非标准二次型函数。
# return 2 * (y ** 2) + 2 * x * y + (x ** 2)
def GetAnalytical(x,C):
# 求根公式
return 0.25 * (-2 * x + math.sqrt(8 * C - (4 * (x ** 2)))),0.25 * (-2 * x - math.sqrt(8 * C - (4 * (x ** 2))))
def DrawContourPicture(CList):
for C in CList:
UpperPointList,LowerPointList = list(),list()
Upper,Lower = CalxLimits(C)
xList = list(np.linspace(Lower,Upper,10000))
for x in xList:
Upperx,Lowerx = GetAnalytical(x,C)
UpperPointList.append(Upperx)
LowerPointList.append(Lowerx)
plt.plot(xList, UpperPointList,'--',c="tab:blue")
plt.plot(xList, LowerPointList,'--',c="tab:blue")
plt.show()
if __name__ == '__main__':
CList = [0.01, 0.1, 0.5, 2, 4.5]
DrawContourPicture(CList)
对应图像效果表示如下:

由于目标函数
f
(
⋅
)
f(\cdot)
f(⋅)是凸二次函数,那么该函数一定存在全局最优解。因而可以使用基于精确搜索的共轭梯度法获取最优解。回顾无约束优化问题——共轭梯度法,其算法步骤表示如下:
初始化操作:
算法过程:
这里利用范数来侧面描述梯度向量
∇
f
(
x
k
)
\nabla f(x_k)
∇f(xk)的大小。当
∥
∇
f
(
x
k
)
∥
⇒
0
\|\nabla f(x_k)\| \Rightarrow 0
∥∇f(xk)∥⇒0意味着向量
∇
f
(
x
k
)
\nabla f(x_k)
∇f(xk)趋于零向量。需要注意一下:上面链接文章中对目标函数的定义为
f
(
x
)
=
1
2
x
T
Q
x
+
C
T
x
f(x)=12xTQx+CTx,而系数
1
2
12只是为了方便求导。在本节中的
f
(
x
)
=
x
T
Q
x
f(x) = x^T \mathcal Q x
f(x)=xTQx没有该系数,因而需要在相应
α
k
\alpha_k
αk化简结果中填上一个系数
1
2
12相应代码表示如下:
def ConjugateGradient():
def f(PointInput, Q):
# 二次型
return np.dot(np.dot(PointInput, Q), PointInput)
def Nablaf(PointInput, Q):
# 二次型求导
return 2 * np.dot(Q, PointInput)
def GetNorm(ArrayInput):
return np.linalg.norm(ArrayInput)
Epsilon = 0.05
InitPoint = np.array([3., 2.])
Q = np.array([[1., 1.], [1., 2.]])
PointList = list()
PointList.append(InitPoint)
ConjugateStart = -1 * Nablaf(InitPoint, Q)
while True:
if GetNorm(Nablaf(InitPoint, Q)) <= Epsilon:
break
else:
alpha = -0.5 * (np.dot(Nablaf(InitPoint, Q), ConjugateStart) / np.dot(np.dot(ConjugateStart, Q),ConjugateStart))
NewPoint = InitPoint + alpha * ConjugateStart
Beta = np.dot(np.dot(Nablaf(NewPoint, Q), Q), ConjugateStart) / np.dot(np.dot(ConjugateStart, Q),ConjugateStart)
NewConjugate = -1 * Nablaf(NewPoint, Q) + Beta * ConjugateStart
PointList.append(NewPoint)
print("[info] Iterations: ", len(PointList))
InitPoint = NewPoint
ConjugateStart = NewConjugate
return PointList
对应图像表示如下:

很明显,可以发现:使用精确搜索,它仅需要
1
1
1次线搜索:
n
=
2
n=2
n=2次迭代必然能够找到最优解。由于下降方向是共轭方向
d
k
d_k
dk,并且
Q
\mathcal Q
Q不是对角阵,因此上面迭代产生的下降方向之间并不满足垂直关系。
只是看起来有点迷惑人~
当然,如果在计算
α
k
\alpha_k
αk过程中没有乘以
1
2
12
循环无法停止,它只会在这几个点上无限循环下去。不要问我是怎么知道的~

在非精确搜索——
Wolfe Condition
\text{Wolfe Condition}
Wolfe Condition一节中介绍了这种方法,它主要通过参数
C
1
\mathcal C_1
C1来描述所选
α
\alpha
α的上界;以及参数
C
2
\mathcal C_2
C2来描述上界以下范围内
α
\alpha
α满足的梯度范围:
其中
ϕ
(
α
)
=
f
(
x
k
+
1
)
=
f
(
x
k
+
α
⋅
d
k
)
\phi(\alpha) = f(x_{k+1}) = f(x_k + \alpha \cdot d_k)
ϕ(α)=f(xk+1)=f(xk+α⋅dk)
{
ϕ
(
α
)
≤
f
(
x
k
)
+
C
1
[
∇
f
(
x
k
)
]
T
d
k
⋅
α
ϕ
′
(
α
)
≥
C
2
⋅
[
∇
f
(
x
k
)
]
T
d
k
C
1
∈
(
0
,
1
)
C
2
∈
(
C
1
,
1
)
{ϕ(α)≤f(xk)+C1[∇f(xk)]Tdk⋅αϕ′(α)≥C2⋅[∇f(xk)]TdkC1∈(0,1)C2∈(C1,1)
线搜索过程中,每次迭代只选择一个满足上述条件的优质结果(不一定是最优解)参与迭代,从而得到近似最优解。关于
Wolfe Condition
\text{Wolfe Condition}
Wolfe Condition的收敛性证明详见传送门。对应代码描述如下:
def WolfeConditionOperation(C1,C2,PointInput,Conjugate,Q,UpperLimits):
def phi(alpha,PointInput,Conjugate,Q):
return np.dot(np.dot(PointInput + alpha * Conjugate,Q),PointInput + alpha * Conjugate)
def Derphi(alpha,PointInput,Conjugate,Q):
# phi()函数关于alpha的导函数
return np.dot(np.dot(Conjugate,Q),PointInput) + np.dot(np.dot(PointInput,Q),Conjugate) \
+ 2 * alpha * np.dot(np.dot(Conjugate,Q),Conjugate)
assert 0 < C1 < 1 and C1 < C2 < 1
while True:
alpha = random.uniform(0.0,UpperLimits)
if phi(alpha,PointInput,Conjugate,Q) <= f(PointInput,Q) + C1 * alpha * np.dot(Nablaf(PointInput,Q),Conjugate):
if Derphi(alpha,PointInput,Conjugate,Q) >= C2 * np.dot(Nablaf(PointInput,Q),Conjugate):
if not alpha:
continue
else:
UpperLimits /= 1.2
break
return alpha
基于
Wolfe Condition
\text{Wolfe Condition}
Wolfe Condition的共轭梯度法收敛效果描述如下:
需要说明的是,每次迭代产生的
α
\alpha
α不是固定的,对应图像也不是固定的。甚至有些时候选择出的
α
\alpha
α结果不满足迭代条件甚至卡死。多试几次~

import math
import random
import numpy as np
import matplotlib.pyplot as plt
def CalxLimits(C):
return math.sqrt(2 * C),-1 * math.sqrt(2 * C)
# def f(x,y):
# 没有用到;只是描述一下这个非标准型函数。
# return 2 * (y ** 2) + 2 * x * y + (x ** 2)
def GetAnalytical(x,C):
return 0.25 * (-2 * x + math.sqrt(8 * C - (4 * (x ** 2)))),0.25 * (-2 * x - math.sqrt(8 * C - (4 * (x ** 2))))
def DrawContourPicture(CList):
for C in CList:
UpperPointList,LowerPointList = list(),list()
Upper,Lower = CalxLimits(C)
xList = list(np.linspace(Lower,Upper,10000))
for x in xList:
Upperx,Lowerx = GetAnalytical(x,C)
UpperPointList.append(Upperx)
LowerPointList.append(Lowerx)
plt.plot(xList, UpperPointList,'--',c="tab:blue")
plt.plot(xList, LowerPointList,'--',c="tab:blue")
# plt.show()
def ConjugateGradient(mode="WolfeCondition"):
# 使用精确搜索(步长是最优解)的迭代效果。
def f(PointInput,Q):
# 二次型
return np.dot(np.dot(PointInput,Q),PointInput)
def Nablaf(PointInput,Q):
# 二次型求导
return 2 * np.dot(Q,PointInput)
def GetNorm(ArrayInput):
return np.linalg.norm(ArrayInput)
def WolfeConditionOperation(C1,C2,PointInput,Conjugate,Q,UpperLimits):
def phi(alpha,PointInput,Conjugate,Q):
return np.dot(np.dot(PointInput + alpha * Conjugate,Q),PointInput + alpha * Conjugate)
def Derphi(alpha,PointInput,Conjugate,Q):
# phi()函数关于alpha的导函数
return np.dot(np.dot(Conjugate,Q),PointInput) + np.dot(np.dot(PointInput,Q),Conjugate) \
+ 2 * alpha * np.dot(np.dot(Conjugate,Q),Conjugate)
assert 0 < C1 < 1 and C1 < C2 < 1
while True:
alpha = random.uniform(0.0,UpperLimits)
if phi(alpha,PointInput,Conjugate,Q) <= f(PointInput,Q) + C1 * alpha * np.dot(Nablaf(PointInput,Q),Conjugate):
if Derphi(alpha,PointInput,Conjugate,Q) >= C2 * np.dot(Nablaf(PointInput,Q),Conjugate):
if not alpha:
continue
else:
UpperLimits /= 1.2
break
return alpha
assert mode in ["Exact","WolfeCondition"]
Epsilon = 0.05
InitPoint = np.array([3.,2.])
Q = np.array([[1.,1.],[1.,2.]])
PointList = list()
PointList.append(InitPoint)
UpperLimits = 2.0
C1 = 0.5
C2 = 0.8
ConjugateStart = -1 * Nablaf(InitPoint,Q)
while True:
if GetNorm(Nablaf(InitPoint,Q)) <= Epsilon:
break
else:
if mode == "Exact":
alpha = -0.5 * (np.dot(Nablaf(InitPoint,Q),ConjugateStart) / np.dot(np.dot(ConjugateStart,Q),ConjugateStart))
else:
alpha = WolfeConditionOperation(C1,C2,InitPoint,ConjugateStart,Q,UpperLimits)
NewPoint = InitPoint + alpha * ConjugateStart
Beta = np.dot(np.dot(Nablaf(NewPoint,Q),Q),ConjugateStart) / np.dot(np.dot(ConjugateStart,Q),ConjugateStart)
NewConjugate = -1 * Nablaf(NewPoint,Q) + Beta * ConjugateStart
PointList.append(NewPoint)
InitPoint = NewPoint
ConjugateStart = NewConjugate
return PointList
def DrawPicture(PointList):
CList = [0.01,0.1,0.5,2,4.5]
DrawContourPicture(CList)
plotList = list()
for (x,y) in PointList:
plotList.append((x,y))
plt.scatter(x, y, s=40, facecolor="none", edgecolors="tab:red", marker='o')
if len(plotList) < 2:
continue
else:
plt.plot([plotList[0][0], plotList[1][0]], [plotList[0][1], plotList[1][1]], c="tab:red")
plotList.pop(0)
plt.show()
if __name__ == '__main__':
PointList = ConjugateGradient()
# PointList = ConjugateGradient(mode="Exact")
DrawPicture(PointList)