كيفية تعديل أي نموذج من نماذج Transformers
توفر مكتبة 🤗 Transformers مجموعة من النماذج المسبقة التدريب والأدوات لمعالجة اللغات الطبيعية، والرؤية، وما إلى ذلك. على الرغم من أن هذه النماذج تغطي مجموعة واسعة من التطبيقات، فقد تواجه حالات استخدام لا تدعمها المكتبة بشكل افتراضي. يُمكن للتخصيص أن يفتح إمكانيات جديدة، مثل إضافة طبقات جديدة، أو تعديل البنية المعمارية، أو تحسين آليات الانتباه. سيُوضح لك هذا الدليل كيفية تعديل نماذج Transformers الموجودة لتلبية احتياجاتك المحددة. الشيء الرائع هو أنك لست بحاجة إلى الخروج من إطار عمل Transformers لإجراء هذه التغييرات. ي يمكنك تعديل النماذج مباشرةً في Transformers والاستفادة من الميزات مثل واجهة برمجة التطبيقات Trainer، و PreTrainedModel، والضبط الدقيق الفعال باستخدام أدوات مثل PEFT.
سنرشدك في هذا الدليل لكيفية تخصيص نماذج Transformers الموجودة لتلبية متطلباتك، دون فقدان مزايا الإطار. ستتعلم كيفية:
- تعديل بنية نموذج ما من خلال تغيير آلية الانتباه الخاصة به.
- تطبيق تقنيات مثل Low-Rank Adaptation (LoRA) على مكونات نموذج محددة.
نحن نشجعك على المساهمة باختراقاتك الخاصة ومشاركتها هنا مع المجتمع1
مثال: تعديل آلية الانتباه في نموذج Segment Anything (SAM)
نموذج Segment Anything (SAM) هو نموذج رائد في مجال تجزئة الصور. في تنفيذه الافتراضي، يستخدم SAM إسقاطًا مجمعًا للاستعلام والمفتاح والقيمة (qkv
) في آلية الانتباه الخاصة به. ومع ذلك، قد ترغب في ضبط مكونات محددة فقط من آلية الانتباه، مثل إسقاطات الاستعلام (q
) والقيمة (v
)، لتقليل عدد المعلمات القابلة للتدريب والموارد الحسابية المطلوبة.
الدافع
من خلال تقسيم الإسقاط المجمع qkv
إلى إسقاطات منفصلة q
و k
و v
، يمكنك تطبيق تقنيات مثل LoRA (Low-Rank Adaptation) على إسقاطي q
و v
فقط. يسمح لك هذا بما يلي:
- ضبط عدد أقل من المعلمات، مما يقلل من العبء الحسابي.
- تحقيق أداء أفضل من خلال التركيز على مكونات محددة.
- تجربة استراتيجيات تعديل مختلفة في آلية الانتباه.
التنفيذ
الخطوة 1: إنشاء فئة اهتمام مخصصة
بعد ذلك، قم بإنشاء فئة فرعية من فئة SamVisionAttention
الأصلية وعدلها لتضم إسقاطات q
و k
و v
منفصلة.
import torch
import torch.nn as nn
from transformers.models.sam.modeling_sam import SamVisionAttention
class SamVisionAttentionSplit(SamVisionAttention, nn.Module):
def __init__(self, config, window_size):
super().__init__(config, window_size)
del self.qkv
# إسقاطات منفصلة q و k و v
self.q = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias)
self.k = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias)
self.v = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias)
self._register_load_state_dict_pre_hook(self.split_q_k_v_load_hook)
def split_q_k_v_load_hook(self, state_dict, prefix, *args):
keys_to_delete = []
for key in list(state_dict.keys()):
if "qkv." in key:
# تقسيم q و k و v من الإسقاط المجمع
q, k, v = state_dict[key].chunk(3, dim=0)
# استبدال الإسقاطات الفردية q و k و v
state_dict[key.replace("qkv.", "q.")] = q
state_dict[key.replace("qkv.", "k.")] = k
state_dict[key.replace("qkv.", "v.")] = v
# وضع علامة على مفتاح qkv القديم للحذف
keys_to_delete.append(key)
# حذف مفاتيح qkv القديمة
for key in keys_to_delete:
del state_dict[key]
def forward(self, hidden_states: torch.Tensor, output_attentions=False) -> torch.Tensor:
batch_size, height, width, _ = hidden_states.shape
qkv_shapes = (batch_size * self.num_attention_heads, height * width, -1)
query = self.q(hidden_states).reshape((batch_size, height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes)
key = self.k(hidden_states).reshape((batch_size, height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes)
value = self.v(hidden_states).reshape((batch_size, height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes)
attn_weights = (query * self.scale) @ key.transpose(-2, -1)
if self.use_rel_pos:
attn_weights = self.add_decomposed_rel_pos(
attn_weights, query, self.rel_pos_h, self.rel_pos_w, (height, width), (height, width)
)
attn_weights = torch.nn.functional.softmax(attn_weights, dtype=torch.float32, dim=-1).to(query.dtype)
attn_probs = nn.functional.dropout(attn_weights, p=self.dropout, training=self.training)
attn_output = (attn_probs @ value).reshape(batch_size, self.num_attention_heads, height, width, -1)
attn_output = attn_output.permute(0, 2, 3, 1, 4).reshape(batch_size, height, width, -1)
attn_output = self.proj(attn_output)
if output_attentions:
outputs = (attn_output, attn_weights)
else:
outputs = (attn_output, None)
return outputs
الشرح:
- الإسقاطات المنفصلة: يتم إزالة الإسقاط المُجمع
qkv
، وإنشاء إسقاطات خطية منفصلةq
وk
وv
. - دالة استدعاء تحميل الأوزان: تقوم طريقة
_split_qkv_load_hook
بتقسيم أوزانqkv
المسبقة التدريب إلى أوزانq
وk
وv
منفصلة عند تحميل النموذج. يضمن هذا التوافق مع أي نموذج مسبق التدريب. - التنفيذ الأمامي: يتم حساب الاستعلامات والمفاتيح والقيم بشكل منفصل، وتستمر آلية الانتباه كالمعتاد.
الخطوة 2: استبدال فئة الانتباه الأصلية
استبدل فئة SamVisionAttention
الأصلية بفئتك المخصصة بحيث يستخدم النموذج آلية الانتباه المعدلة.
from transformers import SamModel
from transformers.models.sam import modeling_sam
# استبدال فئة الاهتمام في وحدة نمطية modeling_sam
modeling_sam.SamVisionAttention = SamVisionAttentionSplit
# تحميل نموذج SAM المسبق التدريب
model = SamModel.from_pretrained("facebook/sam-vit-base")
الشرح:
- استبدال الفئة: من خلال تعيين فئتك المخصصة إلى
modeling_sam.SamVisionAttention
، فإن أي حالات من فئةSamVisionAttention
في النموذج ستستخدم النسخة المعدلة. وبالتالي، عند استدعاءSamModel
، سيتم استخدامSamVisionAttentionSplit
المحددة حديثًا. - تحميل النموذج: يتم تحميل النموذج باستخدام
from_pretrained
، ويتم دمج آلية الانتباه المخصصة.
الخطوة 3: تطبيق LoRA على إسقاطات محددة
مع وجود إسقاطات q
و k
و v
منفصلة، يمكنك الآن تطبيق LoRA على مكونات محددة، مثل إسقاطات q
و v
.
from peft import LoraConfig, get_peft_model
config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q", "v"], # تطبيق LoRA على إسقاطات q و v
lora_dropout=0.1,
task_type="mask-generation"
)
# تطبيق LoRA على النموذج
model = get_peft_model(model, config)
الشرح:
- تكوين LoRA: تحدد
LoraConfig
المرتبةr
، وعامل القياسlora_alpha
، والوحدات المستهدفة ("q"
و"v"
)، ومعدل التخلي، ونوع المهمة. - تطبيق LoRA: تقوم دالة
get_peft_model
بتطبيق LoRA على الوحدات المحددة في النموذج. - تقليل المعلمات: من خلال التركيز على
q
وv
، فإنك تقلل عدد المعلمات القابلة للتدريب، مما يؤدي إلى تسريع التدريب وتقليل استخدام الذاكرة.
الخطوة 4: التحقق من عدد المعلمات القابلة للتدريب
من السهل التحقق من عدد المعلمات القابلة للتدريب ومعرفة تأثير تعديلك.
model.print_trainable_parameters()
الناتج المتوقع:
عدد المعلمات القابلة للتدريب: 608,256 || جميع المعلمات: 94,343,728 || نسبة المعلمات القابلة للتدريب: 0.6447
عدد المعلمات القابلة للتدريب: 912,384 || جميع المعلمات: 94,647,856 || نسبة المعلمات القابلة للتدريب: 0.9640 # مع k
المساهمة بابداعاتك الخاصة
يمكن لتعديل النماذج المسبقة التدريب أن يفتح آفاقًا جديدة للبحث والتطبيق. من خلال فهم وتعديل الآليات الداخلية للنماذج مثل SAM، يمكنك تخصيصها لتلبية احتياجاتك المحددة، وتحسين الأداء، وتجربة أفكار جديدة.
إذا قمت بتطوير تعديﻻتك الخاصة لنماذج Transformers وترغب في مشاركتها، ففكر في المساهمة في هذه الوثيقة.
- إنشاء طلب سحب (Pull Request): شارك تغييراتك وتحسيناتك في التعليمات البرمجية مباشرة في المستودع.
- كتابة التوثيق: قدم تفسيرات وأمثلة واضحة لتعديلاتك.
- التفاعل مع المجتمع: ناقش أفكارك واحصل على تعليقات من المطورين والباحثين الآخرين من خلال فتح مشكلة.