Pixeltable: Infrastruktur Data AI Deklaratif untuk Data Multimodal

# Pixeltable: Infrastruktur Data AI Deklaratif untuk Data Multimodal Kalau temen-temen pernah bikin pipeline AI yang harus ngolah gambar, video, audio, atau dokumen, pasti familiar sama satu rasa cap...

By Ruby Abdullah · · tutorial
pixeltablemultimodal-aicomputed-columnsembeddingsdata-infrastructure

Pixeltable: Infrastruktur Data AI Deklaratif untuk Data Multimodal

Kalau temen-temen pernah bikin pipeline AI yang harus ngolah gambar, video, audio, atau dokumen, pasti familiar sama satu rasa capek yang sama: datanya kececer di mana-mana. Ada folder berisi file mentah, ada script buat resize dan konversi, ada folder lain buat hasil embedding, ada database vektor terpisah, ada satu file CSV buat nyimpen prediksi model, terus ada satu notebook berantakan yang nyambungin semua itu. Begitu ada satu data baru masuk, aku harus jalanin ulang setengah pipeline secara manual dan berdoa semoga urutannya bener. Nah, Pixeltable itu jawaban buat kekacauan ini.

Di tutorial ini aku bakal ajak temen-temen kenalan sama Pixeltable dari nol. Kita mulai dari instalasi, bikin tabel, masukin data, terus naik ke bagian yang bikin Pixeltable spesial: computed columns yang update otomatis, UDF, embedding index buat similarity search, dan cara dia ngurusin gambar sampai frame video. Aku pakai gaya santai aja biar gampang dicerna, tapi semua kodenya bisa langsung temen-temen jalanin.

Apa Itu Pixeltable dan Kenapa Deklaratif Itu Penting

Pixeltable adalah library Python untuk infrastruktur data AI yang bersifat deklaratif. Maksud "deklaratif" di sini: temen-temen mendeskripsikan apa yang temen-temen mau (misalnya "kolom ini isinya thumbnail dari gambar di kolom itu"), bukan menuliskan langkah demi langkah kapan dan bagaimana thumbnail itu dibikin. Pixeltable yang ngurusin eksekusinya, cache-nya, dan kapan harus dihitung ulang.

Ide intinya sederhana tapi kuat: semua data temen-temen, mau itu teks, gambar, video, audio, atau dokumen, hidup di dalam tabel. Tabel ini mirip tabel database, punya baris dan kolom. Bedanya, satu kolom bisa nyimpen tipe data berat kayak gambar atau video secara native, bukan cuma angka dan string. Terus, temen-temen bisa nambahin computed column, yaitu kolom yang nilainya dihitung dari kolom lain lewat sebuah fungsi. Begitu data baru masuk, computed column-nya keisi otomatis. Begitu temen-temen tambah computed column baru ke tabel yang udah ada isinya, Pixeltable langsung ngitung mundur buat semua baris lama.

Kenapa ini penting? Karena pola kerja AI itu 90 persennya transformasi data yang berulang: resize gambar, ekstrak frame video, panggil model buat inference, bikin embedding, terus simpan indeksnya. Kalau semua langkah itu bisa dinyatakan sebagai kolom, temen-temen nggak perlu lagi nulis loop, nggak perlu ngurus file cache manual, dan nggak perlu takut lupa jalanin ulang salah satu tahap. Pixeltable juga otomatis nyimpen data plus versinya, jadi tiap perubahan bisa dilacak dan bisa di-rollback.

Bandingin sama cara lama. Biasanya aku punya satu skrip preprocessing, satu skrip inference, satu database vektor, dan satu direktori artefak. Empat sistem berbeda yang harus aku sinkronin sendiri. Di Pixeltable, keempat hal itu jadi satu tabel dengan beberapa computed column plus satu embedding index. Satu sumber kebenaran, dan semuanya nyambung.

Konsep Kunci yang Perlu Dipahami

Sebelum ngoding, ada beberapa istilah yang bakal sering muncul:

  • Table: wadah utama data, mirip tabel SQL tapi mendukung tipe multimodal.
  • Column: bisa kolom biasa (data yang temen-temen masukin) atau computed column (dihitung otomatis).
  • Insert: cara masukin data baru ke tabel. Tiap insert memicu perhitungan ulang computed column terkait.
  • UDF (User-Defined Function): fungsi Python biasa yang temen-temen daftarin ke Pixeltable buat dipakai di computed column.
  • Embedding index: indeks vektor yang nempel di sebuah kolom, buat similarity search.
  • View dan iterator: cara "meledakkan" satu baris jadi banyak baris, misalnya satu video jadi banyak frame.

Sekarang kita langsung praktik.

Instalasi

Pixeltable butuh Python 3.9 ke atas. Aku saranin bikin virtual environment dulu biar bersih, terus install lewat pip.

# Bikin dan aktifin virtual environment (jalankan di terminal)

python -m venv venv

source venv/bin/activate # Linux / macOS

venv\Scripts\activate # Windows

Install Pixeltable

pip install pixeltable

Setelah kepasang, coba import buat mastiin semuanya beres:

import pixeltable as pxt

print(pxt.version)

Pertama kali temen-temen jalanin, Pixeltable bakal bikin direktori penyimpanan lokal (defaultnya di ~/.pixeltable). Di situlah semua data, metadata, dan file media disimpan. Jadi tabel yang temen-temen bikin itu persisten, artinya kalau script-nya berhenti terus temen-temen jalanin ulang besok, tabelnya masih ada. Ini beda sama DataFrame Pandas yang ilang begitu program mati.

Untuk fitur tertentu, temen-temen mungkin perlu dependency tambahan. Misalnya buat model dari Hugging Face, install transformers dan torch. Buat pemrosesan video pastikan ada codec yang dibutuhin (biasanya udah ke-bundle). Buat pemanggilan API model eksternal seperti OpenAI, install SDK-nya dan set API key lewat environment variable.

# Contoh dependency tambahan yang sering dipakai

pip install transformers torch sentence-transformers

pip install openai

Satu hal yang perlu temen-temen tahu: Pixeltable bekerja berbasis directory dan namespace buat ngatur tabel. Jadi kita bisa kelompokin tabel ke dalam direktori logis, mirip folder. Ini kepakai banget kalau proyeknya udah gede.

import pixeltable as pxt

Bikin direktori buat proyek kita (aman kalau udah ada)

pxt.createdir('demo', ifexists='ignore')

Basic Usage: Tabel, Insert, dan Query

Sekarang kita bikin tabel pertama. Bayangin kita lagi bangun katalog produk sederhana yang nyimpen gambar produk plus deskripsinya.

Bikin Tabel

Bikin tabel dilakukan dengan pxt.createtable, di mana kita definisiin skema berupa nama kolom dan tipenya.

import pixeltable as pxt

Bersihin dulu kalau ada tabel lama dengan nama sama (opsional saat eksperimen)

pxt.droptable('demo.produk', ifnotexists='ignore')

Bikin tabel dengan skema kolom

produk = pxt.createtable(

'demo.produk',

{

'nama': pxt.String,

'kategori': pxt.String,

'harga': pxt.Float,

'gambar': pxt.Image,

'deskripsi': pxt.String,

}

)

print(produk)

Perhatiin tipe pxt.Image. Ini bukan string path biasa. Pixeltable ngerti bahwa kolom itu berisi gambar, jadi dia bisa nampilin, resize, atau ngasih ke model computer vision langsung. Tipe multimodal lain yang tersedia antara lain pxt.Video, pxt.Audio, pxt.Document, dan pxt.Json. Ada juga tipe standar kayak pxt.String, pxt.Int, pxt.Float, pxt.Bool, pxt.Timestamp, dan pxt.Array.

Masukin Data

Data dimasukin lewat insert. Buat kolom gambar, temen-temen cukup kasih path lokal atau URL. Pixeltable yang ngurus loading-nya.

produk.insert([

{

'nama': 'Sepatu Lari Ringan',

'kategori': 'olahraga',

'harga': 850000.0,

'gambar': 'https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/resources/images/000000000009.jpg',

'deskripsi': 'Sepatu lari dengan sol empuk dan bahan mesh yang breathable.',

},

{

'nama': 'Tas Ransel Kanvas',

'kategori': 'aksesoris',

'harga': 450000.0,

'gambar': 'https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/resources/images/000000000025.jpg',

'deskripsi': 'Tas ransel kanvas tahan lama dengan banyak kompartemen.',

},

])

print(f'Total baris sekarang: {produk.count()}')

Kita bisa masukin satu dict atau list of dict. Kalau temen-temen punya data gede, insert bisa dilakukan per batch dan Pixeltable akan proses secara efisien.

Query Data

Nge-query di Pixeltable rasanya kayak campuran antara Pandas dan SQL, tapi API-nya sendiri. Kita ambil kolom yang kita mau lewat notasi bracket, kasih filter dengan where, terus panggil collect buat ngambil hasilnya.

# Ambil semua produk kategori olahraga

hasil = produk.where(produk.kategori == 'olahraga').select(

produk.nama, produk.harga

).collect()

print(hasil)

Ambil produk dengan harga di bawah 500 ribu

murah = produk.where(produk.harga < 500000).select(

produk.nama, produk.harga

).collect()

print(murah)

collect mengembalikan objek hasil yang bisa temen-temen ubah ke Pandas DataFrame dengan .to
pandas() kalau butuh, atau langsung iterasi. Buat lihat sekilas isi tabel, cukup panggil produk.head() atau produk.show(3).
# Lihat 3 baris pertama

print(produk.head(3))

Ambil kolom gambar juga (akan berupa objek image)

sample = produk.select(produk.nama, produk.gambar).head(2)

print(sample)

Sampai sini kita udah punya tabel yang nyimpen data multimodal secara persisten dan bisa di-query. Tapi kekuatan Pixeltable baru kelihatan di bagian selanjutnya.

Advanced Usage: Computed Columns, UDF, dan Embedding

Computed Column dengan Fungsi Bawaan

Computed column adalah jantungnya Pixeltable. Ide dasarnya: kita nambah kolom yang nilainya adalah hasil sebuah ekspresi atas kolom lain. Karena datanya gambar, mari kita bikin computed column yang menghasilkan thumbnail dengan memanfaatkan operasi bawaan pada tipe image.

# Tambah computed column thumbnail (resize gambar jadi 224x224)

produk.addcomputedcolumn(

thumbnail=produk.gambar.resize([224, 224])

)

Tambah computed column yang menyimpan lebar gambar asli

produk.addcomputedcolumn(

lebargambar=produk.gambar.width

)

print(produk.select(produk.nama, produk.lebargambar).collect())

Yang bikin keren: karena kita nambahin kolom ini setelah data masuk, Pixeltable langsung ngitung thumbnail dan lebargambar buat semua baris yang udah ada. Dan lebih penting lagi, begitu nanti ada produk baru yang di-insert, kedua kolom itu bakal keisi otomatis tanpa kita jalanin apa-apa lagi. Ini yang aku maksud "update otomatis".

Objek image di Pixeltable itu pada dasarnya membungkus PIL Image, jadi banyak operasi PIL yang tersedia sebagai method, kayak resize, rotate, crop, convert, dan properti width, height, mode.

Bikin UDF Sendiri

Fungsi bawaan itu terbatas. Kalau temen-temen mau logika custom, bikin UDF. Caranya tinggal tulis fungsi Python biasa terus kasih decorator @pxt.udf. Anotasi tipe di parameter dan return-nya penting biar Pixeltable ngerti tipe kolomnya.

import pixeltable as pxt

@pxt.udf

def labelharga(harga: float) -> str:

"""Kategorikan harga jadi label yang gampang dibaca."""

if harga < 300000:

return 'murah'

elif harga < 700000:

return 'menengah'

else:

return 'premium'

Pakai UDF-nya sebagai computed column

produk.addcomputedcolumn(

segmenharga=labelharga(produk.harga)

)

print(produk.select(produk.nama, produk.harga, produk.segmenharga).collect())

UDF nggak terbatas ke tipe sederhana. Temen-temen bisa bikin UDF yang nerima gambar, ngolahnya pakai OpenCV atau NumPy, dan mengembalikan gambar atau array. Contoh UDF yang ngitung rata-rata kecerahan gambar:

import PIL.Image

import numpy as np

@pxt.udf

def ratakecerahan(img: PIL.Image.Image) -> float:

"""Hitung kecerahan rata-rata sebuah gambar (0-255)."""

arr = np.asarray(img.convert('L')) # grayscale

return float(arr.mean())

produk.addcomputedcolumn(

kecerahan=ratakecerahan(produk.gambar)

)

print(produk.select(produk.nama, produk.kecerahan).collect())

Perhatiin bahwa parameter UDF-nya bertipe PIL.Image.Image. Pixeltable otomatis nge-decode kolom gambar jadi objek PIL sebelum ngasih ke fungsi kita. Ini yang bikin nulis logika multimodal jadi enteng: kita fokus ke logika, decoding-nya diurusin.

Model Inference sebagai Computed Column

Salah satu use case paling umum adalah manggil model AI di computed column. Pixeltable punya banyak fungsi bawaan buat integrasi model, misalnya lewat modul pixeltable.functions. Sebagai contoh, kita bisa pakai model image classification dari Hugging Face buat ngasih label otomatis ke tiap gambar produk.

from pixeltable.functions.huggingface import clipimage, cliptext

Contoh konsep: klasifikasi zero-shot dengan CLIP

(butuh transformers + torch terpasang)

kategorikandidat = ['sepatu', 'tas', 'baju', 'aksesoris', 'elektronik']

Bikin embedding gambar dan teks lewat model, lalu bandingkan.

Detail API bisa sedikit beda antar versi, cek dokumentasi pixeltable.functions.

Pola pentingnya bukan API spesifik model tertentu, tapi konsepnya: pemanggilan model jadi sebuah kolom. Begitu ada data baru masuk, inference-nya jalan otomatis dan hasilnya kesimpan. Nggak ada lagi skrip batch inference terpisah yang harus temen-temen jalanin manual, dan nggak ada masalah "aku lupa nge-run model buat data yang kemarin masuk".

Sekarang bagian yang paling sering ditanyain: cara bikin semantic search. Di Pixeltable, temen-temen nambahin embedding index ke sebuah kolom, terus Pixeltable ngurus pembuatan embedding plus penyimpanan vektornya. Buat cari yang mirip, tinggal panggil similarity.

Mari kita bikin embedding index untuk kolom deskripsi teks pakai model sentence-transformers.

from pixeltable.functions.huggingface import sentencetransformer

Daftarkan embedding untuk kolom teks 'deskripsi'

produk.addembeddingindex(

column='deskripsi',

stringembed=sentencetransformer.using(

modelid='sentence-transformers/all-MiniLM-L6-v2'

)

)

Setelah index kepasang, kita bisa cari produk yang deskripsinya paling mirip dengan sebuah query teks. Caranya pakai ekspresi similarity di dalam query, terus urutkan.

query = 'perlengkapan buat lari pagi'

hasil = (

produk

.orderby(produk.deskripsi.similarity(query), asc=False)

.select(produk.nama, produk.deskripsi)

.limit(3)

.collect()

)

print(hasil)

Yang perlu digarisbawahi: embedding index ini juga hidup. Kalau nanti ada produk baru di-insert, Pixeltable otomatis bikin embedding buat baris baru itu dan masukin ke index. Jadi search-nya selalu up to date tanpa kita rebuild index manual. Bandingin sama alur biasa di mana kita harus ngurus database vektor terpisah dan sinkronisasi embedding sendiri, ini jauh lebih ringkas.

Pixeltable juga mendukung embedding index buat gambar. Misalnya pakai model CLIP, kita bisa bikin index atas kolom gambar, terus cari gambar yang mirip dengan query teks (text to image search) ataupun dengan gambar lain (image to image search). Konsepnya sama persis, cuma model embed-nya diganti yang mendukung gambar.

Bekerja dengan Gambar dan Frame Video

Video adalah tempat Pixeltable bener-bener bersinar, karena satu video itu ribuan frame dan ngurus itu manual bikin pusing. Pixeltable pakai konsep view dengan iterator buat "meledakkan" satu video jadi banyak baris frame.

Pertama, kita bikin tabel buat nyimpen video.

import pixeltable as pxt

pxt.droptable('demo.video', ifnotexists='ignore')

videos = pxt.createtable(

'demo.video',

{'sumber': pxt.Video}

)

videos.insert([

{'sumber': 'https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/resources/bangkok.mp4'},

])

Sekarang kita bikin view yang mengekstrak frame dari tiap video pakai FrameIterator. Tiap frame jadi satu baris di view, lengkap dengan kolom frame (gambar) dan nomor urut frame.

from pixeltable.iterators import FrameIterator

Bikin view yang ambil 1 frame per detik (fps=1)

frames = pxt.createview(

'demo.frames',

videos,

iterator=FrameIterator.create(video=videos.sumber, fps=1)

)

print(f'Jumlah frame yang diekstrak: {frames.count()}')

print(frames.select(frames.frame, frames.pos).head(3))

Yang menarik, view ini bukan copy data. Dia adalah tampilan turunan yang dihitung dari tabel video induk. Begitu temen-temen tambah video baru ke tabel videos, view frames otomatis nambah baris frame buat video itu. Dan karena frame juga kolom bertipe image, kita bisa nempelin computed column atau embedding index di atasnya, persis kayak tabel gambar biasa.

# Contoh: tambah computed column deteksi objek per frame (konsep)

frames.addcomputedcolumn(deteksi=yolomodel(frames.frame))

Atau bikin embedding index buat cari frame lewat teks

frames.addembeddingindex(column='frame', embedding=clipembed)

Dengan pola ini, membangun sistem "cari momen di dalam video pakai kalimat" jadi cuma beberapa baris: bikin tabel video, bikin view frame, pasang embedding CLIP di kolom frame, terus query pakai similarity. Semua pipeline decoding, sampling frame, embedding, dan indexing ditangani deklaratif.

Update Inkremental dan Versioning

Salah satu janji Pixeltable adalah pemrosesan yang inkremental. Artinya, dia cuma menghitung ulang apa yang perlu, bukan semuanya. Kalau temen-temen insert 5 baris baru ke tabel yang isinya udah 1000 baris, computed column dan embedding cuma dihitung buat 5 baris itu. Yang lama nggak disentuh.

Update nilai kolom juga memicu perhitungan ulang yang tepat sasaran. Misalnya kita ubah harga sebuah produk, maka computed column yang bergantung ke harga (kayak segmenharga) bakal dihitung ulang cuma buat baris itu.

# Update harga produk tertentu

produk.where(produk.nama == 'Tas Ransel Kanvas').update(

{'harga': 280000.0}

)

Kolom segmenharga otomatis ikut berubah jadi 'murah'

print(produk.select(produk.nama, produk.harga, produk.segmenharga).collect())

Pixeltable juga nyimpen versi. Tiap operasi yang mengubah tabel (insert, update, add column) menaikkan versi tabel. Kita bisa lihat riwayatnya dan bahkan balik ke versi sebelumnya dengan revert. Ini bikin eksperimen jadi aman: kalau sebuah addcomputedcolumn ternyata salah, tinggal revert.

# Lihat versi tabel sekarang lewat metadata

print(produk.getmetadata())

Balik satu langkah ke versi sebelumnya

produk.revert()

Kemampuan versioning ini juga bikin lineage data terlacak. Karena tiap computed column tahu dia dihitung dari kolom mana pakai fungsi apa, temen-temen selalu bisa telusuri asal-usul sebuah nilai. Buat proyek yang butuh reproducibility, ini nilai plus yang gede.

Best Practices

Setelah beberapa kali pakai Pixeltable di proyek nyata, ada beberapa hal yang menurutku bikin pengalaman jauh lebih mulus.

Pertama, atur tabel dengan direktori sejak awal. Pakai pxt.createdir buat mengelompokkan tabel per proyek atau per domain. Nama tabel yang rapi kayak katalog.produk atau riset.eksperimen1 bakal nyelametin temen-temen dari kebingungan pas tabelnya udah banyak. Ingat, tabel itu persisten, jadi mereka nggak ilang sendiri. Kedua, pikirin computed column sebagai pipeline. Alih-alih bikin satu UDF raksasa yang ngerjain lima hal sekaligus, pecah jadi beberapa computed column berantai. Misalnya kolom frame lalu frameterdeteksi lalu jumlahobjek. Ini bikin tiap tahap bisa di-debug sendiri, dan Pixeltable bisa cache hasil antaranya. Kalau satu tahap gagal, temen-temen nggak kehilangan tahap sebelumnya. Ketiga, hati-hati sama UDF yang mahal. Kalau UDF-nya manggil model besar atau API eksternal, tiap insert bakal micu pemanggilan itu. Untuk model lokal, pertimbangkan batching. Untuk API berbayar, sadari bahwa nambah computed column ke tabel besar bakal manggil API buat semua baris lama sekaligus, dan itu bisa mahal. Uji dulu di tabel kecil. Keempat, manfaatin embedding index alih-alih ngurus vektor sendiri. Godaan buat ekspor embedding ke database vektor eksternal itu ada, tapi selama kebutuhannya masih terpenuhi, biarin Pixeltable yang ngurus. Embedding yang otomatis sinkron sama data itu menghilangkan seluruh kelas bug sinkronisasi. Kelima, pakai ifexists dan ifnotexists saat scripting. Waktu eksperimen, script sering dijalanin berkali-kali. Parameter kayak ifexists='ignore' di createdir atau ifnotexists='ignore' di droptable bikin script idempoten, jadi nggak error cuma gara-gara tabelnya udah ada atau belum ada. Keenam, gunakan anotasi tipe yang benar di UDF. Pixeltable pakai anotasi tipe buat nentuin tipe kolom output. Salah anotasi bisa bikin hasil nggak sesuai harapan atau error. Kalau UDF-mu ngembaliin gambar, anotasikan -> PIL.Image.Image. Kalau ngembaliin array, pikirkan apakah pxt.Array dengan tipe elemen tertentu lebih cocok. Ketujuh, manfaatin sifat inkremental. Karena Pixeltable cuma ngitung yang berubah, desain alur kerjamu buat masukin data secara bertahap ketimbang membangun ulang semuanya. Pola "insert data baru terus biarin computed column mengejar" jauh lebih efisien dan lebih murah dibanding nge-drop tabel dan bikin ulang dari nol tiap kali.

Kesimpulan

Pixeltable itu menyelesaikan masalah yang bikin banyak proyek AI berantakan: data multimodal yang kececer di banyak sistem plus pipeline transformasi yang rapuh. Dengan menaruh semuanya di dalam tabel dan menyatakan transformasi sebagai computed column, temen-temen dapet satu sumber kebenaran yang tetap konsisten secara otomatis. Data baru masuk, semua kolom turunan dan indeks langsung nyusul tanpa kita jalanin apa-apa.

Di tutorial ini kita udah jalan dari instalasi, bikin tabel multimodal, masukin dan query data, sampai ke bagian intinya: computed column dengan fungsi bawaan dan UDF, embedding index buat similarity search, ekstraksi frame video lewat view dan iterator, serta update inkremental dan versioning. Yang aku harap nempel di kepala temen-temen bukan sintaks spesifiknya, tapi pergeseran pola pikirnya: dari "aku nulis skrip yang mengeksekusi langkah-langkah" jadi "aku mendeklarasikan bentuk data yang aku mau, biar sistem yang ngeksekusi".

Langkah selanjutnya buat temen-temen: coba ganti dataset contoh di atas dengan data temen-temen sendiri, entah itu koleksi foto, arsip PDF, atau folder video. Tambahin satu computed column buat inference model favoritmu, pasang satu embedding index, terus rasain gimana rasanya search semantik yang selalu sinkron. Begitu temen-temen ngerasain "insert data, semuanya beres sendiri", susah buat balik ke cara lama. Selamat ngoprek, dan semoga pipeline AI temen-temen makin rapi.

Artikel Terkait

DINOv2: Panduan Lengkap Vision Foundation Model dari Meta AI untuk Embedding Gambar Tanpa Label

DINOv2: Panduan Lengkap Vision Foundation Model dari Meta AI untuk Embedding Gambar Tanpa Label Halo temen-temen, di tut...

FastEmbed: Bikin Embedding Cepat dan Ringan Tanpa Torch dari Qdrant

FastEmbed: Bikin Embedding Cepat dan Ringan Tanpa Torch dari Qdrant Halo temen-temen, balik lagi sama aku Ruby Abdullah....

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 BERTopic: Topic Modeling Modern dengan Embeddings

BERTopic: Pemodelan Topik Modern dengan Embedding BERTopic adalah library pemodelan topik yang menggabungkan embedding t...