寫給小白的梯度下降算法詳解(從下山比喻、數學推導到代碼實現)

如對博文有任何疑問,請留言。

1. 方向導數

方向導數:類比於函數的偏導數是函數沿座標軸方向的變化率,方向導數是函數沿某一射線方向的變化率。

定理:如果函數 f(x,y)f(x,y) 在點 P0(x0,y0)P_0(x_0,y_0) 可微分,那麼函數在該點沿任一方向 ll 的方向導數存在,且有
fl(x0,y0)=fx(x0,y0)cosα+fy(x0,y0)cosβ(1) \left. \frac{\partial f}{\partial l} \right|_{(x_0,y_0)} =f_x(x_0,y_0)\cos\alpha + f_y(x_0,y_0)\cos\beta\tag{1}
其中 cosρ\cos\rhocosβ\cos\beta 是方向 ll 的方向餘弦。

2. 梯度

梯度:梯度是一個向量(矢量),表示某一函數在該點處的方向導數沿着該方向取得最大值,即函數在該點處沿着該方向(梯度的方向)變化最快,變化率最大(爲梯度的模)。

另一種理解:在微積分裏,對多元函數的參數求偏導,把求得的各個參數的偏導以向量的形式寫出來,就是梯度。

定義:設二元函數 z=f(x,y)z=f(x,y) 在平面區域D上具有一階連續偏導數,則對與於每一個點 P(x,y) 都可定出一個向量 {fx,fy}=fx(x,y)i+fy(x,y)j\left \lbrace \frac{\partial f}{\partial x} ,\frac{\partial f}{\partial y}\right \rbrace=f_x(x,y)\vec i + f_y(x,y)\vec j ,該函數就稱爲函數 z=f(x,y)z=f(x,y) 在點 P(x,y) 的梯度,記作 gradf(x,y)gradf(x,y)f(x,y)\nabla f(x,y) ,既有:
gradf(x,y)=f(x,y)={fx,fy}=fx(x,y)i+fy(x,y)j(2) gradf(x,y) = \nabla f(x,y)=\left \lbrace \frac{\partial f}{\partial x} ,\frac{\partial f}{\partial y}\right \rbrace=f_x(x,y)\vec i + f_y(x,y)\vec j \tag{2}

注意,梯度是在多元函數中的,如果要拓展到一元函數,則要這樣理解:它是一個標量,並且在某點的梯度等於這一點的導數。

3. 梯度下降算法數學推導

梯度下降算法針對的是最小優化問題(即求最小值問題),目的是使目標函數沿最快路徑下降到最小值。

通俗的解釋,是模擬下山,每次沿着當前位置最陡峭最易下山的方向前進一小步,然後繼續沿下一個位置最陡方向前進一小步。這樣一步一步走下去,一直走到覺得我們已經到了山腳。

1.gradient_descent_1

雖然這樣很好理解,但這只是給不懂梯度下降算法的小白講的形象比喻,最終要落實到算法上代碼上,具體的過程是怎麼樣的呢?還得靠數學推導。

算法作用於損失函數(也稱目標函數、代價函數、誤差函數),是爲了找到使損失函數取最小值的權重(ww)和偏置(bb)。

設損失函數爲 l(x,w,b,y)l(x,w,b,y) ,要尋找最優的 wwbb ,爲便於計算,抽象出函數描述 f(w,b)f(w,b) ,這裏 f=lf=l ,只是描述形式不同。同時設 θ=(w,b)T\theta = (w,b)^TTT 爲轉置符號,此時 θ\theta 是一個二維列向量。

則損失函數爲 f(θ)f(\theta),將其進行一階泰勒展開,得:
f(θ)f(θ0)+(θθ0)f(θ0)(3) f(\theta)\approx f(\theta_0)+(\theta-\theta_0)·\nabla f(\theta_0) \tag{3}

爲什麼要一階泰勒展開呢,因爲這樣可以“以直代曲”,數學術語叫“局部線性近似”,就是在很小的區間內,直線與曲線近似重合,對曲線不易做的計算,可以對直線計算作爲代替。

θθ0\theta-\theta_0 就是這個很小的區間,可以表示爲 Δθ\Delta \theta ,但它仍然是一個矢量,將其分解爲模和單位向量的形式,即長度和方向的形式:
Δθ=θθ0=ρv(4) \Delta \theta=\theta-\theta_0=\rho \vec v \tag{4}
這裏的 ρ\rho 就是前面下山比喻中每次走的一小步的距離(步長),v\vec v 就是走的方向,這裏注意ρ\rho 是距離(長度),所以 ρ\rho > 0

則損失函數 f(θ)f(\theta) 的一階泰勒展開式可描述爲:
f(θ)f(θ0)+ρvf(θ0)(5) f(\theta)\approx f(\theta_0)+\rho \vec v·\nabla f(\theta_0) \tag{5}

其中 f(θ0)f(\theta_0) 是現在的損失函數的值,f(θ)f(\theta) 是即將要更新的損失函數的值,前面說過我們的目的是爲了找到使損失函數取最小值的權重(ww)和偏置(bb),所以我們每次更新要保證 f(θ)<f(θ0)f(\theta)<f(\theta_0) ,即:
f(θ)f(θ0)ρvf(θ0)<0(6) f(\theta)-f(\theta_0)\approx \rho \vec v·\nabla f(\theta_0)<0 \tag{6}
又因爲 ρ\rho > 0,所以有:
vf(θ0)<0(7) \vec v·\nabla f(\theta_0)<0 \tag{7}
這裏注意,v\vec v 是一個向量,f(θ0)\nabla f(\theta_0) 是函數 f(w,b)f(w,b) 在點 (w0,b0)(w_0,b_0) 處的梯度,它也是一個向量。要兩個向量的乘積小於0,則需要他們的夾角大於90°。再次想到我們的目的,使目標函數沿 最快 路徑下降到最小值,既然要最快,f(θ)f(\theta) 與 $ f(\theta_0)$ 的距離(即 f(θ)f(θ0)|f(\theta)-f(\theta_0)|)就要越大越好,根據公式 f(θ)f(θ0)ρvf(θ0)<0f(\theta)-f(\theta_0)\approx \rho \vec v·\nabla f(\theta_0)<0 ,就是結果越小越好(因爲結果是負值)。再回到 v\vec vf(θ0)\nabla f(\theta_0) 兩個向量的乘積上,就是他們的夾角爲180°時,vf(θ0)\vec v·\nabla f(\theta_0) 的結果最小(這裏也是爲什麼要沿着梯度反方向更新自變量的原因),此時結果爲:
vf(θ0)=vf(θ0)=f(θ0)(8) \vec v·\nabla f(\theta_0)= -||\vec v||·||\nabla f(\theta_0)||=-||\nabla f(\theta_0)|| \tag{8}
v\vec v 描述爲:
v=f(θ0)f(θ0)(9) \vec v=-\frac{\nabla f(\theta_0)}{||\nabla f(\theta_0)||} \tag{9}
帶入 θθ0=ρv\theta-\theta_0=\rho \vec v 得:
θ=θ0ρf(θ0)f(θ0)(10) \theta=\theta_0-\rho \frac{\nabla f(\theta_0)}{||\nabla f(\theta_0)||} \tag{10}
因爲 ρ\rho1f(θ0)\frac{1}{||\nabla f(\theta_0)||} 都是標量,所以可以設 α=ρf(θ0)\alpha=\frac{\rho}{||\nabla f(\theta_0)||} ,則上式(梯度下降算法中 θ\theta 的更新表達式)可描述爲:
θ=θ0αf(θ0)=θ0αθ0f(θ)(11) \theta=\theta_0-\alpha\nabla f(\theta_0) =\theta_0-\alpha \frac{\partial}{\partial \theta_0}f(\theta) \tag{11}
這裏的 α\alpha 就是我們常說的學習速率。

另外,這個公式還有一種比較專業的描述方法:
θθ0αθ0f(θ)(12) \theta \leftarrow \theta_0-\alpha \frac{\partial}{\partial \theta_0}f(\theta) \tag{12}
這個公式就是最終往代碼裏寫的形式,只不過要結合你的 ff (損失函數)的具體形式進一步計算偏導,再落實到代碼。

實際使用該算法時,有時將公式寫成分量的形式,即將 θ=(w,b)Tθ0=(w0,b0)T\theta = (w,b)^T,\theta_0 = (w_0,b_0)^T 代入上式得:
(w,b)T=(w0,b0)Tα(fx,fy)T(13) (w,b)^T=(w_0,b_0)^T-\alpha \left ( \frac{\partial f}{\partial x} ,\frac{\partial f}{\partial y} \right )^T \tag{13}
即:
w=w0αfw0b=b0αfb0(14) \begin{aligned} w=w_0-\alpha \frac{\partial f}{\partial w_0} \\ b=b_0-\alpha \frac{\partial f}{\partial b_0} \end{aligned} \tag{14}

它們的意思是一樣的,只是表示方法不同。但分量形式不常用,多用向量形式。

以上是梯度下降算法自變量更新的數學推導,那麼什麼時候停止更新呢,你肯定會說當然是找到使損失函數取最小值的權重(ww)和偏置(bb)時,沒錯這也是我們前面算法目的中的描述。但是怎樣知道找到了損失函數的最小值呢,實際應用中不會真的一直迭代到損失函數的最小值,而是在精度和訓練時間都可接受的範圍內,儘可能的接近最小值,在資源消耗和精度要求間權衡。具體結束條件通常爲:

  1. f(θ0)||\nabla f(\theta_0)|| 的值足夠小。(也可以說是損失函數不再明顯的減小,但同時也要兼顧損失函數的值,否則就要檢查初始參數和訓練數據等),實際編程時,考慮到程序性能,不一定以直接判斷損失函數的值爲依據,也可以間接判斷(比如誤差值)。
  2. 迭代次數達到預定值。

這裏講的是梯度下降算法的核心思想,最後實際應用還要落實到具體算法,梯度下降算法家族包括批量梯度下降法(Batch Gradient Descent,BGD,也叫最速梯度下降法)、隨機梯度下降法(Stochastic Gradient Descent,SGD)、小批量梯度下降法(Mini-Batch Gradient Descent,MBGD)三種。它們的算法原理相同,只是在輸入數據時採取不同的策略。

4. 批量梯度下降法代碼實現

4.1 選擇損失函數與模型

損失函數選擇均方差損失函數(MSE),其表達式爲:
L(θ;x,y)=1Mi=1M(y^(xi,θ)yi)2(15) L(\theta;x,y) = \frac{1}{M} \sum^M_{i=1} (\hat y(x_i,\theta) - y_i)^2 \tag{15}
其中:

y^\hat y — — 預測函數,

x,yx, y — — 分別是訓練數據的輸入值與標籤值,

θ\theta — — 是 wwbb 組成的向量,

MM — — 是訓練數據個數 。

預測模型(函數)選擇線性迴歸模型,表達式爲:
y^(x,θ)=j=0nθjxj(16) \hat y(x,\theta)=\sum_{j=0}^n\theta_jx_j \tag{16}
其中:

xx — — 是預測輸入數據點,

θ\theta — — 是學習得到的權重(ww)和偏置(b)。

nn — — 是輸入數據的維數。

4.2 從公式到代碼

首先確定目的,是爲了找到使損失函數取最小值的權重(ww)和偏置(bb)。我們找到了嗎?我們找到了。他們的計算式就是公式 (12)(12) ,所以我們的代碼核心部分就是實現公式 (12)(12) ,對於這個公式,重要的部分是 θ0f(θ)\frac{\partial}{\partial \theta_0}f(\theta),損失函數對 θ\theta 的偏導數,我們的損失函數已由公式 (15)(15) 給出,即 f(θ)=L(θ;x,y)f(\theta) = L(\theta;x,y),公式 (15)(15)θ\theta 求偏導得:
θ0f(θ)=θ0(y^(x,θ0)y)2=2(y^(x,θ0)y)θ0(y^(x,θ0)y)=2(y^(x,θ0)y)θ0(j=0nθjxjy)=2(y^(x,θ0)y)x(17) \begin{aligned} \frac{\partial}{\partial \theta_0}f(\theta) &= \frac{\partial}{\partial \theta_0}(\hat y(x,\theta_0) - y)^2 \\ &= 2·(\hat y(x,\theta_0) - y)·\frac{\partial}{\partial \theta_0}(\hat y(x,\theta_0) - y)\\ &= 2·(\hat y(x,\theta_0) - y)·\frac{\partial}{\partial \theta_0}\left(\sum_{j=0}^n\theta_jx_j - y\right)\\ &= 2·(\hat y(x,\theta_0) - y)·x \tag{17} \\ \end{aligned}

將公式 (17)(17) 代入公式 (12)(12) 得:
θθ0α2(y^(x,θ0)y)x(18) \theta \leftarrow \theta_0-\alpha·2·(\hat y(x,\theta_0) - y)·x \tag{18}
其中 y^\hat y 是預測函數,θ0\theta_0 代表當前值,θ\theta 是下一次的更新值,x,yx,y 是訓練數據的輸入值與標籤值,α\alpha 是學習率,由於 22 是常數,α\alpha 是標量,可以將 22 併入 α\alpha ,則實際上的 α\alpha2ρf(θ0)\frac{2\rho}{||\nabla f(\theta_0)||} ,可見學習率可以表達梯度下降迭代步長的變化,實際應用中常常人爲賦值或使用特定策略賦值,而不是使用原公式。

至此,公式 (18)(18) 可描述爲:
θθ0α(y^(x,θ0)y)x(19) \theta \leftarrow \theta_0-\alpha·(\hat y(x,\theta_0) - y)·x \tag{19}
這是最後寫入程序的公式。

爲了編程方便,將公式分解,令:

hypothesis=y^(x,θ0)(20)hypothesis = \hat y(x,\theta_0) \tag{20}

error=y^(x,θ0)y=hypothesisy(21)error = \hat y(x,\theta_0) - y = hypothesis - y\tag{21}

gradient=(y^(x,θ0)y)x=(errorx)/m(22)gradient = (\hat y(x,\theta_0) - y)·x = (error·x)/m \tag{22}

θ=θ0αgradient(23)\theta = \theta_0-\alpha·gradient \tag{23}

關於 gradient 的計算,這裏說明一下,梯度下降算法家族的三個算法的不同之處就在這裏。

批量梯度下降法(BGD):每次迭代計算梯度,使用整個數據集(m=Mm=M),也就是每次計算 gradient 都用上所有數據點,然後求均值。

隨機梯度下降法(SGD):每次迭代計算梯度,從整個數據集中隨機選取一個數據點(m=1m=1)。

小批量梯度下降法(MBGD):每次迭代計算梯度,從整個數據集中選取一個小批量數據(1<m<M1<m<M)。

以下根據公式 (20)(21)(22)(23)(20)、(21)、(22)、(23) 編寫代碼,實現批量梯度下降法:

def batchGradientDescent(x, y, theta, alpha, m, maxInteration):
    '''批梯度下降算法簡單實現
    x: 輸入
    y: 輸出
    theta: w 和 b 組成的向量
    alpha: 學習率
    m: 批數據數量
    maxInteration:最大迭代次數
    '''
    x_train = x.transpose() # 轉置
    for i in range(0, maxInteration):
        # 預測值
        hypothesis = np.dot(x, theta)
        # 預測誤差
        error = hypothesis - y
        # 下降梯度
        gradient = np.dot(x_train, error) / m
        # 更新theta
        theta = theta - alpha * gradient
    return theta

同理,隨機梯度下降代碼爲:

def stochasticGradientDescent(x, y, theta, alpha, maxInteration):
    '''批梯度下降算法簡單實現
    x: 輸入
    y: 輸出
    theta: w 和 b 組成的向量
    alpha: 學習率
    m: 批數據數量
    maxInteration:最大迭代次數
    '''
    data = []
    for i in range(4):
        data.append(i)
    for i in range(0, maxInteration):
        hypothesis = np.dot(x, theta)
        # 預測誤差
        error = hypothesis - y
        # 選取一個隨機數
        index = random.sample(data, 1)  # 從列表data中隨機選取一個數據
        index1 = index[0]
        # 下降梯度
        gradient = error[index1] * x[index1]
        # 求導之後得到theta
        theta = theta - alpha * gradient
    return theta

小批量梯度下降:

def miniBatchGradientDescent(x, y, theta, alpha, m, batch_size, epochs):
    '''
    x: 輸入
    y: 輸出
    theta: w 和 b 組成的向量
    alpha: 學習率
    m: 數據集的數據量
    batch_size:一個批次的數據量
    epochs:數據集最大迭代次數
    '''
    for epoch in range(epochs):
        # 生成索引列表
        indexs_list = np.arange(m)
        # 按批次迭代
        for batch in range(m // batch_size):
            # 生成批次數據索引
            index_list = indexs_list[batch*batch_size : batch*batch_size+batch_size]
            # 獲取批次數據
            x_batch = x[index_list]
            y_batch = y[index_list]
            # 預測值
            hypothesis = np.dot(x_batch, theta)
            # 預測誤差
            error = hypothesis - y_batch
            # 下降梯度
            gradient = np.dot(x_batch.T, error) / m
            # 更新theta
            theta = theta - alpha * gradient
    return theta

1.5 總結及其他的一些說明

梯度下降運行步驟:

  1. 用隨機值初始化權重和偏差

  2. 把輸入傳入網絡,得到輸出值(預測值)

  3. 計算預測值和真實值(標籤值)之間的誤差

  4. 對每一個產生誤差的神經元,調整相應的(權重和偏差)值以減小誤差

  5. 重複迭代,直至得到網絡權重和偏差的最佳值

批量梯度下降法(BGD):每次迭代計算梯度,使用整個數據集。每次更新都會朝着正確的方向進行,最後能夠保證收斂於極值點,凸函數收斂於全局極值點,非凸函數可能會收斂於局部極值點,缺陷就是學習時間太長,消耗大量內存。

隨機梯度下降法(SGD):每次迭代計算梯度,從整個數據集中隨機選取一個數據,所以每次迭代的時間非常快。但收斂時震盪,不穩定,在最優解附近波動,難以判斷是否已經收斂。

小批量梯度下降法(MBGD):這個是 BGDSGD 的折中方法, BGD 每次使用整體數據,收斂太慢, SGD 每次只使用一條數據,雖然收斂快但震盪厲害,所以出現了折中的 MBGD,每次使用 n 條數據,如果 n(batch size) 選擇的合適,不僅收斂速度比SGD更快、更穩定,而且在最優解附近的震盪也不會很大,甚至得到比 BGD 更好的解。

batch size 的選擇,一般取2的冪次時能充分利用矩陣運算操作,因此可以在2的冪次中挑選最優取值。例如16、32、64、128、256等等。

另外,還有一種小批量隨機梯度下降法,即在小批量梯度下降法中,獲取批次數據時,不是按原有輸入順序選取數據,而是先把原有輸入數據打亂,再選取批次數據,代碼如下:

def mini_batch_stochastic_gradient_descent(x, y, theta, alpha, m, batch_size, epochs):
    '''
    x: 輸入
    y: 輸出
    theta: w 和 b 組成的向量
    alpha: 學習率
    m: 數據集的數據量
    batch_size:一個批次的數據量
    epochs:數據集最大迭代次數
    '''
    for epoch in range(epochs):
        # 生成索引列表
        data_index = np.arange(m)
        # 打亂樣本順序
        np.random.shuffle(data_index)
        # 按批次迭代
        for batch in range(m // batch_size):
            # 生成批次數據索引
            batch_index = data_index[batch*batch_size : batch*batch_size+batch_size]
            # 獲取批次數據
            x_batch = x[batch_index]
            y_batch = y[batch_index]
            # 預測值
            hypothesis = np.dot(x_batch, theta)
            # 預測誤差
            error = hypothesis - y_batch
            # 下降梯度
            gradient = np.dot(x_batch.T, error) / m
            # 更新theta
            theta = theta - alpha * gradient
    return theta

梯度(數學名詞)_百度百科

簡單的梯度下降算法,你真的懂了嗎?

解梯度下降法的三種形式BGD、SGD以及MBGD

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章