1 最优化问题
1.1 什么是最优化问题
最优化问题大体上分为连续最优化问题和离散最优化问题两种。后者涉及到离散数学、组合数学等学科,属于计算机专业的专业课程,而前者的雏形在微积分课程中,甚至在高中、初中、小学的数学课堂上就有所涉及。
我们还记得求一个定义域内连续可微的一元实函数的最小值点的求解方法:求导,寻找导数为0的点,再求二阶导,这些点当中二阶导大于0的点是我们需要的极值点,再将这些极点对应的函数值进行对比,函数值最小的点就是我们需要的最小值点。
上述问题就是一个最基本的最优化问题,但它已经充分描述了最优化问题的基本目标:求解函数的最小值点。当然,求解最大值点的方法也是相同的,因此我们再讨论最优化问题时只讨论最小化问题。
若函数定义域为,则该最优化问题为无约束最优化问题。否则,就是约束最优化问题。
下面,针对多元函数,我们提出最优化问题规范化的描述。
1.2 名词与符号
- 最优点
- 最优值
- 最优解
- 可行域,对应于基本问题中的定义域
- 梯度,对应于基本问题中的一阶导数
- 黑森矩阵,对于n元函数,位置的值为,对应于基本问题中的二阶导数
- 局部最优解:在附近一个邻域内最小,对应基本问题中的极值点
- 全局最优解:在内最小,对应基本问题中的最小值点
1.3 最优解条件
- 一阶必要条件:f(x)一阶连续可微且是为f(x)的局部最优解的必要条件。满足的点称为驻点。驻点分为:极小点,极大点,鞍点
仅从三维空间来看,梯度为0的几何意义是该处曲面水平。当然这个曲面还可以拓展到更高维的空间中,在此不再赘述
- 二阶充分条件:f(x)二阶连续可微且正定,是为f(x)的严格局部最优解的充分条件。如果说一阶条件刻画了f(x)在x处切平面的法向,二阶条件就刻画了曲面的弯曲方向,而正定代表了此处曲面向上弯曲
以上两个条件虽然拓展到了高维,但实际上和一元函数最优化问题的条件是通过1.2中给出的对应关系对应的,结合起来就不难理解
2 用计算机求解问题
2.1 迭代搜索
我们最终的目标是要实现让计算机代替我们去寻找最优化问题的解。我们不能指望计算机具有和我们一样的智能、对于一个方程可以使用人类总结出来的诸如分离变量之类的各种方法去求解。我们只能使用数值方法,通过循环迭代解出一个逼近于最优解的答案。这个过程称为迭代搜索。
我们考虑如图所示的一个三维曲面,是当前我们所在点。假如这个点不是我们需要的最优解,我们就需要移动到下一个点。移动需要确定方向和距离,因此我们有迭代搜索的基本元素如下:
步长:迭代搜索第k步的长度,用表示
下降方向:迭代搜索第k步的方向,用表示,沿该方向函数值需要下降,这样才能保证算法的收敛性。其中可行方向表示沿着该方向搜索,存在使得下一个点不超出可行域的步长
- 终止准则:考虑一阶必要条件,即,我们将这个条件作为迭代终止的准则。如果我们当前所在点满足准则,就不需要再寻找下一个点了。当然,对于搜索算法来说,我们不能保证我们一定可以通过有限步的搜索找到精确地满足的点,而我们在实际工程运用中对于最小值点的要求也并非是绝对精确的。因此,引入一个精确度,只要梯度满足即可认为满足了终止准则。
根据基本元素,我们可以得出n元函数迭代搜索的步骤:
- 给出
- 如果在点终止准则被满足,停止迭代
- 确定点下降方向
- 确定步长因子
- 令,转步骤2
2.2 质量评估
- 收敛性:对于迭代序列,严格的收敛定义要求k趋近于正无穷时等于最优点,弱化的收敛定义要求k趋近于无穷时一阶偏导向量的范数为0
-
收敛速度:为p阶收敛,则
- ,则算法线性收敛
- ,则算法超线性收敛
- ,则算法二阶收敛。二阶收敛必定超线性收敛,且二阶收敛的算法收敛速度高于线性收敛的算法
- 二次终止性:若某个算法对于任意的正定二次函数,从任意初始点出发,都能经过有限步迭代达到极小值,则称算法具有二次终止性
一个可行的收敛算法必须满足收敛性,收敛速度越快的算法性质越好,而具有二次终止性的算法可以在求解二次函数最优化问题时具有广泛的应用价值。但证明这些性质并不作为本系列教程的重点。本教程会给出每一种算法性质的结论与相关证明教程链接,有兴趣的读者可以自行查阅
3 最小二乘问题——无约束最优化问题实例
点列的曲线拟合是我们高中开始就接触过的问题。为了寻找一个待定系数的函数,可以以最小的误差去描述点列,我们需要用到最小二乘法。有关最小二乘法可以参阅:https://www.zhihu.com/question/37031188
最小二乘法是我们研究无约束最优化问题的一个出色的实例。它具有广泛的应用价值,而且目标函数的局部最优解就是全局最优解。我们在接下来的无约束最优化问题部分将针对一个最小二乘法例题进行研究。
例题:
给出了11组的数据,要求我们通过最小二乘法解出,使得函数可以最好地拟合
代码实现
本博客所有代码在https://github.com/HarmoniaLeo/optimization-in-a-nutshell开源,如果帮助到你,请点个star,谢谢这对我真的很重要!
本次代码实现内容是梯度、梯度二范数、黑森矩阵等最优化问题基本件的python数值解法,以及简化的python线性代数库
梯度、梯度二范数、黑森矩阵求法参考自博客,有修改:https://www.cnblogs.com/kisetsu/p/9145393.html
制作一个文件Fuction.py,在里面写入:
import numpy as np
class Function:
def __init__(self, _f):
self.fun = _f
def value(self, val):
return self.fun(val)
def part(self, var_index, val):
a = self.fun(val)
b = a + 1
i = 0
e = 2 ** 10 - 1
e1 = 2 ** 10
while 10 ** (-6) < e < e1 or i > -6:
e1 = e
a = b
val_ = list(val)
val_[var_index] += 10 ** i
m = self.fun(val_)
n = self.fun(val)
b = (m - n) / 10 ** i
i -= 2
e = abs(b - a)
return a
def part_2(self, x_index, y_index, val):
return self.__diff_(x_index).__diff_(y_index).value(val)
def diff(self, val):
a = self.fun(val)
b = a + 1
i = 0
e = 2 ** 10 - 1
e1 = 2 ** 10
while 10 ** (-6) < e < e1 or i > -6:
e1 = e
a = b
val_ = val + 10 ** i
m = self.fun(val_)
n = self.fun(val)
b = (m - n) / 10 ** i
i -= 2
e = abs(b - a)
return a
def grad(self, val):
g = np.array(val).astype('float')
for i in range(0, g.size):
g[i] = self.part(i, val)
return np.array(g)
def __diff_(self, index):
def diff_f(vals):
vals_ = list(vals)
vals_[index] = vals_[index] + 10 ** (-6)
m = self.fun(vals_)
n = self.fun(vals)
return (m - n) / 10 ** (-6)
return Function(diff_f)
def hesse(self, val):
v = np.mat(val)
G = np.mat(np.dot(v.T, v)).astype('float')
for i in range(0, v.size):
for j in range(0, v.size):
p = self.part_2(i, j, val)
G[i, j] = p
G=np.array(G)
return G
def norm(self, val):
s = 0
for x in self.grad(val):
s += x ** 2
return np.sqrt(s)
在之后的每个方法实现中,我们都会用到Fuction.py
from Function import Function
tar=Function(func) #func是一个以列表为参数的多元函数
val=tar.val(para) #传入参数获取值
diff=tar.diff(para) #一元函数求导
part=tar.part(index,para) #对第index个参数求导
grad=tar.grad(para) #求梯度数组
hesse=tar.hesse(para) #求hesse阵
norm=tar.norm(para) #求梯度范数
另外制作一个文件lagb.py,在里面写入:
import numpy as np
def turn(a): #转置
if len(a.shape)==1:
a=a.reshape((1,a.shape[0]))
return a
else:
return np.array(np.mat(a).T)
def dot(a,b): #点乘
if len(a.shape)==1:
a=a.reshape((a.shape[0],1))
if len(b.shape)==1:
b=b.reshape((b.shape[0],1))
res=np.array(np.mat(a)*np.mat(b))
if res.shape==(1,1):
return res[0][0]
else:
if res.shape[1]==1:
return res.squeeze()
else:
return res
def muldot(*args): #连乘
res=args[0]
for i in range(1,len(args)):
res=dot(res,args[i])
return res
def rev(a): #求逆
return np.array(np.mat(a).I)
在之后的每个方法实现中,我们也都会用到lagb.py
from lagb import *
#输入参数可以为向量或矩阵,即numpy创建的数组
#用numpy创建的一维数组会默认为列向量
a=turn(a) #求转置
c=dot(a,b) #点乘,视情况会返回向量、矩阵、数值
d=dot([a,b,c]) #连续的点乘
b=rev(a) #求逆矩阵