Tutorial BERTopic: Topic Modeling Modern dengan Embeddings

# BERTopic: Pemodelan Topik Modern dengan Embedding BERTopic adalah library pemodelan topik yang menggabungkan embedding transformer, reduksi dimensi, clustering berbasis kepadatan, dan representasi...

By Ruby Abdullah · · tutorial
BERTopicTopic ModelingNLPClusteringEmbeddingsPython

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:

  • Embed dokumen dengan model sentence-transformer.
  • Reduksi dimensi embedding dengan UMAP.
  • Cluster embedding yang sudah direduksi dengan HDBSCAN.
  • Tokenisasi dokumen per kelompok dengan vectorizer (CountVectorizer).
  • Bobot token dengan class-based TF-IDF (c-TF-IDF) untuk menemukan kata kunci topik.
  • 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(similartopics, 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
    
    

    embeddingmodel = 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

    topicmodel.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.writehtml("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)
    

    topicmodel.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"]
    

    topicmodel = 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 mintopicsize lebih dulu. Ini pengaturan paling berpengaruh. Mulai sekitar 1-2% dari ukuran korpus lalu sesuaikan berdasarkan apakah topik terasa terlalu rinci atau terlalu luas.
    • Setel randomstate di 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 mintopicsize terlalu 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, dan reducetopics.
    • 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_state UMAP; untuk kecepatan pakai ulang embedding pra-hitung; untuk portabilitas simpan dengan safetensors.

    Artikel Terkait

    Tutorial Sentence Transformers: Embeddings, Similarity, dan Reranker

    Sentence Transformers: Embedding, Kemiripan Semantik, dan Reranker Sentence Transformers (sering disebut SBERT) adalah p...

    Tutorial Lengkap txtai: Database Embeddings All-in-One untuk Semantic Search dan LLM Workflows

    Tutorial Lengkap txtai: Database Embeddings All-in-One untuk Semantic Search dan LLM Workflows txtai adalah framework Py...

    Tutorial spaCy: NLP Berskala Industri dengan Python

    spaCy: NLP Kelas Industri di Python spaCy adalah pustaka open-source untuk pemrosesan bahasa alami (NLP) yang dirancang ...

    Tutorial FAISS: Pencarian Kemiripan Vektor dalam Skala Besar

    FAISS: Pencarian Kemiripan Vektor yang Efisien dalam Skala Besar FAISS (Facebook AI Similarity Search) adalah library C+...