機器學習中數學優化專門用於解決尋找一個函式的最小值的問題。這裡的函式被稱為cost function或者objective function,或者energy:損失函式或者目標函式。
更進一步,在機器學習優化中,我們並不依賴於被優化的函式的數學解析表示式,我們通過使用$scipy.optimize$從而實現類似黑盒優化。
瞭解你的優化問題本身
並不是所有的優化問題都是一樣的,如果你能對你待優化的問題本身有一個深刻的理解,這樣可以選擇正確的優化方法。
1.其中非常重要的一點就是要考察問題本身的維數。問題本身資料的維數(標量變數的個數)決定了優化問題的規模。
2.convex和non-convex優化
凸函式的特徵:
- $f$總是在其切線之上
- 或者說,對於兩個點$A,B$,如果$A<C<B$,則$f(C)$總是線上段$f(A),f(B)$之間
凸函式優化相對非常簡單。
3.函式光滑與否(是否處處可導)
4. loss函式以及gradient函式是否存在噪聲
如果gradient沒有解析式,那麼我們都須通過計算式計算,這必然導致誤差。
5. 是否有限制條件
比如,下圖中就要求優化只能在$x_1,x_2 \in (-1,1)$間優化
常見的優化方法
Brent’s method
理論依據是中值定理,對於一個連續函式,$f$,如果兩個端點a,b的函式值$f(a)f(b)<0$,則$(a,b)$之間必然存在一個駐點(導數為0),我們可以不斷迭代最終求得駐點;
from scipy import optimize def f(x): return -np.exp(-(x - 0.7)**2) result = optimize.minimize_scalar(f) result.success # check if solver was successful x_min = result.x
gradient based methods
簡單梯度下降法
原理如教科書的描述,根據梯度下降的方向去做數值變更和迭代,最終求得駐點。
存在的問題是:引數調整時可能反覆跨過駐點從而形成震盪,雖然減少引數調整的布幅可以減輕這個問題,但是無法徹底解決。
共軛梯度下降
共軛演算法在簡單梯度下降基礎上增加一個摩擦項(friction term),每個step依賴於梯度最近的兩個值,這樣可以有效解決反覆震盪問題。
def f(x): # The rosenbrock function return .5*(1 - x[0])**2 + (x[1] - x[0]**2)**2 optimize.minimize(f, [2, -1], method="CG")
梯度優化方法需要loss函式的雅可比(梯度)值,雖然即使你不傳入導數(雅可比矩陣是針對y值為向量的情況下的梯度向量),演算法也能夠通過數值計算方法算出來,但是如果有解析表示式,我們傳入這個值會大大提升收斂效率.
def jacobian(x): return np.array((-2*.5*(1 - x[0]) - 4*x[0]*(x[1] - x[0]**2), 2*(x[1] - x[0]**2))) optimize.minimize(f, [2, 1], method="CG", jac=jacobian)
注意:通過傳入解析表示式後,只需要28次就能收斂,而之前需要108次迭代才能收斂。
牛頓梯度法(Newton and quasi-newton methods)
def f(x): # The rosenbrock function return .5*(1 - x[0])**2 + (x[1] - x[0]**2)**2 def jacobian(x): return np.array((-2*.5*(1 - x[0]) - 4*x[0]*(x[1] - x[0]**2), 2*(x[1] - x[0]**2))) optimize.minimize(f, [2,-1], method="Newton-CG", jac=jacobian)
BFGS
BFGS在牛頓法基礎上細調了Hessian矩陣的估算方法
L-BFGS
L-BFGS位於BFGS和共軛梯度法之間。對於非常高維(大於250個)的loss函式,Hessian矩陣的計算是非常耗時的,L-BFGS keeps a low-rank version.
非梯度優化方法
Powell演算法
Nelder-Mead
帶約束條件的函式優化
盒子邊界
def f(x): return np.sqrt((x[0] - 3)**2 + (x[1] - 2)**2) optimize.minimize(f, np.array([0, 0]), bounds=((-1.5, 1.5), (-1.5, 1.5)))
通用約束:拉格朗日乘數法化約束為對偶無約束的優化問題
def f(x): return np.sqrt((x[0] - 3)**2 + (x[1] - 2)**2) def constraint(x): return np.atleast_1d(1.5 - np.sum(np.abs(x))) x0 = np.array([0, 0]) optimize.minimize(f, x0, constraints={"fun": constraint, "type": "ineq"})