天天看點

LR梯度下降法MSE演練

回顧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 整個訓練過程, 用入門級的 線性回歸和 梯度下降法來整.

繼續閱讀