0Pricing
AI Engineering Academy · Pelajaran

Penggabungan Peringkat Timbal Balik untuk Menggabungkan Skor

Implementasikan penggabungan peringkat timbal balik untuk menggabungkan daftar hasil berperingkat dari pengambil padat dan jarang tanpa perlu menormalkan skor kemiripan yang tidak kompatibel.

Penggabungan Peringkat Timbal Balik untuk Menggabungkan Skor adalah pelajaran AI Engineering Academy gratis di CoddyKit. Ini adalah pelajaran 3 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.

Masalah Ketidakcocokan Skor

Pengambil data padat menghasilkan skor kemiripan kosinus antara -1 dan 1, sedangkan BM25 menghasilkan skor positif tanpa batas yang bergantung pada ukuran korpus dan frekuensi istilah. Anda tidak dapat menjumlahkan angka-angka ini secara langsung — skor BM25 sebesar 3.7 dan kemiripan kosinus sebesar 0.85 memiliki arti yang sama sekali berbeda. Normalisasi skor sederhana (membaginya dengan skor maksimum) rapuh karena dokumen pencilan mendistorsi skala. Kita memerlukan metode yang tidak bergantung pada nilai skor absolut.

Gagasan Inti di Balik RRF

Penggabungan Kebalikan Peringkat (RRF) menghindari masalah ketidakcocokan skor dengan mengubah hasil setiap pengambil data menjadi posisi berperingkat dan menggabungkan peringkat tersebut, bukan skor mentah. Dokumen yang berada di peringkat pertama memperoleh kontribusi RRF yang tinggi, dokumen yang berada di peringkat kesepuluh memperoleh kontribusi yang jauh lebih rendah, dan skor akhirnya merupakan jumlah kontribusi RRF dari semua pengambil data. Rumusnya adalah: RRF(d) = sum(1 / (k + rank_i(d))) dengan k sebagai konstanta penghalus (biasanya 60).

# RRF formula
# For each retriever i, document d receives:
#   contribution = 1 / (k + rank_i(d))
# Final RRF score = sum of contributions from all retrievers
# k = 60 is the standard constant from the original 2009 paper

# Example:
# Document A: rank 1 in BM25, rank 4 in dense
#   RRF(A) = 1/(60+1) + 1/(60+4) = 0.01639 + 0.01563 = 0.03202
# Document B: rank 2 in BM25, rank 2 in dense
#   RRF(B) = 1/(60+2) + 1/(60+2) = 0.01613 + 0.01613 = 0.03226
# Document B scores slightly higher due to consistent top-2 ranking

Mengimplementasikan RRF dari Awal

Implementasinya ternyata sangat sederhana. Untuk setiap pengambil data, iterasikan daftar hasil berperingkatnya dan akumulasikan skor RRF ke dalam kamus yang menggunakan ID dokumen sebagai kunci. Dokumen yang muncul pada beberapa pengambil data akan mengakumulasikan kontribusi dari masing-masing pengambil data. Terakhir, urutkan berdasarkan total skor RRF secara menurun untuk menghasilkan peringkat gabungan.

from collections import defaultdict

def reciprocal_rank_fusion(
    result_lists: list[list[str]],
    k: int = 60,
) -> list[tuple[str, float]]:
    '''Merge multiple ranked result lists using RRF.
    result_lists: each inner list is a ranked list of document IDs
    Returns: sorted list of (doc_id, rrf_score) tuples
    '''
    rrf_scores = defaultdict(float)

    for ranked_list in result_lists:
        for rank, doc_id in enumerate(ranked_list, start=1):
            rrf_scores[doc_id] += 1.0 / (k + rank)

    sorted_results = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
    return sorted_results

Pencarian Hibrida Ujung ke Ujung dengan RRF

Dalam praktiknya, Anda menjalankan kedua pengambil data, mengumpulkan daftar ID dokumen berperingkatnya, meneruskannya ke RRF, lalu mencari ID dokumen teratas dalam penyimpanan dokumen. Perhatikan bahwa normalisasi skor tidak diperlukan — yang penting hanyalah urutan peringkat hasil setiap pengambil data. Hal ini membuat RRF sangat tangguh terhadap pergeseran distribusi dan perubahan ukuran korpus.

def hybrid_search_rrf(
    query: str,
    bm25_index,
    dense_retriever,
    documents: dict,  # id -> text
    top_k: int = 5,
) -> list[dict]:
    # Get ranked lists from each retriever
    bm25_ids = bm25_index.search(query, top_k=20)  # over-retrieve then fuse
    dense_ids = dense_retriever.search(query, top_k=20)

    # Run RRF
    fused = reciprocal_rank_fusion([bm25_ids, dense_ids])

    # Return top-K with scores
    results = []
    for doc_id, score in fused[:top_k]:
        results.append({
            'id': doc_id,
            'text': documents[doc_id],
            'rrf_score': round(score, 6),
        })
    return results

Mengapa Mengambil Hasil Berlebih Sebelum Penggabungan

Perhatikan pola pengambilan 20 kandidat dari setiap pengambil data, lalu mengambil 5 teratas setelah penggabungan. Strategi pengambilan berlebih ini penting karena dokumen yang berada di peringkat 10 pada BM25 tetapi di peringkat 1 pada pengambil data padat dapat naik dalam daftar gabungan. Jika Anda hanya mengambil 5 hasil dari masing-masing pengambil data, dokumen seperti itu akan terlewat. Praktik yang umum adalah mengambil top_k * 4 dari setiap pengambil data sebelum penggabungan, lalu mengembalikan top_k hasil akhir.

Konstanta Penghalus K

Konstanta k dalam rumus RRF mengendalikan seberapa besar bobot yang diberikan kepada dokumen berperingkat teratas dibandingkan dokumen berperingkat lebih rendah. Nilai k kecil memperbesar perbedaan antarperingkat (peringkat 1 jauh lebih baik daripada peringkat 2), sedangkan nilai k besar meratakan distribusi (semua peringkat kurang lebih sama pentingnya). Makalah asli menggunakan k=60, dan penelitian empiris menunjukkan bahwa nilai antara 30 dan 100 memiliki kinerja yang serupa. k=60 adalah nilai bawaan yang tangguh.

# Visualize effect of k on contribution weights
for k in [10, 60, 100]:
    weights = [1.0 / (k + rank) for rank in range(1, 11)]
    print(f'k={k}: rank1={weights[0]:.4f}, rank5={weights[4]:.4f}, rank10={weights[9]:.4f}')

# k=10:  rank1=0.0909, rank5=0.0667, rank10=0.0500  (steep)
# k=60:  rank1=0.0164, rank5=0.0154, rank10=0.0143  (flat)
# k=100: rank1=0.0099, rank5=0.0095, rank10=0.0091  (very flat)

Menangani Dokumen yang Tidak Ada pada Salah Satu Pengambil Data

RRF menangani dokumen yang hilang dengan baik: dokumen yang muncul hanya pada satu pengambil data tetap mengakumulasikan kontribusi RRF dari pengambil data tersebut. Dokumen itu hanya tidak menerima kontribusi kedua. Artinya, dokumen yang disepakati oleh kedua pengambil data secara alami akan memperoleh skor lebih tinggi daripada dokumen yang ditemukan hanya oleh salah satunya. Sinyal konsensus ini tepat seperti yang Anda inginkan dalam sistem hibrida.

# Example showing consensus effect
example_docs = {
    'doc_A': [1, 3],   # ranks in [bm25, dense]
    'doc_B': [2, 2],   # both retrievers like it
    'doc_C': [3, None],  # only in BM25
    'doc_D': [None, 1],  # only in dense
}

k = 60
for doc, ranks in example_docs.items():
    score = sum(1/(k + r) for r in ranks if r is not None)
    print(f'{doc}: RRF = {score:.5f}')
# doc_B ranks highest because both retrievers agree
# doc_D (rank 1 in dense only) may outscore doc_A (1,3) despite lower consensus

RRF dengan Lebih dari Dua Pengambil Data

RRF secara alami dapat diperluas ke tiga pengambil data atau lebih. Anda dapat menggabungkan BM25, pengambil data embedding padat, dan pengambil data terlatih yang jarang (seperti SPLADE) yang secara khusus dioptimalkan untuk pencocokan kata kunci dalam ruang jarang terlatih. Setiap pengambil data tambahan menambahkan kontribusi RRF lain untuk setiap dokumen, dan dokumen yang memiliki peringkat tinggi pada semua pengambil data akan menang dengan selisih yang jelas.

# Three-way hybrid: BM25 + dense + SPLADE
bm25_ids = ['doc_B', 'doc_A', 'doc_C', 'doc_D']
dense_ids = ['doc_D', 'doc_A', 'doc_B', 'doc_E']
splade_ids = ['doc_B', 'doc_D', 'doc_A', 'doc_F']

fused = reciprocal_rank_fusion([bm25_ids, dense_ids, splade_ids])
print('Fused ranking:')
for doc_id, score in fused:
    print(f'  {doc_id}: {score:.5f}')
# doc_A and doc_B likely dominate because they appear in all three lists

RRF Berbobot untuk Pengambil Data yang Tidak Simetris

RRF standar memperlakukan semua pengambil data secara setara, tetapi dalam praktiknya satu pengambil data mungkin lebih andal daripada yang lain untuk domain khusus Anda. RRF berbobot mengalikan kontribusi setiap pengambil data dengan suatu bobot sebelum menjumlahkannya. Bobot 1.5 pada pengambil data padat dan 0.5 pada BM25 menekankan pencocokan semantik, sedangkan bobot yang sama, yaitu 1.0, memperlakukan keduanya secara simetris. Sesuaikan bobot pada himpunan validasi.

def weighted_rrf(
    result_lists: list[list[str]],
    weights: list[float],
    k: int = 60,
) -> list[tuple[str, float]]:
    from collections import defaultdict
    scores = defaultdict(float)
    for ranked_list, weight in zip(result_lists, weights):
        for rank, doc_id in enumerate(ranked_list, start=1):
            scores[doc_id] += weight * (1.0 / (k + rank))
    return sorted(scores.items(), key=lambda x: x[1], reverse=True)

# Emphasize dense retrieval (0.7) over BM25 (0.3)
fused = weighted_rrf([bm25_ids, dense_ids], weights=[0.3, 0.7])

RRF vs Normalisasi Skor: Mengapa RRF Unggul

Alternatif untuk RRF mencakup normalisasi min-maks (menskalakan skor setiap retriever ke rentang [0,1]) dan normalisasi z-score. Keduanya sensitif terhadap dokumen pencilan yang memperbesar skor maksimum dan membuat skor lainnya mendekati nol. RRF tahan terhadap pencilan karena hanya menggunakan posisi peringkat, bukan skor mentah. Tolok ukur empiris secara konsisten menunjukkan bahwa RRF mengungguli pendekatan penggabungan berbasis normalisasi.

Menerapkan RRF dengan LangChain

EnsembleRetriever milik LangChain menerapkan penggabungan RRF secara internal. Komponen ini menerima daftar retriever dan bobot opsional, menjalankannya secara paralel, lalu mengembalikan hasil yang digabungkan dengan RRF. Dengan demikian, Anda dapat mengintegrasikan pencarian hibrida ke dalam rangkaian LCEL dengan kode pendukung minimal dan mengganti retriever individual tanpa mengubah logika penggabungan.

from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

# Dense retriever
vectorstore = FAISS.from_documents(langchain_docs, OpenAIEmbeddings())
vector_retriever = vectorstore.as_retriever(search_kwargs={'k': 20})

# Sparse retriever
bm25_retriever = BM25Retriever.from_documents(langchain_docs, k=20)

# Hybrid ensemble using RRF internally
ensemble = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6],
)
results = ensemble.invoke('hybrid search reciprocal rank fusion')

Pemeriksaan Singkat

Uji pemahaman Anda tentang penggabungan peringkat timbal balik dari pelajaran ini.

Rangkuman Pelajaran

Dalam pelajaran ini, Anda mempelajari bahwa RRF menggabungkan daftar berperingkat tanpa menormalisasi skor yang tidak sebanding, rumus 1/(k + rank) memberikan kontribusi dari setiap retriever kepada setiap dokumen, dan dokumen yang mendapat peringkat tinggi dari beberapa retriever memperoleh skor gabungan tertinggi. Konstanta penghalus k=60 merupakan nilai bawaan yang tangguh. EnsembleRetriever milik LangChain menerapkan RRF secara native. Selanjutnya, kita akan mengonfigurasi pencarian hibrida secara langsung di Pinecone dan pgvector.

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Penggabungan Peringkat Timbal Balik untuk Menggabungkan Skor” gratis?

Ya — teks lengkap “Penggabungan Peringkat Timbal Balik untuk Menggabungkan Skor” 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 “Penggabungan Peringkat Timbal Balik untuk Menggabungkan Skor”?

Implementasikan penggabungan peringkat timbal balik untuk menggabungkan daftar hasil berperingkat dari pengambil padat dan jarang tanpa perlu menormalkan skor kemiripan yang tidak kompatibel. 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 3 dari 4.

Berapa lama pelajaran “Penggabungan Peringkat Timbal Balik untuk Menggabungkan Skor” 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

  1. Pengambilan Padat vs Jarang: Keseimbangan
  2. Menerapkan Pencarian Kata Kunci BM25
  3. Penggabungan Peringkat Timbal Balik untuk Menggabungkan Skor
  4. Pencarian Hibrida di Pinecone dan pgvector
← Kembali ke AI Engineering Academy