เทคนิคที่เรียกว่าเป็น Golden Standard สำหรับการสร้างและทดสอบ Machine Learning Model คือ “K-Fold Cross Validation” หรือเรียกสั้นๆว่า k-fold cv เป็นหนึ่งในเทคนิคการทำ Resampling
ไอเดียของ k-fold cv คือการแบ่งข้อมูลเป็น k ส่วนเท่าๆกันเพื่อสร้างและทดสอบโมเดล (train + validate) คำนวณค่าเฉลี่ย accuracy หรือ error (i.e. model performance) ก่อนที่จะนำโมเดลไปใช้ทำนายข้อมูล test set รูปด้านล่างแสดงการแบ่งข้อมูลเป็น 5 folds เท่าๆกัน โดยการแบ่งข้อมูลต้องเป็นไปอย่าง random
ทำไมต้อง random? เพราะ randomness จะช่วยให้ข้อมูลในแต่ละ fold มีการกระจายตัวใกล้เคียงกัน ช่วยลด bias เวลาที่เราสร้างและทดสอบโมเดล จำนวน k ที่นิยมใช้กันในทางปฏิบัติมีสองค่าคือ k=5 หรือ k=10

พอแบ่งข้อมูลเสร็จแล้ว (k=5) เราจะสร้างและทดสอบโมเดลจนกว่าข้อมูลทุก fold จะถูกนำมาใช้ ถ้า k=5 เราต้องเทรนโมเดลทั้งหมด 5 รอบด้วย {train folds} และทดสอบโมเดลทั้งหมด 5 รอบด้วย {validation fold}
ในแต่ละ iteration (รอบ) เราต้องบันทึกค่า validation error ไว้ด้วยเพื่อนำไปสรุปผลหลังจบกระบวนการ cross validation ทั้งหมด ตัวอย่างตารางด้านล่างเราสามารถคำนวณค่าเฉลี่ย validation error ได้เท่ากับ (.20 + .25 + .22 + .20 + .18)/ 5 = 0.21 และนี่คือที่มาของคำว่า “Cross Validation (Error)” มีเหตุผล!
Iteration | Train Folds | Validation Fold | Validation Error |
---|---|---|---|
1 | {1, 2, 3, 4} | 5 | .20 |
2 | {1, 2, 3, 5} | 4 | .25 |
3 | {1, 2, 4, 5} | 3 | .22 |
4 | {1, 3, 4, 5} | 2 | .20 |
5 | {2, 3, 4, 5} | 1 | .18 |
บทความนี้แอดจะสอนเขียน k-fold cross validation แบบ programmatically ด้วยภาษา R ความรู้พื้นฐานสำหรับ tutorial นี้คือ data structures (list), function และ control flow (for loop)
- Load Dataset
- Create Fold ID
- Look at Data in Each Fold
- Build a Simple Model
- Full R Code
Load Dataset
ข้อมูล Boston Housing ที่แอดใช้ในบทความนี้อยู่ใน package mlbench โหลดข้อมูลเข้าสู่ RStudio ด้วยฟังชั่น data() และเปลี่ยนข้อมูลเป็น tibble เพื่อให้แสดงผลใน console ได้ดีขึ้นด้วยฟังชั่น as.tbl()
## load libraries
library(caret)
library(mlbench)
library(dplyr)
## load dataset
data("BostonHousing")
BostonHousing <- as.tbl(BostonHousing)
Create Fold ID
วิธีการ split ข้อมูลเป็น k folds เท่าๆกันใน R ทำได้ง่ายๆด้วยฟังชั่น createFolds() ของ caret ตั้งแต่เขียน R มา แอดคิดว่าฟังชั่นนี้ใช้ง่ายสุดแล้ว ปกติแอดใช้ฟังชั่น train() และ trainControl() ของ caret เพื่อสร้างและทดสอบโมเดล ML ลองอ่านบทความ caret ของเราได้ที่นี่
เราสามารถเปลี่ยนค่า k argument ในฟังชั่น createFolds() เพื่อกำหนดจำนวน folds ถ้าอยากให้ผลลัพธ์ reproducible ต้องเขียนฟังชั่น set.seed() ก่อนที่เราจะแบ่ง folds
## create folds
folds <- createFolds(BostonHousing$medv, k=5, list=TRUE)

อธิบาย – ตัวเลขในแต่ละ fold คือ row index ของข้อมูล BostonHousing ตัวเลข [9, 26, 27, 42, 56 … 475, 487, 491, 496, 500] เอาไว้ใช้ subset เพื่อสร้างข้อมูล Fold1 ตัวเลข row index ในแต่ละ fold จะไม่ซ้ำกันเลย
Look at Data in Each Fold
Output ที่ได้จากฟังชั่น createFolds() ของ caret จะอยู่ในรูปแบบของ list เราสามารถใช้ฟังชั่น lapply() เพื่อ loop through list เพื่อสร้าง dataframe ของแต่ละ fold และสร้าง object ใหม่ชื่อว่า dataFolds
ใน R เราเขียน [[ ]] เพื่อ subset list ออกมาดูข้อมูล ถ้าอยากได้ข้อมูลใน fold1 แค่ใส่เลข 1 (index) ไปใน [[ ]] หรือถ้าอยากได้ข้อมูลใน fold5 แค่เปลี่ยนเลขหนึ่งเป็นเลขห้าเสร็จเลย ทำไมง่ายอย่างนี้ 555+
## look at data in each fold
dataFolds <- lapply(folds, FUN = function(x) BostonHousing[x, ])
## data in fold1
dataFolds[[1]]

Build A Simple Model
มาลองเทรน linear regression ง่ายๆกับข้อมูลทั้ง 5-fold หาค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานของค่า R-Squared ที่ได้จากการเทรนโมเดลทั้ง 5 รอบ (i.e. cross validation error/ accuracy)

## create linear regression k-fold function
kfoldLM <- function(data, k) {
folds <- createFolds(BostonHousing$medv, k=k, list=T)
result <- vector()
for(fold in folds) {
trainData <- data[-fold, ]
testData <- data[fold, ]
r2 <- summary(lm(trainData))$r.squared
result <- append(result, r2)
}
cat("Average R2:", round(mean(result),4) )
cat("\nStandard Deviation R2:", round(sd(result),4) )
}
Note – ปกติเรานิยมใช้ k-fold cv กับโมเดลที่ต้องมีการจูนค่า parameters เช่น decision tree (cp, max depth, split rule), random forest (mtry, ntree), knn (k, distance) เป็นต้น
Full R Code
A learning curve is essential to growth.
Tammy Bjelland
โค้ด R แบบเต็มๆสำหรับทำ cross validation ด้วยฟังชั่น kfoldLM() สำหรับเทรน linear regression ติดตรงไหน comment สอบถามแอดได้ในบทความนี้ได้เลย 😛
## R version 3.6.1 | |
## Created by DataRockie 15 November 2019 | |
## load library | |
library(caret) | |
library(mlbench) | |
library(dplyr) | |
## load dataset | |
data("BostonHousing") | |
## tibble dataframe | |
BostonHousing <- as.tbl(BostonHousing) | |
## create folds | |
(folds <- createFolds(BostonHousing$medv, k=5, list=T)) | |
## train linear regression | |
kfoldLM <- function(data, k) { | |
folds <- createFolds(BostonHousing$medv, k=k, list=T) | |
result <- vector() | |
for(fold in folds) { | |
trainData <- data[-fold, ] | |
testData <- data[fold, ] | |
r2 <- summary(lm(trainData))$r.squared | |
result <- append(result, r2) | |
} | |
cat("Average R2:", round(mean(result),4) ) | |
cat("\nStandard Deviation R2:", round(sd(result),4) ) | |
} | |
## test function with k=5 | |
kfoldLM(data = BostonHousing, k = 5) |
คำตอบของ linear regression model คืออะไรครับ เพราะแต่ละ fold ก็จะได้ค่า coef ที่แตกต่างกัน
เราทำ CV เพื่อวัด error ของโมเดลครับ สำหรับ linear regression เราต้องสร้าง final model ตอนจบอีกที lm(medv ~ ., data=BostonHousing)
ปล. lm เป็น high bias model ไม่ได้มี parameter ที่ต้องจูนด้วยครับ (นอกจาก intercept) ในบทความนี้ทำให้ดูเป็นตัวอย่างง่ายๆเฉยครับ CV เรานิยมใช้กับโมเดลที่ต้องจูนเพื่อหา optimal parameters กับ cross validation error
ผมสงสัยว่าการทำ cv ก็เหมือนกับการทำ model หลายรอบด้วยข้อมุลที่เปลี่ยนไปมา แล้วหา mean error ACC ใช่ไหมครับ
แล้วตอนที่เราจะ depoy model เราจะใช้ model ไหนครับ หรือว่าเราเอาการทำ cv ไปทำอะไรครับ
Final model ใช้ค่า parameters ที่ทำให้ค่า error ต่ำที่สุดตอนทำ cross validation ครับ