編譯 | VK
來源 | Towards Data Science
假設我們有一個虛擬的數據集,一對變量,一個母親和她女兒的身高:
考慮到另一位母親的身高為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 + b
y = 1.2x + b我們知道回歸線穿過了平均點,所以線上的一個點是(x值的平均值,y值的平均值),其中有(63.5,63.33)
63.33 = 1.2*63.5 + b
b = -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軸截距。重新計算新行的誤差平方和。繼續調整,直到達到局部最小值,其中平方誤差之和最小。
❝梯度下降法是一種通過多次迭代最小化誤差平方和來逼近最小平方回歸線的算法。
❞梯度下降算法在機器學習術語中,誤差平方和稱為「成本」。這個成本公式是:
其中
因此,這個方程大致是「誤差平方和」,因為它計算的是預測值減去實際值平方的總和。
1/2m是「平均」數據點數量的平方誤差,這樣數據點的數量就不會影響函數。為什麼除以2請看這個解釋(https://datascience.stackexchange.com/questions/52157/why-do-we-have-to-divide-by-2-in-the-ml-squared-error-cost-function)。
在梯度下降中,目標是使代價函數最小化。我們通過嘗試不同的斜率和截距值來實現這一點。但是應該嘗試哪些值以及如何改變這些值?
我們根據梯度下降公式改變它們的值,這個公式來自於對代價函數的偏導數。確切的數學公式可以在這個連結中找到:https://www.ritchieng.com/one-variable-linear-regression/
通過偏導數,得到:
這個公式計算每次迭代時θ的變化量。
α(α)被稱為學習率。學習率決定了每次迭代的步驟有多大。有一個好的學習率是非常重要的,因為如果它太大,你的算法不會達到最小值,如果它太小,你的算法會花很長時間才能達到。對於我的例子,我選擇alpha為0.001
總而言之,步驟如下:
這是我使用梯度下降實現簡單線性回歸的方法。
斜率和截距都是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。
在機器學習領域,調整學習率和初始估計等參數是比較常見的做法。
這就是線性回歸中梯度下降的要點。
❝梯度下降法是一種通過多次迭代最小化誤差平方和來逼近最小平方回歸線的算法。
❞到目前為止,我已經討論過簡單線性回歸,其中只有1個自變量(即一組x值)。理論上,梯度下降可以處理n個變量。
我已經重構了我以前的算法來處理下面的n個維度。
import numpy as np
class 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個變量來計算預測。