Natural Language Processing and Text Mining (NLPTM)
Unstructured Data Analysis (UDA)*

02 - Dasar-Dasar Natural Language Processing (NLP)- Bagian ke-01

(C) Taufik Sutanto - 2020
tau-data Indonesia ~ https://tau-data.id

Outline Module NLPTM-02/UDA-02:

  • Tokenisasi
  • Stemming dan Lemma

Instalasi Modul:

  1. Google Colab ==> Jalankan Cell Berikutnya, stop reading the rest of this cell.
  2. Local (Windows/Linux)
  • Coba di terminal jalankan perintah pip install --upgrade spacy
  • Kalau gagal, unduh spacy dari sini (Asumsi menggunakan Python 3.6 - Recommended, Baca ADSP-01):
    • Link: https://www.lfd.uci.edu/~gohlke/pythonlibs/#spacy
    • thinc‑7.4.1‑cp36‑cp36m‑win_amd64.whl
    • srsly‑2.2.0‑cp36‑cp36m‑win_amd64.whl
    • preshed‑3.0.2‑cp36‑cp36m‑win_amd64.whl
    • murmurhash‑1.0.2‑cp36‑cp36m‑win_amd64.whl
    • cymem‑2.0.3‑cp36‑cp36m‑win_amd64.whl
    • spacy‑2.3.2‑cp36‑cp36m‑win_amd64.whl

di terminal jalankan perintah "pip install XYZ" dimana XYZ adalah file-file diatas secara terurut.

"Setelah" berhasil menginstall spacy, jalankan perintah berikut di terminal/command prompt.

  • python -m spacy download en
  • python -m spacy download xx
  • python -m spacy download en_core_web_sm

Setelah itu (masih di terminal/command prompt):

  • python (lalu enter)
  • import nltk
  • nltk.download('popular')

Semua langkah diatas hanya dilakukan 1x (karena sudah terinstall di komputer). Jika tidak suka repot, maka gunakan saja Google Colaboratory.

In [ ]:
# Jalankan Cell ini "HANYA" jika anda menggunakan Google Colab
import nltk

!mkdir data
!wget -P data/ https://raw.githubusercontent.com/taudata-indonesia/eLearning/master/data/slang.txt
!wget -P data/ https://raw.githubusercontent.com/taudata-indonesia/eLearning/master/data/stopwords_id.txt
!wget -P data/ https://raw.githubusercontent.com/taudata-indonesia/eLearning/master/data/stopwords_en.txt

!pip install spacy python-crfsuite unidecode textblob sastrawi
!python -m spacy download en
!python -m spacy download xx
!python -m spacy download en_core_web_sm

nltk.download('popular')

Tokenisasi

Tokenisasi adalah pemisahan kata, simbol, frase, dan entitas penting lainnya (yang disebut sebagai token) dari sebuah teks untuk kemudian di analisa lebih lanjut. Token dalam NLP sering dimaknai dengan "sebuah kata", walau tokenisasi juga bisa dilakukan ke kalimat, paragraf, atau entitas penting lainnya (misal suatu pola string DNA di Bioinformatika).

Mengapa perlu tokenisasi?

  • Langkah penting dalam preprocessing, menghindari kompleksitas mengolah langsung pada string asal.
  • Menghindari masalah (semantic) saat pemrosesan model-model natural language.
  • Suatu tahapan sistematis dalam merubah unstructured (text) data ke bentuk terstruktur yang lebih mudah di olah.


[Image Source]: https://www.softwareadvice.com/resources/what-is-text-analytics/

Tokenisasi dengan modul NLTK

Kelebihan:

  1. Well established dengan dukungan bahasa yang beragam
  2. Salah satu modul NLP dengan fungsi terlengkap, termasuk WordNet
  3. Free dan mendapat banyak dukungan akademis.

Kekurangan:

  1. "Tidak support" bahasa Indonesia
  2. Murni Python: relatif lebih lambat

https://www.nltk.org/

In [124]:
import nltk

T = "Hello, Mr. Man. He smiled!! This, i.e. that, is it."
Word_Tokens = nltk.word_tokenize(T)
print(Word_Tokens) # tokenisasi kata
['Hello', ',', 'Mr.', 'Man', '.', 'He', 'smiled', '!', '!', 'This', ',', 'i.e', '.', 'that', ',', 'is', 'it', '.']
In [125]:
# Bandingkan jika menggunakan fungsi split di Python, apakah bedanya? 
print(T.split())
# Apakah kesimpulan yang bisa kita tarik?
# Split() ==> Bukan Tokenisasi!.
['Hello,', 'Mr.', 'Man.', 'He', 'smiled!!', 'This,', 'i.e.', 'that,', 'is', 'it.']
In [126]:
Sentence_Tokens = nltk.sent_tokenize(T)
print(Sentence_Tokens) # Tokenisasi kalimat
# Perhatikan hasilnya, ada berapa kalimat yang di deteksi? setuju?
['Hello, Mr. Man.', 'He smiled!!', 'This, i.e.', 'that, is it.']

Trigger Diskusi Forum:</h3>

  • Apakah tanda baca seperti "?" atau "!" akan memisahkan kalimat?
  • Apakah tanda "carriage return"/enter/ganti baris memisahkan kalimat?
  • Apakah ";" memisahkan kalimat?
  • Apakah tanda dash "-" memisahkan kata? Dalam bahasa Indonesia/Inggris?

Tips: Perhatikan bentuk struktur data "output" dari tokenisasi NLTK.
Catatan: pindah baris di Python string bisa dilakukan dengan menggunakan symbol "\n"
Contoh:

Tokenisasi dengan modul Spacy

Kelebihan:</p>

  1. Di claim lebih cepat (C-based)
  2. License termasuk untuk komersil
  3. Dukungan bahasa yang lebih banyak dari NLTK (termasuk bahasa Indonesia*)

Kekurangan:

  1. Fungsi yang lebih terbatas (dibandingkan NLTK).
  2. Karena berbasis compiler, sehingga instalasi cukup menantang.

https://spacy.io/

In [3]:
# Contoh tokenisasi menggunakan Spacy
from spacy.lang.en import English
nlp_en = English()

T = "Hello, Mr. Man. He smiled!! This, i.e. that, is it."
nlp = nlp_en(T)
for token in nlp:
    print(token.text, end =', ')
Hello, ,, Mr., Man, ., He, smiled, !, !, This, ,, i.e., that, ,, is, it, ., 
In [4]:
nlp_en.add_pipe(nlp_en.create_pipe('sentencizer')) # New in latest Spacy

nlp = nlp_en(T)
for kalimat in nlp.sents:
    print(kalimat)
Hello, Mr. Man.
He smiled!!
This, i.e. that, is it.
In [6]:
# Hati-hati! ... token bukan string di Spacy, karena C-based, ia bekerja di byte bukan unicode.
token = nlp[0]
print(token)
type(token)
#token=='Hello'
Hello
Out[6]:
spacy.tokens.token.Token

Trigger Diskusi Forum:</h3>

  • Apakah hasil tokenisasi Spacy = NLTK? Mengapa?
  • Lakukan latihan seperti yang dilakukan sebelumnya dengan modul NLTK, apakah hasilnya sama dengan Spacy?

Catatan: Contoh sederhana ini menekankan perbedaan ilmu linguistik dan computational linguistic.</p>

Tokenisasi dengan TextBlob

Kelebihan:</p>

  1. Sederhana & mudah untuk digunakan/pelajari.
  2. Textblob objects punya behaviour/properties yang sama dengan string di Python.
  3. TextBlob dibangun dari kombinasi modul NLTK dan (Clips) Pattern

Kekurangan:

  1. Tidak secepat Spacy dan NLTK
  2. Language Model terbatas: English, German, French

*Blob : Binary large Object

In [8]:
# Tokenizing di TextBlob
from textblob import TextBlob

T = "Hello, Mr. Man. He smiled!! This, i.e. that, is it."
print(TextBlob(T).words)
['Hello', 'Mr', 'Man', 'He', 'smiled', 'This', 'i.e', 'that', 'is', 'it']
In [9]:
kalimatS = TextBlob(T).sentences
print([str(kalimat) for kalimat in kalimatS])
['Hello, Mr. Man.', 'He smiled!!', 'This, i.e.', 'that, is it.']

Trigger Diskusi Forum:</h3>

  • Ada yang berbeda dari hasilnya? Apakah lebih baik seperti ini?

Tips: TextBlob biasa digunakan untuk prototyping pada data yang tidak terlalu besar.
Catatan: Hati-hati tipe data Blob tidak biasa (objek).

In [10]:
# Saat melakukan coding di Python, selalu perhatikan "tipe data" yang dihasilkan oleh modul.
A = TextBlob(T).sentences
B = TextBlob(T).words
print(A[0], type(A[0]))
print(B[0], type(B[0]))
# Apakah bedanya dengan tipe data str biasa di python?
Hello, Mr. Man. <class 'textblob.blob.Sentence'>
Hello <class 'textblob.blob.Word'>
In [19]:
# Di Spacy ini tidak berlaku
B[0]=='Hello'
Out[19]:
True
In [137]:
# "properties" Blob word
print(dir(C))
['LancasterStemmer', 'PorterStemmer', 'SnowballStemmer', '__add__', '__class__', '__contains__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'capitalize', 'casefold', 'center', 'correct', 'count', 'define', 'definitions', 'detect_language', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'get_synsets', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'lemma', 'lemmatize', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'pluralize', 'pos_tag', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'singularize', 'spellcheck', 'split', 'splitlines', 'startswith', 'stem', 'string', 'strip', 'swapcase', 'synsets', 'title', 'translate', 'translator', 'upper', 'zfill']
In [138]:
# "properties" string di Python
print(dir(D))
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

Tokenisasi 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.


[Image Source]

In [12]:
# Contoh Tokenizer untuk twitter
from nltk.tokenize import TweetTokenizer

Tokenizer = TweetTokenizer(strip_handles=True, reduce_len=True)
tweet = "@Kirana_Sutanto I am so happpppppppy"
print(Tokenizer.tokenize(tweet))

# Masih salah (i.e. "happpy"), nanti kita akan perbaiki ini dengan "spell check"
# catatan: pada permasalahan "Sentiment analysis" kata yang ditulis panjang seperti diatas 
# bisa mengindikasikan sentiment yang kuat
['I', 'am', 'so', 'happpy']

Tokenisasi (NLP) Bahasa Indonesia:

NLTK belum support Bahasa Indonesia, bahkan module NLP Python yang support bahasa Indonesia secara umum masih sangat langka. Beberapa resources yang dapat digunakan:

  1. KirraLabs: Mix of NLP-TextMining resources
  2. Sastrawi 1.0.1: untuk "stemming" & stopwords bahasa Indonesia.
  3. Daftar Kata Dasar Indonesia: Bisa di load sebagai dictionary di Python
  4. Wiktionary: ProyekWiki bahasa Indonesia [termasuk Lexicon]
  5. WordNet Bahasa Indonesia: Bisa di load sebagai dictionary (atau NLTK*) di Python.
  6. Daftar Kata Baku-Tidak Baku: Bisa di load sebagai dictionary di Python.
  7. Spacy: Cepat/efisien, MIT License, tapi language model Indonesia masih terbatas.
  8. UdPipe: Online request & restricted license (support berbagai bahasa - pemrograman).
In [13]:
# Contoh Tokenisasi dalam bahasa Indonesia dengan Spacy
from spacy.lang.id import Indonesian
nlp_id = Indonesian()  # Language Model

teks = 'Sore itu, Hamzah melihat kupu-kupu di taman. Ibu membeli oleh-oleh di pasar'
tokenS_id = nlp_id(teks)
#T = []
#for token in tokenS_id:
#    T.append(token)
print([t for t in tokenS_id])
[Sore, itu, ,, Hamzah, melihat, kupu-kupu, di, taman, ., Ibu, membeli, oleh-oleh, di, pasar]
In [143]:
# Jika menggunakan Language model English:
tokenS_en = nlp_en(teks)
print([token.text for token in tokenS_en])
['Sore', 'itu', ',', 'Hamzah', 'melihat', 'kupu', '-', 'kupu', 'di', 'taman', '.', 'Ibu', 'membeli', 'oleh', '-', 'oleh', 'di', 'pasar']

Word Case (Huruf BESAR/kecil):

  • Untuk menganalisa makna (semantic) dari suatu (frase) kata dan mencari informasi dalam proses textmining, seringnya (*) kita tidak membutuhkan informasi huruf besar/kecil dari kata tersebut.
  • Text case normaliation dapat dilakukan pada string secara efisien tanpa melalui tokenisasi (mengapa?).
  • Namun, bergantung pada analisa teks yang akan digunakan pengguna harus berhati-hati dengan urutan proses (pipelining) dalam preprocessing. Mengapa dan apa contohnya?

(*) Coba temukan minimal 2 pengecualian dimana  huruf kapital/kecil (case) mempengaruhi makna/pemrosesan teks.

In [14]:
# Ignore case (huruf besar/kecil)
T = "Hi there!, I am a student. Nice to meet you :)"
print(T.lower())
print(T.upper())
# Perintah ini sangat efisien karena hanya merubah satu bit di setiap (awal) bytes dari setiap karakter
# Sehingga tetap efisien jika ingin dilakukan sebelum tokenisasi
hi there!, i am a student. nice to meet you :)
HI THERE!, I AM A STUDENT. NICE TO MEET YOU :)

Morphological-Linguistic Normalization: Stemming & Lemmatization

(Canonical Representation)

Stemming dan Lemma

  1. Stemmer akan menghasilkan sebuah bentuk kata yang disepakati oleh suatu sistem tanpa mengindahkan konteks kalimat. Syaratnya beberapa kata dengan makna serupa hanya perlu dipetakan secara konsisten ke sebuah kata baku. Banyak digunakan di IR & komputasinya relatif sedikit. Biasanya dilakukan dengan menghilangkan imbuhan (suffix/prefix).

  2. lemmatisation akan menghasilkan kata baku (dictionary word) dan bergantung konteks.

  3. Lemma & stemming bisa jadi sama-sama menghasilkan suatu akar kata (root word). Misal : Melompat ==> lompat

Mengapa melakukan Stemming & Lemmatisasi?

  1. Sering digunakan di IR (Information Retrieval) agar ketika seseorang mencari kata tertentu, maka seluruh kata yang terkait juga diikutsertakan.
    Misal: organizeorganizes, and organizing  dan democracydemocratic, and democratization.
  2. Di Text Mining Stemming dan Lemmatisasi akan mengurangi dimensi (mengurangi variasi morphologi), yang terkadang akan meningkatkan akurasi.
  3. Tapi di IR efeknya malah berkebalikan: meningkatkan recall, tapi menurunkan akurasi [Link]. Contoh: kata operate, operating, operates, operation, operative, operatives, dan operational jika di stem menjadi operate, maka ketika seseorang mencari "operating system", maka entry seperti operational and research dan operative and dentistry akan muncul sebagai entry dengan relevansi yang cukup tinggi.

Stemming tidak perlu "benar", hanya perlu konsisten. Sehingga memiliki berbagai variansi, (sebagian) contoh di NLTK:

In [15]:
# Contoh Stemming di NLTK
from nltk.stem.lancaster import LancasterStemmer
from nltk.stem.porter import PorterStemmer
from nltk.stem.snowball import SnowballStemmer

T = 'presumably I would like to MultiPly my provision, saying tHat without crYing'
print('Sentence: ',T)

StemmerS = [LancasterStemmer, PorterStemmer, SnowballStemmer] 
Names = ['Lancaster', 'Porter', 'SnowBall']

for stemmer_name,stem in zip(Names,StemmerS):
    if stemmer_name == 'SnowBall':
        st = stem('english')
    else:
        st = stem()
        
    print(stemmer_name,': ',' '.join(st.stem(t) for t in T.split()))
# perhatikan, kita tidak melakukan case normalization (lowercase) 
# Hasil stemming bisa tidak bermakna
Sentence:  presumably I would like to MultiPly my provision, saying tHat without crYing
Lancaster :  presum i would lik to multiply my provision, say that without cry
Porter :  presum I would like to multipli my provision, say that without cri
SnowBall :  presum i would like to multipli my provision, say that without cri
In [17]:
# Contoh Lemmatizer di NLTK
from nltk.stem import WordNetLemmatizer

lemmatizer = WordNetLemmatizer()
T = "apples and Oranges are similar. boots and hippos aren't, don't you think?"
print('Sentence: ', T)
print('Lemmatize: ',' '.join(lemmatizer.lemmatize(t) for t in T.split()))
# Lemma case sensitive. Dengan kata lain string harus diubah ke dalam bentuk huruf kecil (lower case)
Sentence:  apples and Oranges are similar. boots and hippos aren't, don't you think?
Lemmatize:  apple and Oranges are similar. boot and hippo aren't, don't you think?
In [18]:
# Lemmatizer menggunakan informasi pos. "pos" (part-of-speech) akan dibahas di segmen berikutnya
print(lemmatizer.lemmatize("better", pos="a")) # adjective
print(lemmatizer.lemmatize("better", pos="v")) # verb
good
better
In [19]:
# TextBlob Stemming & Lemmatizer
from textblob import Word
# Stemming
print(Word('running').stem()) # menggunakan NLTK Porter stemmer

# Lemmatizer
print(Word('went').lemmatize('v'))

# default Noun, plural akan menjadi singular dari akar katanya
# Juga case sensitive
run
go
In [36]:
# Spacy Lemmatizer English
import spacy
nlp = spacy.load("en_core_web_sm")

E = "I am sure apples and oranges are similar"
doc = nlp(E)

for token in doc:
    print(token.text, token.lemma_)
# Perhatikan huruf besar/kecil
I -PRON-
am be
sure sure
apples apple
and and
oranges orange
are be
similar similar

Spacy "tidak" (bukan belum) support Stemming:

[Image Source]

In [45]:
# Lemmatizer dengan Sastrawi
from Sastrawi.Stemmer.StemmerFactory import StemmerFactory
stemmer = StemmerFactory().create_stemmer()

I = "perayaan itu Berbarengan dengan saat kita bepergian ke Makassar"
print(stemmer.stem(I))
print(stemmer.stem("Perayaan Bepergian Menyuarakan"))
# Ada beberapa hal yang berbeda antara Sastrawi dan modul-modul diatas.
# Apa sajakah?
raya itu bareng dengan saat kita pergi ke makassar
raya pergi suara

Tips:

  • Secara umum 'biasanya' di Text Mining yang kita butuhkan hanyalah Lemma.
  • "Kecuali" di aplikasi IR, spelling correction, variasi kata, clustering, atau terkadang klasifikasi. Pada aplikasi-aplikasi tersebut stemming terkadang lebih diinginkan.
  • Stemming jauh lebih cepat, tapi tidak selalu tersedia di modul NLP.
  • Beberapa algoritma tertentu membutuhkan tanda "." dan "," : contohnya untuk document summarization di textRank.
  • "_" juga biasa digunakan untuk menyatakan frase kata di representasi n-grams (misal "buah_tangan").
  • Stemming juga digunakan pada Word Sense Disambiguation (WSD)

Trigger Diskusi Forum:

  • Untuk menghemat storage database, apakah sebaiknya kita menyimpan saja hasil preprocessed texts/documents?

End of Module NLPTM-02