اذهب إلى المحتوى

صُمِّمت مكتبة المحوّلات Transformers المتخصصة في بناء نماذج الذكاء الاصطناعي من منصة Huggingface بحيث يمكن توسيعها بسهولة، وتُكتَب النماذج Models بالكامل في مجلد فرعي محدَّد من المستودع بدون تجريد أو إخفاء لأي من تفاصيل العمل، لذا يمكننا بسهولة نسخ أي ملف نموذج وتعديله وفقًا لاحتياجاتنا. وإذا أردنا كتابة نموذج Model جديد خاص بنا، فيمكن البدء بالنموذج من الصفر.

سنوضّح في هذا المقال كيفية كتابة نموذج مخصَّص وضبطه Configuration لنتمكّن من استخدامه بشكل يتوافق مع مكتبة المحوّلات Transformers، وسنوضّح كيفية مشاركته مع المجتمع مع شيفرته البرمجية ليتمكن أي شخص من استخدامه، حتى إن لم يكن موجودًا في مكتبة المحوّلات Transformers، حيث سنرى كيفية إضافة أو تعديل الوظائف التي يقدمها إطار العمل الافتراضي في مكتبة Transformers باستخدام أدوات برمجية كالخطافات Hooks والشيفرة البرمجية الخاصة بنا. سنستخدم في هذا المقال نموذج ResNet الذي هو جزء من المكتبة timm ونعدّله ليعمل كجزء من مكتبة Transformers وسنغلفه ضمن النموذج PreTrainedModel الذي يعد أساس جميع النماذج في Transformers.

كتابة ضبط Configuration مخصص

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

ملاحظة: لا حاجة لتمرير كل وسيط بشكل فردي عند إنشاء النموذج، حيث تتبع النماذج في مكتبة المحوّلات Transformers منهجية تمرير كائن واحد config إلى التابع __init__ الخاص بالنموذج. بعد ذلك، يُمرَّر هذا الكائن بالكامل إلى الطبقات الفرعية للنموذج بدلاً من تقسيمه إلى عدة وسطاء. هذا يجعل الشيفرة البرمجية بسيطة ومنظمة من خلال الاحتفاظ بجميع الإعدادات في مكان واحد يسهل الوصول إليه، كما يساهم هذا النهج في تحسين قابلية إعادة استخدام الشيفرة البرمجية مع نماذج أخرى في مكتبة المُحوِّلات.

إنشاء كائن ضبط النموذج

سنأخذ في المثال التالي، بعض الإعدادات أو الوسطاء من الصنف ResNet والتي نرغب في تعديلها. بعد ذلك، ستوفر عمليات الضبط المختلفة أنواعًا متنوعة من أصناف ResNet المحتملة. ثم سنُخزّن هذه الوسطاء بعد التحقق من صحتها.

from transformers import PretrainedConfig
from typing import List

class ResnetConfig(PretrainedConfig):
    model_type = "resnet"

    def __init__(
        self,
        block_type="bottleneck",
        layers: List[int] = [3, 4, 6, 3],
        num_classes: int = 1000,
        input_channels: int = 3,
        cardinality: int = 1,
        base_width: int = 64,
        stem_width: int = 64,
        stem_type: str = "",
        avg_down: bool = False,
        **kwargs,
    ):
        if block_type not in ["basic", "bottleneck"]:
            raise ValueError(f"`block_type` must be 'basic' or bottleneck', got {block_type}.")
        if stem_type not in ["", "deep", "deep-tiered"]:
            raise ValueError(f"`stem_type` must be '', 'deep' or 'deep-tiered', got {stem_type}.")

        self.block_type = block_type
        self.layers = layers
        self.num_classes = num_classes
        self.input_channels = input_channels
        self.cardinality = cardinality
        self.base_width = base_width
        self.stem_width = stem_width
        self.stem_type = stem_type
        self.avg_down = avg_down
        super().__init__(**kwargs)

الأمور الثلاثة المهمة التي يجب تذكرها عند كتابة الضبط الخاص بنا هي كالتالي:

  • يجب أن يرث الصنف المخصص ResnetConfig من الصنف الأب PretrainedConfig
  • يجب أن يقبل التابع __init__ من الصنف المخصص أي وسطاء kwargs
  • يجب تمرير هذه الوسطاء kwargs إلى الصنف الأب للتابع __init__

تعني الوراثة Inheritance التأكد من الحصول على جميع الوظائف من مكتبة المحوّلات Transformers، ويمثّل القيدان الآخران احتواء الصنف PretrainedConfig على حقول أكثر من الحقول التي نضبطها، ويجب أن يقبل ضبطنا كافة هذه الحقول ثم تُرسَل إلى الصنف الأب عند إعادة تحميل الضبط باستخدام التابع from_pretrained.

لا يُعَد تحديد السمة model_type للضبط الخاص بنا بالقيمة model_type="resnet"‎ هنا إلزاميًا، إلا إذا أردنا تسجيل نموذجنا في الأصناف التلقائية Auto Classes كما سنوضح لاحقًا. يمكننا بعد ذلك إنشاء وحفظ الضبط الخاص بنا بسهولة كما نفعل مع أي ضبط نموذج آخر للمكتبة. لاحظ المثال التالي الذي يوضّح كيفية إنشاء الضبط resnet50d وحفظه:

resnet50d_config = ResnetConfig(block_type="bottleneck", stem_width=32, stem_type="deep", avg_down=True)
resnet50d_config.save_pretrained("custom-resnet")

سيؤدي هذا لحفظ ملف بالاسم config.json ضمن المجلد custom-resnet، يمكننا بعدها إعادة تحميل ملف الضبط الخاص باستخدام التابع from_pretrained كما يلي:

resnet50d_config = ResnetConfig.from_pretrained("custom-resnet")

ويمكننا أيضًا استخدام أي تابع آخر من الصنف PretrainedConfig مثل التابع push_to_hub()‎ لرفع الضبط الخاص بنا إلى المستودع Hub مباشرة.

كتابة نموذج مخصص

أصبح لدينا ضبط مخصص لنموذجنا ResNet، ويمكننا الآن كتابة النموذج نفسه، حيث سنكتب نموذجين الأول يستخرج الميزات المخفية من مجموعة الصور مثل النموذج BertModel، والثاني لتصنيف الصور وفق الفئات المختلفة مثل النموذج BertForSequenceClassification.

لن نكتب نموذج كامل بل سنكتب فقط مغلِّف wrapper بسيط للنموذج للسهولة، سيكون بمثابة هيكل بسيط للنموذج يمكننا تمرير الإعدادات أو الضبط إليه. وقبل أن نكتب الصنف ResNet أو النموذج نفسه، يجب أن نحدد أنواع الكتل في النموذج مثل basic أو bottleneck، ونحدد كيفية بناء هذه الكتل أو الطبقات في النموذج. بمجرد تحديد هذه الأمور، سنستخدم الضبط الذي حددناه سابقًا لتمرير هذه الإعدادات إلى الصنف ResNet لإنشاء النموذج بناءً على هذه الإعدادات.

from transformers import PreTrainedModel
from timm.models.resnet import BasicBlock, Bottleneck, ResNet
from .configuration_resnet import ResnetConfig

BLOCK_MAPPING = {"basic": BasicBlock, "bottleneck": Bottleneck}

class ResnetModel(PreTrainedModel):
    config_class = ResnetConfig

    def __init__(self, config):
        super().__init__(config)
        block_layer = BLOCK_MAPPING[config.block_type]
        self.model = ResNet(
            block_layer,
            config.layers,
            num_classes=config.num_classes,
            in_chans=config.input_channels,
            cardinality=config.cardinality,
            base_width=config.base_width,
            stem_width=config.stem_width,
            stem_type=config.stem_type,
            avg_down=config.avg_down,
        )

    def forward(self, tensor):
         return self.model.forward_features(tensor)

الآن، سنعدّل التابع forward فقط بالنسبة للنموذج ResNet المخصص لتصنيف الصور، فهذا التابع يتعامل مع البيانات المدخلة، ويحدد كيف تتم معالجتها عبر طبقات النموذج للحصول على النتيجة المطلوبة، سنجري التعديل كما يلي:

import torch

class ResnetModelForImageClassification(PreTrainedModel):
    config_class = ResnetConfig

    def __init__(self, config):
        super().__init__(config)
        block_layer = BLOCK_MAPPING[config.block_type]
        self.model = ResNet(
            block_layer,
            config.layers,
            num_classes=config.num_classes,
            in_chans=config.input_channels,
            cardinality=config.cardinality,
            base_width=config.base_width,
            stem_width=config.stem_width,
            stem_type=config.stem_type,
            avg_down=config.avg_down,
        )

    def forward(self, tensor, labels=None):
        logits = self.model(tensor)
        if labels is not None:
            loss = torch.nn.cross_entropy(logits, labels)
            return {"loss": loss, "logits": logits}
        return {"logits": logits}

نلاحظ في كلتا الحالتين كيف ورثنا الصنف PreTrainedModel واستدعينا تهيئة الصنف الأب باستخدام الضبط config كما يحدث عندما نكتب وحدة torch.nn.Module عادية في PyTorch. ولا يُعَد السطر الذي يضبط config_class إلزاميًا، إلا إذا أردنا تسجيل نموذجنا في الأصناف التلقائية Auto Classes أي عندما نرغب بأن نتيح لمنصة Hugging Face تحديد النموذج تلقائيًا بناءً على الضبط كما سنوضح لاحقًا.

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

يمكن جعل نموذجنا يعيد أي مخرجات نريدها، ولكن ستؤدي إعادة قاموس Dictionary كما فعلنا مع الصنف ResnetModelForImageClassification مع تضمين الخسارة عند تمرير التسميات التوضيحية Labels إلى جعل نموذجك قابلًا للاستخدام مباشرة في الصنف Trainer. يُعدّ استخدام تنسيق خرج آخر جيدًا طالما أنك تخطط لاستخدام حلقة تدريب خاصة بك أو أي مكتبة أخرى للتدريب.

أصبح لدينا صنف النموذج الخاص بنا، فلننشئ الآن نموذجًا كما يلي:

resnet50d = ResnetModelForImageClassification(resnet50d_config)

يمكننا استخدام أي تابع من توابع الصنف PreTrainedModel مثل التابع save_pretrained()‎ أو push_to_hub()‎، حيث سنستخدم التابع الثاني في القسم التالي وسنرى كيفية دفع أوزان النموذج باستخدام الشيفرة البرمجية الخاصة بنموذجنا، ولكن لنحمّل أولًا بعض الأوزان المدرَّبة مسبقًا في نموذجنا.

يمكن أن ندرّب نموذجنا المخصّص على بياناتنا الخاصة في حالة استخدامه بشكل مخصص، ولكن سنستخدم في هذا المقال النسخة المدرَّبة مسبقًا من الضبط resnet50d، وبما أن نموذجنا يحتوي على مغلِّف فقط، فسيكون من السهل نقل هذه الأوزان كما يلي:

import timm

pretrained_model = timm.create_model("resnet50d", pretrained=True)
resnet50d.model.load_state_dict(pretrained_model.state_dict())

لنوضّح الآن كيفية التأكد من حفظ شيفرة النموذج البرمجية عند تنفيذ التابع save_pretrained()‎ أو push_to_hub()‎.

تسجيل النموذج في الأصناف التلقائية Auto Classes

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

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

from transformers import AutoConfig, AutoModel, AutoModelForImageClassification

AutoConfig.register("resnet", ResnetConfig)
AutoModel.register(ResnetConfig, ResnetModel)
AutoModelForImageClassification.register(ResnetConfig, ResnetModelForImageClassification)

نلاحظ أن الوسيط الأول المُستخدَم عند تسجيل ضبطنا المخصص في الصنف التلقائي AutoConfig يجب أن يتطابق مع السمة model_type لضبطنا المخصص، ويجب أن يتطابق الوسيط الأول المُستخدَم عند تسجيل النماذج المخصَّصة في أي صنف نموذج تلقائي مع السمة config_class لتلك النماذج.

إرسال الشيفرة البرمجية للمستودع

علينا التأكّد أولًا من تعريف نموذجنا الكامل في ملف بايثون ‎.py، حيث يمكن الاعتماد على الاستيراد النسبي لبعض الملفات الأخرى طالما أن جميع الملفات موجودة في المجلد نفسه، فالوحدات الفرعية لهذه الميزة غير مدعومة حتى الآن. سنعرّف في مثالنا ملف modeling_resnet.py وملف configuration_resnet.py في مجلد ضمن مجلد العمل الحالي resnet_model، ويحتوي ملف الضبط على الشيفرة البرمجية الخاصة بالصنف ResnetConfig، ويحتوي ملف النموذج على الشيفرة البرمجية الخاصة بالصنفين ResnetModel و ResnetModelForImageClassification.

.
└── resnet_model
    ├── __init__.py
    ├── configuration_resnet.py
    └── modeling_resnet.py

يمكن أن يكون الملف ‎__init__.py فارغًا، لكنه موجود لتتمكّن لغة بايثون من اكتشاف إمكانية استخدام resnet_model كوحدة Module مما يعني أنه يمكن استيراد المكونات والملفات من هذا المجلد في برامج بايثون أخرى.

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

ملاحظة2: تُعدّ واجهة التطبيقات البرمجية API هذه تجريبية وقد تحتوي على بعض التغييرات في الإصدارات اللاحقة.

بإمكاننا إعادة استخدام أو إنشاء صنف فرعي لضبط أو لنموذج موجود مسبقًا، ويمكن مشاركة نموذجنا مع المجتمع من خلال استيراد نموذج وضبط ResNet أولًا من الملفات التي أنشأناها كما يلي:

from resnet_model.configuration_resnet import ResnetConfig
from resnet_model.modeling_resnet import ResnetModel, ResnetModelForImageClassification

بعد ذلك، علينا إخبار المكتبة بأننا نريد نسخ ملفات الشيفرة البرمجية لتلك الكائنات عند استخدام التابع save_pretrained وتسجيلها بطريقة صحيحة في صنف تلقائي محدّد وخاصةً بالنسبة للنماذج، لذا ننفّذ التعليمات التالية:

ResnetConfig.register_for_auto_class()
ResnetModel.register_for_auto_class("AutoModel")
ResnetModelForImageClassification.register_for_auto_class("AutoModelForImageClassification")

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

عندما نريد جعل نموذجنا الخاص قابلاً للاستخدام في مكتبة Transformers وتسجيله ضمن النماذج التلقائية مثل AutoModel, AutoConfig، يجب استخدام التابعregister_for_auto_class()‎ لتسجيل النموذج بشكل صحيح، وإذا كنا نفضل استخدام الشيفرة البرمجية الموجودة على المستودع Hub من مستودع آخر، فلن تحتاج لاستدعاء هذا التابع. يمكننا تعديل الملف config.json مباشرة باستخدام البنية التالية في الحالات التي يوجد فيها أكثر من صنف تلقائي:

"auto_map": {     
    "AutoConfig": "<your-repo-name>--<config-name>",     
    "AutoModel": "<your-repo-name>--<config-name>",
    "AutoModelFor<Task>": "<your-repo-name>--<config-name>",    
},

لننشئ بعد ذلك الضبط والنماذج كما فعلنا سابقًا:

resnet50d_config = ResnetConfig(block_type="bottleneck", stem_width=32, stem_type="deep", avg_down=True)
resnet50d = ResnetModelForImageClassification(resnet50d_config)

pretrained_model = timm.create_model("resnet50d", pretrained=True)
resnet50d.model.load_state_dict(pretrained_model.state_dict())

لنتأكّد الآن من تسجيل الدخول لإرسال النموذج إلى المستودع Hub، لذا نشغّل الأمر التالي في الطرفية Terminal:

huggingface-cli login

أو نكتب من تطبيق المفكرة ما يلي:

from huggingface_hub import notebook_login

notebook_login()

يمكن بعد ذلك رفع النموذج إلى فضاء الأسماء Namespace الخاص بحسابنا على Hugging Face كما يلي:

resnet50d.push_to_hub("custom-resnet50d")

ترفع التعليمة النموذج resnet50d إلى المستودع Hugging Face Hub باسم  custom-resnet50d وتجعل النموذج متاحًا لاستخدامه مباشرة في المنصة Hugging Face. حيث تُنسَخ ملفات ‎.py للنموذج وللضبط بالإضافة إلى أوزان النموذج والضبط بتنسيق json في المجلد custom-resnet50d وستُرفَع النتيجة للمستودع Hub، ويمكننا التحقق من النتيجة في مستودع النماذج على منصة Huggingface.
وللمزيد حول طريقة الدفع إلى المستودع Hub ننصح بمطالعة مقال مشاركة نموذج ذكاء اصطناعي على منصة Hugging Face .

استخدام نموذج مع شيفرة برمجية مخصصة

يمكن استخدام أي ضبط أو نموذج أو مرمِّز Tokenizer مع ملفات الشيفرة البرمجية المخصَّصة في مستودعها باستخدام الأصناف التلقائية والتابع from_pretrained، حيث تُفحَص جميع الملفات والشيفرات البرمجية المرفوعة إلى المستودع Hub بحثًا عن البرامج الضارة، ولمزيد من التفاصيل يُنصَح بمطالعة توثيق أمان Hub، ويجب أيضًا مراجعة شيفرة النموذج والتحقق من كاتبها لتجنّب تنفيذ شيفرة برمجية ضارة.

سنضبط القيمة trust_remote_code=True لاستخدام نموذج مع شيفرة برمجية مخصصة كما يلي:

from transformers import AutoModelForImageClassification

model = AutoModelForImageClassification.from_pretrained("sgugger/custom-resnet50d", trust_remote_code=True)

يُفضَّل أيضًا تمرير قيمة تعمية الإيداع Commit Hash إلى سمة المراجعة revision للتأكّد من أن كاتب النماذج لم يُحدّث الشيفرة البرمجية ببعض الأسطر الجديدة الضارة.

commit_hash = "ed94a7c6247d8aedce4647f00f20de6875b5b292"
model = AutoModelForImageClassification.from_pretrained(
    "sgugger/custom-resnet50d", trust_remote_code=True, revision=commit_hash
)

نلاحظ وجود زر لنسخ قيمة تعمية الإيداع commit hash يمكننا من خلاله نسخ التعديل بسهولةعند تصفح سجل الإيداعات الخاص بمستودع النماذج الموجود على Hugging Face Hub.

الخلاصة

شرحنا في مقال اليوم كيفية كتابة نموذج مخصَّص وضبطه وطريقة استخدامه في مكتبة المحوّلات Transformers، كما شرحنا كيفية مشاركته مع المجتمع على مستودع Hugging Face Hub ليتمكّن أي شخص من استخدامه.

ترجمة -وبتصرّف- للقسم Building custom models من توثيقات Hugging Face.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...