梯度下降法的推導

梯度下降法是一個一階最優化算法,通常也稱爲最陡下降法,要使用梯度下降法找到一個函數的局部極小值,必須向函數上當前點對應梯度反方向的規定步長距離點進行迭代搜索。如果相反地向梯度正方向迭代進行搜索,則會接近函數的局部極大值點;這個過程則被稱爲梯度上升法

介紹梯度下降法之前首先先介紹一下梯度。梯度的本意是一個向量(矢量),表示某一函數在該點處的方向導數沿着該方向取得最大值,即函數在該點處沿着該方向(此梯度的方向)變化最快,變化率最大(爲該梯度的模)。

首先介紹一元函數:

梯度下降法的公式:x_{i+1} - x_{i} = \eta f{}'(x)

其中\eta是學習率(learn rate),f{}'(x)就是梯度。

接下來我們來介紹一下這個公式是怎麼來的

首先從泰勒展開式入手f(x)=\sum _{n=0}^{\infty }{\frac {f^{(n)}(x_{0})}{n!}}(x-x_{0})^{n}

一階泰勒展開式爲f(x)\approx f(x_{0}) + (x-x_{0})f{}'(x)

現在我們令x-x_{0} = \eta \nu 其中\eta爲步進長度,是標量。而\nu表示的是x-x_{0}方向的單位向量。

現在f(x)\approx f(x_{0}) + (x-x_{0})f{}'(x)可以表示成f(x)\approx f(x_{0}) + \eta \nu f{}'(x)

移項後得到f(x) - f(x_{0}) \approx \eta \nu f{}'(x)

因爲梯度下降法每次更新x希望f(x)可以減小,那麼得到的是\nu f{}'(x)< 0。其中\nuf{}'(x)都是矢量。

現在我們來討論如何保證\nu f{}'(x)最小。

此時我們需要引入向量的知識, 看下面的三幅圖

圖1
圖2
圖3

 

 

 

 

可以看到圖1的銳角和圖2的鈍角都不是最小的結果,只有圖3的\alpha = 180^{\circ}cos\alpha =-1使得\nu f{}'(x)有最小值,即沿着導數f{}'(x)的反方向,函數下降的最快。

到此爲止,我們分析了爲什麼導數沿着函數的反方向會下降最快,現在在把公式整理一下。

因爲\nu是單位向量,且方向是沿着f{}'(x)的反方向,因此\nu = -\frac{f{}'(x)}{\left \| f{}'(x) \right \|}

現在我們將\nu = -\frac{f{}'(x)}{\left \| f{}'(x) \right \|}代入到x-x_{0} = \eta \nu中得:x-x_{0} = -\eta\frac{f{x}'}{\left \| f{}'(x))\right \|},我們將\eta\frac{1}{\left \| f{}'(x))\right \|}都令爲\eta

因此可以寫成x - x_{0} = \eta f{}'(x),不斷迭代此公式即x_{i+1} - x_{i} = \eta f{}'(x)。其中\eta被稱爲learn rate學習率

上述就是梯度下降法的推導過程。

求此函數f(x) = x^{2}+2x+0.2

實現代碼

#實現f = x**2+2*x - 0.2的最小值
import numpy as np

#定義fx
def f(x):
    f = x**2+2*x - 0.2
    return f
#定義導數
def df(x):
    df = 2*x+2
    return df
# f:函數
# df:導數
# x:zip變量
# err:誤差,用來判斷是否滿足求解要求
def gradient_discent(f, df, x, err, learn_rate):
    loop = 1
    x_i = float(x)
    e_tmp = err + 1
    while e_tmp > err:
        #超出循環次數
        if loop > 1000:
            print('cycles exceeded')
            break
        print('######loop'+str(loop))
        f_tmp = f(x_i)
        df_tmp = df(x_i)
        print('xi = ' + str(x_i) + ',f = ' + str(f_tmp) + ',df = ' + str(df_tmp))
        x_i = x_i - learn_rate*df_tmp
        e_tmp = abs(f(x_i) - f_tmp)
        print('err = ' + str(e_tmp))
        loop = loop + 1
    return x_i
x = gradient_discent(f, df, 3, 0.00000001, 0.01)
print(x)

多元函數的代碼

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
 
def Fun(x,y):#原函數
    return x-y+2*x*x+2*x*y+y*y
 
def PxFun(x,y):#偏x導
    return 1+4*x+2*y
 
def PyFun(x,y):#偏y導
    return -1+2*x+2*y
 
#初始化
fig=plt.figure()#figure對象
ax=Axes3D(fig)#Axes3D對象
X,Y=np.mgrid[-2:2:40j,-2:2:40j]#取樣並作滿射聯合
Z=Fun(X,Y)#取樣點Z座標打表
ax.plot_surface(X,Y,Z,rstride=1,cstride=1,cmap="rainbow")
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
 
#梯度下降
step=0.0008#下降係數
x=0
y=0#初始選取一個點
tag_x=[x]
tag_y=[y]
tag_z=[Fun(x,y)]#三個座標分別打入表中,該表用於繪製點
new_x=x
new_y=y
Over=False
while Over==False:
    new_x-=step*PxFun(x,y)
    new_y-=step*PyFun(x,y)#分別作梯度下降
    if Fun(x,y)-Fun(new_x,new_y)<7e-9:#精度
        Over=True
    x=new_x
    y=new_y#更新舊點
    tag_x.append(x)
    tag_y.append(y)
    tag_z.append(Fun(x,y))#新點三個座標打入表中
 
#繪製點/輸出座標
ax.plot(tag_x,tag_y,tag_z,'r.')
plt.title('(x,y)~('+str(x)+","+str(y)+')')
plt.show()
Caption
發佈了53 篇原創文章 · 獲贊 50 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章