पाठ 9 / 27

TF-IDF से Keyword Search

भारित शब्द-overlap से दस्तावेज़ों को score करें और देखें कहाँ विफल होता है।

दुर्लभ शब्द ज़्यादा मायने रखते हैं

TF-IDF किसी शब्द को इस आधार पर भार देता है कि वह दस्तावेज़ में कितनी बार आता है (term frequency) और सभी दस्तावेज़ों में कितना दुर्लभ है (inverse document frequency), इसलिए "leave" का वज़न "the" से ज़्यादा होता है। दस्तावेज़ और queries विरल vectors बनते हैं और cosine similarity से तुलना होते हैं। Keyword तरीक़े तेज़ हैं, मॉडल नहीं चाहिए, और सटीक शब्दों के लिए उत्कृष्ट हैं: उत्पाद कोड, error संख्याएँ, नाम, संक्षिप्त रूप। उनकी कमज़ोरी शब्दावली-बेमेल है: दस्तावेज़ से अलग शब्दों वाला सवाल कुछ नहीं पाता।

पाँच नीति अंशों पर TF-IDF, चलाकर

मैंने यह सादा-Python (सिर्फ़ standard library) उदाहरण चलाया। "carry over" और "hotel limit" सवाल सही अंश पाते हैं। पर "can I work from home" expense नीति को पहले और remote-work नीति को दूसरे स्थान पर रखता है, क्योंकि दस्तावेज़ में "remote" लिखा है "home" नहीं: शब्दावली-बेमेल जिसे embeddings बेहतर सँभालते।

DOCS = {
 "leave": "Employees get 24 days of paid leave per year. Unused leave up to 5 days can be carried over to the next year.",
 "remote": "Remote work is allowed up to 3 days per week with manager approval. Core hours are 11:00 to 16:00.",
 "expense": "Expenses above 5000 rupees need approval from a director. Submit receipts within 30 days.",
 "security": "Use a password manager and enable two factor authentication. Report lost laptops within 24 hours.",
 "travel": "Flights must be booked at least 14 days in advance. Hotel cost is capped at 6000 rupees per night.",
}
import math, re
from collections import Counter

def tok(t): return re.findall(r"[a-z0-9]+", t.lower())

names = list(DOCS)
docs = [tok(DOCS[n]) for n in names]
N = len(docs)
df = Counter(w for d in docs for w in set(d))
idf = {w: math.log(N / df[w]) + 1 for w in df}

def vec(words):
    tf = Counter(words)
    return {w: tf[w] * idf.get(w, 0) for w in tf}

def cos(a, b):
    dot = sum(a[w] * b.get(w, 0) for w in a)
    na = math.sqrt(sum(v * v for v in a.values())); nb = math.sqrt(sum(v * v for v in b.values()))
    return dot / (na * nb) if na and nb else 0.0

vecs = [vec(d) for d in docs]
def search(q, k=3):
    qv = vec(tok(q))
    return sorted(((round(cos(qv, v), 3), n) for n, v in zip(names, vecs)), reverse=True)[:k]

print(search("how many days of leave can I carry over"))
print(search("what is the hotel limit"))
print(search("can I work from home"))

Output:

[(0.541, 'leave'), (0.032, 'expense'), (0.025, 'travel')]
[(0.227, 'travel'), (0.128, 'leave'), (0.077, 'remote')]
[(0.171, 'expense'), (0.131, 'remote'), (0.118, 'leave')]

पाठ को दोनों बार एक-सा normalise करें

दस्तावेज़ों और queries को एक ही function से lowercase और tokenise करें, वरना मिलान चुपचाप ग़ायब हो जाते हैं।

त्वरित जाँच: Keyword search सबसे साफ़ कब विफल होता है?

  • जब index छोटा हो
  • जब सटीक error code खोजा जाए
  • जब सवाल दस्तावेज़ से अलग शब्द उपयोग करे
  • जब पाठ अंग्रेज़ी हो
Answer

जब सवाल दस्तावेज़ से अलग शब्द उपयोग करे — सटीक-शब्द मिलान एक ही विचार के अलग शब्दांकन नहीं जोड़ सकता।