python迭代算法_Python实现简单的梯度下降法
Python 實(shí)現(xiàn)簡(jiǎn)單的梯度下降法
機(jī)器學(xué)習(xí)算法常常可以歸結(jié)為求解一個(gè)最優(yōu)化問(wèn)題,而梯度下降法就是求解最優(yōu)化問(wèn)題的一個(gè)方法。
梯度下降法(gradient descent)或最速下降法(steepest decent),是求解無(wú)約束最優(yōu)化問(wèn)題的一種最常用的方法。
梯度下降法實(shí)現(xiàn)簡(jiǎn)單,是一種迭代算法,每一步會(huì)求解目標(biāo)函數(shù)的梯度向量。
本文分為理論和 Python 代碼實(shí)踐,希望實(shí)現(xiàn)簡(jiǎn)單的梯度下降法,相關(guān)代碼已放在?GitHub?中。
理論
問(wèn)題定義
那么什么是目標(biāo)函數(shù),在機(jī)器學(xué)習(xí)中這常常是一個(gè)損失函數(shù)。不管怎么稱(chēng)呼,它就是一個(gè)函數(shù) $f(x)$,而梯度下降法的目的就是獲取這個(gè)函數(shù)的極小值。
下面給出一個(gè)較為正式的問(wèn)題定義。
假設(shè) $f(x)$ 是 $R^n$ 上具有一階連續(xù)偏導(dǎo)數(shù)的函數(shù)。需要求解的無(wú)約束最優(yōu)化問(wèn)題是:
$$\underset{x\in R^n}{min}f(x)$$
即需要求出目標(biāo)函數(shù) $f(x)$ 的極小點(diǎn) $x^*$。
算法思想和推導(dǎo)
要理解梯度下降法,首先要理解梯度和負(fù)梯度的概念。
梯度是從 n 維推廣出來(lái)的概念,類(lèi)似于斜率。梯度的本意是一個(gè)向量,表示某一函數(shù)在該點(diǎn)處的方向?qū)?shù)沿著該方向取得最大值,即函數(shù)在該點(diǎn)處沿著該方向(此梯度的方向)變化最快,變化率最大(為該梯度的模)。具體定義和公式可以參考百度定義。
舉個(gè)例子再體會(huì)一下梯度是表示方向的一個(gè)向量:
對(duì)于函數(shù) $f(x_1,x_2)=2x_1^3-x_2^2$ 來(lái)說(shuō),它的梯度就是 $g(x_1,x_2)=[6x_1^2,-2x_2]$。對(duì)于給定點(diǎn) $[x_1, x_2]$ 的附近處,它在 $[6x_1^2,-2x_2]$ 方向變化率最大,而其負(fù)梯度方向就是 $[-6x_1^2,2x_2]$。例如,在點(diǎn)?$[2, 3]$ 附近處,它的負(fù)梯度方向就是 $[-24, -6]$。在此處,點(diǎn)?$[2, 3]$ 向這個(gè)方向移動(dòng),會(huì)使得?$f(x_1,x_2)=2x_1^3-x_2^2$ 值減小的速率最快。反之,如果點(diǎn)?$[2, 3]$ 向梯度方向?$[24, 6]$?移動(dòng),會(huì)使得?$f(x_1,x_2)=2x_1^3-x_2^2$ 值增加的速率最快。
理解了梯度之后,其實(shí)就可以很容易推導(dǎo)出梯度下降法的算法過(guò)程了。
梯度下降法的思想,就是選取適當(dāng)?shù)某踔?$x_{0}$,不斷迭代更新 $x$ 的值,極小化目標(biāo)函數(shù),最終收斂。
由于負(fù)梯度方向是使函數(shù)值下降最快的方向,因此梯度下降在每一步采用負(fù)梯度方向更新 $x$ 的值,最終達(dá)到函數(shù)值最小。
可以看出,梯度下降法采用的是貪心的思想。
根據(jù)一階泰勒展開(kāi),當(dāng) $x$ 趨近于 $x_k$ 時(shí):
$$f(x)\approx f(x_k)+g_{k}(x-x_k)$$
這里,$g_k=g(x_k)=\bigtriangledown f(x_k)$ 是 $f(x)$ 在 $x_k$ 的梯度。
我們假設(shè)設(shè)定了一個(gè)初始值 $x_0$,現(xiàn)在需要確定一個(gè) $x_1$,代入上式可得:
$$f(x_1)\approx f(x_0)+g_{0}(x_1-x_0)$$
假設(shè) $x_1$ 和 $x_0$ 之間的距離一定時(shí),為了讓?$f(x_1)$ 最小(貪心策略),應(yīng)該有:
$$g_{0}(x_1-x_0)=\left | g_{0} \right | \left | x_1-x_0 \right |cos\theta =-\left | g_{0} \right | \left | x_1-x_0 \right |$$
也就是需要讓 $x_1-x_0$ 和梯度 $g_{0}$ 的夾角 $\theta$ 為 180°,使得 $cos\theta =-1$。換言之,$x_1-x_0$ 和梯度?$g_{0}$ 方向相反。
由于 $x_1-x_0=-\frac{g_0}{\left | g_0 \right |}\left | x_1-x_0 \right |$,那么可以得到:
$$x_1=x_0-\frac{g_0}{\left | g_0 \right |}\left | x_1-x_0 \right |=x_0-g_0\lambda_0$$
其中 $\lambda_0=\frac{\left | x_1-x_0 \right |}{\left | g_0 \right |}$ 定義為學(xué)習(xí)率,它實(shí)際上步長(zhǎng)除以梯度的模。因此當(dāng)學(xué)習(xí)率一定時(shí),步長(zhǎng)其實(shí)是一直變化的。當(dāng)梯度較大時(shí),步長(zhǎng)也較大;而當(dāng)梯度較小時(shí),步長(zhǎng)也較小。這往往是我們希望的性質(zhì),因?yàn)楫?dāng)接近于局部最優(yōu)解時(shí),梯度變得較小,這時(shí)往往也需要步長(zhǎng)變得更小,以利于找到局部最優(yōu)解。
同理,我們可以得到 $x_2=x_1-g_1\lambda_1$ ,依次類(lèi)推,有:
$$x_{k+1}=x_k-g_k\lambda_k$$
其中,學(xué)習(xí)率$\lambda_k$ 要足夠小,使得:
滿足泰勒公式所需要的精度。
能夠很好地捕捉到極小值。
這是一個(gè)顯式表達(dá)式,可以不斷求出 $x_{k+1}$,當(dāng)滿足收斂條件時(shí)(如梯度足夠小或者 $x_{k+1}$ 更新變化量足夠小),退出迭代,此時(shí) $f(x_{k+1})$ 就是一個(gè)求解出來(lái)的最小函數(shù)值。
至此完成了梯度下降法邏輯上的推導(dǎo)。
Python 代碼實(shí)現(xiàn)
理論已經(jīng)足夠多了,接下來(lái)敲一敲實(shí)在的代碼吧。
一維問(wèn)題
假設(shè)我們需要求解的目標(biāo)函數(shù)是:
$$f(x)=x^2+1$$
顯然一眼就知道它的最小值是 $x=0$ 處,但是這里我們需要用梯度下降法的 Python 代碼來(lái)實(shí)現(xiàn)。
1 #!/usr/bin/env python
2 #-*- coding: utf-8 -*-
3 """
4 一維問(wèn)題的梯度下降法示例5 """
6
7
8 deffunc_1d(x):9 """
10 目標(biāo)函數(shù)11 :param x: 自變量,標(biāo)量12 :return: 因變量,標(biāo)量13 """
14 return x ** 2 + 1
15
16
17 defgrad_1d(x):18 """
19 目標(biāo)函數(shù)的梯度20 :param x: 自變量,標(biāo)量21 :return: 因變量,標(biāo)量22 """
23 return x * 2
24
25
26 def gradient_descent_1d(grad, cur_x=0.1, learning_rate=0.01, precision=0.0001, max_iters=10000):27 """
28 一維問(wèn)題的梯度下降法29 :param grad: 目標(biāo)函數(shù)的梯度30 :param cur_x: 當(dāng)前 x 值,通過(guò)參數(shù)可以提供初始值31 :param learning_rate: 學(xué)習(xí)率,也相當(dāng)于設(shè)置的步長(zhǎng)32 :param precision: 設(shè)置收斂精度33 :param max_iters: 最大迭代次數(shù)34 :return: 局部最小值 x*35 """
36 for i inrange(max_iters):37 grad_cur =grad(cur_x)38 if abs(grad_cur) <39 break>
40 cur_x = cur_x - grad_cur *learning_rate41 print("第", i, "次迭代:x 值為", cur_x)42
43 print("局部最小值 x =", cur_x)44 returncur_x45
46
47 if __name__ == '__main__':48 gradient_descent_1d(grad_1d, cur_x=10, learning_rate=0.2, precision=0.000001, max_iters=10000)
其輸出結(jié)果如下:
第 0 次迭代:x 值為 6.0第 1 次迭代:x 值為 3.5999999999999996第 2 次迭代:x 值為 2.1599999999999997第 3 次迭代:x 值為 1.2959999999999998第 4 次迭代:x 值為 0.7775999999999998第 5 次迭代:x 值為 0.46655999999999986第 6 次迭代:x 值為 0.2799359999999999第 7 次迭代:x 值為 0.16796159999999993第 8 次迭代:x 值為 0.10077695999999996第 9 次迭代:x 值為 0.06046617599999997第 10 次迭代:x 值為 0.036279705599999976第 11 次迭代:x 值為 0.021767823359999987第 12 次迭代:x 值為 0.013060694015999992第 13 次迭代:x 值為 0.007836416409599995第 14 次迭代:x 值為 0.004701849845759997第 15 次迭代:x 值為 0.002821109907455998第 16 次迭代:x 值為 0.0016926659444735988第 17 次迭代:x 值為 0.0010155995666841593第 18 次迭代:x 值為 0.0006093597400104956第 19 次迭代:x 值為 0.0003656158440062973第 20 次迭代:x 值為 0.0002193695064037784第 21 次迭代:x 值為 0.00013162170384226703第 22 次迭代:x 值為 7.897302230536021e-05第 23 次迭代:x 值為 4.7383813383216124e-05第 24 次迭代:x 值為 2.8430288029929674e-05第 25 次迭代:x 值為 1.7058172817957805e-05第 26 次迭代:x 值為 1.0234903690774682e-05第 27 次迭代:x 值為 6.1409422144648085e-06第 28 次迭代:x 值為 3.684565328678885e-06第 29 次迭代:x 值為 2.210739197207331e-06第 30 次迭代:x 值為 1.3264435183243986e-06第 31 次迭代:x 值為 7.958661109946391e-07第 32 次迭代:x 值為 4.775196665967835e-07局部最小值 x = 4.775196665967835e-07
二維問(wèn)題
接下來(lái)推廣到二維,目標(biāo)函數(shù)設(shè)為:
$$f(x,y) = -e^{-(x^2 + y^2)}$$
該函數(shù)在 $[0, 0]$ 處有最小值。
1 #!/usr/bin/env python
2 #-*- coding: utf-8 -*-
3 """
4 二維問(wèn)題的梯度下降法示例5 """
6 importmath7 importnumpy as np8
9
10 deffunc_2d(x):11 """
12 目標(biāo)函數(shù)13 :param x: 自變量,二維向量14 :return: 因變量,標(biāo)量15 """
16 return - math.exp(-(x[0] ** 2 + x[1] ** 2))17
18
19 defgrad_2d(x):20 """
21 目標(biāo)函數(shù)的梯度22 :param x: 自變量,二維向量23 :return: 因變量,二維向量24 """
25 deriv0 = 2 * x[0] * math.exp(-(x[0] ** 2 + x[1] ** 2))26 deriv1 = 2 * x[1] * math.exp(-(x[0] ** 2 + x[1] ** 2))27 returnnp.array([deriv0, deriv1])28
29
30 def gradient_descent_2d(grad, cur_x=np.array([0.1, 0.1]), learning_rate=0.01, precision=0.0001, max_iters=10000):31 """
32 二維問(wèn)題的梯度下降法33 :param grad: 目標(biāo)函數(shù)的梯度34 :param cur_x: 當(dāng)前 x 值,通過(guò)參數(shù)可以提供初始值35 :param learning_rate: 學(xué)習(xí)率,也相當(dāng)于設(shè)置的步長(zhǎng)36 :param precision: 設(shè)置收斂精度37 :param max_iters: 最大迭代次數(shù)38 :return: 局部最小值 x*39 """
40 print(f"{cur_x} 作為初始值開(kāi)始迭代...")41 for i inrange(max_iters):42 grad_cur =grad(cur_x)43 if np.linalg.norm(grad_cur, ord=2) <44 break>
45 cur_x = cur_x - grad_cur *learning_rate46 print("第", i, "次迭代:x 值為", cur_x)47
48 print("局部最小值 x =", cur_x)49 returncur_x50
51
52 if __name__ == '__main__':53 gradient_descent_2d(grad_2d, cur_x=np.array([1, -1]), learning_rate=0.2, precision=0.000001, max_iters=10000)
$x_0$ 的初始值設(shè)為 $[1,-1]$ ,運(yùn)行后的結(jié)果如下:
[ 1 -1] 作為初始值開(kāi)始迭代...
第 0 次迭代:x 值為 [ 0.94586589 -0.94586589]
第 1 次迭代:x 值為 [ 0.88265443 -0.88265443]
第 2 次迭代:x 值為 [ 0.80832661 -0.80832661]
第 3 次迭代:x 值為 [ 0.72080448 -0.72080448]
第 4 次迭代:x 值為 [ 0.61880589 -0.61880589]
第 5 次迭代:x 值為 [ 0.50372222 -0.50372222]
第 6 次迭代:x 值為 [ 0.3824228 -0.3824228]
第 7 次迭代:x 值為 [ 0.26824673 -0.26824673]
第 8 次迭代:x 值為 [ 0.17532999 -0.17532999]
第 9 次迭代:x 值為 [ 0.10937992 -0.10937992]
第 10 次迭代:x 值為 [ 0.06666242 -0.06666242]
第 11 次迭代:x 值為 [ 0.04023339 -0.04023339]
第 12 次迭代:x 值為 [ 0.02419205 -0.02419205]
第 13 次迭代:x 值為 [ 0.01452655 -0.01452655]
第 14 次迭代:x 值為 [ 0.00871838 -0.00871838]
第 15 次迭代:x 值為 [ 0.00523156 -0.00523156]
第 16 次迭代:x 值為 [ 0.00313905 -0.00313905]
第 17 次迭代:x 值為 [ 0.00188346 -0.00188346]
第 18 次迭代:x 值為 [ 0.00113008 -0.00113008]
第 19 次迭代:x 值為 [ 0.00067805 -0.00067805]
第 20 次迭代:x 值為 [ 0.00040683 -0.00040683]
第 21 次迭代:x 值為 [ 0.0002441 -0.0002441]
第 22 次迭代:x 值為 [ 0.00014646 -0.00014646]
第 23 次迭代:x 值為 [ 8.78751305e-05 -8.78751305e-05]
第 24 次迭代:x 值為 [ 5.27250788e-05 -5.27250788e-05]
第 25 次迭代:x 值為 [ 3.16350474e-05 -3.16350474e-05]
第 26 次迭代:x 值為 [ 1.89810285e-05 -1.89810285e-05]
第 27 次迭代:x 值為 [ 1.13886171e-05 -1.13886171e-05]
第 28 次迭代:x 值為 [ 6.83317026e-06 -6.83317026e-06]
第 29 次迭代:x 值為 [ 4.09990215e-06 -4.09990215e-06]
第 30 次迭代:x 值為 [ 2.45994129e-06 -2.45994129e-06]
第 31 次迭代:x 值為 [ 1.47596478e-06 -1.47596478e-06]
第 32 次迭代:x 值為 [ 8.85578865e-07 -8.85578865e-07]
第 33 次迭代:x 值為 [ 5.31347319e-07 -5.31347319e-07]
第 34 次迭代:x 值為 [ 3.18808392e-07 -3.18808392e-07]
局部最小值 x = [ 3.18808392e-07 -3.18808392e-07]
我們?cè)僭囍猿跏贾?$[3,-3]$ 處開(kāi)始尋找最小值,即:
gradient_descent_2d(grad_2d, cur_x=np.array([3, -3]), learning_rate=0.2, precision=0.000001, max_iters=10000)
結(jié)果可能出乎人意料:
[ 3 -3] 作為初始值開(kāi)始迭代...
局部最小值 x = [ 3 -3]
梯度下降法沒(méi)有找到真正的極小值點(diǎn)!
如果仔細(xì)觀察目標(biāo)函數(shù)的圖像,以及梯度下降法的算法原理,你就很容易發(fā)現(xiàn)問(wèn)題所在了。在 $[3, -3]$ 處的梯度就幾乎為 0 了!
print(grad_2d(np.array([3, -3])))
[ 9.13798785e-08 -9.13798785e-08]
由于“梯度過(guò)小”,梯度下降法可能無(wú)法確定前進(jìn)的方向了。即使人為增加收斂條件中的精度,也會(huì)由于梯度過(guò)小,導(dǎo)致迭代中前進(jìn)的步長(zhǎng)距離過(guò)小,循環(huán)時(shí)間過(guò)長(zhǎng)。
梯度下降法的局限性
梯度下降法實(shí)現(xiàn)簡(jiǎn)單,原理也易于理解,但它有自身的局限性,因此有了后面很多算法對(duì)它的改進(jìn)。
對(duì)于梯度過(guò)小的情況,梯度下降法可能難以求解。
此外,梯度下降法適合求解只有一個(gè)局部最優(yōu)解的目標(biāo)函數(shù),對(duì)于存在多個(gè)局部最優(yōu)解的目標(biāo)函數(shù),一般情況下梯度下降法不保證得到全局最優(yōu)解(由于凸函數(shù)有個(gè)性質(zhì)是只存在一個(gè)局部最優(yōu)解,所有也有文獻(xiàn)的提法是:當(dāng)目標(biāo)函數(shù)是凸函數(shù)時(shí),梯度下降法的解才是全局最優(yōu)解)。
由于泰勒公式的展開(kāi)是近似公式,要求迭代步長(zhǎng)要足夠小,因此梯度下降法的收斂速度并非很快的。
總結(jié)
以上是對(duì)用 Python 實(shí)現(xiàn)簡(jiǎn)單梯度下降法的思考與總結(jié),整個(gè)代碼示例參見(jiàn)?GitHub,有何建議和問(wèn)題請(qǐng)留下您的反饋,謝謝!
44>39>總結(jié)
以上是生活随笔為你收集整理的python迭代算法_Python实现简单的梯度下降法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux下mysql主从同步是主从i/
- 下一篇: python正则表达式操作指南_第二篇详