Tokenization dalam Bahasa Inggris, Indonesia, & Alay


Kali ini kita akan membahas salah satu bagian penting dari Text Mining/Natural Language Processing: Tokenization. Tokenization adalah salah satu bagian penting dari proses awal pengolahan data teks. Pengolahan data teks dimulai dengan proses preprocessing yang terkadang disebut juga sebagai data munging/wrangling. Membahas definisi exact dari apa itu data Munging atau wrangling dan apa bedanya menurut saya tidak penting dan menghabiskan waktu. Salah satu istilah yang lebih umum dipakai adalah preprocessing, so mari kita pakai istilah ini saja.
[Text Analytics </TES>®]
Sebelum data diolah dalam model data science/machine learning, data dalam bentuk teks diolah dalam preprocessing terlebih dahulu. Di dalam preprocessing data teks ada beberapa sub proses utama:
  1. Sumber Data: Data dalam berbagai format (istilahnya variety di Big Data)  diterima oleh sistem/user. Contohnya berformat HTML, XML, pdf, csv, xls, atau data dari API media sosial yang sekarang seringnya berformat JSON.
  2. Parser:Formatted teks data seperti yang disebutkan di (1) biasanya memuat perintah "tags", misal <b>, {var:"text}, dsb.  Parser merubah teks data menjadi plain information yang siap diolah ke bentuk selanjutnya. Contoh:
    import json
    T = '{"Nama":"Udin", "umur":79}'
    data = json.loads(T)
    print("Nama:{0}, Umur:{1}".format(data['Nama'],data['umur']))
    Tips: Hati-hati walau di Pyhton tanda ' dan " interchangeable untuk string, tapi tidak untuk JSON. Di contoh di atas sebuah string T (misal berasal dari data berformat JSON) di load ke variabel data yang bertipe dictionary.
  3. Cleansing:Setelah proses Parser barulah text cleansing dilakukan. Pada proses ini akan dilakukan beberapa proses, sebagai contoh tokenization, filtering (e.g. stopwords removal), stemming/lemmatization, dsb. Nah di post ini mari kita fokus ke Tokenization terlebih dahulu.
Apa itu tokenization ? Apa ada hubungannya dengan Token di Gojek, Listrik PLN, atau TimeZone? ... yaaa,... kalau mau maksa sih ada aja hubungannya ??, tapi tokenization disini adalah memisahkan kata, simbol, frase, dan entitas penting lainnya (yang disebut sebagai token) dari sebuah teks untuk kemudian di analisa. Token dalam NLP sering dimaknai dengan "sebuah kata", walau tokenisasi juga bisa dilakukan ke kalimat atau paragraf [more about this latter]. Stop, stop Gan ... ane udah paham ... udah ga usah dilanjutkan ... Gampang ini mah di Python. Gini doang kan?
T = "hi hi, I am Mukidi. Nice to meet you :)"
tokens = T.split()
print(tokens) # ['hi', 'hi', 'I', 'am', 'Mukidi.', 'Nice', 'to', 'meet', 'you', ':)']
Selesai kan Gan urusan? .... #lelumpatan #NgajakMakanMakan #BegadangMakanKacang .... Bukan maksud hati untuk mempersulit hidup permasalahan, HHhmmm...?? sayangnya tidak sesederhana itu. Secara umum memang dalam bahasa Inggris, Indonesia dan bahasa-bahasa lainnya yang menggunakan alfabet romawi (a-z,0-9) "biasanya" memang dipisahkan dengan spasi. Dengan kata lain "mungkin" >90% memang begitu, tapi sayangnya tidak selalu (misal Kata Majemuk di bahasa Indonesia). Selain itu ada masalah lain:

Symbols

Di output code diatas, hasil split ndak murni sebuah kata. "hi," dan "Mukidi." mengandung tanda koma dan titik. Kalau tidak ditangani maka kita akan mengolah "hi" dan "hi," sebagai 2 entitas yang berbeda. Stop, stop Gan ... kalau cuma masalah itu, ane tau solusinye:
T = "hi hi, I am Mukidi. Nice to meet you :)"
symbols = [',', '.',':', ')']
for s in symbols:
    T = T.replace(s,' ')
tokens = T.split()
print(tokens) # ['hi', 'hi', 'I', 'am', 'Mukidi', 'Nice', 'to', 'meet', 'you']
Gimane? sip kan? #Sedakep #PalaMenengadah #MulutMangap #MintaDiFoto

Inefficiency

Hati-hati dengan perintah "T = T.replace(s,' ')" dengan cara diatas. Python akan membuat memory baru untuk hasil replace di setiap iterasi-nya. Bayangkan di setiap iterasi ada "copy of T" di memory. Dan sayangnya setau saya (please CMIIW) tidak ada parameter untuk merubahnya menjadi "inplace" untuk perintah "replace" [Link]. Mengapa? Karena string di Python object immutable (tidak bisa dirubah), sama seperti tipe data Tuple. Ini ilustrasinya:
T = "hi hi, I am Mukidi. Nice to meet you :)"
print(T[0]) # ok, output "h"
T[0] = 'H' # => Error!!!! ...
Sehingga kalau T besar (misal mengolah banyak dokumen dengan ukuran masing² beberapa MBs) maka iterasi diatas akan berjalan lambat karena di memory akan melakukan create & destroy T pada setiap iterasi. Intinya komputer panas Gan ... kerjanya juga ndak efisien. Tapi ok untuk string yang kecil dan replace yang sederhana (misal status twitter).  Kalau keperluan replace-nya rumit, biasanya pakai regular expression (dibahas di post lain). Tapi kalau replace-nya sederhana maka perintah standard "replace" lebih cepat ketimbang reguler expression [baca disini].

"Don't Reinvent The Wheel"

Daripada susah², mending pakai module saja. Kalau kata orang Eropa: "mbok ya ora sah neko-neko to mas" ??. Salah satu module untuk pengolahan bahasa yang paling tersohor adalah NLTK. Menggunakan NLTK tokenization diatas dilakukan dengan cara sebagai berikut:
import nltk
T = "hi hi, I am Mukidi. Nice to meet you :)"
Sentence_Tokens = nltk.sent_tokenize(T)
# Sentence_Tokens = ['hi hi, I am Mukidi.', 'Nice to meet you :)']
Word_Tokens = nltk.word_tokenize(T)
# Word_Tokens = ['hi', 'hi', ',', 'I', 'am', 'Mukidi', '.', 'Nice', 'to', 'meet', 'you', ':', ')']
Ntar dulu Gan ... itu di Word_Tokens masih ada symbol²-nya!... :( Tenang Boss .... Setelah dapet tokens-nya baru kita lakukan cleansing, misal:
symbols = set(symbols)
Tokens_1 = [w for w in Word_Tokens if w not in symbols]
# ATAU jika hanya akan mengolah kata yang berupa huruf A-A, a-z, 0-9
Tokens_2 = [w for w in Word_Tokens if w.isalnum()]
# Tokens_1==Tokens_2 = ['hi', 'hi', 'I', 'am', 'Mukidi', 'Nice', 'to', 'meet', 'you']
Notes di contoh di atas "symbols" di rubah dari list ke set, karena di python "x in set" jauh lebih cepat ketimbang "x in list" [Keterangan lebih lanjut disini].  O iya ... best practice-nya ... sebaiknya sebelum di tokenize sebaiknya Text dirubah ke lowercase dulu. Perintahnya mudah (T = T.lower()). Namun kita akan bahas normalisasi tokens/words lebih lanjut di lain waktu. Last but not least, alternatif cara dengan isalnum diatas akan gagal jika di token-nya terdapat spasi, " ' ", "-", atau symbol lain selain A-Z,a-z, dan 0-9.

Tokenization tidak hanya language dependent, tapi juga environment dependent

Tokenization sebenarnya tidak sesederhana memisahkan berdasarkan spasi dan removing symbol. Sebagai contoh dalam bahasa Jepang/Cina/Arab suatu kata bisa terdiri dari beberapa karakter. Lalu seperti yang sudah dijabarkan sebelumnya ada kata² majemuk (2 atau lebih kata yang bermakna tunggal) seperti : "terima kasih", "rumah sakit", "tanggung jawab", dsb. Tidak mungkin untuk membahas semua Tokenization di post ini. Pembahasan akademis tentang Tokenizer ini-pun tidak sederhana, berikut saya contohkan beberapa paper akademis terkait tokenizer (pembahasan detailnya nanti di buku yang saya tulis saja ya, terlalu berat untuk blog post) [J.Jiang 2007][Jirí Maršík 2012][Horsmann 2015]. Di atas sudah dibahas tokenization bahasa Inggris, berikutnya kita akan bahas special tokenization (misal twitter) dan tokenization dalam bahasa Indonesia. Tokenization data twitter (Alay): NLTK memiliki tokenization khusus untuk twitter. Berikut contohnya:
from nltk.tokenize import TweetTokenizer

Tokenizer = TweetTokenizer()
tweet = "This blog post is so cooool #smiley: :-) :-P <3 and some arrows < > -> <--"
tweet_token = Tokenizer.tokenize(tweet)
# tweet_token = ['This', 'blog', 'post', 'is', 'so', 'cooool', 
# '#smiley', ':', ':-)', ':-P', '<3', 'and', 'some', 'arrows', 
# '<', '>', '->', '<--']
Perhatikan bagaimana Tokenizer-nya tetap menjaga tanda hashtag (#). Bahkan kita bisa mencoba untuk menangani status alay sebagai berikut:
Tokenizer_Alayers1 = TweetTokenizer(strip_handles=True, reduce_len=True)
tweet = "@KiranaSutanto And IIIIIIiiiiii... Will Alwayys Luv Uuuuuuuuu <3 "
tweet_token = Tokenizer_Alayers1.tokenize(tweet)
# tweet_token = ['And', 'IIIiii', '...', 'Will', 'Alwayys', 'Luv', 'Uuuu', '<3']
Parameter "strip_handles" akan menghilangkan mention, dan "reduce_len" akan mengurangi character repetition >3 ke 3. Untuk alayers yang menggunakan character² aneh, NLTK punya moses tokenizer. Atau kombinasi antara modul unidecode dan-atau word correction seperti pyenchant (Py 3.4<), module autocorrect, atau generic spell_checker seperti Norvig Spell Checker. Berikut contohnya:
from unidecode import unidecode
Tweet_Alay = "???? ?ØÐ, p?l?e?a?s?e ???? ?? ???? ???? ???????"
Tweet_Waras = unidecode(Tweet_Alay).lower()
# Tweet_Waras = 'dear god, please help me with this alayers'
Tokenizer Bahasa Indonesia: Alayers ga penting banget sih, mari kita tinggalkan saja mereka dan bahas sesuatu yang lebih penting: Tokenizer bahasa Indonesia. First thing first, sayangnya NLTK setau saya tidak support Bahasa Indonesia, bahkan module NLP Python yang support bahasa Indonesia secara umum sangat langka (ehm ehm ehm ... #KodeKeras #ParaPenelitiNLPIndonesia #AtauSastraIndonesia). Kalaupun ada, dengan berbagai keterbatasan saja. Misal yang saya tau ini (Kalau tau more than this please comment di bawah ya, thanks ??):
  1. Sastrawi 1.0.1 untuk stemming & stopwords bahasa Indonesia.
  2. Indonesian stopwords only
  3. Daftar Kata Dasar Indonesia (Saya punya yang dengan part-of-speech nya cuma lupa sumbernya ????)
  4. Wiktionary:ProyekWiki bahasa Indonesia [termasuk Lexicon]
  5. Daftar Kata Baku-Tidak Baku
  6. Last, but not least my favourite: Spacy.
Kita akan lakukan tokenisasi Bahasa Indonesia dengan Module Python Spacy. Saya pribadi lebih suka Spacy ketimbang Gensim, TextBlob, atau bahkan NLTK. Mengapa? Spacy cocok untuk level produksi (License-nya MIT !!!... ??). Spacy sangat (sangat) cepat (me love fast/efficient modules ??), ini perbandingan yang saya ambil dari websitenya:
Spacy
[Spacy Benchmarks]
Sebelum kita mulai Tokenisasi Bahasa Indonesia dengan Spacy, pertama-tama saya perlu menyebutkan bahwa support untuk Bahasa Indonesia masih dalam tahap Alpha. Tapi development dan system-nya promising banget. Lagipula modifikasinya cukup mudah (kita bahas kalau kopi darat saja, lewat blog post riweuh ??). Ok, cukup pengantarnya, here goes (probably) the most important code snippets of this post:
# Need Spacy >= 2.05
from spacy.lang.id import Indonesian
import spacy

nlp = Indonesian()  # use directly
nlp = spacy.blank('id')  # blank instance'
Teks = nlp('Hamzah melihat kupu-kupu di Taman')
Token_kata = [token.text for token in Teks]
# Token_kata = ['Hamzah', 'melihat', 'kupu-kupu', 'di', 'Taman']
There has been a very interesting development di Spacy. Saya berharap para ahli Bahasa Indonesia (Sastra Indonesia) dan para peneliti NLP Indonesia untuk gabung ke Spacy ketimbang menciptakan module Python baru tersendiri. Dengan begitu ilmu NLP-Indonesia bisa berkembang lebih cepat & efisien untuk kepentingan bersama. Btw, post ini udah kepanjangan... saatnya Ishoma dan mengerjakan kerjaan lainnya ... ?? ... Silahkan share jika sekiranya bermanfaat, like kalau emang suka, dan cicing wae kalau ngga ... ?? Cheers, </TES>® Code Encodings dan Module Versions yang dipakai di Blog Post ini:
# -*- coding: utf-8 -*-
# NLTK '3.2.5' , Spacy '2.0.5', unidecode '1.0.22', json '2.0.9'
PS: Di near future saya ingin nulis tentang scalable Text Preprocessing, kalau udah lama ga ditulis-tulis juga tolong diingatkan ... ???? Testing Latex: $\int_{a}^{z}\sum_{i=1}^{N}\phi(x)dx$

Tidak ada komentar:

Posting Komentar

Relevant & Respectful Comments Only.