Tentang Artikel

Artikel ini merupakan salah satu bagian dari supervised machine learning workflow yang akan diterapkan pada dashboard “AKSATA”; Sebuah dashboard yang didesain untuk melakukan Social Network Analysis dan Sentiment Analysis. Artikel ini akan membahas langkah-langkah menyiapkan model text classification, yang mana merupakan dasar dari sentiment analysis.
Pada umumnya mungkin kita sering melihat berbagai report sentiment analysis yang mengklasifikasi setiap kalimat/frasa berdasarkan kategori: (1) Positif, (2) Netral, (3) Negatif.

Secara khusus untuk project AKSATA, saya ingin memberi label kategori tersebut berdasarkan aktor yang paling sering memenuhi suatu topik di jejaring sosial Twitter, yakni: (1) Personal, (2) Buzzer, (3) Media.

Tujuan dari pembedaan diatas adalah, saya ingin agar pengguna dashboard dapat diyakinkan, bahwa suatu trending topic tidak selalu tercipta atas respon kolektif para pengguna twitter. Dapat saja suatu topik menjadi trending karena aktivitas yang terorganisir (buzzer).

Tentang Dataset

Dataset saya dapatkan dari API Twitter, dengan beberapa query yang didominasi oleh Buzzer seperti #Wadas_melawan, #Lurah_ham, #PecatYaqut, dsb; beberapa query yang didominasi oleh Personal seperti “quweenjojo”, “Pak Marsani”, dsb; dan beberapa tweet dari akun media seperti CNN Indonesia, Tribun (daerah), Tempo.

Workflow 1: To Wordcloud, Exploratory Data Analysis

Memuat Data

aksata_data <- readRDS("for_model.rds")
str(aksata_data)
#> Classes 'tbl_df', 'tbl' and 'data.frame':    15000 obs. of  2 variables:
#>  $ full_text: chr  "CS: Pastiian HPnya masih ada pulsa ya Pak.\n\nOrang kaya beneran<U+2705>:\nTenang mba, pascabayar kok\n\nOrang "| __truncated__ "Mau hp lo iPhone berlapis emas juga kalau gak ada pulsa ya gak bisa masuk kode OTP. Bener kata costumer service"| __truncated__ "@jaydiristui @DRespati3 \"you can buy stuff, but you cannot buy class\" -aming" "Tp rumah lo jelek bgt bang.<U+0001F44C> https://t.co/7jxXCwBgOy https://t.co/ZBX7jg4CAK" ...
#>  $ Label    : chr  "Personal" "Personal" "Personal" "Personal" ...

Mengganti Label “Personal”, “Media”, “Buzzer” dengan angka

library(dplyr)

# Normalize
aksata_enc <- aksata_data %>% 
  mutate(Label = as.factor(Label)) %>% 
  mutate(Label = as.integer(Label)-1)


aksata_enc[10001,]
aksata_enc[1,]
  • Kelas nomor 1: Media

  • Kelas nomor 2: Personal

  • Kelas nomor 0: Buzzer

Load Library

library(tidyverse)
library(katadasaR)
library(tm)
library(stringr)

Subsetting + Cleansing tweet Buzzer

# Menghapus pattern mention

buzzer <- aksata_enc %>%
  group_by(Label) %>%
  filter(Label == 0) %>%
  mutate(full_text = str_replace_all(
    string = full_text,
    pattern = "^@[A-Za-z]+|.+ @[A-Za-z]+",
    replacement = ""
  )) %>%
    mutate(full_text = str_replace_all(
    string = full_text,
    pattern = "@([^@ ]+){5,}",
    replacement = ""
  )) %>% 
    mutate(full_text = str_replace_all(
    string = full_text,
    pattern = "^([^@]*@){5,}[^@]*$",
    replacement = "")) 
chr_buzzer <- as.character(buzzer$full_text)

Data preprocessing untuk wordcloud

library(rtweet)
library(textclean)

chr_buzzer <- replace_emoji(chr_buzzer) 
chr_buzzer <- gsub( ","," ", chr_buzzer)
chr_buzzer <- gsub( "/n"," ", chr_buzzer)
chr_buzzer <- gsub( "#wadasmelawan"," ", chr_buzzer)
chr_buzzer <- gsub( "#usutwadas"," ", chr_buzzer)
chr_buzzer <- replace_hash(chr_buzzer)
chr_buzzer <- replace_html(chr_buzzer, symbol = FALSE)
# chr_buzzer <- replace_incomplete(chr_buzzer, replacement = "")
chr_buzzer <- replace_number(chr_buzzer, remove = TRUE)
chr_buzzer <- replace_url(chr_buzzer, replacement = "")
chr_buzzer <- tolower(chr_buzzer)
chr_buzzer <- removePunctuation(chr_buzzer)
#later: replace internet slang

chr_buzzer[100:110]
#>  [1] " akhir nya kebobrokan rezim terkuak  "                                                                                                                                    
#>  [2] "  "                                                                                                                                                                       
#>  [3] " mah kan "                                                                                                                                                                
#>  [4] " sikap muhammadiyah layak diacungi jempol  peka terhadap permasalahan sosial   kemanusiaan  "                                                                             
#>  [5] "  "                                                                                                                                                                       
#>  [6] "menurut catatan greenpeace indonesia  penambangan batu andesit di wadas ini akan mengancam ekosistem alam di desa wadas   "                                               
#>  [7] "wadas  adalah bukti kongkrit  betapa pemimpin pencitraan tidak melulu memikirkan rakyat mereka hanya berbusa saat kampanye  tapi tak menganggap rakyat setelah berkuasa  "
#>  [8] "selamat pagi gaesss  lebih percaya mana nih  leterangan warga atau keterangan penguasa    "                                                                               
#>  [9] "semakin aneh    "                                                                                                                                                         
#> [10] "musang berbulu ayam   "                                                                                                                                                   
#> [11] "sebelum daerah   lain akan seperti desa wadas maka bersatulah dan  dan kawan   mari support  "

Cleansing membuat banyak tweet bernilai NA. mencari alternatif lain

buzzer2 <- aksata_enc %>%
  group_by(Label) %>%
  filter(Label == 0) 

Raw data “Buzzer”

tes <- as.character(buzzer2$full_text)

# tes <- textclean::replace_number(tes, remove = TRUE)

# tes <- str_squish(tes)

str(tes)
#>  chr [1:5000] "Catet...<U+0001F60C>\n\n#UsutWadas \n#UsutWadas\n\nhttps://t.co/tCcxb3qEi1" ...
tes[1:10]
#>  [1] "Catet...<U+0001F60C>\n\n#UsutWadas \n#UsutWadas\n\nhttps://t.co/tCcxb3qEi1"                                                                                                                                                                                                                                       
#>  [2] "Tolong permintaan warga @Wadas_Melawan mengenai pencabutan IPL pertambangannya dilaksanakan ya,pak.. @ganjarpranowo \n\n#Wadas\n#FaktaWadas #UsutWadas # https://t.co/N7O3HjilOB"                                                                                                                                 
#>  [3] "Proyek oligarki meminjam tangan penguasaan, telah merampas hak rakyat. BPN 'menjadi martir' Proyek Pembangunan Waduk Bener .\n#UsutWadas  \n#UsutWadas\nhttps://t.co/m2kbWnYPw6"                                                                                                                                  
#>  [4] "Kau siapkan karpet merah para pemburu rente yang bergentayangan. Kau mudahkan dan kau buka lebar semua jalan, bahkan hak hidup pekerja pun kau abaikan.\n#UsutWadas \n#UsutWadas https://t.co/UYcQzjz6NW"                                                                                                         
#>  [5] "Ketakutan yg kau tebarkan, intimidasi yg kau hujamkan pada setiap mereka yg berseberangan menjadi berbalik bak senjata makan tuan. Kekesalan,kesakitan, rasa diremehkan, disepelekan,dilupakan dan dihinakan, menggumpal-mengkristal meneguhkan satu kata LAWAN!\n#UsutWadas \n#UsutWadas https://t.co/trkFBNybS2"
#>  [6] "Ya Allah...\n\n#UsutWadas \n#UsutWadas https://t.co/hONX6MMTQG"                                                                                                                                                                                                                                                   
#>  [7] "Ada bau busuk..\n\n#UsutWadas \n#UsutWadas https://t.co/zZSI0xwPJS"                                                                                                                                                                                                                                               
#>  [8] "Fadli Zon: Sebenarnya pembangunan ini untuk siapa.?\n\n#UsutWadas \n#UsutWadas https://t.co/L3AVRXfoam"                                                                                                                                                                                                           
#>  [9] "Ansor Banser menuntut 4 hal kepada presiden Joko Widodo dan gubernur Jawa Tengah Ganjar Pranowo untuk Warga Wadas Purworejo.\n@AlissaWahid\n@GUSDURians\n\nHidup NU !!!\nHidup Rakyat Indonesia !!!\nHidup Warga Wadas !!!\n\n#SaveWadas\n#WadasMelawan\n#UsutWadas\n#WadasMeradang https://t.co/Ft0BjQMYCW"      
#> [10] "Hanya Terjadi Di 62..\nRakyat Pemilik Sah Atas Tanah Diusir Dengan Dengan Cara Cara Yang Tidak Berprikemanusiaan \n\n#UsutWadas\n#UsutWadas https://t.co/ZeJZWCatIo"

Cleansing: Buzzer

library(rtweet)
tes <- replace_emoji(tes) # replace emoji
tes <- replace_hash(tes) # replace hashtags
tes <- replace_html(tes, symbol = FALSE) # replace HTML Markups
tes <- replace_number(tes, remove = TRUE) # replace number
tes <- replace_url(tes, replacement = " ") # replace all http tag

tes <- gsub( ":"," ", tes)
tes <- gsub( "\n"," ", tes)
tes <- gsub( "^@[A-Za-z]+|.+ @[A-Za-z]+" ," ", tes)
tes <- gsub( "@([^@ ]+){5,}" ," ", tes)
tes <- gsub( "^([^@]*@){5,}[^@]*$" ," ", tes)
tes <- removePunctuation(tes)

tes <- gsub( "relieved face"," ", tes)
tes <- gsub( "winking face with tongue"," ", tes)
tes <- gsub( "face"," ", tes)
tes <- gsub( "rt"," ", tes)
tes <- gsub( "oii"," ", tes)
tes <- gsub( "wadas"," ", tes)

tes <- tolower(tes)

tes[1:10]
#>  [1] "catet      "                                                                                                                                                                                                                                                
#>  [2] "       "                                                                                                                                                                                                                                                    
#>  [3] "proyek oligarki meminjam tangan penguasaan telah merampas hak rakyat bpn menjadi ma ir proyek pembangunan waduk bener     "                                                                                                                                 
#>  [4] "kau siapkan karpet merah para pemburu rente yang bergentayangan kau mudahkan dan kau buka lebar semua jalan bahkan hak hidup pekerja pun kau abaikan    "                                                                                                   
#>  [5] "ketakutan yg kau tebarkan intimidasi yg kau hujamkan pada setiap mereka yg berseberangan menjadi berbalik bak senjata makan tuan kekesalankesakitan rasa diremehkan disepelekandilupakan dan dihinakan menggumpalmengkristal meneguhkan satu kata lawan    "
#>  [6] "ya allah    "                                                                                                                                                                                                                                               
#>  [7] "ada bau busuk    "                                                                                                                                                                                                                                          
#>  [8] "fadli zon  sebenarnya pembangunan ini untuk siapa    "                                                                                                                                                                                                      
#>  [9] "  hidup nu  hidup rakyat indonesia  hidup warga wadas       "                                                                                                                                                                                               
#> [10] "hanya terjadi di 62 rakyat pemilik sah atas tanah diusir dengan dengan cara cara yang tidak berprikemanusiaan    "

Stopwords with tokenizer::tokenize_words()

Selanjutnya, proses cleansing saya lanjutkan dengan menghilangkan stopwords. Untuk diketahui juga bahwa operasi ini mengubah bentuk data menjadi list.

#Removing Stopwords, untuk menghilangkan kata-kata yang tidak diperlukan karena kemunculannya sangat sering (kata hubung, sambung, dsb)

library(stopwords)
library(tokenizers)

unfaedah_words <- readLines("stopwords_new.txt")
# buzzer_list <- as.list(chr)
to_cloud_buzzer <- tokenize_words(tes, stopwords = unfaedah_words)
to_cloud_buzzer[1:10]
#> [[1]]
#> [1] "catet"
#> 
#> [[2]]
#> [1] NA
#> 
#> [[3]]
#>  [1] "proyek"      "oligarki"    "meminjam"    "tangan"      "penguasaan" 
#>  [6] "merampas"    "hak"         "rakyat"      "bpn"         "ma"         
#> [11] "ir"          "proyek"      "pembangunan" "waduk"       "bener"      
#> 
#> [[4]]
#>  [1] "kau"            "siapkan"        "karpet"         "merah"         
#>  [5] "pemburu"        "rente"          "bergentayangan" "kau"           
#>  [9] "mudahkan"       "kau"            "buka"           "lebar"         
#> [13] "jalan"          "hak"            "hidup"          "pekerja"       
#> [17] "kau"            "abaikan"       
#> 
#> [[5]]
#>  [1] "ketakutan"             "kau"                   "tebarkan"             
#>  [4] "intimidasi"            "kau"                   "hujamkan"             
#>  [7] "berseberangan"         "berbalik"              "senjata"              
#> [10] "makan"                 "tuan"                  "kekesalankesakitan"   
#> [13] "diremehkan"            "disepelekandilupakan"  "dihinakan"            
#> [16] "menggumpalmengkristal" "meneguhkan"            "lawan"                
#> 
#> [[6]]
#> [1] "ya"    "allah"
#> 
#> [[7]]
#> [1] "bau"   "busuk"
#> 
#> [[8]]
#> [1] "fadli"       "zon"         "pembangunan"
#> 
#> [[9]]
#> [1] "hidup"     "nu"        "hidup"     "rakyat"    "indonesia" "hidup"    
#> [7] "warga"     "wadas"    
#> 
#> [[10]]
#> [1] "62"                "rakyat"            "pemilik"          
#> [4] "sah"               "tanah"             "diusir"           
#> [7] "tidak"             "berprikemanusiaan"

Final Wordcloud: Buzzer

library(wordcloud)

sudah_chr <- as.character(to_cloud_buzzer)
set.seed(100)
cloud_buzzer <- wordcloud(sudah_chr)

cloud_buzzer
#> NULL

Wordlcoud Buzzer: Analisis

Berdasarkan wordcloud, kita dapat melihat bahwa Buzzer lebih banyak menggaungkan hal-hal yang bersifat politik, pemerintahan, sara, dengan narasi-narasi negatif terhadap suatu topik. Kita nantinya dapat melihat cara kerja model dengan berpedoman pada hasil visualisasi wordcloud ini.

Menyiapkan data untuk training model (1):

disimpan pada object buzzer_clean

tes_list <- list(
  text_all = tes,
  label = buzzer$Label
)

buzzer_clean <- as_tibble(tes_list) %>% 
  drop_na()

tail(buzzer_clean,5)

Mengambil data dengan tweet “Personal Statement”

personal <- aksata_enc %>%
  group_by(Label) %>%
  filter(Label == 2)   

cleansing_personal <- as.character(personal$full_text)
cleansing_personal[1:10]
#>  [1] "CS: Pastiian HPnya masih ada pulsa ya Pak.\n\nOrang kaya beneran<U+2705>:\nTenang mba, pascabayar kok\n\nOrang kaya jadi2an<U+26D4><U+FE0F>:\n*ngamuk di twitter sambil pamer harta* https://t.co/U46xI8jnQV"                                                                                      
#>  [2] "Mau hp lo iPhone berlapis emas juga kalau gak ada pulsa ya gak bisa masuk kode OTP. Bener kata costumer service, emang itu sistemnya, dia gak sedang merendahkan lo sebagai orang kaya yg punya mobil harga stengah M. https://t.co/59jWGtvwRb"                                                    
#>  [3] "@jaydiristui @DRespati3 \"you can buy stuff, but you cannot buy class\" -aming"                                                                                                                                                                                                                    
#>  [4] "Tp rumah lo jelek bgt bang.<U+0001F44C> https://t.co/7jxXCwBgOy https://t.co/ZBX7jg4CAK"                                                                                                                                                                                                           
#>  [5] "@afrkml @DRespati3 Bacain replyannya ners, dia ga seneng cara penyampainnya. Mungkin harapan dia gini kali ya.\n\"Yang mulia baginda nasabah kami? Sudikah kiranya yang mulia baginda memastikan dlm handphone iphonenya yang keluaran terbaru paling mahal terdapat pulsa?\""                     
#>  [6] "@DRespati3 Jangan2 mobil yg setengah M juga jarang dibawa kemari menghindari biar gak tersinggunh <U+0001F62A><U+0001F62A><U+0001F62A> https://t.co/LKgP0184Dk"                                                                                                                                    
#>  [7] "@DRespati3 10 things money can’t buy:\n1. Manners\n2. Morals\n3. Respect\n4. Character\n5. Common sense\n6. Trust\n7. Patience\n8. Class\n9. Integrity\n10. Love"                                                                                                                                  
#>  [8] "@Annezui1 @DRespati3 Kelumpuhan yang dialaminya sejak menderita penyakit meningitis hidrosefalus, menimbulkan nyeri yang luar biasa. Tapi Devi tidak menyerah.<U+2063>\n<U+2063>\n#orangbaik, mari bantu berikan semangat untuk Devi dan berdonasi <U+0001F64F>\nhttps://t.co/iucxwGrurX"          
#>  [9] "@DRespati3 Gua pikir Anda sedang bercanda, rupanya emg benar2 tersinggung. Kok bs tersinggung ya? Itu kan komunikasi yg penting utk mengonfirmasi ada pulsa atau tidak karena prosedur urus m-banking memang pakai pulsa utk SMS. Gak ada hubungannya sama IPHONE apalagi agenda pamermu itu!!! <U+0001F629>"
#> [10] "@DRespati3 It’s just joke https://t.co/CyoiZ0Ss8w"
cleansing_personal <- replace_emoji(cleansing_personal) # replace emoji
cleansing_personal <- replace_hash(cleansing_personal) # replace hashtags
cleansing_personal <- replace_html(cleansing_personal, symbol = FALSE) # replace HTML Markups
cleansing_personal <- replace_number(cleansing_personal, remove = TRUE) # replace number
cleansing_personal <- replace_url(cleansing_personal, replacement = " ") # replace all http tag

cleansing_personal <- gsub( ":"," ", cleansing_personal)
cleansing_personal <- gsub( "\n"," ", cleansing_personal)
cleansing_personal <- gsub( "^@[A-Za-z]+|.+ @[A-Za-z]+" ," ", cleansing_personal)
cleansing_personal <- gsub( "@([^@ ]+){5,}" ," ", cleansing_personal)
cleansing_personal <- gsub( "^([^@]*@){5,}[^@]*$" ," ", cleansing_personal)
cleansing_personal <- removePunctuation(cleansing_personal)

# cleansing2 <- gsub( "relieved face"," ", cleansing2)
# cleansing2 <- gsub( "winking face with tongue"," ", cleansing2)
# cleansing2 <- gsub( "face"," ", cleansing2)
# cleansing2 <- gsub( "rt"," ", cleansing2)
# cleansing2 <- gsub( "oii"," ", cleansing2)
cleansing_personal <- tolower(cleansing_personal)
cleansing_personal <- gsub( "pak marsani"," ", cleansing_personal)
cleansing_personal <- gsub( "steno ricardo"," ", cleansing_personal)
cleansing_personal <- gsub( "mawar afi"," ", cleansing_personal)
cleansing_personal <- gsub( "3"," ", cleansing_personal)
cleansing_personal[1:10]
#>  [1] "cs  pastiian hpnya masih ada pulsa ya pak orang kaya beneran white heavy check mark   tenang mba pascabayar kok orang kaya jadi2an no entry      ngamuk di twitter sambil pamer harta  "                                                                                                    
#>  [2] "mau hp lo iphone berlapis emas juga kalau gak ada pulsa ya gak bisa masuk kode otp bener kata costumer service emang itu sistemnya dia gak sedang merendahkan lo sebagai orang kaya yg punya mobil harga stengah m  "                                                                       
#>  [3] "   you can buy stuff but you cannot buy class aming"                                                                                                                                                                                                                                        
#>  [4] "tp rumah lo jelek bgt bang ok hand    "                                                                                                                                                                                                                                                     
#>  [5] "   bacain replyannya ners dia ga seneng cara penyampainnya mungkin harapan dia gini kali ya yang mulia baginda nasabah kami sudikah kiranya yang mulia baginda memastikan dlm handphone iphonenya yang keluaran terbaru paling mahal terdapat pulsa"                                        
#>  [6] "   jangan2 mobil yg setengah m juga jarang dibawa kemari menghindari biar gak tersinggunh sleepy face sleepy face sleepy face  "                                                                                                                                                            
#>  [7] "    things money can   t buy  1 manners 2 morals   respect 4 character 5 common sense 6 trust 7 patience 8 class 9 integrity 10 love"                                                                                                                                                       
#>  [8] "   kelumpuhan yang dialaminya sejak menderita penyakit meningitis hidrosefalus menimbulkan nyeri yang luar biasa tapi devi tidak menyerah         mari bantu berikan semangat untuk devi dan berdonasi folded hands  "                                                                      
#>  [9] "   gua pikir anda sedang bercanda rupanya emg benar2 tersinggung kok bs tersinggung ya itu kan komunikasi yg penting utk mengonfirmasi ada pulsa atau tidak karena prosedur urus mbanking memang pakai pulsa utk sms gak ada hubungannya sama iphone apalagi agenda pamermu itu weary face "
#> [10] "   it   s just joke  "

Final Wordcloud: Personal

to_cloud_personal <- tokenize_words(cleansing_personal, stopwords = unfaedah_words)

sudah_chr_pers <- as.character(to_cloud_personal)
set.seed(100)

cloud_personal <- wordcloud(sudah_chr_pers)

cloud_personal
#> NULL

Menyiapkan Data untuk Training model (2):

Disimpan pada object personal_clean

personal_list <- list(
  text_all = cleansing_personal,
  label = personal$Label
)

personal_clean <- as_tibble(personal_list) %>% 
  drop_na()

tail(personal_clean, 5)

Mengambil data dari tweet “Media”

media <- aksata_enc %>%
  group_by(Label) %>%
  filter(Label == 1)   

cleansing_media <- as.character(media$full_text)
cleansing_media[1]
#> [1] "Akustik Lagu Sunda Dalam Rangka Menghibur Anak Yatim, Kolaborasi antara ... https://t.co/BdFhd1Pu9F via @YouTube"
cleansing_media <- replace_emoji(cleansing_media) # replace emoji
cleansing_media <- replace_hash(cleansing_media) # replace hashtags
cleansing_media <- replace_html(cleansing_media, symbol = FALSE) # replace HTML Markups
cleansing_media <- replace_number(cleansing_media, remove = TRUE) # replace number
cleansing_media <- replace_url(cleansing_media, replacement = " ") # replace all http tag

cleansing_media <- gsub( ":"," ", cleansing_media)
cleansing_media <- gsub( "\n"," ", cleansing_media)
# cleansing3 <- gsub( "^@[A-Za-z]+|.+ @[A-Za-z]+" ," ", cleansing3)
# cleansing3 <- gsub( "@([^@ ]+){5,}" ," ", cleansing3)
# cleansing3 <- gsub( "^([^@]*@){5,}[^@]*$" ," ", cleansing3)
cleansing_media <- removePunctuation(cleansing_media)

# cleansing2 <- gsub( "relieved face"," ", cleansing2)
# cleansing2 <- gsub( "winking face with tongue"," ", cleansing2)
# cleansing2 <- gsub( "face"," ", cleansing2)
# cleansing2 <- gsub( "rt"," ", cleansing2)
# cleansing2 <- gsub( "oii"," ", cleansing2)
cleansing_media <- tolower(cleansing_media)
# cleansing3 <- gsub( "pak marsani"," ", cleansing3)
cleansing_media <- gsub( "viral"," ", cleansing_media)
cleansing_media <- gsub( "via youtube"," ", cleansing_media)
cleansing_media <- gsub( "intipseleb"," ", cleansing_media)
cleansing_media[2050:2060]
#>  [1] "kpk gelontorkan nyaris rp1 miliar untuk sms blast ingatkan setor lhkpn  "                                                                                                                              
#>  [2] "airlangga blakblakan soal jht dan jkp  "                                                                                                                                                               
#>  [3] "tim konsesi motogp adalah tim pabrikan yang kurang berprestasi atau gagal meraih podium sepanjang satu musim apa saja keuntungan yang diperoleh tim konsesi simak selengkapnya di edisi terbaru       "
#>  [4] "foto  susah dapat jodoh partai komunis china jadi mak comblang  "                                                                                                                                      
#>  [5] "tahanan kasus narkoba kabur dari rutan sampang  "                                                                                                                                                      
#>  [6] "ratusan aparat diterjunkan amankan bentrok di tengah hutan maluku  "                                                                                                                                   
#>  [7] "menkes  indonesia berada di puncak kasus kematian covid19  "                                                                                                                                           
#>  [8] "happy salma hingga kamila andini mejeng di berlinale  "                                                                                                                                                
#>  [9] "wfo ppkm level  dilonggarkan jadi  persen  "                                                                                                                                                           
#> [10] "sejarah saudi ramaikan valentine berawal dari dekrit pangeran mbs  "                                                                                                                                   
#> [11] "tes motogp mandalika  alex marquez ubah  kali setelan motor  "

Final Wordcloud: Media

to_media_personal <- tokenize_words(cleansing_media, stopwords = unfaedah_words)

sudah_chr_media <- as.character(to_media_personal)

set.seed(100)

media_personal <- wordcloud(sudah_chr_media)

media_personal
#> NULL

Menyiapkan Data untuk Training model (2):

Disimpan pada object media_clean

media_list <- list(
  text_all = cleansing_media,
  label = media$Label
)

media_clean <- as_tibble(media_list) 

tail(media_clean, 5)

Mengumpulkan Cleaned Dataset

train_ready <- rbind(buzzer_clean, personal_clean, media_clean) %>% 
  drop_na()

Menyimpan data ke file internal

# saveRDS(train_ready, file = "datatrain_clean.rds")

Training Model

Model 1: LSTM (Long Short Term Memory) using Tensorflow

data <- read_rds("datatrain_clean.rds")
head(data,10)

Menghilangkan missing values pada dataset

# library(dplyr)
data <- data %>%
   na.omit() %>%
   mutate(label = as.factor(label))
glimpse(data)
#> Rows: 14,977
#> Columns: 2
#> $ text_all <chr> "catet      ", "       ", "proyek oligarki meminjam tangan pe~
#> $ label    <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0~

Inisialisasi conda env dan package keras

library(keras)
use_condaenv("r-tensorflow329")

Menyiapkan Tokenizer

num_words <- 2000 # proporsi sekitar 7 persen dari total unique words, mengikuti contoh di algotech

# prepare for tokenizers
tokenize <- keras::text_tokenizer(num_words = num_words,
                             lower = TRUE) %>%
   keras::fit_text_tokenizer(as.character(data$text_all))

paste("number of unique words:", length(tokenize$word_counts))
#> [1] "number of unique words: 24992"
tokenize$word_index[100:150]
#> $setelah
#> [1] 100
#> 
#> $ibu
#> [1] 101
#> 
#> $para
#> [1] 102
#> 
#> $lain
#> [1] 103
#> 
#> $rumah
#> [1] 104
#> 
#> $sendiri
#> [1] 105
#> 
#> $hanya
#> [1] 106
#> 
#> $soal
#> [1] 107
#> 
#> $nya
#> [1] 108
#> 
#> $hukum
#> [1] 109
#> 
#> $tau
#> [1] 110
#> 
#> $index
#> [1] 111
#> 
#> $pointing
#> [1] 112
#> 
#> $adalah
#> [1] 113
#> 
#> $jd
#> [1] 114
#> 
#> $bagi
#> [1] 115
#> 
#> $bakal
#> [1] 116
#> 
#> $jalan
#> [1] 117
#> 
#> $sekarang
#> [1] 118
#> 
#> $o
#> [1] 119
#> 
#> $suka
#> [1] 120
#> 
#> $desa
#> [1] 121
#> 
#> $emang
#> [1] 122
#> 
#> $malah
#> [1] 123
#> 
#> $pas
#> [1] 124
#> 
#> $lalu
#> [1] 125
#> 
#> $kerja
#> [1] 126
#> 
#> $grinning
#> [1] 127
#> 
#> $minyak
#> [1] 128
#> 
#> $secara
#> [1] 129
#> 
#> $bgt
#> [1] 130
#> 
#> $proyek
#> [1] 131
#> 
#> $kalau
#> [1] 132
#> 
#> $bener
#> [1] 133
#> 
#> $jokowi
#> [1] 134
#> 
#> $presiden
#> [1] 135
#> 
#> $masa
#> [1] 136
#> 
#> $oleh
#> [1] 137
#> 
#> $tears
#> [1] 138
#> 
#> $hands
#> [1] 139
#> 
#> $joy
#> [1] 140
#> 
#> $pake
#> [1] 141
#> 
#> $soeha
#> [1] 142
#> 
#> $anjing
#> [1] 143
#> 
#> $tanah
#> [1] 144
#> 
#> $perlu
#> [1] 145
#> 
#> $backhand
#> [1] 146
#> 
#> $semoga
#> [1] 147
#> 
#> $allah
#> [1] 148
#> 
#> $suara
#> [1] 149
#> 
#> $rp
#> [1] 150

Menyiapkan data untuk Cross Validation

to_split <- data %>% 
   mutate(label = as.numeric(label),
          label = label - 1)
head(to_split) 
set.seed(100)
rows <- sample(nrow(to_split))

split <- to_split[rows, ]

split[200:210,2]

Data sudah teracak, dilihat melalui label yang sudah tidak berurutan.

Memeriksa proporsi target variabel

table(split$label) %>% 
   prop.table()
#> 
#>         0         1         2 
#> 0.3338452 0.3338452 0.3323095

Cross Validation

library(rsample)

set.seed(100)
intrain <- initial_split(data = to_split, prop = 0.8, strata = "label")

data_train <- training(intrain)
data_val <- testing(intrain)

Inisialisasi panjang karakter maksimal dalam tweet

library(stringr)
maxlen <- max(str_count(to_split$text_all, "\\w+")) + 1 
paste("maximum length of words in data:", maxlen)
#> [1] "maximum length of words in data: 105"

Menyiapkan target dan prediktor

# prepare x

data_train_x <- texts_to_sequences(tokenize, data_train$text_all) %>%
  pad_sequences(maxlen = maxlen)

data_val_x <- texts_to_sequences(tokenize, data_val$text_all) %>%
  pad_sequences(maxlen = maxlen)


# prepare y
data_train_y <- to_categorical(data_train$label, num_classes = 3)#, num_classes = 3)
data_val_y <- to_categorical(data_val$label, num_classes = 3)

Inisialisasi keras_model_sequential

model <- keras_model_sequential()

Arsitektur Model LSTM

embedding –> dropout –> LSTM –> dense

tensorflow::tf$random$set_seed(100)
# model
model %>%
  # layer input
  layer_embedding(
    name = "input",
    input_dim = num_words,
    input_length = maxlen,
    output_dim = 32, 
    embeddings_initializer = initializer_random_uniform(minval = -0.05, maxval = 0.05, seed = 2)
  ) %>%
  # layer dropout
  layer_dropout(
    name = "embedding_dropout",
    rate = 0.3
  ) %>%
  # layer lstm 1
  layer_lstm(
    name = "lstm1",
    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 = 3,
    activation = "softmax", 
    kernel_initializer = initializer_random_uniform(minval = -0.05, maxval = 0.05, seed = 2)
  )

Compiling Model

# compile the model
model %>% compile(
  optimizer = "adam",
  metrics = "accuracy",
  loss = "categorical_crossentropy"
)

# model summary
summary(model)
#> Model: "sequential"
#> ________________________________________________________________________________
#> Layer (type)                        Output Shape                    Param #     
#> ================================================================================
#> input (Embedding)                   (None, 105, 32)                 64000       
#> ________________________________________________________________________________
#> embedding_dropout (Dropout)         (None, 105, 32)                 0           
#> ________________________________________________________________________________
#> lstm1 (LSTM)                        (None, 256)                     295936      
#> ________________________________________________________________________________
#> output (Dense)                      (None, 3)                       771         
#> ================================================================================
#> Total params: 360,707
#> Trainable params: 360,707
#> Non-trainable params: 0
#> ________________________________________________________________________________

Model Fitting

Dengan epoch = 10 dan batch_size = 512

epochs <- 10
batch_size <- 512

# fit the model
history <- model %>% fit(
  data_train_x, data_train_y,
  batch_size = batch_size, 
  epochs = epochs,
  verbose = 1,
  validation_data = list(
    data_val_x, data_val_y
  )
)

# history plot
plot(history)

Predict data validation

# predict on train
options(scipen = 123)
data_train_pred <- model %>%
  predict_classes(data_train_x) %>%
  as.vector()

# predict on val
data_val_pred <- model %>%
  predict_classes(data_val_x) %>%
  as.vector()

Model Evaluation: LSTM with Tensorflow

# accuracy on data train
library(yardstick)
accuracy_vec(
 truth = factor(data_train$label,labels = c("buzzer", "media", "personal")),
 estimate = factor(data_train_pred, labels = c("buzzer", "media", "personal"))
)
#> [1] 0.940823

Akurasi pada data validation: 89% (acuan overfitting: lebih dari 5 persen)

# accuracy on data validation
accuracy_vec(
 truth = factor(data_val$label,labels = c("buzzer", "media", "personal")),
 estimate = factor(data_val_pred, labels = c("buzzer", "media", "personal"))
)
#> [1] 0.8931909

Sensitivity data validation: 89%

# sensitivity on data validation
sensitivity_vec(
 truth = factor(data_val$label,labels = c("buzzer", "media", "personal")),
 estimate = factor(data_val_pred, labels = c("buzzer", "media", "personal"))
)
#> [1] 0.893162

Specificity data validation: 94%

specificity_vec(
  truth = factor(data_val$label, labels = c("buzzer", "media", "personal")),
  estimate = factor(data_val_pred, labels = c("buzzer", "media", "personal"))
)
#> [1] 0.9465965

Post-pred value data validation: 90%

# precision on data validation

ppv_vec(
   truth = factor(data_val$label, labels = c("buzzer", "media", "personal")),
   estimate = factor(data_val_pred, labels = c("buzzer", "media", "personal"))
)
#> [1] 0.8933404

Menguji Performa Model dengan data baru dari Twitter

Saya memiliki sebuah data twitter search query yang didominasi oleh personal statement. Saya ingin melihat bagaimana mesin dapat memprediksi bahwa teks-teks tersebut ditulis oleh perseorangan.

test <- read_rds("geprek bensu-AKSATA.rds")
test_5000 <- test %>%
  select(full_text) %>% 
  mutate(label = NA) %>%
  distinct() %>% 
  head(5000)

nrow(test_5000)
#> [1] 1776

Model 1: Predict on Datatest

data_test_x <- texts_to_sequences(tokenize, test_5000$full_text) %>%
  pad_sequences(maxlen = maxlen)
# predict on data test
data_test_pred <- model %>%
  predict_classes(data_test_x) %>%
  as.vector()


# create submission data

submission <- test_5000 %>% 
  mutate(label = data_test_pred)


inspect_buzzer_pred <- submission %>% 
  filter(label == 0)

inspect_personal_pred <- submission %>% 
  filter(label == 2)

inspect_media_pred <- submission %>% 
  filter(label == 1)


inspect_media_pred
# to_submit <- ifelse(submission$bully == 1, "yes", "no")
# 
# submission <- datatest %>% 
#   mutate(bully = to_submit)
inspect_buzzer_pred
inspect_personal_pred

Pada Model 1 (Long Short Term Memmory menggunakan Tensorflow), kita dapat melihat bahwa dari total entri data test sebanyak 1776 tweets, model dapat memprediksi 1153 tweets murni sebagai personal statement. Sedangkan, berbicara mengenai dataset, kita dapat melihat bahwa test_5000 tidak sepenuhnya diisi oleh tweet perseorangan. Ada beberapa tweet yang betul-betul dikemukakan oleh Media, misalnya.

Sedangkan untuk class buzzer pada kasus ini, Mesin mengalami bias klasifikasi karena kelas buzzer pada data train lebih banyak mengandung kalimat bernada negatif. Sehingga ketika terdapat tweet perseorangan namun bernada negatif pula (pada data test), mesin mempelajarinya sebagai buzzer.