回顧LR和梯度下降法, 結合MSE來, 代碼案例說明.
同步進行一波網上代碼搬磚, 先來個入門的線性回歸模型訓練, 基于梯度下降法來, 優化用 MSE 來做. 理論部分就不講了, 網上一大堆, 我自己也是了解好多年了, 什麼 偏導數, 梯度(多遠函數一階偏導數組成的向量) , 方向導數, 反方向(梯度下降) 這些基本的高數知識, 假設大家是非常清楚原理的.
如不清楚原理, 那就沒有辦法了, 隻能自己補, 畢竟 ML 這塊, 如果不清楚其數學原理, 隻會有架構和導包, 那得是多門的無聊和無趣呀. 這裡是搬運代碼的, 當然, 我肯定是有改動的,基于我的經驗, 做個小筆記, 友善自己後面遇到時, 直接抄呀.
01 采樣資料
這裡呢, 假設已知一個線性模型, 就假設已經基本訓練好了一個, 比如是這樣的.
\(y=1.477x + 0.089\)
現在為了更好模拟真實樣本的觀測誤差, 給模型添加一個誤差變量 \(\epsilon\)
\(y =1.477x + 0.089 + \epsilon, \epsilon-N(0, 0.01^2)\)
現在來随機采樣 100次, 得到 n=100 的樣本訓練資料集
import numpy as np
def data_sample(times=100):
"""資料集采樣times次, 傳回一個二維數組"""
for i in range(times):
# 随機采樣輸入 x, 一個數值 (均勻分布)
x = np.random.uniform(-10, 10)
# 采樣高斯噪聲(随機誤差),正态分布
epsilon = np.random.normal(0, 0.01)
# 得到模型輸出
y = 1.447 * x + 0.089 + epsilon
# 用生成器來生成或存儲樣本點
yield [x, y]
# test
# 将資料轉為 np.array 的二維數組
data = np.array(list(data_sample()))
data 是這樣的, 2D數組, 一共100行記錄, 每一行表示一個樣本點 (x, y).
array([[ 5.25161007, 7.6922458 ],
[ 9.00034456, 13.11119931],
[ 9.47485633, 13.80426132],
[ -4.3644416 , -6.2183884 ],
[ -3.35345323, -4.76625711],
[ -5.10494006, -7.30976062],
.....
[ -6.78834597, -9.73362456]]
02 計算誤差 MSE
計算每個點 (xi, yi) 處的預測值 與 真實值 之差的平方 并 累加, 進而得到整個訓練集上的均方誤差損失值.
# y = w * x + b
def get_mse(w, b, points):
"""計算訓練集的 MSE"""
# points 是一個二維數組, 每行表示一個樣本
# 每個樣本, 第一個數是 x, 第二個數是 y
loss = 0
for i in range(0,len(X)):
x = points[i, 0]
y = points[i, 1]
# 計算每個點的誤差平方, 并進行累加
loss += (y - (w * x + b)) ** 2
# 用 總損失 / 總樣本數 = 均方誤差 mse
return loss / len(points)
樣本是一個二維數組, 或者矩陣. 每一行, 表示一個樣本, 每一清單示該樣本的某個子特征
03 計算梯度
關于梯度, 即多元函數的偏導數向量, 這個方向是, 多元函數的最大導數方向 (變化率最大) 方向 (向量方向), 于是, 反方向, 則是函數變化率最小, 即極值點的地方呀, 就咱需要的, 是以稱為, 梯度下降法嘛, 從數學上就非常好了解哦.
def step_gradient(b_current, w_current, points, lr):
# 計算誤差函數在所有點的導數, 并更新 w, b
b_gradient = 0
w_gradinet = 0
n = len(points) # 樣本數
for i in range(n):
# x, y 都是一個數值
x = points[i, 0]
y = points[i, 1]
# 損失函數對 b 的導數 g_b = 2/n * (wx+b-y) 數學推導的
b_gradient += (n/2) * ((w_current * x + b) - y)
# 損失函數對 w 的導數 g_w = 2/n (wx+b-y) x
w_gradinet += (n/2) * x * ((w_current * x + b) - y)
# 根據梯度下降法, 更新 w, b
new_w = w_current - (lr * b_gradient)
new_b = b_current - (lr * b_gradient)
return [new_w, new_b]
04 更新梯度 Epoch
根據第三步, 在算出誤差函數在 w, b 的梯度後, 就可以通過 梯度下降法來更新 w,b 的值. 我們把對資料集的所有樣本訓練一次稱為一個 Epoch, 共循環疊代 num_iterations 個 Epoch.
def gradient_descent(points, w, b, lr, max_iter):
"""梯度下降 Epoch"""
for step in range(max_iter):
# 計算梯度并更新一次
w, b = step_gradient(b, w, np.array(points),lr)
# 計算目前的 均方差 mse
loss = get_mes(w, b, points)
if step % 50 == 0:
# 每隔50次列印一次目前資訊
print(f"iteration: {step} loss: {loss}, w:{w}, b:{b}")
# 傳回最後一次的 w,b
return [w, b]
05 主函數
def main():
# 加載訓練資料, 即通過真實模型添加高斯噪聲得到的
lr = 0.01 # 學習率
init_b = 0
init_w = 0
max_iter = 500 # 最大Epoch=100次
# 用梯度下降法進行訓練
w, b = gradient_descent(data, init_w, init_b, lr, max_iter)
# 計算出最優的均方差 loss
loss = get_mse(w, b, dataa)
print(f"Final loss: {loss}, w:{w}, b:{b}")
# 運作主函數
main()
iteration: 0 loss: 52624.8637745707, w:-37.451784525811654, b:-37.451784525811654
iteration: 50 loss: 8.751081967754209e+134, w:-5.0141110054193505e+66, b:-5.0141110054193505e+66
iteration: 100 loss: 1.7286223665339186e+265, w:-7.047143783692584e+131, b:-7.047143783692584e+131
iteration: 150 loss: inf, w:-9.904494626138306e+196, b:-9.904494626138306e+196
iteration: 200 loss: inf, w:-1.3920393397706614e+262, b:-1.3920393397706614e+262
iteration: 250 loss: nan, w:nan, b:nan
iteration: 300 loss: nan, w:nan, b:nan
iteration: 350 loss: nan, w:nan, b:nan
iteration: 400 loss: nan, w:nan, b:nan
iteration: 450 loss: nan, w:nan, b:nan
************************************************************
Final loss: nan, w:nan, b:nan
可以看到, 在 Epoch 100多次, 後, 就已經收斂了. 當然正常來說, 應該給 loss 設定一個門檻值的, 不然後面都 inf 了, 我還在 epoch, 這就有問題了. 這裡就不改了, 總是習慣留下一些不完美, 這樣才會記得更深. 其目的也是在與數理 ML 整個訓練過程, 用入門級的 線性回歸和 梯度下降法來整.