पाठ 11 / 28

Recall मापना और Tune करना (ef_search)

Index की तुलना सटीक खोज से करें और recall knob घुमाएँ।

Recall एक setting है, गारंटी नहीं

Query समय पर hnsw.ef_search (डिफ़ॉल्ट 40) खोजी जाने वाली उम्मीदवार सूची का आकार है: ऊँचे का अर्थ बेहतर recall और ज़्यादा काम। मापने के लिए: असली query vectors का नमूना चुनें, index बंद करके (SET enable_indexscan = off) हर एक का सटीक top-k निकालें, फिर कई ef_search मानों पर index से top-k निकालें और औसत overlap, recall@k, रिपोर्ट करें। ध्यान दें कि ef_search कम से कम k होना चाहिए। सबसे कम setting चुनें जो आपका recall लक्ष्य पूरा करे (कई applications 0.95 से 0.99 का लक्ष्य रखते हैं), और डेटा बढ़ने या parameters बदलने पर दोबारा मापें। HNSW निर्माण पूरी तरह निश्चित नहीं है, इसलिए सटीक recall संख्याएँ builds के बीच थोड़ी बदलती हैं; यहाँ तीन builds में ef_search=10 ने लगभग 0.96 से 0.97, 40 ने लगभग 0.985 और 200 ने लगभग 0.998 दिया।

सटीक खोज के विरुद्ध recall, SQL में, चलाकर

मैंने यह SQL Docker container में pgvector extension संस्करण 0.8.6 के साथ PostgreSQL 16 पर चलाया। 100 query vectors; सटीक उत्तर मजबूर sequential scan से आते हैं, फिर छोटा function तीन ef_search मानों पर HNSW परिणामों से overlap मापता है। HNSW builds थोड़े बदलते हैं, इसलिए आउटपुट सटीक भिन्नों की जगह सीमा-जाँचें (सब true) दिखाता है।

SET client_min_messages = warning;
CREATE TEMP TABLE queries AS SELECT id AS qid, embedding AS qv FROM big WHERE id % 200 = 0;      -- 100 query vectors

SET enable_indexscan = off;                                   -- force the exact (sequential) plan for ground truth
CREATE TEMP TABLE truth AS
  SELECT q.qid, array_agg(t.id) AS ids FROM queries q
  CROSS JOIN LATERAL (SELECT id FROM big b ORDER BY b.embedding <-> q.qv LIMIT 10) t GROUP BY q.qid;
RESET enable_indexscan;                                       -- now the HNSW index can be used again

DROP FUNCTION IF EXISTS recall_at(int);
CREATE FUNCTION recall_at(ef int) RETURNS numeric LANGUAGE plpgsql AS $$
DECLARE q record; found int[]; hit int := 0; total int := 0;
BEGIN
  EXECUTE format('SET LOCAL hnsw.ef_search = %s', ef);
  FOR q IN SELECT qid, qv FROM queries LOOP
    SELECT array_agg(id) INTO found FROM (SELECT id FROM big ORDER BY embedding <-> q.qv LIMIT 10) x;
    hit := hit + cardinality(ARRAY(SELECT unnest(found) INTERSECT SELECT unnest(ids) FROM truth WHERE truth.qid = q.qid));
    total := total + 10;
  END LOOP;
  RETURN round(hit::numeric / total, 3);
END $$;

SELECT recall_at(10)  >= 0.95  AS "ef_search=10 recall >= 0.95",
       recall_at(40)  >= 0.98  AS "ef_search=40 recall >= 0.98",
       recall_at(200) >= 0.995 AS "ef_search=200 recall >= 0.995";

Output:

 ef_search=10 recall >= 0.95 | ef_search=40 recall >= 0.98 | ef_search=200 recall >= 0.995 
-----------------------------+-----------------------------+-------------------------------
 t                           | t                           | t
(1 row)

ef_search को कम से कम LIMIT जितना रखें

ef_search आपकी माँगी rows से छोटा हो तो index उतने उम्मीदवार नहीं लौटा सकता।

त्वरित जाँच: HNSW index का recall कैसे मापते हैं?

  • इसे मापा नहीं जा सकता
  • उसकी rows गिनकर
  • Table का नाम पढ़कर
  • Queries के नमूने पर उसके परिणामों की सटीक खोज से तुलना करके
Answer

Queries के नमूने पर उसके परिणामों की सटीक खोज से तुलना करके — Recall = approximate और सटीक top-k परिणामों का overlap।