Naive Bayes didasari oleh Bayes’ Theorem of Probability. Bayes Theorem membahas terkait peluang kejadian dependen (dependent events)
Kejadian A dan kejadian B terjadi bersamaan
\[P(A \cap B) = P(A) \times P(B)\]
Contoh Kasus: Cobalah hitung peluan dadu muncul angka 4 pada lemparan pertama dan peluang menucul angka 6 pada lemparan kedua?
\[P(A \cap B) = P(A) \times P(B) = \frac{1}{6} \times \frac{1}{6} = \frac{1}{36}\] > Maka, peluang dadu muncul angka 4 pada lemparan pertama dan dadu muncul angka 6 pada lemparan kedua adalah 1/36.
Illustrasi penghitungan peluang independent event:
Kejadian A terjadi jika diketahui kejadian B telah terjadi a.k.a peluang bersyarat. Peluang A Bersyarat B.
Untuk menghitung peluang kejadian A, perlu menggunakan Bayes Theorem:
\[P(A|B) = \frac{P(B|A) P(A)}{P(B|A) P(A)\ +\ P(B|\neg A) P(\neg A)}\]
Atau
\[P(A|B) = \frac{P(A \cap B) P(A)}{P(B \cap A) P(A)\ +\ P(B \cap A | \neg A) P(\neg A)}\]
Keterangan:
Illustrasi penghitungan peluang independent event:
Sebagai seorang marketing analyst, kita ingin meningkatkan penjualan
dengan menargetkan customer dengan karakteristik tertentu. Dari data
historis 400 customer yang sudah diprospek, kita
peroleh informasi tentang gender
, umur
, dan
kategori gaji
serta apakah
dia membeli produk kita atau tidak
.
# Please run the code down below
library(dplyr)
<- read.csv("data_input/Customer_Behaviour.csv",
cust_behaviour stringsAsFactors = T)
glimpse(cust_behaviour)
#> Rows: 400
#> Columns: 4
#> $ Gender <fct> Male, Male, Female, Female, Male, Male, Female, Female, Male…
#> $ Age <fct> < 30, 30-50, < 30, < 30, < 30, < 30, < 30, 30-50, < 30, 30-5…
#> $ Salary <fct> Low, Low, Medium, Medium, Medium, Medium, Medium, High, Low,…
#> $ Purchased <fct> No, No, No, No, No, No, No, Yes, No, No, No, No, No, No, No,…
Deskripsi data:
Gender
: Jenis Kelamin (Male, Female)Age
: Range usia (< 30, 30-50, > 50)Salary
: Kategori Gaji Customer (Low, Medium, High)Purchased
: Apakah klien membeli produk kita atau tidak
(Yes, No)Pertanyaan:
Jika ada seorang customer perempuan dengan nama Diva, yang usianya masih di bawah 30 tahun dan memiliki kategori gaji yang tinggi. Berapakan peluang Diva membeli produk?
Naive Bayes mengasumsikan bahwa tiap prediktor saling independent atau tidak berkaitan satu sama lain. Secara matematis:
Untuk mempersingkat notasi, kita simbolkan kejadian \(A, B, C\) (prediktornya) sebagai:
\[P(A\ \cap\ B\ \cap\ C\ |\ Purchase) = P(A\ |\ Purchase) \times P(B\ |\ Purchase) \times P(C\ |\ Purchase)\]
\[P(A\ \cap\ B\ \cap\ C\ |\ \neg Purchase) = P(A\ |\ \neg Purchase) \times P(B\ |\ \neg Purchase) \times P(C\ |\ \neg Purchase)\]
Sehingga:
\[P(Purchase| A\ \cap\ B\ \cap\ C) = \frac{P(Purchase) \ P(A\ |\ Purchase)\ P(B\ |\ Purchase)\ P(C\ |\ Purchase)}{P(Purchase) \ P(A\ |\ Purchase)\ P(B\ |\ Purchase)\ P(C\ |\ Purchase)\ +\ P(\neg Purchase) \ P(A\ |\neg Purchase)\ P(B\ |\neg Purchase)\ P(C\ |\neg Purchase)}\]
Mari kita cari satu per satu peluang yang dibutuhkan:
# Proporsi customer membeli dan tidak membeli
prop.table(table(cust_behaviour$Purchased))
#>
#> No Yes
#> 0.6425 0.3575
# Peluang Customer Membeli
<- 0.3575
p_purchase
# Peluang Customer Tidak Membeli
<- 0.6425 p_not_purchase
# Please run the code down below
table(cust_behaviour$Gender, cust_behaviour$Purchased)
#>
#> No Yes
#> Female 127 77
#> Male 130 66
\(P(A\ |\ Purchase)\)
# Please type your code down below
<- 77/ (77 + 66) p_female_purchase
\(P(A\ |\neg Purchase)\)
# Please type your code down below
<- 127/ (127 + 130) p_female_not_purchase
# Please run the code down below
table(cust_behaviour$Age, cust_behaviour$Purchased)
#>
#> No Yes
#> < 30 96 4
#> > 50 4 45
#> 30-50 157 94
\(P(B\ |\ Purchase)\)
# Please type your code down below
<- 4 / (4 + 45 + 94) p_age30_purchase
\(P(B\ |\neg Purchase)\)
# Please type your code down below
<- 96 / (96 + 4 + 157) p_age30_not_purchase
# Please run the code down below
table(cust_behaviour$Salary, cust_behaviour$Purchased)
#>
#> No Yes
#> High 19 75
#> Low 56 39
#> Medium 182 29
\(P(C\ |\ Purchase)\)
# Please type your code down below
<- 75 / (75 + 39 + 29) p_high_purchase
\(P(C\ |\neg Purchase)\)
# Please type your code down below
<- 19 / (19 + 56 + 182) p_high_not_purchase
\(P(Purchase| A\ \cap\ B\ \cap\ C) = \frac{P(Purchase) \ P(A\ |\ Purchase)\ P(B\ |\ Purchase)\ P(C\ |\ Purchase)}{P(Purchase) \ P(A\ |\ Purchase)\ P(B\ |\ Purchase)\ P(C\ |\ Purchase)\ +\ P(\neg Purchase) \ P(A\ |\neg Purchase)\ P(B\ |\neg Purchase)\ P(C\ |\neg Purchase)}\)
# Please run the code down below
* p_female_purchase * p_age30_purchase * p_high_purchase) /
(p_purchase * p_female_purchase * p_age30_purchase * p_high_purchase +
(p_purchase * p_female_not_purchase * p_age30_not_purchase * p_high_not_purchase) p_not_purchase
#> [1] 0.243622
naiveBayes()
functionDengan menggunakan function naiveBayes()
dari package
e1071
, kita tidak perlu melakukan perhitungan manual.
Terdapat 2 cara dalam membuat model menggunakan function
naiveBayes()
:
naiveBayes(formula, data)
formula
: formula y~x, dimana y: target variabel, x:
prediktor variabeldata
: data yang digunakan untuk target dan prediktor
variabelnaiveBayes(x, y)
x
: prediktor variabel dari data yang
digunakan
y
: target variabel dari data yang digunakan
Pembuatan Model & Interpretasi
# Please run the code down below
#install.packages("e1071")
library(e1071)
<- naiveBayes(formula = Purchased ~ Age + Gender + Salary,
model_naive data = cust_behaviour)
Model Naive Bayes menyimpan informasi tentang nilai peluang dependen antara target dengan setiap feature dalam bentuk prop.table(). Kita bisa periksa nilainya dengan perhitungan manual:
# Please run the code down below
model_naive
#>
#> Naive Bayes Classifier for Discrete Predictors
#>
#> Call:
#> naiveBayes.default(x = X, y = Y, laplace = laplace)
#>
#> A-priori probabilities:
#> Y
#> No Yes
#> 0.6425 0.3575
#>
#> Conditional probabilities:
#> Age
#> Y < 30 > 50 30-50
#> No 0.37354086 0.01556420 0.61089494
#> Yes 0.02797203 0.31468531 0.65734266
#>
#> Gender
#> Y Female Male
#> No 0.4941634 0.5058366
#> Yes 0.5384615 0.4615385
#>
#> Salary
#> Y High Low Medium
#> No 0.07392996 0.21789883 0.70817121
#> Yes 0.52447552 0.27272727 0.20279720
Melalukan prediksi dengan function predict()
Syntax: predict(object model, newdata, type)
Terdapat parameter tambahan yang akan kita gunakan pada fungsi
predict()
, yaitu parameter type
. Parameter
type
dapat kita isi dengan:
type = "raw"
mengembalikan nilai peluang untuk
masing-masing kelastype = "class"
mengembalikan label kelasnya (default
threshold 0.5Contoh prediksi dengan menggunakan type = raw
Membuat dataframe untuk diprediksi
# Please run the code down below
<- data.frame(Gender = "Female",
diva Age = "< 30",
Salary = "High")
# Please run the code down below
predict(model_naive,
newdata = diva,
type = "raw")
#> No Yes
#> [1,] 0.756378 0.243622
Contoh prediksi dengan menggunakan type = class
# Please run the code down below
predict(model_naive,
newdata = diva,
type = "class")
#> [1] No
#> Levels: No Yes
Hubungan prediktor dengan target saling dependen (berhubungan).
Pada kasus tertentu, dapat terjadi data scarcity, yaitu kondisi
dimana suatu prediktor tidak hadir sama sekali di salah satu
kelas. Misalkan dari data Customer_Behaviour
sebelumnya, pada customer yang melakukan Purchase sama
sekali tidak ada yang ber-Gender Female, sehingga tabel frekuensinya
menjadi sebagai berikut:
#> No Purchase Purchase
#> Female 127 0
#> Male 130 143
Dari tabel di atas, kita peroleh: \(P(Gender = Female\ |\ Purchase) = 0\). Apabila tabel frekuensi untuk prediktor lainnya tetap sama, maka peluang Tiara sebagai Female dengan Age < 30 dengan High Salary untuk membeli produk adalah:
\[P(Purchase\ |\ Age <30 \ \cap\ Gender = Female\ \cap\ Salary = High ) \\ = \frac{\frac{143}{143+257}\ \frac{4}{4+45+94}\ \frac{0}{0+143}\ \frac{75}{75+39+29}} {\frac{143}{143+257}\ \frac{4}{4+45+94}\ \frac{0}{0+143}\ \frac{75}{75+39+29} + \frac{257}{143+257}\ \frac{96}{96+4+157}\ \frac{127}{127+130}\ \frac{19}{19+56+182}} = 0\]
Ini adalah karakteristik kedua dari Naive Bayes: Skewness Due
To Scarcity. Ketika terdapat suatu prediktor yang frekuensi
nilainya 0 untuk salah satu kelas (pada kasus ini
Female
untuk Purchase = Yes
), maka model
secara otomatis memprediksi bahwa peluangnya adalah 0 untuk kondisi
tersebut, tanpa memperdulikan nilai dari prediktor yang lainnya.
Dengan kata lain, setiap ada customer ber-gender Female
maka model kita akan langsung memprediksi dia sebagai tidak membeli.
Model menjadi bias atau kurang akurat dalam melakukan prediksi.
Berikut adalah solusinya:
Prediktor yang bermasalah tidak digunakan untuk pembuatan model. Namun, cara ini mungkin kurang tepat kalau prediktor tersebut memang punya pengaruh besar terhadap hasil prediksi, sehingga ketika dihilangkan maka hasil prediksi justru kurang baik. Selain itu, dengan bertambahnya data, ada kemungkinan frekuensinya berubah dari yang sebelumnya tidak ada menjadi ada
Kita ingin memastikan tidak ada observasi yang nol, namun juga proporsi tidak berubah jauh dari aslinya. Solusi alternatifnya menggunakan Laplace Smoothing, yaitu dengan cara menambahkan frekuensi dari setiap prediktor sebanyak angka tertentu (biasanya 1), sehingga tidak ada lagi prediktor yang memiliki nilai 0.
Contoh pada kasus di atas dengan menggunakan
laplace = 1
:
Tabel Frekuensi Purchased (TETAP, karena bukan predictor)
\[\begin {matrix} No & Yes \\\hline257 & 143\end{matrix}\]
Tabel Frekuensi Gender
\[\begin {matrix}& Female & Male \\\hline\neg Purchase & 127+1 & 130+1 \\Purchase & 0+1 & 143+1\end{matrix}\]
Tabel Frekuensi Age
\[\begin {matrix}& <30 & >50 & 30-50 \\\hline\neg Purchase & 96+1 & 4+1 & 157+1 \\Purchase & 4+1 & 45+1 & 94+1 \\\end{matrix}\]
Tabel Frekuensi Salary
\[\begin {matrix}& High & Low & Medium \\\hline\neg Purchase & 19+1 & 56+1 & 182+1 \\Purchase & 75+1 & 39+1 & 29+1 \\\end{matrix}\]
Jika kita hitung kembali, dengan laplace smoothing, peluang Tiara dalam membeli produk dari permasalahan data scarcity adalah:
143/(143+257) * 1/(1+144) * 5/(5+46+95) * 76/(76+40+30)) /
(143/(143+257) * 1/(1+144) * 5/(5+46+95) * 76/(76+40+30) +
(257/(143+257) * 128/(128+131) * 97/(97+5+158) * 20/(20+57+183))
#> [1] 0.004800184
Walaupun peluangnya sangat kecil, tetapi setidaknya dengan metode Laplace Smoothing kita dapat memastikan model tidak terlalu ekstrim dalam mengklasifikasikan observasi serta tetap dapat mempertimbangkan nilai peluang dari prediktor lainnya.
Dengan menggunakan function naiveBayes()
, kita cukup
menambahkan parameter laplace = 1
:
# Please run the code down below
# Modeling
<- naiveBayes(formula = Purchased ~ . ,
model_naive2 data = cust_behaviour,
laplace = 1)
# Predict
predict(model_naive2, newdata = diva, type = "raw")
#> No Yes
#> [1,] 0.7266279 0.2733721
Text Mining adalah salah satu metode analisis data yang fokus utamanya adalah mencari informasi dan pola-pola dari data yang tidak terstruktur, yaitu data teks. Data teks disebut tidak terstruktur karena:
Berikut ilustrasi workflow dari text mining:
Business Question: Berdasarkan kata-kata pada SMS, kita ingin melakukan klasifikasi apakah suatu SMS termasuk spam atau bukan (ham)?
# Please run the code down below
<- read.csv("data_input/spam.csv",
sms_raw stringsAsFactors = FALSE,
encoding = "UTF-8")
head(sms_raw)
# Please type your code down below
<- sms_raw %>%
sms_clean select(-X , -X.1 , -X.2) %>%
mutate(v1 = as.factor(v1))
Silahkan ambil 5 sample text yang termasuk spam, kemudian amati kata-kata apa saja yang dapat menjadi indikator (prediktor) bahwa suatu text adalah spam?
# Please type your code down below
%>%
sms_clean filter(v1 == 'spam') %>%
head(5)
Apa kata-kata yang berpotensial mengindikasi bahwa suatu text adalah spam? - Free
Text preprocessing adalah suatu proses untuk menyeleksi data text agar menjadi lebih terstruktur lagi dengan melalui serangkaian tahapan. Berikut beberapa tahapan dalam data text pre-processing.
Corpus adalah kumpulan dari dokumen. Pada kasus ini, satu dokumen ekuivalen dengan satu observasi SMS. Di dalam satu SMS bisa terdapat satu atau lebih kalimat.
Salah satu package yang bisa kita gunakan untuk text mining adalah
tm
. Pengubahan dari vector text menjadi corpus bisa
dilakukan menggunakan function VCorpus()
# Please type your code down below
library(tm)
<- VCorpus(VectorSource(sms_clean$v2)) sms.corpus
# Please run the code down below
1,2] sms_clean[
#> [1] "Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat..."
# Please run the code down below
1]]$content sms.corpus[[
#> [1] "Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat..."
Untuk melihat beberapa content sekaligus, kita dapat menggunakan
lapply()
# Please run the code down below
lapply(sms.corpus[1:3]$content, as.character)
#> [[1]]
#> [1] "Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat..."
#>
#> [[2]]
#> [1] "Ok lar... Joking wif u oni..."
#>
#> [[3]]
#> [1] "Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&C's apply 08452810075over18's"
Text Cleansing perlu kita lakukan untuk merapikan dan menghilangkan
informasi yang kurang penting data text kita. Dalam melakukan text
cleansing kita akan menggunakan fungsi tm_map()
dari
package tm
.
Berikut parameter yang dapat kita gunakan pada fungsi tersebut:
x =
: Parameter ini akan di-isi dengan object
corpusFUN =
: Parameter ini akan kita isi dengan fungsi untuk
melakukan text cleansing, atau fungsi tersebut akan kita bungkus dengan
content_transformer()
jika fungsi yang digunakan bukan
berasal dari library tm
.Agar kita memiliki pembanding data sebelum dilakukan text cleansing dan sesudah, mari kita coba panggil konten SMS ke-9:
# Please run the code down below
9]]$content sms.corpus[[
#> [1] "WINNER!! As a valued network customer you have been selected to receive a 900 prize reward! To claim call 09061701461. Claim code KL341. Valid 12 hours only."
Tahapan pertama yang biasanya dilakukan adalah tahapan case folding. Tahapan ini hampir selalu disertakan ketika melakukan text preprocessing.
Mengapa? Karena data yang kita miliki tidak selalu terstruktur dan konsisten dalam penggunaan huruf kapital. Jadi, peran dari case folding adalah untuk menyamaratakan penggunaan huruf kapital. Misalnya data teks yang kita dapat berupa tulisan “DaTA SCIence” maka dengan case folding artinya kita mengubah semua huruf menjadi huruf kecil (lowercase) semua.
Berikut adalah cara untuk melakukan case-folding
# Please type your code down below
<- tm_map(x = sms.corpus,
sms.corpus FUN = content_transformer(tolower))
# inspect content sms ke-9
9]]$content sms.corpus[[
#> [1] "winner!! as a valued network customer you have been selected to receive a 900 prize reward! to claim call 09061701461. claim code kl341. valid 12 hours only."
Angka maupun kata sambung bisa dibilang tidak penting karena bisanya kedua hal tersebut tidak memiliki makna tersendiri. Maka dari itu bisa kita coba hilangkan saja.
# Please type your code down below
# Menghapus angka
<- tm_map(x = sms.corpus,
sms.corpus FUN = removeNumbers)
# inspect content sms ke-9
9]]$content sms.corpus[[
#> [1] "winner!! as a valued network customer you have been selected to receive a prize reward! to claim call . claim code kl. valid hours only."
# Please type your code down below
<- tm_map(x = sms.corpus,
sms.corpus FUN = removeWords, stopwords("English"))
# cek content sms ke-9
9]]$content sms.corpus[[
#> [1] "winner!! valued network customer selected receive prize reward! claim call . claim code kl. valid hours ."
Selain menghilangkan angka maupun kata sambung, kita juga dapat menghilangkan tanda baca. Untuk menghilangkan tanda baca, kita harus menggunakan fungsi DIY.
# Please run the code down below
<- content_transformer(FUN = function(x, pattern){
transformer gsub(x = x,
pattern = pattern,
replacement = " ")
})
# Please run the code down below
# replace ".", "/", "@", "-" with a white space
# Ingin me-replace tanda / dengan spasi
<- tm_map(sms.corpus, transformer, "/")
sms.corpus
#ingin me-replace tanda @ dengan spasi
<- tm_map(sms.corpus, transformer, "@")
sms.corpus <- tm_map(sms.corpus, transformer, "-")
sms.corpus <- tm_map(sms.corpus, transformer, "\\.")
sms.corpus
# cek content ke-9
9]]$content sms.corpus[[
#> [1] "winner!! valued network customer selected receive prize reward! claim call claim code kl valid hours "
Proses stemming merupakan tahapan dimana kita akan mengembalikan sebuah kata menjadi kata dasar, sebagai contoh kata bahasa Inggris walking, walked, walks menjadi walk.
# Please run the code down below
library(SnowballC)
wordStem(c("do", "doing", "kicked", "kick"))
#> [1] "do" "do" "kick" "kick"
# Please type your code down below
<- tm_map(x = sms.corpus,
sms.corpus FUN = stemDocument)
# cek content ke-9
9]]$content sms.corpus[[
#> [1] "winner!! valu network custom select receiv prize reward! claim call claim code kl valid hour"
Hal ini diperlukan karena pada proses tokenizing (selanjutnya), kata akan dipotong berdasarkan karakter spasi.
# Please type your code down below
# remove white space
<- tm_map(x = sms.corpus,
sms.corpus FUN = stripWhitespace)
# inspect content sms ke-9
9]]$content sms.corpus[[
#> [1] "winner!! valu network custom select receiv prize reward! claim call claim code kl valid hour"
Summary singkat, secara umum tahapan yang sering dilakukan untuk text cleansing adalah:
Sampai di tahap ini, data kita masih berupa text. Pertanyaannya bagaimana cara model kita belajar apabila prediktornya masih berupa text?
Data perlu diubah menjadi Document Term Matrix:
Tahapan pemecahan tiap kata di text menjadi 1 term disebut
Tokenization. Hasil tokenization akan disusun menadi
Document Term Matrix. Untuk membuat data kita menjadi DTM, kita bisa
menggunakan fungsi DocumentTermMatrix()
# Please type your code down below
<- DocumentTermMatrix(x = sms.corpus) sms.dtm
Hasil dari DTM dapat kita inspeksi dengan menggunakan fungsi
inspect()
# cek singkat struktur dtm
inspect(sms.dtm)
#> <<DocumentTermMatrix (documents: 5572, terms: 8004)>>
#> Non-/sparse entries: 43544/44554744
#> Sparsity : 100%
#> Maximal term length: 42
#> Weighting : term frequency (tf)
#> Sample :
#> Terms
#> Docs <#> call can come free get just know now will
#> 1085 0 0 0 1 0 1 0 0 0 12
#> 1579 18 0 0 0 0 0 0 0 0 0
#> 1863 0 0 0 0 0 0 0 1 0 0
#> 2158 0 0 0 0 0 0 0 0 0 0
#> 2370 0 0 0 0 1 0 0 0 0 0
#> 2380 1 0 1 0 0 0 0 0 0 0
#> 2434 6 0 3 0 1 1 0 0 0 0
#> 2848 0 0 0 0 0 0 0 0 0 0
#> 3016 2 0 0 0 0 0 0 0 0 0
#> 5105 0 0 0 0 1 0 0 0 0 0
Glossary:
documents
: Jumlah data SMSterms
: kata yang unique di seluruh SMS kitanon-sparse
: nilai yang bukan 0 pada matrixsparse
: nilai yang 0 pada matrixMari kita amati SMS ke 1085 yang sudah di-cleansing, kita dapat konfirmasi bahwa kata will muncul sebanyak 12 kali:
# Please run the code down below
1085]]$content sms.corpus[[
#> [1] "love start attract feel need everi time around first thing come thought start day end everi time dream love will everi breath name life happen around life will name cri will give happi take sorrow will readi fight anyon will love will craziest thing love will proov anyon girl beauti ladi whole planet will alway sing prais love will start make chicken curri end maki sambar life will beauti will get everi morn thank god day like say lot will tell later"
Split data menjadi sms_train
dan sms_test
dengan perbandingan 75-25.
RNGkind(sample.kind = "Rounding")
set.seed(100)
# Index Sampling
<- sample(nrow(sms.dtm), nrow(sms.dtm)*0.75)
index
# Implementasi Splitting ke Data Train
<- sms.dtm[index,]
sms_train
# Implementasi Splitting ke Data Test
<- sms.dtm[-index,] sms_test
Kita akan melakukan pemisahan antara target & prediktor, karena kita akan mencoba untuk membuat model Naive Bayes dengan cara kedua nantinya.
# label untuk train dan test, tersimpan pada dataframe sms_celan
# Label data train
<- sms_clean[index, "v1"]
label_train
# Label data test
<- sms_clean[-index, "v1"] label_test
Setelah melakukan tahapan cross validation, terdapat 2 tahapan lagi yang perlu kita lakukan. Tujuan dari tambahan proses pada data kita adalah untuk membuat model lebih mudah dalam mempelajari pola-pola yang terjadi.
Bisa kita lihat, jumlah prediktor yang kita miliki sangat banyak. Padahal tidak semua kata tersebut muncul di setiap SMS. Kita akan menguangi jumlah prediktor dengan mengambil kata-kata yang cukup sering muncul, misalnya muncul di setidaknya 20 sms.
Gunakan function findFreqTerms()
:
# Please type your code down below
<- findFreqTerms(x = sms_train, # object data train
sms_freq lowfreq = 20) # minimal frekuensi
length(sms_freq)
#> [1] 325
Note: Penentuan lowfreq = 20
tidak
mutlak dan dapat diubah-ubah untuk feature selection. Semakin
besar lowfreq
, semakin sedikit terms yang kita gunakan
sebagai prediktor.
Kita akan mengambil kata-kata pada sms_train
dan
sms_test
sesuai dengan kata-kata pada sms_freq
dengan melakukan subseting:
# Please type your code down below
# Data Train
<- sms_train[, sms_freq]
sms_train
# Data Test
<- sms_test[, sms_freq] sms_test
Nilai pada matrix sms_train
masih berupa frekuensi.
Untuk perhitungan peluang, frekuensi akan diubah menjadi muncul (1) atau
tidak (0).
Caranya dengan menggunakan Bernoulli Converter.
Kita bisa melakukan ini, karena Naive Bayes tidak memberikan bobot terhadap setiap prediktor yang ada. Selain itu, proses ini akan meringankan proses komputasi.
# Please run the code down below
<- function(x){
bernoulli_conv
<- as.factor(ifelse(x > 0, 1, 0))
x return(x)
}
Selanjutnya, terapkan bernoulli_conv
ke
sms_train
dan sms_test
:
Untuk menerapkan fungsi bernoulli_conv()
, kita akan
memanfaatkan fungsi apply()
, berikut beberapa parameter
pada fungsi apply()
X
= parameter ini untuk memberitahu object dataMARGIN
= parameter diperuntuhkan untuk memberitahu
fungsi bernoulli_conv()
akan di-implementasikan pada kolom
atau baris
MARGIN = 1
-> mengaplikasikan FUN by barisMARGIN = 2
-> mengaplikasikan FUN by kolom, karena
kita ingin tetap matrix berupa DocumentTermMatrix, gunakan parameter
ini.FUN
= Fungsi apa yang akan digunakan# Please type your code down below
# Data train
<- apply(X = sms_train,
sms_train_bn MARGIN = 2,
FUN = bernoulli_conv)
# Data test
<- apply(X = sms_test,
sms_test_bn MARGIN = 2,
FUN = bernoulli_conv)
Mari cek hasilnya:
15:25, 35:40] sms_train_bn[
#> Terms
#> Docs can cant car care cash chanc
#> 4239 "0" "0" "0" "0" "0" "0"
#> 3718 "0" "0" "0" "0" "0" "0"
#> 1137 "1" "0" "0" "0" "0" "0"
#> 1987 "0" "0" "0" "0" "0" "0"
#> 1997 "0" "0" "0" "0" "0" "0"
#> 3834 "0" "0" "0" "0" "0" "0"
#> 2975 "0" "0" "0" "0" "0" "0"
#> 3946 "0" "0" "0" "0" "0" "0"
#> 2988 "0" "0" "0" "0" "0" "0"
#> 4157 "0" "0" "0" "0" "0" "0"
#> 2331 "0" "0" "0" "0" "0" "0"
Pada proses modelling kali ini kita akan menggunakan metode kedua, yaitu dengan menggunakan metode berikut ini:
Menggunakan argumen naiveBayes(x, y)
x
: prediktor variabel dari data yang digunakany
: target variabel dari data yang digunakan# Please type your code down below
<- naiveBayes(x = sms_train_bn,
naive_spam y = label_train,
laplace = 1)
Setelah membuat model, mari kita coba lakukan prediksi.
# Please type your code down below
<- predict(naive_spam, sms_test_bn, "class")
sms_pred_class
head(sms_pred_class)
#> [1] ham spam ham ham ham ham
#> Levels: ham spam
# Please type your code down below
library(caret)
confusionMatrix(data = sms_pred_class,
reference = label_test,
positive = "spam")
#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction ham spam
#> ham 1204 23
#> spam 14 152
#>
#> Accuracy : 0.9734
#> 95% CI : (0.9636, 0.9812)
#> No Information Rate : 0.8744
#> P-Value [Acc > NIR] : <0.0000000000000002
#>
#> Kappa : 0.8764
#>
#> Mcnemar's Test P-Value : 0.1884
#>
#> Sensitivity : 0.8686
#> Specificity : 0.9885
#> Pos Pred Value : 0.9157
#> Neg Pred Value : 0.9813
#> Prevalence : 0.1256
#> Detection Rate : 0.1091
#> Detection Prevalence : 0.1192
#> Balanced Accuracy : 0.9285
#>
#> 'Positive' Class : spam
#>
Sebelum kita membahas tentang ROC & AUC, mari kita coba cek proporse dari kelas target kita:
# Please run the code down below
prop.table(table(sms_clean$v1))
#>
#> ham spam
#> 0.8659368 0.1340632
Jika kita coba lihat, dari jumlah proporsi target kita tidak balanced. Maka dari itu, ketika kita melihat nilai accuracy pada hasil confusion matrix, tidak dapat kita percaya.
Accuracy memiliki kekurangan untuk memperlihatkan kebaikan model dalam mengklasifikasi ke kedua kelas. Mengatasi kekurangan accuracy tersebut, hadir ROC dan AUC sebagai alat evaluasi selain Confusion Matrix.
Receiver-Operating Curve (ROC) adalah kurva yang menggambarkan nilai True Positive Rate dan False Positive Rate pada setiap kemungkinan threshold. Area Under Curve (AUC) adalah area di bawah kurva ROC.
Keduanya dapat menggambarkan kebaikan model dalam mengklasifikasikan kedua belah kelas.
Model yang baik:
# Please type your code down below
<- predict(object = naive_spam,
spam_prob newdata = sms_test_bn,
type = "raw")
head(spam_prob)
#> ham spam
#> [1,] 0.8828534310611811 0.1171465689
#> [2,] 0.0000000003686327 0.9999999996
#> [3,] 0.9997303432068110 0.0002696568
#> [4,] 0.9954532240518041 0.0045467759
#> [5,] 0.9400228395666025 0.0599771604
#> [6,] 0.9998947022166202 0.0001052978
Fungsi prediction()
akan melakukan transformasi hasil
probability menjadi sebuah bentuk prediksi yang dapat dibuatkan sebuah
kurva ROC AUC nantinya. Pada fungsi tersebut terdapat 2 parameter yang
akan digunakan, yaitu:
predictions
= untuk mengambil nilai predictions kelas
positif yang masih berbentuk probabilitylabels
= untuk mengubah target kelas positif menjadi 1
dan 0# Please type your code down below
library(ROCR)
<- prediction(predictions = spam_prob[,'spam'],
sms_roc labels = ifelse(label_test == 'spam', 1, 0))
Setelah berhasil membuat hasil prediksinya, kita akan menampilkannya dalam bentuk kurva. Untuk membuat kurva kita akan menggunakan syntax berikut ini:
plot(performance(prediction.obj = ..., measure = "tpr", x.measure = "fpr"))
# Please type your code down below
plot(performance(prediction.obj = sms_roc,
measure = "tpr",
x.measure = "fpr"))
Selain memanfaatkan bentuk visualisasi, kita dapat melihat nilai AUC
dengan menggunakan fungsi performace()
. Pada fungsi
tersebut, kita akan menggunakan 2 parameter berikut ini.
prediction.obj
= object yang menyimpan hasil prediksi
dari fungsi prediction()
measure
= untuk melihat hasil measurementnya, kita akan
isi dengan “auc”# Please type your code down below
<- performance(prediction.obj = sms_roc,
sms_auc measure = "auc")
@y.values sms_auc
#> [[1]]
#> [1] 0.97289
Insight: Model diindikasikan baik dan bisa memisahkan kelas positif dan negatif dengan baik. Model dapat mengklasifikasikan ke kelas positif maupun ke kelas negatif.
Decision Tree merupakan tree-based model yang cukup sederhana dengan performa yang robust/powerful untuk prediksi. Decision Tree menghasilkan visualisasi berupa pohon keputusan yang dapat diinterpretasi dengan mudah.
Note: Decision Tree tidak hanya terbatas pada kasus Classification, namun dapat digunakan pada kasus Regression. Pada course ini fokus kita ke kasus Classification karena idenya sama.
Mari memahami bagaimana struktur dari Decision Tree dan istilah yang sering digunakan. Berikut contoh untuk menentukan apakah weekend ini kita akan beraktifitas keluar atau tidak:
Pertanyaan selanjutnya, bagaimana Decision Tree memilih predictor pada setiap percabangannya?
Bagaimana rule terbentuk?
Decision Tree membuat rule dengan memilih predictor yang dapat membuat homogen target. Oleh karena itu pemilihannya berdasarkan,
Kelompok data yang diharapkan setelah dilakukan percabangan adalah kelompok yang memiliki entropy rendah.
Untuk memilih prediktor mana yang menjadi root node, dihitunglah perubahan entropy yaitu selisih antara entropy sebelum dan sesudah dilakukan percabangan menggunakan variable predictor.
Predictor yang dipilih adalah predictor yang menghasilkan penurunan entropy paling besar, berarti membuat data setelah pemisahan semakin homogen. Perubahan entropy inilah yang disebut Information Gain.
Prediktor yang dipilih pada setiap percabangan adalah predictor yang menghasilkan information gain terbesar.
Contoh:
Misalkan saya mencatat perilaku saya dalam menentukan apakah saya pergi makan ke restoran/tidak sebagai berikut:
<- read.csv("data_input/dineout.csv",
dine sep = ";",
stringsAsFactors = T)
dine
Budget
: Budget yang saya miliki (High atau Low)Distance
: Jarak restoran dari rumah saya (Far atau
Near)Friend
: Apakah ada teman yang menemani? (Available atau
Absent)Dine.Out
: Keputusan apakah makan di restoran atau tidak
(Yes atau No)Tujuan Decision Tree yaitu memisahkan data menjadi kelompok-kelompok kecil berdasarkan variable tertentu sehingga dihasilkan data yang homogen. Sehingga prosesnya:
Visualisasi Decision Tree:
<- read.csv("data_input/diabetes.csv",
diab stringsAsFactors = T)
head(diab)
Data Description:
pregnant
: Number of times pregnantglucose
: Plasma glucose concentration (glucose
tolerance test)pressure
: Diastolic blood pressure (mm Hg)triceps
: Triceps skin fold thickness (mm)insulin
: 2-Hour serum insulin (mu U/ml)mass
: Body mass index (weight in kg/(height in
m)^2)pedigree
: Diabetes pedigree functionage
: Age (years)diabetes
: Test for DiabetesTarget: diabetes
# proportion of class target
prop.table(table(diab$diabetes))
#>
#> neg pos
#> 0.6510417 0.3489583
Insight: Insight masih balance
Split data menjadi diab_train
dan diab_test
dengan proporsi 80:20
RNGkind(sample.kind = "Rounding")
set.seed(100)
<- sample(nrow(diab), nrow(diab) * 0.8)
index <- diab[index,]
diab_train <- diab[-index,] diab_test
Cek proporsi kelas di data train:
prop.table(table(diab_train$diabetes))
#>
#> neg pos
#> 0.6449511 0.3550489
disclaimer: pada kasus ini sebenarnya data masih cukup balance, namun mari kita coba pelajari cara balancing data.
Berikut code nya:
RNGkind(sample.kind = "Rounding")
set.seed(100)
# upsample
<- upSample(x = diab_train %>% select(-diabetes), # prediktor
diab_train_up y = diab_train$diabetes, # target
yname = "diabetes") # nama kolom target
# cek class balance
prop.table(table(diab_train_up$diabetes))
#>
#> neg pos
#> 0.5 0.5
Buat model untuk memprediksi diabetes menggunakan seluruh prediktor:
# library(partykit)
<- ctree(formula = diabetes ~ .,
model_diabetes data = diab_train_up)
# visualisasi decision tree
plot(model_diabetes, type = "simple")
# print rule
model_diabetes
#>
#> Model formula:
#> diabetes ~ pregnant + glucose + pressure + triceps + insulin +
#> mass + pedigree + age
#>
#> Fitted party:
#> [1] root
#> | [2] glucose <= 127
#> | | [3] pregnant <= 6
#> | | | [4] mass <= 28.8
#> | | | | [5] age <= 48: neg (n = 117, err = 0.9%)
#> | | | | [6] age > 48: neg (n = 9, err = 44.4%)
#> | | | [7] mass > 28.8
#> | | | | [8] pedigree <= 0.498
#> | | | | | [9] age <= 30
#> | | | | | | [10] triceps <= 12: neg (n = 16, err = 37.5%)
#> | | | | | | [11] triceps > 12: neg (n = 77, err = 6.5%)
#> | | | | | [12] age > 30: neg (n = 45, err = 44.4%)
#> | | | | [13] pedigree > 0.498: neg (n = 83, err = 45.8%)
#> | | [14] pregnant > 6
#> | | | [15] pedigree <= 0.84
#> | | | | [16] glucose <= 99: neg (n = 20, err = 10.0%)
#> | | | | [17] glucose > 99
#> | | | | | [18] age <= 41: pos (n = 29, err = 24.1%)
#> | | | | | [19] age > 41: neg (n = 23, err = 30.4%)
#> | | | [20] pedigree > 0.84: pos (n = 13, err = 0.0%)
#> | [21] glucose > 127
#> | | [22] glucose <= 157
#> | | | [23] mass <= 26.2: neg (n = 21, err = 14.3%)
#> | | | [24] mass > 26.2: pos (n = 180, err = 28.3%)
#> | | [25] glucose > 157: pos (n = 159, err = 8.2%)
#>
#> Number of inner nodes: 12
#> Number of terminal nodes: 13
note: tiap prediktor bisa digunakan pada cabang / node lebih dari 1 kali (digunakan pada beberapa cabang)
Interpretation:
Lakukan prediksi ke data test dan hasilkan label prediksinya!
predict()
"response"
= label kelas (default threshold 0.5)"prob"
= peluang ke kelas negatif dan positif# prediksi ke data train
<- predict(object = model_diabetes,
train_pred newdata = diab_train_up,
type = "response")
# prediksi ke data test
<- predict(object = model_diabetes,
test_pred newdata = diab_test,
type = "response")
# Confusion Matrix: data train
confusionMatrix(data = train_pred,
reference = diab_train_up$diabetes,
positive = "pos")
#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction neg pos
#> neg 325 86
#> pos 71 310
#>
#> Accuracy : 0.8018
#> 95% CI : (0.7723, 0.829)
#> No Information Rate : 0.5
#> P-Value [Acc > NIR] : <0.0000000000000002
#>
#> Kappa : 0.6035
#>
#> Mcnemar's Test P-Value : 0.2639
#>
#> Sensitivity : 0.7828
#> Specificity : 0.8207
#> Pos Pred Value : 0.8136
#> Neg Pred Value : 0.7908
#> Prevalence : 0.5000
#> Detection Rate : 0.3914
#> Detection Prevalence : 0.4811
#> Balanced Accuracy : 0.8018
#>
#> 'Positive' Class : pos
#>
# Confusion Matrix: data test
confusionMatrix(data = test_pred,
reference = diab_test$diabetes,
positive = "pos")
#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction neg pos
#> neg 75 22
#> pos 29 28
#>
#> Accuracy : 0.6688
#> 95% CI : (0.5885, 0.7425)
#> No Information Rate : 0.6753
#> P-Value [Acc > NIR] : 0.6054
#>
#> Kappa : 0.2713
#>
#> Mcnemar's Test P-Value : 0.4008
#>
#> Sensitivity : 0.5600
#> Specificity : 0.7212
#> Pos Pred Value : 0.4912
#> Neg Pred Value : 0.7732
#> Prevalence : 0.3247
#> Detection Rate : 0.1818
#> Detection Prevalence : 0.3701
#> Balanced Accuracy : 0.6406
#>
#> 'Positive' Class : pos
#>
Performa Metrics:
Apakah performa model konsisten di data train maupun data test? tidak konsisten, hanya bagus di train dan amat buruk di test.
Kondisi Model:
Decision Tree rentan overfitting karena bisa membuat rule terlalu kompleks/terlalu spesifik menyesuaikan data train nya. Model hanya menghafal bukan belajar pola. Sehingga ketika digunakan untuk data baru, rule nya tidak cocok dan prediksi menjadi buruk.
Untuk mengatasinya bisa gunakan tree prunning.
Decision Tree perlu tahu kapan berhenti membuat cabang sehingga pohon lebih sederhana. Pemotongan cabang disebut sebagai Pruning. Secara umum, terbagi atas 2 cara:
Parameter pruning pada function ctree()
:
Untuk prunning tree:
mincriterion
: nilainya diperbesarminsplit
: nilainya diperbesarminbucket
: nilainya diperbesarCoba lakukan prunning untuk mendapatkan performa model yang lebih baik!
# model tuning
<- ctree(formula = diabetes ~ .,
model_diabetes_tuned data = diab_train_up,
control = ctree_control(mincriterion = 0.97,
minsplit = 20,
minbucket = 10))
# prediksi ke data train
<- predict(object = model_diabetes_tuned,
train_pred_tuned newdata = diab_train_up,
type = "response")
# prediksi ke data test
<- predict(object = model_diabetes_tuned,
test_pred_tuned newdata = diab_test,
type = "response")
# Confusion Matrix: data train
confusionMatrix(data = train_pred_tuned,
reference = diab_train_up$diabetes,
positive = "pos")
#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction neg pos
#> neg 325 86
#> pos 71 310
#>
#> Accuracy : 0.8018
#> 95% CI : (0.7723, 0.829)
#> No Information Rate : 0.5
#> P-Value [Acc > NIR] : <0.0000000000000002
#>
#> Kappa : 0.6035
#>
#> Mcnemar's Test P-Value : 0.2639
#>
#> Sensitivity : 0.7828
#> Specificity : 0.8207
#> Pos Pred Value : 0.8136
#> Neg Pred Value : 0.7908
#> Prevalence : 0.5000
#> Detection Rate : 0.3914
#> Detection Prevalence : 0.4811
#> Balanced Accuracy : 0.8018
#>
#> 'Positive' Class : pos
#>
# Confusion Matrix: data test
confusionMatrix(data = test_pred_tuned,
reference = diab_test$diabetes,
positive = "pos")
#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction neg pos
#> neg 75 22
#> pos 29 28
#>
#> Accuracy : 0.6688
#> 95% CI : (0.5885, 0.7425)
#> No Information Rate : 0.6753
#> P-Value [Acc > NIR] : 0.6054
#>
#> Kappa : 0.2713
#>
#> Mcnemar's Test P-Value : 0.4008
#>
#> Sensitivity : 0.5600
#> Specificity : 0.7212
#> Pos Pred Value : 0.4912
#> Neg Pred Value : 0.7732
#> Prevalence : 0.3247
#> Detection Rate : 0.1818
#> Detection Prevalence : 0.3701
#> Balanced Accuracy : 0.6406
#>
#> 'Positive' Class : pos
#>
note: bila dari hasil tuning tree pruning tidak memperbaiki model: masih overfit, maka sebaiknya berpindah ke model lain.
Wisdom of the Crowd: the collective knowledge of a group of people as expressed through their aggregated actions or opinions, regarded as an alternative to specialist or expert knowledge.
Ensemble method adalah metode yang menggabungkan prediksi dari beberapa model machine learning menjadi 1 prediksi tunggal. Tujuannya adalah untuk meningkatkan performa model. Contoh model ensemble method adalah Random Forest.
Random Forest adalah salah satu jenis Ensemble Method yang terdiri dari banyak Decision Tree. Masing-masing Decision Tree memiliki karakteristik masing-masing dan tidak saling berhubungan. Random Forest memanfaatkan konsep Bagging (Bootstrap and Aggregation) dalam pembuatannya. Berikut adalah prosesnya:
mtry
untuk memilih
banyaknya calon prediktor secara random (Automatic Feature
Selection)Kelebihan Random Forest:
Business Question: Kita adalah seorang analis dari perusahaan teknologi yang mengeluarkan product wearable fitness gadgets. Gadget tersebut dapat memprediksi secara otomatis untuk memberikan warning apabila suatu gerakan belum dilakukan secara optimal.
Buatlah sebuah model random forest yang dapat mengklasifikasi kelima
label gerakan (classe
) sebagai berikut:
<- read.csv("data_input/fitbit.csv", stringsAsFactors = T)
fb dim(fb)
#> [1] 19622 158
head(fb)
Kekurangan dari Random Forest adalah membutuhkan waktu komputasi yang
cukup lama. Hal ini dapat diatasi dengan membuang predictor yang
variansinya mendekati nol (dianggap kurang informatif). Kita dapat
mengeceknya dengan function nearZeroVar()
dari package
caret
:
# Please type your code down below
library(caret)
<- nearZeroVar(fb) n0_var
Hasil dari penggunaan fungsi nearZeroVar()
adalah index
kolom dari dataframe dan dari index kolom tersebut, terindikasi memiliki
variansi yang sangat rendah (mendekati 0). Maka dari itu kita bisa
menghilangkan kolom-kolom tersebut.
# Please type your code down below
<- fb[, -n0_var]
fb length(n0_var)
#> [1] 101
Ternyata ada 101 kolom yang dianggap sebagai nearZeroVar, sehingga data kita yang semulanya 158 kolom hanya tersisa 57 kolom.
Splitting training dan testing dataset dengan proporsi 80:20.
RNGkind(sample.kind = "Rounding")
set.seed(100)
library(rsample)
<- sample(nrow(fb), nrow(fb)*0.8)
index_fb <- fb[index_fb,]
train_fb <- fb[-index_fb,] test_fb
# cek proporsi kelas target data train
prop.table(table(train_fb$classe))
#>
#> 1 2 3 4 5
#> 0.2852137 0.1918201 0.1746831 0.1628337 0.1854494
Membuat model Random Forest menggunakan fb_train
dengan
5-fold cross validation, kemudian proses tersebut diulang sebanyak 3
kali.
# JANGAN DI RUN!
# set.seed(417)
#
# ctrl <- trainControl(method = "repeatedcv",
# number = 5, # k-fold
# repeats = 3) # repetisi
#
# fb_forest_2 <- train(classe ~ .,
# data = fb_train,
# method = "rf", # random forest
# trControl = ctrl)
#
# saveRDS(fb_forest, "fb_forest_2.RDS") # simpan model
Ilustrasi untuk
trainControl(method = "repeatedcv", number = 5, repeats = 3)
dapat dilihat pada Google Sheets
Ilustrasi untuk
train()
di atas dapat dilihat pada Google Sheets
Salah satu kelemahan Random Forest adalah pembuatan model yang
membutuhkan waktu yang cukup lama. Practice yang baik saat selesai
melakukan training adalah menyimpan model tersebut ke dalam bentuk file
RDS dengan function saveRDS()
agar model dapat langsung
digunakan tanpa harus training dari awal.
# read model
<- readRDS("fb_forest.RDS")
fb_forest fb_forest
#> Random Forest
#>
#> 15697 samples
#> 56 predictor
#> 5 classes: '1', '2', '3', '4', '5'
#>
#> No pre-processing
#> Resampling: Cross-Validated (5 fold, repeated 3 times)
#> Summary of sample sizes: 12557, 12557, 12559, 12557, 12558, 12557, ...
#> Resampling results across tuning parameters:
#>
#> mtry Accuracy Kappa
#> 2 0.9957743 0.9946543
#> 31 0.9990020 0.9987376
#> 60 0.9980676 0.9975556
#>
#> Accuracy was used to select the optimal model using the largest value.
#> The final value used for the model was mtry = 31.
Pada summary model di atas, dilakukan beberapa kali percobaan
mtry
(jumlah predictor random yang digunakan saat splitting
node). Secara default akan dicoba sebanyak 3 nilai
mtry
, yaitu:
mtry = 2
mtry
sebanyak jumlah predictor (numerik +
dummy variable). Pada kasus ini, terdapat 55 predictor numerik dan 5
dummy variable dari predicyot kategorikalmtry
minimal dan maksimalModel yang dipilih adalah mtry = 31
dengan nilai
Accuracy tertinggi ketika diujikan ke data hasil bootstrap sampling
(atau data in-sample, bisa dianggap sebagai data train seperti pada
pembuatan model).
Pada tahap Bootstrap sampling, terdapat data yang tidak digunakan dalam pembuatan model, ini yang disebut sebagai data Out-of-Bag (OOB). Model Random Forest akan menggunakan data OOB sebagai data test untuk melakukan evaluasi dengan cara menghitung error. Error inilah yang disebut sebagai OOB Error. Dalam kasus klasifikasi, OOB error merupakan persentase data OOB yang misklasifikasi.
library(randomForest)
$finalModel fb_forest
#>
#> Call:
#> randomForest(x = x, y = y, mtry = param$mtry)
#> Type of random forest: classification
#> Number of trees: 500
#> No. of variables tried at each split: 31
#>
#> OOB estimate of error rate: 0.08%
#> Confusion matrix:
#> 1 2 3 4 5 class.error
#> 1 4470 0 0 0 0 0.0000000000
#> 2 2 3032 1 0 0 0.0009884679
#> 3 0 4 2731 0 0 0.0014625229
#> 4 0 0 3 2585 1 0.0015449981
#> 5 0 0 0 1 2867 0.0003486750
1 - 0.0008)*100 (
#> [1] 99.92
Nilai OOB Error pada model fb_forest
sebesar 0.08%.
Dengan kata lain, akurasi model pada data OOB adalah 99.92%!
Note: In practice, ketika membuat model Random Forest, kita tidak diwajibkan untuk splitting training-testing dataset di awal. Hal ini karena OOB sudah dapat mengestimasi performa model di unseen data.
<- predict(fb_forest, newdata = test_fb, type = "raw") #menghasilkan kelas
fb_test_pred head(fb_test_pred)
#> [1] 1 1 1 1 1 1
#> Levels: 1 2 3 4 5
confusionMatrix(data = fb_test_pred, reference = as.factor(test_fb$classe))
#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 1 2 3 4 5
#> 1 1103 0 0 0 0
#> 2 0 786 0 0 0
#> 3 0 0 680 0 0
#> 4 0 0 0 660 0
#> 5 0 0 0 0 696
#>
#> Overall Statistics
#>
#> Accuracy : 1
#> 95% CI : (0.9991, 1)
#> No Information Rate : 0.281
#> P-Value [Acc > NIR] : < 0.00000000000000022
#>
#> Kappa : 1
#>
#> Mcnemar's Test P-Value : NA
#>
#> Statistics by Class:
#>
#> Class: 1 Class: 2 Class: 3 Class: 4 Class: 5
#> Sensitivity 1.000 1.0000 1.0000 1.0000 1.0000
#> Specificity 1.000 1.0000 1.0000 1.0000 1.0000
#> Pos Pred Value 1.000 1.0000 1.0000 1.0000 1.0000
#> Neg Pred Value 1.000 1.0000 1.0000 1.0000 1.0000
#> Prevalence 0.281 0.2003 0.1732 0.1682 0.1773
#> Detection Rate 0.281 0.2003 0.1732 0.1682 0.1773
#> Detection Prevalence 0.281 0.2003 0.1732 0.1682 0.1773
#> Balanced Accuracy 1.000 1.0000 1.0000 1.0000 1.0000
Pada machine learning model, terdapat trade-off antara sisi interpretability dan performance. Performance Random Forest dapat diunggulkan dibandingkan model yang lain, namun tidak terlalu dapat diinterpretasi karena banyak faktor random yang terlibat. Namun setidaknya kita dapat melihat predictor apa saja yang paling penting dalam pembuatan Random Forest melalui variable importancenya:
varImp(fb_forest)
#> rf variable importance
#>
#> only 20 most important variables shown (out of 60)
#>
#> Overall
#> raw_timestamp_part_1 100.000
#> num_window 58.600
#> roll_belt 45.994
#> pitch_forearm 27.061
#> magnet_dumbbell_z 21.709
#> magnet_dumbbell_y 18.956
#> yaw_belt 16.727
#> pitch_belt 15.780
#> roll_forearm 14.340
#> accel_dumbbell_y 7.650
#> roll_dumbbell 6.667
#> accel_forearm_x 6.610
#> accel_belt_z 6.473
#> magnet_dumbbell_x 6.070
#> total_accel_dumbbell 5.936
#> accel_dumbbell_z 5.246
#> magnet_belt_y 5.211
#> magnet_belt_z 5.004
#> yaw_dumbbell 3.181
#> magnet_forearm_z 3.163
Nilai importance berasal dari perhitungan Gini Importance yang konsepnya sama dengan Information Gain, hanya berbeda rumus saja.
mtry
: banyaknya predictor yang dicoba untuk
splittingmtry
)mtry
: banyaknya predictor yang dicoba untuk
splittingmtry
)