อธิบาย K-Fold Cross Validation พร้อมโค้ดตัวอย่างใน R

เทคนิคที่เรียกว่าเป็น 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

ตัวอย่างการ split data เป็น 5 folds เท่าๆกัน

พอแบ่งข้อมูลเสร็จแล้ว (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)” มีเหตุผล!

IterationTrain FoldsValidation FoldValidation 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
  • เปรียบเทียบกับการทำ train test split ทั่วไป k-fold cv จะได้ผลลัพธ์ที่น่าเชื่อถือมากกว่า (more robust) เพราะเราสร้างและทดสอบโมเดลมากกว่าหนึ่งรอบ ช่วยลดปัญหา overfitting

บทความนี้แอดจะสอนเขียน 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)
row index สำหรับสร้าง folds

อธิบาย – ตัวเลขในแต่ละ 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]]
ตัวอย่าง dataframe ใน fold1

Build A Simple Model

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

  • เราสามารถสรุปผลได้ว่าโมเดล linear regression ที่เราเทรนด้วย 5-fold cv มีค่าเฉลี่ย R-Squared = 45.96% และส่วนเบี่ยงเบนมาตรฐาน = 2.59%
## 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)

4 thoughts on “อธิบาย K-Fold Cross Validation พร้อมโค้ดตัวอย่างใน R

  1. คำตอบของ linear regression model คืออะไรครับ เพราะแต่ละ fold ก็จะได้ค่า coef ที่แตกต่างกัน

    1. เราทำ CV เพื่อวัด error ของโมเดลครับ สำหรับ linear regression เราต้องสร้าง final model ตอนจบอีกที lm(medv ~ ., data=BostonHousing)

      ปล. lm เป็น high bias model ไม่ได้มี parameter ที่ต้องจูนด้วยครับ (นอกจาก intercept) ในบทความนี้ทำให้ดูเป็นตัวอย่างง่ายๆเฉยครับ CV เรานิยมใช้กับโมเดลที่ต้องจูนเพื่อหา optimal parameters กับ cross validation error

  2. ผมสงสัยว่าการทำ cv ก็เหมือนกับการทำ model หลายรอบด้วยข้อมุลที่เปลี่ยนไปมา แล้วหา mean error ACC ใช่ไหมครับ
    แล้วตอนที่เราจะ depoy model เราจะใช้ model ไหนครับ หรือว่าเราเอาการทำ cv ไปทำอะไรครับ

Leave a Reply