R筆記 – (18) Subsets & Shrinkage Regression (Stepwise & Lasso)

skydome20

2018/03/03

返回主目錄

Co-authors: Jeff Hung


本篇目錄

  1. 簡言
  2. Subsets Method
  3. Shrinkage Method
  4. 總結
  5. Reference
  6. R and packages version

0. 簡言

線性迴歸有許多變形(variants),主要是因為實務上的情況往往不會跟理論假設一樣,因此若單純使用線性迴歸,可能會產生許多問題。

例如,以模型的表現來說,常常都會遇到overfitting的問題,這時候就必須要試圖降低「模型的複雜度」。

又或者,當資料中具有「共線性(collinearity)」的問題時,在估計迴歸係數時就會發生問題,其係數的正負值就不可信。【這裡必須要扯到線性迴歸的估計數學式:簡言之,在估計係數時,會使用到資料的反矩陣,又在計算反矩陣的過程中,會有行列式(Determinant)值的計算,而此行列式值是放在分母的位置。當有共線性的問題時,表示資料間彼此有高相關,所計算的行列式值會趨近於 0。這時,若再把趨於 0 的值放分母,那估出來的係數就容易出錯啦!也就是說,原本參數有不偏統計量的特性(unbiased estimation),但參數變異數將得很大。而這個問題可以藉由變異數膨脹係數(VIF)檢查,或利用後續參數挑選的方法來解決】

為了解決上述的建模問題,Subsets 跟 Shrinkage 的方法便孕育而生:

方法 代表模型
子集法(subsets selection) Best Subsets, Stepwise Regression
收縮法(shrinkage / regularization) Lasso, Ridge

其中,由於Stepwise 跟 Lasso 具有迴歸本身統計特性,如今也被廣泛應用於「挑選重要變數」的議題上。(Lasso: least absolute shrinkage and selection operator)


1. Subsets Regression

Stepwise 跟 Best Subsets 討論

Stepwise 的中文叫「逐步回歸法」,往往都跟 Best Subsets Regression 一起拿出來討論。

這兩者的核心概念很簡單,都是想說能不能用比較簡單的模型,就能達成跟原模型差不多的表現與效果。

舉個例子,假設現在資料中有 x1 ~ x5 五個變數,那線性迴歸就能寫成:

y = a + b1 * x1 + b2 * x2 + b3 * x3 + b4 * x4 + b5 * x5

這時候,如果是 Best Subsets 的手法,就會開始列出所有的排列組合:

  1. 模型只有一個變數(共5個模型):
    y = a + b1 * x1y = a + b2 * x2 …以此類推
  2. 模型只有兩個變數(共10個模型):
    y = a + b1 * x1 + b2 * x2y = a + b1 * x1 + b3 * x3 …以此類推
  3. 模型只有三個變數(共10個模型)

  4. 模型只有四個變數(共5個模型)

  5. 模型只有五個變數(共1個模型 = 原模型)

此時就會有31個模型,然後根據 AIC 或 BIC指標,選取一個表現最佳的模型!但可以想見的,這樣的做法是會耗費大量的時間, n 個變數就會需要建 2^n -1 個模型,效率上面很不讓人喜愛。

所以 Stepwise Regression 改善了這種情況:只需要建構「一個模型」,然後在上面直接新增(或減少)變數。一般有兩種方法:向前選取法(Forward)跟向後選取法(Backward):

  • Forward Stepwise:在一個空的迴歸中,逐一添加變數,直到任何一個變數的額外貢獻度(AIC值)已經沒有統計意義了,那就停止。(p >> n 可以使用)

  • Backward Stepwise:在一個完整的迴歸中,逐一移除變數,直到移除任何一個變數時,模型都會損失過多的解釋力,那就停止。(只有 n > p 才可以使用)

  • Both:以上兩種方法的結合, 同時考量新增/移除變數對模型的影響,缺點是運算效率會比較慢。

要注意的是,Forward 在新增變數後就不會再取出,並以現狀為基準,來衡量後續添加變數的貢獻,因此有時候會因為添加順序而產生問題(例如,一開始先選 x1,那接下來就會選 x2;可是如果先選 x2,卻不保證接下來一定會選 x1)。Backward 跟 Both 也同理。

有關於Best Subsets 跟 Stepwise Regression 的比較與優劣,可以參考這篇文章,裡面有較完善的討論: Which Is Better, Stepwise Regression or Best Subsets Regression?


R Code for Stepwise Regression

在R裡面,要建立 Stepwise Regression,會使用step()的函式。(由於已在R的內建package stats中,故不用再額外匯入。)

這裡拿套件lasso2中的前列腺癌症資料,示範後續的 Forward 和 Backward 的程式碼。(關於資料欄位的詳細描述,可以參考官方文件)

要注意的是,依變數會是lpsa(Log Prostate Specific Antigen),其中 PSA 叫做「攝護腺特異抗原」,代表血漿內的攝護腺特異抗原濃度,當其上升時,是提示攝護腺癌的敏感監測指標之一(但不能作為確診指標)。而其他的變數則作為自變數。

data(Prostate, package="lasso2")
str(Prostate)
## 'data.frame':    97 obs. of  9 variables:
##  $ lcavol : num  -0.58 -0.994 -0.511 -1.204 0.751 ...
##  $ lweight: num  2.77 3.32 2.69 3.28 3.43 ...
##  $ age    : num  50 58 74 58 62 50 64 58 47 63 ...
##  $ lbph   : num  -1.39 -1.39 -1.39 -1.39 -1.39 ...
##  $ svi    : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ lcp    : num  -1.39 -1.39 -1.39 -1.39 -1.39 ...
##  $ gleason: num  6 6 7 6 6 6 6 6 6 6 ...
##  $ pgg45  : num  0 0 20 0 0 0 0 0 0 0 ...
##  $ lpsa   : num  -0.431 -0.163 -0.163 -0.163 0.372 ...
# 先把資料區分成 train=0.8, test=0.2 
set.seed(22)
train.index <- sample(x=1:nrow(Prostate), size=ceiling(0.8*nrow(Prostate) ))

train = Prostate[train.index, ]
test = Prostate[-train.index, ]

Forward Stepwise Regression

使用Forward Stepwise Regression ,步驟有兩個:

  1. 先建立一個空的線性迴歸(只有截距項)

  2. step(),一個一個把變數丟進去,看哪個變數貢獻最多!(衡量AIC)

值得注意的是,其中有一個 scope 參數,是用來描述模型的完整度(如果不設定,預設是會進行 backward):

# 1.建立空的線性迴歸(只有截距項)
null = lm(lpsa ~ 1, data = train)  
full = lm(lpsa ~ ., data = train) # 建立上界,也就是完整的線性迴歸

# 2.使用step(),一個一個把變數丟進去
forward.lm = step(null, 
                  # 從空模型開始,一個一個丟變數,
                  # 最大不會超過完整的線性迴歸
                  # (一定要加上界 upper=full,不可以不加) 
                  scope=list(lower=null, upper=full), 
                  direction="forward")
## Start:  AIC=20.65
## lpsa ~ 1
## 
##           Df Sum of Sq    RSS     AIC
## + lcavol   1    52.638 46.429 -36.465
## + svi      1    33.464 65.603  -9.501
## + lcp      1    33.419 65.648  -9.448
## + lweight  1    15.051 84.016   9.795
## + gleason  1    14.805 84.262  10.023
## + pgg45    1    14.077 84.991  10.695
## <none>                 99.067  20.649
## + lbph     1     2.172 96.895  20.920
## + age      1     1.290 97.777  21.626
## 
## Step:  AIC=-36.46
## lpsa ~ lcavol
## 
##           Df Sum of Sq    RSS     AIC
## + lweight  1    6.6621 39.767 -46.546
## + svi      1    4.7677 41.661 -42.916
## + lbph     1    2.6805 43.749 -39.103
## + gleason  1    1.3424 45.087 -36.753
## + lcp      1    1.2402 45.189 -36.577
## <none>                 46.429 -36.465
## + pgg45    1    1.1378 45.291 -36.400
## + age      1    0.0002 46.429 -34.465
## 
## Step:  AIC=-46.55
## lpsa ~ lcavol + lweight
## 
##           Df Sum of Sq    RSS     AIC
## + svi      1    5.1450 34.622 -55.353
## + gleason  1    2.1190 37.648 -48.817
## + lcp      1    1.7044 38.063 -47.963
## + pgg45    1    1.3691 38.398 -47.279
## <none>                 39.767 -46.546
## + age      1    0.7036 39.063 -45.939
## + lbph     1    0.2523 39.515 -45.043
## 
## Step:  AIC=-55.35
## lpsa ~ lcavol + lweight + svi
## 
##           Df Sum of Sq    RSS     AIC
## + gleason  1   1.61903 33.003 -57.088
## <none>                 34.622 -55.353
## + lbph     1   0.68717 33.935 -54.917
## + pgg45    1   0.49814 34.124 -54.483
## + age      1   0.31402 34.308 -54.064
## + lcp      1   0.02117 34.601 -53.401
## 
## Step:  AIC=-57.09
## lpsa ~ lcavol + lweight + svi + gleason
## 
##         Df Sum of Sq    RSS     AIC
## <none>               33.003 -57.088
## + age    1   0.62897 32.374 -56.589
## + lbph   1   0.44736 32.556 -56.153
## + pgg45  1   0.07885 32.924 -55.275
## + lcp    1   0.07516 32.928 -55.266

訓練過程中,我們可以觀察到是怎麼每一次增加變數到模型內的。

這裡是用 AIC 指標(暫不贅述,只要知道它是用來評選模型的指標就好,跟 BIC一樣是越小越好) 衡量每個變數的貢獻度。

一開始空的模型,其 AIC = 20.65,如果增加lcavol變數,AIC 會變化最多,成為 -36.465。

再來拿 AIC = -36.46 的模型,如果增加lweight的話, AIC 會變成 -46.546,改變幅度最大。

以此類推……直到發現如果新增變數時,反而會使 AIC 上升,那就停止。

因此上面訓練過程的結果可以彙整成下表:

原模型 原AIC 增加變數 改變後的 AIC
lpsa ~ 1 20.65 lcavol -36.465
lpsa ~ lcavol -36.46 lweight -46.546
lpsa ~ lcavol + lweight -46.55 svi -55.353
lpsa ~ lcavol + lweight + svi -55.35 gleason -57.088
lpsa ~ lcavol + lweight + svi + gleason -57.09 -57.088
summary(forward.lm)
## 
## Call:
## lm(formula = lpsa ~ lcavol + lweight + svi + gleason, data = train)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.74192 -0.42170  0.02164  0.45456  1.49081 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -1.99756    0.95111  -2.100   0.0392 *  
## lcavol       0.46495    0.08294   5.606 3.48e-07 ***
## lweight      0.60667    0.14693   4.129 9.57e-05 ***
## svi          0.72366    0.22577   3.205   0.0020 ** 
## gleason      0.21629    0.11429   1.892   0.0624 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.6724 on 73 degrees of freedom
## Multiple R-squared:  0.6669, Adjusted R-squared:  0.6486 
## F-statistic: 36.53 on 4 and 73 DF,  p-value: < 2.2e-16

最終的模型如上結果, Adj R-squared = 0.6486。

此外,forward 的概念是用「挑的」,所以保留於模型中的這四個變數,可以說是「被挑出來的重要變數」。

若要進一步進行推論,則得先留意其 p-value,看哪幾個比較顯著(lcavol + lweight + svi),再由係數判斷對攝護腺特異抗原濃度的影響:


Backward Stepwise Regression

同理,使用Backward Stepwise Regression ,步驟也有兩個:

  1. 先建立一個完整的線性迴歸

  2. step(),一個一個把變數移除,看移除哪個變數後 AIC 下降最多!

# 1. 先建立一個完整的線性迴歸
full = lm(lpsa ~ ., data = train)  

# 2. 用`step()`,一個一個把變數移除,看移除哪個變數後 AIC 下降最多!
backward.lm = step(full, 
                   # 這裡可以加下界(lower=null),也可以不加
                   scope = list(upper=full), 
                   direction="backward")  
## Start:  AIC=-52.86
## lpsa ~ lcavol + lweight + age + lbph + svi + lcp + gleason + 
##     pgg45
## 
##           Df Sum of Sq    RSS     AIC
## - pgg45    1    0.0083 31.453 -54.840
## - lcp      1    0.0760 31.521 -54.672
## - lbph     1    0.8099 32.255 -52.877
## <none>                 31.445 -52.861
## - age      1    0.9993 32.444 -52.421
## - gleason  1    1.1411 32.586 -52.080
## - svi      1    3.9258 35.371 -45.684
## - lweight  1    5.2059 36.651 -42.911
## - lcavol   1   12.8637 44.309 -28.111
## 
## Step:  AIC=-54.84
## lpsa ~ lcavol + lweight + age + lbph + svi + lcp + gleason
## 
##           Df Sum of Sq    RSS     AIC
## - lcp      1    0.0987 31.552 -56.596
## - lbph     1    0.8157 32.269 -54.843
## <none>                 31.453 -54.840
## - age      1    1.0369 32.490 -54.310
## - gleason  1    1.7900 33.243 -52.523
## - svi      1    3.9179 35.371 -47.683
## - lweight  1    5.1976 36.651 -44.911
## - lcavol   1   13.1575 44.611 -29.581
## 
## Step:  AIC=-56.6
## lpsa ~ lcavol + lweight + age + lbph + svi + gleason
## 
##           Df Sum of Sq    RSS     AIC
## <none>                 31.552 -56.596
## - lbph     1    0.8222 32.374 -56.589
## - age      1    1.0038 32.556 -56.153
## - gleason  1    1.7071 33.259 -54.486
## - svi      1    4.4770 36.029 -48.246
## - lweight  1    5.2044 36.756 -46.687
## - lcavol   1   15.1702 46.722 -27.974

這裡的解析跟 forward 一樣,可以將訓練過程彙整成下表:

原模型 原AIC 移除變數 改變後的 AIC
lpsa ~ lcavol + lweight + age + lbph + svi + lcp + gleason + pgg45 -52.86 pgg45 -54.840
lpsa ~ lcavol + lweight + age + lbph + svi + lcp + gleason -54.84 lcp -56.596
lpsa ~ lcavol + lweight + age + lbph + svi + gleason -56.6 -56.596
summary(backward.lm)
## 
## Call:
## lm(formula = lpsa ~ lcavol + lweight + age + lbph + svi + gleason, 
##     data = train)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.80514 -0.28946  0.00112  0.33592  1.46639 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -0.83322    1.14222  -0.729  0.46811    
## lcavol       0.48468    0.08295   5.843 1.43e-07 ***
## lweight      0.57611    0.16835   3.422  0.00103 ** 
## age         -0.01788    0.01190  -1.503  0.13729    
## lbph         0.08551    0.06286   1.360  0.17807    
## svi          0.72265    0.22768   3.174  0.00222 ** 
## gleason      0.22637    0.11550   1.960  0.05393 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.6666 on 71 degrees of freedom
## Multiple R-squared:  0.6815, Adjusted R-squared:  0.6546 
## F-statistic: 25.32 on 6 and 71 DF,  p-value: 7.469e-16

最終的模型如上結果, Adj R-squared = 0.6546。

由於backward 是用「剔除」,所以保留於模型中的這六個變數,可以說是「被留下來的重要變數」。

若要進一步進行推論,一樣得先留意其 p-value,看哪幾個比較顯著(lcavol + lweight + svi),再由係數判斷對攝護腺特異抗原濃度的影響:


Both Stepwise Regression

至於 Both,其概念是同時衡量「移除」跟「新增」變數時,哪個行為以及哪個變數影響 AIC 最多。

因此從 null 開始或是從 full 開始都可以,只不過兩者的結果會不一樣(可以思考看看為什麼會這樣):

# 此案例中,剛好跟 forward 結果一樣
step(null, scope = list(upper=full), direction="both")
# 此案例中,剛好跟 backward 結果一樣
step(full, scope = list(upper=full), direction="both")  

預測

在建模過程中,我們已經達成「變數挑選」的目的,看變數的 p-value 以及係數來作推論,而建完模後往往要面對的議題便是預測。

先比較一開始建立的 forward 跟 backward 模型:

模型 AIC 方法
lpsa ~ lcavol + lweight + svi + gleason -57.09 Forward
lpsa ~ lcavol + lweight + age + lbph + svi + gleason -56.6 Backward

由於step()回傳的模型形態跟lm()是一樣的,所以直接用線性迴歸(R筆記-(5))的預測方法就可以。

以下會比較三個模型(full, forward, backward)的預測效果:

# self-defined 的 R-squared 函式
R_squared <- function(actual, predict){
  mean_of_obs <- rep(mean(actual), length(actual))
  
  SS_tot <- sum((actual - mean_of_obs)^2)
  SS_reg <- sum((predict - mean_of_obs)^2)
  #SS_res <- sum((actual - predict)^2)
  R_squared <- SS_reg/SS_tot   #1 - (SS_res/SS_tot)
  R_squared
}


# 直接用 predict()來預測
lm.test = predict(full, test)
forward.test = predict(forward.lm, test)
backward.test = predict(backward.lm, test)

# 衡量 lm, forward, backward 的預測結果
c(R_squared(test$lpsa, lm.test),
  R_squared(test$lpsa, forward.test),
  R_squared(test$lpsa, backward.test)
)
## [1] 0.5495385 0.6073483 0.5488452

可以發現,forward 無論在 train 或 test 的效果比較好。

換句話說,在攝護腺癌的這個案例,使用較多的變數會讓線性模型的效果變差(只有線性,非線性模型則不一定),反而是保留某些重要變數來建模,更能呈現出資料的線性形態。


結果討論

以上的範例是先分 Train 跟 Test 來進行,主要是「預測」的考量。若今天是要探討「變數重要性」、或「解釋 X 跟 Y」的關係,還是會傾向將全部資料放進線性迴歸來建模。

而關於p-value,統計中 0.05 ~ 0.1稱之為marginal significance,雖然本文是拿 0.05 當作一個基準,不過事實上有某些學者認為這樣的變數還是具有一定的顯著性,因此在變數挑選的過程中,還是要根據實務狀況來訂下一個基準。

當我們比較 forward 跟 backward 的模型時,lcavol + lweight + svi這三個變數,基本上可以說是真正「統計上的重要變數」。(之所以不把gleason納入考量,是因為其 p-value 並不顯著。)

然而,即使gleason並不顯著,又或是 backward 比 forward 多增加 age + lbph 這兩個變數,也無法降低 AIC、無法使預測效果變好,那是否就可以說,這幾個變數不重要呢?

答案是不一定。

需知道,在判斷「重要變數」時,往往也得跟實務上的「領域知識」做結合。

使用 Stepwise Regression 挑選出來的重要變數,其實只是在「統計上」具有意義,並不代表「實際上」是有意義的,只能說有這個可能性而已。

所謂的資料,其實都只是「樣本」,並非「母體」。

因此,當今天判斷出某個變數並不顯著(重要)時,有可能只是因為我們所蒐集的資料不足,所以無法在統計上顯現出這些變數的重要性。

又或者,資料本身並不適合用線性模型來模擬,反而用非線性的模型比較好(e.g., 決策樹、SVM)。

有很多因素都會影響最後資料分析提供的結果,因此需要多方比較,並佐以實務面上的知識,才能做出最後定論。


2. Shrinkage Method

Lasso 跟 Ridge Regression 討論

另一種迴歸的變形是引入正則化 regularization (i.e., shrinkage)的技巧,將迴歸的權重和給予限制,藉此「限制模型的複雜度」,解決 overfitting的問題。其數學公式跟幾何意義可以用下圖表示:

以最佳化的概念來看:

  • L1 term 代表權重的絕對值和給限制,故會形成菱形的可行解域,也就是 Lasso。

  • L2 term 代表權重的平方和給限制,故會形成圓形的可行解域,也就是 Ridge。

其中,因為 L1 term 可行解域的關係,使得訓練過程中會使變數產生稀疏性(Sparsity),故常被用來進行變數挑選。此外,由於稀疏性的議題在大數據(Big Data)中相當重要,牽扯到運算效率跟記憶體的使用,因此許多科技大廠紛紛提出以此為基準的模型,結合梯度下降法(SGD),應用於他們的廣告實務上(i.e., Online Machine Learning Algorithms):

此外,由於 L1 跟 L2 term 是對權重給予限制,因此此手法並不只侷限於線性迴歸,在類神經網路(i.e.,深度學習)的建模,也時常將此概念(regularization)引入,避免 overfitting。

在此不詳述詳細的 Lasso 跟 Ridge 的數學意義,若有興趣可以閱讀這本DS聖經 The Elements of Statistical Learning。這裡只簡單整理兩者的優缺點:


R Code for Shrinkage Regression

要建 Lasso 跟 Ridge Regression 的模型,會需要用到glment這個套件:

require(glmnet)

跟 Stepwise 一樣,這裡拿套件lasso2中的前列腺癌症資料,示範後續的 Forward 和 Backward 的程式碼。(關於資料欄位的詳細描述,可以參考官方文件)

要注意的是,依變數會是lpsa(Log Prostate Specific Antigen),其中 PSA 叫做「攝護腺特異抗原」,代表血漿內的攝護腺特異抗原濃度,當其上升時,是提示攝護腺癌的敏感監測指標之一(但不能作為確診指標)。而其他的變數則作為自變數。

data(Prostate, package="lasso2")
str(Prostate)
## 'data.frame':    97 obs. of  9 variables:
##  $ lcavol : num  -0.58 -0.994 -0.511 -1.204 0.751 ...
##  $ lweight: num  2.77 3.32 2.69 3.28 3.43 ...
##  $ age    : num  50 58 74 58 62 50 64 58 47 63 ...
##  $ lbph   : num  -1.39 -1.39 -1.39 -1.39 -1.39 ...
##  $ svi    : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ lcp    : num  -1.39 -1.39 -1.39 -1.39 -1.39 ...
##  $ gleason: num  6 6 7 6 6 6 6 6 6 6 ...
##  $ pgg45  : num  0 0 20 0 0 0 0 0 0 0 ...
##  $ lpsa   : num  -0.431 -0.163 -0.163 -0.163 0.372 ...
# 把資料區分成 train=0.8, test=0.2 
set.seed(22)
train.index = sample(x=1:nrow(Prostate),
                     size=ceiling(0.8*nrow(Prostate)))

train = Prostate[train.index, ]
test = Prostate[-train.index, ]

這裡三個參數在建模時需要注意:

glmnet(...
       family = y是連續值,設"gaussian";若y是二元分類,設"binomial";若y是多元分類,設"multinomial"alpha = 0(Ridge) 或 1(Lasso)。
       lambda = 懲罰值,也就是給權重和的限制 (跟 SVM 中的 C 概念很像)
       )

其中的 lambda ,也就是當把 L1, L2 term 的限制式,用 Largange Multipler 放到優化的目標式時,跟隨限制式的一個常數,學術上叫懲罰值(penalty)。


係數如何收縮:Ridge 跟 Lasso 的不同

由於 L1 跟 L2形成的可行解域並不同,因此在收縮變數上面, Ridge 跟 Lasso的表現也不一樣。

我們可以用 R 做圖,X 軸為 lambda(懲罰值) , Y 軸為各變數的係數值。

可以觀察到,隨著 lambda(懲罰值)增加時, Lasso 的變數係數會陸續變為 0;但 Ridge 卻不一樣,直到某個瞬間才會全部一起變成 0。

正因為這樣的特性, 只要選取一個恰當的 lambda,便可以在 Lasso 上找出係數尚未為 0 的變數,以此來進行變數挑選。

ridge = glmnet(x = as.matrix(train[, -9]), 
               y = train[, 9], 
               alpha = 0,
               family = "gaussian")

lasso = glmnet(x = as.matrix(train[, -9]), 
               y = train[, 9], 
               alpha = 1,
               family = "gaussian")

par(mfcol = c(1, 2))
plot(lasso, xvar='lambda', main="Lasso")
plot(ridge, xvar='lambda', main="Ridge")


如何找出最佳lambda?

因此,在使用 Ridge 跟 Lasso 時,會有一個參數需要去調,那就是 lambda(懲罰值)。

不同的 lambda 會產生不同的收縮效果,所以我們可以利用 Cross Validation 的手法,驗證在不同 lambda 值下模型的表現如何,然後取殘差最小的(表現最好)模型,其所對應的 lambda 算是比較好的值

在這裡,使用的函式是cv.glmnet(),以 lasso 為例,並在下一節進一步探討 lasso 的變數挑選:

# 經由 cv 的手法,評估每個模型在不同 lambda 下 
# 的 cvm(mean cross-validated error)
cv.lasso = cv.glmnet(x = as.matrix(train[, -9]), 
                     y = train[, 9], 
                     alpha = 1,  # lasso
                     family = "gaussian")

# 評估每個模型的 cvm(mean cross-validated error)後
# 取最小 cvm 模型所對應的 lambda
best.lambda = cv.lasso$lambda.min
best.lambda
## [1] 0.02628165
# 藍色垂直虛線就是最佳 lambda 的所在位置,
# 跟其他線相交的位置就是該變數收縮後的係數
plot(lasso, xvar='lambda', main="Lasso")
abline(v=log(best.lambda), col="blue", lty=5.5 )


Lasso 的變數挑選

變數挑選的話,就要觀察在最佳 lambda 下,哪些變數的係數不為0:

# 觀察哪些變數被挑選出來,其係數不為 0的那些
coef(cv.lasso, s = "lambda.min")
## 9 x 1 sparse Matrix of class "dgCMatrix"
##                       1
## (Intercept) -0.89017110
## lcavol       0.47285952
## lweight      0.52861327
## age         -0.01023395
## lbph         0.05972457
## svi          0.68955252
## lcp          .         
## gleason      0.19098116
## pgg45        .
# 如果要取出這些重要變數的名稱,可以這樣寫:
select.ind = which(coef(cv.lasso, s = "lambda.min") != 0)
select.ind = select.ind[-1]-1 # remove `Intercept` and 平移剩下的ind
select.ind # 第幾個變數是重要的 (不看 `Intercept`)
## [1] 1 2 3 4 5 7
# 挑出重要變數的名稱
select.varialbes = colnames(train)[select.ind]
select.varialbes
## [1] "lcavol"  "lweight" "age"     "lbph"    "svi"     "gleason"
# 若要進行推論的話,得挑選出的變數重新做一次線性迴歸
# 因為 Lasso 中的係數值已經經過收縮,不能直接拿來推論
# (請將下面各項變數的係數,跟前面 Lasso的係數比較)
lm(lpsa ~ ., train[, c(select.varialbes, "lpsa")])
## 
## Call:
## lm(formula = lpsa ~ ., data = train[, c(select.varialbes, "lpsa")])
## 
## Coefficients:
## (Intercept)       lcavol      lweight          age         lbph  
##    -0.83322      0.48468      0.57611     -0.01788      0.08551  
##         svi      gleason  
##     0.72265      0.22637

預測

預測的步驟可以分成三步:

  1. 先用glmnet()建立基本的 Ridge / Lasso 模型

  2. cv.glmnet()找出最佳的懲罰值 best.lambda

  3. 使用predict()進行預測

以下用 Ridge 為例:(若要用 Lasso ,只要設定alpha = 1就好)

# 1. 先用 glmnet() 建立基本的 Ridge / Lasso 模型
ridge = glmnet(x = as.matrix(train[, -9]), 
               y = train[, 9], 
               alpha = 0, # ridge
               family = "gaussian")

# 2. 用 cv.glmnet() 找出最佳的懲罰值 best.lambda
cv.ridge = cv.glmnet(x = as.matrix(train[, -9]), 
                     y = train[, 9], 
                     alpha = 0,  # ridge
                     family = "gaussian")
best.ridge.lambda = cv.ridge$lambda.min

# 3. 使用 predict()進行預測
ridge.test = predict(ridge, 
                     s = best.ridge.lambda, 
                     newx = as.matrix(test[, -9]))

# 評估模型
R_squared(test$lpsa, ridge.test)
## [1] 0.5125524

3. 總結

線性模型中, Stepwise 跟 Lasso 是常被用來進行變數挑選的模型,其概念主要是為了避免 overfitting,因此以減少變數的方式來降低模型的複雜度。

不過兩者使用的技巧並不一樣:

  • Stepwise 是使用子集法(Subsets),將各種變數進行放入或移除,看模型的表現幅度怎麼樣。

  • Lasso(跟 Ridge)則是用最佳化的手法,對係數權重之和下限制,使得各變數隨著 lambda(懲罰值)上升時,其係數會有所收縮(shrinkage)。

另一方面,非線性的模型也能用來進行變數挑選(e.g., RF, Decision Tree, GBM)。

由於非線性模型在訓練過程中,背後並沒有統計假設,只是單純優化損失函數而已,因此這時所挑出來的變數,雖然一樣是對損失函數(Loss Function)有貢獻的,可是卻不能說有統計上的意義,此點跟線性模型的變數挑選不太一樣。

It’s still a long way to go~


5. R and packages version

這是本篇筆記使用R跟套件版本:

pkgs = c("stats", "lasso2", "glmnet")
r_version = paste("R", getRversion())
pkg_version = c()
for (package_name in pkgs) {
    pkg_version = c(pkg_version, 
                    paste(package_name,packageVersion(package_name)))
}
c(r_version, pkg_version)
## [1] "R 3.4.3"       "stats 3.4.3"   "lasso2 1.2.19" "glmnet 2.0.13"