يمكن أن تكون نماذج التدريب لفهم التفضيلات البشرية والتنبؤ بها معقدة بشكل لا يصدق. غالبًا ما تتطلب الأساليب التقليدية، مثل الضبط الدقيق الخاضع للإشراف، تعيين تسميات محددة للبيانات، وهو أمر غير فعال من حيث التكلفة، خاصة بالنسبة للمهام الدقيقة. يعد تحسين التفضيلات أسلوبًا بديلاً يمكنه تبسيط هذه العملية والحصول على نتائج أكثر دقة. ومن خلال التركيز على مقارنة إجابات المرشحين وترتيبها بدلاً من تعيين تسميات ثابتة، يسمح تحسين التفضيلات للنماذج بالتقاط التفاصيل الدقيقة للحكم البشري بشكل أكثر فعالية.

يتم استخدام تحسين التفضيلات على نطاق واسع لضبط نماذج اللغة، ولكن يمكن تطبيقه أيضًا على نماذج لغة الرؤية (VLM). يسعدنا أن نعلن أن تدعم مكتبة TRL الآن تحسين التفضيل المباشر (DPO) لـ VLMs. سترشدك هذه المقالة خلال عملية تدريب VLMs باستخدام TRL وDPO.

مجموعة البيانات التفضيلية

يتطلب تحسين التفضيلات بيانات تلتقط تفضيلات المستخدم. في إعداد الاختيار الثنائي، يتكون كل مثال من مطالبة وإجابتين مرشحتين: واحدة تم اختيارها والأخرى مرفوضة. هدف النموذج هو تعلم التنبؤ بالإجابة المختارة بدلاً من الإجابة المرفوضة. على سبيل المثال، يجب أن يكون لديك عينات مثل ما يلي:

صورة من openbmb/RLAIF-V-Dataset

❔ سؤال: كم عدد العائلات؟

  • ❌ مرفوض: الصورة لا تقدم أي معلومات عن العائلات.
  • ✅ تم الاختيار: تُظهر الصورة إعداد جدول منظمة الاتحاد الذي يضم 18000 عائلة.

لاحظ أن الرسالة المختارة ليست صحيحة بالضرورة. على سبيل المثال، الإجابة المختارة التي تقول 18000 أسرة لا تزال خاطئة، ولكنها أقل خطأ مقارنة بالإجابة المرفوضة.

بالنسبة لمنشور المدونة هذا، سنستخدم openbmb/RLAIF-V-Dataset، والذي يتضمن أكثر من 83000 صف مشروح. دعونا نلقي نظرة فاحصة على مجموعة البيانات:

>>> from datasets import load_dataset
>>> dataset = load_dataset("openbmb/RLAIF-V-Dataset", split="train[:1%]")
>>> sample = dataset[1]
>>> sample["image"].show()
>>> sample["question"]
'how many families?'
>>> sample["rejected"]
'The image does not provide any information about families.'
>>> sample["chosen"]
'The image shows a Union Organization table setup with 18,000 families.'

يتطلب نموذجنا كلاً من النص والصور كمدخلات، لذا فإن الخطوة الأولى هي تنسيق مجموعة البيانات لتناسب هذا المطلب. يجب تنظيم البيانات لمحاكاة محادثة بين المستخدم والمساعد. ويقدم المستخدم مطالبة تتضمن صورة وسؤالًا، بينما يقوم المساعد بالرد بإجابة. وإليك كيفية إجراء هذا التنسيق:

from datasets import features
from transformers import AutoProcessor

processor = AutoProcessor.from_pretrained("HuggingFaceM4/idefics2-8b", do_image_splitting=False)

def format(example):
    
    prompt = [
        
            "role": "user",
            "content": ["type": "image", "type": "text", "text": example["question"]],
        ,
    ]
    chosen = [
        
            "role": "assistant",
            "content": ["type": "text", "text": example["chosen"]],
        ,
    ]
    rejected = [
        
            "role": "assistant",
            "content": ["type": "text", "text": example["rejected"]],
        ,
    ]
    
    prompt = processor.apply_chat_template(prompt, tokenize=False)
    chosen = processor.apply_chat_template(chosen, tokenize=False)
    rejected = processor.apply_chat_template(rejected, tokenize=False)
    
    
    max_size = processor.image_processor.size["longest_edge"]
    example["image"].thumbnail((max_size, max_size))
    return "images": [example["image"]], "prompt": prompt, "chosen": chosen, "rejected": rejected



dataset = dataset.map(format, remove_columns=dataset.column_names)



f = dataset.features
f["images"] = features.Sequence(features.Image(decode=True))  
dataset = dataset.cast(f)

تم الآن تنسيق مجموعة البيانات الخاصة بنا. دعونا نلقي نظرة على المثال الأول:

>>> dataset[1]
'images': [<PIL.JpegImagePlugin.JpegImageFile image mode=L size=980x812 at 0x154505570>],
 'prompt': 'User:<image>how many families?<end_of_utterance>\n',
 'rejected': 'Assistant: The image does not provide any information about families.<end_of_utterance>\n',
 'chosen': 'Assistant: The image shows a Union Organization table setup with 18,000 families.<end_of_utterance>\n'

قم بإحماء وحدات معالجة الرسومات الخاصة بك، مجموعة البيانات جاهزة للتدريب!

تمرين

من أجل المثال، سنقوم بتدريب نموذج Idefics2-8b، لكن لاحظ أن تنفيذ DPO في TRL يدعم نماذج أخرى مثل Lava 1.5 وPaliGemma. مزيد من المعلومات في القسم Finetuning Lava 1.5 وPaliGemma وغيرها. قبل النظر في عملية التدريب، سنتأكد أولاً من أن كل شيء يتناسب بسلاسة مع الذاكرة.

ما مقدار الذاكرة التي أحتاجها؟

لدي وحدة معالجة الرسومات (GPU) بسعة 80 جيجابايت من VRAM. هل يكفي تدريب نموذج Idefics2-8b الخاص بي؟ فيما يلي خطوات الحساب للحصول على تقدير تقريبي للذاكرة المطلوبة.

يترك ن ن يكون عدد المعلمات ، ص ص الدقة. يجب أن تتناسب المكونات التالية معًا في الذاكرة:

  • نموذج للتدريب: ن×ص ن \ مرات ص
  • النموذج المرجعي: النموذج المرجعي هو نفس النموذج المطلوب تدريبه، لذا فهو يتطلب أيضًا ن×ص ن \ مرات ص
  • التدرجات: نقوم بتدريب النموذج بأكمله، وكل معلمة تتطلب تدرجًا، لذلك فهي تتطلب ذلك ن×ص ن \ مرات ص
  • الدول محسن: نستخدم AdamW، الأمر الذي يتطلب حالتين لكل معلمة، لذلك يتطلب الأمر 2×ن×ص 2 \مرات N \مرات P

يحتوي Idefics2-8b على 8 مليار معلمة، ونحن نستخدمها float32 الدقة التي تتطلب 4 بايت لكل تعويم. وبالتالي فإن إجمالي الذاكرة المطلوبة هو:

عنصر حساب ذاكرة
نموذج للتدريب 8×109×4 8 مرات 10^9 مرات 4 32 جيجابايت
النموذج المرجعي 8×109×4 8 مرات 10^9 مرات 4 32 جيجابايت
التدرجات 8×109×4 8 مرات 10^9 مرات 4 32 جيجابايت
الدول محسن 2×8×109×4 2 مرات 8 مرات 10^9 مرات 4 64 جيجابايت
المجموع 160 جيجابايت

هذا أعلى بكثير من سعة ذاكرة وحدة معالجة الرسومات الخاصة بي. لحسن الحظ، من خلال تطبيق تقنيات مثل التكميم وLoRA، يمكننا تقليل متطلبات الذاكرة بشكل كبير وجعل التدريب ممكنًا. دعونا نرى كيفية القيام بذلك.

التكميم

التكميم هو أسلوب يقلل من دقة أوزان النموذج وعمليات التنشيط. التحول من float32 ل bfloat16 تعمل الدقة على خفض متطلبات التخزين لكل معلمة إلى النصف من 4 بايت إلى 2 بايت. يحافظ هذا التحسين على الذاكرة بينما يعمل أيضًا على تسريع العمليات الحسابية، مما يضمن الأداء العالي بأقل قدر من التنازلات. للتنفيذ bfloat16 الدقة في النموذج:

import torch
from transformers import AutoModelForVision2Seq

model = AutoModelForVision2Seq.from_pretrained("HuggingFaceM4/idefics2-8b", torch_dtype=torch.bfloat16)

bfloat16 يمكن أيضًا تطبيق الدقة على المحسن من خلال الإعداد bf16=True في الحجج التدريبية:

from transformers import TrainingArguments

training_args = TrainingArguments(..., bf16=True)

لورا

LoRA هي طريقة تقلل من عدد المعلمات القابلة للتدريب عن طريق تعلم أزواج من مصفوفات تحليل الرتب مع الحفاظ على الأوزان الأصلية مجمدة. وهذا يقلل بشكل كبير من احتياجات التخزين لـ LLM التي تتكيف مع مهام محددة. تم دمج LoRA في PEFT ويمكنك إعداده في أي وقت من الأوقات:

  from transformers import AutoModelForVision2Seq
+ from peft import get_peft_model, LoraConfig

  model = AutoModelForVision2Seq.from_pretrained("HuggingFaceM4/idefics2-8b")
+ peft_config = LoraConfig(target_modules="all-linear")
+ model = get_peft_model(model, peft_config)

يعمل PEFT كغلاف (يسمى محول) حول النموذج. هذا هو المحول الذي سيتم تدريبه أثناء تجميد النموذج الداخلي. إلى أي مدى يقلل LoRA من عدد المعلمات القابلة للتدريب؟

>>> model.print_trainable_parameters()
trainable params: 55,348,736 || all params: 8,458,116,848 || trainable%: 0.6543860411799315

فهو يقلل من عدد المعلمات القابلة للتدريب من 8 مليارات إلى 55 مليونًا، وهي فجوة كبيرة، وسيؤدي إلى تقليل متطلبات الذاكرة بشكل كبير.

متطلبات الذاكرة الجديدة بعد التكميم وLoRA

الآن بعد أن قمنا بتقليل متطلبات الذاكرة، دعونا نعيد حساب الذاكرة المطلوبة:

عنصر حساب ذاكرة
نموذج للتدريب 8ز×2 8 MathrmG مرات 2 16 جيجابايت
النموذج المرجعي 8ز×2 8 MathrmG مرات 2 16 جيجابايت
التدرجات 55م×2 55 \ MathrmM \ مرات 2 0.1 جيجابايت
الدول محسن 2×55م×2 2 مرات 55 MathrmM مرات 2 0.2 جيجابايت
المجموع 32.3 جيجابايت

هذه المرة، نحتاج إلى حوالي 32 جيجابايت من الذاكرة لضبط نموذج Idefics2-8b الخاص بنا، وهو أكثر معقولية ويتناسب مع وحدة معالجة الرسومات الخاصة بي!

للحصول على معلومات إضافية حول تحسين استخدام الذاكرة باستخدام LoRA وQLoRA، راجع وثائق PEFT أو توصيات LoRA وQLoRA Google لـ LLMs.

ماذا عن حجم الدفعة؟

حساب الذاكرة الخاص بنا ليس دقيقًا لأنه لا يأخذ في الاعتبار عمليات التنشيط. عمليات التنشيط هي المخرجات الوسيطة لطبقات الشبكة وتعتمد متطلبات الذاكرة الخاصة بها على بنية النموذج وحجم الدفعة. يعد إجراء حساب دقيق للذاكرة اللازمة لعمليات التنشيط أمرًا صعبًا، لذا سنعتمد على الملاحظات التجريبية.

لاختيار حجم الدفعة التدريبية المناسب (per_device_train_batch_size)، ابدأ بحجم الدفعة المطلوبة (على سبيل المثال، 64). من المحتمل أن يؤدي هذا إلى حدوث خطأ نفاد الذاكرة (OOM). إذا حدث ذلك، فقم بتقليل حجم الدفعة إلى النصف ومضاعفة خطوات تراكم التدرج (gradient_accumulation_steps) للحفاظ على نفس حجم الدفعة الفعال. كرر هذه العملية حتى يتم احتواء الذاكرة داخل وحدة معالجة الرسومات الخاصة بك. في حالتنا، انتهى بنا الأمر بحجم دفعة يبلغ 2 وخطوات تراكم متدرجة تبلغ 32.

التحسين الإضافي هو استخدام نقاط فحص التدرج (gradient_checkpointing) لتقليل الذاكرة اللازمة لعمليات التنشيط. تقوم هذه التقنية باستبدال الحساب بالذاكرة عن طريق إعادة حساب أجزاء من الشبكة أثناء التمرير للخلف. يمكن تمكينه عن طريق الإعداد gradient_checkpointing=True في الحجج التدريبية.

ملخص: البرنامج النصي للتدريب الكامل

الآن وبعد أن قمنا بإعداد النموذج ومجموعة البيانات ومعلمات التدريب، أصبحنا جاهزين للتدريب. إليك كيفية تجميع كل شيء معًا في البرنامج النصي، بما في ذلك بعض العناصر الإضافية لتسريع المعالجة، مثل dataset_num_proc و dataloader_num_workers:


from datasets import features, load_dataset
from transformers import AutoModelForVision2Seq, AutoProcessor
import torch
from trl import DPOConfig, DPOTrainer
from peft import LoraConfig


def main():
    
    model = AutoModelForVision2Seq.from_pretrained("HuggingFaceM4/idefics2-8b", torch_dtype=torch.bfloat16)
    processor = AutoProcessor.from_pretrained("HuggingFaceM4/idefics2-8b", do_image_splitting=False)

    
    dataset = load_dataset("openbmb/RLAIF-V-Dataset", split="train")

    def format(example):
        
        prompt = ["role": "user", "content": ["type": "image", "type": "text", "text": example["question"]]]
        chosen = ["role": "assistant", "content": ["type": "text", "text": example["chosen"]]]
        rejected = ["role": "assistant", "content": ["type": "text", "text": example["rejected"]]]
        
        prompt = processor.apply_chat_template(prompt, tokenize=False)
        chosen = processor.apply_chat_template(chosen, tokenize=False)
        rejected = processor.apply_chat_template(rejected, tokenize=False)
        
        
        max_size = processor.image_processor.size["longest_edge"] // 2
        example["image"].thumbnail((max_size, max_size))
        return "images": [example["image"]], "prompt": prompt, "chosen": chosen, "rejected": rejected

    
    dataset = dataset.map(format, remove_columns=dataset.column_names, num_proc=32)

    
    
    f = dataset.features
    f["images"] = features.Sequence(features.Image(decode=True))
    dataset = dataset.cast(f)

    
    training_args = DPOConfig(
        output_dir="idefics2-8b-dpo",
        bf16=True,
        gradient_checkpointing=True,
        per_device_train_batch_size=2,
        gradient_accumulation_steps=32,
        num_train_epochs=1,
        dataset_num_proc=32,  
        dataloader_num_workers=32,  
        logging_steps=10,
    )
    trainer = DPOTrainer(
        model,
        ref_model=None,  
        args=training_args,
        train_dataset=dataset,
        tokenizer=processor,
        peft_config=LoraConfig(target_modules="all-linear"),
    )

    trainer.train()


if __name__ == "__main__":
    main()

يلا نجري وننتظر… 🚀

accelerate launch dpo_idefics2-8b.py

نتائج

وبعد ساعات قليلة، يكتمل التدريب. دعونا نلقي نظرة على منحنيات التدريب:

منحنيات التعلم

في DPO، نركز على عدة مقاييس لتقييم جودة التدريب:

  • دقة: يشير هذا المقياس إلى النسبة المئوية لعينات التدريب حيث من المرجح أن يقوم النموذج بإخراج الإجابة المختارة بدلاً من الإجابة المرفوضة. يمكننا أن نرى زيادة في الدقة، وهي علامة إيجابية.
  • المكافآت: ترتبط المكافآت باحتمال اختيار الإجابة. لمزيد من التفاصيل، راجع ورقة DPO، القسم 5. نتوقع أن تكون مكافأة الإجابة المختارة أعلى من مكافأة الإجابة المرفوضة. وللتأكد من ذلك ننظر إلى هامش المكافأةوهو الفرق بين مكافآت الإجابات المختارة والمرفوضة. كما أن هامش المكافأة المتزايد، كما هو ملاحظ هنا، يعد علامة جيدة أيضًا.

تقييم

الاستدلال

مع اكتمال التدريب على النموذج، فإن الخطوة التالية هي تقييم أدائه في بعض الأمثلة. سيعطينا هذا فكرة عن مدى جودة تعلم النموذج ومدى فعاليته في تقديم التنبؤات. فيما يلي برنامج نصي لمساعدتك في تقييم النموذج وتحليل أدائه على مجموعة من أمثلة الاختبار:

from transformers import AutoModelForVision2Seq, AutoProcessor
from PIL import Image

model = AutoModelForVision2Seq.from_pretrained("HuggingFaceM4/idefics2-8b").to("cuda")
processor = AutoProcessor.from_pretrained("HuggingFaceM4/idefics2-8b", do_image_splitting=False)
model.load_adapter("HuggingFaceH4/idefics2-8b-dpo-rlaif-v-v0.3")  


user_message = ...
image_path = ...
data = ["role": "user", "content": ["type": "image", "type": "text", "text": user_message]]
prompts = processor.apply_chat_template(data, add_generation_prompt=True)  
images = [Image.open(image_path)]
inputs = processor(prompts, images, return_tensors="pt")
inputs = k: v.to("cuda") for k, v in inputs.items()


generated_ids = model.generate(**inputs, max_new_tokens=500)
response_text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
print(response_text)

كما ذكرنا سابقًا، تم تصميم openbmb/RLAIF-V-Dataset لتقليل الهلوسة. ولكن هل أدى الضبط الدقيق إلى تقليل الهلوسة بالفعل؟ لمعرفة ذلك، يمكننا استخدام معيار AMBER، وهو عبارة عن مجموعة بيانات تم إنشاؤها خصيصًا لتقييم الهلوسة في VLMs. سنقوم بالإبلاغ عن نتائج Idefics2 وIdefics2+DPO في المهمة التمييزية ومقارنتها مع النماذج الأخرى كمرجع.

دقة F1
جي بي تي-4o 88.8 91.6
Idefics2+DPO 85.9 89.4
تعريف2 85.8 89.1
جي بي تي-4ف 83.4 87.4
مينيجيميني 82.6 87.6
لافا-نيكست 81.4 85.4
كوين-VL 81.9 86.4
إغراء 73.5 77.7
أوبرا 75.2 78.3
الأقل هو أكثر 72.4 75.8
أقراص الفيديو الرقمية 71.8 74.9

بشكل عام، يبدو أن النموذج المضبوط يبدو أقل هلوسة قليلاً. يبدو أن التدريب كان ناجحاً!

فيما يلي بعض الأمثلة المختارة بعناية لتوضيح أداء النموذج:

صورة سؤال تعريف2 Idefics2+DPO
أمبر_2 هل هناك سفينتين في هذه الصورة؟ نعم لا
أمبر_111 هل الأرض غير مستوية في هذه الصورة؟ لا نعم
أمبر_7 هل هناك مجرفة واحدة في هذه الصورة؟ نعم لا

جربه بنفسك وشاهد أداء النموذج باستخدام الأمثلة الخاصة بك!

Finetuning Lava 1.5 وPaliGemma وغيرها

في وقت كتابة هذا التقرير، كان تطبيق DPO في TRL يدعم Idefics2 وLava 1.5 وPaliGemma، مع الجهود المستمرة لإضافة دعم لمزيد من النماذج. أسهل طريقة لضبط هذه النماذج هي استخدام مثال البرنامج النصي الموجود في مستودع TRL. على سبيل المثال، لضبط PaliGemma، يمكنك استخدام الأمر التالي:

accelerate launch examples/scripts/dpo_visual.py \
    --dataset_name HuggingFaceH4/rlaif-v_formatted \
    --model_name_or_path google/paligemma-3b-pt-224 \
    --per_device_train_batch_size 2 \
    --gradient_accumulation_steps 32 \
    --dataset_num_proc 32 \
    --output_dir dpo_paligemma_rlaif-v \
    --bf16 \
    --torch_dtype bfloat16 \
    --gradient_checkpointing \
    --use_peft \
    --lora_target_modules=all-linear

يمكنك العثور على تركيز تفصيلي على ضبط PaliGemma في مشروع smol-vision.

🚀🚀 الآن لديك كل ما تحتاجه لبدء ضبط أجهزة VLM الخاصة بك باستخدام DPO. شارك نتائجك ونماذجك ومجموعات البيانات مع المجتمع!

شاركها.
اترك تعليقاً