Pipeline spaCy di production

Arsitektur pipeline, komponen kustom, dan gabungan aturan dengan statistik.

Daftar isi

Pipeline spaCy adalah barisan komponen yang memproses satu objek Doc secara berurutan, mulai dari representasi numerik token hingga pengenalan entitas bernama. Topik ini membahas arsitektur pipeline en_core_web_sm, penambahan komponen kustom (custom component) melalui @Language.component dan nlp.add_pipe, serta gabungan aturan deterministik dengan model statistik. Setelah membacanya, urutan komponen pada pipeline dapat dibaca, komponen kustom dapat dipasang pada posisi yang benar, dan pembagian kerja antara aturan serta model statistik dapat dijelaskan.

Arsitektur pipeline en_core_web_sm#

Tokenizer memotong teks menjadi token sebelum komponen mana pun berjalan, sehingga tokenizer tidak dihitung sebagai komponen dalam pipeline. Komponen sesungguhnya adalah langkah yang membaca dan memperkaya objek Doc, dan setiap langkah menambahkan atribut baru tanpa menghapus atribut yang sudah ada. Model en_core_web_sm memuat enam komponen yang urutannya dapat dibaca langsung dari nlp.pipe_names, sebagaimana diperlihatkan berikut ini.

python Contoh: membaca urutan komponen
import spacy

nlp = spacy.load("en_core_web_sm")
print("tokenizer :", type(nlp.tokenizer).__name__)
print("komponen  :", nlp.pipe_names)
print("bahasa    :", nlp.meta["lang"])
text Keluaran
tokenizer : Tokenizer
komponen  : ['tok2vec', 'tagger', 'parser', 'attribute_ruler', 'lemmatizer', 'ner']
bahasa    : en

Urutan tersebut bukan kebetulan, melainkan konsekuensi ketergantungan antarkomponen. Komponen tok2vec menghasilkan tensor kontekstual kecil yang dipakai bersama oleh komponen lain, sehingga komponen itu diletakkan paling depan. Tagger melabel kelas kata, parser menyusun pohon dependensi, attribute_ruler menyebarkan informasi tag ke token lain, lemmatizer memilih lemma berdasarkan tag, dan ner menandai entitas bernama di ujung barisan. Diagram berikut menggambarkan alur dari teks mentah hingga Doc yang siap dibaca.

teksTokenizertok2vectaggerparserattribute_rulerlemmatizernerDoc
Urutan komponen pada pipeline en_core_web_sm

Diagram di atas sekaligus menunjukkan titik sisip untuk komponen pihak pengguna. Komponen kustom dapat dipasang di posisi mana pun pada barisan, dan urutannya menentukan keluaran mana yang bisa dibaca. Komponen yang membutuhkan label tagger harus berada setelah tagger, sedangkan komponen yang menyediakan entitas untuk ner sebaiknya berada sebelum ner.

Komponen kustom dengan Language.component#

Komponen kustom adalah fungsi yang menerima objek Doc, menambahkan atau mengubah atributnya, lalu mengembalikan Doc yang sama. Dekorator @Language.component("nama") mendaftarkan fungsi semacam itu sebagai komponen tanpa data antarpemanggilan, sedangkan Language.factory dipakai apabila komponen menyimpan data saat inisialisasi, misalnya daftar pola yang dimuat dari konfigurasi. Komponen hasil pendaftaran dipasang melalui nlp.add_pipe("nama"), dan posisinya diatur dengan argumen before, after, first, atau last.

Kasus yang sering muncul di production adalah pola khusus domain yang tidak ada pada korpus pelatihan model, misalnya nomor tiket internal. Skrip berikut mendefinisikan komponen penanda_tiket yang mencari pola nomor tiket dengan regex, lalu memasangnya sebelum ner.

python komponen_tiket.py
import re
import spacy
from spacy.language import Language

nlp = spacy.load("en_core_web_sm")
teks = "Return ticket TIK-2024-ABC to the front desk, and keep receipt TIK-2024-QRS."

doc = nlp(teks)
print("NER saja        :", [(e.text, e.label_) for e in doc.ents])

@Language.component("penanda_tiket")
def penanda_tiket(doc):
    pola = re.compile(r"TIK-\d{4}-[A-Z]{3}")
    spans = []
    for m in pola.finditer(doc.text):
        span = doc.char_span(m.start(), m.end(), label="TIKET", alignment_mode="expand")
        if span is not None:
            spans.append(span)
    if spans:
        doc.set_ents(spans, default="outside")
    return doc

nlp.add_pipe("penanda_tiket", before="ner")
print("urutan pipeline :", nlp.pipe_names)

doc = nlp(teks)
print("dengan komponen :", [(e.text, e.label_) for e in doc.ents])
text Keluaran
NER saja        : [('TIK-2024-ABC', 'WORK_OF_ART')]
urutan pipeline : ['tok2vec', 'tagger', 'parser', 'attribute_ruler', 'lemmatizer', 'penanda_tiket', 'ner']
dengan komponen : [('TIK-2024-ABC', 'TIKET'), ('TIK-2024-QRS', 'TIKET')]

Baris pertama keluaran memperlihatkan kelemahan model statistik. Komponen ner menebak TIK-2024-ABC sebagai WORK_OF_ART karena bentuknya mirip judul, dan tebakan itu salah. Setelah komponen dipasang dengan before="ner", urutan pipeline menunjukkan penanda_tiket tepat sebelum ner, dan kedua nomor tiket berlabel TIKET dengan benar. Prosesnya deterministik, yaitu pola regex yang sama selalu menghasilkan entitas yang sama, tanpa bergantung pada data pelatihan.

Dua detail teknis pada komponen itu patut dicatat. Metode doc.char_span memetakan rentang karakter hasil regex menjadi rentang token, karena entitas spaCy harus sejajar dengan token, dan doc.set_ents dengan default="outside" memasang semua rentang sekaligus sambil menganggap token lain berada di luar entitas.

Gabungan aturan dan model statistik#

EntityRuler adalah komponen bawaan spaCy yang menetapkan entitas dari daftar pola token, dan komponen itu dirancang untuk dipasang sebelum ner. Pembagian kerjanya sederhana, yaitu aturan deterministik menangani pola yang bentuknya tetap, sedangkan model statistik menangani pola yang berubah-ubah, misalnya tanggal dan nama orang. Gabungan semacam ini lazim di production karena pola internal sebuah organisasi hampir tidak pernah muncul pada korpus pelatihan model publik.

Skrip berikut memasang entity_ruler dengan satu pola token untuk nomor tiket, kemudian membandingkan jalur tanpa komponen statistik dengan jalur penuh.

python aturan_dan_statistik.py
import spacy

nlp = spacy.load("en_core_web_sm")

# pola token mengikuti hasil tokenizer, yaitu TIK-2024, -, ABC
pola_tiket = [
    {"TEXT": {"REGEX": "^TIK-[0-9]{4}$"}},
    {"TEXT": "-"},
    {"TEXT": {"REGEX": "^[A-Z]{3}$"}},
]
ruler = nlp.add_pipe("entity_ruler", before="ner")
ruler.add_patterns([{"label": "TIKET", "pattern": pola_tiket}])

teks = "Return ticket TIK-2024-ABC to the front desk before March 3."

nonaktif = [p for p in nlp.pipe_names if p != "entity_ruler"]
doc_aturan = nlp(teks, disable=nonaktif)
print("hanya aturan       :", [(e.text, e.label_) for e in doc_aturan.ents])

doc_lengkap = nlp(teks)
print("aturan + statistik :", [(e.text, e.label_) for e in doc_lengkap.ents])
text Keluaran
hanya aturan       : [('TIK-2024-ABC', 'TIKET')]
aturan + statistik : [('TIK-2024-ABC', 'TIKET'), ('March 3', 'DATE')]

Pada jalur pertama, hanya entity_ruler yang berjalan, sehingga satu-satunya entitas adalah TIKET. Pada jalur penuh, ner menambahkan March 3 sebagai DATE di samping hasil aturan, dan kedua sumber itu hidup berdampingan tanpa saling menimpa karena polanya tidak bertabrakan. Satu hal penting terlihat pada definisi pola, yaitu pola ditulis mengikuti hasil tokenizer, bukan bentuk permukaan teks, karena tokenizer memecah TIK-2024-ABC menjadi token TIK-2024, -, dan ABC.

Kesalahan umum#

  1. Memasang komponen kustom setelah ner. Komponen yang memanggil set_ents setelah ner berjalan akan menimpa seluruh keluaran ner, karena pemanggilan itu mengganti entitas pada seluruh dokumen. Pasang komponen sebelum ner, misalnya dengan before="ner", agar ner membaca entitas yang sudah ada dan menambahkan sisanya.
  2. Mendaftarkan nama komponen yang sudah terdaftar. Dekorator dengan nama yang sama pada satu sesi Python memunculkan error pendaftaran ganda, dan kasus ini sering terjadi saat sel pada notebook dijalankan ulang. Gunakan nama komponen yang unik, atau jalankan ulang sesi Python dari awal sebelum mendaftarkannya kembali.
  3. Menganggap perubahan pipeline tersimpan otomatis. Pemanggilan nlp.add_pipe hanya mengubah objek nlp pada sesi tersebut, sehingga pipeline kembali ke bentuk bawaan pada sesi berikutnya. Simpan komposisi pipeline pada kode inisialisasi yang dijalankan saat aplikasi berjalan, atau pada berkas konfigurasi yang dibaca spacy.load.

Ringkasan#

  • Pipeline en_core_web_sm menjalankan enam komponen berurutan, yaitu tok2vec, tagger, parser, attribute_ruler, lemmatizer, dan ner, dengan tokenizer di luar barisan komponen.
  • Komponen kustom didaftarkan melalui @Language.component atau Language.factory, dipasang melalui nlp.add_pipe, dan posisinya dikendalikan argumen before, after, first, atau last.
  • EntityRuler yang dipasang sebelum ner memisahkan kerja aturan dari kerja statistik, sehingga pola internal tetap terdeteksi dan pola umum tetap ditangani model.
  • Pola token wajib disusun dari hasil tokenisasi, karena pola yang tidak sejajar token tidak pernah cocok tanpa pesan error apa pun.

Referensi#