14 April 2018

SpaCy Bahasa Indonesia

SpaCy merupakan library natural language processing (NLP) yang sangat powerful, terutama untuk pemrosesan bahasa Inggris. Tidak hanya fungsi-fungsi dasar seperti tokenizer, library ini juga mendukung fungsi NLP yang bergantung pada solusi berbasis machine learning seperti part-of-speech (POS) tagging, Named entity recognition (NER), dan dependency parsing.

Buat yang belum tahu, mulai dari spacy versi 2.0.0, sebagian fungsi NLP bahasa Indonesia sudah didukung oleh SpaCy. Kredit setinggi-tingginya perlu kita berikan kepada Mas Jim Geovedi atas pull request-nya ke SpaCy. Tidak semua fungsi NLP ada di bahasa Indonesia, karena tidak seperti bahasa Inggris, fungsi-fungsi yang bergantung pada model machine learning yang disebutkan diatas belum didukung secara langsung. Lalu apa saja fungsi NLP yang bisa dapat dari SpaCy? Yuk kita cari tahu.

Sebelum kita mulai, tentu kita harus meng-install SpaCy terlebih dahulu

pip install spacy>2.0.0

Setelah itu kita mulai dengan import module SpaCy

In [1]:
import spacy

lalu kita load bahasa Indonesia

In [2]:
nlp = spacy.blank('id')

Perhatikan perbedaan dengan cara me-load bahasa Inggris yang diperlihatkan di tutorial. Di bahasa Indonesia tidak tersedia pre-trained model untuk NER, POS, dll, sehingga kita aktivasi dengan spacy.blank, bukan spacy.load

In [3]:
# ini akan gagal
spacy.load('id')
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-3-9d24c7bdab0b> in <module>()
      1 # ini akan gagal
----> 2 spacy.load('id')

~/.pyenv/versions/3.6.3/envs/nlp/lib/python3.6/site-packages/spacy/__init__.py in load(name, **overrides)
     17             "to load. For example:\nnlp = spacy.load('{}')".format(depr_path),
     18             'error')
---> 19     return util.load_model(name, **overrides)
     20 
     21 

~/.pyenv/versions/3.6.3/envs/nlp/lib/python3.6/site-packages/spacy/util.py in load_model(name, **overrides)
    118     elif hasattr(name, 'exists'):  # Path or Path-like to model data
    119         return load_model_from_path(name, **overrides)
--> 120     raise IOError("Can't find model '%s'" % name)
    121 
    122 

OSError: Can't find model 'id'

Setelah kita load, berikut paparan singkat fungsi-fungsi yang bisa kita dapatkan dari SpaCy.

Tokenizer

Walau tanpa pre-trained model, kita akan tetap mendapatkan banyak fitur SpaCy. Fitur pertama yaitu tokenizer. Caranya kita load satu kalimat ke dalam spacy Doc. Doc sendiri merupakan iterable dimana setiap elemennya yaitu kelas Token. Secara otomatis tokenisasi dilakukan ketika kita menginstansiasi kelas Doc dengan dokumen/kalimat yang kita inginkan.

In [4]:
s = 'Galaxy Note 8, flagship terbaru dari Samsung, bisa ditebus dengan harga 11 juta rupiah (cashback 1 juta).'
doc = nlp(s)
print(type(doc))
print(type(doc[0]))
<class 'spacy.tokens.doc.Doc'>
<class 'spacy.tokens.token.Token'>
In [5]:
for i, token in enumerate(doc):
    print(f'token-{i}', token)
token-0 Galaxy
token-1 Note
token-2 8
token-3 ,
token-4 flagship
token-5 terbaru
token-6 dari
token-7 Samsung
token-8 ,
token-9 bisa
token-10 ditebus
token-11 dengan
token-12 harga
token-13 11
token-14 juta
token-15 rupiah
token-16 (
token-17 cashback
token-18 1
token-19 juta
token-20 )
token-21 .

Dari contoh diatas kita lihat proses tokenisasi berhasil. Namun sayangnya sentence tokenization (segmentation) untuk bahasa Indonesia belum didukung, layaknya bahasa Inggris.

In [6]:
par = (
'Seiring perkembangan, kebutuhan kini semakin mahal saja harganya. '
'Lalu apa yang bisa kita lakukan? '
'Alih-alih mengeluh sepanjang hari dan menyalahkan banyak orang, kini Anda harus memulai perubahan pada kehidupan Anda.'
)
print(par)

doc_par = nlp(par)
for sentence in doc_par.sents:
    print(sentence)
Seiring perkembangan, kebutuhan kini semakin mahal saja harganya. Lalu apa yang bisa kita lakukan? Alih-alih mengeluh sepanjang hari dan menyalahkan banyak orang, kini Anda harus memulai perubahan pada kehidupan Anda.
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-68272f53b766> in <module>()
      7 
      8 doc_par = nlp(par)
----> 9 for sentence in doc_par.sents:
     10     print(sentence)

doc.pyx in __get__()

ValueError: Sentence boundaries unset. You can add the 'sentencizer' component to the pipeline with: nlp.add_pipe(nlp.create_pipe('sentencizer')) Alternatively, add the dependency parser, or set sentence boundaries by setting doc[i].sent_start
In [7]:
# bahasa Inggris sudah didukung oleh sentence tokenizer

nlp_en = spacy.load('en')

par_en = (
    'After an uneventful first half, Romelu Lukaku gave United the lead on 55 minutes with a close-range volley.'
    'Sanchez was then fouled by Huddersfield defender Michael Hefele to win a penalty and the Chilean, a January signing from Arsenal, stepped up to take the spot-kick.'
    'The forward saw his low shot saved by Jonas Lossl, but made no mistake with the rebound to double United\'s lead on his home debut.'
)

doc_en = nlp_en(par_en)

for sent in doc_en.sents:
    print(sent)
After an uneventful first half, Romelu Lukaku gave United the lead on 55 minutes with a close-range volley.
Sanchez was then fouled by Huddersfield defender Michael Hefele to win a penalty and the Chilean, a January signing from Arsenal, stepped up to take the spot-kick.
The forward saw his low shot saved by Jonas Lossl, but made no mistake with the rebound to double United's lead on his home debut.

Stop Words

SpaCy juga mencakup daftar stop words untuk bahasa Indonesia. Daftar tersebut bisa diakses melalui

In [8]:
from spacy.lang.id.stop_words import STOP_WORDS

# STOP WORDS is a set
# convert to list
print(list(STOP_WORDS)[:10])
['beberapa', 'hampir', 'ditanya', 'cukup', 'kepada', 'mengucapkannya', 'depan', 'karenanya', 'merupakan', 'bila']

setiap Token di dalam SpaCy mempunyai attribute is_stop untuk mengecek apakah token tsb merupakan stop word

In [9]:
doc = nlp('saya menyukai kemewahan')
for token in doc:
    print(token, token.is_stop)
saya True
menyukai False
kemewahan False

Atribut Linguistik

is_stop merupakan satu dari banyak atribut linguistik dari Token yang didukung SpaCy.

In [13]:
token = doc[0]
attributes = [attr for attr in dir(token) if not attr.startswith('_')]
for attr in attributes:
    print(attr)
ancestors
check_flag
children
cluster
conjuncts
dep
dep_
doc
ent_id
ent_id_
ent_iob
ent_iob_
ent_type
ent_type_
get_extension
has_extension
has_vector
head
i
idx
is_alpha
is_ancestor
is_ascii
is_bracket
is_currency
is_digit
is_left_punct
is_lower
is_oov
is_punct
is_quote
is_right_punct
is_sent_start
is_space
is_stop
is_title
is_upper
lang
lang_
left_edge
lefts
lemma
lemma_
lex_id
like_email
like_num
like_url
lower
lower_
n_lefts
n_rights
nbor
norm
norm_
orth
orth_
pos
pos_
prefix
prefix_
prob
rank
right_edge
rights
sent_start
sentiment
set_extension
shape
shape_
similarity
string
subtree
suffix
suffix_
tag
tag_
text
text_with_ws
vector
vector_norm
vocab
whitespace_

Ortografi

Salah satu atribut yang berguna yaitu attribut-atribut yang terkait dengan ortografi token seperti is_upper, is_lower, is_digit, is_title, is_punct. Sesuai namanya, attribut tersebut akan mengecek apakah token tersebut ditulis dengan huruf kecil, huruf besar, mengandung digit, mengandung tanda baca, dll.

In [14]:
doc = nlp('HP Samsung Galaxy Note 8 bisa ditebus dengan harga 11 juta rupiah (cashback 1jt).')
str_template = '{:>15} {:>10} {:>10} {:>10} {:>10} {:>10}'
print(str_template.format('token', 'is_lower', 'is_title', 'is_upper', 'is_digit', 'is_punct'))
for token in doc:
    print(str_template.format(str(token),
                              str(token.is_lower),
                              str(token.is_title),
                              str(token.is_upper),
                              str(token.is_digit),
                              str(token.is_punct)))
          token   is_lower   is_title   is_upper   is_digit   is_punct
             HP      False      False       True      False      False
        Samsung      False       True      False      False      False
         Galaxy      False       True      False      False      False
           Note      False       True      False      False      False
              8      False      False      False       True      False
           bisa       True      False      False      False      False
        ditebus       True      False      False      False      False
         dengan       True      False      False      False      False
          harga       True      False      False      False      False
             11      False      False      False       True      False
           juta       True      False      False      False      False
         rupiah       True      False      False      False      False
              (      False      False      False      False       True
       cashback       True      False      False      False      False
              1      False      False      False       True      False
             jt       True      False      False      False      False
              )      False      False      False      False       True
              .      False      False      False      False       True

Lemma

SpaCy token juga mengandung atribut lemma, yaitu bentuk lemma dari sebuah kata. Ini menarik, karena sepengatahuan saya, ini pertama kali saya melihat library lematisasi dalam bahasa Indonesia. Bentuk lemma di SpaCy didapatkan dengan cara manual, layaknya melihat sebuah kamus. Terdapat satu dictionary besar di spacy.lang.id.LOOKUP, dimana key merupakan kata dan value adalah bentuk lemmanya.

In [21]:
from spacy.lang.id import LOOKUP
import random
lemma_as_list = list(LOOKUP.items())
samples = random.choices(lemma_as_list, k=20)
for k, v in samples:
    print(f'{k}: {v}')
tempelan: tempel
cetakan: cetak
mengutuhkan: utuh
kepayahan: payah
menunjukkankan: tunjuk
memberkati: berkat
pengetikkan: tik
pemerintah: perintah
kesempatannya: sempat
berkaok-kaok: kaok
menurusnya: turus
peracunan: racun
melanggarnya: langgar
berkembang biak: kembang biak
peragaannya: raga
pengecaman: kecam
kurang-kurangan: kurang
pengadaptasian: adaptasi
kegoblokan: goblok
jaring-jaring: jaring

kalau dilihat, dictionary tersebut mengandung kata-kata formal bahasa Indonesia. Karena sistemnya adalah kamus, maka untuk kata yang tidak ada, attribut lemma dari token tersebut merupakan kata aslinya.

In [22]:
doc = nlp('tertidur tidur tercyduk')
for token in doc:
    ori = token.text
    lemma = token.lemma_  # token.lemma is integer index
    print(ori, lemma)
tertidur tidur
tidur tidur
tercyduk tercyduk

POS, NER, Dependency Tree

Atribut-attribut seperti POS, NER, dependency tree, dan embedded vector membutuhkan model machine learning, sedangkan kita menggunakan spacy.blank, bukan spacy.load, maka attribut tersebut tidak tersedia.

In [31]:
missing_attrs = ['pos_', 'tag_', 'dep_', 'prob', 'cluster', 'vector']
doc = nlp('selamat malam semuanya')
for token in doc:
    print(token.text, token.pos_, token.tag_, token.dep_, token.ent_iob_)
    print(token.prob, token.cluster, token.vector)
selamat    
-20.0 0 []
malam    
-20.0 0 []
semuanya    
-20.0 0 []

Word Shape

Word shape, juga tersedia sebagai attribut. Fungsi ini sangat berguna untuk mengurangi jumlah vocabulary anda.

In [34]:
doc = nlp('10 Mei 2018 - 15 Juni 2018')
for token in doc:
    print(f'{token.text} {token.shape_:>5}')
10    dd
Mei   Xxx
2018  dddd
-     -
15    dd
Juni  Xxxx
2018  dddd

Penutup

Tulisan diatas menggambarkan secara singkat fitur-fitur yang dapat anda gunakan untuk membantu pekerjaan NLP bahasa Indonesia menggunakan SpaCy. Tentunya tulisan ini tidak bisa mengulas semua fitur yang dimiliki library SpaCy. Pembaca disarankan untuk coba mengeksplorasi SpaCy lebih jauh. Banyak fitur-fitur yang ditulis diatas yang disediakan juga oleh library lain seperti NLTK, namun beberapa fungsi menurut saya cukup unik seperti lemma. Sekian!

Theme adapted from Hemingway2 Hugo theme by Malte Kiefer