0%
البحث الهجين وإعادة الترتيب

البحث الهجين

طريقتان غير مثاليتين تفشلان في حالات مختلفة

10-15 دقيقة

البحث الهجين وإعادة الترتيب

يجيب نظامك بشكل صحيح عن الأسئلة المصاغة بلغة طبيعية، لكنه يخفق باستمرار في ثلاث فئات: المراجع الدقيقة مثل MNT-2019-07، والاختصارات الداخلية، والأسماء العلم النادرة. جرّبتَ نموذج تضمين (embedding) أكبر، وبُعدًا أعلى، وتقطيعًا أدق. لكن الاستدعاء (recall) لا يتحسن إلا بنقطتين. وهذه الأسئلة تمثل 30% من حركة الاستخدام الفعلية، لأن المستخدمين الذين يعرفون مرجع المستند يكتبونه بدلًا من وصف ما يبحثون عنه.

لن يحل أي نموذج تضمين هذه المشكلة، للسبب الذي رأيناه في الدرس 3: المرجع الأبجدي الرقمي لا دلالة متعلَّمة له، ومتجهه ضجيج. في المقابل، يجده الفهرس المعجمي فورًا، لأن هذا هو بالضبط اختصاصه: مطابقة السلاسل النصية النادرة. وعلى العكس من ذلك، يفشل الفهرس المعجمي حيث يتفوق البحث المتجهي: السؤال الذي لا يشترك مع الإجابة في أي كلمة. فالطريقتان لا تتنافسان بل تتكاملان، والسؤال المهم هو كيفية دمجهما.

BM25 في ثلاثين سطرًا

تصنّف BM25 المستندات بحسب تكرار مصطلحات الاستعلام، مع ترجيحها بندرتها في المدونة (corpus) وتخفيف أثر طول المستند. يمكنك الحصول عليها من PostgreSQL عبر ts_rank، أو تنفيذها في الذاكرة.

from rank_bm25 import BM25Okapi
import re

def normaliser(texte: str) -> list[str]:
    # الإبقاء على الأرقام والشرطات: فهي التي تحمل المراجع
    return re.findall(r"[a-zà-ÿ0-9][a-zà-ÿ0-9\-_]*", texte.lower())

class IndexLexical:
    def __init__(self, passages: list[str]):
        self.passages = passages
        self.bm25 = BM25Okapi([normaliser(p) for p in passages])

    def chercher(self, question: str, k: int = 30) -> list[tuple[int, float]]:
        scores = self.bm25.get_scores(normaliser(question))
        ordre = sorted(range(len(scores)), key=lambda i: -scores[i])[:k]
        return [(i, float(scores[i])) for i in ordre]

التعليقات

التعليقات مدعومة عبر GitHub Discussions

سجّل الدخول باستخدام GitHub للمشاركة في النقاش

تم نسخ الرابط!