بيانات الضبط
درس 2 من 6
2بيانات الضبط
الصورة الكبيرةجودة النموذج المضبوط لا تتجاوز جودة بياناته. ألف مثال نظيف ومتّسق ومتنوّع تتفوّق على عشرين ألفًا فيها تكرار وتسميات متناقضة وأخطاء. وأغلب وقت أي مشروع ضبط ناجح يذهب هنا، في بناء البيانات وتنظيفها وتقسيمها، لا في التدريب.
ماذا تشاهد في الرسم: مئة مثال من أربعة قوالب. في التقسيم العشوائي كان لكل مثال اختبار توأم في التدريب، فقاس الحفظ. حين يذهب القالب الرابع كله للاختبار، يقيس الرقم هل يعمم النموذج على صياغة لم يرها.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
النموذج يتعلّم من الأمثلة كما يتعلّم الطفل من القدوة. ألف مثال صحيح ومرتب تعلّمه أفضل من عشرين ألفًا فيها أخطاء وتناقض. وتخفي بعض الأمثلة جانبًا لتختبره عليها لاحقًا، لا ليتدرّب عليها.
أمثلة بصيغة محادثة messages أو prompt/completion في ملف JSONL، مع إزالة التكرار والتسريب بين التقسيمات، وتوازن بين الحالات. الجودة والتنوع أهم من العدد، وتحتفظ بمجموعة اختبار ثابتة لم يرها التدريب أبدًا.
قبل أن تبدأ
⚖️قبل أن تضبط
قرّرت أن الضبط هو الدرجة الصحيحة لمشكلتك، ولا يحلّها قالب أو استرجاع.
✂️تقسيم البيانات
تعرف التدريب والتحقّق والاختبار، ولماذا يُفصل الاختبار قبل أي تجربة.
الفكرة الجوهرية
النموذج المضبوط لا يكون أفضل من بياناته. في هذا الدرس تكتب البيانات بالشكل الصحيح، ثم تنظّفها، ثم تقسّمها بلا تسرّب، ثم تجمّد ملف الاختبار.
1الشكل: JSONL
كل سطر في ملف JSONL مثال كامل. فيه الأدوار system وuser وassistant بنفس القالب الذي سيعمل به النموذج في الإنتاج.
انظر إلى الرسم: سطر واحد يتفكّك إلى أدواره، ومعها حقل source يقول من أين جاء المثال. النموذج يتعلّم أن يكتب ما في assistant.
2النظافة قبل الكمية
ألف مثال نظيف ومتّسق يتفوّق على عشرين ألفًا فيها تكرار وتسميات متناقضة. ابدأ بخمسمئة إلى ألف مثال نظيف، وقِس، ثم أضف حيث يفشل النموذج.
قبل أي تدريب، يمرّ كل سطر بفحوص آلية: الأدوار، والإجابات الفارغة، والتسميات من قائمة ثابتة، والتكرار بعد توحيد النص.
import json
import re
lines = [
'{"messages": [{"role": "user", "content": "صنّف: التوصيل تأخر أسبوعا"}, {"role": "assistant", "content": "شكوى"}]}',
'{"messages": [{"role": "user", "content": "صنّف: شكرا على سرعة الرد"}, {"role": "assistant", "content": "شكر"}]}',
'{"messages": [{"role": "user", "content": "صنّف: التوصيل تأخّر أسبوعاً"}, {"role": "assistant", "content": "شكوى"}]}',
'{"messages": [{"role": "user", "content": "صنّف: متى تفتحون؟"}, {"role": "assistant", "content": ""}]}',
'{"messages": [{"role": "user", "content": "صنّف: السعر مرتفع جدا"}]}',
'{"messages": [{"role": "user", "content": "صنّف: أين فاتورتي"}, {"role": "assistant", "content": "استفسار"}]}',
]
LABELS = {"شكوى", "شكر", "استفسار"}
def key(text):
text = re.sub(r"[ً-ْـ]", "", text)
return re.sub(r"\s+", " ", text).strip()
clean, seen, problems = [], set(), []
for n, line in enumerate(lines, 1):
msgs = json.loads(line)["messages"]
roles = [m["role"] for m in msgs]
if roles != ["user", "assistant"]:
problems.append((n, f"roles are {roles}"))
continue
answer = msgs[1]["content"]
if answer not in LABELS:
problems.append((n, f"label {answer!r} is not one of {sorted(LABELS)}"))
continue
k = key(msgs[0]["content"])
if k in seen:
problems.append((n, "duplicate of an earlier example"))
continue
seen.add(k)
clean.append(msgs)
print("kept", len(clean), "of", len(lines))
for n, why in problems:
print(f"line {n}: dropped, {why}")
ثلاثة أسطر من ستة سقطت، وكل منها كان سيؤذي النموذج: السطر الثالث نفس الأول بتشكيل مختلف، فيتعلّمه النموذج مرتين ويبدو التقييم أفضل مما هو. والرابع إجابته فارغة، فيتعلّم أن الصمت إجابة مقبولة. والخامس بلا إجابة أصلًا. في ملف حقيقي من آلاف الأسطر، هذه الفحوص تجد عشرات الحالات دائمًا.
3التقسيم والتسرّب
التسرّب أمثلة متطابقة أو شبه متطابقة في التدريب والاختبار. النموذج يحفظها، فيبدو ممتازًا ثم يفشل مع المدخلات الجديدة.
انظر إلى الرسم: في التقسيم العشوائي كان لكل مثال اختبار توأم في التدريب. وفي التقسيم المجمّع بحسب القالب لم يكن لأي منها توأم.
مئة مثال مولّدة من أربعة قوالب جمل بأرقام طلبات مختلفة. هذا شائع جدًّا في البيانات المولّدة أو المستخرجة من نظام تذاكر.
import random
random.seed(0)
templates = ["الطلب رقم {} لم يصل", "أريد إلغاء الطلب {}", "الطلب {} وصل تالفا", "كم سعر شحن الطلب {}"]
examples = [{"text": t.format(random.randint(1000, 9999)), "template": i}
for i, t in enumerate(templates) for _ in range(25)]
def shape(text):
return "".join("#" if c.isdigit() else c for c in text)
def leaked(train, test):
seen = {shape(e["text"]) for e in train}
return sum(shape(e["text"]) in seen for e in test)
random.shuffle(examples)
train, test = examples[:80], examples[80:]
print("random split: test examples whose exact shape is in train:", leaked(train, test), "of", len(test))
test_templates = {3}
train = [e for e in examples if e["template"] not in test_templates]
test = [e for e in examples if e["template"] in test_templates]
print("grouped split: test examples whose exact shape is in train:", leaked(train, test), "of", len(test))
بالتقسيم العشوائي، كل مثال اختبار له «توأم» في التدريب يختلف في الرقم فقط، فالنموذج يحفظ القالب ويحصل على درجة كاملة دون أن يتعلّم شيئًا ينفع مع صياغة جديدة. بالتقسيم المجمّع، القالب الرابع كله في الاختبار، فالدرجة تقيس هل يعمم النموذج على صياغة لم يرها. هذا الرقم أقل، وهو الصحيح.
4تجميد ملف الاختبار
التقسيم ثلاثة: تدريب وتحقّق واختبار. والاختبار يُكتب مرة قبل أي تدريب، ويُحفظ في Git، ولا يُعاد توليده أبدًا.
from datasets import load_dataset
ds = load_dataset("json", data_files="clean.jsonl", split="train")
# group by the source each example came from, so near-twins stay on one side
sources = sorted(set(ds["source"]))
test_sources = set(sources[::5]) # every fifth source goes to test
test = ds.filter(lambda e: e["source"] in test_sources)
rest = ds.filter(lambda e: e["source"] not in test_sources).train_test_split(test_size=0.1, seed=0)
train, val = rest["train"], rest["test"]
test.to_json("test_frozen.jsonl") # written once, never regenerated
print(len(train), len(val), len(test))
حقل source (المحادثة الأصلية، أو القالب، أو العميل) هو ما يجعل التقسيم المجمّع ممكنًا، فأضفه لكل مثال من البداية. وملف الاختبار يُكتب مرة ويُحفظ في Git، ولا يُعاد توليده أبدًا حتى لا يتغيّر خلسة بين التجارب.
كيف يعمل، خطوة بخطوة: كيف تجهّز ملف الضبط
تكتب دليل تسمية من صفحة واحدة، وتقيس اتفاق شخصين على نفس مئة مثال.
تكتب الأمثلة JSONL بنفس الأدوار والقالب الذي سيعمل به النموذج، مع حقل المصدر لكل مثال.
تمرّر كل سطر على فحوص الأدوار والإجابة الفارغة والتسمية، وتحذف المكرّر بعد توحيد النص.
تقسّم بحسب المصدر، وتكتب ملف الاختبار مرة واحدة وتحفظه في Git ولا تعيد توليده.
أعمق قليلًا
والتنوّع يحمي من التخصّص الزائد. إن كانت كل أمثلتك قصيرة ورسمية، فسيفشل النموذج مع رسالة طويلة بلهجة عامية. اجمع أمثلة من مصادر مختلفة وأطوال مختلفة ولهجات مختلفة إن كان مستخدموك يكتبون بها، وتأكّد أن كل فئة ممثّلة بما يكفي.
مثال محلول
البيانات للضبط على المحادثات تُكتب غالبًا كملف JSONL: كل سطر مثال كامل فيه رسائل المستخدم والمساعد. قبل أي تدريب، يمرّ كل سطر بفحوص آلية.
جرّب بلا كود
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| أمثلة أكثر تعني دائمًا نموذجًا أفضل. | ألف مثال نظيف ومتّسق يتفوّق على عشرين ألفًا فيها تكرار وتسميات متناقضة. |
| البيانات المولّدة بنموذج لغوي جاهزة للتدريب. | تحمل أنماط المولّد وأخطاءه وتكراره. يقرأ إنسان عيّنة لا تقل عن مئة مثال ويصحّحها. |
| التقسيم العشوائي عادل دائمًا. | مع أمثلة متشابهة من مصدر واحد يضع توائم في الجهتين، فيبدو النموذج ممتازًا ويفشل مع المدخلات الجديدة. |
جرّب بنفسك
عندك 20 ألف مثال مولّد آليًّا بلا مراجعة، أو 1000 مثال راجعها إنسان. بأيّهما تبدأ؟
البيانات النظيفة تعطي نقطة بداية موثوقة تقيس عليها. الإضافة الموجّهة بالأخطاء أكفأ من إغراق النموذج بأمثلة لا تعرف جودتها.
لماذا تقسّم البيانات بحسب المصدر لا عشوائيًّا؟
أمثلة المصدر الواحد متشابهة. إن انقسمت بين الجهتين، يقيس الاختبار الحفظ، كما رأيت: 20 من 20 «توأمًا».
بطاقات مراجعة سريعة
system وuser وassistant كما سيستخدمها النموذج فعلًا في الإنتاج.تحقق من فهمك
النموذج المضبوط حصل على 97% في الاختبار، ثم 70% مع رسائل المستخدمين الحقيقية. ما السبب المرجّح؟
تسرّب أو عدم تمثيل. إمّا أن الاختبار فيه توائم لأمثلة التدريب فقاس الحفظ لا التعميم، وإمّا أن البيانات كلها لا تشبه ما يكتبه المستخدمون فعلًا (أقصر، أرسمى، بلا أخطاء إملائية). الحل: تقسيم مجمّع، وإضافة عيّنة من الرسائل الحقيقية إلى الاختبار.
في فحص الملف، لماذا حُذفت التشكيلات قبل مقارنة النصوص؟
لأن نفس الجملة بتشكيل مختلف هي نفس المثال. بدون التوحيد يمرّ السطر الثالث كمثال جديد، فيتعلّمه النموذج مرتين ويبدو التقييم أفضل مما هو.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.