Đây là một mô hình phân loại. Bản chất của nó là để phân loại các user thành từng nhóm. Vì sao phải phân loại thành từng nhóm thì mục đích là như thế này:
Bản chất của Advosight là công cụ cho Influencer Marketing, thay vì tập trung vào 1 thị trường mục tiêu để quảng cáo thì lại đi tìm một đối tượng có sức ảnh hưởng (thông qua mạng xã hội). Dùng đối tượng này để nhắm đến được nhiều người mà họ có sức ảnh hưởng. Sau đó kết nối các đối tượng này với nhu cầu quảng cáo sản phẩm của các doanh nghiệp.
Mà doanh nghiệp ở Việt Nam theo thống kê đến năm 2016 là khoảng 500 ngàn doanh nghiệp, trong đó SME (Small and medium size) chiếm tới 98% phần trăm và chỉ có 2 phần trăm là các doanh nghiệp lớn.
Để mà kết nối được doanh nghiệp với đúng những Influencer thì cần những điều sau:
Thứ nhất là tìm ra được những Influencer và đo được độ quan trọng và ảnh hưởng của người đó trên mạng xã hội. Phần này đã được xây dựng trên Shiny App chính là những công thức để rank điểm số của mỗi người.
Thứ 2 là bài toán phân loại, bởi vì mỗi nhu cầu của doanh nghiệp nhắm đến đối tượng khách hàng khác nhau vì vậy họ cũng cần tìm được những Influencer mà đại diện được cho nhóm khách hàng mục tiêu của họ, tức là sản phẩm của họ nó hướng tới đến nhóm khách hàng riêng biệt.
Thì mô hình này sinh ra để giải quyết vấn đề thứ 2.
Ban đầu mục tiêu của mô hình này để phân loại ra thành từng nhóm như KOL, TOL, retailter, employee of retailer, fan (people love the product), thì mới đáp ứng được nhu cầu cho từng doang nghiệp, bởi vì mỗi mô hình doang nghiệp nhắm đến các đối tượng khác nhau. Không thể nào mà match 1 TOL (người có hiểu biết chuyên sâu về giải dụ như công nghệ) cho 1 doanh nghiệp sản xuất ra sản phẩm sữa.
Nhưng sau khi trao đổi với anh Lương Duy Bình thì anh ấy đang chỉ muốn tập trung vào Samsung nên mô hình thay đổi ở chỗ đánh Label. Bây giờ chỉ còn 3 nhóm là KOL (người nổi tiếng, showbiz), TOL (người am hiểu về 1 lĩnh vực chuyên biệt nào đó) và FAN (những người yêu thích sản phẩm, có share bài, hay post bài ở số lượng thấp với mức độ là tích cực)
Mô hình này được xây dựng để phân loại cho 3 nhóm là KOL, TOL và FAN
Xây dựng mô hình để tiết kiệm thời gian, tự động hóa, không phải cứ sau này cào về rồi lọc từng người xem ai ở nhóm nào sẽ rất là tốn công.
Ý tưởng của Random forest là : Từ 1 tập dữ liệu train data, ta chia nó ra nhiều sample nhỏ hơn, mỗi sample đại diện cho 1 tree và nhiều sample đó hợp lại tạo thành 1 rừng cây. Thì sự tổng hợp của những cây nhỏ và đơn giản này nó phản ánh được tính phức tạp của dữ liệu. Mỗi cây nhỏ này phản ánh được sự tinh túy của pattern trong kết quả được dựng nên mô hình Mỗi 1 sample đó là 1 cây hay 1 weak learner, nó giống như nguyên lý 1 team mạnh thì sẽ có nhiều sự phối hợp của những thành viên có những đặc điểm mạnh và chuyên biệt, như 1 team gồm có pháp sư, xạ thủ, support và đấu sĩ. Mỗi người có điểm mạnh yếu khác nhau, nhưng khi kết hợp lại thì sẽ tạo ra 1 team rất mạnh. Mỗi thành viên trong team sẽ có sự bổ trợ về kỹ năng cho nhau.
Việc tạo ra được sự đa dạng này là chìa khóa để tạo nên được mô hình phân loại mạnh mẽ. Thì theo cái nguyên lý này người ta gọi là ensemble method (based on the principle that weaker learners become stronger with teamwork).
Mỗi cây thì phải thực hiện việc dự báo phân loại, sau đó kết quả dự báo chung cho cả nhóm cây (group overall prediction) thì sẽ được quyết định bằng majority vote.
Mặc dù mỗi cây chỉ phản ánh 1 phần nhỏ của tổng thể dữ liệu, nhưng tổng thể thì được hoàn thiện bằng sự đa dạng ở mỗi khía cạnh.
Vậy để tạo ra sự đa dạng về đặc tính của mỗi cây nhỏ, (mỗi cây thể hiện được 1 khía cạnh thiết yếu), ta phải làm sao đưa được sự ngẫu nhiên (randomess) và mỗi cây nhỏ, nếu chia ra 100 cây giống nhau thì không có ý nghĩa gì, nhưng nếu ta chia ra được 100 cây khác nhau thì thuật toán sẽ bắt đầu có tác dụng.Việc đưa sư ngẫu nhiên (randomness) này sẽ được thực hiện bằng việc Bootstrap
Việc xây dưng randomforest thì giống với các quy tắc nền tảng khi xây dựng decision tree and bagging. Măc dù bagging cũng đã đưa sự randomness bằng phương pháp boostrap vào việc xây dựng cây phân loại để giảm đi sự sai lệch (variance) của kết quả dự đoán của mỗi cây con trong rừng cây và cải thiện quá trình thể hiện. Nhưng trees in bagging , thì mỗi cây (decision tree) nó không thực sự là độc lập với nhau (có tính correlation) bởi vì tất cả các biến gốc đều được xem xét khi thực hiện 1 quyết định phân tách của mỗi 1 cây con. Vấn đề nãy làm mỗi cây con có cùng 1 cấu trúc và nó ngăn cản mô hình tối ưu việc giảm thiểu độ sai số trong dự báo. Thì giải quyết cái viêc này bằng cách đưa thêm 1 tính randomness nữa là Split-varibale randomization. Tức là chia tách nhánh cây đầu tiên (root) dưa trên việc xem xét đa dạng hóa các biến một cách ngẫu nhiên. Vì vậy randomforest là phiên bản cải tiến của thuật toán Bagging
Boostrap là phương pháp tái chọn mẫu ngẫu nhiên và độc lập có trùng lặp từ tập dữ liệu train gốc thành nhiều tập mẫu hơn. Đơn thuần nó chỉ là việc lấy mẫu ngẫu nhiên có trùng lặp những hàng của tập dữ liệu train. Khi mà lấy mẫu có cho phép trùng lặp thì mỗi hàng có thể lặp lại nhiều lần và một số hàng thì bị vắng mặt. Cái ý tưởng của nó là muốn tạo ra một tập dữ liệu mới mà có giữ được một số những tính chất đặc trưng của tập train (dữ liệu gốc ban đầu) vì thế mà chúng ta có thể huấn luyện 1 mô hình giống nhau cho nhiều tập dữ liệu để lấy ra những đặc tính tiêu biểu nhất.
Có thể hiểu theo các bước sau:
Bước 1: Lấy m đối tượng ngẫu nhiên có trùng lặp từ trong n đối tượng ở tập train (dữ liệu gốc). m thì nhỏ hơn hoặc bằng n.
Bước 2: Huấn luyện cây quyết định trên những mẫu mới được tạo thành. Lặp lại những bước này bao nhiêu lần tùy thích. Càng nhiều cây thì mô hình càng tốt.
Bước 3: Giả sử hình trên là có 1000 cái cây, mỗi cái cây thì sẽ liên quan đến những đặc tính khác nhau của dữ liệu so với 1 cái cây cho toàn bộ dữ liệu và mỗi cái cây này sẽ có số điểm kết thúc khác nhau (terminal node), sau đó train mỗi cây và lấy giá trị trung bình của tổng tất cả các cây cho mỗi class để làm kết quả cuối cùng
Thì cái thuật toán cây quyết định thì nó sẽ bắt đầu bằng cách tìm toàn bộ tất cả những biến đặc tính trong bootstrap sample và sau đó nó sẽ tìm cái biến nào là biến tốt nhất mà biến này có thể dùng để chia tách ra những nhóm có tính đồng nhất cao nhất. Tức là khả năng phân biệt lớn nhất. Thì vì cái nảy xảy ra như nhau ở từng cây thì nó sẽ dẫn đến sự không mấy khác biệt nhau mấy về cấu trúc ở mỗi cây quyết định trong rừng cây dẫn đến sự gọi là tree correlation như đã nói ở trên. Vì vậy thuật toán random forest phải khống chế cái chuyện này bằng cách là không đi tìm toàn bộ những biến đặc tính mà chỉ chọn từng cụm biến ngẫu nhiên trong tổng số biến, rồi đi tìm ra biến tốt nhất trong cụm đó để thực hiện việc chia tách.
Confusion Matrix: A breakdown of predictions into a table showing correct predictions (the diagonal) and the types of incorrect predictions made (what classes incorrect predictions were assigned).
Precision: A measure of a classifiers exactness.
Recall: A measure of a classifiers completeness
F1 Score (or F-score): A weighted average of precision and recall.
ROC Curves: Like precision and recall, accuracy is divided into sensitivity and specificity and models can be chosen based on the balance thresholds of these values.
Gọi các thư viện cần thiết để làm việc
# load tools
library(tidyverse) # for handlinf data
library(dplyr) # manipulating data
library(readxl) # read xlsx file
library(rsample) # data splitting
library(randomForest) # basic implementation
library(ranger) # a faster implementation of randomForest
library(caret) # an aggregator package for performing many machine learning models
library(h2o) # an extremely fast java-based platform
library(ggplot2) # for visualization
library(pROC) # for calculate multiple class AUC and plot it
library(randomForestExplainer)
library(knitr)
library(kableExtra)
library(xlsx)
Chỉnh lại quy ước hiển thị số theo ngôn ngữ khoa học
# set to scientific notation
options("scipen"=0, "digits"=7)
Load dữ liệu và format dữ liệu
# load and format data
full = read.xlsx('full.xlsx', sheetIndex = 1) # read data with label and meta information
full1 = full[-which(is.na(full)),] # remove unavailabel content
full1$user_id = as.character(full1$user_id) # convert user_id from factor to character type of sample data
check = read.csv('features.csv')# load the data with necessary featuresd
colnames(check)[2] = 'user_id' # rename the user id to the consensus name with names in label data
check = check[,-1]
check$user_id = as.character(check$user_id) # convert user_id from factor to character type of database data
full2 = inner_join(full1, check, by = 'user_id') # join the sample data with database data, intersection joined by 'user_id'
full3 = full2[-which(full2$Label == 'NA'), ] # remove the NA label
full3$Label = as.character(full3$Label)
full3 = full3[,-1]
Xem qua thống kê về dữ liệu
print(dim(full3))
[1] 786 32
Dữ liệu hiện tại có 822 hàng và 33 cột
Xem qua 10 đối tượng ban đầu có số lượng like trên tổng số post cao nhất
ten_people = full3 %>% arrange(desc(totalLikesPosts))
#head(ten_people[, -1], 10) %>% kable() %>% kable_styling(bootstrap_options = "striped", font_size = 8)
print(ten_people[, -1])
Xem qua cấu trúc dữ liệu và định dạng của từng biến
print(str(full3[, -(1:4)]))
'data.frame': 786 obs. of 28 variables:
$ totalPosts : int 0 0 0 0 0 0 0 0 0 0 ...
$ totalPosPosts : int 0 0 0 0 0 0 0 0 0 0 ...
$ totalNegPosts : int 0 0 0 0 0 0 0 0 0 0 ...
$ word_count_post : int 0 0 0 0 0 0 0 0 0 0 ...
$ totalLikesPosts : int 0 0 0 0 0 0 0 0 0 0 ...
$ totalSharesPosts : int 0 0 0 0 0 0 0 0 0 0 ...
$ totalCommentsPosts : int 0 0 0 0 0 0 0 0 0 0 ...
$ images_count_post : int 0 0 0 0 0 0 0 0 0 0 ...
$ videos_count_post : int 0 0 0 0 0 0 0 0 0 0 ...
$ link_yes_no : int 0 0 0 0 0 0 0 0 0 0 ...
$ totalShares : int 1 1 1 2 1 1 1 15 1 3 ...
$ totalPosShares : int 0 0 0 1 0 0 0 1 0 0 ...
$ totalNegShares : int 0 0 0 0 0 0 0 0 0 0 ...
$ word_count_share : int 24 7 8 1 0 0 0 5 0 0 ...
$ totalLikesShares : int 0 0 0 0 0 0 0 0 0 6 ...
$ totalCommentsShares : int 0 0 0 0 0 0 0 0 0 0 ...
$ images_count_share : int 0 0 0 0 0 0 0 0 0 0 ...
$ videos_count_share : int 0 0 0 0 0 0 0 0 0 0 ...
$ totalComments : int 0 0 0 0 0 0 0 0 0 0 ...
$ totalPosComments : int 0 0 0 0 0 0 0 0 0 0 ...
$ totalNegComments : int 0 0 0 0 0 0 0 0 0 0 ...
$ word_count_comments : int 0 0 0 0 0 0 0 0 0 0 ...
$ totalLikesComments : int 0 0 0 0 0 0 0 0 0 0 ...
$ totalRepliesComments: int 0 0 0 0 0 0 0 0 0 0 ...
$ images_count_comment: int 0 0 0 0 0 0 0 0 0 0 ...
$ videos_count_comment: int 0 0 0 0 0 0 0 0 0 0 ...
$ total_follower : int 0 0 0 0 0 0 0 0 0 0 ...
$ total_friends : int 0 0 0 0 0 0 0 0 0 0 ...
NULL
Xem qua thống kê về mỗi biến đặc trưng của tập dữ liệu
print(summary(full3[,-(1:6)]))
totalNegPosts word_count_post totalLikesPosts totalSharesPosts totalCommentsPosts images_count_post
Min. :0.000000 Min. : 0.0 Min. : 0 Min. : 0.0 Min. : 0.0 Min. : 0.000
1st Qu.:0.000000 1st Qu.: 0.0 1st Qu.: 0 1st Qu.: 0.0 1st Qu.: 0.0 1st Qu.: 0.000
Median :0.000000 Median : 0.0 Median : 0 Median : 0.0 Median : 0.0 Median : 0.000
Mean :0.002544 Mean : 318.4 Mean : 9980 Mean : 130.8 Mean : 371.2 Mean : 2.753
3rd Qu.:0.000000 3rd Qu.: 0.0 3rd Qu.: 0 3rd Qu.: 0.0 3rd Qu.: 0.0 3rd Qu.: 0.000
Max. :1.000000 Max. :51146.0 Max. :4896526 Max. :41638.0 Max. :79872.0 Max. :354.000
videos_count_post link_yes_no totalShares totalPosShares totalNegShares word_count_share
Min. : 0.0000 Min. : 0.000 Min. : 0.0 Min. : 0.000 Min. :0.000000 Min. : 0.00
1st Qu.: 0.0000 1st Qu.: 0.000 1st Qu.: 1.0 1st Qu.: 0.000 1st Qu.:0.000000 1st Qu.: 0.00
Median : 0.0000 Median : 0.000 Median : 1.0 Median : 0.000 Median :0.000000 Median : 0.00
Mean : 0.3753 Mean : 4.939 Mean : 13.4 Mean : 1.711 Mean :0.001272 Mean : 16.56
3rd Qu.: 0.0000 3rd Qu.: 0.000 3rd Qu.: 2.0 3rd Qu.: 0.000 3rd Qu.:0.000000 3rd Qu.: 0.00
Max. :70.0000 Max. :893.000 Max. :1636.0 Max. :257.000 Max. :1.000000 Max. :1770.00
totalLikesShares totalCommentsShares images_count_share videos_count_share totalComments
Min. : 0.00 Min. : 0.0000 Min. : 0.00000 Min. : 0.00000 Min. : 0.00
1st Qu.: 0.00 1st Qu.: 0.0000 1st Qu.: 0.00000 1st Qu.: 0.00000 1st Qu.: 0.00
Median : 0.00 Median : 0.0000 Median : 0.00000 Median : 0.00000 Median : 0.00
Mean : 10.67 Mean : 0.1361 Mean : 0.08906 Mean : 0.05089 Mean : 33.29
3rd Qu.: 0.00 3rd Qu.: 0.0000 3rd Qu.: 0.00000 3rd Qu.: 0.00000 3rd Qu.: 0.00
Max. :2169.00 Max. :27.0000 Max. :15.00000 Max. :15.00000 Max. :14071.00
totalPosComments totalNegComments word_count_comments totalLikesComments totalRepliesComments
Min. : 0.000 Min. : 0.00000 Min. : 0 Min. : 0.000 Min. : 0.0000
1st Qu.: 0.000 1st Qu.: 0.00000 1st Qu.: 0 1st Qu.: 0.000 1st Qu.: 0.0000
Median : 0.000 Median : 0.00000 Median : 0 Median : 0.000 Median : 0.0000
Mean : 6.723 Mean : 0.03053 Mean : 1318 Mean : 3.188 Mean : 0.3613
3rd Qu.: 0.000 3rd Qu.: 0.00000 3rd Qu.: 0 3rd Qu.: 0.000 3rd Qu.: 0.0000
Max. :2792.000 Max. :10.00000 Max. :513841 Max. :1302.000 Max. :211.0000
images_count_comment videos_count_comment total_follower total_friends
Min. : 0.000 Min. : 0.00000 Min. : 0 Min. : 0.0
1st Qu.: 0.000 1st Qu.: 0.00000 1st Qu.: 0 1st Qu.: 0.0
Median : 0.000 Median : 0.00000 Median : 0 Median : 0.0
Mean : 1.131 Mean : 0.05216 Mean : 266803 Mean : 124.5
3rd Qu.: 0.000 3rd Qu.: 0.00000 3rd Qu.: 0 3rd Qu.: 0.0
Max. :648.000 Max. :35.00000 Max. :158378908 Max. :5000.0
Trước hết gieo hạt bằng 13 để sau này mỗi lần chạy có một kết quả giống nhau. Vì random forest chạy bằng bootstrap cho nên phải gieo hạt để có thể tái hiện lại kết quả khi ngẫu nhiên chọn mẫu.
set.seed(13)
Phân chia dữ liệu theo tỉ lệ 7:3, 7 cho train set và 3 cho test set
# create a random index for every row of data frame
train_data_index = sample(1: nrow(full3), round(nrow(full3)*.7)) # create a random index in 818 observations, with the 7:3 ratio
# create trainning data and format
train_data = full3[train_data_index,]
train_data = train_data %>% select(-user_id, -Name, -Description, -totalLikesPosts, -totalCommentsPosts, -images_count_post)
train_data$Label = as.character(train_data$Label)
train_data$Label = as.factor(train_data$Label)
# create testing data and format
test_data = full3[-train_data_index,]
test_data = test_data %>% select(-user_id, -Name, -Description, -totalLikesPosts, -totalCommentsPosts, -images_count_post)
test_data$Label = as.character(test_data$Label)
test_data$Label = as.factor(test_data$Label)
Xem lại kích thước của mỗi tập
Train set
print(dim(train_data))
[1] 550 26
Test set
print(dim(test_data))
[1] 236 26
Bởi vì mỗi cây con trong random forest thì được huấn luyện dựa trên mẫu được tạo bởi phương pháp bootstrap (tái chọn mẫu nhỏ hơn trên tập dữ liệu train data gốc) cho nên một số đối tượng trong mẫu được tạo sẽ bị trùng lặp và một số đối tượng sẽ bị loại ra. Thì tập những cái đối tượng bị loại ra sẽ được gọi là OUT OF BAG samples và sẽ được dùng như một tập validation set.
Bởi vì cái tập OOB này không được dùng để huấn luyện những cái cây, cho nên nó sẽ được dùng để đánh giá khả năng thể hiện của mô hình cây trên dữ liệu chưa được tiếp cận đến.
Dùng thang đo OOB error rate để đánh giá xem số cây cần chia ra là bao nhiêu để giảm độ sai số của mô hình xuống nhỏ nhất. Thì đây là error rate mà được tính từ những sample mà không được lựa chọn ra khi thực hiện việc tái chọn mẫu(bootstrap, random sampling)
Search trong khoảng 2000 cây.
# find the number of tree
set.seed(13)
# defautl rf model with 2000 trees
m1 = randomForest(
formula = Label~. ,
data = train_data,
ntree = 2000
)
# number of tree with the error rate , plotting
m1 # 2000 trees
Call:
randomForest(formula = Label ~ ., data = train_data, ntree = 2000)
Type of random forest: classification
Number of trees: 2000
No. of variables tried at each split: 5
OOB estimate of error rate: 1.82%
Confusion matrix:
FAN KOL TOL class.error
FAN 494 1 0 0.002020202
KOL 0 34 1 0.028571429
TOL 3 5 12 0.400000000
plot(m1, main = 'error rate with 2000 trees')
legend("topright", colnames(m1$err.rate),col=1:4,cex=0.8,fill=1:4)
# convert form matrix to data frame
err.df_2000 = m1$err.rate %>% as.data.frame()
#find the number of tree that correspondent to min error rate
which.min(err.df_2000$OOB)
[1] 39
Nhìn vào hình và kết quả trả về ta thấy mức error rate nhỏ nhất ứng với mức là 181 cây.
# SIMPLE MODEL
# model data
set.seed(13)
rf = randomForest(Label ~ . , data = train_data, importance = T, ntree = 181 )
# compare the predicted label with the actual label
result_rf = predict(rf, newdata = test_data)
confusionMatrix(result_rf, test_data$Label, mode = 'everything')
Confusion Matrix and Statistics
Reference
Prediction FAN KOL TOL
FAN 211 2 1
KOL 1 10 4
TOL 1 2 4
Overall Statistics
Accuracy : 0.9534
95% CI : (0.9181, 0.9765)
No Information Rate : 0.9025
P-Value [Acc > NIR] : 0.003117
Kappa : 0.7362
Mcnemar's Test P-Value : 0.801252
Statistics by Class:
Class: FAN Class: KOL Class: TOL
Sensitivity 0.9906 0.71429 0.44444
Specificity 0.8696 0.97748 0.98678
Pos Pred Value 0.9860 0.66667 0.57143
Neg Pred Value 0.9091 0.98190 0.97817
Precision 0.9860 0.66667 0.57143
Recall 0.9906 0.71429 0.44444
F1 0.9883 0.68966 0.50000
Prevalence 0.9025 0.05932 0.03814
Detection Rate 0.8941 0.04237 0.01695
Detection Prevalence 0.9068 0.06356 0.02966
Balanced Accuracy 0.9301 0.84588 0.71561
# calculate the auc
actual = as.numeric(test_data$Label )
predicted = as.numeric(result_rf)
result_roc = multiclass.roc(actual, predicted)
result_roc
Call:
multiclass.roc.default(response = actual, predictor = predicted)
Data: predicted with 3 levels of actual: 1, 2, 3.
Multi-class area under the curve: 0.835
auc(result_roc)
Multi-class area under the curve: 0.835
Xem error rate thay đổi theo số cây tương ứng
# plot the error rate and number of tree
plot(rf, main = 'Learning curve')
legend("topright", colnames(rf$err.rate),col=1:4,cex=0.8,fill=1:4)
Xem các biến quan trọng đóng góp vào mô hình dự đoán
varImpPlot(rf, main = 'Important variable', cex = .7)
Trên hình trên ta có thể thấy 5 biến quan trọng đóng góp vào mô hình là :
rf %>% important_variables(k=5,ties_action = "draw")
[1] "total_follower" "word_count_post" "totalPosPosts" "totalPosts" "link_yes_no"
Xem thêm các đối tượng nào bị phân loại sai
which(result_rf != test_data$Label)
[1] 155 189 191 199 200 202 203 204 205 208 209
error = full3[-train_data_index,][which(result_rf != test_data$Label), ]
error$predict_label = result_rf[which(result_rf != test_data$Label)]
compare = error %>% select(user_id, Name, Description, Label, predict_label)
# kable(compare) %>% kable_styling(bootstrap_options = "striped", font_size = 8)
# kable(error) %>% kable_styling(bootstrap_options = "striped", font_size = 8)
print(compare)
print(error[, -1])
Ở đây ta có thể thấy là độ chính xác của mô hình phân loại cho 2 class là FAN là rất cao.
Class FAN có độ chính xác Precision : 0.986 và mức độ không bỏ sót Recall: là 0988
Class KOL có độ chính xác Precision : 0.6667 và mức độ không bỏ sót Recall: là 0.71
Còn yếu nhất là class TOL với độ chính xác là 0.571 nhưng mức độ không bỏ sót chỉ có 0.57
Chính vì thế độ chính xác Accuracy : 0.9534 là vô nghĩa, không phải thang đo chính xác, nó đã bị đẩy cao lên do sự mất cân bằng giữ tỉ lệ các class. Trong đó FAN và KOL chiếm gần 97 phần trăm (mà 2 class này lại phân loại đúng nhất). (Xem phụ lục ở dưới)
print(prop.table(table(full3$Label)))
FAN KOL TOL
0.90076336 0.06234097 0.03689567
Nhìn vào chỉ số No Information Rate chúng ta thấy, chọn ngẫu nhiên thì độ chính xác trong 100 lần chọn 90 phần trăm trong khi đó mô hình chỉ thể hiện sự cải thiện được 5 phần trăm. Thì mô hình thế này còn kém
Area under ROC curve (AUC) : 0.853 phản ánh được phần nào sự thiếu chính xác của mô hình với sự mất cân bằng tỉ lệ giữa các class KOL, TOL và FAN
Mà vì sao lại có sự phân loại sai là do không có nhiều dữ liệu để cho máy học về TOL và KOL.
Xem lại sự tỉ lệ và phân bố của nhãn dán
unique(full3$Label)
[1] "FAN" "KOL" "TOL"
print(table(full3$Label))
FAN KOL TOL
708 49 29
Phân phối biểu diễn
ggplot(data = full3,
aes(Label)) +
geom_bar(aes(fill = Label))
Nhìn vào trên hình ta có thể thấy sự mất cân bằng của nhãn FAN so với các nhóm còn lại là KOL và TOL Tỉ lệ thực tế 3 nhóm chiếm là
print(prop.table(table(full3$Label)))
FAN KOL TOL
0.90076336 0.06234097 0.03689567
Bởi vì mô hình nó quá chênh lệch giữa các class như đã nói ở mục 9. Mà yêu cầu là cần nhiều dữ liệu cho nên phương pháp giải quyết vấn đề này là dùng phương pháp Oversampling.
Mà cụ thể là dùng kĩ thuật SMOTE: Synthetic Minority Over-sampling Technique. Kỹ thuật này dùng để tạo ra những fake user thuộc các nhóm chiếm tỉ lệ thấp như KOL và TOL
Để diễn giải vấn đề này thì xin dùng 1 small data set rất nổi tiếng là IRIS để diễn giải. Iris là tập dữ liệu gồm các loại hoa là Setosa, versicolor và virginica và các đặc tính của các loại hoa như là độ dài của đài hoa ….
smote = iris[c(2, 9, 16, 23, 51:63),
c(1, 2, 5)]
smote$Species = as.character(smote$Species)
print(smote)
table(smote$Species)
setosa versicolor
4 13
Ta có thể thấy loại hoa thuộc nhóm setosa chiếm số lượng 4, 0.23 phần trăm, rất là mất cân bằng so với versicolor
Như hình minh họa ở trên, loại hoa setosa, vòng tròn mà đỏ là nhóm thiểu số (Minority) mất cân bằng so với nhóm màu xanh versicolor (Majority class). Để mô hình dự báo đúng hơn, ta sẽ tạo thêm những điểm màu đỏ sao cho cân bằng với điểm màu xanh.
Thì thuật toán SMOTE sẽ vẽ các đường nối các điểm đó lại với nhau như hình dưới
Vì sao tạo được các đường nối nhau thì xin giải thích như sau:
Thuật toán sẽ đi tìm nhóm chiếm tỉ lệ thấp nhất, sau đó đó nó sẽ chọn lấy 1 điểm gốc mà điểm đó gấn nhất với tất cả các điểm còn lại. Sau đó chúng ta sẽ chọn bán kính vùng chúng ta muốn lấy các điểm dữ liệu bằng cách cho biết số điểm gần nhất có thể có so với điểm ban đầu. Sau đó nối chúng lại với nhau.
Cụ thể như hình dưới, chúng ta muốn có những điểm dữ liệu giả nằm trong bán kính từ điểm gốc đến 1 điểm gần nhất so với nó
hoặc là từ điểm gốc đến 2 điểm gần nhất so với nó
Và tạo các điểm dữ liệu trên các đường này như hình dưới
Sau đây là code để tạo thuật toán
Thì có thể nhận thấy ở trên các biến đầu vào X chính là các ma trận chứa các biến đặc tính ở hàng cột, target chính là nhãn của các đối tượng, K chính là số điểm gần nhất so với điểm gốc. dup_size chính là số điểm mới cần được tạo bằng tổng số điểm gốc cộng với các điểm gần nó nhất nhân với hệ số dup_size
Sau đây sẽ chạy tạo dữ liệu giả của các nhóm bị mất cân bằng bằng thư viện smotefamily
library(smotefamily)
dat = full3 %>% select(-user_id, -Name, -Description, -Label)
set.seed(13)
# create a TOL synth
tol_bal = SMOTE(dat,
as.numeric(as.factor(full3$Label)),
K = 10,
dup_size = 11)
tol_synth = tol_bal$syn_data
# add the label for TOL class
tol_synth$user_id = 'fake id'
tol_synth$Name = 'fake name'
tol_synth$Description = 'fake description'
tol_synth$Label = 'TOL'
tol_synth = tol_synth[, -29]
# create a KOL synth
dat2 = tol_bal$data
kol_bal = SMOTE(dat2[, -29],
dat2$class,
K = 10,
dup_size = 9 )
kol_synth = kol_bal$syn_data
# add the label for KOL class
kol_synth$user_id = 'fake id'
kol_synth$Name = 'fake name'
kol_synth$Description = 'fake description'
kol_synth$Label = 'KOL'
kol_synth = kol_synth[, -29]
# create a full new data
balance_data = bind_rows(full3, tol_synth, kol_synth)
table(balance_data$Label)
Sau khi đã tạo dữ liệu giả để khắc phục sự mất cân bằng giữa các class ta load dữ liệu vào máy
# load data
dat = read.xlsx('class_balanced.xlsx', sheetIndex = 1) # read data with label and meta information
dat = dat[,-1]
Bởi vì có những thang đo không chuẩn nên ta phải đi quy đổi lại, vì đây là biến quan trọng như đã nêu ra ở mục 7, ví dụ như tổng số like trên tất cả phải lấy thành trung bình số like trên một post, tương tự cho comment và image trên Post
# adjust the variable for more objectivity
dat = dat %>% mutate(likesPerPost = totalLikesPosts / (totalPosts +1 ),
commentsPerPost =totalCommentsPosts / (totalPosts + 1),
imagesPerPost = images_count_post / (totalPosts + 1))
Chia thành tập train set và test set, tỉ lê 7 : 3.
set.seed(13)
train_data_index2 = sample(1: nrow(dat), round(nrow(dat)*.7)) # create a random index in 818 observations, with the 7:3 ratio
# create trainning data and format
train_data2 = dat[train_data_index2,]
train_data2 = train_data2 %>% select(-user_id, -Name, -Description, -totalLikesPosts, -totalCommentsPosts, -images_count_post)
train_data2$Label = as.character(train_data2$Label)
train_data2$Label = as.factor(train_data2$Label)
# create testing data and format
test_data2 = dat[-train_data_index2,]
test_data2 = test_data2 %>% select(-user_id, -Name, -Description, -totalLikesPosts, -totalCommentsPosts, -images_count_post)
test_data2$Label = as.character(test_data2$Label)
test_data2$Label = as.factor(test_data2$Label)
Chạy mô hình với các tham số mặc đinh chỉ thay đổi số cây bằng 1000 cây.
set.seed(13)
m_1000 = randomForest(Label ~ . , data = train_data2, importance = T, ntree = 1000 )
# compare the predicted label with the actual label
result_m_1000 = predict(m_1000, newdata = test_data2)
metric = confusionMatrix(result_m_1000, test_data2$Label, mode = 'everything')
print(m_1000)
Call:
randomForest(formula = Label ~ ., data = train_data2, importance = T, ntree = 1000)
Type of random forest: classification
Number of trees: 1000
No. of variables tried at each split: 5
OOB estimate of error rate: 1.26%
Confusion matrix:
FAN KOL TOL class.error
FAN 522 0 0 0.000000000
KOL 1 346 2 0.008595989
TOL 3 8 225 0.046610169
Ta có thể thấy được độ chính xác trên tập train set là rất cao, không có sai sot cho nhóm FAN, và hầu như là cũng thế cho nhóm KOL. Chỉ có nhóm TOL là sai sót khoảng 0.046. Và tổng error rate ước tính là 1.26 phần trăm trên tập OOB.
print(metric)
Confusion Matrix and Statistics
Reference
Prediction FAN KOL TOL
FAN 222 0 1
KOL 0 139 3
TOL 0 2 108
Overall Statistics
Accuracy : 0.9874
95% CI : (0.9727, 0.9954)
No Information Rate : 0.4674
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.9802
Mcnemar's Test P-Value : NA
Statistics by Class:
Class: FAN Class: KOL Class: TOL
Sensitivity 1.0000 0.9858 0.9643
Specificity 0.9960 0.9910 0.9945
Pos Pred Value 0.9955 0.9789 0.9818
Neg Pred Value 1.0000 0.9940 0.9890
Precision 0.9955 0.9789 0.9818
Recall 1.0000 0.9858 0.9643
F1 0.9978 0.9823 0.9730
Prevalence 0.4674 0.2968 0.2358
Detection Rate 0.4674 0.2926 0.2274
Detection Prevalence 0.4695 0.2989 0.2316
Balanced Accuracy 0.9980 0.9884 0.9794
Confusion matrix
table(test_data2$Label)
FAN KOL TOL
222 141 112
metric$table
Reference
Prediction FAN KOL TOL
FAN 222 0 1
KOL 0 139 3
TOL 0 2 108
Thì ta có thể thấy là 222 đối tượng được gắn nhãn là FAN đã được phân loại chính xác tuyệt đối.
có 141 đối tượng được gắn nhẵn KOL chỉ có 2 đối tượng bị phân loại chệch ra thành TOL.
Và 112 đối tượng được gắn nhẵn TOL chỉ có 4 đối tượng bị phân loại thành 1 cho FAN và 3 cho KOL
Thống kê chung về mô hình
print(prop.table(table(test_data2$Label)))
FAN KOL TOL
0.4673684 0.2968421 0.2357895
Mô hình này có độ chính xác Accuracy rate là 0.987 rất là cao
Khi so sánh với lại với chỉ số No Information Rate, như đã nói ở mục 8. Chỉ số này cho thấy, nếu không xét đến bất kể thông tin nào ngoài phân bố tỉ lệ của 3 nhóm FAN, KOL và TOL thì nếu chọn ngẫu nhiên, mỗi 100 lần thì có 46 lần là chọn được trúng FAN, 30 lần là cọn được trúng KOL và 23 lần là TOL .
Độ chính xác của mô hình Accuracy rate là 0.987 cho thấy mô hình đang đi đúng hướng và làm việc rất tốt.
Precison và Recall
Ở đây ta có thể thấy là độ chính xác của mô hình phân loại cho 3 class là FAN, KOL và TOL là rất cao.
Class FAN có độ chính xác Precision : 0.9955 và mức độ không bỏ sót Recall: là 1, có nghĩa là máy nó đã lọc ra 100 phần trăm các đối tượng là FAN (Recall) và Phân loại chính xác đến 99,5 phần trăm là (precision)
Class KOL có độ chính xác Precision : 0.9789 và mức độ không bỏ sót Recall: là 0.985
Class TOL với độ chính xác là 0.9818 và mức độ không bỏ sót là: 0.9643
Thì cái điều này cho thấy mô hình hoạt động rất chính xác
Các đối tượng bị phân loại sai
error_1000 = dat[-train_data_index2,][which(result_m_1000 != test_data2$Label), ]
error_1000$predict_label = result_m_1000[which(result_m_1000 != test_data2$Label)]
compare_1000 = error_1000 %>% select(user_id, Name, Description, Label, predict_label)
print(compare_1000)
print(error_1000)
AUC (Area under ROC curve)
# calculate the auc
actual_m_1000 = as.numeric(test_data2$Label )
predicted_m_1000 = as.numeric(result_m_1000)
result_roc_m_1000 = multiclass.roc(actual_m_1000, predicted_m_1000)
result_roc_m_1000
Call:
multiclass.roc.default(response = actual_m_1000, predictor = predicted_m_1000)
Data: predicted_m_1000 with 3 levels of actual_m_1000: 1, 2, 3.
Multi-class area under the curve: 0.9887
auc(result_roc_m_1000)
Multi-class area under the curve: 0.9887
Ở đây ta thấy độ chính xác tổng hợp cho 3 class là 0.9887 là rất tốt
Có thể coi hình sau:
# plot the auc
rs <- result_roc_m_1000[['rocs']]
plot.roc(rs[[1]], main = "AUC of FAN")
plot.roc(rs[[2]], main = "AUC of KOL")
plot.roc(rs[[3]], main = "AUC of TOL")
plot.roc(rs[[3]], main = "AUC of 3 classes")
sapply(1:length(rs),function(i) lines.roc(rs[[i]],col=i, lwd = .8))
[,1] [,2] [,3]
percent FALSE FALSE FALSE
sensitivities Numeric,4 Numeric,4 Numeric,4
specificities Numeric,4 Numeric,4 Numeric,4
thresholds Numeric,4 Numeric,4 Numeric,4
direction "<" "<" "<"
cases Numeric,141 Numeric,112 Numeric,112
controls Numeric,222 Numeric,222 Numeric,141
fun.sesp ? ? ?
call Expression Expression Expression
original.predictor Numeric,475 Numeric,475 Numeric,475
original.response Numeric,475 Numeric,475 Numeric,475
predictor Numeric,363 Numeric,334 Numeric,253
response Numeric,363 Numeric,334 Numeric,253
levels Character,2 Character,2 Character,2
Các biến quan trọng
varImpPlot(m_1000, cex =.8)
Learning curve
plot(m_1000, main = 'error rate with 1000 trees')
legend("topright", colnames(m_1000$err.rate),col=1:4,cex=0.8,fill=1:4)
# convert form matrix to data frame
err.df = m_1000$err.rate %>% as.data.frame()
#find the number of tree that correspondent to min error rate
which.min(err.df$OOB)
[1] 121
Ở đây có thể thấy sai số thấp nhất ở 121 cây
Chạy mô hình mới với 121 cây với các tham số mặc định
set.seed(13)
m_121 = randomForest(Label ~ . , data = train_data2, importance = T, ntree = 121 )
# compare the predicted label with the actual label
result_m_121 = predict(m_121, newdata = test_data2)
metric_121 = confusionMatrix(result_m_121, test_data2$Label, mode = 'everything')
print(metric_121)
Confusion Matrix and Statistics
Reference
Prediction FAN KOL TOL
FAN 222 0 1
KOL 0 139 3
TOL 0 2 108
Overall Statistics
Accuracy : 0.9874
95% CI : (0.9727, 0.9954)
No Information Rate : 0.4674
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.9802
Mcnemar's Test P-Value : NA
Statistics by Class:
Class: FAN Class: KOL Class: TOL
Sensitivity 1.0000 0.9858 0.9643
Specificity 0.9960 0.9910 0.9945
Pos Pred Value 0.9955 0.9789 0.9818
Neg Pred Value 1.0000 0.9940 0.9890
Precision 0.9955 0.9789 0.9818
Recall 1.0000 0.9858 0.9643
F1 0.9978 0.9823 0.9730
Prevalence 0.4674 0.2968 0.2358
Detection Rate 0.4674 0.2926 0.2274
Detection Prevalence 0.4695 0.2989 0.2316
Balanced Accuracy 0.9980 0.9884 0.9794
Thực hiện grid search để tìm các tham số tối ưu
# full grid search
hyper_grid <- expand.grid(
mtry = seq(2, 28, by = 1),
node_size = seq(2, 28, by = 1),
sample_size = c(.55, .632, .70, .80),
OOB_RMSE = 0
)
# searching
for(i in 1:nrow(hyper_grid)) {
# train model
model <- ranger(
formula = Label ~ .,
data = train_data2,
num.trees = 121,
mtry = hyper_grid$mtry[i],
min.node.size = hyper_grid$node_size[i],
sample.fraction = hyper_grid$sample_size[i],
seed = 12
)
# add OOB error to grid
hyper_grid$OOB_RMSE[i] <- sqrt(model$prediction.error)
}
Xem các tham số tối ưu nhất
# look at lowest RMSE
hyper_grid %>%
dplyr::arrange(OOB_RMSE) %>%
head(10)
Kết quả cho thấy sau khi thực hiện tìm kiếm trên 2916 lần chạy mô hình với các tham số ta được bộ tham số có OOB thấp nhất là
mtry =3
node_size = 2
Ta chạy mô hình mới với các tham số tối ưu vừa tìm được
set.seed(13)
optimized_model = randomForest(formula = Label ~.,
data = train_data2,
ntree = 121,
mtry = 3,
nodesize = 3
)
m_op = predict(optimized_model, newdata = test_data2)
confusionMatrix(m_op, test_data2$Label, mode = 'everything')
Confusion Matrix and Statistics
Reference
Prediction FAN KOL TOL
FAN 222 0 1
KOL 0 139 3
TOL 0 2 108
Overall Statistics
Accuracy : 0.9874
95% CI : (0.9727, 0.9954)
No Information Rate : 0.4674
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.9802
Mcnemar's Test P-Value : NA
Statistics by Class:
Class: FAN Class: KOL Class: TOL
Sensitivity 1.0000 0.9858 0.9643
Specificity 0.9960 0.9910 0.9945
Pos Pred Value 0.9955 0.9789 0.9818
Neg Pred Value 1.0000 0.9940 0.9890
Precision 0.9955 0.9789 0.9818
Recall 1.0000 0.9858 0.9643
F1 0.9978 0.9823 0.9730
Prevalence 0.4674 0.2968 0.2358
Detection Rate 0.4674 0.2926 0.2274
Detection Prevalence 0.4695 0.2989 0.2316
Balanced Accuracy 0.9980 0.9884 0.9794
Model mới kết quả cũng không hơn gì model cũ với tham số mặc định và chạy với 1000 cây