Kalau Anda punya tumpukan teks berbahasa Indonesia yang perlu dikelompokkan otomatis, Rekan ArtonLabs, inilah jalur yang paling sering kami tempuh.
Mengklasifikasi teks berbahasa Indonesia, entah sentimen ulasan, kategori pengaduan, atau jenis berita, adalah tugas NLP yang paling sering kami kerjakan untuk klien dan riset. Model encoder seperti IndoBERT, yang dilatih khusus pada korpus Indonesia, memberi hasil jauh lebih baik daripada model multibahasa umum untuk tugas seperti ini, dan cukup kecil untuk dilatih di CPU kalau datanya tidak terlalu besar. Tulisan ini berjalan dari dataset mentah sampai model yang bisa dipanggil dari aplikasi.
Daftar Isi
- Apa Itu IndoBERT
- Menyiapkan Dataset
- Lingkungan
- Melatih dengan Trainer
- Evaluasi: Akurasi Saja Tidak Cukup
- Inferensi di Aplikasi
- Yang Membuat Perbedaan
- Penutup
Apa Itu IndoBERT
IndoBERT dari IndoBenchmark adalah model BERT yang dilatih pada korpus teks Indonesia berukuran besar. Ia tersedia di Hugging Face dengan id seperti indobenchmark/indobert-base-p1 (sekitar 124 juta parameter) dan varian lite yang jauh lebih kecil untuk perangkat terbatas. Sebagai encoder, ia tidak menghasilkan teks; ia menghasilkan representasi teks yang lalu diberi "kepala" klasifikasi kecil. Untuk tugas dengan label dari daftar tetap, pendekatan ini lebih murah, lebih cepat, dan sering lebih akurat daripada model generatif yang jauh lebih besar.
Menyiapkan Dataset
Format CSV dengan dua kolom sudah cukup: teks dan label. Beberapa hal yang menentukan hasil lebih dari arsitektur model mana pun: label yang konsisten (satu ejaan untuk tiap kelas), tidak ada duplikat antara data latih dan uji, dan distribusi kelas yang Anda ketahui. Kalau 90 persen data berlabel "netral", model yang selalu menjawab "netral" sudah 90 persen akurat dan sama sekali tidak berguna; kita kembali ke ini di bagian evaluasi. Bagi data 80/10/10 untuk latih, validasi, dan uji, dengan stratifikasi agar proporsi kelas sama di ketiganya.
import pandas as pd
from sklearn.model_selection import train_test_split
df = pd.read_csv("data.csv").drop_duplicates("teks")
latih, sisa = train_test_split(df, test_size=0.2, stratify=df.label, random_state=42)
valid, uji = train_test_split(sisa, test_size=0.5, stratify=sisa.label, random_state=42)
for nama, d in (("latih", latih), ("valid", valid), ("uji", uji)):
d.to_csv(f"{nama}.csv", index=False)
Lingkungan
pip install torch transformers datasets scikit-learn accelerate
Dengan beberapa ribu contoh dan panjang teks di bawah 256 token, pelatihan 3 epoch di CPU 4 inti memakan sekitar satu sampai dua jam untuk varian base. GPU apa pun memangkasnya menjadi menit. Pasang torch versi CPU kalau server tidak punya GPU, agar unduhannya jauh lebih kecil.
Melatih dengan Trainer
from datasets import load_dataset
from transformers import (AutoTokenizer, AutoModelForSequenceClassification,
TrainingArguments, Trainer)
import numpy as np
from sklearn.metrics import f1_score, accuracy_score
MODEL = "indobenchmark/indobert-base-p1"
data = load_dataset("csv", data_files={"train": "latih.csv", "validation": "valid.csv", "test": "uji.csv"})
label = sorted(set(data["train"]["label"]))
id2label = dict(enumerate(label)); label2id = {v: k for k, v in id2label.items()}
tok = AutoTokenizer.from_pretrained(MODEL)
def enc(b):
out = tok(b["teks"], truncation=True, max_length=256)
out["labels"] = [label2id[l] for l in b["label"]]
return out
data = data.map(enc, batched=True, remove_columns=["teks", "label"])
model = AutoModelForSequenceClassification.from_pretrained(
MODEL, num_labels=len(label), id2label=id2label, label2id=label2id)
def metrik(p):
pred = np.argmax(p.predictions, axis=1)
return {"akurasi": accuracy_score(p.label_ids, pred),
"f1_makro": f1_score(p.label_ids, pred, average="macro")}
args = TrainingArguments(output_dir="keluaran", num_train_epochs=3, learning_rate=2e-5,
per_device_train_batch_size=16, eval_strategy="epoch",
save_strategy="epoch", load_best_model_at_end=True,
metric_for_best_model="f1_makro")
trainer = Trainer(model=model, args=args, train_dataset=data["train"],
eval_dataset=data["validation"], tokenizer=tok, compute_metrics=metrik)
trainer.train()
print(trainer.evaluate(data["test"]))
trainer.save_model("model-klasifikasi")
Learning rate 2e-5 dan 3 epoch adalah titik awal yang hampir selalu masuk akal untuk BERT. load_best_model_at_end memastikan yang disimpan adalah epoch dengan F1 validasi terbaik, bukan epoch terakhir yang mungkin sudah menghafal.
Evaluasi: Akurasi Saja Tidak Cukup
Selalu laporkan F1 makro bersama akurasi, karena F1 makro memberi bobot sama pada tiap kelas, termasuk kelas kecil yang justru sering paling penting (pengaduan darurat, ulasan sangat negatif). Cetak juga matriks kebingungan dengan sklearn.metrics.confusion_matrix dan baca kesalahan yang sebenarnya; pola seperti "netral sering tertukar dengan positif" menunjukkan label yang ambigu di data, bukan kelemahan model. Bandingkan dengan baseline sederhana, misalnya TF-IDF dengan regresi logistik; kalau IndoBERT hanya unggul satu atau dua poin, baseline yang seratus kali lebih ringan mungkin pilihan yang lebih bijak untuk produksi.
Baseline TF-IDF dalam Beberapa Baris
from sklearn.pipeline import make_pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
dasar = make_pipeline(TfidfVectorizer(ngram_range=(1, 2), min_df=2),
LogisticRegression(max_iter=1000, class_weight="balanced"))
dasar.fit(latih.teks, latih.label)
print(classification_report(uji.label, dasar.predict(uji.teks)))
Baseline ini dilatih dalam detik dan sering mencapai 85 sampai 90 persen dari skor IndoBERT pada teks yang kosakatanya khas. Laporkan keduanya. Kalau selisihnya tipis, pertimbangkan baseline untuk produksi dan simpan IndoBERT untuk kasus yang benar-benar membutuhkannya.
Kelas yang Tidak Seimbang
Untuk IndoBERT, bobot kelas diterapkan dengan mengganti fungsi loss di Trainer (turunkan kelas Trainer dan timpa compute_loss dengan CrossEntropyLoss(weight=...)), atau lebih sederhana, sampel ulang data latih agar kelas kecil muncul lebih sering. Jangan menyeimbangkan data validasi dan uji; keduanya harus mencerminkan distribusi nyata, kalau tidak skornya tidak berarti untuk produksi.
Inferensi di Aplikasi
from transformers import pipeline
klasifikasi = pipeline("text-classification", model="model-klasifikasi", tokenizer="model-klasifikasi")
print(klasifikasi("Pengirimannya cepat, tapi kemasannya penyok."))
# [{'label': 'netral', 'score': 0.87}]
Muat model sekali saat aplikasi mulai, bukan per permintaan; pemuatannya memakan beberapa detik dan beberapa ratus megabyte. Di Django atau FastAPI, simpan objek pipeline di tingkat modul. Untuk throughput tinggi, kirim teks dalam batch. Simpan juga skornya, dan perlakukan prediksi dengan skor rendah sebagai "perlu ditinjau manusia", bukan sebagai jawaban.
Yang Membuat Perbedaan
Dari beberapa proyek: membersihkan data memberi peningkatan lebih besar daripada mengganti model. Teks dari media sosial perlu normalisasi ringan, misalnya menyatukan huruf berulang ("bagusss"), tetapi jangan berlebihan; IndoBERT sudah cukup tahan terhadap ragam tulisan. Untuk kelas yang tidak seimbang, pakai bobot kelas di fungsi loss atau sampel ulang kelas kecil. Dan simpan versi data bersama versi model; enam bulan lagi Anda akan ingin tahu model ini dilatih dengan data yang mana.
Penutup
Untuk klasifikasi teks Indonesia dengan label tetap, IndoBERT yang di-fine-tune adalah pilihan yang jarang mengecewakan: dilatih dalam jam di CPU, diinferensi dalam milidetik, dan hasilnya bisa dijelaskan lewat matriks kebingungan. Yang perlu dijaga adalah data yang bersih dan evaluasi yang jujur terhadap baseline.
Terima kasih sudah membaca. Dataset yang bersih adalah separuh pekerjaan; semoga separuh lainnya terasa lebih ringan setelah ini.