計算
對於線性迴歸,梯度下降法的目標就是找到一個足夠好的向量,使代價函數取得最小值。線性迴歸的代價函數是關於的多元函數。如下:
對代價函數的每一個分量求偏導數,
這裏將J()除以m可以縮小梯度的值:
此時梯度爲:
- 此處也可以除以2m,因爲可以方便求導數,不過,對結果沒有什麼影響,可以隨意
- 這裏的分爲m個部分,因爲有m個點。計算梯度的時候需要把m個點的座標代進去關於的表達式。這是梯度的標準求法。這個公式,是完整的梯度公式。即,在每個方向求導數。得到的梯度向量就是下降最快的方向。
- 隨機梯度下降法就是簡化了這個,不需要再把m個點都代入進去,而是隨機地抽取一個點,所以計算速度大爲提高。
- 小批量梯度下降法就是中和了這兩個算法,每次取batch個點代進去,計算梯度。BGD的梯度最後除以了m,這裏的MBGD也可以除以batch。其實除和不除,都能計算出來,因爲除的這個batch,在迭代的時候,會和相乘,只不過會對的取值有所影響。
以下是求梯度的函數,很簡單,但是我總是記不住,這裏作詳細的介紹:
def dJ(theta, X_b, y):
res = np.empty(len(theta))
res[0] = np.sum(X_b.dot(theta) - y)
for i in range(1, len(theta)):
res[i] = (X_b.dot(theta) - y).dot(X_b[:, i])
return res * 2 / len(X_b)
首先上面矩陣中的每個元素其實也需要通過矩陣運算得出
上面的式子可以用用兩個向量的內積表示爲,此處是內積,不是矩陣乘法:
上面的式子又等價於:
最終可以表示爲,是python中的切片的寫法:
因此,除了第一行以外,每一行的偏導數都可以表示爲 ,上面的代碼中的循環,就是對除了第一行以外的所有行,每一行都進行一次計算。這裏dot()
是點乘,向量點乘之後就自動求和了。所以不需要再次求和。
for i in range(1, len(theta)):
res[i] = (X_b.dot(theta) - y).dot(X_b[:, i])
所以其實是兩種情況:
第一行
之後的所有行
實現
思路:先準備數據集X和y ==> 使用交叉驗證選出訓練樣本 ==> 迴歸
迴歸的過程: 將轉換爲X_b(就是加上X0) ==> 設置初始化 ==> 進行梯度下降(計算各個方向的偏導數-> 循環計算theta = theta - eta * dj
)
class LinearRegression2:
def __init__(self):
self.coef_ = None # 表示參數,theta_[1:]
self.intercept_ = None # 表示截距 ==>theta[0]
self._thera = None # 表示完整的theta==> theta[:]
def fit_gd(self, X_train, y_train, eta=0.01, n_iters=1e4):
"""使用梯度下降法尋找最小的代價函數"""
# 格式化X和theta,加上x0 和 theta0
X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
initial_theta = np.zeros(X_b.shape[1])
# 調用循環的梯度下降
self._thera = self.gradient_descent(X_b, y_train, initial_theta, eta=eta, n_iters=n_iters)
self.intercept_ = self._thera[0]
self.coef_ = self._thera[1:]
return self
def gradient_descent(self, X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8):
theta = initial_theta
i = 0
while i < n_iters:
i += 1
lastTheta = theta # 記錄上一個參數向量
dj = self.dj(theta, X_b, y)
theta = theta - eta * dj
if np.absolute(self.J(lastTheta, X_b, y) - self.J(theta, X_b, y)) < epsilon:
break
return theta
def dj(self, theta, X_b, y):
"""計算代價函數的偏導數"""
# res 是 長度爲len的一維數組
res = np.zeros((len(theta))) ##### zeros(5) == zeros((5,)) != zeros((5,1))
res[0] = np.sum(X_b.dot(theta) - y) # 需要將向量求和。沒有辦法,這裏只能手動分開求解。因爲這裏X是從列方向分割,沒有X0
for i in range(1, len(theta)):
res[i] = (X_b.dot(theta) - y).dot(X_b[:, i])
return res * 2 / len(X_b)
def J(self, theta, X_b, y):
"""計算代價函數"""
return np.sum((y - X_b.dot(theta)) ** 2) / len(y)
調用效果如下:
np.random.seed(666)
x = 2 * np.random.random(size=100)
y = x * 3. + 4. + np.random.normal(size=100)
X = x.reshape(-1, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_ratio=0.2, seed=666)
line = LinearRegression2()
line.fit_gd(X_train, y_train)
###
4.085675667692203
[ 2.97732994]
使用向量實現
博客沒法顯示全公式。。就用圖片湊合吧.是列向量
即,最後的公式爲:
使用python的numpy實現:
def dJ(theta, X_b, y):
return X_b.T.dot(X_b.dot(theta) - y) * 2. / len(y)
其中,是在原始數據的每一列前面加了一列後的矩陣,y是訓練數據中給定的。
小結
- 不管是用向量計算還是用手動計算。都需要先把訓練數據轉換爲
- 一般來說,都是ndarray類型的二維數組,使用
hstack([ist1, list2])
函數可以將兩個數組攤在一起。將轉換爲的代碼爲:X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
- 大體思路就是:
- 調用
fit()
方法擬合,該方法中產生,並初始化。最後使調用梯度下降方法gradient_descent()
來找到最優的 gradient_descent()
循環調用dj()
計算對的偏導數,並每一次都對的值進行更新。gradient_descent()
中會調用J()
來判斷梯度的增量是否已經足夠小。
- 調用
數據標準化
在這裏使用波士頓房價數據測試,發現一些很有意思的東西,如下。爲什麼會這樣?,因爲數據沒有歸一化,在數據集中存在一下特別大和特別小的數字,導致在計算梯度時,可能會導致梯度的跨度太大,而無法收斂。也有可能時計算式出現了過大的數inf。
linear.fit_gd(X, y, n_iters=1e4, eta=0.001)
print(linear.coef_)
###
[nan nan nan nan nan nan nan nan nan nan nan nan nan]
只要將學習係數設定的足夠小,就不會報錯。
linear.fit_gd(X, y, n_iters=1e4, eta=0.0000001)
使用sklearn的StandardScaler
歸一化數據。注意歸一化需要將所有的X都歸一化,包括用來訓練的x_train和用來測試的x_test
from sklearn.preprocessing import StandardScaler
standardScaler = StandardScaler()
standardScaler.fit(X) # 導入數據
X_standard = standardScaler.transform(X) # 轉換數據
所以,到目前爲止,要預測一個波士頓房價數據的完整過程是這樣的:
from sklearn import datasets
# 加載數據集
X = datasets.load_boston().data
y = datasets.load_boston().target
# 剔除噪音
X = X[y < 50]
y = y[y < 50]
# 數據歸一化處理
from sklearn.preprocessing import StandardScaler
standardScaler = StandardScaler()
standardScaler.fit(X)
X_standard = standardScaler.transform(X)
# 交叉驗證
X_train_standard, X_test_standard, y_train, y_test = train_test_split(X_standard, y, test_ratio=0.2, seed=666)
# 迴歸,擬合
linear = LinearRegression2()
linear.fit_gd(X_train_standard, y_train, n_iters=3e5)
# 使用score計算擬合的效果
print(linear.score(X_test_standard, y_test))
隨機梯度下降法
原本的梯度下降法:
隨機梯度下降法:
原本的計算偏導數的函數用這個替代。裏只有一個樣本哦,所有的誤差和X都是一個同一個樣本的。而原來的函數,每計算一次偏導數都要對n個特徵,每個循環m次,共mn次計算。計算量確實是大大提高,但是,不能保證每次都找到最好的梯度。使用隨機產生的梯度。相比較原來的函數,去掉了步長。
學習率:
- 去掉了參數中的步長
- n_iters 表示遍歷所有樣本的輪數
- 學習率需要不斷縮小,因爲,算出來的梯度不能保證是越來越小的(因爲隨機)
class StochasticDescent:
"""隨機梯度下降法"""
def __init__(self):
self.coef_ = None # 表示參數,theta_[1:]
self.intercept_ = None # 表示截距 ==>theta[0]
self._thera = None # 表示完整的theta==> theta[:]
def fit_SGD(self, X_train, y_train, eta=0.01, n_iters=1):
"""使用梯度下降法尋找最小的代價函數"""
# 格式化X和theta,加上x0 和 theta0
X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
initial_theta = np.zeros(X_b.shape[1])
# 調用循環的梯度下降
self._thera = self.sgd(X_b, y_train, initial_theta, n_iters, 5, 50)
self.intercept_ = self._thera[0]
self.coef_ = self._thera[1:]
return self
def sgd(self, X_b, y_train, initial_theta, n_iters, t0=5, t1=50):
"""隨機梯度下降, niters是輪數"""
def get_study_rate(i_iters):
"""把學習率和迭代次數聯繫起來"""
return t0 / (t1 + i_iters)
def dJ_sgd(X_b_i, theta, y):
"""計算隨機一個元素的梯度"""
return 2 * X_b_i.T.dot(X_b_i.dot(theta) - y)
m = len(X_b)
theta = initial_theta
for n in range(int(n_iters)):
indexes = np.random.permutation(m)
X_b_new = X_b[indexes, :]
y_new = y[indexes]
for i in range(m):
sgd = dJ_sgd(X_b_new[i], theta, y_new[i])
theta = theta - get_study_rate(m * n + i) * sgd
return theta
sklearn 中的隨機梯度下降
SGDRegressor
位於線性模型下,SGDRegressor(max_iter=50)
可以傳入參數 max_iter
指定迭代的次數。
from sklearn.linear_model import SGDRegressor
sgd_reg = SGDRegressor(max_iter=50)
sgd_reg.fit(X, y)
調試梯度下降法
對每個分量,都要微積分,最後,用算出來的微積分向量,作爲梯度。
- 計算量巨大,調試成功後,要關閉
- 分子的兩個順序不能錯,我把順序搞錯了,結果導數算反了。。。
關鍵代碼在這:
def dj_debug(self, X_b, theta, y, epsilon=0.001):
res = np.zeros((len(theta), ))
for i in range(len(theta)):
theta_1 = theta.copy()
theta_2 = theta.copy()
theta_1[i] += epsilon
theta_2[i] -= epsilon
res[i] = (self.J(theta_1, X_b, y) - self.J(theta_2, X_b, y)) / (2 * epsilon)
return res
完整代碼:
class aDebugGradient:
"""隨機梯度下降法"""
def __init__(self):
self.coef_ = None # 表示參數,theta_[1:]
self.intercept_ = None # 表示截距 ==>theta[0]
self._thera = None # 表示完整的theta==> theta[:]
def fit_debug(self, X_train, y_train, eta=0.01, n_iters=1e4):
"""使用梯度下降法尋找最小的代價函數"""
# 格式化X和theta,加上x0 和 theta0
X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
initial_theta = np.zeros(X_b.shape[1])
# 調用循環的梯度下降
self._thera = self.gradient_Descent(self.dj_debug, X_b, y_train, initial_theta, eta, n_iters)
self.intercept_ = self._thera[0]
self.coef_ = self._thera[1:]
return self
def J(self, theta, X_b, y):
return np.sum((y - X_b.dot(theta)) ** 2) / len(y)
def dj_debug(self, X_b, theta, y, epsilon=0.001):
res = np.zeros((len(theta), ))
for i in range(len(theta)):
theta_1 = theta.copy()
theta_2 = theta.copy()
theta_1[i] += epsilon
theta_2[i] -= epsilon
res[i] = (self.J(theta_1, X_b, y) - self.J(theta_2, X_b, y)) / (2 * epsilon)
return res
def dJ_math(self, X_b, theta, y):
return X_b.T.dot(X_b.dot(theta) - y) * 2. / len(y)
def gradient_Descent(self, dj, X_b, y_train, initial_theta, eta, n_iters, epsilon=1e-8):
"""梯度下降"""
theta = initial_theta
for i in range(int(n_iters)):
gradient = dj(X_b, theta, y_train) # 計算得到梯度
last_theta = theta
theta = theta - eta * gradient
if np.absolute(self.J(theta, X_b, y_train) - self.J(last_theta, X_b, y_train)) < epsilon:
break
return theta
最後,,,這垃圾公式真是煩死人!!!