Menerapkan Pencarian Kata Kunci BM25
Siapkan BM25 menggunakan rank_bm25 di Python, indeks korpus dokumen Anda, lalu jalankan pencarian kata kunci yang secara andal menangani istilah persis, jargon teknis, dan nama produk.
Menerapkan Pencarian Kata Kunci BM25 adalah pelajaran AI Engineering Academy gratis di CoddyKit. Ini adalah pelajaran 2 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar AI Engineering Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus AI Engineering Academy mencakup 4 pelajaran total.
Menginstal rank_bm25
rank_bm25 adalah pustaka Python ringan yang menyediakan varian BM25Okapi, BM25L, dan BM25Plus dari algoritma BM25. Pustaka ini tidak memerlukan layanan eksternal, berjalan sepenuhnya di memori, dan dapat mengindeks ribuan dokumen dalam hitungan detik pada perangkat keras umum. Instal dengan pip install rank-bm25 dan Anda siap membangun pencarian kata kunci tanpa penyiapan infrastruktur apa pun.
# Install: pip install rank-bm25
from rank_bm25 import BM25Okapi
# BM25Okapi is the most common variant
# BM25L and BM25Plus handle very short documents better
# For most RAG use cases BM25Okapi is the right choice
corpus = [
'Python decorator pattern explained with examples',
'How to use context managers in Python',
'JavaScript async await tutorial',
]
tokenized = [doc.lower().split() for doc in corpus]
bm25 = BM25Okapi(tokenized)
print('Index built with', len(corpus), 'documents')Tokenisasi: Langkah Pertama yang Kritis
BM25 bekerja pada daftar token, bukan string mentah. Kualitas tokenisasi Anda berdampak langsung pada kualitas pengambilan data. Pemisahan berdasarkan spasi sederhana tidak menangani penghapusan tanda baca, stemming, dan penghapusan kata henti. Untuk sistem produksi, gunakan tokenizer yang tepat untuk mengubah teks menjadi huruf kecil, menghapus tanda baca, menghapus kata henti, dan secara opsional menerapkan stemming agar varian morfologis seperti 'run', 'runs', dan 'running' dapat dicocokkan.
import re
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer
STOP_WORDS = set(stopwords.words('english'))
stemmer = PorterStemmer()
def tokenize(text: str) -> list[str]:
text = text.lower()
text = re.sub(r'[^a-z0-9\s]', ' ', text)
tokens = text.split()
tokens = [t for t in tokens if t not in STOP_WORDS and len(t) > 1]
tokens = [stemmer.stem(t) for t in tokens]
return tokens
print(tokenize('Running Python decorators efficiently in production!'))
# ['run', 'python', 'decor', 'effici', 'product']Membangun Indeks BM25
Membuat indeks BM25 adalah operasi luring yang dilakukan satu kali. Anda meneruskan korpus yang sudah ditokenisasi ke BM25Okapi, lalu indeks tersebut menghitung frekuensi dokumen terbalik untuk semua istilah dan menyimpan panjang dokumen untuk normalisasi. Indeks ini ringan — hanya beberapa megabita bahkan untuk puluhan ribu dokumen. Anda harus membangunnya ulang setiap kali dokumen baru ditambahkan ke korpus.
from rank_bm25 import BM25Okapi
def build_bm25_index(documents: list[str]):
tokenized = [tokenize(doc) for doc in documents]
bm25 = BM25Okapi(tokenized)
return bm25, tokenized
# Example with a small corpus
docs = [
'Vector databases store dense embeddings for similarity search',
'BM25 is a sparse keyword retrieval algorithm used in search engines',
'Hybrid search combines dense and sparse retrieval for better recall',
'PostgreSQL supports vector search via the pgvector extension',
]
bm25, tokenized = build_bm25_index(docs)
print(f'Index contains {bm25.corpus_size} documents')Melakukan Pencarian BM25
Untuk melakukan pencarian, tokenisasikan kueri menggunakan tokenizer yang sama seperti yang digunakan oleh indeks — tokenisasi yang tidak konsisten adalah sumber umum buruknya pengambilan data. Panggil get_scores untuk memperoleh skor relevansi semua dokumen, atau get_top_n untuk langsung mengambil N hasil teratas. Selalu gunakan alur prapemrosesan yang sama untuk pengindeksan dan pembuatan kueri.
def bm25_search(bm25, documents: list[str], query: str, top_k: int = 3):
query_tokens = tokenize(query)
scores = bm25.get_scores(query_tokens)
# Get indices sorted by score descending
ranked = sorted(enumerate(scores), key=lambda x: x[1], reverse=True)
results = []
for idx, score in ranked[:top_k]:
results.append({
'document': documents[idx],
'score': round(score, 4),
'rank': len(results) + 1,
})
return results
results = bm25_search(bm25, docs, 'sparse keyword search engine')
for r in results:
print(f"Rank {r['rank']} (score {r['score']}): {r['document'][:60]}")Menyesuaikan Hiperparameter BM25
BM25Okapi menerima dua hiperparameter: k1 mengendalikan saturasi frekuensi istilah (nilai yang lebih tinggi memungkinkan istilah berfrekuensi tinggi memperoleh skor lebih tinggi), sedangkan b mengendalikan normalisasi panjang dokumen (1.0 = normalisasi penuh, 0.0 = tanpa normalisasi). Nilai bawaan k1=1.5, b=0.75 bekerja dengan baik untuk prosa. Untuk potongan pendek (kurang dari 100 kata), coba nilai b yang lebih rendah seperti 0.3 untuk mengurangi bias panjang.
from rank_bm25 import BM25Okapi
# Default hyperparameters — good starting point
bm25_default = BM25Okapi(tokenized, k1=1.5, b=0.75)
# Tuned for short document chunks
bm25_short = BM25Okapi(tokenized, k1=1.2, b=0.3)
# Tuned for long documents
bm25_long = BM25Okapi(tokenized, k1=2.0, b=0.9)
# Always benchmark hyperparameters against a golden eval set
# before deploying to productionMenangani Jargon Teknis dan Token Kode
Untuk basis kode dan dokumentasi teknis, tokenizer Anda harus mempertahankan token teknis, bukan melakukan stemming secara agresif. Istilah seperti BM25Okapi, pgvector, dan LLM harus tetap utuh. Tokenizer hibrida yang melewati stemming untuk token yang cocok dengan pola seperti akronim huruf besar, CamelCase, atau pengenal snake_case akan menghasilkan hasil yang lebih baik untuk pencarian yang ditujukan bagi pengembang.
import re
def technical_tokenize(text: str) -> list[str]:
text = text.lower()
# preserve underscores in snake_case and dots in version numbers
text = re.sub(r'[^a-z0-9_.\s]', ' ', text)
tokens = text.split()
# keep tokens that look like identifiers (contain _ or .)
tokens = [
t for t in tokens
if len(t) > 1 and t not in STOP_WORDS
]
return tokens
print(technical_tokenize('Install pgvector 0.5.1 extension in PostgreSQL 16'))
# ['pgvector', '0.5.1', 'extension', 'postgresql', '16']Menyimpan Indeks BM25 secara Permanen
Indeks BM25 harus disimpan ke disk di antara mulai ulang aplikasi untuk menghindari biaya pengindeksan ulang. Karena objek rank_bm25 adalah Python biasa, Anda dapat membuat serialisasinya dengan pickle. Untuk korpus yang lebih besar, simpan indeks dan daftar dokumen asli agar Anda dapat mengambil teks setelah pemberian skor. Jangan pernah menyimpan data sensitif dalam berkas pickle karena berkas tersebut tidak aman terhadap input yang tidak tepercaya.
import pickle
def save_bm25_index(bm25, documents: list[str], path: str):
with open(path, 'wb') as f:
pickle.dump({'bm25': bm25, 'documents': documents}, f)
print(f'Index saved to {path}')
def load_bm25_index(path: str):
with open(path, 'rb') as f:
data = pickle.load(f)
return data['bm25'], data['documents']
save_bm25_index(bm25, docs, '/tmp/bm25_index.pkl')
bm25_loaded, docs_loaded = load_bm25_index('/tmp/bm25_index.pkl')Pembaruan Indeks Bertahap
BM25 tidak mendukung pembaruan bertahap — Anda harus membangun ulang seluruh indeks ketika dokumen baru tiba. Untuk korpus yang sering berubah, pembaruan secara berkelompok adalah solusi praktis: kumpulkan dokumen baru selama jangka waktu tertentu, lalu bangun ulang indeks di luar jalur kritis. Gunakan pola penyangga ganda, yaitu satu indeks melayani lalu lintas aktif sementara indeks lainnya dibangun ulang, lalu tukar keduanya secara atomik.
import threading
class SwappableBM25Index:
def __init__(self):
self._index = None
self._docs = []
self._lock = threading.RLock()
def rebuild(self, new_docs: list[str]):
tokenized = [tokenize(d) for d in new_docs]
new_index = BM25Okapi(tokenized)
with self._lock:
self._index = new_index
self._docs = new_docs
print(f'Index rebuilt with {len(new_docs)} documents')
def search(self, query: str, top_k: int = 5):
with self._lock:
return bm25_search(self._index, self._docs, query, top_k)Mengintegrasikan BM25 dengan LangChain
LangChain menyediakan pembungkus BM25Retriever yang mengintegrasikan pencarian BM25 ke dalam antarmuka pengambil data standar. Dengan demikian, Anda dapat menggunakan BM25 sebagai komponen siap pakai dalam rantai LCEL dan menggabungkannya dengan pengambil data vektor menggunakan EnsembleRetriever. Parameter bobot mengendalikan seberapa besar pengaruh BM25 dibandingkan pengambil data padat terhadap peringkat akhir.
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever
from langchain_core.documents import Document
langchain_docs = [Document(page_content=d) for d in docs]
bm25_retriever = BM25Retriever.from_documents(langchain_docs)
bm25_retriever.k = 5
# Combine with a vector retriever (assuming vector_retriever is already defined)
# ensemble = EnsembleRetriever(
# retrievers=[bm25_retriever, vector_retriever],
# weights=[0.4, 0.6], # 40% BM25, 60% dense
# )
results = bm25_retriever.invoke('sparse keyword search')
for doc in results:
print(doc.page_content[:80])Mengevaluasi Kualitas BM25
Untuk mengukur kualitas pengambilan data BM25, buat himpunan data acuan yang terdiri atas kueri yang dipasangkan dengan dokumen relevan yang telah diketahui. Hitung tingkat kecocokan pada K (apakah dokumen relevan muncul dalam K hasil teratas) dan MRR (rata-rata kebalikan peringkat). Bandingkan angka-angka ini dengan pengambilan data padat pada himpunan uji yang sama untuk menentukan pembobotan optimal dalam sistem hibrida Anda.
def hit_rate_at_k(bm25, documents, queries, relevant_docs, k=5):
hits = 0
for query, relevant in zip(queries, relevant_docs):
results = bm25_search(bm25, documents, query, top_k=k)
retrieved = [r['document'] for r in results]
if relevant in retrieved:
hits += 1
return hits / len(queries)
# Example evaluation
test_queries = ['BM25 algorithm', 'hybrid search systems']
test_relevant = [
'BM25 is a sparse keyword retrieval algorithm used in search engines',
'Hybrid search combines dense and sparse retrieval for better recall',
]
hit_rate = hit_rate_at_k(bm25, docs, test_queries, test_relevant, k=3)
print(f'Hit rate @3: {hit_rate:.2%}')BM25 Produksi dalam Skala Besar
Untuk korpus yang berisi jutaan dokumen, rank_bm25 dalam Python murni akan terlalu lambat. BM25 berskala produksi tersedia di Elasticsearch dan OpenSearch (keduanya menggunakan BM25 sebagai fungsi pemberian skor bawaan), Typesense, dan mode vektor jarang milik Qdrant. Sistem-sistem ini memelihara indeks terbalik di disk, mendukung pembaruan sebagian, dan menangani kueri serentak tanpa membangun ulang seluruh indeks.
Pemeriksaan Singkat
Uji pemahaman Anda tentang implementasi pencarian kata kunci BM25 dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda mempelajari bahwa: rank_bm25 menyediakan indeks BM25 dalam memori yang memerlukan input bertokenisasi, tokenisasi yang konsisten antara pengindeksan dan pembuatan kueri sangat penting untuk pemberian skor yang akurat, dan hiperparameter k1 dan b dapat disesuaikan dengan distribusi panjang dokumen Anda. Untuk produksi berskala besar, gunakan Elasticsearch atau OpenSearch, bukan BM25 dalam memori. Selanjutnya, kita akan mengimplementasikan penggabungan kebalikan peringkat untuk menggabungkan hasil pengambilan data BM25 dan padat.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Menerapkan Pencarian Kata Kunci BM25” gratis?
Ya — teks lengkap “Menerapkan Pencarian Kata Kunci BM25” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus AI Engineering Academy, upgrade ke CoddyKit PRO. Kursus AI Engineering Academy mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Menerapkan Pencarian Kata Kunci BM25”?
Siapkan BM25 menggunakan rank_bm25 di Python, indeks korpus dokumen Anda, lalu jalankan pencarian kata kunci yang secara andal menangani istilah persis, jargon teknis, dan nama produk. Kamu berlatih AI Engineering Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai AI Engineering Academy?
Tidak diperlukan pengalaman sebelumnya. AI Engineering Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 2 dari 4.
Berapa lama pelajaran “Menerapkan Pencarian Kata Kunci BM25” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran AI Engineering Academy ini?
Ya. Setiap pelajaran AI Engineering Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Pengambilan Padat vs Jarang: Keseimbangan
- Menerapkan Pencarian Kata Kunci BM25
- Penggabungan Peringkat Timbal Balik untuk Menggabungkan Skor
- Pencarian Hibrida di Pinecone dan pgvector