Dependency Parsing Bahasa Indonesia dengan Lib UUParser

Update: demo visualisasi dependency dapat dilihat di: http://nlp.yuliadi.pro/ (tab ke-3)

Salah satu cara memandang struktur sebuah kalimat adalah dengan dependency grammar. Pada  dependency grammar,  setiap kata dianggap bergantung dengan kata yang lain. Sebagai contoh untuk kalimat “Ibu pergi ke pasar”,  maka kata “pergi” adalah akar kalimat (root), kata “ibu” dan “pasar” bergantung pada kata “pergi” dan kata “ke” bergantung pada “pasar”.  Dengan dependency grammar makna suatu kalimat dapat lebih mudah dipahami (oleh komputer). Misalnya kalimat: “Budi mempelajari ikan dari udara”.  Kalimat ini dapat memiliki dua arti, “[mempelajari ikan] dari udara” atau “mempelajari [ikan dari udara]”. Manusia  dengan cepat dapat menentukan makna yang benar adalah yang “[mempelajari ikan] dari udara” karena manusia memiliki konteks dan pengetahuan umum (tidak masuk akal ada ikan dari udara).

Dengan dependency grammar, kalimat tersebut dapat digambarkan seperti gambar berikut. Panah menunjukan arah ketergantungan. Gambar kedua memperlihatkan struktur dalam bentuk pohon.  Dapat dilihat bahwa kata udara bergantung pada mempelajari, bukan ikan. Catatan: salah buat gambar, harusnya “ikan” bukan “burung” 🙂

dep_grammar

Hubungan dependency  juga dapat dirinci maknanya, misalnya pada gambar di atas, hubungan antara Budi-mempelajari adalah nsubj = subyek. Hubungan yang lain adalah obj= “objek”, obl = “oblique argumentdsb. Rincian relasi yang digunakan universal dependency dapat dilihat di: http://universaldependencies.org/u/dep/ .    Universal dependency adalah proyek yang menyediakan  dataset POS-Tag, dependency dan morfologi untuk banyak bahasa, termasuk Bahasa Indonesia. Dataset ini yang akan kita gunakan untuk membuat model.

Penjelasan lebih lanjut tentang dependency grammar bisa dilihat di (materi dari Stanford): https://web.stanford.edu/~jurafsky/slp3/14.pdf  dan videonya: https://youtu.be/PVShkZgXznc 

Sebelumnya saya sudah membahas mengenai dependency parsing dengan Syntaxnet. Tapi Syntaxnet ini menggunakan lib  dalam bahasa C, yang  lebih sulit untuk diinstall dan lebih sulit  dimodifikasi. Saya ingin mencoba lib lain yang lebih sederhana.

Alternatif pertama adalah parser T. Dozat  https://github.com/tdozat/Parser-v1   tetapi saya kesulitan untuk menentukan lib yang diperlukan lib parser ini. Ternyata untuk proyek Python penting untuk menyebutkan proyek tersebut menggunakan lib apa saja. Banyak nama lib yang sama (tidak ada namaspace seperti package di Java), atau mungkin karena saya tidak paham saja.

Kemudian saya menemukan lib lain, BIST parser: https://github.com/elikip/bist-parser yang menggunakan Dynet dengan teknik transition based dan biLSTM. Sempat lihat-lihat Dynet, menarik juga, pendekatannya berbeda dengan Tensorflow dan sepertinya lebih mudah dipahami.  Ada beberapa code di BIST parser yang perlu disesuaikan karena masih menggunakan Dynet versi lama.

Terakhir saya lihat lib UUParser (https://github.com/UppsalaNLP/uuparser) yang merupakan fork dari BIST-parser. Codenya lebih baru dan terlihat ada usaha merapikan code BIST-parser. Jadi saya memilih lib ini. (lib ini menggunakan Python 2.7)

Pertama siapkan data training dari Universal Dependencies (http://universaldependencies.org/), pilih dan download dataset Bahasa Indonesia. Pilih dataset GSD yang memiliki data train, dev dan test.

Berdasarkan data tersebut, lakukan training sesuai petunjuk yang ada di https://github.com/UppsalaNLP/uuparser. Tanpa modifikasi parameter, hasilnya sudah lebih baik dari Syntaxnet. Nilai UAS dan LAS masing-masing 81.97 dan 75.37 (Syntaxnet: 80 dan 73). Mungkin nanti bisa dioptimasi lagi, cuma  bagi saya sudah cukup untuk sekarang.

Saya tambahkan sedikit code supaya bisa memproses input string, bukan file. Berikut codenya. Jangan lupa  tambah utils.read_conll_kalimat yang merupakan modifikasi dari utils.read_conll  (tadinya memproses file jadi memproses list teks). Isi variabel modeldir, modelfile, param sesuai hasil training.


from arc_hybrid import ArcHybridLSTM
import pickle, utils, os
# sesuaikan
modelDir="/home/yudiwbs/dataset/model/uuparser/id"
params="/home/yudiwbs/dataset/model/uuparser/id/params.pickle"
modelFile="barchybrid.model"

with open(params, 'r') as paramsfp:
    words, w2i, pos, rels, cpos, langs, stored_opt, ch = pickle.load(paramsfp)
    parser = ArcHybridLSTM(words, pos, rels, cpos, langs, w2i,
                           ch, stored_opt)
    model = os.path.join(modelDir, modelFile)
    parser.Load(model)

    kalimatStr = "Ibu pergi ke pasar"
    kalimat = [str(counter+1) + "\t" + kal for counter, kal in enumerate(kalimatStr.split())]
    kalimat.append("")

    data = utils.read_conll_kalimat(kalimat)
    pred = list(parser.Predict(data))
    for p in pred:
        print(p)

Hasilnya:

0	*root*	*root*	ROOT-CPOS	ROOT-POS	_	-1	rroot	_	_
1	Ibu	_	_	_	_	2	nsubj	_	_
2	pergi	_	_	_	_	0	root	_	_
3	ke	_	_	_	_	4	case	_	_
4	pasar	_	_	_	_	2	obl	_	_

9 tanggapan untuk “Dependency Parsing Bahasa Indonesia dengan Lib UUParser”

  1. Bagus sekali tulisannya, pak. Oh, ya, untuk ‘read_conll_kalimat’ itu fungsi tambahan kan, pak? Boleh dijabarkan, pak? Terima kasih.

    1. Nggak tambahan, cuma modifikasi dikit. Tadinya read_conll itu inputnya file teks. Saya copas function-nya lalu di-edit sedikit supaya inputnya jadi kalimat. Cuma edit 3 baris kok. Kalau anda tdk bisa python, pake aja yang aslinya (read_conll), tapi inputnya file teks.

      1. Terima kasih balasannya. Iya, pak, kemarin saya kurang teliti baca, ternyata modifikasi dari read_conll. Kemarin-kemarin saya sudah modifikasi dengan mengganti parameter ‘filename’ menjadi ‘fh’, dan menghapus bagian codecs.open. Tapi variable ‘tok’ hanya berisi 2 value array, yaitu angka 1 dan ibu. Sehingga di bagian ConllEntry mengalami error karena tidak ada tok[2]. Untuk mendapatkan nsubj, case, root, obl, itu dari mana ya, pak? Karena variable ‘tok’ hanya berisi hasil split kalimat.
        Terima kasih.

  2. parameter filename diganti dengan list kalimat (yang antar kata dipisahkan tab) yang mau diparsing. Jadi seperti ini:
    def read_conll_kalimat(kalimat, language=None, maxSize=-1, hard_lim=False, vocab_prep=False, drop_nproj=False):
    # fh = codecs.open(filename,’r’,encoding=’utf-8′) –> komentari
    # print “Reading ” + filename –> komentari
    if vocab_prep and not hard_lim:
    maxSize = -1 # when preparing the vocab with a soft limit we need to use the whole corpus
    ts = time.time()
    dropped = 0
    read = 0
    root = ConllEntry(0, ‘*root*’, ‘*root*’, ‘ROOT-POS’, ‘ROOT-CPOS’, ‘_’, -1, ‘rroot’, ‘_’, ‘_’)
    root.language_id = language
    tokens = [root]
    yield_count = 0
    if maxSize > 0 and not hard_lim:
    all_tokens = []
    for line in kalimat:
    tok = line.strip().split(‘\t’)
    …. dst

    sedangkan untuk menyiapkan string untuk masuk ke kalimat adalah sbb: (mungkin bisa saja nanti diletakkan di fungsi read_connl_kalimat biar parameter nanti bisa string biasa)

    kalimat = [str(counter+1) + “\t” + kal for counter, kal in enumerate(kalimatStr.split())]
    kalimat.append(“”)

  3. Saya cek sudah sama. Hanya nama variable-nya saja yang beda. Ini punya saya, Pak, https://pastebin.com/S2Tng2cC.
    Tapi error di bagian line 321, in read_conll_kalimat
    token = ConllEntry(int(tok[0]), tok[1], tok[2], tok[4], tok[3], tok[5], int(tok[6]) if tok[6] != ‘_’ else -1, tok[7], tok[8], tok[9])
    IndexError: list index out of range.
    isi dari tok hanya [‘1’, ‘Ibu’]
    Terima kasih.

    1. Ternyata bagian itu juga saya edit:
      #token = ConllEntry(int(tok[0]), tok[1], tok[2], tok[4], tok[3], tok[5], int(tok[6]) if tok[6] != ‘_’ else -1, tok[7], tok[8], tok[9])
      #print(“tok0=”+tok[0])
      #print(“tok1=”+tok[1])
      token = ConllEntry(int(tok[0]), tok[1], None, None, None, None,None, None, None, None)

      tapi lupa kenapa alasannya.

  4. Terima kasih, pak, sudah berhasil. Tadinya juga saya isi tok[2], dst. Dengan string kosong. Tapi tetap error. Saya ganti dengan None juga sama error di bagian:
    tok.parent_entry = [i for i in conll_tokens if i.id == tok.parent_id][0].
    Tapi saya coba hapus [0], sehingga menjadi:
    tok.parent_entry = [i for i in conll_tokens if i.id == tok.parent_id]
    Sekarang sudah berhasil, dan outputnya sama seperti punya bapak.
    Terima kasih bantuannya, Pak.

    1. Sepertinya sih nggak, walaupun biLSTM harusnya bisa untuk task sequence labeling ya, jadi mungkin bisa juga. Tapi menurut saya sih mending cari lib yang spesifik untuk sequence labeling aja seperti Anago.

Tinggalkan Balasan

Isikan data di bawah atau klik salah satu ikon untuk log in:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout /  Ubah )

Foto Google

You are commenting using your Google account. Logout /  Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout /  Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout /  Ubah )

Connecting to %s