BERTopic: Pemodelan Topik Modern dengan Embedding
BERTopic adalah library pemodelan topik yang menggabungkan embedding transformer, reduksi dimensi, clustering berbasis kepadatan, dan representasi class-based TF-IDF untuk menemukan tema dalam kumpulan teks. Alih-alih memperlakukan dokumen sebagai kantong kata seperti metode klasik, BERTopic bekerja pada level makna, sehingga biasanya menghasilkan topik yang lebih koheren dan mudah ditafsirkan. Tutorial ini menelusuri seluruh pipeline BERTopic memakai contoh praktis: mengelompokkan korpus tiket dukungan pelanggan.
Apa Itu Pemodelan Topik dan Mengapa BERTopic Membantu
Pemodelan topik adalah tugas mengelompokkan kumpulan dokumen secara otomatis menjadi tema ("topik") dan menggambarkan tiap tema dengan sekumpulan kata representatif. Ini bersifat unsupervised: Anda tidak memberi label, model menemukan strukturnya sendiri.
Pendekatan tradisional adalah Latent Dirichlet Allocation (LDA). LDA memperlakukan tiap dokumen sebagai campuran topik dan tiap topik sebagai distribusi atas kata. LDA bekerja pada hitungan kata mentah, sehingga mengabaikan urutan dan makna kata. Dalam praktiknya ini menimbulkan beberapa masalah yang berulang:
- Sulit menangani teks pendek (tweet, tiket, ulasan) di mana ko-okurensi kata jarang.
- Mengharuskan Anda menetapkan jumlah topik di awal, yang sulit ditebak.
- Topik yang dihasilkan sering mencampur kata yang tidak berkaitan karena tidak ada konsep kemiripan semantik.
BERTopic menempuh jalur berbeda. Ia merepresentasikan tiap dokumen sebagai embedding padat dari model bahasa, sehingga dokumen yang maknanya mirip berdekatan dalam ruang vektor meski tidak berbagi kata yang persis sama. Kemudian ia mengelompokkan embedding tersebut dan, baru di akhir, menurunkan kata kunci per kelompok. Inti idenya adalah clustering dan ekstraksi kata kunci dipisahkan, sehingga seluruh proses menjadi modular dan mudah disesuaikan.
Pipeline default memiliki lima tahap:
Setiap tahap adalah komponen yang bisa ditukar, dan inilah alasan utama BERTopic fleksibel.
Instalasi
pip install bertopic
BERTopic otomatis menarik sentence-transformers, UMAP, HDBSCAN, dan scikit-learn. Untuk fitur opsional, pasang ekstra yang Anda butuhkan:
# Menyimpan model dalam format safetensors
pip install bertopic[safetensors]
Dukungan visualisasi (plotly sudah termasuk, datamapplot ekstra)
pip install bertopic[visualization]
Label topik berbasis LLM via backend OpenAI
pip install bertopic[openai]
Jika punya GPU, pasang build CUDA dari PyTorch lebih dulu agar tahap embedding berjalan lebih cepat.
Model Pertama
Mari bangun model pada sekumpulan kecil tiket dukungan. Pada proyek nyata Anda akan memuat ribuan dokumen; API-nya identik.
from bertopic import BERTopic
docs = [
"Tagihan saya menampilkan biaya yang tidak saya setujui bulan ini",
"Saya ditagih dua kali untuk langganan yang sama, mohon refund",
"Aplikasi crash setiap kali saya membuka halaman laporan",
"Tab laporan membeku lalu aplikasi tertutup sendiri",
"Bagaimana cara reset password? Email reset tidak pernah datang",
"Saya tidak bisa login, tautan reset password rusak",
"Bisa jelaskan perbedaan paket Pro dan Team?",
"Fitur apa saja yang termasuk dalam harga paket Team?",
# ... ribuan lainnya pada praktiknya
]
topicmodel = BERTopic()
topics, probs = topicmodel.fittransform(docs)
fittransform mengembalikan dua array. topics berisi id topik yang diberikan ke tiap dokumen, dan probs berisi probabilitas tiap dokumen termasuk ke topik yang ditetapkan.
Memeriksa Topik
gettopicinfo() mengembalikan DataFrame yang merangkum setiap topik: id-nya, berapa dokumen yang dikandung, nama otomatis singkat, dan kata kunci teratas.
info = topicmodel.gettopicinfo()
print(info[["Topic", "Count", "Name"]])
Untuk melihat kata kunci dan bobot c-TF-IDF-nya pada satu topik, gunakan gettopic:
print(topicmodel.gettopic(0))
[('refund', 0.041), ('ditagih', 0.038), ('tagihan', 0.033), ...]
Untuk menemukan topik yang paling dekat dengan suatu frasa, gunakan findtopics:
similartopics, similarity = topicmodel.findtopics("masalah tagihan", topn=3)
print(similar
topics, similarity)
Topik -1: Outlier
BERTopic memakai HDBSCAN secara default, dan HDBSCAN tidak memaksa setiap dokumen masuk ke suatu kelompok. Dokumen yang tidak cocok dengan wilayah padat mana pun diberi topik -1, yaitu topik outlier. Ini fitur, bukan bug: ia mencegah dokumen yang berisik atau unik mencemari topik nyata. Anda bisa mengurangi atau menugaskan ulang outlier ini nanti (dibahas di bawah), tetapi jangan pernah menafsirkan topik -1 sebagai tema yang bermakna.
Pipeline Modular
Setiap tahap pada pipeline default bisa diganti. Anda mengoper komponen ke konstruktor BERTopic. Berikut kita bangun masing-masing secara eksplisit lalu merakitnya.
1. Model Embedding
Secara default BERTopic memakai all-MiniLM-L6-v2, model bahasa Inggris yang cepat. Anda bisa mengoper model sentence-transformers apa pun berdasarkan nama, atau objek model.
from sentencetransformers import SentenceTransformer
embedding
model = SentenceTransformer("all-MiniLM-L6-v2")
topicmodel = BERTopic(embeddingmodel=embeddingmodel)
Untuk korpus non-Inggris atau campuran bahasa, gunakan model multilingual:
embeddingmodel = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
topicmodel = BERTopic(embeddingmodel=embeddingmodel)
Ada juga pintasan untuk dukungan multilingual tanpa Anda memilih model sendiri:
topicmodel = BERTopic(language="multilingual")
2. Reduksi Dimensi dengan UMAP
Embedding berdimensi tinggi (384 atau 768 dimensi). Clustering bekerja lebih baik di ruang berdimensi rendah, jadi BERTopic mereduksinya dengan UMAP. Parameter terpenting adalah nneighbors (struktur lokal vs global) dan ncomponents (dimensi tujuan).
from umap import UMAP
umapmodel = UMAP(
nneighbors=15,
ncomponents=5,
mindist=0.0,
metric="cosine",
randomstate=42, # setel ini untuk hasil yang bisa direproduksi
)
Menyetel randomstate sangat penting jika Anda ingin topik yang sama di setiap eksekusi. UMAP bersifat stokastik, jadi tanpa itu clustering akan sedikit bergeser tiap kali.
3. Clustering dengan HDBSCAN
HDBSCAN menemukan kelompok dengan kepadatan beragam dan menandai titik berkepadatan rendah sebagai outlier. minclustersize mengendalikan topik terkecil yang diizinkan; nilai lebih besar menghasilkan topik yang lebih sedikit dan luas.
from hdbscan import HDBSCAN
hdbscanmodel = HDBSCAN(
minclustersize=15,
metric="euclidean",
clusterselectionmethod="eom",
predictiondata=True, # diperlukan untuk transform() dan reduksi outlier
)
Setel predictiondata=True agar model nantinya dapat menugaskan dokumen baru dan mengurangi outlier.
4. Vectorizer (CountVectorizer)
Setelah clustering, BERTopic menokenisasi dokumen untuk membangun kata kunci topik. CountVectorizer memberi Anda kendali atas stop word, n-gram, dan frekuensi minimum. Menghapus stop word di sini, setelah clustering, aman karena tidak memengaruhi cara dokumen dikelompokkan, hanya cara topik diberi label.
from sklearn.featureextraction.text import CountVectorizer
vectorizermodel = CountVectorizer(
stopwords="english",
ngramrange=(1, 2), # sertakan bigram seperti "reset password"
mindf=2,
)
5. Representasi c-TF-IDF
c-TF-IDF (class-based TF-IDF) memperlakukan semua dokumen dalam satu kelompok sebagai satu dokumen dan menghitung TF-IDF antar kelompok. Kata yang sering muncul di satu kelompok tetapi jarang di kelompok lain mendapat skor tinggi, sehingga menjadi kata kunci yang baik. Anda bisa menyetelnya dengan ClassTfidfTransformer.
from bertopic.vectorizers import ClassTfidfTransformer
ctfidfmodel = ClassTfidfTransformer(
reducefrequentwords=True, # turunkan bobot kata yang sangat umum
)
Merakit Pipeline
topicmodel = BERTopic(
embeddingmodel=embeddingmodel,
umapmodel=umapmodel,
hdbscanmodel=hdbscanmodel,
vectorizermodel=vectorizermodel,
ctfidfmodel=ctfidfmodel,
verbose=True,
)
topics, probs = topicmodel.fittransform(docs)
Model Representasi: Label Topik yang Lebih Baik
Kata kunci c-TF-IDF adalah dasar yang solid, tetapi bisa redundan atau terlalu umum. Model representasi menyempurnakan daftar kata kunci setelahnya tanpa clustering ulang. Anda mengopernya melalui representationmodel.
KeyBERTInspired
Komponen ini memberi peringkat ulang kata kunci kandidat berdasarkan kemiripan semantiknya dengan topik, menghasilkan label yang terbaca lebih alami.
from bertopic.representation import KeyBERTInspired
representationmodel = KeyBERTInspired()
topicmodel = BERTopic(representationmodel=representationmodel)
MaximalMarginalRelevance
MMR mengurangi redundansi dengan menghukum kata kunci yang terlalu mirip dengan yang sudah dipilih, sehingga himpunannya lebih beragam. Parameter diversity berkisar dari 0 (hanya relevansi) hingga 1 (keberagaman maksimum).
from bertopic.representation import MaximalMarginalRelevance
representationmodel = MaximalMarginalRelevance(diversity=0.3)
Menggabungkan Representasi
Anda bisa menerapkan beberapa representasi dan membandingkannya dengan mengoper sebuah dictionary. BERTopic menghitung masing-masing dan menyimpannya di bawah kunci yang diberikan.
from bertopic.representation import KeyBERTInspired, MaximalMarginalRelevance
representationmodel = {
"KeyBERT": KeyBERTInspired(),
"MMR": MaximalMarginalRelevance(diversity=0.3),
}
topicmodel = BERTopic(representationmodel=representationmodel)
Label Berbasis LLM (Opsional)
Untuk judul topik yang mudah dibaca manusia, Anda bisa meminta large language model meringkas tiap kelompok. BERTopic mengirim beberapa dokumen representatif dan kata kunci ke model lalu memakai responsnya sebagai label.
from bertopic.representation import OpenAI
import openai
client = openai.OpenAI(apikey="KUNCIANDA")
prompt = "Kata kunci topik: [KEYWORDS]\nDokumen: [DOCUMENTS]\nBeri label topik singkat."
representationmodel = OpenAI(client, model="gpt-4o-mini", prompt=prompt, chat=True)
topicmodel = BERTopic(representationmodel=representationmodel)
Ini opsional dan melakukan panggilan jaringan, jadi jauhkan dari pipeline offline atau yang sensitif terhadap biaya.
Mengendalikan Jumlah Topik
BERTopic tidak mengharuskan Anda menetapkan jumlah topik, tetapi sering kali Anda ingin topik lebih sedikit daripada yang dihasilkan HDBSCAN secara default.
mintopicsize adalah tuas paling sederhana. Ia dipetakan ke minclustersize pada HDBSCAN dan menetapkan jumlah minimum dokumen yang dibutuhkan sebuah topik. Nilai lebih besar berarti topik lebih sedikit dan luas.
topicmodel = BERTopic(mintopicsize=20)
nrtopics memungkinkan Anda meminta jumlah target atau "auto" agar BERTopic menggabungkan topik serupa secara otomatis.
topicmodel = BERTopic(nrtopics=10) # gabungkan menjadi ~10 topik
topicmodel = BERTopic(nrtopics="auto") # gabungkan berdasarkan kemiripan
Anda juga bisa mengurangi topik setelah fitting, yang menghindari embedding ulang:
topicmodel.reducetopics(docs, nrtopics=8)
Mengurangi Outlier
Jika topik -1 menampung terlalu banyak dokumen, Anda bisa menugaskannya ulang ke topik nyata terdekat. reduceoutliers mendukung beberapa strategi; c-tf-idf dan embeddings adalah pilihan umum.
newtopics = topicmodel.reduceoutliers(docs, topics, strategy="c-tf-idf")
Simpan penugasan baru agar label mencerminkannya
topic
model.updatetopics(docs, topics=newtopics)
Lakukan ini dengan hati-hati: memaksa outlier masuk ke topik dapat mengaburkan kelompok yang semula bersih. Kurangi hanya bila konsumen di hilir tidak bisa menangani kategori "tak tertugaskan".
Visualisasi
BERTopic menyertakan visualisasi Plotly interaktif. Masing-masing mengembalikan figure yang bisa Anda tampilkan atau simpan ke HTML.
# Peta jarak antar-topik (topik sebagai gelembung di 2D)
topicmodel.visualizetopics()
Kata kunci teratas per topik sebagai bar chart
topicmodel.visualizebarchart(topntopics=8)
Hubungan hierarkis antar topik
topicmodel.visualizehierarchy()
Dokumen diproyeksikan ke 2D, diwarnai berdasarkan topik
topicmodel.visualizedocuments(docs)
Untuk visualizedocuments akan lebih cepat bila Anda mengoper embedding yang sudah dihitung (lihat di bawah) dan koordinat 2D yang sudah direduksi agar plot tidak menghitung ulang.
fig = topicmodel.visualizebarchart(topntopics=8)
fig.write
html("topicsbarchart.html")
Topik Lanjutan
Topik Sepanjang Waktu (Dynamic Topic Modeling)
Jika dokumen Anda punya timestamp, Anda bisa melacak naik-turunnya topik. Anda fit model biasa dulu, lalu menghitung representasi per bin waktu.
topicsovertime = topicmodel.topicsovertime(
docs,
timestamps, # daftar tanggal sejajar dengan docs
nrbins=20,
)
topicmodel.visualizetopicsovertime(topicsovertime, topntopics=10)
Ini menggunakan kembali topik global yang sama dan hanya menghitung ulang c-TF-IDF dalam tiap irisan waktu, sehingga definisi topik tetap konsisten sepanjang lini masa.
Topik per Kelas
Ketika dokumen membawa kategori (lini produk, wilayah, kanal), Anda bisa melihat bagaimana tiap topik terungkap dalam tiap kelas.
topicsperclass = topicmodel.topicsperclass(docs, classes=ticketchannels)
topic
model.visualizetopicsperclass(topicsperclass, topntopics=10)
Pemodelan Topik Hierarkis
Untuk memahami bagaimana topik halus menyatu menjadi tema yang lebih luas, bangun sebuah hierarki.
hierarchicaltopics = topicmodel.hierarchicaltopics(docs)
topicmodel.visualizehierarchy(hierarchicaltopics=hierarchicaltopics)
Pemodelan Topik Terpandu dan Berbenih (Guided/Seeded)
Jika Anda sudah tahu beberapa tema seharusnya ada, oper kata benih untuk mengarahkan model ke arah itu.
seedtopiclist = [
["tagihan", "invoice", "refund", "biaya"],
["login", "password", "reset", "akses"],
]
topicmodel = BERTopic(seedtopiclist=seedtopiclist)
Pemodelan Topik Zero-Shot
Zero-shot memungkinkan Anda mendefinisikan label topik di awal dan menugaskan dokumen ke label tersebut, sambil tetap menemukan topik baru untuk dokumen yang tidak cocok dengan satu pun.
zeroshottopiclist = ["masalah tagihan", "aplikasi crash", "akses akun"]
topic
model = BERTopic(
zeroshottopiclist=zeroshottopiclist,
zeroshotminsimilarity=0.85,
)
topics, probs = topicmodel.fittransform(docs)
Memprediksi pada Dokumen Baru
Setelah di-fit, transform menugaskan topik ke dokumen yang belum terlihat tanpa pelatihan ulang. Ini memerlukan predictiondata=True pada model HDBSCAN.
newdocs = [
"Saya terus ditagih setelah membatalkan paket",
"Dashboard menjadi kosong saat saya ekspor ke PDF",
]
newtopics, newprobs = topicmodel.transform(newdocs)
print(newtopics)
Memakai Embedding Pra-hitung untuk Kecepatan
Embedding adalah tahap paling lambat. Jika Anda bereksperimen dengan pengaturan UMAP atau HDBSCAN berulang kali, hitung embedding sekali lalu gunakan kembali.
from sentencetransformers import SentenceTransformer
embeddingmodel = SentenceTransformer("all-MiniLM-L6-v2")
embeddings = embeddingmodel.encode(docs, showprogressbar=True)
Oper array langsung; BERTopic melewati tahap embedding
topicmodel = BERTopic(embeddingmodel=embeddingmodel)
topics, probs = topicmodel.fittransform(docs, embeddings=embeddings)
Anda kemudian dapat mengubah parameter clustering dan fit ulang pada array embeddings yang sama dalam hitungan detik, bukan menit.
Menyimpan dan Memuat Model
BERTopic mendukung tiga opsi serialisasi. Pendekatan yang disarankan adalah safetensors, yang menyimpan bobot topik secara ringkas dan memungkinkan Anda memuat ulang model embedding berdasarkan nama alih-alih mem-pickle-nya.
# Safetensors (disarankan): kecil, aman, portabel
topicmodel.save(
"supportmodel",
serialization="safetensors",
savectfidf=True,
saveembeddingmodel="all-MiniLM-L6-v2",
)
Format PyTorch
topicmodel.save("supportmodel", serialization="pytorch")
Pickle (menyimpan semuanya, tetapi mengikat Anda ke versi library)
topicmodel.save("supportmodel.pkl", serialization="pickle")
Memuat mengikuti pemanggilan save:
from bertopic import BERTopic
loaded = BERTopic.load("supportmodel")
Utamakan safetensors atau pytorch untuk produksi. Pickle praktis tetapi rapuh saat upgrade versi dan tidak aman dimuat dari sumber yang tidak tepercaya.
Praktik Terbaik
- Praproses secukupnya. Jangan ubah ke huruf kecil atau hapus stop word sebelum embedding; model transformer memakai konteks itu. Terapkan penghapusan stop word pada tahap
CountVectorizer, di mana ia hanya memengaruhi label. - Setel
mintopicsizelebih dulu. Ini pengaturan paling berpengaruh. Mulai sekitar 1-2% dari ukuran korpus lalu sesuaikan berdasarkan apakah topik terasa terlalu rinci atau terlalu luas. - Setel
randomstatedi UMAP. Tanpa itu, hasil berubah tiap eksekusi, yang menyulitkan debugging dan komunikasi dengan pemangku kepentingan. - Perlakukan topik -1 secara jujur. Periksa outlier sebelum menguranginya; kelompok outlier yang besar sering menandakan
mintopicsizeterlalu tinggi atau korpus memang berisik. - Cache embedding saat bereksperimen. Memakai ulang array pra-hitung mengubah penjelajahan parameter dari menit menjadi detik.
- Validasi topik dengan manusia. Skor koherensi membantu, tetapi membaca dokumen teratas per topik adalah cara paling andal memastikan topik berguna.
Kesimpulan
BERTopic merumuskan ulang pemodelan topik sebagai pipeline embedding, reduksi, clustering, dan ekstraksi kata kunci, dengan tiap tahap bisa ditukar secara independen. Modularitas itulah kekuatan sejatinya: Anda bisa mulai dengan default, lalu memperbaiki embedding untuk bahasa Anda, menyetel granularitas clustering, dan menyempurnakan label dengan model representasi atau LLM, semuanya tanpa menulis ulang kode.
Poin Penting
- BERTopic memakai embedding semantik alih-alih hitungan kata, sehingga menangani teks pendek dan berisik lebih baik daripada LDA.
- Pipeline lima tahap (embed, reduksi, cluster, vectorize, c-TF-IDF) sepenuhnya bisa disesuaikan.
- Topik -1 adalah kategori outlier, bukan topik nyata; kurangi dengan sengaja.
- Kendalikan granularitas topik dengan
mintopicsize,nrtopics, danreducetopics. - Model representasi seperti KeyBERTInspired dan MMR mempertajam label topik.
- Mode lanjutan mencakup topik sepanjang waktu, per kelas, hierarki, terpandu, dan zero-shot.
- Untuk reproduktifitas setel
random_stateUMAP; untuk kecepatan pakai ulang embedding pra-hitung; untuk portabilitas simpan dengansafetensors.