knitr::opts_chunk$set(
message = FALSE,
warning = FALSE
)
Text Classification merupkan bagian dari Text Mining yaitu satu metode analisis data yang berfokus pada mengekstraksi informasi dan pola data dari data tidak terstruktur, khususnya data teks.
Dataset ini berisikan jenis dari teks email yaitu spam atau not spam. Dataset ini akan digunakan untuk mendeteksi dan mengklasifikasikan teks email tersebut temasuk kedalam katagori spam/not spam menggunakan machime learning. Algoritma yang digunakan adalah Long-Short Term Memory (LSTM).
Business Question: Berdasarkan konten email pada dataset, kami akan membuat model yang bisa mendeteksi atau mengklasifikasikan email spam dan not spam
# Data Wrangling
library(tidyverse)
library(rwhatsapp)
library(wordcloud)
# Text Preprocessing
library(textclean)
library(stringr)
library(tm)
# Cross Validation
library(rsample)
# Machine Learning
## LSTM
library(keras)
# Machine Learning Evaluation
library(caret)
email <- read.csv("datainput/email_spam.csv")
head(email)
glimpse(email)
## Rows: 84
## Columns: 3
## $ title <chr> "?? the secrets to SUCCESS", "?? You Earned 500 GCLoot Points", …
## $ text <chr> "Hi James,\n\nHave you claim your complimentary gift yet?\n\nI'v…
## $ type <chr> "spam", "not spam", "not spam", "not spam", "spam", "not spam", …
Dari dataset di atas, dataset ini memiliki 3 kolom, 84 baris, dan tipe data untuk setiap kolom.
colSums(is.na(email))
## title text type
## 0 0 0
Pada dataset diatas tidak ditemukan missing value.
email <- email%>%
mutate(type = as.factor(type))# change data type chr to factor
glimpse(email)
## Rows: 84
## Columns: 3
## $ title <chr> "?? the secrets to SUCCESS", "?? You Earned 500 GCLoot Points", …
## $ text <chr> "Hi James,\n\nHave you claim your complimentary gift yet?\n\nI'v…
## $ type <fct> spam, not spam, not spam, not spam, spam, not spam, not spam, sp…
table(email$type) %>%
prop.table()
##
## not spam spam
## 0.6904762 0.3095238
insight :
Proporsi kedua kelas tidak seimbang dan cukup signifikan perbedaanya dimana email ‘not spam’ lebih tinggi dari ‘spam’
Mari kita ambil tiga contoh teks email yang merupakan spam, lalu amati kata-kata mana yang dapat menjadi indikator (prediktor) bahwa sebuah teks adalah spam.
# your code here
email %>%
filter(type == "spam") %>%
pull(text) %>% # Mengambil kolom dan mengubah jadi bentuk vector
tail(2)
## [1] "Hey Prachanda,\n \nToday's video... To build your biz - \"It is a numbers game\"\n==> Listen in Here \n \nTo Your Success \n \nRyan Gunness \nMLM Recruit On Demand ==> \"Spoon Fed Marketing\"\n "
## [2] "We noticed a new login\nHello ondiekijohn254@gmail.com, we noticed an unusual login from a device or location you don't usually use.\nWas this you?"
Untuk lebih jelasnya dapat dilihat divisualisasi dibawah ini
spam <- subset(email, type == "spam")
wordcloud(spam$text, max.words = 20, scale = c(2.5, 1.25), colors = brewer.pal(8, "Dark2"),rot.per=0.5, random.order = FALSE)
Insight :
kata-kata terbanyak yang mengandung email spam contoh nya “click”, “see”,“even” dan “win”
notspam <- subset(email, type == "not spam")
wordcloud(notspam$text, max.words = 20, scale = c(2.5, 1.25), colors = brewer.pal(8, "Dark2"),rot.per=0.5, random.order = FALSE)
Insight :
kata-kata terbanyak yang mengandung email not spam contoh nya “account”, “email”, “company”, “this privacy” dan “please”
Sebelum membangun sebuah model, kita perlu membersihkan data text terlebih dahulu. Selanjutnya, text akan dikonversi ke dalam format ‘Corpus’ dan kemudian akan dibersihkan.
Corpus adalah kumpulan dari dokumen. Pada kasus ini, satu dokumen ekuivalen dengan satu observasi email. Di dalam satu email bisa terdapat satu kalimat atau lebih.
Salah satu package yang bisa kita gunakan untuk text mining adalah
tm. Pengubahan dari vector text menjadi corpus bisa
dilakukan menggunakan function VCorpus()
# ubah format menjadi corpus
email_corpus <- VCorpus(VectorSource(email$text))
Untuk selanjutnya mari kita inspect content email
ke-4:
email_corpus[[4]]$content
## [1] "Hello,\n \nThank you for contacting the Virtual Reward Center. We here at the Virtual Reward Center do not dictate what Rewards get sent out to recipients. Please contact your program sponsor, (the company from which you originally received your Reward) at helpdesk@cspace.com for this inquiry.\n \nWe apologize for any inconvenience.\n \n \nThank You,\n\nThe Virtual Reward Center\nCustomer Support Team Lead"
Tahapan selanjutnya melakukan text cleansing. Setiap kata disetiap corpus (email) akan dijadikan predictor/kolom sehingga perlu dibersihkan terlebih dahulu untuk komputasi yang lebih ringan.
tm_map() : menerapkan function yang ada didalamnya
untuk setiap corpus (email)transformer <- content_transformer(function(x, pattern){
gsub(pattern, " ", x)
})
email_corpus <- email_corpus %>%
tm_map(content_transformer(tolower)) %>%
tm_map(transformer, pattern = "(https?|ftp)://\\S+|www\\.\\S+") %>%
tm_map(removeNumbers) %>%
tm_map(removePunctuation) %>%
tm_map(function(x) { stemDocument(x, language="indonesian") }) %>%
tm_map(stripWhitespace)
email_corpus[[9]]$content
## [1] "logo imag senol yildirim someon signedin to your account when jul pm turkey time devic amazon shop app for android near manisa turkey if this was you you can disregard this messag otherwis pleas let us know"
Melakukan transformasi data text menjadi Document-Term Matrix
(DTM) melalu proses tokenization. Dengan fungsi
DocumentTermMatrix() untuk membuat DTM dan fungsi
inspect() untuk melihat hasil DTM.
# change to DTM
email_dtm <- email_corpus %>%
DocumentTermMatrix()
inspect(email_dtm)
## <<DocumentTermMatrix (documents: 84, terms: 2155)>>
## Non-/sparse entries: 5700/175320
## Sparsity : 97%
## Maximal term length: 145
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs and are for our that the this with you your
## 10 15 5 6 0 2 16 7 6 9 12
## 14 7 3 2 10 3 3 0 1 9 3
## 19 3 0 1 0 0 0 2 1 2 0
## 30 11 8 11 4 2 29 3 3 4 21
## 34 11 7 8 1 8 13 4 2 7 2
## 38 8 1 5 0 4 6 2 4 3 5
## 5 35 6 15 10 6 50 12 7 9 5
## 51 16 4 7 3 6 16 9 2 12 3
## 59 3 0 1 0 0 0 2 1 2 0
## 83 16 1 4 7 3 19 1 5 5 21
Dengan melihat kata yang muncul setidaknya minimal 5 email, kita bisa mendapatkan kandidat prediktor yang paling berpengaruh sehingga kita bisa menghemat waktu untuk training model kita.
# email_train dari text.dtm
email_freq <- findFreqTerms(email_dtm, lowfreq = 5)
email_dtm <- email_dtm[,email_freq]
Tokenizer bertujuan untuk memisahkan setiap kata di seluruh dokumen ke dalam bentuk token. Parameter num_words berfungsi untuk mengatur jumlah maksimum kata yang akan digunakan, diurutkan berdasarkan urutan frekuensi terbesar. kata-kata yang jarang muncul akan dihilangkan.
teks_clean <- email
teks_clean$text_clean <- NA
for (i in seq_along(email_corpus)) {
teks_clean$text_clean[i] <- as.character(email_corpus[[i]])
}
teks_clean %>%
select(text, text_clean) %>%
head(3)
num_words <- 1024
tokenizer <- text_tokenizer(num_words = num_words, lower = T) %>%
fit_text_tokenizer(teks_clean$text_clean)
paste("Number of unique words is ", length(tokenizer$word_counts))
## [1] "Number of unique words is 2253"
Dari total 2253 kata unik yang terdapat pada data teks, kita kurangi menjadi 1024 yang akan digunakan untuk membuat model.
Kemudian, kita melakukan cross validation dari data teks_clean dentan proporsi 75% data train dan 25% data test
set.seed(100)
index <- sample(nrow(teks_clean), nrow(teks_clean)*0.75)
data_train <- teks_clean[index,]
data_test <- teks_clean[-index,]
Setelah berhasil memisahkan datanya menjadi 2 data-train dan data_test, mari implementasikan hasil temuan pada bagian tokenisasi di atas dan penjelasan lanjutan mengenai kenapa kita harus mengetahui panjang maksimal dari kata pada sebuat kalimat akan dilanjutkan di bawah ini.
maxlen <- max(str_count(teks_clean$text_clean, "\\w+")) + 1
paste("Maximum length words in data:", maxlen)
## [1] "Maximum length words in data: 1047"
# prepare x (prediktor)
data_train_x <- texts_to_sequences(tokenizer, data_train$text_clean) %>%
pad_sequences(maxlen = maxlen, padding = "pre", truncating = "post")
data_test_x <- texts_to_sequences(tokenizer, data_test$text_clean) %>%
pad_sequences(maxlen = maxlen, padding = "pre", truncating = "post")
# prepare y (target)
data_train$type <- as.factor(ifelse(data_train$type == 'spam', 1, 0))
data_test$type <- as.factor(ifelse(data_test$type == 'spam', 1, 0))
data_train_y <- to_categorical(data_train$type)
data_test_y <- to_categorical(data_test$type)
Fungsi texts_to_sequence() ialah membuat matriks hasil transformasi text ke bentuk urutan angka (integer). Setelah itu diwrap dengan fungsi pad_sequences(), fungsi tersebut harus digunakan karena panjang teks bisa berbeda, dengan menggunakan fungsi pad_sequences() untuk memastikan semua teks memiliki panjang yang sama dengan memasukkan nilai 0 jika teks terlalu pendek.
Dalam membuat model LSTM, kita akan meminjam library(keras) dari Tensorflow Python. Dari library tersebut, kita akan menggunakan fungsi keras_model_sequential() untuk membangun arstitektur model LSTM. Secara umum arsitektur pada setiap model Neural Network setidaknya harus memiliki 3 komponen berikut ini, Input Layer, Hidden Layer dan Output Layer. Selain dari 3 komponen yang sudah disebutkan, sebenarnya kita bisa menambahkan beberapa layer lagi sesuai dengan kebutuhan, seperti layer dropout yang akan saya gunakan.
Layer Pertama: Input Layer
Pada layer ini nantinya akan diisi dengan kata-kata yang ada pada setiap berita. Pada lapisan ini juga akan mempelajari pengaruh dari posisi atau kedekatan anatara satu kata dengan kata yang lainnya.
Layer Kedua: Dropout Layer
Layer dropout adalah teknik regulasi yang acak menonaktifkan sebagian unit atau neuron selama pelatihan. Tujuannya adalah untuk mengurangi overfitting dan meningkatkan generalisasi model pada data baru. Dengan menerapkan dropout pada layer tertentu, kita secara acak menonaktifkan sebagian unit atau neuron selama pelatihan, dengan probabilitas tertentu (disebut tingkat dropout). Ini berarti beberapa koneksi antara neuron akan terputus pada setiap iterasi pelatihan. Proses dropout mengharuskan model belajar tanpa terlalu bergantung pada setiap fitur atau hubungan yang spesifik dari data pelatihan, sehingga meningkatkan kemampuan model untuk beradaptasi dengan data yang tidak dilihat sebelumnya.
Layer Ketiga: Hidden Layer
Layer ketiga adalah hidden layer dan pada layer ini kita bisa menambahkan berbagai macam layer, akan tetapi layer yang akan kita tambahkan di sini adalah LSTM layer karena model yang kita ingin buat adalah model LSTM. Pada layer ini nantinya setiap input atau infromasi yang berasal dari embedding layer akan proses lebih lanjut lagi.
Layer Keempat: Output Layer
Layer keempat adalah output layer atau sering juga disebut sebagai dense layer. Pada layer ini nantinya kita bisa mengatur berapa banyak kelas yang ingin dihasilkan, kelas di sini akan kita samakan dengan target variabel yang ingin kita klasifikasikan.
model_lstm <- keras_model_sequential(name = "model_nn") %>%
# layer input
layer_embedding(
name = "input",
input_dim = num_words,
input_length = maxlen,
output_dim = 256,
embeddings_initializer = initializer_random_uniform(minval = -0.05, maxval = 0.05, seed = 2)
) %>%
# layer dropout
layer_dropout(
name = "embedding_dropout",
rate = 0.5
) %>%
# layer lstm
layer_lstm(
name = "lstm",
units = 256,
dropout = 0.2,
recurrent_dropout = 0.2,
return_sequences = FALSE,
recurrent_initializer = initializer_random_uniform(minval = -0.05, maxval = 0.05, seed = 2),
kernel_initializer = initializer_random_uniform(minval = -0.05, maxval = 0.05, seed = 2)
) %>%
# layer output
layer_dense(
name = "output",
units = 2,
activation = "softmax",
kernel_initializer = initializer_random_uniform(minval = -0.05, maxval = 0.05, seed = 2)
)
Setelah berhasil membangun arsitektur, langkah terakhir yang harus dilakukan adalah menambahkan beberapa parameter berikut ini, loss function, optimizer & metrics. Penambahan ketiga parameter tersebut biasanya disebut sebagai compilation/model compile.
model_lstm %>% compile(
optimizer = optimizer_adam(learning_rate = 0.01),
metrics = "accuracy",
loss = "categorical_crossentropy"
)
Sekarang kita sudah bisa memasuki tahapan training model. Kita dapat menggunakan fungsi fit(), nantinya dalam fungsi tersebut ada dua parameter yang harus kita perhatikan, yaitu:
epoch: parameter ini nantinya akan mengatur banyaknya iterasi pengulangan pada saat pelatihan model. Penentuan banyaknya iterasi juga tidak memiliki aturan khusus, akan tetapi semakin banyak epoch maka error bisa lebih kecil, namun proses training semakin lama dan rentan overfitting.
batch_size: parameter ini nantinya akan mengatur banyak jumlah sampel yang dilatih pada tiap iterasi/epoch. Sama seperti epoch, penentuan jumlah batch size juga tidak memiliki aturan khusus, akan tetapi semakin sedikit batch size maka proses training semakin lama (karena proses optimasi model semakin banyak); namun bisa mencegah komputasi terlalu besar di 1 waktu sekaligus.
history <- model_lstm %>%
fit(data_train_x,
data_train_y,
batch_size = 256,
epochs = 10,
verbose = 1)
## Epoch 1/10
##
## 1/1 [==============================] - ETA: 0s - loss: 0.6933 - accuracy: 0.4762
## 1/1 [==============================] - 43s 43s/step - loss: 0.6933 - accuracy: 0.4762
## Epoch 2/10
##
## 1/1 [==============================] - ETA: 0s - loss: 0.6554 - accuracy: 0.7302
## 1/1 [==============================] - 46s 46s/step - loss: 0.6554 - accuracy: 0.7302
## Epoch 3/10
##
## 1/1 [==============================] - ETA: 0s - loss: 1.8090 - accuracy: 0.3016
## 1/1 [==============================] - 46s 46s/step - loss: 1.8090 - accuracy: 0.3016
## Epoch 4/10
##
## 1/1 [==============================] - ETA: 0s - loss: 1.0521 - accuracy: 0.7302
## 1/1 [==============================] - 55s 55s/step - loss: 1.0521 - accuracy: 0.7302
## Epoch 5/10
##
## 1/1 [==============================] - ETA: 0s - loss: 0.5842 - accuracy: 0.8254
## 1/1 [==============================] - 59s 59s/step - loss: 0.5842 - accuracy: 0.8254
## Epoch 6/10
##
## 1/1 [==============================] - ETA: 0s - loss: 0.5133 - accuracy: 0.8254
## 1/1 [==============================] - 60s 60s/step - loss: 0.5133 - accuracy: 0.8254
## Epoch 7/10
##
## 1/1 [==============================] - ETA: 0s - loss: 0.3863 - accuracy: 0.8095
## 1/1 [==============================] - 72s 72s/step - loss: 0.3863 - accuracy: 0.8095
## Epoch 8/10
##
## 1/1 [==============================] - ETA: 0s - loss: 0.3164 - accuracy: 0.9683
## 1/1 [==============================] - 63s 63s/step - loss: 0.3164 - accuracy: 0.9683
## Epoch 9/10
##
## 1/1 [==============================] - ETA: 0s - loss: 0.1702 - accuracy: 0.9841
## 1/1 [==============================] - 68s 68s/step - loss: 0.1702 - accuracy: 0.9841
## Epoch 10/10
##
## 1/1 [==============================] - ETA: 0s - loss: 0.4069 - accuracy: 0.8889
## 1/1 [==============================] - 62s 62s/step - loss: 0.4069 - accuracy: 0.8889
plot(history)
predictions_train <- model_lstm %>% predict(data_train_x)
## 2/2 - 12s - 12s/epoch - 6s/step
predicted_classes <- apply(predictions_train, 1, which.max)
confusionMatrix(
factor(predicted_classes, labels = c("spam", "not spam")),
factor(data_train$type, labels = c("spam", "not spam"))
)
## Confusion Matrix and Statistics
##
## Reference
## Prediction spam not spam
## spam 43 0
## not spam 1 19
##
## Accuracy : 0.9841
## 95% CI : (0.9147, 0.9996)
## No Information Rate : 0.6984
## P-Value [Acc > NIR] : 4.26e-09
##
## Kappa : 0.9629
##
## Mcnemar's Test P-Value : 1
##
## Sensitivity : 0.9773
## Specificity : 1.0000
## Pos Pred Value : 1.0000
## Neg Pred Value : 0.9500
## Prevalence : 0.6984
## Detection Rate : 0.6825
## Detection Prevalence : 0.6825
## Balanced Accuracy : 0.9886
##
## 'Positive' Class : spam
##
Insights :
Berdasarkan hasil matrix klasifikasi data training diatas , didapatkan hasil prediksi tepat untuk email “spam” sebanyak 44 dan prediksi tepat untuk email “not spam” sebanyak 18.
hasil prediksi salah untuk email “spam” sebanyak 0 dan prediksi salah untuk email “not spam” sebanyak 1.
Hasil ‘Acccuracy’ yg didapat dari model LSTM untuk data train sebesar 98,4%, ‘recall’ sebesar 100%, ‘precision’ sebesar 97% , dan specificity sebesar 94%.
predictions_test <- model_lstm %>% predict(data_test_x)
## 1/1 - 5s - 5s/epoch - 5s/step
predicted_classes <- apply(predictions_test, 1, which.max)
confusionMatrix(
factor(predicted_classes, labels = c("spam", "not spam")),
factor(data_test$type, labels = c("spam", "not spam"))
)
## Confusion Matrix and Statistics
##
## Reference
## Prediction spam not spam
## spam 14 3
## not spam 0 4
##
## Accuracy : 0.8571
## 95% CI : (0.6366, 0.9695)
## No Information Rate : 0.6667
## P-Value [Acc > NIR] : 0.04616
##
## Kappa : 0.64
##
## Mcnemar's Test P-Value : 0.24821
##
## Sensitivity : 1.0000
## Specificity : 0.5714
## Pos Pred Value : 0.8235
## Neg Pred Value : 1.0000
## Prevalence : 0.6667
## Detection Rate : 0.6667
## Detection Prevalence : 0.8095
## Balanced Accuracy : 0.7857
##
## 'Positive' Class : spam
##
Insights :
Berdasarkan hasil matrix klasifikasi data test diatas , didapatkan hasil prediksi tepat untuk email “spam” sebanyak 14 dan prediksi tepat untuk email “not spam” sebanyak 4.
hasil prediksi salah untuk email “spam” sebanyak 4 dan prediksi salah untuk email “not spam” sebanyak 0.
Hasil ‘Acccuracy’ yg didapat dari model LSTM untukdat test sebesar 85%, ‘recall’ sebesar 100%, ‘precision’ sebesar 82% , dan specificity sebesar 57%.
Tujuan dari project ini adalah mendeteksi email spam dengan cara mengklasifikasikan email apakah termasuk spam atau not spam.
Dari model yang sudah dibuat dapat ditarik sebuah kesimpulan bahwa model dengan menggunakan algoritma LSTM (Long-Short Term Memory) baik dalam mengklasifikasikan email spam dan not spam. Hasil dari Confusion Matrix and Statistics untuk data test dan train tidak terlalu jauh. Artinya baik data train maupun data test sama-sama memiliki performa yang baik.
Matrix yang digunakan untuk kasus ini adalah recall/sensitivity dengan positive class adalah email spam yaitu bagaimana model dapat memprediksi email spam secara tepat.
Model LSTM dari data test menghasilkan ‘Acccuracy’ yg didapat dari model LSTM data train sebesar 98,4% dan data test 85%, ‘recall’ data train dan data test sebesar 100%, ‘precision’ data train dan data test yaitu sebesar 97% dan 82%, dan specificity sebesar 94% untuk data train dan 57% untuk data test.
Project ini bisa kembangkan lebih lanjut dan memiliki potensial business seperti klasifikasi phishing email dan safe email, klasifikasi positive review customer dan negative review customer serta sentiment analysis.