大白話5分鐘帶你走進人工智慧 - 第二十一節 牛頓法和L-BFGS求函式最優解

LHBlog發表於2019-05-04

                                                                 第二十一節 牛頓法和L-BFGS求函式最優解

這一節中,我們講解一個新的求函式最優化的方法就是L-BFGS。以下是本節目錄。

           目錄

1-L-BFGS演算法簡介

2-牛頓法求根問題

3-牛頓法求駐點問題

4-牛頓法求駐點的本質

5-多元函式利用牛頓法求駐點  

6-BFGS演算法

7-L-BFGS演算法


1-L-BFGS演算法簡介

我們知道演算法在計算機中執行的時候是需要很大的記憶體空間的.就像我們解決函式最優化問題常用的梯度下降,它背後的原理就是依據了泰勒一次展開式.泰勒展開式展開的次數越多,結果越精確,沒有使用三階四階或者更高階展開式的原因就是目前硬體記憶體不足以儲存計算過程中演變出來更復雜體積更龐大的矩陣.L-BFGS演算法翻譯過來就是有限記憶體中進行BFGS演算法,L是limited memory的意思.那演算法為什麼叫BFGS呢,請看下圖:

上圖中從左到右依次是Broyden,Fletcher,Goldfarb,Shanno.四位數學家名字的首字母是BFGS,所以演算法的名字就叫做BFGS演算法.接下來我們就一起來學習BFGS演算法的內容。

2-牛頓法求根問題

我們先來回顧下牛頓法求根問題,比如求1元2次方程的根公式為x=\frac{-b \pm \sqrt{b^{2}-4 a c}}{2 a},我們通常管這種形式的根叫解析根。所謂解析解就是你不用給我具體的值,就是一個公式。3次方程也是有解析解的,但是當函式達到5次方以上,就不好找解析解了,對於這種複雜的函式,很遺憾我們不能找到它的全部根,但是至少有辦法找到它的一個根。

我們看一個對於一元函式的例子

對於一個無法解出解析解的函式來說,現在是一元函式,在只有一個x的情況下,我最終想找到x令y=0,即函式的根,怎麼找到它?牛頓和另外一個人同時分別發現,假如這個函式是連續可導的,我隨機出來一個x1,總能求它在這點x1的導數,導數是一個實數,它是能代表切線的斜率,那麼我們就在這個x1點上畫一個原函式切線,這個切線一定會與x軸相交,除非特別倒黴,你一步就隨機到它的駐點上了,也不用求了,你找的就是駐點。不倒黴的情況下一定會使x1點的切線和x軸有個交點,我們命名為x2。 點完之後,又可以找一下x2對應在函式上的點,再畫一條切線找到了x3,這時候相比x1,距離跟的位置來說比較近,然後發現經過有限次數的迭代之後,怎麼迭代都不會變化。它還是會產生震盪,經過震盪之後,最終會收斂在某一個點上,而這個點就是函式的根。所以說牛頓法利用幾何直覺就是在找到某一個函式與x軸的交點。

我們分析下幾何原理:

函式本身f(x)也是已知的,那麼f(x1)就可以計算。x2是x1切線的位置,我們建立一個關於x2,x1的解析式,三角形這條邊是x1-x2,另外一條邊是f(x1),如果用f(x1)/ x1-x2就應該是這條線的斜率,斜率又等於x1這點導數的值。所以x2,x1建立一個關係,x2 = x1-f(x1)/ f`(x1),此時x2就可求了。我們大的思路想從x1找到x2,從x2找到x3,最終找著發現xn,xn+1它倆沒區別的時候xn就是函式的根。因此建立了一個xn,xn-1的之間關係的迭代公式:

                                                                    xn=xn-1- f(xn-1)/ f`(xn-1)

我們在求數值解的時候,雖然我們求根法是求損失函式最小值,當最小值只差一點點的時候,對模型的預測結果影響不大,但是它在底部會震盪很久。 對於這種在底部頻繁震盪的情況下,通常會設定超參tolerance,當xk,xk+1的變化已經小於你設的閾值的時候,我就認為它已經收斂了,而不去非得接近它,駐點數本身就是不那麼可控的。設定超參tolerance,它幾乎在不犧牲精度的情況下來對我們這個函式最優化演算法進行。

總結下牛頓法求根的流程:

.1、已知函式f(x)的情況下隨機產生x0。

 2、由已知的x0按照   xn=xn-1- f(xn-1)/ f`(xn-1)公式進行n次迭代。

 3、當迭代結果xn與上一次迭代結果xn-1相同或小於一定閾值時,本次的結果即為函式f(x)的根。

 

3-牛頓法求駐點問題

既然利用上述辦法找到任意函式的根,能不能借這個找到函式最小值呢?我們可以利用駐點的知識,雖然駐點不一定是極值點,極值點也不一定是駐點,但大部分情況下兩個值相等,駐點也就是導數為零的點,即原函式最小值的點。 所以我們需要找到導函式為零的點,實際上也就是導函式的根。導函式是一個函式,它是用來計算一個函式導數的工具,給我一個x,我就算這個原函式在這個點的導數是多少的這麼一個函式。如果我們能把原函式的導函式寫出來,進而求導函式的根,就解決了求原函式最小值的問題。

既然都是求根問題,我們可不可以利用上面的方法呢?肯定是可以的,只不過現在的函式是我們要求的導函式而已。我們看下剛才原函式求根的迭代公式:xn+1 = xn-f(xn)/ f`(xn)。對於導函式來說,唯一的區別就是f(xn)表示式不同,所以我們此時的原函式就是f`(xn),此時f`(xn)的導函式就是f``(xn)。最終的迭代公式就是:

                                                                   xk = xk-1-f'(xk-1)/ f''( xk-1

通過這個東西,我最終找到xn不再是f(x)的根,而是f(x)導數的根,也就是駐點。

          總結下利用牛頓法求函式的駐點過程:

               1、當函式f(x) 的一階導數 f’(x) = 時點(x,f(x))為函式f(x)的駐點

               2、求某函式的駐點即為求該函式的導函式的根,同樣可以利用牛頓法進行求解

               3、對於f(x) 函式來說 迭代公式為 xk = xk-1 - f’(xk-1)/f’’(xk-1)

 

4-牛頓法求駐點的本質

牛頓法求駐點本質實際上是二階泰勒展開公式。我們先來回顧下什麼是泰勒展開。所謂泰勒展開就是把任意一個複雜函式在某個點附近,用一個有限長度的多項式來擬合,那麼通常多項式在xk點附近展開是φ(x)=f(x)+ f’(x-xk)+ 1/2!f''(xk)*(x-xk)^2+1/3!f'''(xk)(x-xn)^3...,一直往上加,越加越像原函式。 我們看下一階展開就是φ(x)=f(x)+ f’(x-xk)。它本質就是y=ax+b,一條直線,只不過a和b裡面雜糅了很多關於xk函式的數值,a和b是通過xk這個點的函式還有一階導函式算出來的,所以一階泰勒展開就是在xk附近用一條直線儘量的去擬合原函式。什麼叫做xk儘量的去擬合原函式?意思是在xk點附近,我不關注其它地方,我就在你周圍畫一條直線,你能跟我原先xk點的附近重合的越多越好。而二階泰勒展開式是ax2+bx+c,只不過a,b,c是通過這些導數什麼雜糅在一起算出來,無論這些東西等於多少,它始終是一個實數,它一定就是這個形勢,這個形式是一個拋物線。

我們看下任意函式在 xk點附近的二階泰勒展開公式為:

                                           \varphi(x)=f\left(x_{k}\right)+f^{\prime}\left(x_{k}\right)\left(x-x_{k}\right)+\frac{1}{2} f^{\prime \prime}\left(x_{k}\right)\left(x-x_{k}\right)^{2}

該公式表達的函式φ(x) 的幾何意義為通過2次函式對於原函式的最佳擬合。當φ'(x)=0時:

                                                        $f^{\prime}\left(x_{k}\right)+f^{\prime \prime}\left(x_{k}\right)\left(x-x_{k}\right)=0$

解釋下這個公式怎麼來的:對x求導,沒有對xk求導的,xk是個已知數,第一項f(xk)沒有x,求導等於0。第二項對x的求導,把它展開變成x*f’(xk)就剩這麼一個東西,f’(xk)它是個數,它不是x函式。第三項對x求導結果是1/2 f''(xk).2(x-xk) 。綜合以上結果就得到φ'(x)=0的解析式,也就是函式二階泰勒展開所擬合出來的拋物線的最小值。

把上述φ'(x)=0的解析式展開成x= xk -f’(xk)/f’’(xk)。發現剛好和之前牛頓法求駐點xk = xk-1 - f’(xk-1)/f’’(xk-1)的迭代公式一致。所以下一次xxk的迭代值就是以它擬合出來的拋物線的最小值對應的x來作為我原函式根的下一次迭代值。

我們畫圖示意牛頓法求駐點的本質:

因為一階泰勒展開是一條直線它沒有最小值,而拋物線有最小值,實際上它就是在拿擬合出來的拋物線的最小值當成更好一點的x作為下一次的起始點,下次到這新的點,就又找了一個拋物線,以此類推,類似於我們每次拿一個碗形的曲線去套原先的曲線,所以它能更好的擬合在某一點附近的真實曲線情況,正因為如此,這個東西帶來的好處就是牛頓法比梯度下降在底部震盪次數小很多,它能夠讓我們在求函式最優解的過程中有更少的收斂次數。

5-多元函式利用牛頓法求駐點  

上面所說的都是針對一元的,而機器學習裡面都是多元的,所謂的x在機器學習裡面是誰?你要優化損失函式,損失函式是跟著w來的,我們一定要了然一個事,在機器學習的訓練集裡,x是已知數,你唯一未知數就是w。x都是w的係數,它在損失函式裡面無論什麼情況,最終都會變成w的係數。那麼這裡面的w實際上是機器學習裡面的未知數x,w有多少個,往往不止一個。對於多元函式求駐點怎麼求?實際在多元函式中,一階導數對映到多元函式裡就是梯度:即一階導數f'(x) ----->梯度

                                                                                \nabla f=\left[ \begin{array}{c}{\frac{\partial f}{\partial x_{1}}} \\ {\frac{\partial f}{\partial x_{2}}} \\ {\vdots} \\ {\frac{\partial f}{\partial x_{N}}}\end{array}\right]

梯度就是它的所有一階導數給它寫一塊去。比如你原來就一個x,求個導就完事了。你現在有十個x,你求誰都不合適,你就乾脆把十個逐個求個叫偏導,然後把這第一個x求偏導的函式放在這個向量的第一位\frac{\partial f}{\partial x_{1}},第二個求偏導的函式放在向量第二位\frac{\partial f}{\partial x_{2}},最後一個求偏導放在最後一位,組成一個向量。這個向量裡面存的目前來講還是一個函式,這個叫做梯度函式向量,而梯度是不是指的是某個點的梯度,就像導數指的是某一個點的導數一樣,那麼梯度裡邊存的就不再是函式,而是把某組x帶到這一組函式裡面,去得到了那一組實數的向量值,所有你只要說梯度,雖然它裡面寫的是字母,但實際上知道這是一個真真切切的實數。 它是一個實數向量。   那麼梯度是什麼情況?假設你有十個w,梯度就有10個元素,因為要對10個未知的w逐個求偏導放到向量裡面。

對第一個w求偏導,它得的結果是一個導函式,它們通常我們在寫梯度的時候,有時候會這麼寫,$g\left(\vec{x}_{k}\right)$這個x代表一個向量,它並不是向量中的某個元素,這個x就已經包含了若干個x了,x1到xn,k就代表第幾代x。 這個梯度的意思就是說你先把這些偏導函式擱在這之後,然後再對當前這一代xk的具體的值帶到這裡來求出的結果,這個東西叫梯度。涉及到梯度,就意味著有一定的方向。

多元函式中,我們之前的求駐點的迭代公式xk = xk-1 - f’(xk-1)/f’’(xk-1),f’(x)在多元函式中就是它對應的的梯度g(x) ,f’’( x)叫做Hessian矩陣對應關係為:一階導數f''(x) ----->Hessian矩陣

  

這個矩陣是怎麼求?就是它要求兩次導,假如說原來對x1求導,求了之後還對x1求一次導,它還需要再對x1到xn分別求一次導,這樣就形成了一個巨大的偏導數的矩陣,這個矩陣的情況應該是N乘N的,它的對角線元素是對同一個自變數求兩次,而其它是各種各樣的排列組合。這個就是二階導數Hessian矩陣。

所以我們的迭代公式演變為:從x_{k+1}=x_{k}-\frac{f^{\prime}\left(x_{k}\right)}{f^{\prime \prime}\left(x_{k}\right)}$\mathbf{x}_{k+1}=\mathbf{x}_{k}-H_{k}^{-1} \cdot \mathbf{g}_{k}, \quad k=0,1, \cdots$ 其中gk就是多元函式一階導,Hk就是多元函式二階導。gk原來在分子中,Hessian矩陣應該是除以二階導數,所以變成了矩陣的逆。我們可以看一下Hk矩陣,逆矩陣還是一個N*N的,gk是一個N*1的矩陣,它倆相乘得到是一個N*1的向量,就是一個高高的向量, N行1列。xk本身也是一個高高的向量,它倆做減法完全沒有問題。但是Hk矩陣的N*N就給我們帶來了一個非常不喜歡的問題,這個矩陣太難運算的,當你有1000個維度的時候,就是1000×1000的矩陣,然後1萬個維度的時候,10000×10000,維度就爆炸了。而牛頓法求駐點又是一個迭代演算法,所以這個困難我們還要面臨無限多次,導致了牛頓法求駐點在機器學習中無法使用.有沒有什麼解決辦法呢。

6-BFGS演算法

 BFGS演算法是通過迭代來逼近H_{k}^{-1}的演算法.逼近的方式如下:

                                      D_{k+1}=\left(I-\frac{\mathbf{s}_{k} \mathbf{y}_{k}^{T}}{\mathbf{y}_{k}^{T} \mathbf{s}_{k}}\right) D_{k}\left(I-\frac{\mathbf{y}_{k} \mathbf{s}_{k}^{T}}{\mathbf{y}_{k}^{T} \mathbf{s}_{k}}\right)+\frac{\mathbf{s}_{k} \mathbf{s}_{k}^{T}}{\mathbf{y}_{k}^{T} \mathbf{s}_{k}}

        其中:$\mathrm{s}_{k}=\mathrm{x}_{k+1}-\mathrm{x}_{k}, \mathrm{y}_{k}=\mathrm{g}_{k+1}-\mathrm{g}_{k}$。I是單位矩陣,

        BFGS就是通過迭代來逼近Hk的逆矩陣矩陣,其中第一步的D矩陣是單位矩陣.所謂單位矩陣就是隻有對角線元素為1,其餘全為零的矩陣。根據之前的迭代公式:$\mathrm{x}_{k+1}=\mathrm{x}_{k}-H_{k}^{-1} \cdot \mathrm{g}_{k}, \quad k=0,1, \cdots$,除了第一步還是需要計算之前的逆矩陣H1,第一步迭代需要計算出x2=x1-H1*g1,s1=x2-x1,y1=g2-g1。因為有x2,所以g2是其梯度,也可求,有了s1,y1便可以求D2來近似代替H2的逆矩陣,然後一步步迭代,求出駐點。

         我們要通過牛頓求駐點法和BFGS演算法來求得一個函式的根,兩個演算法都需要迭代,慢慢逼近函式根,經過k次迭代以後,所得到的解就是機器學習中目標函式導函式的根.這種兩個演算法共同迭代的計算方式,我們稱之為On The Fly。

          回顧一下梯度下降的表示式\Theta_{k} = \Theta_{k+1} - \alpha \cdot g,在BFGS演算法迭代的第一步x2=x1-D1*g1,單位矩陣與梯度g相乘,就等於梯度g,形式上同梯度下降的表示式是相同的。相當於學習率等於1的梯度下降,所以BFGS演算法可以理解為從梯度下降逐步轉換為牛頓法求函式解的一個演算法.

         雖然我們使用了BFGS演算法來利用單位矩陣逐步逼近H矩陣,但是根據Dk+1的公式,每次計算的時候都要儲存上一代的Dk矩陣,Dk矩陣有多大呢.假設我們的資料集有十萬個維度(不算特別大),那麼每次迭代所要儲存D矩陣的結果是74.5GB.

我們無法儲存如此巨大的矩陣內容,如何解決呢? L-BFGS 演算法上陣。

 7-L-BFGS演算法

我們每一次對D矩陣的迭代,都是通過迭代計算sk和yk得到的.既然存不下D矩陣,那麼我們儲存下所有的sk和yk,想要得到D10就用單位矩陣同儲存下的s1和y1到s10和y10計算就可以了.這樣一個時間換空間的辦法,有效節省了記憶體空間。

         但是,僅僅是這樣還是不夠的,因為當迭代次數非常大的時候,我們的記憶體同樣存不下.這個時候只能丟掉一些存不下的資料.假設我們設定的儲存向量數為100,當s和y迭代超過100時,就會扔掉第一個s和y,儲存s2到s101和y2到y101,每多一次迭代就對應的扔掉最前邊的s和y。假如最後收斂次數是1000的話,只保留s901,y901從而計算出D901 ,然後再保留s902,y902計算出D902,依次根據s1000,y1000,從而算出D1000,按理說需要D900才能計算出D901 ,此時我們粗暴的將D901置為單位矩陣I,這樣雖然損失了精度,但確可以保證使用有限的記憶體將函式的解通過BFGS演算法求得到。

雖然L-BFGS演算法是線性收斂,但是每次迭代的開銷非常小,因此L-BFGS演算法執行速度還是很快的,而且由於每一步迭代都能保證近似矩陣的正定,因此演算法的魯棒性還是很強的。

 

相關文章