最优化问题大体上分为连续最优化问题和离散最优化问题两种。后者涉及到离散数学、组合数学等学科,属于计算机专业的专业课程,而前者的雏形在微积分课程中,甚至在高中、初中、小学的数学课堂上就有所涉及。
我们还记得求一个定义域内连续可微的一元实函数的最小值点的求解方法:求导,寻找导数为0的点,再求二阶导,这些点当中二阶导大于0的点是我们需要的极值点,再将这些极点对应的函数值进行对比,函数值最小的点就是我们需要的最小值点。
上述问题就是一个最基本的最优化问题,但它已经充分描述了最优化问题的基本目标:求解函数的最小值点。当然,求解最大值点的方法也是相同的,因此我们再讨论最优化问题时只讨论最小化问题。
若函数定义域为 R R R,则该最优化问题为无约束最优化问题。否则,就是约束最优化问题。
下面,针对多元函数,我们提出最优化问题规范化的描述。
仅从三维空间来看,梯度为0的几何意义是该处曲面水平。当然这个曲面还可以拓展到更高维的空间中,在此不再赘述
以上两个条件虽然拓展到了高维,但实际上和一元函数最优化问题的条件是通过1.2中给出的对应关系对应的,结合起来就不难理解
我们最终的目标是要实现让计算机代替我们去寻找最优化问题的解。我们不能指望计算机具有和我们一样的智能、对于一个方程可以使用人类总结出来的诸如分离变量之类的各种方法去求解。我们只能使用数值方法,通过循环迭代解出一个逼近于最优解的答案。这个过程称为迭代搜索。
我们考虑如图所示的一个三维曲面, x k x_k xk是当前我们所在点。假如这个点不是我们需要的最优解,我们就需要移动到下一个点。移动需要确定方向和距离,因此我们有迭代搜索的基本元素如下:
步长:迭代搜索第k步的长度,用 α k \alpha_k αk表示
下降方向:迭代搜索第k步的方向,用 d k d_k dk表示,沿该方向函数值需要下降,这样才能保证算法的收敛性。其中可行方向表示沿着该方向搜索,存在使得下一个点不超出可行域的步长
根据基本元素,我们可以得出n元函数迭代搜索的步骤:
一个可行的收敛算法必须满足收敛性,收敛速度越快的算法性质越好,而具有二次终止性的算法可以在求解二次函数最优化问题时具有广泛的应用价值。但证明这些性质并不作为本系列教程的重点。本教程会给出每一种算法性质的结论与相关证明教程链接,有兴趣的读者可以自行查阅
点列的曲线拟合是我们高中开始就接触过的问题。为了寻找一个待定系数的函数,可以以最小的误差去描述点列,我们需要用到最小二乘法。有关最小二乘法可以参阅:https://www.zhihu.com/question/37031188
最小二乘法是我们研究无约束最优化问题的一个出色的实例。它具有广泛的应用价值,而且目标函数的局部最优解就是全局最优解。我们在接下来的无约束最优化问题部分将针对一个最小二乘法例题进行研究。
例题:
m i n ∑ i = 1 11 ( y i − f ^ ( x , t i ) ) 2 , f ^ ( x , t ) = x 1 ( t 2 + x 2 t ) t 2 + x 3 t + x 4 min\displaystyle\sum_{i=1}^{11}(y_i-\hat{f}(x,t_i))^2,\hat{f}(x,t)=\frac{x_1(t^2+x_2 t)}{t^2+x_3t+x_4} mini=1∑11(yi−f^(x,ti))2,f^(x,t)=t2+x3t+x4x1(t2+x2t)
给出了11组 ( t i , y i ) (t_i,y_i) (ti,yi)的数据,要求我们通过最小二乘法解出 x i , i ∈ 1 , 2 , 3 , 4 x_i,i\in1,2,3,4 xi,i∈1,2,3,4,使得函数 f ^ ( x , t ) \hat{f}(x,t) f^(x,t)可以最好地拟合 y = f ( t ) y=f(t) y=f(t)
本博客所有代码在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) #求逆矩阵