Tutorial Sentence Transformers: Embeddings, Similarity, dan Reranker

# Sentence Transformers: Embedding, Kemiripan Semantik, dan Reranker Sentence Transformers (sering disebut SBERT) adalah pustaka Python untuk mengubah teks menjadi embedding vektor padat yang menangk...

By Ruby Abdullah · · tutorial
Sentence TransformersEmbeddingsSemantic SearchNLPRerankingPython

Sentence Transformers: Embedding, Kemiripan Semantik, dan Reranker

Sentence Transformers (sering disebut SBERT) adalah pustaka Python untuk mengubah teks menjadi embedding vektor padat yang menangkap makna, bukan sekadar kata di permukaan. Dalam tutorial ini kita membangun sistem retrieval kecil di atas korpus contoh, mengukur kemiripan semantik, menambahkan reranker cross-encoder, lalu melatih model embedding kita sendiri dengan API pelatihan modern. Tujuannya adalah gambaran praktis menyeluruh tentang bagaimana pustaka ini cocok ke dalam pipeline pencarian atau RAG nyata.

Apa Itu Sentence Transformers

Pustaka sentence-transformers membungkus model transformer (BERT, RoBERTa, MPNet, dan banyak lainnya) dan menambahkan pooling sehingga satu kalimat atau paragraf utuh dipetakan ke satu vektor berukuran tetap. Dua teks dengan makna serupa menghasilkan vektor yang berdekatan, sehingga Anda dapat membandingkannya dengan cosine similarity alih-alih pencocokan kata kunci.

Pustaka ini dikelola bersama ekosistem Hugging Face, sehingga model dimuat dari Hub, dataset memakai format datasets, dan model hasil pelatihan dapat diunggah kembali ke Hub dengan satu pemanggilan. Ini adalah alat standar untuk membangun pencarian berbasis embedding, clustering, deduplikasi, dan tahap retrieval pada sistem RAG.

Bi-Encoder vs Cross-Encoder

Ada dua keluarga model dalam pustaka ini, dan memilih dengan tepat adalah keputusan desain yang paling penting.

Sebuah bi-encoder mengkodekan setiap teks secara independen menjadi sebuah vektor. Anda meng-embed seluruh korpus sekali, menyimpan vektornya, dan saat kueri datang Anda cukup meng-embed kuerinya saja lalu membandingkannya dengan vektor tersimpan. Ini cepat dan dapat menskala hingga jutaan dokumen karena perbandingannya hanya berupa dot product. Komprominya adalah akurasi: model tidak pernah melihat kueri dan dokumen secara bersamaan, sehingga dapat melewatkan interaksi halus.

Sebuah cross-encoder menerima pasangan teks sekaligus (kueri dan kandidat) dan menghasilkan satu skor relevansi. Karena model memperhatikan kedua teks secara bersamaan, akurasinya jauh lebih tinggi. Biayanya adalah Anda tidak dapat menghitung apa pun di awal: setiap pasangan kueri-dokumen harus dijalankan melalui model. Menilai satu kueri terhadap satu juta dokumen menjadi tidak praktis.

Pola standar menggabungkan keduanya. Bi-encoder mengambil beberapa lusin kandidat dengan cepat, lalu cross-encoder me-rerank hanya kandidat tersebut untuk presisi. Inilah pola retrieve-then-rerank yang kita bangun nanti.

Kueri --> [Bi-encoder] --> 50 kandidat teratas --> [Cross-encoder] --> 5 teratas hasil rerank

(cepat, perkiraan) (lambat, presisi)

Instalasi

Pasang pustaka dengan pip. Ia akan menarik PyTorch, transformers, dan datasets sebagai dependensi.

pip install -U sentence-transformers

Untuk pelatihan dan evaluasi Anda mungkin juga membutuhkan beberapa tambahan. Memasang accelerate memungkinkan pelatihan lebih cepat dan multi-GPU, dan datasets diperlukan oleh API pelatihan (biasanya sudah ikut terpasang).

pip install -U accelerate datasets

Verifikasi pemasangan dan periksa perangkat yang tersedia.

import torch

from sentencetransformers import SentenceTransformer

print("sentence-transformers siap")

print("CUDA tersedia:", torch.cuda.isavailable())

Memuat Model dan Mengkodekan Teks

Kelas inti adalah SentenceTransformer. Berikan nama model dari Hub dan ia akan mengunduh serta menyimpan bobotnya di cache. Model awal serbaguna yang baik adalah all-MiniLM-L6-v2: kecil, cepat, berdimensi 384, dan kuat untuk kemiripan semantik bahasa Inggris.

from sentencetransformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")

sentences = [

"Bagaimana cara mengatur ulang kata sandi akun saya?",

"Langkah memulihkan login yang terlupa",

"Cuaca di Jakarta panas hari ini.",

]

embeddings = model.encode(sentences)

print(embeddings.shape) # (3, 384)

encode() mengembalikan array NumPy secara default. Argumen yang berguna:
  • batchsize mengatur berapa banyak teks diproses sekaligus. Batch lebih besar lebih cepat di GPU tetapi memakai lebih banyak memori.
  • converttotensor=True mengembalikan tensor PyTorch, praktis ketika semua tetap berada di GPU untuk perhitungan kemiripan.
  • normalizeembeddings=True menskalakan setiap vektor ke panjang satuan, sehingga dot product setara dengan cosine similarity. Lakukan ini jika indeks hilir Anda menggunakan penilaian dot-product.
  • showprogressbar=True membantu untuk korpus besar.

embeddings = model.encode(

sentences,

batchsize=32,

converttotensor=True,

normalizeembeddings=True,

showprogressbar=True,

)

Model Multibahasa

all-MiniLM-L6-v2 berorientasi bahasa Inggris. Untuk pencarian bahasa Indonesia, campuran, atau lintas bahasa, muat model multibahasa. paraphrase-multilingual-MiniLM-L12-v2 mencakup 50+ bahasa dan memetakan terjemahan dari kalimat yang sama ke vektor yang berdekatan.
multi = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

pairs = [

"How do I reset my password?",

"Bagaimana cara mengatur ulang kata sandi saya?",

]

emb = multi.encode(pairs, converttotensor=True, normalizeembeddings=True)

print(multi.similarity(emb[0], emb[1])) # skor tinggi lintas bahasa

Prompt dan promptname

Beberapa model yang lebih baru (misalnya keluarga e5 dan bge, serta model Nomic) dilatih dengan awalan instruksi. Mereka bekerja paling baik ketika kueri dan dokumen mendapat awalan berbeda seperti "query: " dan "passage: ". Pustaka mendukung ini melalui konfigurasi prompts dan argumen promptname sehingga Anda tidak perlu menuliskan string itu di mana-mana.

model = SentenceTransformer(

"intfloat/multilingual-e5-small",

prompts={

"query": "query: ",

"passage": "passage: ",

},

)

queryemb = model.encode("atur ulang kata sandi", promptname="query")

docemb = model.encode("Langkah memulihkan login yang terlupa", promptname="passage")

Selalu periksa kartu model untuk mengetahui apakah ia memerlukan prompt. Memakai konvensi prompt yang salah diam-diam menurunkan kualitas.

Menghitung Kemiripan

Setelah memiliki embedding, model.similarity menghitung skor berpasangan antara dua kumpulan embedding. Secara default ia memakai cosine similarity, sesuai dengan cara sebagian besar model dilatih.

emb = model.encode(sentences, converttotensor=True)

Matriks berpasangan: setiap baris vs setiap kolom

scores = model.similarity(emb, emb)

print(scores)

Hasilnya adalah matriks di mana elemen [i][j] adalah kemiripan antara kalimat i dan kalimat j. Anda juga dapat mengubah metrik pada model dengan model.similarityfnname = "dot" jika model Anda dilatih untuk penilaian dot-product.

Untuk perbandingan sekali pakai tanpa objek model, modul util menyediakan pembantu.

from sentencetransformers import util

cos = util.cossim(emb[0], emb[1])

print(float(cos))

util.cossim praktis ketika Anda punya embedding mentah dari mana saja dan hanya butuh skor cosine.

Membangun Sistem Pencarian Semantik

Mari bangun inti retrieval di atas korpus kecil. Bi-encoder meng-embed korpus sekali; kueri di-embed sesuai permintaan dan dibandingkan dengan util.semanticsearch, yang menangani pemilihan top-k secara efisien.

from sentencetransformers import SentenceTransformer, util

model = SentenceTransformer("all-MiniLM-L6-v2")

corpus = [

"Atur ulang kata sandi Anda dari halaman pengaturan akun.",

"Aktifkan autentikasi dua faktor untuk keamanan ekstra.",

"Pengembalian dana diproses dalam 5 hari kerja.",

"Perbarui alamat penagihan sebelum faktur berikutnya.",

"Hubungi dukungan jika login Anda terkunci setelah gagal berulang.",

"Ekspor data Anda sebagai CSV dari dasbor.",

]

corpusemb = model.encode(corpus, converttotensor=True, normalizeembeddings=True)

query = "Saya lupa kredensial login saya"

queryemb = model.encode(query, converttotensor=True, normalizeembeddings=True)

hits = util.semanticsearch(queryemb, corpusemb, topk=3)

for hit in hits[0]:

print(f"{hit['score']:.3f} {corpus[hit['corpusid']]}")

util.semanticsearch menerima sekumpulan embedding kueri dan mengembalikan, untuk setiap kueri, daftar hit yang diurutkan berdasarkan skor. Setiap hit memiliki corpusid dan score. Untuk skala produksi Anda akan memasukkan corpusemb ke basis data vektor, tetapi logika penilaiannya sama.

Paraphrase Mining dan Clustering

Selain pencarian, embedding mendukung dua tugas batch yang umum.

Paraphrase mining menemukan pasangan paling mirip di seluruh daftar tanpa menghitung matriks N-kali-N penuh di memori. util.paraphrase
mining dioptimalkan untuk ini.
from sentencetransformers import util

candidates = [

"Bagaimana cara mengatur ulang kata sandi saya?",

"Apa langkah untuk memulihkan login saya?",

"Di mana saya bisa mengekspor data saya?",

"Bagaimana cara mengunduh data saya sebagai berkas?",

"Kapan pengembalian dana saya tiba?",

]

pairs = util.paraphrasemining(model, candidates, topk=2)

for score, i, j in pairs[:5]:

print(f"{score:.3f} | {candidates[i]} <> {candidates[j]}")

Clustering mengelompokkan teks yang berkaitan secara semantik. Anda dapat memberikan embedding ternormalisasi ke KMeans dari scikit-learn, atau memakai util.communitydetection untuk clustering berbasis ambang yang cepat tanpa menentukan jumlah klaster di awal.
emb = model.encode(candidates, converttotensor=True, normalizeembeddings=True)

clusters = util.communitydetection(emb, threshold=0.6, mincommunitysize=2)

for cid, cluster in enumerate(clusters):

print(f"Klaster {cid}:", [candidates[idx] for idx in cluster])

Reranking dengan Cross-Encoder

Bi-encoder cepat tetapi bisa menempatkan dokumen yang hanya berkaitan longgar di atas yang terbaik. Cross-encoder memperbaiki urutan kandidat teratas. Kelas CrossEncoder memuat model yang dilatih untuk menilai pasangan (kueri, dokumen); ms-marco-MiniLM-L-6-v2 adalah pilihan default yang solid untuk peringkat paragraf bahasa Inggris.

from sentencetransformers import CrossEncoder

reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")

query = "Saya lupa kredensial login saya"

Ambil kandidat yang diperoleh bi-encoder

candidatetexts = [corpus[hit["corpusid"]] for hit in hits[0]]

pairs = [(query, text) for text in candidatetexts]

rerankscores = reranker.predict(pairs)

reranked = sorted(zip(rerankscores, candidatetexts), reverse=True)

for score, text in reranked:

print(f"{score:.3f} {text}")

Pipeline Retrieve-Then-Rerank

Menggabungkan semuanya: ambil jaring lebar secara murah, lalu rerank dengan presisi. Metode praktis reranker.rank melakukan pemasangan dan pengurutan untuk Anda.

def search(query, topkretrieve=20, topkfinal=3):

qemb = model.encode(query, converttotensor=True, normalizeembeddings=True)

hits = util.semanticsearch(qemb, corpusemb, topk=topkretrieve)[0]

documents = [corpus[h["corpusid"]] for h in hits]

ranked = reranker.rank(query, documents, topk=topkfinal)

return [(r["score"], documents[r["corpusid"]]) for r in ranked]

for score, text in search("cara mendapatkan uang kembali"):

print(f"{score:.3f} {text}")

Ambil lebih banyak kandidat daripada yang akhirnya Anda tampilkan (20 hingga 100 adalah lazim) agar reranker punya cukup bahan untuk menemukan jawaban terbaik.

Melatih Model Embedding Kustom

Model pralatih adalah baseline yang kuat, tetapi model yang disetel pada domain Anda sendiri biasanya melakukan retrieval jauh lebih baik. API modern mencerminkan Trainer Hugging Face: Anda menyediakan dataset, sebuah loss, argumen pelatihan, dan evaluator opsional.

Memilih Loss dan Format Dataset

Loss menentukan bentuk data Anda.

  • MultipleNegativesRankingLoss adalah tulang punggung untuk retrieval. Ia berlatih pada pasangan positif (anchor, positive) dan memperlakukan semua contoh lain dalam batch sebagai negatif. Dataset Anda perlu dua (atau tiga, dengan negatif sulit) kolom teks dan tanpa label eksplisit. Batch lebih besar memberi lebih banyak negatif dalam-batch dan biasanya membantu.
  • CosineSimilarityLoss berlatih pada pasangan dengan label float antara 0 dan 1, berguna ketika Anda punya skor kemiripan bertingkat alih-alih pasangan positif yang bersih.

Dataset adalah objek Dataset Hugging Face; urutan kolom penting dan kolom label harus diberi nama label atau score.

from datasets import Dataset

Untuk MultipleNegativesRankingLoss: pasangan (anchor, positive)

traindataset = Dataset.fromdict({

"anchor": [

"Bagaimana cara mengatur ulang kata sandi saya?",

"Di mana saya bisa mengekspor data saya?",

"Kapan pengembalian dana saya tiba?",

],

"positive": [

"Atur ulang kata sandi Anda dari halaman pengaturan akun.",

"Ekspor data Anda sebagai CSV dari dasbor.",

"Pengembalian dana diproses dalam 5 hari kerja.",

],

})

Menyiapkan Trainer

SentenceTransformerTrainer mengikat semuanya. Argumen pelatihan berasal dari SentenceTransformerTrainingArguments, yang memperluas TrainingArguments yang sudah dikenal.
from sentencetransformers import (

SentenceTransformer,

SentenceTransformerTrainer,

SentenceTransformerTrainingArguments,

)

from sentencetransformers.losses import MultipleNegativesRankingLoss

model = SentenceTransformer("all-MiniLM-L6-v2")

loss = MultipleNegativesRankingLoss(model)

args = SentenceTransformerTrainingArguments(

outputdir="models/support-embeddings",

numtrainepochs=3,

perdevicetrainbatchsize=32,

learningrate=2e-5,

warmupratio=0.1,

fp16=True, # gunakan bf16=True pada GPU yang lebih baru

evalstrategy="steps",

evalsteps=100,

savestrategy="steps",

savesteps=100,

loggingsteps=20,

)

trainer = SentenceTransformerTrainer(

model=model,

args=args,

traindataset=traindataset,

loss=loss,

)

trainer.train()

Mengevaluasi Kualitas Retrieval

Untuk mengetahui apakah fine-tuning membantu, ukur metrik retrieval pada data yang ditahan. InformationRetrievalEvaluator melaporkan metrik seperti Recall@k, MRR, dan NDCG. Ia memerlukan tiga pemetaan: kueri, korpus, dan id korpus mana yang relevan untuk setiap kueri.

from sentencetransformers.evaluation import InformationRetrievalEvaluator

queries = {"q1": "cara mengatur ulang kata sandi", "q2": "unduh data saya"}

corpusmap = {str(i): text for i, text in enumerate(corpus)}

relevant = {"q1": {"0"}, "q2": {"5"}} # id korpus yang menjawab tiap kueri

irevaluator = InformationRetrievalEvaluator(

queries=queries,

corpus=corpusmap,

relevantdocs=relevant,

name="support-eval",

)

Jalankan mandiri, atau berikan evaluator=irevaluator ke Trainer

results = irevaluator(model)

print(results)

Berikan evaluator ke trainer dengan evaluator=irevaluator untuk melacak metrik ini selama pelatihan dan memilih checkpoint terbaik.

Melatih Cross-Encoder

Anda dapat menyetel reranker dengan cara yang sama memakai CrossEncoderTrainer. Data pelatihan cross-encoder berupa (kueri, dokumen, label) di mana label adalah skor relevansi, sering kali biner (1 untuk relevan, 0 untuk tidak).

from datasets import Dataset

from sentencetransformers.crossencoder import (

CrossEncoder,

CrossEncoderTrainer,

CrossEncoderTrainingArguments,

)

from sentencetransformers.crossencoder.losses import BinaryCrossEntropyLoss

cedata = Dataset.fromdict({

"query": ["atur ulang kata sandi", "atur ulang kata sandi", "ekspor data"],

"document": [

"Atur ulang kata sandi Anda dari halaman pengaturan akun.",

"Pengembalian dana diproses dalam 5 hari kerja.",

"Ekspor data Anda sebagai CSV dari dasbor.",

],

"label": [1.0, 0.0, 1.0],

})

cemodel = CrossEncoder("microsoft/MiniLM-L12-H384-uncased", numlabels=1)

celoss = BinaryCrossEntropyLoss(cemodel)

ceargs = CrossEncoderTrainingArguments(

outputdir="models/support-reranker",

numtrainepochs=2,

perdevicetrainbatchsize=16,

)

cetrainer = CrossEncoderTrainer(

model=cemodel, args=ceargs, traindataset=cedata, loss=celoss,

)

cetrainer.train()

Dalam praktiknya, menambang negatif sulit (dokumen salah yang tampak masuk akal) menghasilkan reranker yang jauh lebih kuat daripada negatif acak.

Menyimpan, Memuat, dan Membagikan

Simpan model terlatih ke disk dan muat kembali seperti model lainnya.

model.savepretrained("models/support-embeddings")

reloaded = SentenceTransformer("models/support-embeddings")

Untuk membagikan di Hugging Face Hub, masuk dulu (huggingface-cli login) lalu unggah. Ini mengunggah bobot, konfigurasi, dan kartu model yang dibuat otomatis.

model.pushtohub("nama-pengguna-anda/support-embeddings")

Mempercepat Inferensi: Kuantisasi dan ONNX

Untuk penyajian produksi, dua backend mengurangi latensi tanpa melatih ulang. Pustaka dapat memuat model dengan runtime ONNX atau OpenVINO melalui argumen backend, dan mendukung bobot terkuantisasi (int8) untuk percepatan lebih lanjut di CPU.

# Backend ONNX untuk inferensi CPU yang lebih cepat

model = SentenceTransformer(

"all-MiniLM-L6-v2",

backend="onnx",

modelkwargs={"filename": "onnx/modelqint8avx512.onnx"},

)

Model int8 terkuantisasi lebih kecil dan lebih cepat di CPU dengan sedikit biaya akurasi; ukur pada data Anda sendiri sebelum berkomitmen. Di GPU, memakai fp16/bf16 dan batch lebih besar biasanya adalah kemenangan paling sederhana.

Praktik Terbaik

  • Cocokkan model dengan bahasa dan domain Anda. Gunakan model multibahasa untuk korpus bahasa Indonesia atau campuran, dan periksa kartu model untuk prompt yang diharapkan.
  • Normalisasi embedding ketika indeks Anda menilai dengan dot product, dan jaga normalisasi yang sama saat kueri maupun pengindeksan.
  • Selalu ambil lebih banyak kandidat daripada yang Anda tampilkan, lalu rerank dengan cross-encoder untuk urutan akhir.
  • Bangun himpunan evaluasi sejak awal. Tanpa metrik InformationRetrievalEvaluator Anda tidak dapat tahu apakah fine-tuning membantu atau merugikan.
  • Utamakan MultipleNegativesRankingLoss dengan batch sebesar yang memori izinkan; negatif dalam-batch itulah yang membuatnya efektif.
  • Cache embedding korpus. Meng-encode ulang korpus besar setiap kali memulai ulang itu boros; simpan vektornya.
  • Untuk pelatihan reranker, investasikan pada negatif sulit alih-alih mengumpulkan lebih banyak pasangan acak.

Kesimpulan dan Poin Utama

Sentence Transformers memberi Anda perangkat lengkap: bi-encoder cepat untuk retrieval, cross-encoder akurat untuk reranking, dan API pelatihan modern untuk menyesuaikan keduanya ke domain Anda.

  • Bi-encoder meng-embed teks secara independen dan menskala ke korpus besar; cross-encoder menilai pasangan secara bersamaan untuk presisi lebih tinggi tetapi tidak dapat menghitung di awal.
  • Pola retrieve-then-rerank menggabungkan kekuatan keduanya dan menjadi arsitektur default untuk pencarian dan RAG yang serius.
  • encode(), model.similarity, dan pembantu util (semanticsearch, cossim, paraphrasemining, communitydetection) mencakup sebagian besar tugas sehari-hari.
  • Fine-tuning dengan SentenceTransformerTrainer, loss seperti MultipleNegativesRankingLoss, dan InformationRetrievalEvaluator biasanya memberi peningkatan kualitas terbesar pada data domain.
  • Untuk produksi, pertimbangkan backend ONNX atau terkuantisasi untuk memangkas latensi, dan selalu cache embedding korpus Anda.

Mulai dari model pralatih yang kuat, ukur pada himpunan evaluasi Anda sendiri, dan lakukan fine-tuning hanya di tempat yang angkanya membenarkan.

Artikel Terkait

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...

Tutorial Semantic Search Engine dari Nol: Embeddings dan Vector Search

Membangun Mesin Pencari Semantik dari Nol Daftar Isi Pendahuluan Prasyarat Memahami Pencarian Semantik [Text Embedding.....

Tutorial Lengkap Pinecone: Vector Database untuk AI dan Semantic Search

Tutorial Lengkap Pinecone: Vector Database untuk AI dan Semantic Search Pinecone adalah managed vector database yang dir...