الوحدة 7 — الهلوسة: الأسباب والحلول
في اختبار على مساعد الدعم لدينا، طرح مُستخدِم سؤالًا: «ما رقم اتّفاقية GDPR الخاصّة بحقّ الوصول؟» أجاب النموذج بثقة: «المادّة 15 من اللائحة 2016/679، الفقرة 2، الصادرة في 4 مايو 2016». المادّة صحيحة، والفقرة صحيحة، لكنّ التاريخ خاطئ: صدرت في 27 أبريل، لا 4 مايو. اختراع دقيق ومحكم — هذه هي الهلوسة، وهي أخطر عيوب النماذج في الإنتاج. تُدَقّق بالمظهر، تُخفي كذبها بدقّة سطحيّة.
لماذا يخترع النموذج
النموذج اللغوي لا يفرّق داخليًا بين «أعلم» و«أخمّن». هدفه الوحيد في التوليد هو إنتاج تسلسل مرشَّح احتماليًا. فحين لا يعرف الجواب الصحيح، لا يُنتج «لا أعلم»، بل يُنتج رمزًا قريبًا نصّيًا من تدريبات ه. أرقام مواد قانونية، وأسماء علماء، وتواريخ نشر أوراق — كلّها تسلسلات معتادة، لكلّ منها احتمالية عالية للظهور بجانب سؤال يشبهها.
أسباب متعدّدة لتضخيم الظاهرة. أوّلها ضغط المكافأة: نموذج مُوَاءَم يُكافأ على الردود المفيدة، ورفض السؤال يُخفّض هذه المكافأة، فيميل إلى المخاطرة بالجواب. ثانيها ندرة المعلومة في التدريب: كلّما كانت الحقيقة نادرةً في المجموعة، ازدادت احتمالية أن يخلق النموذج بديلًا مقنعًا. ثالثها العودة النَصِّيّة: النموذج يستكمل جملة سليمة نحويًا، لكن سلامة النحو لا تعني سلامة الوقائع.
الحلّ الحقيقي: الترسيخ
الحلّ الأمثل ليس تعليم النموذج قواعد أخلاقيّة، بل فصل المعرفة عن التوليد. نُدخل في المُطالبة مقاطع نصّية من مصدر موثوق، ونُطالِبُ النموذج بالإجابة من هذه المقاطع فقط.
def repondre_avec_ancrage(question, contextes, tokenizer, modele):
"""يُجيب من الوثائق المُقدَّمة أو يعترف بعدم المعرفة."""
prompt = f"""أجب من الوثائق التالية حصريًّا. إذا لم تكن الإجابة موجودة في الوثائق، فأجب "لا توجد إجابة في المصادر المتاحة".
الوثائق:
{chr(10).join(f'[{i}] {c}' for i, c in enumerate(contextes, 1))}
السؤال: {question}
الإجابة (اقتبس رقم الوثيقة المستعملة):"""
messages = [{"role": "user", "content": prompt}]
entrees = tokenizer.apply_chat_template(messages, return_tensors="pt", add_generation_prompt=True)
sortie = modele.generate(entrees.to(modele.device), max_new_tokens=200, do_sample=False)
return tokenizer.decode(sortie[0, entrees.shape[1]:], skip_special_tokens=True)
هذا يعمل جيّدًا، لكن ينبغي أن ندرك: جودة الترسيخ تعتمد على جودة الاسترجاع. لو استرجعنا مقاطع غير ذات صلة، سيسمح النموذج لنفسه بالاختراع بذريعة «لم أجد في الوثائق». المسار الكامل — استرجاع، ثم توليد، ثم قياس — سنبنيه في دورة RAG.
الاقتباس الإلزامي
خطوة إضافيّة قويّة: مطالبة النموذج بأن يقتبس المصدر لكلّ ادّعاء واقعي. في المثال السابق، بدل «الإجابة هي X»، نطالبه بـ «الإجابة هي X [الوثيقة رقم 3، السطر 12]». يعمل هذا لسببين: أوّلًا، النموذج المُدرَّب على الاقتباس يميل إلى الالتصاق بالنصّ الأصلي؛ وثانيًا، الاقتباسات قابلة للتحقّق آليًّا في الإنتاج.
def verifier_citations(reponse, contextes):
"""يمرّ على كلّ اقتباس [N] ويؤكد وجود الادّعاء في الوثيقة المذكورة."""
import re
citations = re.findall(r'\[(\d+)\]', reponse)
for num in citations:
idx = int(num) - 1
if idx < 0 or idx >= len(contextes):
return False, f"اقتباس خارج المجال: {num}"
return True, "اقتباسات صحيحة"
في الإنتاج، ردٌّ باقتباسات غير موجودة يُحذَف تلقائيًا ويُطلَب من النموذج إعادة التوليد أو الامتناع.
الامتناع بوصفه مهارة
النموذج الجيّد ليس نموذجًا يجيب دائمًا؛ هو نموذج يعرف متى لا يجيب. ندرّبه على ذلك بأمثلة صريحة في مرحلة الضبط الدقيق:
- «كم عدد الفواتير المُصدَرة لهذا العميل في 2024؟» → «هذا يستوجب الوصول إلى قاعدة المعاملات، لا أستطيع الردّ بدونها.»
- «ما رقم هاتف مسؤولي الشكاوى؟» → «لا يوجد في مصادري رقم مباشر، لكن يمكنك الوصول إليه عبر النظام الداخلي.»
الامتناع يجب أن يكون معلَّلًا لا فارغًا. «لا أستطيع» بلا سبب يُشعر المستخدم بأنّ النموذج مُقفَل، ويدفعه لمحاولة التحايل. «لا أستطيع لأنّي أفتقر إلى الوصول إلى قاعدة X» يُحوّل السلوك من رفض إلى شفافيّة.
التحقّق بنموذج ثانٍ
طريقة أخرى، مكلفة لكن قويّة: بعد التوليد، يُعرَض الردّ على نموذج ثانٍ (يمكن أن يكون نفس النموذج بمُطالبة مختلفة) يعمل مُقيِّمًا. يُطلب من المقيِّم استخراج الادّعاءات الواقعية والحُكم عليها.
def verifier_reponse(question, reponse, contextes, tokenizer, modele):
prompt = f"""أنت مُدقِّق. استخرج الادّعاءات الوقائعيّة من الردّ التالي، وقيّم كلّ ادّعاء استنادًا إلى الوثائق.
السؤال: {question}
الردّ: {reponse}
الوثائق:
{chr(10).join(f'[{i}] {c}' for i, c in enumerate(contextes, 1))}
لكلّ ادّعاء، أجب بواحدة من: "مؤكَّد"، "غير قابل للتحقّق"، "مُخالف للوثائق"."""
messages = [{"role": "user", "content": prompt}]
entrees = tokenizer.apply_chat_template(messages, return_tensors="pt", add_generation_prompt=True)
sortie = modele.generate(entrees.to(modele.device), max_new_tokens=300, do_sample=False)
return tokenizer.decode(sortie[0, entrees.shape[1]:], skip_special_tokens=True)
كلفة إضافيّة بمرور استدلال واحد. لأنظمة حسّاسة (طبّ، قانون، مال)، هذه الكلفة مقبولة. لمساعد دعم عام، تُطبَّق فقط على أنواع الطلبات ذات المخاطر (تفسير سياسة، أرقام محدّدة، إجراءات قانونيّة).
ما لا يعمل
عدّة حلول تبدو جذّابة لكنّها فاشلة أو ضعيفة.
«تذكيرات لا تخترع» في المُطالبة: «لا تخترع، أجب فقط بما تعلم». أثرها هامشي جدًّا. النموذج لا يعلم أنّه يخترع، فتحذيره غير فعّال. البحث التجريبي يُظهر تحسّنًا لا يتجاوز بضع نقاط مئوية على قواعد الهلوسة.
رفع درجة الحرارة إلى الصفر: يخفّض التنوّع، لا يخفّض الهلوسة. النموذج يخترع بحتميّة بدل أن يخترع بعشوائيّة. بعض الأدبيات تربط بين الحرارة العالية والهلوسة، وهذا خطأ: الراب ط بين احتماليّة الرمز والصحّة الوقائعيّة ضعيف جدًّا.
«ثقة» النموذج (probability of the answer): يبدو أنّ رمزًا ذا احتماليّة عالية «أكثر ثقة». لكنّ النموذج يستطيع الاختراع بثقة كاملة إذا كان اختراعه شائعًا في التدريب. الاحتماليّة الذاتيّة ليست مقياس صحّة موثوقًا.
الهلوسة التي تُنكشف بسرعة («ولد آينشتاين عام 1985») ليست الخطر. الخطر هلوسات دقيقة: أرقام مواد قانونية قريبة من الصحيحة، وتواريخ منزاحة بيوم، وأسماء علماء موجودين لكن في تخصّصات أخرى. هذه الهلوسات تعبر مرشّحات المدقّق البشري السريع، وقد تنتهي في وثائق قانونيّة أو طبيّة رسميّة. لا تراهن على «الحسّ السليم للمُستخدم»؛ رَسِّخ، اقتبس، وامتنع.
في الخلاصة
- الهلوسة عيبٌ بنيويّ: ا لنموذج ينتج تسلسلًا محتملًا نصّيًا، لا يفرّق بين المعرفة والتخمين.
- الحلّ الأقوى: الترسيخ في مصادر موثوقة عبر RAG، مع اقتباس يُتحقَّق منه آليًا.
- الامتناع المُعلَّل يجب تعليمه بأمثلة صريحة؛ رفضٌ بلا سبب يُحفّز التحايل.
- التحذيرات في المُطالبة، وخفض الحرارة، والاعتماد على «ثقة النموذج» — كلّها حلول ضعيفة أو خادعة.
الوحدة التالية: بعد أن ضمنّا حسن السلوك، كيف نُخدّم النموذج بكلفة يتحمّلها المشروع — التكميم إلى 4 أو 8 بت، وvLLM، وllama.cpp.