ارزیابی مدل Fine-tune شده — چطور بفهمی خوب شده

قسمت ۷ ۱۸ دقیقه

چطور بفهمی مدلت خوب شده؟

Fine-tuning تموم شد. مدل آموزش دیده. Loss پایین اومده. ولی آیا مدل واقعاً کار درست انجام می‌ده؟ Loss پایین لزوماً به معنی مدل خوب نیست — ممکنه مدل فقط دیتای آموزش رو حفظ کرده باشه (overfitting) بدون اینکه واقعاً یاد گرفته باشه.

ارزیابی مدل Fine-tune شده یه هنره. باید بدونی چی رو اندازه بگیری و چطور تفسیرش کنی.

۱. Perplexity — اولین معیار

Perplexity نشون می‌ده مدل چقدر توی پیش‌بینی توکن بعدی “گیج” می‌شه. هرچی کمتر باشه، مدل بهتر عمل می‌کنه.

import torch
import math
from transformers import AutoModelForCausalLM, AutoTokenizer

def calculate_perplexity(model, tokenizer, texts, max_length=2048):
    """محاسبه Perplexity روی مجموعه‌ای از متن‌ها"""
    model.eval()
    total_loss = 0
    total_tokens = 0
    
    with torch.no_grad():
        for text in texts:
            inputs = tokenizer(
                text, 
                return_tensors="pt", 
                truncation=True, 
                max_length=max_length
            ).to(model.device)
            
            outputs = model(**inputs, labels=inputs["input_ids"])
            total_loss += outputs.loss.item() * inputs["input_ids"].size(1)
            total_tokens += inputs["input_ids"].size(1)
    
    avg_loss = total_loss / total_tokens
    perplexity = math.exp(avg_loss)
    
    return perplexity

# مقایسه مدل قبل و بعد از Fine-tuning
ppl_before = calculate_perplexity(base_model, tokenizer, val_texts)
ppl_after = calculate_perplexity(fine_tuned_model, tokenizer, val_texts)

print(f"Perplexity قبل: {ppl_before:.2f}")
print(f"Perplexity بعد: {ppl_after:.2f}")
print(f"بهبود: {(1 - ppl_after/ppl_before) * 100:.1f}%")
هشدار: Perplexity فقط یه معیار کلیه. Perplexity پایین ممکنه به خاطر overfitting باشه. حتماً روی validation set (داده‌ای که مدل ندیده) محاسبه کن.

۲. ارزیابی انسانی — مهم‌ترین معیار

هیچ معیار خودکاری جای ارزیابی انسانی رو نمی‌گیره. یه آدم باید خروجی مدل رو ببینه و قضاوت کنه.

import json
import random

def create_evaluation_set(test_data, num_samples=50):
    """ساخت مجموعه ارزیابی انسانی"""
    samples = random.sample(test_data, min(num_samples, len(test_data)))
    
    evaluation_sheet = []
    for i, sample in enumerate(samples):
        # تولید جواب از مدل Fine-tune شده
        prompt = sample["instruction"]
        generated = generate_response(fine_tuned_model, prompt)
        
        evaluation_sheet.append({
            "id": i + 1,
            "prompt": prompt,
            "expected": sample["output"],       # جواب مرجع
            "generated": generated,              # جواب مدل
            "scores": {
                "accuracy": None,     # ۱-۵: دقت محتوا
                "relevance": None,    # ۱-۵: مرتبط بودن
                "fluency": None,      # ۱-۵: روانی متن
                "format": None,       # ۱-۵: رعایت فرمت
            },
            "notes": ""
        })
    
    # ذخیره برای بررسی
    with open("evaluation_sheet.json", "w", encoding="utf-8") as f:
        json.dump(evaluation_sheet, f, ensure_ascii=False, indent=2)
    
    print(f"{len(evaluation_sheet)} نمونه آماده ارزیابی شد.")
    return evaluation_sheet

# ساخت و استفاده
eval_set = create_evaluation_set(test_data, num_samples=50)

# بعد از ارزیابی انسانی — تحلیل نتایج
def analyze_human_eval(eval_file):
    with open(eval_file, "r", encoding="utf-8") as f:
        results = json.load(f)
    
    metrics = {"accuracy": [], "relevance": [], "fluency": [], "format": []}
    
    for item in results:
        for key in metrics:
            if item["scores"][key] is not None:
                metrics[key].append(item["scores"][key])
    
    print("نتایج ارزیابی انسانی:")
    for key, scores in metrics.items():
        avg = sum(scores) / len(scores)
        print(f"  {key}: {avg:.2f}/5.0")

۳. ارزیابی Task-Specific

بسته به task، معیارهای خاصی هست:

# برای خلاصه‌سازی: ROUGE
from rouge_score import rouge_scorer

def evaluate_summarization(model, test_data):
    scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'])
    scores = {"rouge1": [], "rouge2": [], "rougeL": []}
    
    for item in test_data:
        generated = generate_response(model, item["instruction"])
        score = scorer.score(item["output"], generated)
        
        for key in scores:
            scores[key].append(score[key].fmeasure)
    
    print("نتایج ROUGE:")
    for key, vals in scores.items():
        print(f"  {key}: {sum(vals)/len(vals):.4f}")

# برای طبقه‌بندی: Accuracy, F1
from sklearn.metrics import classification_report

def evaluate_classification(model, test_data, labels):
    predictions = []
    ground_truth = []
    
    for item in test_data:
        pred = generate_response(model, item["instruction"])
        pred_label = extract_label(pred, labels)
        
        predictions.append(pred_label)
        ground_truth.append(item["expected_label"])
    
    print(classification_report(ground_truth, predictions, target_names=labels))

# برای تولید کد: pass@k
def evaluate_code_generation(model, test_problems, k=1):
    correct = 0
    total = len(test_problems)
    
    for problem in test_problems:
        generated_code = generate_response(model, problem["prompt"])
        
        # اجرای تست‌ها
        try:
            exec(generated_code + "\n" + problem["test_code"])
            correct += 1
        except Exception:
            pass
    
    pass_at_k = correct / total
    print(f"pass@{k}: {pass_at_k:.2%}")

۴. A/B Testing

مقایسه مستقیم مدل Fine-tune شده با مدل پایه:

import random

def ab_test(base_model, fine_tuned_model, test_prompts, num_judges=3):
    """A/B test بین دو مدل"""
    results = {"base_wins": 0, "ft_wins": 0, "tie": 0}
    
    for prompt in test_prompts:
        # تولید جواب از هر دو مدل
        base_response = generate_response(base_model, prompt)
        ft_response = generate_response(fine_tuned_model, prompt)
        
        # شافل ترتیب برای جلوگیری از bias
        if random.random() > 0.5:
            option_a, option_b = base_response, ft_response
            mapping = {"A": "base", "B": "ft"}
        else:
            option_a, option_b = ft_response, base_response
            mapping = {"A": "ft", "B": "base"}
        
        print(f"\nPrompt: {prompt}")
        print(f"\nOption A: {option_a[:200]}...")
        print(f"\nOption B: {option_b[:200]}...")
        
        # از ارزیاب بخواه انتخاب کنه
        choice = input("کدوم بهتره؟ (A/B/T for tie): ").upper()
        
        if choice == "T":
            results["tie"] += 1
        elif mapping.get(choice) == "base":
            results["base_wins"] += 1
        elif mapping.get(choice) == "ft":
            results["ft_wins"] += 1
    
    total = sum(results.values())
    print(f"\nنتایج A/B Test:")
    print(f"  مدل پایه برنده: {results['base_wins']} ({results['base_wins']/total:.0%})")
    print(f"  مدل Fine-tune: {results['ft_wins']} ({results['ft_wins']/total:.0%})")
    print(f"  مساوی: {results['tie']} ({results['tie']/total:.0%})")
    
    return results

۵. تشخیص Overfitting

Overfitting یعنی مدل فقط دیتای آموزش رو حفظ کرده و روی داده جدید خوب عمل نمی‌کنه.

def detect_overfitting(trainer, train_data, val_data):
    """تشخیص overfitting با مقایسه loss آموزش و ارزیابی"""
    
    # محاسبه loss روی train و validation
    train_loss = evaluate_loss(trainer.model, train_data)
    val_loss = evaluate_loss(trainer.model, val_data)
    
    gap = val_loss - train_loss
    
    print(f"Train Loss: {train_loss:.4f}")
    print(f"Validation Loss: {val_loss:.4f}")
    print(f"Gap: {gap:.4f}")
    
    if gap > 0.5:
        print("هشدار: احتمال overfitting بالاست!")
        print("پیشنهادها:")
        print("  - epoch‌ها رو کم کن")
        print("  - داده بیشتر اضافه کن")
        print("  - lora_dropout رو افزایش بده")
        print("  - rank رو کم کن")
    elif gap > 0.2:
        print("کمی overfitting وجود داره — قابل قبوله")
    else:
        print("وضعیت خوبه — overfitting دیده نمی‌شه")

# بررسی روند آموزش
def plot_training_curve(log_history):
    """رسم نمودار training curve"""
    import matplotlib.pyplot as plt
    
    train_losses = [log["loss"] for log in log_history if "loss" in log]
    eval_losses = [log["eval_loss"] for log in log_history if "eval_loss" in log]
    
    plt.figure(figsize=(10, 6))
    plt.plot(train_losses, label="Train Loss")
    
    if eval_losses:
        # نقاط ارزیابی
        eval_steps = [i * (len(train_losses) // len(eval_losses)) 
                      for i in range(len(eval_losses))]
        plt.plot(eval_steps, eval_losses, label="Validation Loss", marker="o")
    
    plt.xlabel("Step")
    plt.ylabel("Loss")
    plt.title("Training Curve")
    plt.legend()
    plt.savefig("training_curve.png")
    plt.show()

نشانه‌های Overfitting

  • Train loss کم می‌شه ولی validation loss زیاد می‌شه
  • مدل جواب‌هایی تولید می‌کنه که عیناً کپی دیتاست آموزشه
  • روی سوالات مشابه خوب جواب می‌ده ولی سوالات متفاوت نه
  • تنوع جواب‌ها خیلی کمه

۶. استراتژی Validation Set

from sklearn.model_selection import train_test_split

def prepare_eval_split(data, test_size=0.1, strategy="random"):
    """تقسیم داده به train و validation"""
    
    if strategy == "random":
        train, val = train_test_split(data, test_size=test_size, random_state=42)
    
    elif strategy == "temporal":
        # برای داده‌های زمانی — آخرین نمونه‌ها validation
        split_idx = int(len(data) * (1 - test_size))
        train, val = data[:split_idx], data[split_idx:]
    
    elif strategy == "category":
        # تقسیم بر اساس دسته‌بندی
        from collections import defaultdict
        categories = defaultdict(list)
        for item in data:
            cat = item.get("category", "general")
            categories[cat].append(item)
        
        train, val = [], []
        for cat, items in categories.items():
            t, v = train_test_split(items, test_size=test_size, random_state=42)
            train.extend(t)
            val.extend(v)
    
    print(f"Train: {len(train)} | Validation: {len(val)}")
    return train, val

# استفاده
train_data, val_data = prepare_eval_split(all_data, test_size=0.1)

چک‌لیست ارزیابی

بعد از Fine-tuning، این مراحل رو طی کن:

  • مرحله ۱: Training curve رو بررسی کن — آیا loss به درستی کاهش یافته؟
  • مرحله ۲: Perplexity روی validation set رو محاسبه کن
  • مرحله ۳: Overfitting رو بررسی کن (مقایسه train vs val loss)
  • مرحله ۴: ۲۰-۵۰ نمونه رو دستی بررسی کن
  • مرحله ۵: A/B test با مدل پایه انجام بده
  • مرحله ۶: معیارهای task-specific رو محاسبه کن

قبل از deploy کردن مدل، حتماً ارزیابی انسانی انجام بده. اعداد و معیارها مهمن ولی قضاوت نهایی با انسانه.

جمع‌بندی

ارزیابی مدل Fine-tune شده فقط نگاه کردن به loss نیست. باید از چند زاویه مختلف بررسی کنی: Perplexity، ارزیابی انسانی، معیارهای تخصصی، A/B test و بررسی overfitting. هر معیار یه بخش از تصویر رو نشون می‌ده.

توی اپیزود بعدی، DPO رو بررسی می‌کنیم — تکنیکی که می‌تونه کیفیت مدل رو بعد از SFT یه پله بالاتر ببره، بدون پیچیدگی RLHF.

نظرات

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

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