Cross-Encoder Reranking 之後,排名第 1 到第 5 的文件可能都是關於同一條路線的描述——從不同文件來源重複提到龍洞某條 5.11a 的路線。這些文件對查詢都很相關,但作為 context 送進 LLM 是一種浪費:重複的資訊不會讓回答更好,反而佔用 context 視窗。
MMR(Maximal Marginal Relevance)解決的是這個問題:在相關性和多樣性之間取平衡,避免把相似的文件都選進去。
MMR 演算法
MMR 是個貪心選擇演算法。每次從候選集選一個文件加入已選集,選擇標準:
MMR(d) = λ × relevance(d, query) - (1 - λ) × max_sim(d, already_selected)
relevance(d, query):文件對查詢的相關性(Cross-Encoder 分數)max_sim(d, already_selected):文件與已選文件中最相似者的相似度λ:相關性的權重(0 → 完全多樣,1 → 完全相關)
這個式子跟 Carbonell & Goldstein 1998 年的原始定義一致:λ=1 時 MMR 退化成一般的相關性排序,λ=0 時變成純粹的多樣性排序。
有個容易踩到的前提:兩項要在可比較的尺度上才有意義。relevance 是 Cross-Encoder 分數、max_sim 是下面那個 metadata 相似度,兩者都必須落在同一個範圍(這裡都是 0–1),λ 才真的是在做「權衡」。如果一邊是 0–1、另一邊是沒有上界的原始 logit,λ 調來調去其實只是在調一個假的旋鈕。
第一個文件:直接選相關性最高的,沒有「已選集」做比較。
第二個文件:在剩餘候選中找 MMR 分數最高的。與已選文件高度相似的候選,max_sim 項很大,MMR 分數被壓低。
依此類推,直到選夠 top_k 個或候選耗盡。
相似度計算
文件間的相似度不用 embedding cosine(太耗計算),而是基於 metadata 的結構化相似度:
function documentSimilarity(a: Document, b: Document): number {
let score = 0;
// 同一岩場 → 高相似
if (a.crag_id && a.crag_id === b.crag_id) score += 0.4;
// 難度接近(數值差 < 5)→ 相似
if (Math.abs(a.grade_numeric - b.grade_numeric) < 5) score += 0.3;
// 同一攀岩類型
if (a.route_type === b.route_type) score += 0.2;
// 同一文件類型(route / crag / video)
if (a.type === b.type) score += 0.1;
return score;
}
這個 metadata-based 相似度計算便宜很多,對攀岩內容也很直觀:來自同一岩場的文件,最可能在內容上重複。權重刻意湊成 0.4 + 0.3 + 0.2 + 0.1 = 1.0,上界剛好對齊 Workers AI 那條 sigmoid 之後的 Cross-Encoder 分數(0–1),這正是上一節說的「兩項要同尺度」。若你的 reranker 回的是未經 sigmoid 的原始 logit,這個對齊就不成立,得先自己壓到同一個尺度。
λ 參數的意義
λ 決定推薦的「個性」:
| λ 值 | 效果 | 適合場景 |
|---|---|---|
| 0.9 | 幾乎完全按相關性 | 使用者問明確問題 |
| 0.7 | 相關性優先,保留一定多樣性 | 一般推薦(預設) |
| 0.5 | 相關性與多樣性各半 | 探索型查詢 |
| 0.3 | 多樣性優先 | 「給我驚喜」場景 |
0.3 和 0.7 這兩個值不是隨便挑的:原始論文就直接建議過這組搭配——先用 λ≈0.3 把查詢周邊的資訊空間攤開來看,等使用者收斂出更精確的查詢後,再用 λ≈0.7 聚焦。表格裡的其他數字則是這個系統實測出來的,沒有論文背書。
系統的預設值是 0.7,可以通過 ai_config 動態調整,不需要重新部署。
熱門度加權
MMR 之後再做一次熱門度加權排序:
const finalScore = mmrScore + popularityWeight * popularityScore;
popularityScore 根據路線/岩場的點擊率、評分、評論數計算。這裡有個必須自己顧好的細節:點擊數、評論數這類原始計數沒有上界,直接加進來就會壓過 MMR 分數,變成「熱門度排序 + 一點點相關性」。這正是 RRF 那篇講的量綱問題,只是換個地方出現。所以 popularityScore 必須先壓到 0–1(取百分位或對數後再正規化都行),popularityWeight 才是一個看得懂的旋鈕。熱門度加權的邏輯很簡單:當兩個文件 MMR 分數接近時,讓社群認可度高的排前面,推薦出來的路線更可能讓使用者滿意。
這也解決了一個 embedding 的盲點:一條新路線(資訊完整,評分高)和一條熱門老路線的 embedding 距離可能差不多,但使用者更可能喜歡有口碑的。
整體選取流程
Cross-Encoder 排序後候選
↓
[MMR 貪心選取] ← λ=0.7
↓
MMR 選取結果(Top-K 個)
↓
[熱門度加權排序]
↓
最終文件集 → LLM 生成 context
整體來說
MMR 在 RAG 系統中經常被低估。把 Top-10 相關文件直接送進 LLM,和 MMR 選出 10 個多樣的相關文件,前者的 context 往往有很多重複資訊,後者能讓 LLM 看到問題的不同面向,回答更全面。
不過原始論文的證據強度值得誠實交代:那是一篇兩頁的 SIGIR 1998 poster,作者自己寫的是「preliminary results」。它在文件排序上的評估只是五個人的 pilot study,而句子層級的精確率比較中,λ=1、0.7、0.3 三組沒有統計上的顯著差異;MMR 最明確的效益出現在多文件摘要——也就是「來源之間高度重複」的場景。這跟 RAG 的處境剛好對得上(多路搜尋合併後大量近似重複),但也提醒一件事:候選本來就不重複時,MMR 幫不上忙,只會平白把一份更相關的文件換掉。要不要開,看你的候選集有多少重複,不要當成預設就該有的一層。
加上熱門度加權,recommendation 推薦結果既有語義相關性(向量搜尋 + 重排序),又有社群驗證(熱門度),是一個在算法嚴謹性和使用者體驗之間找到平衡的設計。
更新紀錄
- 2026-08-19:對照官方文件逐篇查證翻新,移除易腐內容,並收進「RAG 技法大全」系列
參考資料
Loading...