梯度下降法

       梯度下降法的定義就是從百科上拿下來的,梯度下降是迭代法的一種,可以用於求解最小二乘問題(線性和非線性都可以)。在求解機器學習算法的模型參數,即無約束優化問題時,梯度下降(Gradient Descent)是最常採用的方法之一,另一種常用的方法是最小二乘法。在求解損失函數的最小值時,可以通過梯度下降法來一步步的迭代求解,得到最小化的損失函數和模型參數值。反過來,如果我們需要求解損失函數的最大值,這時就需要用梯度上升法來迭代了。

       在機器學習中,基於基本的梯度下降法發展了三種梯度下降方法,分別爲隨機梯度下降法、批量梯度下降法和mini-batch隨機梯度下降法。批量梯度下降法是求得所有樣本上的風險函數最小值,隨機梯度下降法在於每次迭代的風險是針對單個樣本而不是整個樣本,所以該方法的收斂速度非常快。而mini-batch隨機梯度下降法是結合了前2者的優點,屬於是一個折中的方法,每一迭代的時候是拿數據集中一部分樣本進行訓練的。

如果一個實值函數在a點處可微並且有定義,那麼函數f(x)在a點沿着梯度相反的方法-∇f(a)下降最快。梯度下降法的迭代公式爲:


其中λ>0是梯度方向上的搜索步長,搜索步長必須合適,如果過大就不會收斂,如果過小就使得收斂點速度過慢,一般步長可以由線性搜索算法來確定。

線性迴歸的損失函數通常定義爲平方損失函數:



也可表示爲

其中f(x,a)是關於線性迴歸方程的擬合函數,i表示樣本標號,j表示樣本維度標號,對於線性迴歸的參數求解方法可以是最小二乘法了,這裏介紹梯度下降法來求解,爲了在求導的時候消除平方的影響,故原方程爲:


爲第i次迭代選取的樣本。

批量梯度下降法

(1)計算L對a_j的偏導數,得到a_j的偏導數


(2)更新每個參數a_j的負梯度方向,其中α爲步長,更新公式爲:


(3)從上式中可以看出,每迭代一次都要用到訓練集中所有的數據,如果數據集中的樣本比較多,那麼迭代的速度就會非常慢。

隨機梯度下降法

隨機梯度下降法是每次迭代的時候都會使用一個樣本,不會用到整體訓練集的樣本。更新公式爲:


mini-batch隨機梯度下降法

在mini-batch梯度下降方法中,我們從數據集m個樣本中選擇k個樣本進行迭代,1<k<m,可以根據不同的樣本數據來調整k。對應的更新公式爲:


在使用梯度下降算法時,可以通過改變步長、算法參考的初始值、歸一化特徵等操作來提高算法的效率。

梯度下降法和其他方法的比較:

(1)最小二乘法不需要選擇步長,是通過計算方程組求組,而梯度下降法需要選擇步長,並通過迭代方法求解。當樣本數量非常少時,最小二乘法具有優勢,並且計算速度快。梯度下降法適合樣本數據比較多的情況。最小二乘法可以得到最優解,而梯度下降法很有可能得到的是局部最小值。

例子:

隨機1000個點,100次的迭代。


(1)隨機梯度下降法

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from matplotlib import style
#構造數據
def get_data(sample_num=1000):
    """
    擬合函數爲y = 2*x1 + 3*x2
    """
    x1 = np.linspace(0, 9, sample_num)
    x2 = np.linspace(4, 13, sample_num)
    x = []
    for i in range(sample_num):
        tmp = []
        tmp.append(x1[i])
        tmp.append(x2[i])
        x.append(tmp)
    x = np.array(x)
    #np.concatenate(([x1], [x2]), axis=0).T
    y = []
    for i in range(sample_num):
        y.append(x1[i]*2+3*x2[i])
    y = np.array(y) 
    painter3D(x1,x2,y)
    return x, y
#隨機梯度下降法
def SGD(samples, y, step_size=0.01, max_iter_count=100):
    """
    :param samples: 樣本
    :param y: 結果value
    :param step_size: 每一接迭代的步長
    :param max_iter_count: 最大的迭代次數
    :param batch_size: 隨機選取的相對於總樣本的大小
    :return:
    """
    #確定樣本數量以及變量的個數初始化theta值
    m, var = samples.shape #100*2
    theta = np.zeros(2) #參數
    #進入循環內
    loss = 1
    iter_count = 0
    iter_list=[]
    loss_list=[]
    theta1=[]
    theta2=[]
    #當損失精度大於0.01且迭代此時小於最大迭代次數時,進行
    while loss > 0.01 and iter_count < max_iter_count:
        loss = 0
        #梯度計算
        theta1.append(theta[0])
        theta2.append(theta[1])      
        #樣本維數下標
        rand1 = np.random.randint(0,m,1)
        h = np.dot(theta,samples[rand1].T)
        #關鍵點,只需要一個樣本點來更新權值
        for i in range(len(theta)):
            theta[i] =theta[i] - step_size*(h - y[rand1])*samples[rand1,i]
        #計算總體的損失精度,等於各個樣本損失精度之和
        for i in range(m):
            h = theta[0] * samples[i][0] + theta[1] * samples[i][1] 
            #每組樣本點損失的精度
            every_loss = (1/(var*m))*np.power((h - y[i]), 2)
            loss = loss + every_loss
        print("iter_count: ", iter_count, "the loss:", loss)
        iter_list.append(iter_count)
        loss_list.append(loss)
        iter_count += 1
    plt.plot(iter_list,loss_list)
    plt.xlabel("iter")
    plt.ylabel("loss")
    plt.show()
    return theta1,theta2,theta,loss_list

def painter3D(theta1,theta2,loss):
    style.use('ggplot')
    fig = plt.figure()
    ax1 = fig.add_subplot(111, projection='3d')
    x,y,z = theta1,theta2,loss
    ax1.plot_wireframe(x,y,z, rstride=5, cstride=5)
    ax1.set_xlabel("x")
    ax1.set_ylabel("y")
    ax1.set_zlabel("z")
    plt.show()
        
if __name__ == '__main__':
    samples, y = get_data()
    theta1,theta2,theta,loss_list = SGD(samples, y)
    print(theta)  # 會很接近[2, 3]  

    painter3D(theta1,theta2,loss_list)




(2)批量梯度下降法

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from matplotlib import style

#構造數據
def get_data(sample_num=1000):
    """
    擬合函數爲y = 2*x1 + 3*x2
    """
    x1 = np.linspace(0, 9, sample_num)
    x2 = np.linspace(4, 13, sample_num)
    x = []
    for i in range(sample_num):
        tmp = []
        tmp.append(x1[i])
        tmp.append(x2[i])
        x.append(tmp)
    x = np.array(x)
    #np.concatenate(([x1], [x2]), axis=0).T
    y = []
    for i in range(sample_num):
        y.append(x1[i]*2+3*x2[i])
    y = np.array(y) 
    painter3D(x1,x2,y)
    return x, y
#批量梯度下降法
def SGD(samples, y, step_size=0.01, max_iter_count=100):
    """
    :param samples: 樣本
    :param y: 結果value
    :param step_size: 每一接迭代的步長
    :param max_iter_count: 最大的迭代次數
    :param batch_size: 隨機選取的相對於總樣本的大小
    :return:
    """
    #確定樣本數量以及變量的個數初始化theta值
    
    m, var = samples.shape #100*2
    theta = np.zeros(2) #參數
    #進入循環內
    loss = 1
    iter_count = 0
    iter_list=[]
    loss_list=[]
    theta1=[]
    theta2=[]
    #當損失精度大於0.01且迭代此時小於最大迭代次數時,進行
    while loss > 0.01 and iter_count < max_iter_count:
        loss = 0
        #梯度計算
        theta1.append(theta[0])
        theta2.append(theta[1])      
        for j in range(m):
            h = np.dot(theta,samples[j].T)
            for i in range(len(theta)):
                theta[i] =theta[i] - step_size*(1/m)*(h - y[j])*samples[j,i]
        #計算總體的損失精度,等於各個樣本損失精度之和
        for i in range(m):
            h = theta[0] * samples[i][0] + theta[1] * samples[i][1] 
            #每組樣本點損失的精度
            every_loss = (1/(var*m))*np.power((h - y[i]), 2)
            loss = loss + every_loss
        print("iter_count: ", iter_count, "the loss:", loss)
        
        iter_list.append(iter_count)
        loss_list.append(loss)
        
        iter_count += 1
    plt.plot(iter_list,loss_list)
    plt.xlabel("iter")
    plt.ylabel("loss")
    plt.show()
    return theta1,theta2,theta,loss_list


def painter3D(theta1,theta2,loss):
    style.use('ggplot')
    fig = plt.figure()
    ax1 = fig.add_subplot(111, projection='3d')
    x,y,z = theta1,theta2,loss
    ax1.plot_wireframe(x,y,z, rstride=5, cstride=5)
    ax1.set_xlabel("x")
    ax1.set_ylabel("y")
    ax1.set_zlabel("z")
    plt.show()
        
if __name__ == '__main__':
    samples, y = get_data()
    theta1,theta2,theta,loss_list = SGD(samples, y)
    print(theta)  # 會很接近[2, 3]
    

    painter3D(theta1,theta2,loss_list)




(3)mini-batch梯度下降法

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from matplotlib import style

#構造數據
def get_data(sample_num=1000):
    """
    擬合函數爲y = 2*x1 + 3*x2
    """
    x1 = np.linspace(0, 9, sample_num)
    x2 = np.linspace(4, 13, sample_num)
    x = []
    for i in range(sample_num):
        tmp = []
        tmp.append(x1[i])
        tmp.append(x2[i])
        x.append(tmp)
    x = np.array(x)
    #np.concatenate(([x1], [x2]), axis=0).T
    y = []
    for i in range(sample_num):
        y.append(x1[i]*2+3*x2[i])
    y = np.array(y) 
    painter3D(x1,x2,y)
    return x, y
#mini梯度下降法
def SGD(samples, y, step_size=0.01, max_iter_count=100):
    """
    :param samples: 樣本
    :param y: 結果value
    :param step_size: 每一接迭代的步長
    :param max_iter_count: 最大的迭代次數
    :param batch_size: 隨機選取的相對於總樣本的大小
    :return:
    """
    #確定樣本數量以及變量的個數初始化theta值
    m, var = samples.shape #100*2
    theta = np.zeros(2) #參數
    #進入循環內
    loss = 1
    iter_count = 0
    iter_list=[]
    loss_list=[]
    theta1=[]
    theta2=[]
    #當損失精度大於0.01且迭代此時小於最大迭代次數時,進行
    while loss > 0.01 and iter_count < max_iter_count:
        loss = 0
        #梯度計算
        theta1.append(theta[0])
        theta2.append(theta[1])      
        j = 0
        while j < m:
            h = np.dot(theta,samples[j].T)
            for w in range(10):
                if j < m:
                    for i in range(len(theta)):
                        theta[i] =theta[i] - step_size*(1/10)*(h - y[j])*samples[j,i]
                    j += 1
                j += 1
        #計算總體的損失精度,等於各個樣本損失精度之和
        for i in range(m):
            h = theta[0] * samples[i][0] + theta[1] * samples[i][1] 
            #每組樣本點損失的精度
            every_loss = (1/(var*m))*np.power((h - y[i]), 2)
            loss = loss + every_loss
        print("iter_count: ", iter_count, "the loss:", loss)
        iter_list.append(iter_count)
        loss_list.append(loss)
        iter_count += 1
    plt.plot(iter_list,loss_list)
    plt.xlabel("iter")
    plt.ylabel("loss")
    plt.show()
    return theta1,theta2,theta,loss_list

def painter3D(theta1,theta2,loss):
    style.use('ggplot')
    fig = plt.figure()
    ax1 = fig.add_subplot(111, projection='3d')
    x,y,z = theta1,theta2,loss
    ax1.plot_wireframe(x,y,z, rstride=5, cstride=5)
    ax1.set_xlabel("x")
    ax1.set_ylabel("y")
    ax1.set_zlabel("z")
    plt.show()
        
if __name__ == '__main__':
    samples, y = get_data()
    theta1,theta2,theta,loss_list = SGD(samples, y)
    print(theta)  # 會很接近[2, 3]

    painter3D(theta1,theta2,loss_list)




總結

通過上述3個方法可以看出,mini-batch方法具有很好的效果,可以達到1.92和3.09的一個真實值。而在批量梯度方法中可以得到最優解,在隨機梯度方法中出現了震盪的情況,這是由於步長的問題導致了在最小值進行徘徊,所以,針對不同的梯度下降方法要進行步長的設定。

改進方向:動量、Nesterov 動量

參考文獻

深度學習

https://segmentfault.com/a/1190000011994447

https://blog.csdn.net/lilyth_lilyth/article/details/8973972

https://www.cnblogs.com/pinard/p/5970503.html

https://blog.csdn.net/m2284089331/article/details/76397658

https://blog.csdn.net/pengjian444/article/details/71075544

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