التقطيع
درس 5 من 10
5التقطيع
الصورة الكبيرةلا تُضمِّن مستندًا من خمسين صفحة كمتّجه واحد، ولا كل جملة وحدها. التقطيع (Chunking) يقرّر أين تُقسَم المستندات قبل الفهرسة، وهو من أكثر القرارات تأثيرًا في جودة الاسترجاع وأقلّها انتباهًا. حجم القطعة، والتداخل، واحترام العناوين، وكلها تُقاس لا تُخمَّن.
ماذا تشاهد في الرسم: كل قطعة هنا موضوع كامل مع عنوانه، فلا تُقطع «قبل أسبوعين» في المنتصف كما حدث في التقطيع الثابت كل 80 حرفًا. بثلاث قطع فقط أصاب التقطيع بالعناوين 5 أسئلة من 6، مقابل 3 للقطع الصغيرة بلا تداخل.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
لا تعطي الباحث كتابًا كاملًا ليجد فيه جملة، بل تقطّعه إلى بطاقات صغيرة يسهل البحث فيها. لكن لو قطعت الجملة من منتصفها ضاع معناها، ولو جعلت البطاقة كبيرة جدًا اختلط فيها موضوعان. التقطيع فن اختيار حجم البطاقة ومكان القص.
التقطيع بطول ثابت مع تداخل overlap، أو حسب البنية بالعناوين والفقرات، أو دلاليًا بتغير الموضوع. المقطع الصغير دقيق لكنه يفقد السياق، والكبير يخفف الدقة ويستهلك النافذة. تحفظ مع كل مقطع عنوانه ومصدره، وتقيس الاختيار بمقاييس الاسترجاع.
قبل أن تبدأ
🧲البحث المتجهي
تعرف أن كل قطعة تصير متّجهًا واحدًا، فما يدخل القطعة يحدّد ما يمكن أن يجده البحث.
📐التقييم أولًا
عندك أسئلة ذهبية بإجاباتها، لأن حجم القطعة يُختار بها لا بالتخمين.
الفكرة الجوهرية
التقطيع يحدّد أين تنقسم المستندات قبل الفهرسة. تبدأ في هذا الدرس بحجم القطعة وما يقطعه، ثم التداخل، ثم التقطيع بالعناوين، ثم تقيس الطرق كلها على نفس الأسئلة.
1حجم القطعة
القطعة الصغيرة دقيقة لكنها قد تقطع المعلومة. والكبيرة تحفظ السياق لكنها تميّع التضمين وتكلّف أكثر في نافذة السياق.
والتقطيع الثابت كل N حرفًا سريع وبسيط، لكنه لا يعرف أين تنتهي الكلمة. انظر إلى الرسم: القطع كل 80 حرفًا شطر «أسبوعين» بين قطعتين.
2التداخل
التداخل يجعل كل قطعة تبدأ قبل أن تنتهي سابقتها، فلا تضيع معلومة على الحدّ. تداخل 10% إلى 20% من حجم القطعة بداية معقولة، والثمن قطع أكثر.
doc = """# الإجازة السنوية
يحق للموظف واحد وعشرون يوم عمل في السنة. تقدم الطلبات قبل أسبوعين. يمكن ترحيل عشرة أيام إلى السنة التالية.
# الإجازة المرضية
تحتاج تقريرا طبيا إذا زادت عن يومين. يوافق عليها المدير المباشر. لا تخصم من رصيد الإجازة السنوية.
# السفر
تحجز التذاكر عبر البوابة الداخلية. تصرف البدلات خلال عشرة أيام من تقديم الفواتير."""
def fixed(text, size, overlap=0):
text = text.replace("\n", " ")
step = size - overlap
return [text[i:i + size] for i in range(0, len(text) - overlap, step)]
def by_heading(text):
parts = []
for block in text.split("# ")[1:]:
title, body = block.split("\n", 1)
parts.append(f"{title}: {body.strip()}")
return parts
for name, chunks in [("fixed 80", fixed(doc, 80)),
("fixed 80, overlap 30", fixed(doc, 80, 30)),
("by heading", by_heading(doc))]:
print(f"{name}: {len(chunks)} chunks")
print(" first:", chunks[0])
print(" second:", chunks[1])
التقطيع الثابت كل 80 حرفًا قطع كلمة «أسبوعين» إلى نصفين، فلا القطعة الأولى ولا الثانية تحتوي «قبل أسبوعين» كاملة. التداخل بـ30 حرفًا أصلح هذا: القطعة الثانية تبدأ قبل نهاية الأولى. والتقطيع بالعناوين أعطى ثلاث قطع، كل منها موضوع كامل ومعه عنوانه.
3التقطيع بالعناوين
المستندات المنظّمة لها حدود طبيعية: العناوين والفقرات. اقطع عليها، وألحق العنوان بكل قطعة حتى تعرف موضوعها قبل التضمين.
import re
def chunk_markdown(text, max_chars=1500, overlap=200):
chunks, path = [], []
for block in re.split(r"(?m)^(?=#{1,3} )", text):
if not block.strip():
continue
heading = re.match(r"(#{1,3}) (.+)", block)
if heading:
level = len(heading.group(1))
path = path[:level - 1] + [heading.group(2).strip()]
title = " > ".join(path)
body = block.split("\n", 1)[1] if heading else block
for i in range(0, max(len(body), 1), max_chars - overlap):
chunks.append({"title": title, "text": f"{title}\n{body[i:i + max_chars]}"})
return chunks
كل قطعة تحمل مسار عناوينها كاملًا («السياسات > الإجازات > المرضية»)، والأقسام الطويلة تُقسم مع تداخل داخل القسم فقط، فلا تختلط أقسام مختلفة في قطعة واحدة.
4قِس ولا تخمّن
لكل طريقة سؤال واحد: هل القطعة الأولى المسترجعة تحتوي الإجابة؟ انظر إلى الرسم: ستة أسئلة ذهبية وأربع طرق على نفس المستند.
ستة أسئلة ذهبية لنفس المستند. لكل طريقة تقطيع، نسترجع أقرب قطعة ونسأل: هل تحتوي الإجابة؟
from sklearn.feature_extraction.text import TfidfVectorizer
doc = """# الإجازة السنوية
يحق للموظف واحد وعشرون يوم عمل في السنة. تقدم الطلبات قبل أسبوعين. يمكن ترحيل عشرة أيام إلى السنة التالية.
# الإجازة المرضية
تحتاج تقريرا طبيا إذا زادت عن يومين. يوافق عليها المدير المباشر. لا تخصم من رصيد الإجازة السنوية.
# السفر
تحجز التذاكر عبر البوابة الداخلية. تصرف البدلات خلال عشرة أيام من تقديم الفواتير."""
golden = [("كم يوم إجازة سنوية؟", "واحد وعشرون يوم"),
("متى أقدم طلب الإجازة السنوية؟", "قبل أسبوعين"),
("هل أرحل أيام الإجازة؟", "ترحيل عشرة أيام"),
("متى أحتاج تقريرا طبيا للإجازة المرضية؟", "زادت عن يومين"),
("من يوافق على الإجازة المرضية؟", "المدير المباشر"),
("متى تصرف بدلات السفر؟", "خلال عشرة أيام من تقديم")]
def fixed(text, size, overlap=0):
text = text.replace("\n", " ")
return [text[i:i + size] for i in range(0, len(text) - overlap, size - overlap)]
def by_heading(text):
return [b.replace("\n", ": ", 1).strip() for b in text.split("# ")[1:]]
def hit_rate(chunks):
vec = TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 4)).fit(chunks + [q for q, _ in golden])
m = vec.transform(chunks)
hits = 0
for q, answer in golden:
best = (m @ vec.transform([q]).T).toarray().ravel().argmax()
hits += answer in chunks[best]
return hits
for name, chunks in [("fixed 60", fixed(doc, 60)),
("fixed 60, overlap 25", fixed(doc, 60, 25)),
("fixed 200", fixed(doc, 200)),
("by heading", by_heading(doc))]:
print(f"{name:22} chunks={len(chunks):2} top-1 contains the answer: {hit_rate(chunks)}/{len(golden)}")
التقطيع الصغير بلا تداخل أصاب في 3 من 6، والتداخل رفعه إلى 4. التقطيع بالعناوين أصاب في 5 بثلاث قطع فقط. والقطع الكبيرة (200 حرف) أصابت في 5 أيضًا، لكن انتبه: في مستند حقيقي من خمسين صفحة، قطعة بهذا الحجم النسبي تعني صفحات كاملة في نافذة السياق لكل سؤال. لا ترسل للنموذج أكثر مما يلزم لمجرّد أن «تحتوي» الإجابة.
كيف يعمل، خطوة بخطوة: كيف يقطّع chunk_markdown مستندًا
يقسم النص عند كل عنوان من المستوى الأول إلى الثالث.
يحدّث مسار العناوين لكل قسم، مثل «السياسات > الإجازات > المرضية».
إن طال القسم عن 1,500 حرف يقطعه داخله فقط بتداخل 200 حرف، فلا يختلط قسمان في قطعة.
يلصق مسار العناوين في أول كل قطعة، فتعرف كل قطعة موضوعها قبل التضمين.
مثال محلول
مستند قصير بثلاثة عناوين. نقطّعه بثلاث طرق ونرى أول قطعتين من كل طريقة.
جرّب بلا كود
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| هناك حجم قطعة مثالي للجميع، مثل 512 رمزًا. | الحجم الأفضل يعتمد على مستنداتك وأسئلتك ونموذج التضمين. جرّب ثلاثة أحجام على مجموعتك الذهبية واختر بالرقم. |
| القطع الكبيرة أفضل لأنها تحتوي الإجابة غالبًا. | تحتويها، لكنها تميّع التضمين وتملأ نافذة السياق بنص لا يحتاجه السؤال، وفي مستند طويل تصير صفحات كاملة لكل سؤال. |
| يكفي استخراج النص من ملف PDF كما هو. | الأعمدة والجداول والترويسات تخرج مختلطة، فتصير القطعة نصف جملة من عمود ونصفًا من آخر. اقرأ عيّنة بعينك قبل الفهرسة. |
جرّب بنفسك
ما فائدة إلحاق العنوان بكل قطعة؟
القطعة بلا عنوان قد تكون غامضة الموضوع. العنوان كلمات قليلة تغيّر التضمين إلى الموضوع الصحيح.
التداخل رفع الإصابة من 3 إلى 4. ما ثمنه؟
كل تداخل يكرّر نصًّا. لهذا تحذف المكرّر عند بناء السياق، كما في درس هندسة السياق.
بطاقات مراجعة سريعة
تحقق من فهمك
لماذا لا نجعل كل جملة قطعة مستقلة؟
لأن الجملة وحدها تفقد سياقها (الضمائر والموضوع)، وتضمينها أضعف لقصرها، ويصير عدد القطع كبيرًا فتحتاج استرجاع عشرات منها لتغطية إجابة واحدة. الجمل المستقلة تصلح لإعادة الترتيب أو للاستشهاد الدقيق، لا كوحدة الاسترجاع الأساسية.
لماذا تُكرّر خطوة إزالة المكرّر في بناء السياق إذا استخدمت التداخل؟
لأن التداخل ينسخ نصًّا بين قطعتين متجاورتين، فقد تُسترجعان معًا ويدخل النص نفسه السياق مرتين، فتدفع ثمنه مرتين ويشغل مساحة قطعة أخرى.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي1
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.