機器學習:隨機梯度下降法(線性回歸中的應用)


一、指導思想

 # 只針對線性回歸中的使用

  • 算法的最優模型的功能:預測新的樣本對應的值;
  • 什么是最優的模型:能最大程度的擬合住數據集中的樣本數據;
  • 怎么才算最大程度的擬合:讓數據集中的所有樣本點,在特征空間中距離線性模型的距離的和最小;(以線性模型為例說明)
  • 怎么得到最優模型:求出最優模型對應的參數;
  • 怎么求解最優模型的參數:通過數學方法,得到目標函數(此函數計算數據集中的所有樣本點,在特征空間中到該線性模型的距離,也就是損失函數),通過批量梯度下降法和隨機梯度下降法對目標函數進行優化,得到目標函數最小值時對應的參數;
  • 梯度下降法的目的求解最優模型對應的參數;(並不是為了求目標函數的最小值,這一點有助於理解隨機梯度下降法)

 

二、批量梯度下降法基礎

 1)批量梯度下降法的特點

 

  1. 運算量大:批量梯度下降法中的每一項計算:,要計算所有樣本(共 m 個);
  2. 批量梯度下降法的梯度是損失函數減小最快的方向,也就是說,對應相同的 theta 變化量,損失函數在梯度方向上的變化量最大;

 

 

 2)批量梯度下降法的思路

  • 思路:計算損失函數的梯度,按梯度的方向,逐步減小損失函數的變量 theta,對應的損失函數也不斷減小,直到損失函數的的變化量滿足精度要求;
  • 梯度計算:變形公式如下

 

  • 梯度是優化的方向,損失函數的變量 theta 的變化量  =  學習率  X  當前梯度值

 

 

三、隨機梯度下降法(Batch Gradient Descent)

 1)基礎理解

  • 思路:隨機抽取 n (一般 n = 總樣本數 / 3)個樣本,在每個樣本的梯度方向上逐步優化(每隨機抽取一個樣本就對 theta 做一次遞減優化)變量 theta;
  • 分析:批量梯度下降法的優化,是整體數據集的梯度方向逐步循環遞減變量 theta ,隨機梯度下降法,是數據集中的一個隨機的樣本的梯度方向,優化變量 theta;
  • 特點一:直接優化變量 theta,而不需要計算 theta 對應的目標函數值;
  • 特點二:不是按整體數據集的梯度方向優化,而是按隨機抽取的某個樣本的梯度方向進行優化;

 

 

 2)優化方向的公式

  • 新的搜索方向計算公式(也即是優化的方向):
  • 此處稱為搜索方向,而不是梯度的計算公式,因為此公式已經不是梯度公式,而表示優化損失函數的方向;
  • 隨機梯度下降法的搜索路徑:

  • 特點
  1. 每一次搜索的方向,不能保證是損失函數減小的方向;
  2. 每一次搜索的方向,不能保證是損失函數減小最快的方向;
  3. 其優化方向具有不可預知性;

 

  • 意義
  1. 實驗結論表明,即使隨機梯度下降法的優化方向具有不可預知性,通過此方法依然可以差不多來到損失函數最小值的附近,雖然不像批量梯度下降法那樣,一定可以來到損失函數最小值位置,但是,如果樣本數量很大時,有時可以用一定的模型精度,換取優化模型所用的時間;

 

  • 實現技巧:確定學習率(η:eta)的取值,很重要;
  1. 原因:在隨機梯度下降法優化損失函數的過程中,如果 η 一直取固定值,可能會出現,已經優化到損失函數最小值位置了,但由於隨機的過程不夠好,η 又是各固定值,導致優化時慢慢的又跳出最小值位置;
  2. 方案:優化過程中讓 η 逐漸遞減(隨着梯度下降法循環次數的增加,η 值越來越小);

 

 

 3)η 的確定過程

  • :如果 η = 1 / i_iters;(i_iters:當前循環次數)
  1. 問題:隨着循環次數(i_iters)的增加,η 的變化率差別太大;
  • :如果 η = 1 / (i_iters + b);(b:為常量)
  1. 解決了 η 的變化率差異過大
  • 再次變形:η = a / (i_iters + b);(a、b:為常量)
  1. 分子改為 a ,增加 η 取值的靈活度;

  

  1. a、b:為隨機梯度下降法的超參數;
  2. 本次學習不對 a、b 調參,選用經驗上比較適合的值:a = 5、b = 50;

 

  • 學習率的特點

   # 學習率隨着循環次數的增加,逐漸遞減;

   # 這種逐漸遞減的思想,是模擬在搜索領域的重要思路:模擬退火思想

   # 模擬退火思想:在退火過程中的冷卻函數,溫度與冷卻時間的關系;

  • 一般根據模擬退火思想,學習率還可以表示:η = t0 / (i_iters + t1)

 

 4)循環次數的確定

  • 原則
  1. 將每個樣本都隨機抽取到;
  2. 將每個樣本至少抽取 n 次,也就是總的循環次數一般為:len(X_b) * n;

 

  • 具體操作
  1. 將變形后的數據集 X_b 的 index 做隨機亂序處理,得到新的數據集 X_b_new ;
  2. 根據亂序后的 index 逐個抽取 X_b_new 中的樣本,循環 n 遍;

 

 

四、實現隨機梯度下降法

  • 優化方向:,結果是一個列向量;

   # array . dot(m) == array . m

  • eta 取值:η = a / (i_iters + b)

 

  • 優化結束條件
  1. 批量梯度下降法:1)達到設定的循環次數;2)找到損失函數的最小值
  2.  隨機梯度下降法:達到設定的循環次數
  • 隨機梯度下降法中不能使用精度來結束優化:因為隨機梯度下降法的優化方向,不一定全都是損失函數減小的方向;

 

 1)代碼實現隨機梯度下降法

  • 模擬數據
    import numpy as np
    import matplotlib.pyplot as plt
    
    m = 100000
    
    x = np.random.normal(size=m)
    X = x.reshape(-1, 1)
    y = 4. * x + 3. + np.random.normal(0, 3, size=m)

     

  • 批量梯度下降法
    def J(theta, X_b, y):
        try:
            return np.sum((y - X_b.dot(theta)) ** 2) / len(y)
        except:
            return float('inf')
        
    def dJ(theta, X_b, y):
        return X_b.T.dot(X_b.dot(theta) - y) * 2. / len(y)
    
    def gradient_descent(X_b, y, initial_theta, eta, n_iters=10**4, epsilon=10**-8):
        
        theta = initial_theta
        cur_iter = 0
        
        while cur_iter < n_iters:
            gradient = dJ(theta, X_b, y)
            last_theta = theta
            theta = theta - eta * gradient
            if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon):
                break
                
            cur_iter += 1
            
        return theta

    # cur_iter:循環次數
    # initial_theta:theta的初始化值

    %%time
    X_b = np.hstack([np.ones((len(X), 1)), X])
    initial_theta = np.zeros(X_b.shape[1])
    eta = 0.01
    theta = gradient_descent(X_b, y, initial_theta, eta)
    # 輸出:Wall time: 898 ms
    theta
    # 輸出:array([3.00280663, 3.9936598 ])

     

 

  • 隨機梯度下降法
    1) 通過每一次隨機抽取的樣本,計算 theta 的優化方向def dJ_sgd(theta, X_b_i, y_i):
        return X_b_i.T.dot(X_b_i.dot(theta) - y_i) * 2

    # X_b_i:是 X_b 中的一行數據,也就是一個隨機樣本,不在是全部的數據集
    # y_i:對應的隨機抽取的樣本的真值

    2) 隨機優化過程
    def sgd(X_b, y, initial_theta, n_iters):
        
        # 計算學習率 eta
        t0 = 5
        t1 = 50
        
        # 定義求解學習率的函數
        def learning_rate(t):
            return t0 / (t + t1)
        
        theta = initial_theta
        for cur_iter in range(n_iters):
            rand_i = np.random.randint(len(X_b))
            gradient = dJ_sgd(theta, X_b[rand_i], y[rand_i])
            theta = theta - learning_rate(cur_iter) * gradient
            
        return theta

    # 此處的形參中不需要設置 eta 值了,eta 值隨着循環的進行,在函數內部求取
    # cur_iter:當前循環次數
    # rand_i:從 [0, len(X_b)) 中隨機抽取的一個數
    # gradient:一次循環中,隨機樣本的優化方向
    # learning_rate(cur_iter) * gradient:一次循環的 theta 的變化量

    3)給初始化數值,預測數據 %%time
    X_b = np.hstack([np.ones((len(X), 1)), X])
    initial_theta = np.zeros(X_b.shape[1])
    theta = sgd(X_b, y, initial_theta, n_iters=len(X_b)//3)
    # 輸出:Wall time: 287 ms
    4)查看最終優化結果
    theta
    # 輸出:array([2.9648937 , 3.94467405])

 

 

 2)封裝與調用自己的代碼

  • 封裝:已規范循環次數(代碼中的紅色字樣)
     1     def fit_sgd(self, X_train, y_train, n_iters=5, t0=5, t1=50):
     2         """根據訓練數據集X_train, y_train, 使用梯度下降法訓練Linear Regression模型"""
     3         assert X_train.shape[0] == y_train.shape[0], \
     4             "the size of X_train must be equal to the size of y_train"
     5         assert n_iters >= 1
     6 
     7         def dJ_sgd(theta, X_b_i, y_i):
     8             return X_b_i * (X_b_i.dot(theta) - y_i) * 2.
     9 
    10         def sgd(X_b, y, initial_theta, n_iters, t0=5, t1=50):
    11 
    12             def learning_rate(t):
    13                 return t0 / (t + t1)
    14 
    15             theta = initial_theta
    16             m = len(X_b)
    17 
    18             for cur_iter in range(n_iters):
    19                 indexes = np.random.permutation(m)
    20                 X_b_new = X_b[indexes]
    21                 y_new = y[indexes]
    22                 for i in range(m):
    23                     gradient = dJ_sgd(theta, X_b_new[i], y_new[i])
    24                     theta = theta - learning_rate(cur_iter * m + i) * gradient
    25 
    26             return theta
    27 
    28         X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
    29         initial_theta = np.random.randn(X_b.shape[1])
    30         self._theta = sgd(X_b, y_train, initial_theta, n_iters, t0, t1)
    31 
    32         self.intercept_ = self._theta[0]
    33         self.coef_ = self._theta[1:]
    34 
    35         return self

     # n_iters:對所有數據集循環的遍數;

 

  • 調用自己封裝的代碼
  1. 獲取原始數據
    import numpy as np
    import matplotlib.pyplot as plt
    
    from sklearn import datasets
    
    boston = datasets.load_boston()
    X = boston.data
    y = boston.target
    
    X = X[y < 50.0]
    y = y[y < 50.0]

     

  2. 數據分割
    from ALG.data_split import train_test_split
    
    X_train, X_test, y_train, y_test = train_test_split(X, y, seed=666)

     

  3. 數據歸一化
    from sklearn.preprocessing import StandardScaler
    
    standardScaler = StandardScaler()
    standardScaler.fit(X_train)
    X_train_standard = standardScaler.transform(X_train)
    X_test_standard = standardScaler.transform(X_test)

    # 數據歸一化,主要是將訓練數據集(X_train)和測試數據集(X_test)歸一化;

  4. 使用線性回歸算法:LinearRegression

    from LR.LinearRegression import LinearRegression
    
    lin_reg = LinearRegression()
    %time lin_reg.fit_sgd(X_train_standard, y_train, n_iters=2)
    lin_reg.score(X_test_standard, y_test)
    # 輸出:Wall time: 10 ms
           0.7865171620468298

    # 問題:通過score()函數得到的 R^2 值,也就是准確度過小
    # 原因:對所有的 X_train_standard 循環優化的遍數太少:n_iters=2

  5. 循環遍數改為 50:n_iters=50

    %time lin_reg.fit_sgd(X_train_standard, y_train, n_iters=50)
    lin_reg.score(X_test_standard, y_test)
    # 輸出:Wall time: 143 ms
           0.8085728716573835

     

  6. 循環遍數改為 100:n_iters = 100
    %time lin_reg.fit_sgd(X_train_standard, y_train, n_iters=100)
    lin_reg.score(X_test_standard, y_test)
    # 輸出:Wall time: 502 ms
           0.8125954368325295

     

  7. 總結隨着循環遍數的增加,模型的准確度也隨着增加;

 

 3)調用 scikit-learn 中的算法模型

  • SGDRegressor:該算法雖是名為隨機梯度下降法的回歸器,但其只能解決線性模型,因為,其被封裝在了 linear_model 線性回歸模塊中;
  • 學習scikit-learn中的算法的過程:掉包 - 構建實例對象 - 擬合 - 驗證訓練模型的效果(查看准確度)

 

  • 實現過程:前期的數據處理與步驟(2)相同
    from sklearn.linear_model import SGDRegressor
    
    sgd_reg = SGDRegressor()
    %time sgd_reg.fit(X_train_standard, y_train)
    sgd_reg.score(X_test_standard, y_test)
    # 輸出:Wall time: 16 ms
            0.8065416815240762

    # 准確度為0.8065 左右,此時循環遍數使用默認的 5 遍:max_iter = 5

  • 修改循環遍數:max_iter = 100
    sgd_reg = SGDRegressor(max_iter=100)
    %time sgd_reg.fit(X_train_standard, y_train)
    sgd_reg.score(X_test_standard, y_test)
    # 輸出:Wall time: 8 ms
            0.813372455938393

     

 

 4)總結

  • 與自己寫的算法相比,scikit-learn中的算法的實現過程更加復雜,性能更優
  1. 通過計算時間就可以看出:n_iters=100時,自己的算法需要 502ms,scikit-learn中的算法需要 8ms;

  2. 自己所學的封裝好的算法,只是為了幫助理解算法的原來,而scikit-learn中使用了很多優化的方案

 


免責聲明!

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



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