Hybrid Search
Khái niệm cơ bản
Tưởng tượng bạn đang tìm sách trong thư viện…
- Keyword Search: Tìm đúng tên sách → Miss sách có tên khác nhưng cùng nội dung
- Semantic Search: Tìm theo chủ đề → Có thể miss sách có tên chính xác bạn cần
- Hybrid Search: Cả hai! → Không bỏ sót
Vấn đề với Semantic Search thuần
User: "Thông số iPhone 15 Pro Max"
❌ Vector Search có thể trả về:
- "So sánh các flagship 2024" (liên quan chung)
- "Camera smartphone cao cấp" (gần về ý nghĩa)
✅ Keyword Search trả về chính xác:
- "iPhone 15 Pro Max: Thông số kỹ thuật chi tiết"Semantic Search yếu với:
- Tên riêng (iPhone, Tesla, Nguyễn Văn A)
- Mã sản phẩm (SKU-12345, INV-2024-001)
- Từ viết tắt (OT, WFH, B2B)
Cơ chế Hybrid Search
User Query
│
┌─────────┴─────────┐
▼ ▼
[BM25/Sparse] [Dense Vector]
│ │
Keyword results Semantic results
│ │
└─────────┬─────────┘
▼
[RRF Fusion]
│
▼
Final RankingsBM25 (Spare Vector)
Thuật toán classic, đếm tần suất từ (TF-IDF cải tiến):
- TF: Term Frequency - từ xuất hiện bao nhiêu lần
- IDF: Inverse Document Frequency - từ hiếm có trọng số cao hơn
Dense Vector (Embeddings)
Sử dụng embedding model để tìm theo ý nghĩa.
Reciprocal Rank Fusion (RRF)
Công thức hợp nhất kết quả:
RRF(d) = Σ 1/(k + rank(d))Trong đó:
R= tập hợp các rankings (BM25, Dense)r(d)= rank của document d trong ranking rk= hằng số (thường = 60)
Ví dụ tính tay
BM25 Results: Doc A (rank 1), Doc B (rank 2), Doc C (rank 5) Dense Results: Doc B (rank 1), Doc C (rank 2), Doc A (rank 8)
RRF(A) = 1/(60+1) + 1/(60+8) = 0.0164 + 0.0147 = 0.0311
RRF(B) = 1/(60+2) + 1/(60+1) = 0.0161 + 0.0164 = 0.0325 ← Winner!
RRF(C) = 1/(60+5) + 1/(60+2) = 0.0154 + 0.0161 = 0.0315Kết quả cuối: B > C > A (Doc B thắng vì đều tốt ở cả hai)
Code: Hybrid Search với Weaviate
import weaviate
client = weaviate.connect_to_local()
# Hybrid query với alpha (cân bằng sparse vs dense)
response = client.query.get("Document", ["content", "title"]).with_hybrid(
query="iPhone 15 Pro Max specifications",
alpha=0.5, # 0.5 = 50% keyword, 50% semantic
fusion_type="ranked" # Dùng RRF
).with_limit(10).do()
# alpha = 1.0 → Pure semantic
# alpha = 0.0 → Pure keyword
# alpha = 0.5 → Balanced (recommended)Code: Hybrid Search với Pinecone
from pinecone import Pinecone
pc = Pinecone(api_key="xxx")
index = pc.Index("my-index")
# Hybrid query
response = index.query(
vector=embed("iPhone 15 Pro Max specs"),
sparse_values={
"indices": [1, 2, 3], # Token IDs
"values": [0.5, 0.3, 0.2] # BM25 weights
},
top_k=10,
include_metadata=True
)So sánh: Khi nào dùng gì?
| Loại Query | Keyword Only | Semantic Only | Hybrid |
|---|---|---|---|
| ”iPhone 15 Pro Max” | ✅ Best | ⚠️ May miss | ✅ |
| “Điện thoại camera tốt” | ❌ Miss | ✅ Best | ✅ |
| “JIRA-2024-001” | ✅ Best | ❌ Miss | ✅ |
| “Làm sao tối ưu hiệu suất” | ❌ Miss | ✅ Best | ✅ |
💡 Rule of thumb: Luôn dùng Hybrid nếu database support!
Bài tập thực hành
Mục tiêu
Implement RRF từ đầu để hiểu cách hoạt động.
Code
def reciprocal_rank_fusion(rankings: list[list[str]], k: int = 60) -> dict:
"""
rankings: list of ranked document lists
Example: [["A", "B", "C"], ["B", "C", "A"]]
"""
scores = {}
for ranking in rankings:
for rank, doc_id in enumerate(ranking, start=1):
if doc_id not in scores:
scores[doc_id] = 0
scores[doc_id] += 1 / (k + rank)
# Sort by score descending
return dict(sorted(scores.items(), key=lambda x: x[1], reverse=True))
# Test
bm25_results = ["doc_a", "doc_b", "doc_c", "doc_d"]
dense_results = ["doc_b", "doc_c", "doc_a", "doc_e"]
fused = reciprocal_rank_fusion([bm25_results, dense_results])
print(fused)
# Expected: doc_b > doc_c > doc_a > doc_d > doc_eThử thách
Thêm weight cho mỗi ranking (ví dụ: BM25 weight 0.3, Dense weight 0.7).
Tóm tắt
| Khái niệm | Ý nghĩa |
|---|---|
| Hybrid Search | Kết hợp keyword + semantic |
| BM25 | Sparse vector, đếm từ |
| RRF | Thuật toán hợp nhất rankings |
| Alpha | Tỷ lệ cân bằng (0.5 recommended) |
Bài tiếp theo: Reranking - Lọc tinh kết quả.
Last updated on