पाठ 10 / 28

Approximate Search: IVF और HNSW

दो सबसे आम index परिवार और उनके recall knobs समझें।

जानबूझकर अधिकांश डेटा छोड़ें

लाखों vectors के लिए सब कुछ स्कैन करना बहुत धीमा है, इसलिए approximate nearest neighbour (ANN) indexes अधिकांश डेटा देखने से बचते हैं। IVF (inverted file) vectors को k-means से nlist cells में बाँटता है; query सिर्फ़ nprobe सबसे पास के cells में देखती है। HNSW (hierarchical navigable small world) परतदार graph बनाता है जिसमें हर vector पास के पड़ोसियों से जुड़ता है; query graph पर लालच से लक्ष्य की ओर चलती है, और efSearch तय करता है कि चलते समय कितने उम्मीदवार रखे जाएँ। दोनों 100% से कम recall देते हैं: सच्चा निकटतम पड़ोसी कभी-कभी छूट जाता है। nprobe या efSearch बढ़ाने से recall और latency साथ बढ़ते हैं, इसलिए आवश्यक recall तक अपने डेटा पर tune करें। IVF को प्रशिक्षण चरण चाहिए और कम memory लगती है; HNSW में प्रशिक्षण नहीं, बहुत तेज़ और सटीक है, पर ज़्यादा memory लेता है और अद्यतन कम सस्ते में समर्थित करता है।

Recall बनाम खोज प्रयास, चलाकर

मैंने यह Python virtual environment में numpy 2.5.3, scikit-learn 1.9.1 और faiss-cpu 1.15.1 के साथ चलाया, निश्चित random seeds के साथ ताकि संख्याएँ दोहराई जाएँ। 20,000 clustered 64-आयामी vectors पर 100 cells वाला IVF 1 cell जाँचने पर 0.743 recall@10, 4 पर 0.998 और 16 पर 1.000 पाता है। HNSW efSearch=4 पर 0.643 से 16 पर 0.932 और 64 पर 1.000 जाता है। Recall सटीक flat-index उत्तरों के विरुद्ध मापा गया है। Latency नहीं दिखाई क्योंकि timings मशीन के अनुसार बदलते हैं।

import numpy as np, faiss
rng = np.random.default_rng(0)
d, n, nq = 64, 20000, 200
centers = rng.normal(size=(50, d)) * 3
xb = (centers[rng.integers(0, 50, n)] + rng.normal(size=(n, d))).astype("float32")
xq = (centers[rng.integers(0, 50, nq)] + rng.normal(size=(nq, d))).astype("float32")

flat = faiss.IndexFlatL2(d); flat.add(xb)
_, truth = flat.search(xq, 10)                                   # exact answers

def recall(found): return float(np.mean([len(set(f) & set(t)) / 10 for f, t in zip(found, truth)]))

ivf = faiss.IndexIVFFlat(faiss.IndexFlatL2(d), d, 100); ivf.train(xb); ivf.add(xb)
for nprobe in (1, 4, 16):
    ivf.nprobe = nprobe
    print(f"IVF  nprobe={nprobe:2d} recall@10 = {recall(ivf.search(xq, 10)[1]):.3f}")

hnsw = faiss.IndexHNSWFlat(d, 16); hnsw.hnsw.efConstruction = 100; hnsw.add(xb)
for ef in (4, 16, 64):
    hnsw.hnsw.efSearch = ef
    print(f"HNSW efSearch={ef:2d} recall@10 = {recall(hnsw.search(xq, 10)[1]):.3f}")

Output:

IVF  nprobe= 1 recall@10 = 0.743
IVF  nprobe= 4 recall@10 = 0.998
IVF  nprobe=16 recall@10 = 1.000
HNSW efSearch= 4 recall@10 = 0.643
HNSW efSearch=16 recall@10 = 0.932
HNSW efSearch=64 recall@10 = 1.000

सटीक उत्तरों के विरुद्ध tune करें

अपने ANN index का recall@k असली queries के नमूने पर flat index के विरुद्ध निकालें। जो सबसे सस्ती setting लक्ष्य पूरा करे वह चुनें।

त्वरित जाँच: nprobe (IVF) या efSearch (HNSW) बढ़ाने से क्या होता है?

  • Recall और latency दोनों बढ़ाता है
  • Recall और latency दोनों घटाता है
  • Embedding model बदलता है
  • Vectors हटाता है
Answer

Recall और latency दोनों बढ़ाता है — ज़्यादा खोज प्रयास ज़्यादा सच्चे पड़ोसी पाता है पर समय लेता है।