關卡 1

這個課程將跟同學介紹近代機器學習常用的一個技術。

關卡 2

在之前的課程,我們是選擇解釋變數的組合、交互作用等方式,來挑選出好的模型。

關卡 3

這堂課程介紹的作法,是一次放入所有可能的變數,然後透過修改「評分」項目,讓演算法來自動找出應該要留在模型裡的變數。這種方法,專業術語叫做:Regularization。

關卡 4

R的glmnet套件在線性模型上實作了Regularization的相關功能。這個套件的作者群,也是機器學習領域中鼎鼎大名的學者。

關卡 5

請同學先安裝glmnet套件。

check_then_install("glmnet", "2.0.3")

關卡 6

接著請載入glmnet套件

library(glmnet)

關卡 7

摸索套件的第一步:找尋vignettes。請同學輸入:vignette(package="glmnet")

vignette(package = "glmnet")

關卡 8

請同學輸入vignette("glmnet_beta",package="glmnet")打開套件的簡介文件。

vignette("glmnet_beta", package = "glmnet")

關卡 9

由於閱讀這份說明文件,需要統計學的專業知識,所以我們就先暫停在這邊。有興趣的同學可以再自行鑽研。

關卡 10

這裡我們先用iris這個資料集來介紹glmnet與regularization。請同學先輸入:m0<-lm(Sepal.Length~.,iris)這是第一個單元介紹的線性模型,透過這個指令,我們可以取得最小方差直線。我們等等會拿這個模型與glmnet的結果做比較。

m0 <- lm(Sepal.Length ~ ., iris)

關卡 11

glmnet的參數並不接受formula,而是接受x(矩陣)與y(向量)。請同學先輸入:X<-model.matrix(Sepal.Length~.,iris)建立一個矩陣好填入glmnet的x參數。model.matrix是lm使用formula時,在背後產生線性代數矩陣的函數。

X <- model.matrix(Sepal.Length ~ ., iris)

關卡 12

接著,請同學輸入:y<-iris$Sepal.Length建立一個向量好填入glmnet的y參數。

y <- iris$Sepal.Length

關卡 13

我們先輸入:m<-glmnet(X[,-1],y,lambda=0)這裡使用X[,-1]是因為glmnet會自動加入Intercept,所以我們要把Intercept從X中移除(Intercept是第一欄)。關於lambda,我們先賣個關子。

m <- glmnet(X[,-1], y, lambda = 0)

關卡 14

請同學輸入:cbind(coef(m,s=0),coef(m0))比較一下glmnet學出來的模型與lm學出來的模型。coef的s=0參數等等會補充說明。

cbind(coef(m, s = 0), coef(m0))

關卡 15

我們應該看到非常接近的數字。這是因為兩者要解的目標函數是一樣的,但是glmnet是用數值解,而lm是用理論直接計算,所以lm是比較準確的。

關卡 16

接著,我們慢慢加大lambda參數。請同學出入:m<-glmnet(X[,-1],y,lambda=seq(1,0,by=-0.1))

m <- glmnet(X[,-1], y, lambda = seq(1, 0, by = -0.1))

關卡 17

請同學看看m的輸出結果。

m

關卡 18

事實上,glmnet剛剛在背後一口氣算了時一個模型,分別對應到11個lambda(即seq(1.0,0.0,by=-0.1))

關卡 19

我們可以運用coef(m,s=0.5)來查詢當lambda=0.5時,學出來的參數。請同學試試看

coef(m, s = 0.5)

關卡 20

同學應該可以注意到,除了Petal.Length之外,其他的參數學出來都是“.”。這就是glmnet透過regularization的技術,去挑選變數。挑選的結果,在lambda=0.5之下,只有挑出Petal.Length而已,其他的變數都是0。

關卡 21

接著請同學輸入:coef(m,s=0.1)

coef(m, s = 0.1)

關卡 22

這次同學可以看到,當lambda是0.1時,glmnet挑出的變數是Sepal.Width與Petal.Length。

關卡 23

所以lambda到底是什麼呢?機器學習的原理,就是在訂定評分機制後,找到能夠最優化評分的參數。而regularization,就是在原本的評分之外,加上對參數數值的懲罰。具體作法是當參數的值越大,評分就會越低。而lambda就是控制懲罰強度的參數。

關卡 24

舉例來說,當lambda=0的時候,代表線性模型的參數值的懲罰加權為0(也就是不懲罰),所以學習出來的模型就等價於原本的線性模型。但是當lambda=1時,參數值得懲罰讓機器學習最後把大部分的參數刪除,只留下Petal.Length。這就是regularization。

關卡 25

glmnet所實作的regularization有兩種。一種是運用模型參數的絕對值的和做懲罰,這種又稱為L1-Regularization。統計上的別名是Lasso。另一種是以模型參數的平方的和作為懲罰,這種稱為L2-Regularization,統計上又稱為RidgeRegression。

關卡 26

glmnet中的參數alpha控制了L1和L2Regularization的權重。當alpha=0,就只剩下L2-Regularization;當alpha=1,就只剩下L1-Regularization。當alpha介於0、1之間,兩者並存時,統計上稱作elasticnet。

關卡 27

回到選變數的問題。當我們使用預設的alpha=1時,隨著lambda的加大或縮小,模型參數變成0的順序,常常被用來當成評估變數重要性。舉例來說,在m之中,我們可以看到Petal.Length可能是最重要的參數,接著才是Sepal.Width。

關卡 28

我們回過頭解釋m的顯示結果。請同學輸入m

m

關卡 29

m會顯示一個有三個欄位的矩陣。第一欄位(Df,DegreeofFreedom的簡稱)就代表有多少個不為0的模型參數;第二欄位(%Dev)就代表這個模型在trainingdataset上解釋了多少比率的變化;第三個欄位(Lambda)則代表lambda的值,也就是Regularization的強度(懲罰模型參數的值的強度)。

關卡 30

要注意的是,類似的選模只有在alpha>0的時候才會有效果。當alpha=0時,參數通常都不會是0。請同學試試看:glmnet(X[,-1],y,alpha=0,lambda=seq(1,0,by=-0.1))

glmnet(X[,-1], y, alpha = 0, lambda = seq(1, 0, by = -0.1))

關卡 31

透過Df,同學可以發現所有的變數都不為0。

關卡 32

類似glm,glmnet除了傳統的線性模型之外,也將類似的概念實作於廣義線性模型上。glmnet會偵測y的型態,或是指定family後,就會以對應的廣義線性模型做處理。我們直接在最後的關卡練習glmnet提供的logisticregression。

關卡 33

實務上,glmnet這套作法雖然解決選模的問題,但是帶來挑選lambda的問題。像lambda這類參數,很難透過傳統的統計方法找到好的值。所以一般都會用trainingdatasetv.s.testingdataset的概念來解決。也就是說,看看不同lambda選出來的模型,在testingdataset上的表現優劣,來挑選好的lambda。

關卡 34

最後我們請同學用上一個單元的使用過得資料集來作練習。

check_then_install("mlbench", "2.1.1")
library(mlbench)
# 方便起見,同學可以使用這個函數計算 Logarithmic Loss
logloss <- function(y, p, tol = 1e-4) {
  # tol 的用途是避免對0取log所導致的數值問題
  p[p < tol] <- tol
  p[p > 1 - tol] <- 1 - tol
  -sum(y * log(p) + (1 - y) * log(1-p))
}

data(Ionosphere)
test.i <- c(4L, 6L, 9L, 13L, 14L, 22L, 31L, 33L, 50L, 52L, 61L, 63L, 68L, 
  79L, 91L, 99L, 119L, 135L, 154L, 155L, 160L, 162L, 166L, 194L, 
  200L, 219L, 233L, 236L, 237L, 242L, 244L, 248L, 250L, 257L, 261L, 
  276L, 278L, 283L, 292L, 310L, 312L, 315L, 319L, 323L, 325L, 327L, 
  335L, 337L, 338L, 344L)
df.test <- Ionosphere[test.i,-2] # remove V2
train.i <- setdiff(seq_len(nrow(Ionosphere)), test.i)
df.train <- Ionosphere[train.i,-2]

# 上一個單元,我們請同學找到df.test上的logloss小於26的模型
# 我們想要讓同學透過glmnet找到更好的模型
# X.train 是等等要放到glmnet的x參數的值
X.train <- model.matrix(Class ~ ., df.train)[,-1]
# y.train 是等等要放到glmnet的y參數的值
y.train <- df.train$Class
# 請同學透過找到適當的lambda、alplha組合
# 利用glmnet在df.train上學出一個模型,
# 它在df.test上logloss小於8
answer_03 <- local({
  NULL
  # 你的最終物件應該要由以下的程式碼產生:
  # glmnet(x = X.train, y = y.train, lambda = ?, alpha = ?, family = "binomial")
  glmnet(x = X.train, y = y.train, lambda = 0.00514326, alpha = 1, family = "binomial")
})
stopifnot(class(answer_03) == c("lognet", "glmnet"))
stopifnot(length(answer_03$lambda) == 1) # 你的答案應該要指定lambda
if (interactive()) { # 讓自動測試略過以下程式碼
  stopifnot(logloss(
    df.test$Class == "good", 
    predict(answer_03, model.matrix(Class ~ ., df.test)[,-1], type = "response")) <
      8
    )
}

# 完成以後請存檔並回到console輸入`submit()`