上一篇講了一些微積分的概括。(http://blog.csdn.net/dajiabudongdao/article/details/52397343) 並引出了梯度下降法與擬牛頓法。當計算機求函數的局部極小值時,我們選用這兩種方法(或衍生方法)來解。求極值的問題很複雜,我麼、們大致分爲兩種:無約束極值問題,與有約束極值問題。這一章主要討論無約束極值問題。有約束的極值在後面會提到。(http://blog.csdn.net/dajiabudongdao/article/details/52462942)
一、無約束優化問題
無約束優化問題,就是我們中學常見的求函數極值的方法。一個函數求極值無需外部控制。一旦有外部條件控制了,我們可以用拉格朗日乘數法求解,那就是條件極值的事情了。但是問題出現了,極值不一定是最值。我們求極值容易,最值很難。這TM就令人尷尬了。
我們工業界的同志發明了“局部最小值”一詞的含義就是想要說“我的極值運算就是某種意義上的最值運算”。
1.局部極小值的解法
局部極小值的通用解法就是迭代。
例如當前自變量Xk,對應F(X)k 。那麼下次迭代的Xk+1 = Xk + a*dk其中a代表挪動步長,dk表示挪動方向。迭代是個趨近過程,若使結果收斂需要遵循一個基本要求:下一步的結果要比這一步好,即Xk+1比Xk更接近值極值。當求解極小值時,我們自然希望Xk+1的結果比Xk小。按照泰勒級數展開,我們可以寫成
F(Xk+1 ) =F( Xk ) + dkF'(Xk)+... (其中d = Xk+1-Xk)
並且Xk+1 < Xk。忽略後面的部分可得dkF'(Xk) <0。那麼我們可知每次迭代,d的方向爲負。。但d的值是什麼呢?
dk = -F'(Xk)
因爲一點的梯度的本質是一種方向。當兩個方向相同時,相乘正結果最大,相反時,相乘負結果最大。
下面是我用python模擬的,深度下降的程序
# coding=utf-8
import numpy
import theano
import theano.tensor as T
###多項式的梯度下降
###
###
# y
# x
# theta:容忍的誤差,因爲編程有誤,這裏的誤差僅控制循環次數
# alpha:步長
# x:用來標識對誰求導的標量,x=T.dvector('x')如'x'標識對x求導
# y:帶上面標量的公式
# dimen:變量的維數
# y=x**2
# 使用:steepest(x,y,3,theta=0.1,alpha=0.1)
def steepest(x, y, dimen, theta=0.1, alpha=0.1):
theta = theta * theta
J, updates = theano.scan(lambda i, y, x: T.grad(y[i], x), sequences=T.arange(y.shape[0]), non_sequences=[y, x])
Gradientfunction = theano.function([x], J, updates=updates)
x_0 = numpy.array([0.] * dimen)
x_k = x_0
wuchav = numpy.diag(numpy.array(Gradientfunction(x_k)))
wucha = numpy.dot(wuchav, wuchav)
while (wucha > theta):
x_k_1 = x_k - alpha * numpy.diag(Gradientfunction(x_k))
wuchav = x_k_1 - x_k
wucha = numpy.dot(wuchav, wuchav)
x_k = x_k_1
return x_k
if __name__ == '__main__':
x = T.dvector('x')
y = x ** 2 + 2 * x + 1
res = steepest(x, y, 1, theta=0.01, alpha=0.1)
print "極小值的參數位置爲"
print res
運行結果如下:
2.進一步探討局部極小值的解法
有個問題,上面的泰勒級數展開只到了一級。如果展開到二級就變成牛頓法。F(Xk+1 ) =F( Xk ) + dF'(Xk)+1/2 dTH(X)d + ... (其中d = Xk+1-Xk)
其中H爲Hassion矩陣,求二階導使用。因爲當X趨近與極值時,F(Xk+1)與F(Xk)很接近。F對d(d = Xk+1-Xk)求導爲0,得的
dk = -H-1(Xk)F'(Xk)
後面的算法與上面一樣。但說實話,矩陣求逆是件很難的事情。很多情況,還無法求逆,這就造成牛頓法不太常用。
所以研究者提出了很多使用方法來近似Hessian矩陣,,這些方法都稱作準牛頓算法,,BFGS就是其中的一種,以其發明者Broyden, Fletcher, Goldfarb和Shanno命名。
我們用BFGS方法直接構造出Hassion矩陣的逆。具體步驟很複雜這裏僅 帖公式,步驟如下:
Sk = H-1(Xk),
rk=F'(Xk+1)-F'(Xk)
δk = Xk+1-Xk
同樣的我用python模擬,BFGS的程序如下:
# coding=utf-8
import numpy as np
import theano
import theano.tensor as T
###多項式的BFGS
###
###
# theta:容忍的誤差
# alpha:步長
# x:用來標識對誰求導的標量,x=T.dvector('x')如'x'標識對x求導
# y:帶上面標量的公式
# dimen:變量的維數
# y=x**2
# 使用:BFGS(x,y,3,theta=0.1,alpha=0.1)
def BFGS(x, y, dimen, theta=0.1, alpha=0.1):
theta = theta * theta
S_0 = np.eye(dimen)
S_k = S_0
J, updates = theano.scan(lambda i, y, x: T.grad(y[i], x), sequences=T.arange(y.shape[0]), non_sequences=[y, x])
Gradientfunction = theano.function([x], J, updates=updates)
x_0 = np.array([0.] * dimen)
x_k = np.mat(x_0)
wuchav = np.diag(np.array(Gradientfunction(x_0)))
wucha = np.dot(wuchav, wuchav)
while (wucha > theta):
x_k_1 = x_k - alpha * np.mat(np.diag(Gradientfunction(np.array(x_k)[0]))) * S_k.T
theta_k = x_k_1 - x_k
gamma_k = np.mat(np.diag(Gradientfunction(np.array(x_k_1)[0])) - np.diag(Gradientfunction(np.array(x_k)[0])))
S_k_1 = S_k + (1 + (gamma_k * S_k.T * gamma_k.T) / (theta_k * gamma_k.T)) * (
(theta_k.T * theta_k) / (theta_k * gamma_k.T))
S_k_1 = S_k_1 - (theta_k.T * gamma_k * S_k + S_k * gamma_k.T * theta_k) / (theta_k * gamma_k.T)
S_k = S_k_1
wuchav = np.array(theta_k)[0]
wucha = np.dot(wuchav, wuchav)
x_k = x_k_1
return x_k
if __name__ == '__main__':
x = T.dvector('x')
y = x ** 2 + 2 * x + 1
res = BFGS(x, y, 1, theta=0.001, alpha=0.1)
print "極小值的參數位置爲"
print res