บทความนี้แอดอธิบายการสร้างโมเดล machine learning ง่ายๆสำหรับปัญหา binary classification ทำนาย spam e-mail โดยใช้ข้อมูลจริงจาก HP Labs และ algorithm ยอดนิยมที่เราจะสอนวันนี้คือ Naive Bayes
Install Packages
tutorial วันนี้ใช้ 3 packages ในตารางด้านล่าง สามารถติดตั้ง package ง่ายๆใน RStudio ด้วยฟังชั่น install.packages()
packages | ใช้ทำอะไร? |
kernlab | โหลดข้อมูล spam e-mail database |
naivebayes | ฟังชั่น naivebayes() เพื่อสร้าง Naive Bayes Classifier |
dplyr | ทำ data wrangling ง่ายๆกับข้อมูล |
# install packages
install.packages(c("kernlab", "naivebayes", "dplyr"))
# load library
library(kernlab)
library(naivebayes)
library(dplyr)
Spam or Ham
ฝรั่งมีศัพท์ slang ไว้ใช้เรียก email ดีว่า “ham” ส่วน email ไม่ดีเรียก “spam” ก่อนจะเริ่มเขียนโมเดล ขั้นตอนแรกของการทำ data analysis ใน R คือ exploratory data analysis เรียกสั้นๆว่า EDA

ชื่อฟังชั่น | ใช้ทำอะไร? |
as.tbl() | เปลี่ยน data frame ทั่วไปเป็น tibble ที่แสดงผลใน console ดีขึ้น |
glimpse() | แสดงชื่อคอลั่มและ data type รวมถึง dimenstion ของ data frame |
table() | สร้างตารางความถี่ ใช้กับตัวแปร factor ใน R |
complete.cases() | ตรวจสอบว่า data frame ของเรามี missing value หรือเปล่า? |
ปกติแอดจะใช้ฟังชั่นนี้กับข้อมูลใหม่ที่เราต้องวิเคราะห์ เช่น ดูชื่อคอลั่มและ dimension ของ data frame เช็คพวก missing value และดูการกระจายตัวของ column ที่เราต้องการทำนายในตัวอย่างวันนี้คือ spam$type
# load dataset into RStudio
data("spam")
# tibble dataframe
spam <- as.tbl(spam)
glimpse(spam) # 58 variables, 4601 records
# type variable
table(spam$type)
table(spam$type) / nrow(spam) # imbalanced classes
# any missing value
mean(complete.cases(spam)) # no missing value
สำหรับ spam database เป็นข้อมูลที่สะอาดมากพร้อมใช้งาน มีทั้งหมด 4601 rows, 58 columns โดยคอลั่มที่เราต้องกสนทำนายคือ type
{nonspam, spam} ส่วนฟีเจอร์ที่เราใช้เป็น predictors คือคำต่างๆที่อยู่ในอีเมล์ เช่น make, all, over, internet, order, mail, credit เป็นต้น
สิ่งที่ต้องโน้ตไว้คือปัญหา spam เป็นแบบ imbalanced classes แปลว่าสัดส่วน ham และ spam ไม่เท่ากับ 50:50 โดยข้อมูลส่วนใหญ่ 60.59% เป็น ham ทำให้ผลทำนายของโมเดลเราจะเอียงไปทาง class ที่ใหญ่กว่านิดหน่อย
Intro to Naive Bayes
Naive Bayes ใช้หลักการ conditional probability และ Bayes theorem ในการสร้าง spam classifier โค้ดด้านล่างใช้ฟังชั่น tapply()
คำนวณ conditional probability ของ word ที่อยู่ใน nonspam vs. spam email
- p(word | spam)
- p(word | nonspam)
# average frequency of these words split by type (nonspam/ spam)
tapply(spam$make, spam$type, mean)
tapply(spam$email, spam$type, mean)
ใช้ Bayes เพื่อทำ inverse probability เปลี่ยนจาก p(word|spam)
เป็น p(spam|word)
ด้วยสมการนี้
# Bayes theorem
# p(spam | word) = p(word | spam) * p(spam) / p(word)
เทคนิคสำคัญของ naive bayes algorithm คือการ apply ทฤษฏี Bayes กับ all words ใน spam data เราสามารถคำนวณ conditional probability ของ spam และ non spam ได้แบบนี้
# probablity of spam given all words in email
# p(spam | word1, word2, word3, word4, ...)
# probability of nonspam given all words in email
# p(nonspam | word1, word2, word3, word4, ...)
เสร็จแล้วแค่เปรียบเทียบความน่าจะเป็นว่า email ฉบับนั้นมี % เป็น spam หรือ nonspam แบบไหนมากกว่ากัน? ถ้า p(spam) > p(nonspam) เราจะ predict spam ตรงๆเลย!
Split and Train
ขั้นตอนแรกก่อนที่จะสร้างโมเดลคือการแบ่ง spam data เป็นสองส่วนคือ train_set 80% และ test_set 20% แล้วค่อย fit model กับ train_set ด้วยฟังชั่น naive_bayes(type ~ .)
"type ~ ."
เรียกว่า formula ในภาษา R โดยตัวแปรที่เราต้องการทำนายคือ type
เป็นฟังชั่นของตัวแปรทั้งหมดที่อยู่ใน data frame ( .
เป็น shortcut ใช้แทน x1 + x2 + x3 + ... + xn
ใน dataframe)

# split dataset
set.seed(99)
id <- sample(nrow(spam), 0.8 * nrow(spam))
train_set <- spam[id, ]
test_set <- spam[-id, ]
# train model
nb_model <- naive_bayes(type ~ ., data = train_set, laplace = 2)
พอสร้างโมเดลเสร็จแล้ว ขั้นตอนต่อไปคือเอา nb_model
ไปลองทำนาย train_set และ test_set เพื่อดู model accuracy โดยเราจะวัดผลโมเดลของเราด้วยข้อมูล test_set ที่เราเตรียมไว้
Functional Programming in R
หัวใจของภาษา R คือการเขียนฟังชั่น หรือที่โปรแกรมเมอร์เรียกกันว่า “Functional Programming” แทนที่เราจะนั่งเขียนโค้ดแล้วกดรันทีละบรรทัด เราสามารถ wrap โค้ดทั้งหมดของเราใน user defined function ได้เลย
function ที่เราจะเขียนชื่อว่า nb_summary()
รับได้ 3 arguments ประกอบด้วย
- nb_model คือโมเดลที่เราสร้างจากฟังชั่น
naive_bayes()
- train_set
- test_set
output ที่ได้จากฟังชั่น nb_summary()
คือ model accuracy ของทั้ง train_set และ test_set ดูว่าโมเดลเราทำนาย spam email ได้แม่นยำขนาดไหน โค้ดด้านล่างเราใช้ฟังชั่น cat()
เพื่อแสดงผลใน console
nb_summary <- function(nb_model, train_set, test_set){
# compute train and test accuracy
train_acc <- mean(predict(nb_model) == train_set$type)
test_acc <- mean(predict(nb_model, newdata = test_set) == test_set$type)
# print accuracy
cat("===== Model Summary =====\n")
cat("train accuracy: ", round(train_acc*100, 2), "%", sep = "")
cat("\n")
cat(" test accuracy: ", round(test_acc*100, 2), "%", sep = "")
}
Test Our Function
ทดสอบฟังชั่น nb_summary()
กับโมเดลและข้อมูล train, test ที่เราเตรียมไว้

# see the accuracy of train and test data
nb_summary(nb_model, train_set, test_set)
ตารางด้านล่างแสดงผล model accuracy ที่ได้จากฟังชั่น nb_summary()
ทั้ง train_set และ test_set ได้ accuracy อยู่ที่ประมาณ ~70% ถือว่ากลางๆ ยังมีอีกหลายโมเดลที่ทำงานได้ดีกว่านี้ เช่น decision tree, random forest หรือ logistic regression
Accuracy | |
train accuracy | 70.54% |
test accuracy | 69.71% |
Optional Reading
ถ้าใครยังไม่คุ้นเคยกับ Bayes Theorem เท่าไร ลองอ่านบทความแนะนำของเราได้ที่นี่
Full Script
สำหรับเพื่อนๆที่อยากได้ R Script ไปศึกษาต่อแบบเต็มๆ ลองดูได้ที่ gist ด้านล่างเลย
# spam or ham | |
# load library | |
library(kernlab) | |
library(naivebayes) | |
library(dplyr) | |
# load dataset into RStudio | |
data("spam") | |
# tibble dataframe | |
spam <- as.tbl(spam) | |
glimpse(spam) # 58 variables, 4601 records | |
# type variable | |
table(spam$type) | |
table(spam$type) / nrow(spam) # imbalanced classes | |
# any missing value | |
mean(complete.cases(spam)) # no missing value | |
# split dataset | |
set.seed(99) | |
id <- sample(nrow(spam), .8*nrow(spam)) | |
train_set <- spam[id, ]; nrow(train_set) | |
test_set <- spam[-id, ]; nrow(test_set) | |
# train model | |
nb_model <- naive_bayes(type ~ ., data = train_set, laplace = 2) | |
# create a summary function | |
nb_summary <- function(nb_model, train_set, test_set){ | |
train_acc <- mean(predict(nb_model) == train_set$type) | |
test_acc <- mean(predict(nb_model, newdata = test_set) == test_set$type) | |
cat("===== Model Summary =====\n") | |
cat("train accuracy: ", round(train_acc*100, 2), "%", sep = "") | |
cat("\n") | |
cat(" test accuracy: ", round(test_acc*100, 2), "%", sep = "") | |
} | |
# see the accuracy of train and test data | |
nb_summary(nb_model, train_set, test_set) |