iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0
AI & Data

讓電腦聽懂人話:30 天 NLP 入門系列 第 29

Day 29|RAG Step 3:Reranking 重排序

  • 分享至 

  • xImage
  •  

引言

上一篇我們用向量檢索找出了幾個相似的 chunk。但有時候即使找到了 top_k 的候選文件,相關性的排序也還不是最完美的。有些內容雖然有相關,但對回答問題的幫助可能不大。

這時候,就可以進行 Reranking 重排序!


圖片來源:https://www.linkedin.com/pulse/cross-encoder-vector-search-re-ranking-viktor-qvarfordt-fnmzf

Reranking 是一個二次篩選機制,它會再用另一個模型來判斷候選 chunk 與 問題 的相關性,然後重新給每個 chunk 打分數,把最有價值的資訊排在前面。
這裡我們採用的是 Cross-Encoder(交叉編碼器) 架構。
它會將 「問題」和「文件」一起輸入模型,讓模型直接判斷兩者的語意關聯程度,並輸出一個相關性分數。

這種方式的優點是,模型能同時理解問題與文件之間的語境,判斷會更精準。但相對地,它的缺點就是每次都要讓問題與每個候選文件配對計算一次,因此運算成本較高,不適合用在即時處理大量資料的場景。

我們今天要實作的部分就是:

  • 針對向量搜尋後的 top_k 結果,用 Cross-Encoder 重排序
  • 使用 BGE Reranker 模型
  • 將 top_k 候選 chunk 與使用者問題配對
  • 計算相關性分數,重新排序
  • 產出重排序後的 chunk,準備給 AI 做生成

本系列實作的架構圖:

ithomeNLP_RAG/
├── requirements.txt 
├── data/         
├── db/           # qdrant vector db
├── indexer.py    # store vector
├── retriever.py  # vector search
├── reranker.txt  # rerank
└── frontend.py   # generation + UI

我們今天會做的是 reranker.py


Rerank

from FlagEmbedding import FlagReranker
from retriever import VectorStore  

EMBEDDING_MODEL = "BAAI/bge-m3"
RERANK_MODEL = "BAAI/bge-reranker-base"

def rerank(query: str, results: list):
    reranker = FlagReranker(
        RERANK_MODEL,
        cache_dir=".",
        use_fp16=False
    )
    
    docs = [res['content'] for res in results]
    # 生成 query-document pair
    pairs = [[query, doc] for doc in docs]
    # 計算相關性分數
    scores = reranker.compute_score(pairs)

    reranked_results = []
    for score, res in sorted(zip(scores, results), key=lambda x: x[0], reverse=True):
        rerank_result = {
            'similarity_score': float(score),  # 使用 reranker 分數
            'source': res.get('source', ''),
            'chunk_index': res.get('chunk_index', ''),
            'content': res.get('content', '')
        }
        reranked_results.append(rerank_result)
    return reranked_results

測試模組功能

def main(query, top_k):
    vector_store = VectorStore()
    search_results = vector_store.search(
            query=query,
            top_k=top_k 
        )
    unranked_list = search_results["results"]
    reranked_list = rerank(query, unranked_list)
    return unranked_list, reranked_list

if __name__ == "__main__":
    query = "tfidf算法是什麼?"
    top_k = 10
    original_results, reranked_results = main(query, top_k)
    
    # 印出原本結果
    print(" \n=== Original Documents ===")
    for i, res in enumerate(original_results[:5], 1):
        print(f"{i}. Source: {res.get('source', '')}, Score: {res.get('similarity_score', 0):.4f}")

    # 印出 rerank 結果
    print("\n=== Reranked Documents ===")
    for i, res in enumerate(reranked_results[:5], 1):
        print(f"{i}. Source: {res.get('source', '')}, Score: {res.get('similarity_score', 0):.4f}")
=== Original Documents ===
1. Source: day11.txt, Score: 0.5558
2. Source: day11.txt, Score: 0.5507
3. Source: day11.txt, Score: 0.5499
4. Source: day11.txt, Score: 0.5368
5. Source: day11.txt, Score: 0.4771
=== Reranked Documents ===
1. Source: day11.txt, Score: 0.4533
2. Source: day11.txt, Score: 0.3088
3. Source: day11.txt, Score: 0.1541
4. Source: day11.txt, Score: -0.0640
5. Source: day11.txt, Score: -1.1218

結語

今天我們用 Cross-Encoder 的架構做了 Reranking 的任務。就像前面說的,Cross-Encoder 的計算量其實蠻大的,所以它通常被放在最後一步「重排序」的階段,來對前面初步檢索出的文件做更精細的評估,挑出 更符合查詢意圖的內容 來生成最終答案!

那走到今天我們終於算是正式完成了 RAG 中,Retrival 的部分了!

明天我們就要進入 Generation(生成),讓 LLM 來生成最終答案。然後我們也會做一個簡單的互動式介面來完成我們的 RAG 系統大合體!!
我們明天見啦~~~


上一篇
Day 28|RAG Step 2:Retrieval 向量檢索
下一篇
Day 30|RAG Step 4:Generation、網頁展示
系列文
讓電腦聽懂人話:30 天 NLP 入門30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言