假設有一個虛擬的資料集包含多對變數,即每位母親和她女兒的身高:
透過這個資料集,我們如何預測另一位身高為63的母親的女兒的身高?
方法是用線性迴歸。
首先找到最佳擬合線,然後用這條直線做預測。
線性迴歸是尋找資料集的最佳擬合線,這條線可以用來做預測。
如何找到最佳擬合線?
這就是為什麼我們需要使用梯度下降。
梯度下降是一種找到最佳擬合線的工具。
在深入研究梯度下降之前,先看看另一種計算最佳擬合線的方法。
最佳擬合線的統計計算方法:
直線可以用公式表示:y=mx+b。
迴歸線斜率m的公式為:m = r * (SD of y / SD of x)。
轉換:x和y值之間的相關係數(r),乘以y值的標準差(SD of y)除以x值的標準偏差(SD of x)。
以上資料中母親身高的標準差約為4.07,女兒身高的標準偏差約為5.5,這兩組變數之間的相關係數約為0.89。
因此,最佳擬合線或迴歸線為:
y = 0.89*(5.5 / 4.07)x + by = 1.2x + b
我們知道迴歸線穿過了平均點,所以線上的一個點是(x值的平均值,y值的平均值),其中有(63.5,63.33)。
63.33 = 1.2*63.5 + bb = -12.87
因此,使用相關係數和標準差計算的迴歸線近似為:
y = 1.2x - 12.87
使用統計學的迴歸線為y=1.2x-12.87。
接下來讓我們研究梯度下降。
計算最佳擬合線的梯度下降法:
在使用梯度下降法時,我們從一條隨機線開始,一點一點地改變直線的引數(即斜率和y軸截距),以得到最佳擬合的直線。
那麼我們怎麼知道什麼時候到達最合適的位置呢?
對於嘗試的每一條直線——直線A、直線B、直線C等等——我們都要計算誤差的平方和。比如:如果直線B的值比直線A的誤差小,那麼直線B更適合,等等。
誤差是實際值減去預測值,最佳擬合線使所有誤差平方和最小化。線上性迴歸中,我們用相關係數計算出的最佳擬合線也恰好是最小平方誤差線。這就是迴歸線被稱為最小二乘迴歸線的原因。
最佳擬合線是最小二乘迴歸線。
在下面的影象中,直線C比直線B更適合,直線B比直線A更適合。
這就是梯度下降的工作原理:
從一條隨機線開始,比如說直線a,我們計算這條線的誤差平方和,然後調整斜率和y軸截距,重新計算新行的誤差平方和。繼續調整,直到達到區域性最小值,其中平方誤差之和最小。
梯度下降法是一種透過多次迭代最小化誤差平方和來逼近最小平方迴歸線的演算法。
梯度下降演算法在機器學習術語中,誤差平方和稱為“成本”(cost)。這個成本公式是:
因此,這個方程是“誤差平方和”,它計算的是預測值減去實際值平方的總和。
在梯度下降中,目標是使代價函式最小化。我們透過嘗試不同的斜率和截距值來實現這一點。但應該嘗試哪些值?又如何改變這些值呢?
透過偏導數,得到:
這個公式計算每次迭代時θ的變化量。
α(α)被稱為學習率,學習率決定了每次迭代的步驟有多大。好的學習率是非常重要的,因為如果它太大,演算法不會達到最小值,如果它太小,演算法會花很長時間才能達到。在本例中,我們設定alpha為0.001。
步驟如下:
估計θ;
計算成本;
調整θ;
重複2和3,直到達到最佳效果。
這是我使用梯度下降實現簡單線性迴歸的方法。
斜率和截距都是0,0。
注:在機器學習中,我們使用θ來表示向量[y-截距,斜率]。θ=y軸截距。θ1=斜率。這就是為什麼在下面的實現中將theta看作變數名。
# x = [58, 62, 60, 64, 67, 70] # 媽媽的身高# y = [60, 60, 58, 60, 70, 72] # 女兒的身高class LinearRegression: def __init__(self, x_set, y_set): self.x_set = x_set self.y_set = y_set self.alpha = 0.0001 # alpha 是學習率 def get_theta(self, theta): intercept, slope = theta intercept_gradient = 0 slope_gradient = 0 m = len(self.y_set) for i in range(0, len(self.y_set)): x_val = self.x_set[i] y_val = self.y_set[i] y_predicted = self.get_prediction(slope, intercept, x_val) intercept_gradient += (y_predicted - y_val) slope_gradient += (y_predicted - y_val) * x_val new_intercept = intercept - self.alpha * intercept_gradient new_slope = slope - self.alpha * (1/m) * slope_gradient return [new_intercept, new_slope] def get_prediction(self, slope, intercept, x_val): return slope * x_val + intercept def calc_cost(self, theta): intercept, slope = theta sum = 0 for i in range(0, len(self.y_set)): x_val = self.x_set[i] y_val = self.y_set[i] y_predicted = self.get_prediction(slope, intercept, x_val) diff_sq = (y_predicted - y_val) ** 2 sum += diff_sq cost = sum / (2*len(self.y_set)) return cost def iterate(self): num_iteration = 0 current_cost = None current_theta = [0, 0] # 初始化為0 while num_iteration < 500: if num_iteration % 10 == 0: print('current iteration: ', num_iteration) print('current cost: ', current_cost) print('current theta: ', current_theta) new_cost = self.calc_cost(current_theta) current_cost = new_cost new_theta = self.get_theta(current_theta) current_theta = new_theta num_iteration += 1 print(f'After {num_iteration}, total cost is {current_cost}. Theta is {current_theta}')
使用這個演算法和上面的母女身高資料集,經過500次迭代後得到了3.4的成本。
500次迭代後的方程為y=0.998x+0.078,實際迴歸線為y=1.2x-12.87,成本約為3.1。
用[0,0]作為[y-截距,斜率]的初始值,得到y=1.2x-12.87是不切實際的。為了在沒有大量迭代的情況下接近這個目標,必須從一個更好的初始值開始。
例如,[-10,1]在不到10次迭代後,大約得到y=1.153x-10,成本為3.1。
在機器學習領域,調整學習率和初始估計等引數是比較常見的做法。
這就是線性迴歸中梯度下降的要點。
梯度下降法是一種透過多次迭代最小化誤差平方和來逼近最小平方迴歸線的演算法。
下面重構了之前的演算法來處理n個變數。
import numpy as npclass LinearRegression: def __init__(self, dataset): self.dataset = dataset self.alpha = 0.0001 # alpha 是學習率 def get_theta(self, theta): num_params = len(self.dataset[0]) new_gradients = [0] * num_params m = len(self.dataset) for i in range(0, len(self.dataset)): predicted = self.get_prediction(theta, self.dataset[i]) actual = self.dataset[i][-1] for j in range(0, num_params): x_j = 1 if j == 0 else self.dataset[i][j - 1] new_gradients[j] += (predicted - actual) * x_j new_theta = [0] * num_params for j in range(0, num_params): new_theta[j] = theta[j] - self.alpha * (1/m) * new_gradients[j] return new_theta def get_prediction(self, theta, data_point): # 使用點乘 # y = mx + b 可以重寫為 [b m] dot [1 x] # [b m] 是引數 # 代入x的值 values = [0]*len(data_point) for i in range(0, len(values)): values[i] = 1 if i == 0 else data_point[i-1] prediction = np.dot(theta, values) return prediction def calc_cost(self, theta): sum = 0 for i in range(0, len(self.dataset)): predicted = self.get_prediction(theta, self.dataset[i]) actual = self.dataset[i][-1] diff_sq = (predicted - actual) ** 2 sum += diff_sq cost = sum / (2*len(self.dataset)) return cost def iterate(self): num_iteration = 0 current_cost = None current_theta = [0] * len(self.dataset[0]) # initialize to 0 while num_iteration < 500: if num_iteration % 10 == 0: print('current iteration: ', num_iteration) print('current cost: ', current_cost) print('current theta: ', current_theta) new_cost = self.calc_cost(current_theta) current_cost = new_cost new_theta = self.get_theta(current_theta) current_theta = new_theta num_iteration += 1 print(f'After {num_iteration}, total cost is {current_cost}. Theta is {current_theta}')
唯一調整的是不用mx+b(即斜率乘以變數x加y截距)來獲得預測值,而是進行矩陣乘法(參見上述的def get_prediction)。
使用點積,你的演算法可以接受n個變數來計算預測。