梯度法、模式搜索法求解最優化問題


最優化問題中常常需要求解目標函數的最大值或最小值,比如SVM支持向量機算法需要求解分類之間最短距離,神經網絡中需要計算損失函數的最小值,分類樹問題需要計算熵的最小或最大值等等。如果目標函數可求導常用梯度法,不能求導時一般選用模式搜索法。

一、梯度法求解最優問題

    由數學分析知識可以知道,函數在一個點的梯度方向是函數值增大的最快方向,與之相反,梯度的反方向是函數值變小的最快方向,函數值在定義域內可以用等值線形式來表示。假設求目標函數最小值,可在定義域內任選一點,求出該初始點梯度反方向,沿着這個反向得到新的一個點,再求出新點的梯度反方向,迭代若干次后,可通過計算函數值的誤差范圍結束迭代獲得函數最小值。根據具體的應用,有的是需要計算函數最值,有的是需要求滿足最值時的自變量(定義域的坐標),梯度法計算過程可以通過下圖來理解:

1601385880599056095.gif

 

    這里需要強調一點,等值線上任選一點后,可沿着梯度下降方向無約束的移動,所以這里討論的是無約束條件的最優化問題,無約束的問題處理起來還是比較簡單的,有約束的問題需要另外的算法,如單純形法、可行方向搜索等,也可以將有約束的問題變為無約束的問題,具體可參考懲罰函數一章。

    用一段代碼介紹下梯度法求解最優化問題,本例是用最小二乘法求線性回歸問題,如有一組樣本數據xi和yi,假設yi=axi+b,即xi和yi存在線性對應關系,其中a,b是未知參數,需要從所給的樣本中計算出a,b值。由最小二乘法知,要求出a,b的最優解實際上就是求平方誤差函數gif.gif的最小值,當得到最小值后其自變量值: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))

無標題.jpg

在梯度下降搜索最值過程中,為了防止每次步長太大而跳出極值點,在代碼注釋標記1.1處,

 step=self.newton(theta[0],theta[1] ,grad_theta1,grad_theta2,x,y_)

利用牛頓法一維搜索獲得最佳步長系數,具體做法是:將參數theta1=theta1-參數theta1梯度*步長,theta2=theta2-參數theta2梯度*步長代入目標函數,此時目標函數變為只有步長系數的一元函數,求解該函數最小值即可獲得在當前前進方向下最優的步長,關於一維搜索知識請看本站文章:一維搜索

result.jpg

二、模式搜索求解最優化問題

    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方向再前進α步長,得到新的搜索起始點,這段描述參考下圖:

加速因子.png

    xk是初始點,通過遍歷所有基方向找到點yn+1后,並不是單純將yn+1作為新的搜索起始點,算法中中有一個經驗內涵:既然千辛萬苦找到了yn+1,那么沿着找到方向再前進一點應該也是滿足條件的,算法中會沿着該方向再前進α長,有時α也稱為加速因子。在實際開發中,將該α值適當調高一些,有時會以百倍的效率縮短算法過程。

    模式搜索與梯度法都是在函數等值線上搜索,兩者區別參考下圖:

1608207350415021231.png

可以看出模式搜索法像極了一個左顧右盼的人,站在十字路口每個方向都嘗試一下,試圖找到函數的最值的方向;如果目標函數是凸集函數,最終還是可以到達目的地,上述算法描述很復雜,但幸好用編程實現並不難。

2.2 代碼說明

下面的代碼是利用模式搜索法實現之前線性回歸問題,實現核心代碼為modesearch函數,可根據注釋參考:

余下文章鏈接:

梯度法、模式搜索法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM