梯度下降法


在機器學習中,我們通常會根據輸入 \(x\) 來預測輸出 \(y\),預測值和真實值之間會有一定的誤差,我們在訓練的過程中會使用優化器(optimizer)來最小化這個誤差,梯度下降法(Gradient Descent)就是一種常用的優化器。

什么是梯度

梯度是一個向量,具有大小和方向。想象我們在爬山,從我所在的位置出發可以從很多方向上山,而最陡的那個方向就是梯度方向。
對函數 \(f(x_1, x_2, ..., x_n)\) 來講,對於函數上的每一個點 \(P(x_1,x_2,...,x_n)\),我們都可以定義一個向量 \(\{\frac{\partial{f}}{\partial{x_1}}, \frac{\partial{f}}{\partial{x_2}}, ...,\frac{\partial{f}}{\partial{x_n}} \}\),這個向量被稱為函數 \(f\) 在點 \(P\)梯度(gradient),記為 \(\nabla{f(x_1, x_2, ...,x_n)}\) 。函數\(f\)\(P\)點沿着梯度方向最陡,也就是變化速率最快。比如對於二元函數 \(f(x, y)\)來講,我們先將函數的偏導數寫成一個向量 \(\{\frac{\partial{f}}{\partial x},\frac{\partial{f}}{\partial y}\}\),則在點 \((x_0, y_0)\)處的梯度為 \(\{\frac{\partial{f}}{\partial{x_0}},\frac{\partial{f}}{\partial{y_0}}\}\)
梯度方向是函數上升最快的方向,沿着梯度方向可以最快地找到函數的最大值,而我們要求誤差的最小值,所以在梯度下降中我們要沿着梯度相反的方向。

梯度下降步驟

假設我們要求函數 \(f(x_1, x_2)\)的最小值,起始點為 \(x^{(1)} = (x_1^{(1)}, x_2^{(1)})\),則在 \(x^{(1)}\) 點處的梯度為 \(\nabla(f(x^{(1)})) = (\frac{\partial{f}}{\partial{x_1^{(1)}}},\frac{\partial{f}}{\partial{x_2^{(1)}}})\),我們可以進行第一次梯度下降來更新x:

\[x^{(2)} = x^{(1)} - \alpha* \nabla{f(x^{(1)})} \]

其中,\(\alpha\) 被稱為步長。這樣我們就得到了下一個點\(x^{(2)}\), 重復上面的步驟,直到函數收斂,此時可認為函數取得了最小值。在實際應用中,我們可以設置一個精度 \(\epsilon\), 當函數在某一點的梯度的模小於 \(\epsilon\) 時,就可以終止迭代。

一個例子

使用梯度下降求函數 \(f(x, y) = x^2+y^2\) 的最小值。
首先求得函數的梯度:

def get_gradient(x, y):
    return 2*x, 2*y

然后迭代:

def gradient_descent():
    x, y = 5, 5   #起始位置
    alpha = 0.01
    epsilon = 0.3
    grad = get_gradient(x, y)
    while x**2+y**2 > epsilon**2:
        x -= alpha*grad[0]     # 沿梯度方向下降
        y -= alpha*grad[1]
    
    print("({},{})取值為{}".format(x, y, x**2+y**2) )

最后的結果:

(0.20000000000000104,0.20000000000000104)取值為0.08000000000000083

真實最小值在(0,0)點取得,最小值為0,兩者非常接近(上面的epsilon設置的比較大,當epsilon很小時,最后的結果會非常接近0)。

梯度下降分類

以線性回歸為例,假設訓練集為 \(T=\{(x_1, y_1), (x_2, y_2),..., (x_N,y_N)\}\),其中\(x_i∈\mathbb{R}^n\),是一個向量,\(y_i∈\mathbb{R}\)。我們通過學習得到了一個模型 \(f_M(x,w) = \Sigma_{j=0}^M w_ix^i\),可以根據輸入值 \(x\) 來預測 \(y\) ,預測值和真實值之間會有一定的誤差,我們用均方誤差(Mean Squared Error, MSE)來表示:

\[L(w) = \frac{1}{2N}\Sigma_{i=1}^N(f_M(x_i,w)-y)^2 \]

\(L(w)\)被稱為損失函數(loss function),加 1/2 的目的是為了計算方便, \(w\)是一個參數向量。
根據梯度下降時使用數據量的不同,梯度下降可以分為3類:批量梯度下降(Batch Gradient Descent,BGD)隨機梯度下降(Stochastic Gradient Descent, SGD)小批量梯度下降(Mini-Batch Gradient Descent, MBGD)

批量梯度下降(SGD)

批量梯度下降每次都使用訓練集中的所有樣本來更新參數,也就是

\[L(w) = \frac{1}{2N}\Sigma_{i=1}^N(f_M(x_i,w)-y)^2 \]

更新方法為

\[w^{(k+1)} = w^{(k)} - \alpha*\frac{\partial{L(w)}}{\partial{w}} \]

當樣本數據集很大時,批量梯度下降的速度就會非常慢。
優點:可以得到全局最優解
缺點:訓練時間長

隨機梯度下降(SGD)

每次梯度下降過程都使用全部的樣本數據可能會造成訓練過慢,隨機梯度下降(SGD)每次只從樣本中選擇1組數據進行梯度下降,這樣經過足夠多的迭代次數,SGD也可以發揮作用,但過程會非常雜亂。“隨機”的含義是每次從全部數據中中隨機抽取一個樣本。這樣損失函數就變為:

\[L(w) = \frac{1}{2}(f_M(x,w)-y)^2 \]

參數更新方法同上:

\[w^{(k+1)} = w^{(k)} - \alpha*\frac{\partial{L(w)}}{\partial{w}} \]

優點:訓練速度快
缺點:准確度下降,得到的可能只是局部最優解

小批量梯度下降(MBGD)

小批量梯度下降是 BGD 和 SGD 之間的折中,MBGD 通常包含 10-1000 個隨機選擇的樣本。MBGD降低了了SGD訓練過程的雜亂程度,同時也保證了速度。

在線性回歸中使用梯度下降

這一部分將介紹一個使用梯度下降來進行線性回歸的例子。
數據如下:

import numpy as np
import matplotlib.pyplot as plt
% matplotlib inline

N = 10 # 數據量
x = np.random.uniform(0, 5, N).reshape(N,1)
y = 2*x + np.random.uniform(0,2,N).reshape(N,1)
plt.scatter(x, y)

我們在(0,5)之間隨機生成了10組數據,如下:

我們將對這十組數據進行線性回歸。
從圖像上我們可以看到 x 和 y 滿足線性關系,所以我們將模型定義為 \(f(x, w) = wx = w_1x+w_2\),然后使用均方誤差來定義損失函數:

\[L(w) = \frac{1}{2N}\Sigma_{i=1}^N(f(x_i,w)-y)^2 \]

對應代碼如下:

def loss_function(omega, x, y):
    diff = np.dot(x, omega) - y
    loss = 1/(2*N)*(np.dot(np.transpose(diff), diff))
    return loss

因為要計算\(f(x, w) = wx = w_1x+w_2\),為了表示方便,我們將 \(x = (x_1, x_2, ..., x_N)^T\)擴充為 \(x=((x_1,x_2,...,x_n), (1,1,...,1))^T\),對應下面的代碼:

ones = np.ones((N, 1))
x = np.hstack((x, ones))

\(w\)求導可得:

\[\frac{\partial{L(w)}}{\partial{w_1}} = \frac{1}{N}\Sigma_i^N(f(x_i, w) - y_i)x_i \]

\[\frac{\partial{L(w)}}{\partial{w_2}} = \frac{1}{N}\Sigma_i^N(f(x_i, w) - y_i) \]

寫成向量的形式:

\[\frac{\partial{L(w)}}{\partial{w}} = x^T(f(x, w) - y) \]

對應下面的代碼:

def loss_gradient(omega, x, y):
    diff = np.dot(x, omega) - y
    gradient = (1./N)*(np.dot(np.transpose(x), diff))
    return gradient

由於數據量比較少,這里使用批量梯度下降的方法(BGD),代碼如下:

def BGD():
    alpha = 0.01
    omega = np.array([1, 1]).reshape(2, 1) #omega初值
    gradient = loss_gradient(omega, x, y)
    epsilon = 1e-3
    while np.linalg.norm(gradient) > epsilon:
        omega = omega - alpha * gradient
        gradient = loss_gradient(omega, x, y)
    return omega

測試代碼:

result = BGD()
print("result={}".format(result))

x1 = np.linspace(0, 5, 10)
y1 = result[0]*x1 + result[1]

plt.scatter(x[:,0], y)
plt.plot(x1, y1)

結果:

result=[[2.15366003]
 [0.69151409]]


可以看到,我們使用梯度下降成功用一條直線擬合了這些數據。
完整代碼如下:

import numpy as np
import matplotlib.pyplot as plt
% matplotlib inline

N = 10 # 數據量
x = np.random.uniform(0, 5, N).reshape(N,1)
y = 2*x + np.random.uniform(0,2,N).reshape(N,1)
plt.scatter(x, y)
ones = np.ones((N, 1))
x = np.hstack((x, ones))

def loss_function(omega, x, y):
    diff = np.dot(x, omega) - y
    loss = (1./2*N)*(np.dot(np.transpose(diff), diff))
    return loss

def loss_gradient(omega, x, y):
    diff = np.dot(x, omega) - y
    gradient = (1./N)*(np.dot(np.transpose(x), diff))
    return gradient

def BGD():
    alpha = 0.01
    omega = np.array([1, 1]).reshape(2, 1) #omega初值
    gradient = loss_gradient(omega, x, y)
    epsilon = 1e-3
    while np.linalg.norm(gradient) > epsilon:
        omega = omega - alpha * gradient
        gradient = loss_gradient(omega, x, y)
    return omega

result = BGD()
print("result={}".format(result))

x1 = np.linspace(0, 5, 10)
y1 = result[0]*x1 + result[1]

plt.scatter(x[:,0], y)
plt.plot(x1, y1)

總結

梯度下降法是一種常用的優化器,梯度可以理解為多元函數偏導數組成的向量(一元函數就是導數),沿着梯度方向函數增加最快,在梯度下降中要沿着梯度相反的方向。根據訓練周期使用的數據量的不同,梯度下降可以分為批量梯度下降(BGD)、隨機梯度下降(SGD)和小批量梯度下降(MBGD)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM