آماده‌سازی دیتاست — مهم‌ترین بخش Fine-tuning

قسمت ۵ ۲۰ دقیقه

چرا داده مهم‌ترین بخشه؟

یه ضرب‌المثل توی دنیای یادگیری ماشین هست: “Garbage in, garbage out.” اگه داده‌هات بد باشن، مدلت هم بد می‌شه — مهم نیست چقدر GPU قوی داری یا از چه تکنیکی استفاده می‌کنی.

Fine-tuning مثل آموزش یه شاگرد باهوشه. اگه بهش کتاب‌های بد بدی، بد یاد می‌گیره. اگه کتاب‌های خوب ولی کم بدی، بهتره از اینکه کلی کتاب بد بدی.

قانون طلایی دیتاست: ۱۰۰۰ نمونه عالی بهتر از ۱۰۰,۰۰۰ نمونه متوسطه.

فرمت‌های رایج دیتاست

۱. فرمت Instruction

ساده‌ترین فرمت. یه دستورالعمل (instruction) و یه خروجی (output):

# فرمت Instruction — هر خط یه JSON
{"instruction": "فرق بین list و tuple رو توضیح بده.", 
 "output": "list تغییرپذیره (mutable) یعنی بعد از ساختنش می‌تونی عناصرش رو تغییر بدی. tuple تغییرناپذیره (immutable) و بعد از ساختن قابل تغییر نیست."}

{"instruction": "یه تابع بنویس که فاکتوریل محاسبه کنه.", 
 "output": "def factorial(n):\n    if n <= 1:\n        return 1\n    return n * factorial(n - 1)"}

# با ورودی اختیاری
{"instruction": "متن زیر رو خلاصه کن.", 
 "input": "هوش مصنوعی شاخه‌ای از علوم کامپیوتر است که...", 
 "output": "هوش مصنوعی حوزه‌ای در علوم کامپیوتر برای..."}

۲. فرمت Conversation (مکالمه‌ای)

برای مدل‌های چت. هر نمونه یه مکالمه کامله:

# فرمت Chat/Conversation
{
    "messages": [
        {"role": "system", "content": "تو یه دستیار برنامه‌نویسی پایتون هستی."},
        {"role": "user", "content": "چطور فایل CSV بخونم؟"},
        {"role": "assistant", "content": "برای خوندن فایل CSV در پایتون از کتابخانه pandas استفاده کن:\n\nimport pandas as pd\ndf = pd.read_csv('data.csv')\nprint(df.head())"},
        {"role": "user", "content": "اگه فایل با جداکننده tab باشه چی؟"},
        {"role": "assistant", "content": "فقط پارامتر sep رو تغییر بده:\n\ndf = pd.read_csv('data.tsv', sep='\\t')"}
    ]
}

۳. فرمت Completion

ساده‌ترین فرمت — فقط متن خام:

# فرمت Completion — برای ادامه متن
{"text": "### سوال: فرق HTTP و HTTPS چیه?\n\n### جواب: HTTPS نسخه امن HTTP هست..."}

# یا بدون ساختار خاص
{"text": "در برنامه‌نویسی پایتون، decorator یه تابعه که تابع دیگه‌ای رو..."}

چقدر داده لازمه؟

جواب کوتاه: بستگی داره. ولی یه راهنمای کلی:

  • تغییر سبک/لحن: ۵۰۰-۱۰۰۰ نمونه
  • Task جدید ساده: ۱۰۰۰-۳۰۰۰ نمونه
  • Task پیچیده: ۳۰۰۰-۱۰,۰۰۰ نمونه
  • دانش جدید تخصصی: ۵۰۰۰-۲۰,۰۰۰ نمونه
  • زبان جدید: ۱۰,۰۰۰+ نمونه
# اسکریپت بررسی کیفیت دیتاست
import json
from collections import Counter

def analyze_dataset(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        data = [json.loads(line) for line in f]
    
    print(f"تعداد نمونه‌ها: {len(data)}")
    
    # بررسی طول‌ها
    lengths = []
    for item in data:
        if "messages" in item:
            total_len = sum(len(m["content"]) for m in item["messages"])
        elif "instruction" in item:
            total_len = len(item["instruction"]) + len(item.get("output", ""))
        else:
            total_len = len(item.get("text", ""))
        lengths.append(total_len)
    
    print(f"میانگین طول: {sum(lengths)/len(lengths):.0f} کاراکتر")
    print(f"کوتاه‌ترین: {min(lengths)} کاراکتر")
    print(f"بلندترین: {max(lengths)} کاراکتر")
    
    # بررسی تکراری‌ها
    if "instruction" in data[0]:
        instructions = [d["instruction"] for d in data]
        duplicates = len(instructions) - len(set(instructions))
        print(f"تکراری‌ها: {duplicates}")
    
    return data

data = analyze_dataset("my_dataset.jsonl")

تمیزکاری داده

۱. حذف نمونه‌های بد

def clean_dataset(data):
    cleaned = []
    removed = {"empty": 0, "too_short": 0, "too_long": 0, "duplicate": 0}
    seen = set()
    
    for item in data:
        instruction = item.get("instruction", "")
        output = item.get("output", "")
        
        # حذف خالی‌ها
        if not instruction.strip() or not output.strip():
            removed["empty"] += 1
            continue
        
        # حذف خیلی کوتاه‌ها
        if len(output) < 20:
            removed["too_short"] += 1
            continue
        
        # حذف خیلی بلندها
        if len(instruction) + len(output) > 8000:
            removed["too_long"] += 1
            continue
        
        # حذف تکراری‌ها
        key = instruction.strip().lower()
        if key in seen:
            removed["duplicate"] += 1
            continue
        seen.add(key)
        
        cleaned.append(item)
    
    print(f"قبل از تمیزکاری: {len(data)}")
    print(f"بعد از تمیزکاری: {len(cleaned)}")
    print(f"حذف شده‌ها: {removed}")
    
    return cleaned

۲. نرمال‌سازی متن فارسی

import re

def normalize_persian(text):
    """نرمال‌سازی متن فارسی"""
    # یکسان‌سازی کاراکترهای عربی و فارسی
    text = text.replace('ك', 'ک')
    text = text.replace('ي', 'ی')
    text = text.replace('٤', '۴')
    text = text.replace('٥', '۵')
    text = text.replace('٦', '۶')
    
    # حذف فاصله‌های اضافی
    text = re.sub(r'\s+', ' ', text)
    
    # حذف نیم‌فاصله‌های اضافی
    text = re.sub(r'‌{2,}', '‌', text)
    
    # اصلاح نیم‌فاصله
    text = re.sub(r' (می|نمی) ', r' \1‌', text)
    text = re.sub(r' (ها|های|هایی) ', r'‌\1 ', text)
    
    return text.strip()

# اعمال روی دیتاست
for item in data:
    item["instruction"] = normalize_persian(item["instruction"])
    item["output"] = normalize_persian(item["output"])

Deduplication: حذف تکراری‌ها

داده تکراری یکی از بدترین مشکلاته. باعث می‌شه مدل overfit کنه و فقط اون نمونه‌ها رو حفظ کنه.

from datasketch import MinHash, MinHashLSH

def remove_near_duplicates(data, threshold=0.8):
    """حذف نمونه‌های تقریباً مشابه با MinHash LSH"""
    lsh = MinHashLSH(threshold=threshold, num_perm=128)
    unique_data = []
    
    for i, item in enumerate(data):
        text = item.get("instruction", "") + " " + item.get("output", "")
        
        # ساخت MinHash
        mh = MinHash(num_perm=128)
        for word in text.split():
            mh.update(word.encode('utf-8'))
        
        # بررسی تکراری بودن
        result = lsh.query(mh)
        if not result:
            lsh.insert(str(i), mh)
            unique_data.append(item)
    
    print(f"قبل: {len(data)} → بعد: {len(unique_data)}")
    return unique_data

Data Augmentation: زیاد کردن داده

اگه داده کم داری، می‌تونی با تکنیک‌های مختلف داده بیشتری بسازی:

# روش ۱: بازنویسی سوال (Paraphrasing)
def augment_with_llm(instruction, model="gpt-4"):
    """ساخت نسخه‌های مختلف سوال با LLM"""
    prompt = f"""سوال زیر رو به ۳ شکل مختلف بازنویسی کن.
    معنی رو تغییر نده، فقط نحوه بیان رو عوض کن.
    
    سوال اصلی: {instruction}
    
    ۳ بازنویسی:"""
    
    # فراخوانی API
    response = call_llm(prompt, model=model)
    return parse_paraphrases(response)

# روش ۲: ساخت داده مصنوعی
def generate_synthetic_data(topic, num_samples=100):
    """ساخت جفت سوال-جواب مصنوعی"""
    prompt = f"""برای موضوع "{topic}" تعداد {num_samples} جفت سوال و جواب 
    آموزشی بساز. جواب‌ها باید دقیق و کامل باشن.
    
    فرمت خروجی:
    Q: سوال
    A: جواب
    ---"""
    
    response = call_llm(prompt)
    return parse_qa_pairs(response)

# روش ۳: تغییر فرمت
def change_format(item):
    """تبدیل فرمت نمونه"""
    variations = []
    
    # تبدیل به سوال بله/خیر
    variations.append({
        "instruction": f"آیا جمله زیر درسته؟ {item['output'][:50]}...",
        "output": "بله، این جمله درست است."
    })
    
    # تبدیل به تکمیل جمله
    variations.append({
        "instruction": f"جمله زیر رو کامل کن: {item['output'][:30]}...",
        "output": item['output']
    })
    
    return variations

ابزار Argilla برای مدیریت دیتاست

Argilla یه ابزار open-source برای مدیریت، برچسب‌گذاری و بررسی دیتاسته:

# نصب Argilla
# pip install argilla

import argilla as rg

# اتصال به سرور Argilla
rg.init(api_url="http://localhost:6900", api_key="your_key")

# ساخت دیتاست برای بررسی کیفیت
dataset = rg.FeedbackDataset(
    fields=[
        rg.TextField(name="instruction", title="سوال"),
        rg.TextField(name="output", title="جواب"),
    ],
    questions=[
        rg.RatingQuestion(
            name="quality", 
            title="کیفیت جواب",
            values=[1, 2, 3, 4, 5]
        ),
        rg.TextQuestion(
            name="corrected_output",
            title="جواب اصلاح‌شده (اگه لازمه)",
            required=False
        ),
    ],
)

# آپلود نمونه‌ها
records = [
    rg.FeedbackRecord(
        fields={"instruction": item["instruction"], "output": item["output"]}
    )
    for item in data
]
dataset.add_records(records)
dataset.push_to_argilla(name="fine-tuning-review")

تبدیل به فرمت نهایی

import json

def convert_to_chat_format(data, system_prompt=""):
    """تبدیل دیتاست به فرمت مکالمه‌ای"""
    converted = []
    
    for item in data:
        messages = []
        
        if system_prompt:
            messages.append({"role": "system", "content": system_prompt})
        
        messages.append({"role": "user", "content": item["instruction"]})
        messages.append({"role": "assistant", "content": item["output"]})
        
        converted.append({"messages": messages})
    
    return converted

# تبدیل و ذخیره
chat_data = convert_to_chat_format(
    cleaned_data,
    system_prompt="تو یه دستیار متخصص برنامه‌نویسی پایتون هستی."
)

# تقسیم به train و validation
from sklearn.model_selection import train_test_split

train_data, val_data = train_test_split(chat_data, test_size=0.1, random_state=42)

# ذخیره
def save_jsonl(data, path):
    with open(path, 'w', encoding='utf-8') as f:
        for item in data:
            f.write(json.dumps(item, ensure_ascii=False) + '\n')

save_jsonl(train_data, "train.jsonl")
save_jsonl(val_data, "val.jsonl")

print(f"Train: {len(train_data)} نمونه")
print(f"Validation: {len(val_data)} نمونه")

چک‌لیست کیفیت دیتاست

قبل از شروع Fine-tuning، این چک‌لیست رو بررسی کن:

  • آیا نمونه‌ها تکراری نیستن؟
  • آیا جواب‌ها دقیق و بدون خطان؟
  • آیا سبک جواب‌ها یکدسته؟
  • آیا تنوع کافی توی سوالات هست؟
  • آیا نمونه‌های خیلی کوتاه یا خیلی بلند حذف شدن؟
  • آیا فرمت همه نمونه‌ها یکسانه؟
  • آیا validation set جدا شده؟
  • آیا متن فارسی نرمال‌سازی شده؟

یادت باشه: ۸۰٪ وقت Fine-tuning باید صرف آماده‌سازی داده بشه. اگه داده‌ات خوب باشه، بقیه کار نسبتاً ساده‌ست.

جمع‌بندی

آماده‌سازی دیتاست مهم‌ترین و زمان‌برترین بخش Fine-tuning هست. روی کیفیت تمرکز کن نه کمیت. داده‌ها رو تمیز، نرمال‌سازی و deduplicate کن. از ابزارهایی مثل Argilla برای بررسی کیفیت استفاده کن.

حالا که دیتاست آماده‌ست، توی اپیزود بعدی Unsloth رو بررسی می‌کنیم — ابزاری که Fine-tuning رو ۲ برابر سریع‌تر و با ۶۰ درصد حافظه کمتر انجام می‌ده.

نظرات

هنوز نظری ثبت نشده. اولین نفر باشید!

نظر خود را بنویسید