Word2Vec Wikipedia Bahasa Indonesia dengan Python Gensim

Update: posting tentang Glove (dalam beberapa task Glove lebih baik kinerjanya)

Sebelumnya saya sudah mengaplikasikan word2vec untuk wikipedia Bahasa Indonesia dengan lib DeepLearning4J.  Sekarang saya mau mencoba dengan  Python Gensim   untuk eksperimen NER yang saya lakukan sebelumnya dan nanti untuk task-task lain.

Download versi terakhir data Wikipedia Bahasa Indonesia di https://dumps.wikimedia.org/idwiki/latest/. Cari yang bernama “idwiki-latest-pages-articles.xml.bz2” Ternyata setelah dua tahun, sudah naik ukurannya dari 300anMB ke 450MB.

Data wiki tersebut berbentuk XML, untungnya, Gensim sudah menyediakan  fasilitas untuk mentrain data wikipedia 🙂 Tidak perlu lagi repot-repot melakukan praproses. Berikut code-nya (diambil dari http://textminingonline.com/training-word2vec-model-on-english-wikipedia-by-gensim dengan sedikit modifikasi, soalnya error saat saya coba)

from __future__ import print_function

import logging
import os.path
import sys

from gensim.corpora import WikiCorpus

program = os.path.basename(sys.argv[0])
logger = logging.getLogger(program)

logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s')
logging.root.setLevel(level=logging.INFO)
logger.info("running %s" % ' '.join(sys.argv))

namaFileInput = "idwiki-latest-pages-articles.xml.bz2"
namaFileOutput = "wiki.id.case.text"

space = " "
i = 0

output = open(namaFileOutput, 'w')

# lower=False: huruf kecil dan besar dibedakan
wiki = WikiCorpus(namaFileInput, lemmatize=False, dictionary={}, lower=False)
for text in wiki.get_texts():
    output.write(' '.join(text) + '\n')
    i = i + 1
    if i % 10000 == 0:
        logger.info("Saved " + str(i) + " articles")

output.close()
logger.info("Finished Saved " + str(i) + " articles")

Hasil dari program di atas adalah satu file besar yang berisi gabungan dari seluruh artikel. Satu artikel menjadi satu baris.

Setelah itu pembuatan model:

import multiprocessing
import logging
import os.path
import sys
import multiprocessing
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence

program = os.path.basename(sys.argv[0])
logger = logging.getLogger(program)

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s')
logging.root.setLevel(level=logging.INFO)
logger.info("running %s" % ' '.join(sys.argv))

namaFileInput = "wiki.id.case.text"
namaFileOutput = "w2vec_wiki_id_case"

model = Word2Vec(LineSentence(namaFileInput), size=400, window=5, min_count=5, workers=multiprocessing.cpu_count())

# trim unneeded model memory = use (much) less RAM
model.init_sims(replace=True)
model.save(namaFileOutput)

Total praproses dan training hanya memerlukan waktu sekitar 20 menit. Model berbentuk 3 file dengan ukuran total 3GB.

Berikut contoh penggunaannya, saya lihat sekilas lebih bagus daripada model yang saya buat sebelumnya.

namaFileModel = "w2vec_wiki_id_case"
model = gensim.models.Word2Vec.load(namaFileModel)
hasil = model.most_similar("Bandung")
print("Bandung:{}".format(hasil))

hasil = model.most_similar("tempo")
print("tempo:{}".format(hasil))
hasil = model.most_similar("Tempo")
print("Tempo:{}".format(hasil))
hasil = model.most_similar("Soekarno")
print("Soekarno:{}".format(hasil))

sim = model.similarity("bakso", "nasi")
print("Kedekatan bakso-nasi: {}".format(sim))
sim = model.similarity("bakso", "pecel")
print("Kedekatan bakso-pecel: {}".format(sim))
sim = model.similarity("bakso", "mobil")
print("Kedekatan bakso-mobil: {}".format(sim))

hasil = model.most_similar_cosmul(positive=['perempuan', 'raja'], negative=['pria'])
print("pria-raja, perempuan-?: {}".format(hasil))

hasil = model.most_similar_cosmul(positive=['perempuan', 'raja'], negative=['lelaki'])
print("lelaki-raja, perempuan-?:{}".format(hasil))

hasil = model.most_similar_cosmul(positive=['minuman', 'mangga'], negative=['buah'])
print("buah-mangga, minuman-?:{}".format(hasil))

Hasilnya seperti ini:

Bandung:[('Bogor', 0.7126705646514893), ('Cimahi', 0.6414915323257446), ('Malang', 0.6207837462425232), ('Tasikmalaya', 0.613646924495697), ('Sukabumi', 0.6094871163368225), ('Cianjur', 0.5948120355606079), ('Purwakarta', 0.5937869548797607), ('Surabaya', 0.5675963759422302), ('Semarang', 0.5648865699768066), ('Garut', 0.5606454014778137)]
tempo:[('temponya', 0.5657634735107422), ('ketukan', 0.5246042609214783), ('irama', 0.4841456115245819), ('bertempo', 0.48334914445877075), ('beat', 0.4792878329753876), ('hitungan', 0.4732634723186493), ('nadanya', 0.4489333927631378), ('durasi', 0.4449218511581421), ('melodi', 0.44300010800361633), ('birama', 0.4420506954193115)]
Tempo:[('Gatra', 0.6947425603866577), ('Republika', 0.6929212212562561), ('TEMPO', 0.6797916293144226), ('KOMPAS', 0.6778919100761414), ('DeTik', 0.6597755551338196), ('Doeloe', 0.6481266617774963), ('Interaktif', 0.6416338682174683), ('Tabloid', 0.6330038905143738), ('Sindo', 0.6202648282051086), ('Galamedia', 0.6029475927352905)]
Soekarno:[('Sukarno', 0.813288152217865), ('Soeharto', 0.7391608953475952), ('Megawati', 0.6650642156600952), ('Suharto', 0.6611993908882141), ('Hatta', 0.6327983736991882), ('SBY', 0.6301325559616089), ('Bung', 0.6262293457984924), ('Jokowi', 0.6140671968460083), ('Yudhoyono', 0.5906702876091003), ('Karno', 0.5696855187416077)]
Kedekatan bakso-nasi: 0.7018489840251805
Kedekatan bakso-pecel: 0.8051109496938957
Kedekatan bakso-mobil: 0.23248957716849333
pria-raja, perempuan-?: [('Firaun', 0.8730435967445374), ('Ahas', 0.8643887639045715), ('Yerobeam', 0.8635646104812622), ('menantu', 0.8627196550369263), ('Uruk', 0.8598164319992065), ('Ahasyweros', 0.8586885929107666), ('kerajaan', 0.8582508563995361), ('Yoas', 0.8566167950630188), ('penguasa', 0.8554568290710449), ('rajanya', 0.8552286624908447)]
lelaki-raja, perempuan-?:[('ratu', 0.8525097966194153), ('kerajaan', 0.8269591927528381), ('Firaun', 0.8241982460021973), ('kaisar', 0.8102306723594666), ('firaun', 0.8079080581665039), ('penguasa', 0.8032354116439819), ('Asyur', 0.7912278771400452), ('rajanya', 0.7909599542617798), ('kerajaannya', 0.7896854281425476), ('Salomo', 0.787830650806427)]
buah-mangga, minuman-?:[('Minuman', 0.9949955344200134), ('yoghurt', 0.9938222169876099), ('bir', 0.953018069267273), ('beralkohol', 0.9518982768058777), ('penganan', 0.9509921073913574), ('jahe', 0.9492940306663513), ('camilan', 0.9480324983596802), ('kola', 0.9406602382659912), ('yogurt', 0.9402189254760742), ('soju', 0.9373763203620911)]

Menariknya, Jakarta tidak masuk ke 10 kata yang terdekat dengan Bandung. Tempo dan tempo memperlihatkan pentingnya membedakan huruf besar dan kecil. Kata “bakso” paling dekat dengan “pecel” dibandingkan dengan “nasi” apalagi “mobil”. Untuk analogi klasik man-king;woman-?, ternyata penggunaan kata “pria” dan “lelaki” menghasilkan hasil berbeda.

Iklan

NER (Named Entity Recognition) dengan anaGo (Python Keras)

Update: demo NER, http://nlp.yuliadi.pro

Update 6 Juni 2018: Anago mengupdate versi packagenya dan tidak compatible dengan versi sebelumnya.  Jika ingin sesuai posting ini,  install dengan versi lama: pip3 install anago==0.0.5.   Saya belum eksplorasi versi anago yang terakhir.

Update: paper yang saya+istri buat tentang ini

Sebelumnya saya sudah membahas NER Bahasa Indonesia dengan Stanford NER.  Kelemahan dari StanfordNER adalah lisensinya  dan menggunakan Java.  Posting ini akan membahas NER dengan Python-Keras dengan library anaGo (https://github.com/Hironsan/anago)  yang menggunakan teknik biLSTM+CRF dan berlisensi MIT yang lebih longgar.  Lib NLTK walaupun menyediakan NER untuk Bahasa Inggris, tetapi tidak menyediakan cara yang mudah untuk men-training bahasa lain.

Untuk training, saya menggunakan  data https://github.com/yohanesgultom/nlp-experiments/blob/master/data/ner/training_data.txt  yang dikonversi ke format BIO. Jadi misalnya

DKI LOCATION
Jakarta LOCATION

perlu diubah menjadi

DKI B-LOC
Jakarta I-LOC

Tambahkan file validasi (saya ambil dari data training, jangan lupa harus diacak terlebih dulu urutannya).

Catatan: penggunaan awalan label “B-” dan “I-” seperti B-LOC, I-LOC, B-PER, I-PER, B-ORG, I-ORG dan B-MISC, I-MISC wajib di anaGo karena di codenya mendeteksi “B-” dan “I-“. Sedangkan untuk bagian belakangnya bebas.

Karena teknik ini dasarnya sequence labeling bisa saja kita manfaatkan untuk task yang lain, misalnya untuk mendeteksi subyek dan obyek sehingga labelnya menjadi B-SUB, I-SUB, B-OBJ, I-OBJ dsb.

Selanjutnya install tensorflow dan keras (catatan saya tentang ini) lalu anago (bisa dengan pip3 anago).

Buat code berikut untuk training membuat model:

import anago
from anago.reader import load_data_and_labels
import os
import numpy as np
import random as rn

namaDir = "/media/yudiwbs/data/"

namaFileTrain = namaDir + "train.txt"
namaFileValid = namaDir + "valid.txt"
namaFileTest = namaDir + "test.txt"

# karena hasil tdk konsisten, random seednya diisi manual
os.environ['PYTHONHASHSEED'] = '0'
np.random.seed(42)
rn.seed(12345)
import tensorflow as tf
from keras import backend as K
tf.set_random_seed(1234)

# atur parameternya disini
model = anago.Sequence(char_emb_size=25, word_emb_size=100, char_lstm_units=25,
              word_lstm_units=100, dropout=0.5, char_feature=True, crf=True,
              batch_size=20, optimizer='adam', learning_rate=0.001,lr_decay=0.9,
              clip_gradients=5.0, max_epoch=30, early_stopping=True, patience=3,train_embeddings=True, max_checkpoints_to_keep=5, log_dir=None)

model.train(x_train, y_train, x_valid, y_valid)

print("\n\nEvaluasi Test:")
model.eval(x_test, y_test)

words = 'Budi Martami kuliah di UPI yang berlokasi di Bandung'.split()
print(model.analyze(words))

words = 'PDIP yang dikawal Megawati menang dalam Pilkada DKI Jakarta'.split()
print(model.analyze(words))

Hasilnya sebagai berikut:

{'entities': [{'score': 1.0, 'beginOffset': 0, 'text': 'Budi Martami', 'type': 'MISC', 'endOffset': 2}, {'score': 1.0, 'beginOffset': 4, 'text': 'UPI', 'type': 'LOC', 'endOffset': 5}, {'score': 1.0, 'beginOffset': 8, 'text': 'Bandung', 'type': 'LOC', 'endOffset': 9}], 'words': ['Budi', 'Martami', 'kuliah', 'di', 'UPI', 'yang', 'berlokasi', 'di', 'Bandung']}
{'entities': [{'score': 1.0, 'beginOffset': 0, 'text': 'PDIP', 'type': 'ORG', 'endOffset': 1}, {'score': 1.0, 'beginOffset': 3, 'text': 'Megawati', 'type': 'PER', 'endOffset': 4}, {'score': 1.0, 'beginOffset': 6, 'text': 'Pilkada DKI', 'type': 'MISC', 'endOffset': 8}, {'score': 1.0, 'beginOffset': 8, 'text': 'Jakarta', 'type': 'LOC', 'endOffset': 9}], 'words': ['PDIP', 'yang', 'dikawal', 'Megawati', 'menang', 'dalam', 'Pilkada', 'DKI', 'Jakarta']}
{'entities': [], 'words': ['Ibu', 'pergi', 'ke', 'pasar']}

“Budi Martami” dideteksi sebagai MISC (harusnya PERSON)
“UPI” dideteksi sebagai LOC (lokasi) (harusnya ORG)
“Bandung” sudah benar dideteksi sebagai LOC

“PDIP” sudah cocok dideteksi ORG
“Megawati”  juga sudah benar sebagai PERSON
“Pilkada DKI”  benar sebagai MISC (DKI bukan sebagai LOC di frasa ini)
“Jakarta” harusnya masuk ke frase “Pilkada DKI Jakarta” sebagai MISC, bukan LOC.

“Ibu pergi ke pasar”  sudah benar karena tidak ada named entity didalamnya.

Data trainingnya memang banyak membahas tentang berita politik sehinggga  lebih akurat untuk kalimat kedua dibandingkan yang pertama. Nanti data trainingnya perlu ditambah lebih banyak lagi.

Hasil terbaik yang saya dapatkan adalah F1 = 0.6837. Masih rendah,  mungkin bisa ditingkatkan dengan menggunakan pretrained Word2Vec atau GloVe

Update: untuk menyimpan model, gunakan:

 model.save(directory) 

parameternya bukan file tapi directory. Sedangkan untuk me-load model:

model = anago.Sequence().load(namaDirModel) 

Update: F1 naik menjadi 0.69-0.70 saat menggunakan Word2Vec dari data Wikipedia Bhs Indonesia (posting tentang w2vec).

Untuk menambahkan word embedding saat training:

import gensim

embeddings =  gensim.models.Word2Vec.load(namaFileW2Vec)
model = anago.Sequence(..param lain.., embeddings=embeddings)

Jangan lupa parameter word_emb_size disamakan dengan model embeddings-nya.

Update: menggunakan glove, F1 naik ke 0.71-0.72 (untuk vector size 50)
Code:

from anago.reader import load_data_and_labels,load_glove
embeddings = load_glove(namaFileVec)
model = anago.Sequence(..param lain.., embeddings=embeddings)

Update: ada bug di package Anago. Parameter patience ternyata di hardcode. Berikut perbaikannnya:

Di file metrics tambahkan parameter patience_nb

function def get_callbacks(log_dir=None, valid=(), tensorboard=True, eary_stopping=True, patience_nb=3):

lalu di code berikut passing parameter ini (tadinya di hardcode 3)

    if eary_stopping:
        callbacks.append(EarlyStopping(monitor='f1', patience=patience_nb, mode='max'))

lalu di trainer tambahkan parameter:

        # Prepare callbacks
        callbacks = get_callbacks(log_dir=self.checkpoint_path,
                                  tensorboard=self.tensorboard,
                              eary_stopping=self.training_config.early_stopping,
                                  patience_nb=self.training_config.patience,
                                  valid=(valid_steps, valid_batches, self.preprocessor))

di trainer juga optimizer adam sepertinya di hardcode.

=== END UPDATE ===

GitLab + IntelliJ & PyCharm di Windows

Jika kita menggunakan IDE IntelliJ dan PyCharm,  maka untuk interface ke GitHub sudah disediakan fasilitas standard. Masalahnya, repo GitHub sifatnya public (untuk account gratisan). Bukan saya tidak mau membagi code, tapi seringkali kualitasnya masih buruk dan memalukan untuk di-share. Alternatifnya adalah GitLab yang dapat membuat private repo. Saya posting langkah-langkahnya sebagai catatan pribadi karena bagi saya selalu bermasalah untuk Windows.

Langkah pertama adalah menginstall plugin GitLab project. Lalu setting –> Gitlab Setting. Isi dengan informasi token yang digenerate di situs GitLab. Pastikan juga git untuk Windows sudah diinstall.

Saat saya coba commit-push, keluar pesan bahwa repo berhasil dibuat tapi tidak bisa push. Error yang lain yang muncul “Push failed: Failed with error: Could not read from remote repository.”

Saya cek ternyata sekarang harus menggunakan SSH key.

  • Gunakan PuttyGen untuk mengenerate private-public key.   Harus diletakkan di   C:\Users\[nama user]\.ssh  (create directory dengan command line). Klik tombol save public key,  beri nama id_rsa.pub. Plih tab convertion, pilih OpenSSH untuk private key, lalu simpan ke direktori tersebut dengan nama id_rsa
  • Copy isi public key yang ada di layar PuttyGen ke situs GitLab
  • Di bagian Setting –> Git SSH Executable pilih “Build-In”, test untuk memastikan git sudah berfungsi.

Coba lagi clone/push.

Catatan: untuk linux gunakan perintah ssh-keygen -t rsa   lalu copy isi id_rsa.pub ke gitlab.

 

Membuat video tutorial praktikum

Saya sudah pernah menggabungkan video ke dalam elearning  sekitar 2006 (era sebelum Youtube). Saya juga sudah upload video di Youtube sejak 2010. Masalahnya saya pribadi tidak terlalu suka dengan media video untuk belajar, saya lebih suka membaca. Itu sebabnya sampai sekarang saya lebih banyak membuat modul tutorial dalam bentuk tulisan.

Beberapa semester terakhir, mahasiswa sering  mengeluh tidak diajari secara detil. Mereka ingin belajar seperti dalam kursus, tahap demi tahap bersama-sama.  Membayangkannya saja  saya langsung malas hehe. Solusinya adalah dengan membuat video tutorial dan upload ke Youtube. Buat satu sekali, bisa digunakan seterusnya.

Untuk screen recorder, saya menggunakan OBS Studio. Bagus fiturnya, dan bisa digabung misalnya dengan stream dari kamera (kapan-kapan nanti beli kamera-nya).  Untuk mic, saya menggunakan headset punya Furqon yang biasa digunakan dia untuk main game 🙂  Tidak terlalu banyak masalah, asal latihan dulu. Hati-hati dengan volume suara, umumnya terlalu kecil. Tambahkan filter gain di OBS untuk memperbesar volume.

Untuk video editor, saya mencoba OpenShot (terlalu sederhana), VSDC (previewnya tidak enak) dan akhirnya Shotcut. Saya paling cocok dengan Shotcut, walaupun perlu waktu belajar agak lama.

Setelah dua hari utak-atik (untuk video berdurasi hanya 15 menit), akhirnya bisa upload tutorial pertama saya tentang pengantar Android Studio. Nanti materi lainnya menyusul.

Playlist tutorial Android yang saya buat:  https://www.youtube.com/playlist?list=PLUh6vS-rrmzRG5FFQeaog46kjs4zxtCWX

 

POS Tagger Bahasa Indonesia dengan Python

Posting sebelumnya: POS Tagger dengan Syntaxnet

Secara bertahap, saya dan istri akan migrasi dari Java ke Python. Salah satu yang kami perlukan adalah POS (Part of Speech)-Tagger Bahasa Indonesia.

Ini cara yang paling sederhana  karena saya sudah sediakan modelnya, untuk cara trainingnya ada di bagian bawah.

Saya menggunakan CRFTagger, jadi library yang perlu diinstall: numpy, nltk dan python-crfsuite.

Lalu download pretrained model (1.6MB) yang saya buat berdasarkan data Fam Rashel (200rb-an token) di https://drive.google.com/open?id=12yJ82GzjnqzrjX14Ob_p9qnPKtcSmqAx

Untuk menggunakannya (sesuaikan path jika diperlukan):

from nltk.tag import CRFTagger
ct = CRFTagger()
ct.set_model_file('all_indo_man_tag_corpus_model.crf.tagger')
hasil = ct.tag_sents([['Saya','bekerja','di','Bandung']])
print(hasil)

Hasilnya akan seperti ini:

[[(‘Saya’, ‘PRP’), (‘bekerja’, ‘VB’), (‘di’, ‘IN’), (‘Bandung’, ‘NNP’)]]

Jika ada yang berminat untuk training sendiri, ada beberapa dataset POS-Tag Bahasa Indonesia:

https://github.com/UniversalDependencies/UD_Indonesian
https://github.com/famrashel/idn-tagged-corpus
http://www.panl10n.net/english/OutputsIndonesia2.htm
https://lindat.mff.cuni.cz/repository/xmlui/handle/11234/1-1989

Saya menggunakan data milik Fam Rashel, code untuk training-nya adalah sbb (mungkin belum efisien, saya masih belajar Python):

from nltk.tag import CRFTagger

jumSample = 500000
namaFile = "/home/yudiwbs/dataset/pos-tag-indonesia/idn-tagged-corpus-master/Indonesian_Manually_Tagged_Corpus.tsv"
with open(namaFile, 'r', encoding='utf-8') as f:
    lines = f.read().split('\n')

pasangan = []
allPasangan = []

for line in lines[: min(jumSample, len(lines))]:
    if line == '':
        allPasangan.append(pasangan)
        pasangan = []
    else:
        kata, tag = line.split('\t')
        p = (kata,tag)
        pasangan.append(p)

ct = CRFTagger()
ct.train(allPasangan,'all_indo_man_tag_corpus_model.crf.tagger')
#test
hasil = ct.tag_sents([['Saya','bekerja','di','Bandung'],['Nama','saya','Yudi']])
print(hasil)

Dokumentasi lengkap tentang lib POS-Tag NLTK dapat dilihat di: http://www.nltk.org/api/nltk.tag.html 

Untuk sekarang saya belum buat pengukuran kinerja model yang dihasilkan.

NER (Named Entity Recognition) Bahasa Indonesia dengan Stanford NER

Update: demo NER yang saya buat: nlp.yuliadi.pro

Update, posting lanjutan: NER Bahasa Indonesia dengan anaGo (Ptyhon+Keras)

Posting sebelumnya tentang NER


Untuk mengekstrak named entity Bahasa Indonesia, kita dapat memanfaatkan library Stanford NER untuk membuat model yang di-train dengan dataset Bahasa Indonesia. Pertama download Stanford NER di: https://nlp.stanford.edu/software/CRF-NER.html#Download

Sedangkan dataset NER Bahasa Indonesia untuk training dapat diperoleh di:

 https://github.com/yohanesgultom/nlp-experiments/blob/master/data/ner/training_data.txt

dan

https://github.com/yusufsyaifudin/indonesia-ner/tree/master/resources/ner

Catatan: format dataset di atas tidak sesuai dengan Stanford NER, jadi perlu dikonversi ke format dua kolom seperti ini:

Sementara	O
itu	O
Pengamat	O
Pasar	O
Modal	O
Dandossi	PERSON
Matram	PERSON
mengatakan	O
,	O
sulit	O
bagi	O
sebuah	O
kantor	ORGANIZATION
akuntan	ORGANIZATION
publik	ORGANIZATION
(	O
KAP	ORGANIZATION
)	O

Selanjutnya untuk training, dokumentasinya ada di:

https://nlp.stanford.edu/software/crf-faq.html#a:   

Setelah training selesai dan model didapat, maka cara menggunakannya adalah sebagai berikut.

Ambil stanford-ner-resources.jar, letakkan di direktori lib. Jika menggunakan Gradle maka setting gradle-nya sbb:


repositories {
  flatDir {
    dirs 'libs'
  }
}

dependencies {
 testCompile group: 'junit', name: 'junit', version: '4.12'
 compile group: 'edu.stanford.nlp', name: 'stanford-parser', version: '3.8.0'
 compile name: 'stanford-ner-3.8.0'
}

Selanjutnya gunakan code yang ada di NERDemo.java.  Sesuaikan variabel serializedClassifier dengan lokasi model bahasa Indonesia. Outputnya akan seperti ini untuk input “Budi Martami kuliah di UPI yang berlokasi di Bandung”:


---
Budi/PERSON Martami/PERSON kuliah/O di/O UPI/ORGANIZATION yang/O berlokasi/O di/O Bandung/LOCATION ./O
---
Budi	PERSON
Martami	PERSON
kuliah	O
di	O
UPI	ORGANIZATION
yang	O
berlokasi	O
di	O
Bandung	LOCATION
.	O
---

Ekstraksi Pasangan Pertanyaan-Jawaban dari Forum Online

Forum online masih memiliki potensi yang besar walaupun popularitasnya turun sejalan dengan populernya group FB dan app group chat.  Di forum online, thread sudah dikelompokkan dalam topik dan umumnya ada moderator yang mencegah spam dan bot sehingga  data lebih “bersih” dibandingkan Twitter.  Saya jadi tertarik untuk mencoba me-mining data di forum online ini.

Salah satu fungsi utama forum adalah untuk media tanya jawab.  Ini yang rencananya saya akan ekstrak, pasangan pertanyaan dan jawabannya (PPJ). Pasangan ini  nantinya akan digunakan sebagai basis pengetahuan  QA (question-answering) system, termasuk chatbot.

Saya memilih forum online kaskus, dengan sub topik roda-empat, dan dengan thread mengenai mobil ayla agnia (karena paling banyak).  Bentuknya megathread, yaitu satu thread besar dengan ratusan halaman. Saya crawl dengan sangat pelan (30 menit per halaman) jadi mudah-mudahan tidak menggangu.  Untuk mengambil pasangan pertanyaan-jawaban (PPJ),  saya ambil posting yang menggunakan quote  dan reply (gambar bawah).

quote

Masalahnya, tidak semua quote dan reply adalah PPJ. Ada  quote yang bukan pertanyaan, ada reply yang berbentuk pertanyaan balik (bukan jawaban) dan ada  pasangan quote-reply yang tidak relevan (misal sapaaan selamat pagi dan jawabannya).

Saya menggunakan klasifikasi teks untuk mencari pasangan quote-reply yang merupakan PPJ. Pelabelan dilakukan oleh saya sendiri, dari  1030 pasangan quote-reply, 189 masuk ke kelas PPJ dan 841 non-PPJ.   Di luar dugaan, ternyata hanya sedikit pasangan quote-reply yang dapat digunakan (10%-an).  Penyebabnya mungkin mega-thread juga digunakan untuk tempat kumpul-kumpul sehingga banyak percakapan yang keluar dari topik.

Selanjutnya saya coba beberapa teknik klasifikasi dan untuk pertama kalinya saya menggunakan scikit-learn  (pelan-pelan migrasi dari Java ke Python 🙂 ) .   Karena ini eksplorasi pertama, supaya cepat saya tidak menggunakan praproses dan fitur-fitur lain, hanya bag-of-words teks quote dan reply-nya. Masalah imbalance juga tidak ditangani.

Data displit menjadi 70% data training, 30% data validasi. Hasil terbaik adalah dengan teknik SGD sbb  (precision, recall, F1):

Non PPJ:  0.89, 0.97, 0.93
PPJ: 0.76, 0.43, 0.55

Hasilnya menurut saya lumayan, mengingat kelas PPJ hanya 10% (kelas minoritas) dan belum dilakukan optimasi apapun.

Untuk eksplorasi berikutnya (selain tentunya meningkatkan akurasi):

  • Lintas domain, misalnya model yang ditraining untuk data otomotif sebagus apa jika diaplikasikan ke data thread tentang smartphone.
  • Pertanyaan-jawaban yang berbentuk thread diskusi. Jadi PPJ yang saling berkait.  ini mungkin  cocok untuk chatbot, agar bot dapat memberikan respon yang lebih natural.
  • Membangun QA System atau chatbot yang menggunakan data ini.

 

Link ke paper: https://osf.io/preprints/inarxiv/5rxak/