Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 4 — Retrieval và đo lường bằng recall@k

Đã đọc 0/9 mục · còn khoảng 3 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Retrieval là bước lấy top-k chunk gần nhất với câu hỏi. Nhưng "gần nhất" không đảm bảo "đúng nhất". Bài này dạy cách retrieval và — quan trọng hơn — cách đo lường chất lượng bằng recall@k để biết pipeline của bạn tốt hay tệ thay vì cảm tính.

Retrieval hoạt động thế nào

  • Embed câu hỏi bằng đúng model đã index.
  • Tính độ tương đồng (cosine) với mọi vector trong index.
  • Trả về k chunk điểm cao nhất (top-k).
def retrieve(query, k=5):
    qv = embed([query])[0]
    hits = client.search("docs", query_vector=qv.tolist(), limit=k)
    return [(h.payload["text"], h.score) for h in hits]

Recall@k là gì

Recall@k = tỉ lệ câu hỏi mà chunk đúng nằm trong top-k kết quả retrieval.

Ví dụ: bạn có 100 câu hỏi, mỗi câu có ít nhất một chunk "vàng" (gold chunk) chứa câu trả lời. Nếu với 82 câu, gold chunk xuất hiện trong top-5 → recall@5 = 82%.

Đây là chỉ số quan trọng nhất vì: nếu chunk đúng không được retrieval kéo ra, LLM giỏi mấy cũng không cứu được. Retrieval là trần chất lượng của cả hệ thống.

Xây tập đánh giá (gold set)

  • Thu thập 30–100 câu hỏi thật.
  • Với mỗi câu, xác định chunk_id nào chứa câu trả lời đúng (gán tay).
  • Lưu thành cặp (câu hỏi, [id gold chunk]).
gold = [
  {"q": "quy đổi phép năm được bao nhiêu ngày?", "gold_ids": [42]},
  {"q": "giờ làm việc mấy giờ?", "gold_ids": [7]},
]

Tính recall@k

def recall_at_k(gold, k):
    hit = 0
    for item in gold:
        ids = [h.id for h in client.search(
            "docs", query_vector=embed([item["q"]])[0].tolist(), limit=k)]
        if any(g in ids for g in item["gold_ids"]):
            hit += 1
    return hit / len(gold)

for k in [1, 3, 5, 10, 20]:
    print(f"recall@{k} = {recall_at_k(gold, k):.2%}")

Đọc kết quả để ra quyết định

  • recall@20 cao nhưng recall@5 thấp: chunk đúng CÓ trong index nhưng bị xếp hạng thấp → cần reranking (Bài 5). Đây là dấu hiệu kinh điển.
  • recall@20 cũng thấp: chunk đúng gần như không được kéo ra → vấn đề ở chunking hoặc embedding, rerank không cứu được.
  • recall@1 ≈ recall@5: retrieval đã rất tốt, có thể giảm k để tiết kiệm.

Chọn k hợp lý

k lớn → recall cao hơn nhưng nhiều nhiễu vào context và tốn token. Chiến lược thực chiến: retrieval với k lớn (ví dụ 20–30) rồi rerank xuống 3–5 để đưa vào LLM. Bài 5 sẽ làm điều này.

Checklist đo lường retrieval

  • [ ] Có gold set ≥ 30 câu, gán chunk đúng bằng tay.
  • [ ] Tính recall@1, @5, @10, @20 — không chỉ một con số.
  • [ ] So sánh recall@5 vs recall@20 để biết cần rerank hay cần sửa chunk.
  • [ ] Chạy lại đo mỗi khi đổi chunk size, model, hoặc prompt viết lại query.
  • [ ] Lưu kết quả theo phiên bản để thấy tiến bộ.

Sai lầm thường gặp

  • Không có gold set: đánh giá RAG bằng "nhìn có vẻ đúng" → không tối ưu được.
  • Chỉ nhìn câu trả lời cuối mà không đo retrieval riêng: không biết lỗi ở retrieval hay ở LLM.
  • Chọn k quá nhỏ rồi kết luận RAG kém, trong khi chunk đúng nằm ở top-8.
  • Nhầm recall với precision: recall@k đo "chunk đúng có được kéo ra không"; precision đo "trong top-k bao nhiêu cái liên quan". Với RAG, recall@k quan trọng hơn vì LLM có thể lọc nhiễu.
  • Đổi embedding model mà không đo lại: giả định model mới tốt hơn là sai lầm phổ biến.
Bài 5: reranking để đẩy chunk đúng lên đầu.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi