一、指導思想
# 只針對線性回歸中的使用
- 算法的最優模型的功能:預測新的樣本對應的值;
- 什么是最優的模型:能最大程度的擬合住數據集中的樣本數據;
- 怎么才算最大程度的擬合:讓數據集中的所有樣本點,在特征空間中距離線性模型的距離的和最小;(以線性模型為例說明)
- 怎么得到最優模型:求出最優模型對應的參數;
- 怎么求解最優模型的參數:通過數學方法,得到目標函數(此函數計算數據集中的所有樣本點,在特征空間中到該線性模型的距離,也就是損失函數),通過批量梯度下降法和隨機梯度下降法對目標函數進行優化,得到目標函數最小值時對應的參數;
- 梯度下降法的目的:求解最優模型對應的參數;(並不是為了求目標函數的最小值,這一點有助於理解隨機梯度下降法)
二、批量梯度下降法基礎
1)批量梯度下降法的特點
- 運算量大:批量梯度下降法中的每一項計算:
,要計算所有樣本(共 m 個);
- 批量梯度下降法的梯度是損失函數減小最快的方向,也就是說,對應相同的 theta 變化量,損失函數在梯度方向上的變化量最大;
2)批量梯度下降法的思路
- 思路:計算損失函數的梯度,按梯度的方向,逐步減小損失函數的變量 theta,對應的損失函數也不斷減小,直到損失函數的的變化量滿足精度要求;
- 梯度計算:變形公式如下
- 梯度是優化的方向,損失函數的變量 theta 的變化量 = 學習率 X 當前梯度值
三、隨機梯度下降法(Batch Gradient Descent)
1)基礎理解
- 思路:隨機抽取 n (一般 n = 總樣本數 / 3)個樣本,在每個樣本的梯度方向上逐步優化(每隨機抽取一個樣本就對 theta 做一次遞減優化)變量 theta;
- 分析:批量梯度下降法的優化,是整體數據集的梯度方向逐步循環遞減變量 theta ,隨機梯度下降法,是數據集中的一個隨機的樣本的梯度方向,優化變量 theta;
- 特點一:直接優化變量 theta,而不需要計算 theta 對應的目標函數值;
- 特點二:不是按整體數據集的梯度方向優化,而是按隨機抽取的某個樣本的梯度方向進行優化;
2)優化方向的公式
- 新的搜索方向計算公式(也即是優化的方向):
;
- 此處稱為搜索方向,而不是梯度的計算公式,因為此公式已經不是梯度公式,而表示優化損失函數的方向;
- 隨機梯度下降法的搜索路徑:
- 特點:
- 每一次搜索的方向,不能保證是損失函數減小的方向;
- 每一次搜索的方向,不能保證是損失函數減小最快的方向;
- 其優化方向具有不可預知性;
- 意義:
- 實驗結論表明,即使隨機梯度下降法的優化方向具有不可預知性,通過此方法依然可以差不多來到損失函數最小值的附近,雖然不像批量梯度下降法那樣,一定可以來到損失函數最小值位置,但是,如果樣本數量很大時,有時可以用一定的模型精度,換取優化模型所用的時間;
- 實現技巧:確定學習率(η:eta)的取值,很重要;
- 原因:在隨機梯度下降法優化損失函數的過程中,如果 η 一直取固定值,可能會出現,已經優化到損失函數最小值位置了,但由於隨機的過程不夠好,η 又是各固定值,導致優化時慢慢的又跳出最小值位置;
- 方案:優化過程中讓 η 逐漸遞減(隨着梯度下降法循環次數的增加,η 值越來越小);
3)η 的確定過程
- 一:如果 η = 1 / i_iters;(i_iters:當前循環次數)
- 問題:隨着循環次數(i_iters)的增加,η 的變化率差別太大;
- 二:如果 η = 1 / (i_iters + b);(b:為常量)
- 解決了 η 的變化率差異過大
- 再次變形:η = a / (i_iters + b);(a、b:為常量)
- 分子改為 a ,增加 η 取值的靈活度;
- a、b:為隨機梯度下降法的超參數;
- 本次學習不對 a、b 調參,選用經驗上比較適合的值:a = 5、b = 50;
- 學習率的特點
# 學習率隨着循環次數的增加,逐漸遞減;
# 這種逐漸遞減的思想,是模擬在搜索領域的重要思路:模擬退火思想;
# 模擬退火思想:在退火過程中的冷卻函數,溫度與冷卻時間的關系;
- 一般根據模擬退火思想,學習率還可以表示:η = t0 / (i_iters + t1)
4)循環次數的確定
- 原則
- 將每個樣本都隨機抽取到;
- 將每個樣本至少抽取 n 次,也就是總的循環次數一般為:len(X_b) * n;
- 具體操作
- 將變形后的數據集 X_b 的 index 做隨機亂序處理,得到新的數據集 X_b_new ;
- 根據亂序后的 index 逐個抽取 X_b_new 中的樣本,循環 n 遍;
四、實現隨機梯度下降法
- 優化方向:
,結果是一個列向量;
# array . dot(m) == array . m
- eta 取值:η = a / (i_iters + b)
- 優化結束條件
- 批量梯度下降法:1)達到設定的循環次數;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:對所有數據集循環的遍數;
- 調用自己封裝的代碼
- 獲取原始數據
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]
- 數據分割
from ALG.data_split import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, seed=666)
- 數據歸一化
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)歸一化;
-
使用線性回歸算法: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 -
循環遍數改為 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
- 循環遍數改為 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
- 總結:隨着循環遍數的增加,模型的准確度也隨着增加;
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中的算法的實現過程更加復雜,性能更優:
-
通過計算時間就可以看出:n_iters=100時,自己的算法需要 502ms,scikit-learn中的算法需要 8ms;
- 自己所學的封裝好的算法,只是為了幫助理解算法的原來,而scikit-learn中使用了很多優化的方案