لاشك أن استخدام نماذج الذكاء الاصطناعي المُدَرَّبة مُسبقًا pretrained model يقلل الوقت والجهد والتكاليف اللازمة لتدريب هذه النماذج من الصفر، فضلًا عن إتاحة الفرصة أمامك لاستخدام أحدث النماذج المتوفرة على منصات متخصصة مثل تلك التي توفرها مكتبة المُحوّلات Transformers من منصة Hugging Face، لذا يلجأ مهندسو الذكاء الاصطناعي لاستخدام النماذج المُدَرَّبة مُسبقًا في كثير من الحالات ويعمدون إلى صقلها أو معايرتها Fine Tuning بدقة وتدريبها على بيانات محددة تناسب أهدافهم، إذ يعني صقل النماذج fine-tuning أخذ نموذج تعلم آلي مدرب مسبقًا ومواصلة تدريبه على مجموعة بيانات أصغر وأكثر تخصصًا للحفاظ على قدرات النموذج المدرب وتكييفه ليناسب استخدامات محددة ويعطي تنبؤاتٍ دقيقة، كما سنطرح بعض الأمثلة التوضيحية على تدريب النماذج باستخدام كل من التقنيات التالية:
-
مُدَرِّب مكتبة المحوّلات Transformers Trainer.
-
إطار العمل تنسرفلو TensorFlow مع كيراس Keras.
-
إطار العمل بايتورش PyTorch لوحده.
تحضير مجموعة بيانات التدريب
قبل أن نبدأ بصقل النموذج fine-tune سنُحَمِّل مجموعة بيانات Dataset ونُحَضِّر بياناتها كما تعلمنا في المقال السابق المعالجة المُسبقة للبيانات قبل تمريرها لنماذج الذكاء الاصطناعي.
اخترنا في هذا المقال مجموعة البيانات Yelp Reviews:
>>> from datasets import load_dataset >>> dataset = load_dataset("yelp_review_full") >>> dataset["train"][100] {'label': 0, 'text': 'My expectations for McDonalds are t rarely high. But for one to still fail so spectacularly...that takes something special!\\nThe cashier took my friends\'s order, then promptly ignored me. I had to force myself in front of a cashier who opened his register to wait on the person BEHIND me. I waited over five minutes for a gigantic order that included precisely one kid\'s meal. After watching two people who ordered after me be handed their food, I asked where mine was. The manager started yelling at the cashiers for \\"serving off their orders\\" when they didn\'t have their food. But neither cashier was anywhere near those controls, and the manager was the one serving food to customers and clearing the boards.\\nThe manager was rude when giving me my order. She didn\'t make sure that I had everything ON MY RECEIPT, and never even had the decency to apologize that I felt I was getting poor service.\\nI\'ve eaten at various McDonalds restaurants for over 30 years. I\'ve worked at more than one location. I expect bad days, bad moods, and the occasional mistake. But I have yet to have a decent experience at this store. It will remain a place I avoid unless someone in my party needs to avoid illness from low blood sugar.'}
وبما أن مجموعة بياناتنا نصية لذا سنحتاج مُرَمِّزًا tokenizer مناسبًا للنموذج لمعالجتها كما تعلمنا في مقالات السلسلة، تتضمن هذه المعالجة أساليب الحشو والاقتطاع لتوحيد أطوال السلاسل النصية، وسنستخدم دالةً تدعى map
لتسريع المعالجة التحضيرية للبيانات وتطبيقها على كامل مجموعة البيانات dataset وفق التالي:
>>> from transformers import AutoTokenizer >>> tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased") >>> def tokenize_function(examples): return tokenizer(examples["text"], padding="max_length", truncation=True) >>> tokenized_datasets = dataset.map(tokenize_function, batched=True)
للسهولة وتسريع العمل يمكنك أخذ جزء من مجموعة البيانات فقط بدلًا من العمل معها كاملةً كما يلي:
>>> small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000)) >>> small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))
تدريب نموذج ذكاء اصطناعي باستخدام PyTorch Trainer
إن المُدَرِّبTrainer هو أحد أصناف مكتبة المحوّلات Transformers حيث يستخدم لتدريب نماذج المكتبة، ويوفر عليك أعباء إنشاء حلقة تدريب خاصة بمشروعك من الصفر، ويتمتع هذا الصنف بواجهة برمجية API متنوعة الخيارات وتؤمن مزايا تدريبية واسعة، مثل: تسجيل الأحداث logging، والتدرج التراكمي gradient accumulation، والدقة المختلطة mixed precision.
سنبدأ عملنا بتحميل النموذج وفق الأوامر التالية مع تحديد عدد التسميات التوضيحية labels
المتوقعة من البيانات المُدخَلة، وإذا قرأت بطاقة وصف مجموعة البيانات التي حضرناها Yelp Review ستجد أن عدد التسميات labels
فيها هو 5:
>>> from transformers import AutoModelForSequenceClassification >>> model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5)
ملاحة: عندما تُنَفِذ الأوامر السابقة ستواجه تحذيرًا مفاده أن بعض الأوزان المُدَرَّبة مسبقًا في النموذج لن تُسْتَخْدَمْ، وبعضها ستُعاد تهيئته عشوائيًا، يُعدّ هذا التحذير طبيعيًا ولا يستوجب القلق إذ سيُهمَل رأس النموذج BERT المُدَرَّب مسبقًا ويُستَبْدَل برأس تصنيف لا على التعيين، ثم يٌدَرَّب الرأس الجديد على تصنيف السلاسل وتنتقل إليه تلقائيًا كل المعرفة التي اكتسبها النموذج المدرب مسبقًا فيستفيد منها، علمًا أن رأس النموذج model head هو الجزء المسؤول عن معالجة مهمة معينة مثل التصنيف أو الترجمة ويستخدم لتحديد نتائجها.
المعاملات الفائقة للنموذج hyperparameters
سننشئ صنفًا لوسطاء التدريب TrainingArguments يتضمن كافة المعاملات الفائقة التي يمكننا ضبطها بالإضافة إلى الرايات flags الخاصة بتفعيل خيارات التدريب المختلفة، سنستعمل هنا المعاملات الافتراضية لكن يمكنك استعراض جميع المعاملات الفائقة وتجريبها لتصل إلى الإعدادات الملائمة لحالتك.
ملاحظة: المعاملات الفائقة للنموذج هي المعاملات التي نحددها قبل بدء عملية تدريب النموذج وتتحكم في كيفية تعلم النموذج.
لا تنسَ أن تحدد مكان حفظ نقاط التحقق checkpoints الناتجة عن التدريب:
>>> from transformers import TrainingArguments >>> training_args = TrainingArguments(output_dir="test_trainer")
التقييم
لا يعطي المُدَرِّب Trainer في الأحوال الطبيعية مؤشراتٍ عن أداء النماذج في أثناء التدريب، فإذا رغبت بالحصول على تقييمٍ لنموذجك، ينبغي أن تمرر دالة خاصة بهذا الأمر تحسب مؤشرات الأداء وترجع لك تقريرًا بتقييم النموذج، وفي هذا المجال توفر مكتبة التقييم Evaluate دالةً بسيطة تدعى accuracy
يمكنك تحميلها باستخدام evaluate.load
كما في المثال التالي (طالع هذه الجولة السريعة في مكتبة التقييم لمزيدٍ من المعلومات):
>>> import numpy as np >>> import evaluate >>> metric = evaluate.load("accuracy")
استدعِ الدالة compute
مع metric
وفق التالي لحساب دقة التنبؤات الناتجة عن نموذجك، ولأن نماذج مكتبة المحوّلات Transformers تضع مخرجاتها في السمة logits
(كما تعلمنا سابقًا في مقال جولة سريعة للبدء مع مكتبة المحوّلات Transformers) فينبغي لنا في البداية تحويل logits
الناتجة عن النموذج إلى تنبؤات predictions ثم تمريرها لدالة حساب الدقة:
>>> def compute_metrics(eval_pred): logits, labels = eval_pred predictions = np.argmax(logits, axis=-1) return metric.compute(predictions=predictions, references=labels)
والآن أعطِ القيمة "epoch"
للمعامل evaluation_strategy
من وسطاء التدريب TrainingArguments لمراقبة أداء نموذجك أثناء التدريب فهذا الخيار سيعطيك تقييمًا في نهاية كل دورة تدريبية epoch للنموذج:
>>> from transformers import TrainingArguments, Trainer >>> training_args = TrainingArguments(output_dir="test_trainer", evaluation_strategy="epoch")
المُدَرِّب Trainer
لننشئ الآن كائن المُدَرِّب Trainer باستخدام جميع الإعدادات السابقة وهي: النموذج الذي اخترناه، ووسطاء التدريب، ومجموعة بيانات التدريب، ودالة التقييم وذلك وفق الأمر التالي:
>>> trainer = Trainer( model=model, args=training_args, train_dataset=small_train_dataset, eval_dataset=small_eval_dataset, compute_metrics=compute_metrics, )
ثم دَرِّب نموذجك باستخدامه كما يلي:
>>> trainer.train()
تدريب نموذج TensorFlow باستخدام Keras
نناقش هنا تدريب نماذج من مكتبة Transformers باستخدام إطار العمل تنسرفلو TensorFlow وكيراس Keras API، حيث أن كيراس هو إطار عمل سهل ومفتوح المصدر يسمح بإنشاء شبكات عصبية معقدة بتعليمات قليلة، بدأ مشروعًا مستقلًا ثم اندمج مع TensorFlow (يمكنك معرفة المزيد بمطالعة قسم الذكاء الاصطناعي على أكاديمية حسوب وخاصةً المقال التعريفي مكتبات وأطر عمل الذكاء الاصطناعي).
تحويل البيانات إلى صيغة تناسب كيراس Keras
يتطلب تدريب نماذج Transformers باستخدام Keras API تحميل مجموعة بيانات بصيغة تتوافق مع كيراس Keras، وإحدى الطرق السهلة لذلك هي تحويل البيانات التدريبية إلى مصفوفات NumPy ثم تمريرها له، تناسب هذه الطريقة مجموعات البيانات صغيرة الحجم وهي ما سنجربه بدايةً قبل الانتقال إلى طرق أكثر تعقيدًا.
لنُحمِّل في البداية مجموعة بيانات، وقد اخترنا هنا المجموعة CoLA dataset من GLUE benchmark وهي مجموعة بيانات بسيطة تناسب تصنيف النصوص الثنائية binary text، وسنأخذ منها القسم المخصص للتدريب فقط:
from datasets import load_dataset dataset = load_dataset("glue", "cola") dataset = dataset["train"] # أخذنا من مجموعة البيانات القسم الخاص بالتدريب فقط
سنُحَمِّل بعد ذلك مُرمِّزًا tokenizer يناسب النموذج ونستخدمه لترميز بيانات الندريب وتحويلها إلى مصفوفات NumPy، ولكن في مثالنا البيانات ثنائية بسيطة وتسمياتها التوضيحية labels
هي مجموعة أصفار وواحدات فيمكننا تحويلها إلى مصفوفة NumPy مباشرةً دون ترميز:
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased") tokenized_data = tokenizer(dataset["sentence"], return_tensors="np", padding=True) # يرجع المُرَمِّز دفعات مُرَمَّزة من البيانات، حوّلناها هنا إلى قاموس يناسب Keras tokenized_data = dict(tokenized_data) labels = np.array(dataset["label"]) # هذه البيانات في الأساس مصفوفة أصفار وواحدات
وفي المرحة الأخيرة سنُحَمِّل النموذج ونخضعه لعملية تصريف compile ثم ملائمة fit، وننوه هنا أن كل نموذج في مكتبة المحوّلات يتضمن دالةً افتراضية لحساب الخسارة loss function تناسب المهمة التي يُستَخدم النموذج لأجلها، فلست بحاجة لضبط أي خيارات بهذا الخصوص:
from transformers import TFAutoModelForSequenceClassification from tensorflow.keras.optimizers import Adam # تحميل النموذج وتصريفه model = TFAutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased") # غالبًا ما تكون معدلات التَعَلُّم المنخفضة هي الأنسب لصقل نماذج مكتبة المحوّلات المُدَرَّبة مُسبقًا model.compile(optimizer=Adam(3e-5)) # لاحظ عدم وجود أي وسيط يتعلق بدالة حساب الخسارة فهي افتراضية model.fit(tokenized_data, labels)
ملاحظة: تختار نماذج Hugging Face تلقائيًا دوال الخسارة المناسبة لبُنيتها ومهامها، لذا لن تضطر لتمرير الوسيط الخاص بحساب الخسارة عند تصريف نموذجك باستخدام compile()
لكن الخيار يبقى لك ففي حال لم ترغب باستخدام دالة الخسارة الافتراضية يمكنك حسابها بنفسك.
يعمل أسلوب الترميز السابق جيدًا مع مجموعات البيانات صغيرة الحجم لكنه لا يُعدّ عمليًّا أبدًا مع مجموعات البيانات الكبيرة بل وسيؤدي إلى إبطاء عملية التدريب، يعود ذلك لسببين: الأول أن مصفوفتي الرموز والتسميات التوضيحية ستكونان كبيرتي الحجم وتحميلهما بالكامل إلى الذاكرة يُعدّ مشكلة، والسبب الثاني أن مكتبة Numpy لا تستطيع التعامل مع المصفوفات غير منظمة الأطوال المعروفة باسم jagged arrays يعني ذلك أنك ستضطر إلى حشو العناصر القصيرة في المصفوفة بالأصفار لتصبح جميعها بطول موحد يساوي أطول عنصر في المصفوفة، وهذا أيضًا سيُبَطئ التدريب.
تحميل مجموعة بيانات بصيغة tf.data.Dataset
يمكنك تحميل مجموعة بياناتك بصيغة tf.data.Dataset
بدلًا من اتباع الطريقة السابقة والمخاطرة بإبطاء التدريب، سنقترح عليك طريقتين لإنجاز الأمر، وتستطيع إنشاء خط أنابيبك الخاص tf.data
يدويًا إذا رغبت بذلك:
-
prepare_tf_dataset()
: تعتمد هذه الطريقة على طبيعة نموذجك لذا تُعدّ الطريقة الأنسب والموصى بها في معظم الحالات، فهي تفحص مواصفات النموذج وتتعرف تلقائيًا على أعمدة مجموعة البيانات المتوافقة معه أي التي يمكن استخدامها كمدخلات للنموذج، وتتجاهل الأعمدة غير المتوافقة فتُنشئ بذلك مجموعة بيانات أبسط وأفضل أداءً. -
to_tf_dataset
: طريقة منخفضة المستوى low-level فهي تتحكم بالتفاصيل الدقيقة لطريقة إنشاء مجموعة بيانات التدريب، فتُمَكِّنك من تحديد الأعمدةcolumns
وتسمياتها التوضيحيةlabel_cols
التي تود تضمينها في مجموعة البيانات.
لنبدأ بالطريقة الأولى prepare_tf_dataset()
، ولكن قبل تطبيقها ينبغي لنا ترميز البيانات وإدخال مُخرجات المُرَمِّز بهيئة أعمدة إلى مجموعة البيانات dataset كما يلي:
def tokenize_dataset(data): # سنُدخِل مفاتيح القاموس الناتج هنا إلى مجموعة البيانات بصفتها أعمدة return tokenizer(data["text"]) dataset = dataset.map(tokenize_dataset)
تُخَزَّن مجموعات بيانات Hugging Face على القرص الصلب افتراضيًا، فلن تسبب ضغطًا على استخدام الذاكرة على حاسوبك، وبمجرد إضافة الأعمدة السابقة تستطيع الحصول على الدفعات batches من مجموعة البيانات، وإضافة رموز الحشو إلى كل دفعة وهو ما يقلل عدد رموز الحشو المطلوبة في كل مرة مقارنةً بحشو مجموعة البيانات كاملةً.
tf_dataset = model.prepare_tf_dataset(dataset["train"], batch_size=16, shuffle=True, tokenizer=tokenizer)
مررنا إلى Preparation_tf_dataset
في التعليمات السابقة وسيطًا خاصًا هو tokenizer
يحدد المُرَمِّز الذي سنستخدمه لحشو الدفعات المُحَمَّلة بطريقة صحيحة، لكن يمكنك الاستغناء عنه إذا كانت العينات في مجموعة بياناتك متساوية الطول ولا تحتاج لأي حشو، أو استبداله بوسيطٍ آخر نحو Collate_fn
إذا كنت ترغب بتدريب النموذج على حالات أعقد، مثل نمذجة اللغة المقنعة masked language modelling أي إخفاء بعض الرموز ليتنبأ النموذج بالكلمات من السياق أو غيرها من الحالات، تستطيع تحدد نوعية المعالجة التحضيرية التي تريدها للدفعات قبل تمريرها للنموذج، يساعدك الاطلاع على بعض الأمثلة والملاحظات المتعلقة بالموضوع من منصة Hugging Face لتطبيق ذلك عمليًّا.
والآن بعد إنشاء مجموعة بيانات tf.data.Dataset
يتبقى لنا الخطوة الأخيرة وهي تصريف النموذج compile
وملائمته fit
وفق التالي:
model.compile(optimizer=Adam(3e-5)) # No loss argument! model.fit(tf_dataset)
تدريب نموذج ذكاء اصطناعي باستخدام Native PyTorch
يساعدك المُدَرِّب Trainer على تدريب نموذجك وضبطه بتعليمة واحدة فقط ويغنيك عن إنشاء حلقة التدريب من الصفر، لكن بعض المستخدمين يفضلون عدم الاعتماد على المُدَرِّب وإنشاء حلقات تدريبهم الخاصة بأنفسهم لتدريب نماذج مكتبة المحوّلات Transformers، فإذا كنت أحدهم يمكنك إجراء ذلك باستخدام إطار العمل PyTorch لوحده أي native PyTorch وفق الخطوات التالية، لكن في البداية ننصحك بتفريغ الذاكرة المؤقتة على جهازك أو دفتر ملاحظاتك notebook بإعادة تشغيله أو بتنفيذ هذه الأوامر:
del model del trainer torch.cuda.empty_cache()
تتضمن الخطوات التالية المعالجة التي سنجريها يدويًا على البيانات المُرَمَّزة tokenized_dataset
لتحضيرها للتدريب.
1. تخَلَّص من عمود النص text
لأن النموذج لا يقبل النصوص الخام مدخلاتٍ له:
>>> tokenized_datasets = tokenized_datasets.remove_columns(["text"])
2. عَدِّل اسم العمود label
إلى labels
ليتوافق مع اسم الوسيط الذي يقبله النموذج:
>>> tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
3. اضبط تنسيق مجموعة البيانات لتُرجِع PyTorch tensors بدلًا من القوائم المعتادة:
>>> tokenized_datasets.set_format("torch")
4. ثم أنشئ مجموعة بيانات مُصَغَّرة من مجموعة البيانات الكاملة كما فعلنا في الفقرات السابقة لتسريع عملية صقل النموذج fine-tuning:
>>> small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000)) >>> small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))
مُحَمِّل البيانات DataLoader
أنشئ مُحَمِّل بيانات DataLoader
لمجموعات بيانات التدريب والاختبار لتٌنَفِّذ العمليات التكرارية على دفعات البيانات:
>>> from torch.utils.data import DataLoader >>> train_dataloader = DataLoader(small_train_dataset, shuffle=True, batch_size=8) >>> eval_dataloader = DataLoader(small_eval_dataset, batch_size=8)
ثم حَمِّل النموذج وحَدِّد عدد التسميات labels
المتوقعة له:
>>> from transformers import AutoModelForSequenceClassification >>> model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5)
المُحَسِّنْ Optimizer ومُجَدّوِل معدل التعلُّم learning rate scheduler
سننشئ مُحَسِّنْ Optimizer ومُجَدّوِل معدل التعلُّم learning rate scheduler لتدريب النموذج وضبطه، اخترنا هنا المُحَسِّنْ AdamW من PyTorch:
>>> from torch.optim import AdamW >>> optimizer = AdamW(model.parameters(), lr=5e-5)
ثم مُجَدّوِل معدل التعلُّم باستخدام المُجَدّوِل الافتراضي للمُدَّرِب Trainer:
>>> from transformers import get_scheduler >>> num_epochs = 3 >>> num_training_steps = num_epochs * len(train_dataloader) >>> lr_scheduler = get_scheduler( name="linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps )
وأخيرًا حَدِّدْ قيمة المعامل device
لتكون "cpu"
إذا كان لديك وحدة معالجة رسوميات (GPU) ورغبت باستخدامها للتدريب، فمن دونها سيستغرق التدريب مدةً طويلة في حال الاعتماد على وحدة المعالجة المركزية (CPU) لوحدها وقد تصل المدة لساعات بدلًا من بضع دقائق:
>>> import torch >>> device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") >>> model.to(device)
اقتباساقتباسننوه هنا إلى توفر خدمات سحابية تتيح لك الوصول إلى وحدات GPU عبر السحابة من مزودين مثل: Colaboratory و SageMaker.
أصبحنا جاهزين الآن للتدريب.
حلقة التدريب Training loop
إليك نموذجًا للشيفرة البرمجية الخاصة بحلقة التدريب، ويمكنك استخدام المكتبة tqdm لإضافة شريط خاص bar يعرض لك تقدم مراحل التدريب لتتبع سير العملية:
>>> from tqdm.auto import tqdm >>> progress_bar = tqdm(range(num_training_steps)) >>> model.train() for epoch in range(num_epochs): for batch in train_dataloader: batch = {k: v.to(device) for k, v in batch.items()} outputs = model(**batch) loss = outputs.loss loss.backward() optimizer.step() lr_scheduler.step() optimizer.zero_grad() progress_bar.update(1)
التقييم Evaluate
يختلف أسلوب تقييم أداء النموذج في حلقة التدريب المخصصة عنه في صنف المُدَرِّب Trainer، فبدلًا من حساب مؤشرات الأداء وتصدير التقارير عنها في نهاية كل دورة تدربية epoch سيجري التقييم هنا في نهاية التدريب إذ سنُجَمع كافة الدفعات باستخدم add_batch
ونحسب مؤشرات الأداء.
>>> import evaluate >>> metric = evaluate.load("accuracy") >>> model.eval() for batch in eval_dataloader: batch = {k: v.to(device) for k, v in batch.items()} with torch.no_grad(): outputs = model(**batch) logits = outputs.logits predictions = torch.argmax(logits, dim=-1) metric.add_batch(predictions=predictions, references=batch["labels"]) >>> metric.compute()
الخلاصة
تغرفنا في مقال اليوم على تقنية الصقل fine-tuning لتدريب نماذج الذكاء الاصطناعي المُدَرَّبة مسبقًا وتحسين أدائها على مهام معينة باستخدام بيانات محددة، ووضحنا الفوائد الرئيسية لاستخدام نماذج مُدَرَّبة مسبقًا وكيفية تحميل وتحضير البيانات لها وتدريبها وضبطها وتقييم أدائها من خلال استخدام مكتبات وأطر عمل متنوعة تساعدنا في تنفيذ مهام الصقل مثل Transformers و TensorFlow و Keras و PyTorch كي نحسن من كفاءة وأداء هذه النماذج في مشاريعنا الخاصة.
ترجمة -وبتصرف- لقسم Fine-tune a pretrained model من توثيقات Hugging Face.
اقرأ أيضًا
- المقال السابق: المعالجة المُسبقة للبيانات قبل تمريرها لنماذج الذكاء الاصطناعي
- تعرف على إطار عمل باي تورش PyTorch وأهميته لتطبيقات الذكاء الاصطناعي
- كيفية بناء شبكة عصبية لترجمة لغة الإشارة إلى اللغة الإنجليزية
- تعرف على أفضل دورات الذكاء الاصطناعي
أفضل التعليقات
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.