有監督學習——線性迴歸

zh_jp發表於2023-03-10

1. 線性模型

有監督學習是透過已知的樣本產生預測模型的學習方法,任何有監督學習模型都可被想象成一個函式:

\[y=f(x_1,x_2,x_3,...x_n) \tag{1-1} \]

其中,\(x_1,x_2,x_3...x_n\)是模型的n維的特徵值,\(y\)是要預測的目標值/分類,當\(y\)是可列舉的型別時,對應分類問題(classification);\(y\)為連續值時,該模型解決迴歸問題(regression)。

線性迴歸(Linear Regression)在機器學習中被用來解決學習特徵和目標值都是連續值型別的問題,可定義為多項式函式:

\[y=w_0+w_1x_1+w_2x_2+...+w_nx_n \tag{1-2} \]

線性模型學習的目標就是確定\(w_0 w_1..w_n\)的值,使\(y\)可以根據特徵值直接透過函式計算得到,\(w_0\)成為截距,\(w_1...w_n\)被成為迴歸係數(coefficient)。

公式(2)可以有其他變體,注意:是否是線性模型取決於其被要求的係數\(w_0...w_n\)之間是否為線性關係,與樣本特徵變數\(x_0..x_n\)的形式無關。e.g.以下前者為線性模型,後者為非線性模型。

\[y=2w_0+w_1x^1+w_1x^2+...w_nx^n \]

\[y=w_0+sin(w_1)x_1+cos(x_2)x_2+... \]

2. 最小二乘法

最簡單的線性模型演算法便是最小二乘法(Ordinary Least Squares, OLS),透過樣本真值與預測值之間的方差和來達到計算出\(w_1,w_2..w_n\)的目的,即:

\[argmin(\sum(\hat{y}-y)^2) \tag{1-3} \]

arg min就是使後面這個式子達到最小值時的變數的取值,其中\(\hat{y}\)是樣本預測值,\(y\)是樣本中的真值(ground truth)

在python使用

import numpy as np
from sklearn import linear_model

x = np.array([[0, 1], [3, -2], [2, 3]]) # 訓練樣本特徵
y = np.array([0.5, 0.3, 0.9])   # 訓練樣本目標值

reg = linear_model.LinearRegression()  # 最小二乘法迴歸物件
reg.fit(x, y)   # 訓練、擬合

print("intercept_:", reg.intercept_)    # 讀取截距
# intercept_: 0.36666666666666686

print("coef_:", reg.coef_)  # 讀取回歸引數
# coef_: [0.06666667 0.13333333]

reg.predict([[1, 2], [-3, 9]]) # 預測
# array([0.7       , 1.36666667])

透過intercept_coef_屬性可知道上述程式碼擬合的線性模型為:

\[y=0.36666666666666686+0.06666667x_1+0.13333333x_2 \]

最小二乘的不足

隨著特徵維度的增加,模型的求得的引數\(w_0 w_2...w_n\)的值顯著增加,OLS始終試圖最小化公式的值,因此為了擬合訓練資料中很小的\(x\)的值差異產生的較大\(y\)值差異,必須使用較大的\(w\)值,結果是任何一個微小的變化都會導致最終預測目標值大幅度變化,即過擬合。請看:

# 測試資料
def make_data(nDim):
    x0 = np.linspace(1, np.pi, 50)  # 一個維度的特徵
    x = np.vstack([[x0,], [i**x0 for i in range(2, nDim+1)]])   # nDim個維度的特徵
    y = np.sin(x0) + np.random.normal(0, 0.15, len(x0)) # 目標值
    return x.transpose(), y
x, y = make_data(12)

使用最小二乘法擬合:

def linear_regression():
    dims = [1,3,6,12]   # 需要訓練的維度
    for idx, i in enumerate(dims):
        plt.subplot(2, int(len(dims)/2), idx+1)
        reg = linear_model.LinearRegression()

        sub_x = x[:, 0:i]   # 取x中前i個維度的特徵
        reg.fit(sub_x, y)   # 訓練
        plt.plot(x[:,0], reg.predict(sub_x))    # 繪製模型
        plt.plot(x[:,0], y, '.')
        plt.title("dim %s"%i)

        print("dim %d :"%i)
        print("intercept_: %s"%(reg.intercept_))    # 檢視截距引數
        print("coef_: %s"%(reg.coef_))  # 檢視回歸引數
    plt.show()

linear_regression()
## 輸出
# dim 1 :
# intercept_: 1.5190767802159115
# coef_: [-0.39131134]
# dim 3 :
# intercept_: 1.1976858427101735
# coef_: [ 1.91353693 -1.39387114  0.16182502]
# dim 6 :
# intercept_: -221.70126917849046
# coef_: [-104.46259453  520.30421899 -573.61812163  429.72658285 -181.37364277
#    32.5448376 ]
# dim 12 :
# intercept_: -7992016.158635169
# coef_: [-1.83126880e+06  5.87461130e+07 -2.99765476e+08  9.72248992e+08
#  -1.92913886e+09  2.18748658e+09 -8.81323910e+08 -1.12973621e+09
#   2.01475457e+09 -1.40758872e+09  4.94058411e+08 -7.17485861e+07]

可以明顯看到dim=12已經過擬合了

3. 嶺迴歸

嶺迴歸(Ridge Regression 或稱Tikhonov Regularization)由俄羅斯科學家Tikhonov提出,透過改變回歸目標係數,達到了控制迴歸引數隨著維度瘋狂增長的目的。新的迴歸函式:

\[argmin(\sum(\hat{y}-y)^2+\alpha\sum w^2) \tag{2-1} \]

OLS的差別在於將\(\alpha\sum w^2\)加入最小化目標(也被稱為L2懲罰項,即L2 Penalty),其中\(\alpha\)是一個可調節的超引數,\(w\)是線性模型中的所有引數。

Ridge與原來的LinearRegression類使用方法相似,只是在初始化物件需要超引數α

def ridge_regression():
    alphas = [1e-15, 1e-12, 1e-5, 1, ]      # α引數

    for idx, i in enumerate(alphas):
        plt.subplot(2, int(len(alphas)/2), idx+1)
        reg = linear_model.Ridge(alpha=i)  # 嶺迴歸模型

        sub_x = x[:, 0:12]   # 取x中前i個維度的特徵
        reg.fit(sub_x, y)   # 訓練
        plt.plot(x[:,0], reg.predict(sub_x))    # 繪製模型
        plt.plot(x[:,0], y, '.')
        plt.title("dim=12, alpha=%e"%i)

        print("alpha %e :"%i)
        print("intercept_: %s"%(reg.intercept_))    # 檢視截距引數
        print("coef_: %s"%(reg.coef_))  # 檢視回歸引數
    plt.show()
ridge_regression()
#輸出結果
# dim 1 :
# intercept_: 1.5190767802159115
# coef_: [-0.39131134]
# dim 3 :
# intercept_: 1.1976858427101735
# coef_: [ 1.91353693 -1.39387114  0.16182502]
# dim 6 :
# intercept_: -221.70126917849046
# coef_: [-104.46259453  520.30421899 -573.61812163  429.72658285 -181.37364277
#    32.5448376 ]
# dim 12 :
# intercept_: -7992016.158635169
# coef_: [-1.83126880e+06  5.87461130e+07 -2.99765476e+08  9.72248992e+08
#  -1.92913886e+09  2.18748658e+09 -8.81323910e+08 -1.12973621e+09
#   2.01475457e+09 -1.40758872e+09  4.94058411e+08 -7.17485861e+07]

比較OLS模型的12維結果,模型引數\(w\)顯著降低。並且\(\alpha\)引數大小與訓練結果成反比:\(\alpha\)越大,迴歸引數越小,模型越平緩。

3. Lasso迴歸

Ridge已經可以解決了多特徵情況下引數太大導致的過度擬合,但在該模型中,無論\(\alpha\)多大,迴歸模型都存在絕對值極小的引數,難以到達零值,這部分引數對最終預測結果影響甚微但消耗計算資源,因此需要將不重要的特徵引數計算為零,即壓縮特徵。這便是Lasso迴歸:

\[argmin(\sum(\hat{y}-y)^2+\alpha\sum|w|) \tag{3-1} \]

對上面的程式碼略作修改

def lasso_regression():
    alphas = [1e-12, 1e-5, 1, 10]      # α引數

    for idx, i in enumerate(alphas):
        plt.subplot(2, int(len(alphas)/2), idx+1)
        reg = linear_model.Lasso(alpha=i)  # Lasso模型

        sub_x = x[:, 0:12]   # 取x中前12個維度的特徵
        reg.fit(sub_x, y)   # 訓練
        plt.plot(x[:,0], reg.predict(sub_x))    # 繪製模型
        plt.plot(x[:,0], y, '.')
        plt.title("dim=12, alpha=%e"%i)

        print("alpha %e :"%i)
        print("intercept_: %s"%(reg.intercept_))    # 檢視截距引數
        print("coef_: %s"%(reg.coef_))  # 檢視回歸引數
    plt.show()
lasso_regression()
## 輸出
# alpha 1.000000e-12 :
# intercept_: 0.6298258513945978
# coef_: [ 1.22542814e+00 -4.54947716e-01 -3.93085543e-02 -5.49725917e-03
#  -5.88010501e-04  2.47220823e-04  3.36442748e-04  2.85372733e-04
#   2.20237636e-04  1.66111569e-04  1.25225041e-04  9.51371122e-05]
# alpha 1.000000e-05 :
# intercept_: 0.6275025198172812
# coef_: [ 1.21848162e+00 -4.48693464e-01 -4.01246675e-02 -5.58492834e-03
#  -6.02833485e-04  2.41969131e-04  3.37470151e-04  2.86500656e-04
#   2.21159502e-04  1.66823390e-04  1.25767364e-04  9.55513093e-05]
# alpha 1.000000e+00 :
# intercept_: 0.9304502324995079
# coef_: [-0.00000000e+00 -0.00000000e+00 -0.00000000e+00 -0.00000000e+00
#  -0.00000000e+00 -0.00000000e+00 -0.00000000e+00 -0.00000000e+00
#  -0.00000000e+00 -6.76060251e-04 -7.30714077e-05 -0.00000000e+00]
# alpha 1.000000e+01 :
# intercept_: 0.9076753246287685
# coef_: [-0.        -0.        -0.        -0.        -0.        -0.
#  -0.        -0.        -0.        -0.        -0.        -0.0004185]

參考文獻

[1]劉長龍. 從機器學習到深度學習[M]. 1. 電子工業出版社, 2019.3.

相關文章