لماذا ينجح الاسترجاع على مرحلتين
افهموا المفاضلة بين الاستدعاء والدقة في الاسترجاع أحادي المرحلة، وكيف يحقق مسترجع سريع وخشن يتبعه معيد ترتيب بطيء لكنه دقيق أفضل ما في النهجين.
لماذا ينجح الاسترجاع على مرحلتين درس مجاني في AI Engineering Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Engineering Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.
المفاضلة بين الاستدعاء والدقة في الاسترجاع
يواجه كل نظام استرجاع مفاضلة أساسية: يقيس الاستدعاء عدد المستندات ذات الصلة التي عثرت عليها (هل فاتك أيٌّ منها؟)، بينما تقيس الدقة مدى صحة النتائج الأولى (كم عدد المستندات المسترجعة ذات الصلة فعلًا؟). ويُعد تعظيم المقياسين في الوقت نفسه مكلفًا حسابيًا. إذ تضحي المسترجِعات السريعة بالدقة لصالح الاستدعاء، بينما تضحي أدوات الترتيب الدقيقة بالسرعة لصالح الدقة.
Bi-Encoder مقابل Cross-Encoder: الفرق الأساسي
يختلف نوعا النماذج اللذان يشكلان أساس الاسترجاع على مرحلتين في كيفية تعاملهما مع الاستعلام والمستند. يرمّز bi-encoder الاستعلام وكل مستند على حدة، ويقيس التشابه بين متجهاتهما؛ وهو سريع، لكن الترميز المستقل يحدّه. أما cross-encoder فيتعامل مع الاستعلام والمستند بعد وصلهما كمدخل واحد، ما يتيح تفاعلًا عميقًا بينهما؛ وهو عالي الدقة، لكن تعقيده O(n) على مجموعة المرشحين.
# Bi-encoder: compute query embedding ONCE, compare to all doc embeddings
# O(1) query encoding + O(n) dot products via ANN index = fast
query_vec = embed(query) # done once
results = vector_index.search(query_vec, top_k=100) # fast ANN search
# Cross-encoder: re-scores (query, doc) pairs jointly
# O(k) forward passes for k candidate documents = slow but accurate
for doc in results[:100]:
score = cross_encoder.score(query, doc.text) # joint scoringالمرحلة الأولى: استرجاع تقريبي سريع
المرحلة الأولى هي مسترجِع سريع — عادةً ما يكون bi-encoder مع فهرس أقرب جار تقريبي أو فهرس BM25 — يسترجع مجموعة كبيرة من المرشحين (50 إلى 200 مستند) باستدعاء مرتفع ودقة متوسطة. والهدف ليس تحقيق الدقة، بل عدم تفويت المستندات ذات الصلة. فنحن نلقي شبكة واسعة ونقبل بعض الإيجابيات الكاذبة، مع علمنا بأن المرحلة الثانية ستنقحها.
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
# Stage 1: retrieve 100 candidates (high recall, modest precision)
vectorstore = FAISS.from_documents(documents, OpenAIEmbeddings())
coarse_retriever = vectorstore.as_retriever(
search_kwargs={'k': 100} # large candidate set
)
candidates = coarse_retriever.invoke(query)
print(f'Stage 1: retrieved {len(candidates)} candidate documents')المرحلة الثانية: إعادة ترتيب دقيقة باستخدام Cross-Encoder
تأخذ المرحلة الثانية مجموعة المرشحين من المرحلة الأولى، وتعيد تقييم كل زوج (استعلام، مستند) باستخدام cross-encoder يقرأهما معًا. وبما أنه يعالج 50 إلى 200 مرشح فقط، لا المجموعة الكاملة، فإنه يستطيع تحمّل تكلفة الترميز المشترك الباهظة. ويجعل الانتباه العميق في cross-encoder إلى المدخل الموصول أكثر دقة بكثير في تقدير الصلة الحقيقية من bi-encoder.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
def rerank(query: str, candidates: list[str], top_k: int = 5) -> list[str]:
# Score each (query, document) pair jointly
pairs = [[query, doc] for doc in candidates]
scores = reranker.predict(pairs)
# Sort by score descending
ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
return [doc for doc, _ in ranked[:top_k]]
candidate_texts = [doc.page_content for doc in candidates]
final_docs = rerank(query, candidate_texts, top_k=5)
print(f'Stage 2: selected top {len(final_docs)} documents after re-ranking')لماذا ينجح هذا الدمج
يستغل التصميم ذو المرحلتين عدم تماثل مهمًا: إذ يتوسع بحث ANN السريع ليشمل ملايين المستندات خلال أجزاء من الثانية، بينما يعمل cross-encoder الدقيق في المرحلة الثانية على مجموعة المرشحين الصغيرة فقط. وهكذا تحصل على قابلية توسع البحث التقريبي مع دقة التقييم المشترك الدقيق. ويكون المسار الإجمالي سريعًا وعالي الدقة، وهو ما لا تحققه أي من المرحلتين منفردة.
ملامح زمن الاستجابة للاسترجاع على مرحلتين
في مسار نموذجي من مرحلتين: تستغرق المرحلة الأولى (بحث ANN للمتجهات في مليون مستند) من 5 إلى 20 مللي ثانية، بينما تستغرق المرحلة الثانية (cross-encoder على 100 مرشح) من 100 إلى 500 مللي ثانية، بحسب طول المستند والعتاد. وتتراوح ميزانية زمن الاستجابة الإجمالية بين 150 و600 مللي ثانية، وهو مقبول لمعظم التطبيقات. ويمكن لتسريع GPU في المرحلة الثانية خفض زمن إعادة الترتيب إلى أقل من 30 مللي ثانية للمستندات القصيرة، ما يجعل المسار منافسًا للاسترجاع أحادي المرحلة في التطبيقات الحساسة لزمن الاستجابة.
import time
def two_stage_search(query, coarse_retriever, reranker, top_k=5):
t0 = time.perf_counter()
candidates = coarse_retriever.invoke(query) # stage 1
t1 = time.perf_counter()
candidate_texts = [c.page_content for c in candidates]
final_docs = rerank(query, candidate_texts, top_k) # stage 2
t2 = time.perf_counter()
print(f'Stage 1 (retrieval): {(t1-t0)*1000:.1f}ms')
print(f'Stage 2 (re-ranking): {(t2-t1)*1000:.1f}ms')
print(f'Total: {(t2-t0)*1000:.1f}ms')
return final_docsاختيار حجم مجموعة المرشحين المناسب
يُعد حجم مجموعة المرشحين في المرحلة الأولى معلمة فائقة الأهمية. فإذا كان صغيرًا جدًا (10 مثلًا)، فقد تُفقد المستندات ذات الصلة قبل بدء إعادة الترتيب. وإذا كان كبيرًا جدًا (500 مثلًا)، ارتفع زمن استجابة المرحلة الثانية بشدة. ويساعد منحنى الاستدعاء عند N — أي عدد المستندات ذات الصلة التي تُلتقط عند قيم مختلفة لـ N — في توجيه هذا الاختيار. وتتراوح النقاط المثلى المعتادة بين 50 و150 مرشحًا، حيث يقترب الاستدعاء من التشبع مع بقاء زمن الاستجابة قابلًا للإدارة.
def recall_at_n(coarse_retriever, test_queries, golden_relevant, n_values):
for n in n_values:
recalls = []
for query, relevant in zip(test_queries, golden_relevant):
# Temporarily set k to n
coarse_retriever.search_kwargs['k'] = n
results = coarse_retriever.invoke(query)
retrieved_ids = {r.metadata.get('id') for r in results}
relevant_found = len(set(relevant) & retrieved_ids)
recalls.append(relevant_found / len(relevant))
avg = sum(recalls) / len(recalls)
print(f'N={n}: recall={avg:.3f}')المرحلة الأولى الهجينة + المرحلة الثانية باستخدام Cross-Encoder
يجمع أقوى إعداد للاسترجاع على مرحلتين بين مسترجِع هجين (كثيف + BM25) في المرحلة الأولى وcross-encoder في المرحلة الثانية. يزيد الاسترجاع الهجين استدعاء المرحلة الأولى إلى أقصى حد من خلال الجمع بين المطابقة الدلالية ومطابقة الكلمات المفتاحية، ثم يختار cross-encoder بدقة المستندات الأكثر صلة من مجموعة المرشحين المدمجة. ويحقق هذا الإعداد باستمرار جودة استرجاع تُعد من الأفضل وفقًا للمعايير.
from langchain.retrievers import EnsembleRetriever
# Stage 1: hybrid retrieval for maximum recall
hybrid_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6],
)
# Stage 2: cross-encoder re-ranking for high precision
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
cross_encoder_model = HuggingFaceCrossEncoder(model_name='cross-encoder/ms-marco-MiniLM-L-6-v2')
compressor = CrossEncoderReranker(model=cross_encoder_model, top_n=5)
from langchain.retrievers import ContextualCompressionRetriever
two_stage = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=hybrid_retriever,
)واجهات برمجة تطبيقات إعادة الترتيب التجارية
إذا أردت دقة cross-encoder دون إدارة نموذجك بنفسك، يوفر كل من Cohere Rerank وJina AI Reranker واجهات API سحابية لإعادة الترتيب. ترسل استعلامًا وقائمة بنصوص المستندات، وتتلقى درجات الصلة. وتستخدم هذه الواجهات نماذج cross-encoder كبيرة (غالبًا بأكثر من 500M معلمة) تتفوق على نماذج cross-encoder الصغيرة المستضافة ذاتيًا، مقابل زمن استجابة إضافي للواجهة (من 50 إلى 300 مللي ثانية) وتسعير لكل مستند أُعيد ترتيبه.
import cohere
co = cohere.Client('YOUR_API_KEY')
def cohere_rerank(query: str, documents: list[str], top_k: int = 5):
response = co.rerank(
model='rerank-english-v3.0',
query=query,
documents=documents,
top_n=top_k,
)
return [
{'text': documents[r.index], 'score': r.relevance_score}
for r in response.results
]
final = cohere_rerank(query, candidate_texts, top_k=5)
for doc in final:
print(f'Score {doc["score"]:.3f}: {doc["text"][:80]}')متى يكون الاسترجاع على مرحلتين مبالغًا فيه
يضيف الاسترجاع على مرحلتين تعقيدًا وزمن استجابة مقارنةً بالاسترجاع أحادي المرحلة، وهو ليس ضروريًا دائمًا. ففي المجموعات الصغيرة التي تضم أقل من 10,000 مستند، قد يكون تشغيل cross-encoder واحد على المجموعة كاملة سريعًا بما يكفي. وفي التطبيقات التي يكون فيها زمن الاستجابة الأقل من 100 مللي ثانية أمرًا بالغ الأهمية وتكون مكاسب الدقة محدودة، قد يكون الاسترجاع الكثيف أحادي المرحلة أفضل. استخدم الاسترجاع على مرحلتين عندما تكون لديك مجموعة كبيرة، ومتطلبات دقة مرتفعة، وإمكانية تحمّل زمن استرجاع يتراوح بين 200 و500 مللي ثانية.
الاسترجاع على ثلاث مراحل للنطاقات الهائلة
بالنسبة إلى مجموعات نصوص تضم عشرات الملايين من المستندات، يُستخدم أحيانًا خط أنابيب من ثلاث مراحل: تسترجع المرحلة الأولى 10,000 مرشح باستخدام ANN، ثم تعيد المرحلة الثانية ترتيبها إلى 100 مرشح باستخدام cross-encoder صغير وسريع، ثم تعيد المرحلة الثالثة ترتيبها إلى 5 مرشحين باستخدام cross-encoder كبير وقوي. تطبّق كل مرحلة نموذجًا أعلى تكلفة وأكثر دقة على مجموعة أصغر. تُستخدم هذه البنية في محركات البحث واسعة النطاق وأنظمة الأسئلة والأجوبة حول المستندات.
اختبار سريع
اختبر مدى فهمك لسبب نجاح الاسترجاع ذي المرحلتين مما تعلّمته في هذا الدرس.
مراجعة الدرس
تعلّمت في هذا الدرس أن bi-encoders سريعة، لكنها تقتصر على ترميز الاستعلام والمستند بشكل مستقل، وأن cross-encoders دقيقة بفضل الترميز المشترك، لكنها بطيئة جدًا للبحث في مجموعة مستندات كاملة، وأن الاسترجاع ذي المرحلتين يجمع بين النهجين: مرحلة أولى سريعة لتحقيق استدعاء مرتفع، تليها مرحلة ثانية دقيقة لتحقيق دقة مرتفعة. تسترجع المرحلة الأولى عددًا من المرشحين أكبر بكثير من العدد المطلوب لتجنب فقدان المستندات ذات الصلة. في الخطوة التالية، سنطبّق إعادة الترتيب باستخدام cross-encoder مع Cohere وBGE.
الأسئلة الشائعة
هل درس «لماذا ينجح الاسترجاع على مرحلتين» مجاني؟
نعم — نص درس «لماذا ينجح الاسترجاع على مرحلتين» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Engineering Academy، انتقل إلى CoddyKit PRO. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.
ماذا ستتعلم في «لماذا ينجح الاسترجاع على مرحلتين»؟
افهموا المفاضلة بين الاستدعاء والدقة في الاسترجاع أحادي المرحلة، وكيف يحقق مسترجع سريع وخشن يتبعه معيد ترتيب بطيء لكنه دقيق أفضل ما في النهجين. تتمرن على AI Engineering Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ AI Engineering Academy؟
لا تُشترط خبرة سابقة. AI Engineering Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.
كم من الوقت يستغرق درس «لماذا ينجح الاسترجاع على مرحلتين»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس AI Engineering Academy هذا؟
نعم. كل درس في AI Engineering Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- لماذا ينجح الاسترجاع على مرحلتين
- إعادة الترتيب باستخدام Cross-Encoder مع Cohere وBGE
- الضغط السياقي وتصفية الصلة
- قياس أثر إعادة الترتيب