解最優化問題
本文版權屬於重慶大學計算機學院劉驥,禁止轉載
最優化問題的分類
按照目標函數的可導性,可以將最優化問題分爲兩類:
1. 目標函數可導,例如目標函數爲
2. 目標函數不可導,例如目標函數爲
對於不可導的目標函數,其解法比較特殊。採用模擬退火算法、粒子羣算法、遺傳算法、蟻羣算法、圖割(Graph Cut)算法等方法,可以求解這類問題,但通常無法得到精確的解(或者說全局最優解)。並且有些問題,僅能用特定的解法,求出特定的解。由於該類問題的複雜性,本文對不可導目標函數問題不進行討論。如果你在實際中遇到了這類問題,可以採用如下方法解決:
查學術論文,github上找代碼;如果找不到解決方案,恭喜你,這是一個獲得數學大獎的機會
本文餘下部分,針對可導的目標函數進行討論,這類問題存在大量通用的算法。人類歷史發展到2017年,你甚至不需要了解具體算法的原理,也能夠很happy的解決這類問題。
直接開始求解
下面我們用一個簡單的最優化問題爲例:
顯然
downhill simplex
Python語言的scipy.optimize包的fmin函數實現了downhill simplex方法,也稱爲Nelder–Mead方法(downhill、simplex和downhill simplex是不同的概念,有興趣可以參考維基百科)。這裏不去講解downhill simplex的原理,有需要原理的讀者,請移步到https://en.wikipedia.org/wiki/Nelder–Mead_method。
fmin函數的說明如下:
我估計你看不懂,所以我不準備解釋。我們直接看代碼吧。
# encoding=utf-8
import scipy.optimize as opt
#定義目標函數f
def f(X):
x=X[0]
y=X[1]
return x**2+y**2+1;
x=10
y=10
X=[x,y]
X=opt.fmin(f,X) #f是目標函數,X是初始猜測的解
x=X[0]
y=X[1]
print x,y
程序執行結果如下:
最終
Conjugate Gradient
downhill simplex比較簡單,實現沒有難度。但它的問題在於比較慢,下面我們試試 Conjugate Gradient法。使用scipy.optimize包的fmin_cg方法。這個方法需要求出目標函數的偏導數(梯度):
# encoding=utf-8
import scipy.optimize as opt
import numpy as np
#定義目標函數f
def f(X):
x=X[0]
y=X[1]
return x**2+y**2+1;
#定義目標函數f對應的梯度函數
def gradf(X):
x=X[0]
y=X[1]
gx=2*x
gy=2*y
return np.asarray((gx,gy)) #梯度以向量形式返回
x=10
y=10
X=[x,y]
X=opt.fmin_cg(f,X,fprime=gradf) #f是目標函數,X是初始猜測的解,增加梯度
x=X[0]
y=X[1]
print x,y
運行結果如下:
可以看出該方法比downhill simplex運行得更快。當然這是顯然的,因爲有了梯度信息。
你居然不會求偏導數!
OK!OK!有辦法可以解決!今年是2017年,如果你還不會求偏導數,那麼交給計算機吧!
X=opt.fmin_cg(f,X)
OK!計算結果如下:
慢了一些,函數的演化次數增加了15次,why?因爲算法用數值法來計算梯度!具體來說(你可以忽略下面的內容,把問題交給人工智能):
def gradf(X):
x=X[0]
y=X[1]
gx=(f([x+0.0000001,y])-f([x,y]))/0.0000001
gy=(f([x,y+0.0000001])-f([x,y]))/0.0000001
return np.asarray((gx,gy))
也就是:
Newton-CG
試試Newton-CG,也就是fmin_ncg函數,該函數需要2階偏導數構成的Hessian矩陣:
代碼如下:
# encoding=utf-8
import scipy.optimize as opt
import numpy as np
#定義目標函數f
def f(X):
x=X[0]
y=X[1]
return x**2+y**2+1;
#定義目標函數f對應的梯度函數
def gradf(X):
x=X[0]
y=X[1]
gx=2*x
gy=2*y
return np.asarray((gx,gy)) #梯度以向量形式返回
def hess(X):
return np.array([[2,0],[0,2]]) #hessian矩陣
x=10
y=10
X=[x,y]
X=opt.fmin_ncg(f,X,fprime=gradf,fhess=hess) #f是目標函數,X是初始猜測的解,增加梯度
x=X[0]
y=X[1]
print x,y
運行結果如下:
非常精確!當然如果不會計算hessian,如下操作:
X=opt.fmin_ncg(f,X,fprime=gradf) #注意梯度是必須的
結果如下:
總結
現在是2017年,基本的最優化問題已經經過了幾百年的發展(不是幾十年),因此有大量現成的函數庫可以使用。直接調用這些庫函數,你甚至不需要知道如何求導數、如何求Hessian矩陣,也可以很好的解決最優化問題(可導的)。如果不追求效率,如果精度可以接受,那麼最最原始的downhill simplex方法已經能夠適用於大多數場景(你需要小數點後幾位的精度?)。
數學原理
大多數最優化的書籍,在講解數學原理時,都抱着一定要讓人看不懂,才能顯得高深莫測的心態。其實求解可導目標函數的數學原理是極其簡單的,那就是:窮舉,更高效的窮舉。
假設目標函數爲
學過代數吧?於是這個函數就變成了:
現在我們來討論女性擇偶函數f(X)的最優化!假定女性朋友給出的函數是這樣的:
這是一個二次函數(請自行展開),C是係數矩陣(我們只是假設女性朋友給出了這樣的二次函數,真實情況未必)。OK,其實這個函數長什麼樣子,不影響後面的討論。我們所要討論的是
最笨的方法就是窮舉!!換言之,先試試
聰明一點的方式是窮舉n次,找其中的最優。先試試
再聰明一點的方法固定一些變量,搜索另外一些變量。必須長得帥,像鹿晗;必須有錢,和王思聰一樣;必須是初戀。你看解空間一下就變小了。接下來的搜索就簡單了很多。如果找不到解,那麼就修改某些固定的變量。爲什麼長相必須像鹿晗?郭德綱也可以接受!這樣我們就可以開始新的搜索啦!事實上,這就是絕大多數人,擇偶的方式。
如果變量是連續的呢?數學家發現,在這種情況下,搜索策略其實是這樣的:
1. 首先選擇一個起始的解
2. 比較
3. 另
上述方法的核心在於存在一個
上述算法有三個終止條件:
1. 迭代次數達到限額(都已經120歲的單身貴族啦!)
2.
3.
下面舉個例子,目標函數如下:
假設初始值爲
Stop!不是
怎麼解決這個問題呢?數學家出場了。我們知道
針對該公式求
現在我們可以解出
計算過程如下:
數學白癡表示求偏導數矩陣太難理解!!如何破!!數學家馬上送上福利:
假設
運氣不錯!如果
如果
可見如果採用新的計算公式,
最後,再來考慮一下,如果數學白癡連梯度都不會算怎麼辦?Nelder–Mead Method!對有高等數學背景的人而言,理解Nelder–Mead Method反而很困難!這裏就不再詳談了。
數學白癡的福利
我們都不喜歡數學,還好現在是2017年,大量的工作已經由擅長數學的科學家做完了。你所需要的就是找一個算法豐富的庫!比如Python的scipy或者matlab。爲了寫作本文,我對最優化問題進行了很多簡化,首先只考慮可導的目標函數,其次也沒有考慮函數變量的條件約束(比如