التعلّم المنقول
درس 3 من 7
3التعلّم المنقول
الصورة الكبيرةنموذج رأى مليون صورة تعلّم مسبقًا كيف يرى الحواف والقوام والأشكال. التعلّم المنقول يستعير هذا الإبصار ويدرّب فقط الجزء الأخير على فئاتك. هذا ما يجعل مئات الصور تكفي بدل مئات الآلاف، لكن مقدار الفائدة يعتمد على ما رآه النموذج الأصلي، ويجب أن تقيسه.
ماذا تشاهد في الرسم: الصورة تمرّ للأمام عبر كل الطبقات، لكن الأوزان التي تتغيّر هي الرأس الجديد وحده بفئاته الأربع. العمود الفقري يعطي «النظر» الذي تعلّمه من ImageNet، والرأس يتعلّم فئاتك.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
بدل أن تعلّم طفلًا أن يرى من الصفر، تأخذ رسامًا محترفًا يعرف الخطوط والأشكال وتعلّمه شيئًا جديدًا واحدًا: أن يميّز أنواع التمر. التعلّم المنقول أن تأخذ نموذجًا تدرّب على ملايين الصور، وتغيّر جزءه الأخير فقط ليناسب مهمتك.
تحمّل نموذجًا مدرّبًا مسبقًا مثل ResNet وتجمّد طبقاته requires_grad=False، وتستبدل الرأس بطبقة Linear بعدد فئاتك وتدرّبها وحدها. ثم قد تفك تجميد الطبقات العليا بمعدل تعلّم صغير fine-tuning، وتستخدم تطبيع الصور نفسه الذي تدرّب عليه.
قبل أن تبدأ
🔲الالتفاف
تعرف أن الطبقات الأولى ترى حوافًا والعميقة ترى أجزاء أشياء، ولهذا العمود الفقري عام.
🔁المشتقات التلقائية
تعرف requires_grad، فالتجميد هو إطفاؤه على أوزان العمود الفقري.
الفكرة الجوهرية
النقل يستعير بصر نموذج رأى ملايين الصور. تفهم هنا جزأي النموذج، ثم تجمّد واحدًا وتستبدل الآخر، ثم تقيس الفائدة بصدق، ثم تختار بين ثلاث استراتيجيات.
1العمود الفقري والرأس
النموذج المدرّب جزآن. العمود الفقري طبقات التفافية تحوّل الصورة إلى متّجه خصائص، والرأس طبقة خطية أخيرة تحوّل الخصائص إلى أصناف.
انظر إلى الرسم: في ResNet18 العمود الفقري هو كل الأوزان تقريبًا. هو العام الذي يعرف كيف يرى، والرأس خاص بالمهمة الأصلية.
2التجميد واستبدال الرأس
التجميد يعني إيقاف تدريب طبقات العمود الفقري بـ requires_grad = False، فتبقى أوزانها ثابتة. التدريب يصبح دقائق لا ساعات، حتى على CPU.
ثم نضع رأسًا جديدًا بعدد أصنافنا. في ResNet اسمه model.fc، وفي نماذج أخرى model.classifier.
هذا الشكل الحقيقي للتعلّم المنقول. يحتاج تنزيل أوزان ResNet مدرّبة على ImageNet، فهو للقراءة هنا.
from torch import nn
from torchvision.models import ResNet18_Weights, resnet18
model = resnet18(weights=ResNet18_Weights.DEFAULT)
for p in model.parameters():
p.requires_grad = False # freeze the backbone
model.fc = nn.Linear(model.fc.in_features, 4) # new head for 4 classes
optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-3)
تجميد كل شيء، ثم استبدال الرأس fc بطبقة جديدة لعدد فئاتك. الطبقة الجديدة تُنشأ بعد التجميد فتكون قابلة للتدريب تلقائيًا، والمحسّن يحصل على معاملاتها فقط. 512 سمة في 4 فئات: ألفا معامل تقريبًا تتدرّب، من أصل 11 مليونًا.
3قِس عبر عدة بذور
فائدة النقل تعتمد على ما رآه النموذج الأصلي. لذلك نشغّل كل طريقة بخمس بذور ونقارن المتوسط والمدى.
انظر إلى الرسم: الأعمدة الثلاثة متقاربة جدًا. الفرق بينها أصغر من تأرجح البذور.
بلا إنترنت لا نستطيع تنزيل أوزان ImageNet هنا، فنصنع «تدريبًا مسبقًا» صغيرًا بأنفسنا: نموذج يتعلّم الأرقام 0 إلى 4 بكل بياناتها، ثم ننقله إلى الأرقام 5 إلى 9 بـ25 صورة فقط. ثلاث طرق، وخمس بذور لكل طريقة.
import statistics
import torch
from sklearn.datasets import load_digits
from torch import nn
digits = load_digits()
X = torch.tensor(digits.images, dtype=torch.float32).unsqueeze(1) / 16
y = torch.tensor(digits.target)
order = torch.randperm(len(y), generator=torch.Generator().manual_seed(0))
X, y = X[order], y[order]
source = y < 5 # "pretraining" task: digits 0-4, all labels
X_src, y_src = X[source], y[source]
X_tgt, y_tgt = X[~source], y[~source] - 5 # new task: digits 5-9
X_few, y_few = X_tgt[:25], y_tgt[:25] # only 25 labelled images
X_test, y_test = X_tgt[300:], y_tgt[300:]
def backbone():
return nn.Sequential(
nn.Conv2d(1, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(16, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Flatten(),
)
def train(model, X, y, params):
opt = torch.optim.Adam(params, lr=0.01)
for _ in range(150):
loss = nn.functional.cross_entropy(model(X), y)
opt.zero_grad()
loss.backward()
opt.step()
def accuracy(model):
with torch.no_grad():
return (model(X_test).argmax(1) == y_test).float().mean().item()
results = {"scratch": [], "frozen": [], "fine-tuned": []}
for seed in range(5):
torch.manual_seed(seed)
pretrained = backbone()
train(nn.Sequential(pretrained, nn.Linear(128, 5)), X_src, y_src, pretrained.parameters())
scratch = nn.Sequential(backbone(), nn.Linear(128, 5))
train(scratch, X_few, y_few, scratch.parameters())
results["scratch"].append(accuracy(scratch))
frozen_body = backbone()
frozen_body.load_state_dict(pretrained.state_dict())
frozen_body.requires_grad_(False)
frozen = nn.Sequential(frozen_body, nn.Linear(128, 5))
train(frozen, X_few, y_few, frozen[1].parameters())
results["frozen"].append(accuracy(frozen))
tuned_body = backbone()
tuned_body.load_state_dict(pretrained.state_dict())
tuned = nn.Sequential(tuned_body, nn.Linear(128, 5))
train(tuned, X_few, y_few, tuned.parameters())
results["fine-tuned"].append(accuracy(tuned))
for name, accs in results.items():
print(f"{name:10} mean {statistics.mean(accs):.3f} min {min(accs):.3f} max {max(accs):.3f}")
النتيجة الصادقة: الفروق صغيرة وتقع داخل مدى البذور. المجمَّد أعلى قليلًا في المتوسط وأكثر استقرارًا (أسوأ بذرة 87.6% مقابل 85.1%)، والضبط الكامل على 25 صورة أسوأ قليلًا لأنه يفسد السمات ويحفظ. السبب أن «التدريب المسبق» هنا 900 صورة صغيرة فقط، والأرقام 8 في 8 سهلة يتعلّمها نموذج صغير من الصفر. مع ImageNet (1.2 مليون صورة متنوعة) ومسألة صور حقيقية، يصير الفرق عادة عشرات النقاط المئوية. الدرس العملي: قارن عبر بذور، ولا تفترض أن النقل يفوز دائمًا.
4ثلاث استراتيجيات ومعدّلات متدرّجة
ابدأ دائمًا بتجميد كل العمود الفقري، فهو خط الأساس. إن كانت البيانات كافية نوعًا ما افتح آخر كتلة، وإن كانت كثيرة أو مختلفة جدًا، مثل صور الأشعة، اضبط النموذج كله.
انظر إلى الرسم: كلما زادت بياناتك تقدّمت خطوة. وعند الضبط الكامل تعطي كل طبقة معدّل تعلّم مختلفًا.
optimizer = torch.optim.AdamW([
{"params": model.layer1.parameters(), "lr": 1e-5},
{"params": model.layer2.parameters(), "lr": 3e-5},
{"params": model.layer3.parameters(), "lr": 1e-4},
{"params": model.layer4.parameters(), "lr": 3e-4},
{"params": model.fc.parameters(), "lr": 1e-3},
])
كلما اقتربت الطبقة من المدخل، صغر معدّلها. الطبقات الأولى تعرف الحواف، وهي تقريبًا نفسها في كل الصور، فلا تريد تغييرها كثيرًا. الطبقات الأخيرة أكثر تخصّصًا بالمسألة الأصلية، فتحتاج تعديلًا أكبر.
كيف يعمل، خطوة بخطوة: كيف تنقل نموذجًا مدرّبًا إلى فئاتك
حمّل resnet18 بأوزان ImageNet، وهي تعرف الحواف والقوام والأشكال.
جمّد كل المعاملات بـ requires_grad = False.
استبدل model.fc بطبقة Linear جديدة بعدد فئاتك، وهي قابلة للتدريب لأنها أُنشئت بعد التجميد.
أعطِ المحسّن معاملات الرأس فقط ودرّب، ثم قارن مع التدريب من الصفر عبر عدة بذور.
مثال محلول
النموذج المدرّب قسمان: عمود فقري (الطبقات الالتفافية) يحوّل الصورة إلى متجه سمات، ورأس (طبقة خطية أخيرة) يحوّل السمات إلى فئات. الرأس خاص بالمسألة الأصلية، أما العمود الفقري فعام.
جرّب بلا كود
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| التعلّم المنقول يتفوّق دائمًا وبفارق كبير. | في تجربة الدرس الصغيرة كانت الفروق داخل تذبذب البذور. الفارق الكبير يأتي حين رأى النموذج الأصلي بيانات أكثر وأكثر تنوّعًا بكثير مليس عندك. |
| ضبط كل النموذج أفضل دائمًا من تجميد العمود الفقري. | على 25 صورة كان الضبط الكامل أسوأ قليلًا لأنه أفسد السمات وحفظ الأمثلة. مع بيانات قليلة جدًّا يكون التجميد غالبًا أأمن. |
| التجميد يجعل كل حساب أرخص. | يوفّر حساب ميول العمود الفقري فقط، والمرور الأمامي يبقى كاملًا لكل صورة. لتوفير أكثر استخرج السمات مرة واحدة واحفظها. |
جرّب بنفسك
جمّدت العمود الفقري، واستبدلت الرأس، لكن التدريب بطيء كأن كل النموذج يتدرّب. ما الأرجح؟
التجميد يلغي حساب الميول للعمود الفقري، وهذا نصف التكلفة تقريبًا، لكن الصورة ما زالت تمر عبره أمامًا في كل دورة. الحل: استخرج السمات مرة واحدة واحفظها، ثم درّب الرأس على السمات المحفوظة مباشرة.
طريقة A متوسطها 88.9% بمدى 87.6 إلى 92.3، وطريقة B متوسطها 88.3% بمدى 85.1 إلى 90.8. ماذا تستنتج؟
فرق 0.6 نقطة بينما كل طريقة تتذبذب أكثر من 4 نقاط بين البذور ليس دليلًا على تفوّق. لكن الحد الأدنى الأعلى لـA معلومة مفيدة: إن كان أسوأ احتمال يهمّك، فـA أأمن. لحسم المتوسط تحتاج بذورًا أكثر.
بطاقات مراجعة سريعة
requires_grad = False، فلا تتغيّر أوزانها، ويتدرّب الرأس فقط. أسرع وأرخص وأقل عرضة للحفظ.تحقق من فهمك
عندك 300 صورة أشعة صدر، ونموذج مدرّب على ImageNet. لماذا قد لا يكفي تجميد كل العمود الفقري هنا؟
لأن صور الأشعة بعيدة جدًّا عن صور ImageNet: رمادية، وأنماطها المهمة قوام دقيق في الأنسجة لا أشياء وألوان. الطبقات الأولى (الحواف) ما زالت مفيدة، لكن الطبقات العميقة تعلّمت «أذن قطة» و«عجلة سيارة» وهذه لا تفيد. الحل الشائع فكّ تجميد الكتل الأخيرة بمعدّل صغير، مع توسيع البيانات، وقياس كل خيار على تحقّق منفصل.
عند ضبط كل النموذج، لماذا تعطي layer1 معدّل 1e-5 والرأس 1e-3؟
لأن الطبقات الأولى تعرف الحواف، وهي متشابهة في كل الصور فلا تريد تغييرها كثيرًا. الرأس جديد ومخصّص لمسألتك فيحتاج تعديلًا أكبر.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.