梯度下降法和牛頓法計算開根號
本文將介紹如何不調包,只能使用加減乘除法實現對根號x的求解。主要介紹梯度下降和牛頓法者兩種方法,并給出 C++ 實現。
梯度下降法
思路/步驟
- 轉化問題,將 x\sqrt{x}x? 的求解轉化為最小化目標函數:L(t)L(t)L(t),L(t)=(t2?x)L(t)=(t^2-x)L(t)=(t2?x) ,當 LLL 趨近于 0 時,ttt 就是我們想要的結果;
- 迭代尋找使得 LLL 變小的 ttt ,
- 最終得到足夠小的 LLL 時的 ttt ,即使得 L→0L\rightarrow 0L→0, 得到結果 ttt
- 求解 LLL 的極小值,就是導數為 0 的點
如何迭代
OK,現在的問題就是要如何進行迭代,從而得到盡可能小的 LLL,為此,我們要使得隨著每一次迭代中 ttt 的變化,LLL 都朝著更小的方向變化一個合適的步長。
確定如何迭代,無非就是要確定每次迭代的方向和步長。
最自然的想法,我們使得 ttt 朝政府兩個方向都移動一個很小的步長,然后比一比,看哪個的 LLL 更小了,就向哪個方向移動。即:
- 若 L(t+Δt)<L(t)L(t+\Delta t)<L(t)L(t+Δt)<L(t) ,則 t1=t+Δtt_1=t+\Delta tt1?=t+Δt;
- 若 L(t?Δt)<L(t)L(t-\Delta t)<L(t)L(t?Δt)<L(t) ,則 t1=t?Δtt_1=t-\Delta tt1?=t?Δt;
注意這里的 Δt\Delta tΔt 應當是一個大于零的無窮小數,即 0+0^+0+ 。
我們接下來再對上面的式子進行一點變化:
- 若 L(t+Δt)?L(t)<0L(t+\Delta t)-L(t)<0L(t+Δt)?L(t)<0 ,則 t1=t+Δtt_1=t+\Delta tt1?=t+Δt;
- 若 L(t)?L(t?Δt)>0L(t)-L(t-\Delta t)>0L(t)?L(t?Δt)>0 ,則 t1=t?Δtt_1=t-\Delta tt1?=t?Δt;
將這兩個式子寫在一起:
t1=t?L(t+Δt)?L(t)∣L(t+Δt)?L(t)∣?Δtt_1=t-\frac{L(t+\Delta t)-L(t)}{|L(t+\Delta t)-L(t)|}\cdot \Delta t t1?=t?∣L(t+Δt)?L(t)∣L(t+Δt)?L(t)??Δt
這里的 L(t+Δt)?L(t)∣L(t+Δt)?L(t)∣\frac{L(t+\Delta t)-L(t)}{|L(t+\Delta t)-L(t)|}∣L(t+Δt)?L(t)∣L(t+Δt)?L(t)? 用來指示正負號。再進行一點變形:
t1=t?L(t+Δt)?L(t)∣L(t+Δt)?L(t)∣?Δt=t?L(t+Δt)?L(t)Δt∣L(t+Δt)?L(t)Δt∣?Δt=t?L(t+Δt)ΔtΔt∣L(t+Δt)?L(t)Δt∣=t?αL′(t),α=Δt∣L(t+Δt)?L(t)Δt∣→0+,L′(t)=L(t+Δt)?L(t)Δt\begin{align} t_1&=t-\frac{L(t+\Delta t)-L(t)}{|L(t+\Delta t)-L(t)|}\cdot \Delta t\\ &=t-\frac{\frac{L(t+\Delta t)-L(t)}{\Delta t}}{|\frac{L(t+\Delta t)-L(t)}{\Delta t}|}\cdot \Delta t\\ &=t-\frac{L(t+\Delta t)}{\Delta t}\frac{\Delta t}{|\frac{L(t+\Delta t)-L(t)}{\Delta t}|}\\ &=t-\alpha L'(t), \ \ \ \alpha=\frac{\Delta t}{|\frac{L(t+\Delta t)-L(t)}{\Delta t}|}\rightarrow 0^+,\ \ \ L'(t)=\frac{L(t+\Delta t)-L(t)}{\Delta t} \end{align} t1??=t?∣L(t+Δt)?L(t)∣L(t+Δt)?L(t)??Δt=t?∣ΔtL(t+Δt)?L(t)?∣ΔtL(t+Δt)?L(t)???Δt=t?ΔtL(t+Δt)?∣ΔtL(t+Δt)?L(t)?∣Δt?=t?αL′(t),???α=∣ΔtL(t+Δt)?L(t)?∣Δt?→0+,???L′(t)=ΔtL(t+Δt)?L(t)???
-
當a取無窮小時,雖然一定保證下降,但效率太慢
-
日常設計的很多函數,可以允許使用相對大一些的步長,比如 α=0.01\alpha = 0.01α=0.01。理由是,若步長大了,出現跳過合適位置,使得 L(t1)>L(t0)L(t1) > L(t0)L(t1)>L(t0)。再下一個時刻,依舊可能跳回來使得 L(t2)<L(t1)L(t2) < L(t1)L(t2)<L(t1)
-
大的步長不能保證一定收斂,但是大部分時候是可以很好的工作,也因此,步長 α\alphaα,我們稱之為學習率,通常會給一個相對小的數字,但不會太小。
-
總之,學習率一般需要在不同的模型任務中手動調試。
代碼
float sqrt_grad_decent(float x) {float t = x / 2;float L = (t * t - x) * (t * t - x);float alpha = 0.001;while ( L > 1e-5 ) {float delta = 2 * (t * t - x) * 2 * t;t = t - alpha * delta;L = (t * t - x) * (t * t - x);printf("t=%f\n", t);}return t;
}
總結
-
梯度下降法是通過觀察局部,決定如何調整的算法。如果函數具有多個極值,則可能陷入局部極值,此時初始點的選擇直接影響收斂結果
-
大的步長在一定程度上可能跨過局部極值,但也可能造成震蕩導致不收斂
-
步長的選擇,需要根據函數的特性來找到合適取值,若導數特別大時,則步長取小,導數小時,步長可大。否則很容易造成收斂問題
-
存在一類算法,可以在一定范圍內搜索一個合適步長,使得每一次迭代更加穩定
牛頓法1
梯度下降法常用語求解函數極小值的情況,而牛頓法常用于求解函數零點的情況,即 L=0L=0L=0 時方程的根。
思路/步驟
- 轉化問題,將求解 x\sqrt{x}x? 轉換為求解 L(t)=t2?x=0L(t)=t^2-x=0L(t)=t2?x=0 時的根,即函數的零點
- 迭代尋找 ttt
如何迭代
用曲線在 t0t_0t0? 處切線與 xxx 軸的交點作為 t1t_1t1? ,來逼近函數的零點。圖/牛頓法
切線斜率,同樣可以用導數來表示 。
考慮兩個坐標系:原坐標系 o1o1o1 ,新坐標系 o2o2o2 ,其中 o2o2o2 以 o1o1o1 中的 (x1,f(x1))(x_1,f(x_1))(x1?,f(x1?)) 為原點。則在 o2o2o2 坐標系中,下圖紅色切線可表示為:
fo2(x)=f′(x1)xf_{o2}(x)=f'(x_1)x fo2?(x)=f′(x1?)x
則該切線與 xxx 軸交點:
fo2(x2)=f′(x1)(x2?x1)=?f(x1)f_{o2}(x_2)=f'(x_1)(x_2-x_1)=-f(x_1) fo2?(x2?)=f′(x1?)(x2??x1?)=?f(x1?)
則有:
x2?x1=?f(x1)f′(x1)x2=x1?f(x1)f′(x1)x_2-x_1=-\frac{f(x_1)}{f'(x_1)}\\ x_2=x_1-\frac{f(x_1)}{f'(x_1)} x2??x1?=?f′(x1?)f(x1?)?x2?=x1??f′(x1?)f(x1?)?
代碼
我們經過上一小節已經知道迭代的方法:
t1=t?L(t)L′(t)t_1=t-\frac{L(t)}{L'(t)} t1?=t?L′(t)L(t)?
代碼:
float sqrt_newton1(float x) {float t = x / 2;float L = t * t - x;while ( abs(L) > 1e-5 ) {float dL = 2 * t;t = t - L / dL;L = t * t - x;}return t;
}
牛頓法2
思路
既然牛頓法是對函數求零點,那我們能不能對函數的導函數求零點呢?這樣就可以得到函數的極值了。
與梯度下降法的目標函數 L(t)=(t2?x)L(t)=(t^2-x)L(t)=(t2?x) 是相同的,而區別在于,迭代式不同 t1=t?f′(t)f′′(t)t_1=t-\frac{f'(t)}{f''(t)}t1?=t?f′′(t)f′(t)?,并且其中步長(學習率)為 1。
代碼
float sqrt_newton2(float x) {float t = x / 2;float L = (t * t - x) * (t * t - x);while ( L > 1e-5 ) {float dL = 2 * (t * t - x) * 2 * t;float d2L = 12 * t * t - 4 * x;t = t - dL / d2L;L = (t * t - x) * (t * t - x);}return t;
}
Ref
- 牛頓法