Fine-tuning برای زبان فارسی — چالش‌ها و راه‌حل‌ها

قسمت ۹ ۲۲ دقیقه

زبان فارسی و مدل‌های زبانی

وقتی با مدل‌های زبانی انگلیسی کار می‌کنی، همه چیز نسبتاً ساده‌ست. ولی فارسی یه سری چالش‌های خاص داره که اگه ندونی، ممکنه ساعت‌ها وقتت تلف بشه و نتیجه خوبی نگیری.

بیا از مهم‌ترین چالش شروع کنیم: tokenization.

مشکل Tokenization فارسی

Tokenizer مدل‌های زبانی، متن رو به توکن‌ها (قطعات کوچک) تبدیل می‌کنه. مشکل اینه که اکثر tokenizer‌ها برای انگلیسی بهینه شدن و فارسی رو به توکن‌های خیلی کوچیک (حتی بایت‌های UTF-8) تبدیل می‌کنن.

from transformers import AutoTokenizer

# مقایسه tokenization فارسی در مدل‌های مختلف
models = {
    "LLaMA 3.1": "meta-llama/Llama-3.1-8B-Instruct",
    "Qwen 2.5": "Qwen/Qwen2.5-7B-Instruct",
    "Gemma 2": "google/gemma-2-9b-it",
}

persian_text = "هوش مصنوعی در سال‌های اخیر پیشرفت چشمگیری داشته است."

for name, model_id in models.items():
    tokenizer = AutoTokenizer.from_pretrained(model_id)
    tokens = tokenizer.tokenize(persian_text)
    token_ids = tokenizer.encode(persian_text)
    
    print(f"\n{name}:")
    print(f"  تعداد توکن: {len(token_ids)}")
    print(f"  توکن‌ها: {tokens[:10]}...")
    print(f"  نسبت توکن/کلمه: {len(token_ids)/len(persian_text.split()):.1f}")

چرا تعداد توکن مهمه؟

  • هزینه بالاتر: هرچی توکن بیشتر، هزینه API بیشتر
  • Context window کمتر: متن فارسی ۲-۳ برابر بیشتر توکن مصرف می‌کنه
  • آموزش کندتر: نمونه‌های فارسی توکن بیشتری دارن
  • کیفیت پایین‌تر: وقتی کلمات به بایت تبدیل می‌شن، مدل ساختار زبان رو بد یاد می‌گیره
نکته مهم: مدل‌هایی که tokenizer بهتری برای فارسی دارن (مثل Qwen) نتیجه بهتری توی Fine-tuning فارسی می‌دن. قبل از انتخاب مدل، حتماً tokenization فارسی رو بررسی کن.

انتخاب مدل مناسب برای فارسی

همه مدل‌ها برای فارسی مناسب نیستن. بعضی‌ها فارسی خیلی کمی توی دیتای pre-trainingشون دیدن.

# ارزیابی سریع مدل‌ها برای فارسی
def evaluate_persian_capability(model_name):
    """بررسی توانایی فارسی مدل"""
    from transformers import pipeline
    
    generator = pipeline("text-generation", model=model_name, device_map="auto")
    
    test_prompts = [
        "پایتخت ایران",
        "نوروز جشن",
        "برنامه‌نویسی پایتون یعنی",
        "مزایای هوش مصنوعی شامل",
    ]
    
    print(f"\nمدل: {model_name}")
    for prompt in test_prompts:
        output = generator(prompt, max_new_tokens=50, do_sample=False)
        text = output[0]["generated_text"]
        print(f"  {prompt} → {text[len(prompt):80]}...")

# مدل‌های توصیه‌شده برای فارسی (به ترتیب اولویت)
recommended = [
    "Qwen/Qwen2.5-7B-Instruct",      # بهترین tokenizer فارسی
    "Qwen/Qwen2.5-14B-Instruct",     # بزرگ‌تر و بهتر
    "google/gemma-2-9b-it",           # عملکرد خوب فارسی
    "meta-llama/Llama-3.1-8B-Instruct",  # عملکرد متوسط فارسی
]

چرا Qwen برای فارسی بهتره؟

  • Tokenizer بهینه‌تری برای زبان‌های غیرلاتین داره
  • دیتای pre-training چندزبانه قوی‌تری داره
  • عملکرد خوبی روی زبان‌های RTL نشون داده
  • تعداد توکن فارسی کمتری تولید می‌کنه

منابع دیتاست فارسی

# منابع رایگان دیتاست فارسی
persian_datasets = {
    # دیتاست‌های عمومی
    "Persian Wikipedia": "wikimedia/wikipedia (fa)",
    "CC-100 Persian": "cc100 (fa subset)",
    "OSCAR Persian": "oscar-corpus/OSCAR-2301 (fa)",
    
    # دیتاست‌های NLP
    "ParsiNLU": "persiannlp/parsinlu",        # NLU tasks
    "FarsTail": "persiannlp/farstail",         # NLI
    "Persian QA": "SajjadAyoubi/persian_qa",   # Question Answering
    "PN-Summary": "hooshvare/pn_summary",      # خلاصه‌سازی خبر
    
    # دیتاست‌های مکالمه‌ای
    "Persian Alpaca": "jondurbin/airoboros-gpt4-1.4.1-persian",
}

# لود یه دیتاست فارسی
from datasets import load_dataset

# مثال: Persian QA
dataset = load_dataset("SajjadAyoubi/persian_qa", split="train")
print(f"تعداد نمونه: {len(dataset)}")
print(f"یه نمونه: {dataset[0]}")

ساخت دیتاست فارسی با ترجمه

def translate_dataset(english_data, batch_size=10):
    """ترجمه دیتاست انگلیسی به فارسی"""
    persian_data = []
    
    for i in range(0, len(english_data), batch_size):
        batch = english_data[i:i+batch_size]
        
        for item in batch:
            # ترجمه با LLM
            prompt = f"""Translate the following Q&A pair to Persian.
Keep technical terms in English if commonly used.
Maintain the same level of detail.

Question: {item['instruction']}
Answer: {item['output']}

Persian translation:
Question (Persian):"""
            
            translation = call_llm(prompt)
            parsed = parse_translation(translation)
            
            persian_data.append({
                "instruction": parsed["question"],
                "output": parsed["answer"],
                "original_en": item["instruction"],
            })
    
    return persian_data

# نکته مهم: بعد از ترجمه حتماً بررسی کیفیت کن
# ترجمه ماشینی ممکنه خطا داشته باشه

مدیریت RTL (راست به چپ)

فارسی زبان راست‌به‌چپ (RTL) هست. این توی Fine-tuning مشکل خاصی ایجاد نمی‌کنه (چون مدل با توکن‌ها کار می‌کنه نه جهت نمایش)، ولی چند نکته هست:

# نکته ۱: ترکیب فارسی و انگلیسی (Mixed RTL/LTR)
mixed_text = "برای نصب کتابخانه از دستور pip install transformers استفاده کن."
# این متن ترکیبی از RTL (فارسی) و LTR (انگلیسی) هست
# مدل معمولاً خوب handle می‌کنه ولی توی تولید ممکنه مشکل بخوره

# نکته ۲: نرمال‌سازی Unicode فارسی
import unicodedata

def normalize_persian_text(text):
    """نرمال‌سازی کامل متن فارسی"""
    # NFC normalization
    text = unicodedata.normalize('NFC', text)
    
    # یکسان‌سازی حروف عربی-فارسی
    replacements = {
        'ك': 'ک', 'ي': 'ی',
        '٠': '۰', '١': '۱', '٢': '۲', '٣': '۳',
        '٤': '۴', '٥': '۵', '٦': '۶', '٧': '۷',
        '٨': '۸', '٩': '۹',
        'ؤ': 'و', 'ة': 'ه',
    }
    for old, new in replacements.items():
        text = text.replace(old, new)
    
    # نیم‌فاصله (zero-width non-joiner)
    # اطمینان از استفاده صحیح
    import re
    text = re.sub(r'‌{2,}', '‌', text)  # حذف نیم‌فاصله تکراری
    
    return text

# نکته ۳: توکن‌های خاص فارسی
special_chars = {
    '‌': 'ZWNJ (نیم‌فاصله)',          # ‌
    '‍': 'ZWJ (اتصال‌دهنده)',         # ‍
    '،': 'ویرگول فارسی',
    '؛': 'نقطه‌ویرگول فارسی',
    '؟': 'علامت سوال فارسی',
}

# مطمئن شو دیتاست نیم‌فاصله‌ها رو درست داره
for item in dataset:
    item["instruction"] = normalize_persian_text(item["instruction"])
    item["output"] = normalize_persian_text(item["output"])

ارزیابی مدل فارسی

def evaluate_persian_model(model, tokenizer, test_data):
    """ارزیابی جامع مدل فارسی"""
    results = {
        "fluency": [],       # روانی زبان فارسی
        "accuracy": [],      # دقت محتوا
        "mixed_lang": [],    # مدیریت ترکیب فارسی-انگلیسی
        "formality": [],     # رعایت سطح رسمیت
    }
    
    for item in test_data:
        response = generate_response(model, tokenizer, item["prompt"])
        
        # بررسی‌های خودکار
        # ۱. آیا خروجی فارسیه؟
        persian_ratio = count_persian_chars(response) / max(len(response), 1)
        
        # ۲. آیا اصطلاحات فنی انگلیسی حفظ شدن؟
        tech_terms_preserved = check_tech_terms(response, item.get("expected_terms", []))
        
        # ۳. آیا نیم‌فاصله درست استفاده شده؟
        zwnj_correct = check_zwnj_usage(response)
        
        print(f"Prompt: {item['prompt'][:50]}...")
        print(f"  فارسی: {persian_ratio:.0%}")
        print(f"  اصطلاحات فنی: {'درست' if tech_terms_preserved else 'مشکل‌دار'}")
        print(f"  نیم‌فاصله: {'درست' if zwnj_correct else 'مشکل‌دار'}")

def count_persian_chars(text):
    """شمارش کاراکترهای فارسی/عربی"""
    import re
    persian_pattern = re.compile(r'[؀-ۿݐ-ݿﭐ-﷿ﹰ-]')
    return len(persian_pattern.findall(text))

def check_zwnj_usage(text):
    """بررسی استفاده صحیح از نیم‌فاصله"""
    import re
    # الگوهای رایج نیم‌فاصله
    patterns = [
        r'می‌\w+',      # می‌کنم، می‌شه
        r'نمی‌\w+',     # نمی‌دونم
        r'\w+‌ها',      # کتاب‌ها
        r'\w+‌ای',      # خونه‌ای
    ]
    # اگه فارسی هست ولی نیم‌فاصله نداره، احتمالاً مشکل داره
    has_persian = bool(re.search(r'[؀-ۿ]', text))
    has_zwnj = '‌' in text
    
    if has_persian and not has_zwnj and len(text) > 50:
        return False
    return True

اشتباهات رایج

  • استفاده از مدل نامناسب: مدلی که فارسی کم دیده، حتی با Fine-tuning هم خوب نمی‌شه
  • نادیده گرفتن نرمال‌سازی: کاراکترهای عربی و فارسی قاطی می‌شن
  • ترجمه مستقیم: ترجمه ماشینی بدون بررسی کیفیت
  • نادیده گرفتن نیم‌فاصله: متن بدون نیم‌فاصله غیرطبیعی به نظر می‌رسه
  • context window: فراموش کردن اینکه فارسی ۲-۳ برابر بیشتر توکن مصرف می‌کنه

بهترین استراتژی برای Fine-tuning فارسی: مدل Qwen + دیتاست باکیفیت فارسی + نرمال‌سازی دقیق + ارزیابی انسانی توسط فارسی‌زبان بومی.

فرآیند پیشنهادی

# فرآیند کامل Fine-tuning فارسی

# ۱. انتخاب مدل — Qwen 2.5 توصیه می‌شه
model_name = "Qwen/Qwen2.5-7B-Instruct"

# ۲. آماده‌سازی دیتاست
# - جمع‌آوری داده فارسی باکیفیت
# - نرمال‌سازی Unicode
# - بررسی نیم‌فاصله‌ها
# - حذف تکراری‌ها

# ۳. تنظیم tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
# بررسی کیفیت tokenization فارسی
sample = "برنامه‌نویسی پایتون"
print(f"توکن‌ها: {tokenizer.tokenize(sample)}")
print(f"تعداد: {len(tokenizer.encode(sample))}")

# ۴. Fine-tuning با Unsloth (QLoRA)
# max_seq_length رو بالاتر بذار چون فارسی توکن بیشتری داره
max_seq_length = 4096  # به جای 2048

# ۵. ارزیابی
# حتماً ارزیاب فارسی‌زبان بومی داشته باش

جمع‌بندی

Fine-tuning برای فارسی چالش‌های خاص خودش رو داره ولی غیرممکن نیست. مهم‌ترین نکات: مدل مناسب انتخاب کن (Qwen)، دیتاست رو درست نرمال‌سازی کن، و حتماً ارزیابی انسانی توسط فارسی‌زبان بومی داشته باش.

توی اپیزود آخر، از Fine-tune تا Deploy رو بررسی می‌کنیم — چطور مدل Fine-tune شده‌ات رو سرو کنی و در محیط واقعی استفاده‌اش کنی.

نظرات

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

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