最優化問題中常常需要求解目標函數的最大值或最小值,比如SVM支持向量機算法需要求解分類之間最短距離,神經網絡中需要計算損失函數的最小值,分類樹問題需要計算熵的最小或最大值等等。如果目標函數可求導常用梯度法,不能求導時一般選用模式搜索法。
一、梯度法求解最優問題
由數學分析知識可以知道,函數在一個點的梯度方向是函數值增大的最快方向,與之相反,梯度的反方向是函數值變小的最快方向,函數值在定義域內可以用等值線形式來表示。假設求目標函數最小值,可在定義域內任選一點,求出該初始點梯度反方向,沿着這個反向得到新的一個點,再求出新點的梯度反方向,迭代若干次后,可通過計算函數值的誤差范圍結束迭代獲得函數最小值。根據具體的應用,有的是需要計算函數最值,有的是需要求滿足最值時的自變量(定義域的坐標),梯度法計算過程可以通過下圖來理解:
這里需要強調一點,等值線上任選一點后,可沿着梯度下降方向無約束的移動,所以這里討論的是無約束條件的最優化問題,無約束的問題處理起來還是比較簡單的,有約束的問題需要另外的算法,如單純形法、可行方向搜索等,也可以將有約束的問題變為無約束的問題,具體可參考懲罰函數一章。
用一段代碼介紹下梯度法求解最優化問題,本例是用最小二乘法求線性回歸問題,如有一組樣本數據xi和yi,假設yi=axi+b,即xi和yi存在線性對應關系,其中a,b是未知參數,需要從所給的樣本中計算出a,b值。由最小二乘法知,要求出a,b的最優解實際上就是求平方誤差函數的最小值,當得到最小值后其自變量值:a,b即為線性關系函數的參數,注意這里xi、yi是已知值來自於樣本數據。
import numpy as np import math import scipy from sympy import Matrix import scipy.linalg class optclass(object): def __init__(self, error): self.error = error self.direction=np.array([[1,0],[-1,0],[0,1],[0,-1]]) #求一階導數 def P_firstderivative(self,step,theta1,theta2, grad_theta1, grad_theta2 ,x,y): d1=0 for i in range(y.shape[0]): d1=d1+((theta1-grad_theta1*step)*x[i]+theta2-grad_theta2*step-y[i])*(-grad_theta1*x[i]-grad_theta2) return d1 # 求二階導數 def P_secondderivative(self, grad_theta1, grad_theta2 ,x ,y): d2=0 for i in range(y.shape[0]): d2=d2+ (-grad_theta1*x[i]-grad_theta2)**2 return d2 # 牛頓法實現一維搜索,獲得步長 def newton(self,theta1,theta2, grad_theta1, grad_theta2 ,x,y_): stepx, l, iterNum = 0, 1e-4, 0 dx_1 =self. P_firstderivative(stepx, theta1,theta2, grad_theta1, grad_theta2 , x,y_) while abs(dx_1) > l: dx_1 = self.P_firstderivative(stepx, theta1, theta2, grad_theta1, grad_theta2, x, y_) dx_2 = self.P_secondderivative(grad_theta1, grad_theta2 ,x,y_) stepx = stepx - dx_1 / dx_2 iterNum = iterNum + 1 return stepx #計算誤差 def calerror(self,theta1,theta2,x,y): error=0 for i in range(y_.shape[0]): error = error + 0.5 * ((x[i] * theta1 + theta2 - y_[i]) ** 2) return error def gradiatdesc(self,y_,x,theta): grad_theta1,grad_theta2=0,0 NITER=100000 for t in range(NITER): for i in range(y_.shape[0]): grad_theta1 = grad_theta1 + (theta[0] * x[i] + theta[1] - y_[i]) * x[i] grad_theta2=grad_theta2+(theta[0]*x[i]+theta[1]- y_[i]) distance=math.sqrt(grad_theta1**2+grad_theta2**2) grad_theta1=grad_theta1/distance grad_theta2 = grad_theta2 / distance #1.1 利用一維搜索獲得最佳步長系數 step=self.newton(theta[0],theta[1] ,grad_theta1,grad_theta2,x,y_) theta[0] = theta[0] - grad_theta1 * step theta[1] = theta[1] - grad_theta2 * step error=self.calerror(theta[0] ,theta[1] ,x,y_) #print('誤差%0.3f 步長%0.2f'%(error,step)) if(error<= self.error): break grad_theta1, grad_theta2 = 0, 0 return theta[0],theta[1] if __name__=='__main__': theta1,theta2=2.4,8.21 theta=np.array([0,0],dtype=np.float32) o=optclass(1e-5) x=np.linspace(1,20,100) y_=np.array([theta1*xx+theta2 for xx in x ]) a,b=o.gradiatdesc(y_, x, theta) print('theta1=%0.3f theta2=%0.2f' % (theta1, theta2))
在梯度下降搜索最值過程中,為了防止每次步長太大而跳出極值點,在代碼注釋標記1.1處,
step=self.newton(theta[0],theta[1] ,grad_theta1,grad_theta2,x,y_)
利用牛頓法一維搜索獲得最佳步長系數,具體做法是:將參數theta1=theta1-參數theta1梯度*步長,theta2=theta2-參數theta2梯度*步長代入目標函數,此時目標函數變為只有步長系數的一元函數,求解該函數最小值即可獲得在當前前進方向下最優的步長,關於一維搜索知識請看本站文章:一維搜索。
二、模式搜索求解最優化問題
2.1 模式搜索法介紹
在實際應用中有時函數是不可求導的,甚至函數本身的形式都不知道,這時梯度法顯然不再適用,模式搜索法就應運而生。模式搜索法是一種獲得函數最值通用算法,當目標函數不可求導時其等值線還是存在的,只不過與可導函數相比,其等值線看上去不是那么'順滑'。利用模式搜索法對目標函數只有一個要求'目標函數定義域是一個凸函數',凸函數、凸集可以想象成一個既沒有'洞',也不會相互重疊的區域,這種類型函數的等值線不會相交,只有保證這點才能使用模式搜索法。
模式搜索法思想與梯度法一樣,都是尋找函數變大或變小的方向,如求解函數最大值問題,此時不能直接求出某一點的梯度時,可以退而求其次,找出一個與梯度方向大致相同的向量,沿着這個向量方向運動不斷變化調整,'方向大致相同'用幾何語言描述是:選擇的方向與梯度方向夾角在0-90度之間。方向從線性空間的角度來說是一個向量,由於線性空間中任何一個向量都可以用基線性表示,等值線所在的定義域是一個線性完備空間,可以用線性空間的基表示函數變化的方向,以求函數最小值為例,模式搜索法算法可以這樣表述:
(1) 設初始點為x1,等值線所在線性空間有e1,e2,...en個基,初始步長為s,方向系數α>=1,縮短因子β∈(0,1),誤差ε,並設y1=x1,k=1,j=1 。
(2) 計算f(yj+ej*s),如果f(yj+ej*s)<f(yj),則設:
yj+1=yj+ej*s
轉到步驟4;如f(yj+ej*s)≧f(yj)轉到步驟3。
(3) 如f(yj-ej*s)<f(yj),則設:
yj+1=yj-ej*s ;否則,則令
yj+1=yj,即回到開始嘗試ej時的起點,轉至步驟4。
(4) 這一步主要是切換到下一個基方向,如果j<n,則置j=j+1,轉到步驟2;否則轉到步驟5。
(5) 到這步意味着已經遍歷所有基的方向、包括其反方向,如果f(yn+1)<f(xk),則轉到步驟6;否則轉到步驟7。
(6) 到這步代表遍歷所有基方向后,且新的點函數值小於起始點的函數值,但函數值還有變小的空間。此時令xk+1=yn+1,並設
y1=xk+1+α(xk+1-xk),k=k+1,j=1,轉到步驟(2)。
(7) 如果步長s≦ε,退出計算,得到最值點;否則,設:
s=s*β , y1=xk , xk+1=xk
k=k+1, j=1,轉到步驟2。
模式搜索步驟還是挺繁瑣的,可以歸納一下,模式搜索沿着一個基方向尋找函數值變小的方向,如果該方向函數值沒有降低,則沿着基的反方向尋找,如果都沒找到則切換到下一個基,依此循環,如果所有的基方向都找不到,則回到原點,縮短步長,重新開始搜索。可見這種方法要把基方向都遍歷一遍,全部遍歷后,轉入上面步驟5,此時檢查:如果最后找到點yn+1小於開始點函數值,則yn+1出發,沿着開始點xk指向yn+1方向再前進α步長,得到新的搜索起始點,這段描述參考下圖:
xk是初始點,通過遍歷所有基方向找到點yn+1后,並不是單純將yn+1作為新的搜索起始點,算法中中有一個經驗內涵:既然千辛萬苦找到了yn+1,那么沿着找到方向再前進一點應該也是滿足條件的,算法中會沿着該方向再前進α長,有時α也稱為加速因子。在實際開發中,將該α值適當調高一些,有時會以百倍的效率縮短算法過程。
模式搜索與梯度法都是在函數等值線上搜索,兩者區別參考下圖:
可以看出模式搜索法像極了一個左顧右盼的人,站在十字路口每個方向都嘗試一下,試圖找到函數的最值的方向;如果目標函數是凸集函數,最終還是可以到達目的地,上述算法描述很復雜,但幸好用編程實現並不難。
2.2 代碼說明
下面的代碼是利用模式搜索法實現之前線性回歸問題,實現核心代碼為modesearch函數,可根據注釋參考:
余下文章鏈接: