الطبقات والتطبيع
درس 4 من 8
4الطبقات والتطبيع
الصورة الكبيرةالشبكة العصبية مكدّس من أربع قطع تتكرّر: طبقة خطية تخلط المدخلات، ودالة تنشيط تكسر الخطية، وإسقاط يمنع الحفظ، وتطبيع يُبقي الأرقام في مدى معقول. افهم وظيفة كل قطعة، وستقرأ أي معمارية كجملة.
ماذا تشاهد في الرسم: المدخل يمرّ على أربع قطع بالترتيب: خلط خطي، ثم ReLU تكسر الخطية، ثم إسقاط يمنع الحفظ، ثم خلط خطي إلى 10 فئات. القطعة المميّزة هي ReLU، فبدونها تساوي الطبقتان طبقة خطية واحدة.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
الشبكة العصبية طوابق متراكبة. كل طابق يأخذ الأرقام من الطابق الذي تحته ويخلطها بطريقة تعلّمها، ثم يقرر ما يمرر للأعلى. وبعض الطوابق وظيفتها التنظيم فقط: تُبقي الأرقام في حجم معقول، أو تُطفئ بعض الخلايا عشوائيًا حتى لا يعتمد النموذج على خلية واحدة.
nn.Linear تحويل خطي، ودالة التنشيط مثل ReLU تضيف اللاخطية التي بدونها تنهار الطبقات إلى طبقة واحدة. Dropout يطفئ نسبة من الوحدات أثناء التدريب فيقلل الإفراط في التعلّم، وBatchNorm وLayerNorm يثبّتان توزيع التنشيطات فيسرّعان التدريب ويستقرانه.
قبل أن تبدأ
📈الانحدار الخطي
تعرف y = Wx + b، فالطبقة الخطية هي نفسها لكن بمخرجات كثيرة.
⚙️حلقة تدريب بيدك
تعرف الأسطر الخمسة، فهنا تتعلّم ما داخل model في السطر الأول.
الفكرة الجوهرية
كل شبكة تُبنى من أربع قطع متكرّرة. في هذا الدرس تعدّ معاملات الطبقة الخطية، ثم ترى لماذا نحتاج دالة تنشيط، ثم تجرّب الإسقاط في وضعيه، ثم تقارن نوعي التطبيع.
1الطبقة الخطية: مزيج موزون
الطبقة الخطية تضرب المدخل في مصفوفة أوزان وتضيف انحيازًا، فكل مخرج مزيج موزون من كل المدخلات. انظر إلى الرسم: كل خلية في اليمين متّصلة بكل خلية في اليسار، وكل خط وزن.
لذلك معاملاتها in × out + out. وكل ما يسمّى طبقة «كثيفة» أو «متّصلة بالكامل» هو هذه الطبقة.
هذا الكود يحتاج PyTorch على جهازك، فالناتج المعروض مسجَّل من تشغيل حقيقي. ثبّته بـpip install torch وشغّله بنفسك.
from torch import nn
model = nn.Sequential(
nn.Linear(784, 128),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(128, 10),
)
for name, p in model.named_parameters():
print(f"{name:10} {tuple(p.shape)}")
print("total:", sum(p.numel() for p in model.parameters()))
Linear(784, 128) فيها مصفوفة 128 × 784 أي 100352 وزنًا، و128 انحيازًا. الطبقة الثانية 10 × 128 + 10. المجموع 101770. ReLU وDropout بلا أي معاملات. عدّ المعاملات بيدك مرة لكل معمارية جديدة، فهو يخبرك بالذاكرة وبخطر الحفظ.
2دالة التنشيط تكسر الخطية
عشر طبقات خطية بلا شيء بينها تساوي طبقة خطية واحدة. دالة التنشيط هي ما يسمح للشبكة بتعلّم المنحنيات. ReLU تصفّر السالب وتمرّر الموجب، وGELU نسخة ناعمة منها في أغلب المحوّلات.
import numpy as np
z = np.array([-3.0, -0.5, 0.0, 0.5, 3.0])
relu = np.maximum(0, z)
sigmoid = 1 / (1 + np.exp(-z))
tanh = np.tanh(z)
print("z ", z)
print("relu ", relu)
print("sigmoid", sigmoid.round(3))
print("tanh ", tanh.round(3))
print("sigmoid slope at 3:", round(float(sigmoid[-1] * (1 - sigmoid[-1])), 3))
السيغمويد عند 3 تعطي 0.953، وميلها هناك 0.045 فقط. في شبكة عميقة، الميل يُضرب بهذا الرقم الصغير في كل طبقة، فيختفي قبل أن يصل للطبقات الأولى. هذا هو «تلاشي الميل»، وسبب أن ReLU، التي ميلها 1 لكل قيمة موجبة، حلّت محل السيغمويد في الطبقات المخفية.
3الإسقاط ووضعا التدريب والتقييم
الإسقاط يصفّر عشوائيًا نسبة من الخلايا أثناء التدريب فقط، فلا تعتمد الشبكة على خلية واحدة. النسب المعتادة من 0.1 إلى 0.5. انظر إلى الرسم: ثمانية آحاد بعد Dropout(0.5) في التدريب.
import torch
from torch import nn
torch.manual_seed(0)
drop = nn.Dropout(p=0.5)
x = torch.ones(8)
drop.train()
print("train:", drop(x))
drop.eval()
print("eval: ", drop(x))
في التدريب، نصف القيم صارت صفرًا والباقي صار 2، لا 1. PyTorch يضرب الباقي في 1 / (1 − p) حتى يبقى المتوسط كما هو. في التقييم، كل شيء يمر كما هو. لو نسيت model.eval() لكانت تنبّؤاتك عشوائية جزئيًا في كل مرة.
بعض الطبقات تتصرّف بشكل مختلف بين التدريب والتقييم. لذلك تستدعي model.train() قبل التدريب وmodel.eval() قبل التقييم.
4تطبيع الدفعة مقابل تطبيع الطبقة
التطبيع يُبقي مدى المخرجات ثابتًا بين الطبقات، فيسمح بمعدّلات تعلّم أكبر وشبكات أعمق. الفرق بين النوعين في الاتجاه. انظر إلى الرسم: تطبيع الدفعة يأخذ عمودًا واحدًا في كل مرة، أي سمة واحدة عبر كل الأمثلة.
ثلاثة أمثلة، ولكل مثال ثلاث سمات بمقاييس مختلفة جدًّا. احسب التطبيعين بـNumPy لترى الفرق في الاتجاه.
import numpy as np
batch = np.array([[1.0, 200.0, 3000.0],
[2.0, 180.0, 1000.0],
[3.0, 220.0, 2000.0]])
col_mean, col_std = batch.mean(axis=0), batch.std(axis=0)
batch_norm = (batch - col_mean) / col_std
row_mean, row_std = batch.mean(axis=1, keepdims=True), batch.std(axis=1, keepdims=True)
layer_norm = (batch - row_mean) / row_std
print("batch norm (per column):\n", batch_norm.round(2))
print("layer norm (per row):\n", layer_norm.round(2))
تطبيع الدفعة جعل كل عمود متوسطه صفر، فالسمات صارت قابلة للمقارنة. تطبيع الطبقة جعل كل صف متوسطه صفر، لكن العمود الثالث بقي الأكبر في كل صف لأن قيمه بالآلاف. في المحوّلات تكون السمات أبعاد تضمين متشابهة المعنى، فتطبيع الطبقة مناسب، ولا يعتمد على حجم الدفعة، فيعمل حتى بمثال واحد.
تطبيع الدفعة شائع في الصور، وتطبيع الطبقة في المحوّلات. وتطبيع الدفعة أيضًا يتصرّف بشكل مختلف في وضع التقييم، فيستخدم إحصاءات محفوظة بدل إحصاءات الدفعة.
كيف يعمل، خطوة بخطوة: كيف يمرّ مدخل في مكدّس الطبقات
Linear(784, 128) يضرب المدخل في مصفوفة 128 × 784 ويضيف انحيازًا، فكل مخرج مزيج موزون من كل المدخلات.
ReLU تصفّر القيم السالبة وتمرّر الموجبة، فتسمح للشبكة بتعلّم المنحنيات.
Dropout(0.2) يصفّر عشوائيًا خُمس الخلايا في التدريب فقط، ويكبّر الباقي حتى يبقى المتوسط ثابتًا.
Linear(128, 10) يخرج رقمًا لكل فئة من العشر، وفي التقييم تستدعي model.eval() فيُطفأ الإسقاط.
مثال محلول
طبقة خطية واحدة هي انحدار خطي: y = Wx + b. لو كدّست عشر طبقات خطية بلا شيء بينها، لكان الناتج ما زال خطيًا، لأن تركيب دوال خطية دالة خطية. دالة التنشيط هي ما يسمح للشبكة بتعلّم المنحنيات.
model.eval() عندما يحتوي على Dropout أو BatchNorm. الأرقام تتغيّر بين تشغيلين على نفس البيانات، وتبدو أسوأ من الحقيقة، وقد تقضي يومًا في البحث عن خلل في البيانات.
جرّب بلا كود
Linear(10, 4) ثم ReLU ثم Linear(4, 2). كم معاملًا فيها؟ الجواب: 10 × 4 + 4 = 44، ثم 4 × 2 + 2 = 10، وReLU لا معاملات لها، فالمجموع 54.خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| كل طبقة إضافية تزيد قدرة الشبكة. | ثلاث طبقات خطية بلا تنشيط بينها تساوي طبقة خطية واحدة. المعاملات الإضافية تكلّف ولا تضيف قدرة. |
| Dropout و التطبيع يحسّنان أي شبكة، فأضفهما من البداية. | كل قطعة تحلّ مشكلة محدّدة. أضف Dropout حين ترى فجوة بين التدريب والتحقّق، والتطبيع حين يصير التدريب العميق غير مستقر. |
| السيغمويد جيد في الطبقات المخفية لأنه ناعم. | ميله عند 3 هو 0.045 فقط، والضرب في رقم صغير كهذا في كل طبقة يجعل الميل يختفي. لهذا حلّت ReLU محلّه. |
جرّب بنفسك
ما عدد معاملات nn.Linear(256, 64)؟
مصفوفة الأوزان 64 × 256 = 16384، والانحياز 64 قيمة، فالمجموع 16448. الخيار الأول ينسى الانحياز.
نتائج التقييم تتغيّر قليلًا في كل تشغيل على نفس بيانات التحقّق ونفس الأوزان. ما الأرجح؟
الإسقاط عشوائي في وضع التدريب، فكل تشغيل يصفّر خلايا مختلفة. model.eval() يوقفه ويجعل النتائج ثابتة. ترتيب القراءة لا يغيّر متوسط الدقة على كل البيانات.
بطاقات مراجعة سريعة
in × out + out.eval.تحقق من فهمك
شبكة من ثلاث طبقات Linear متتالية بلا أي تنشيط بينها. كم «طبقة» تتعلّم فعليًا؟
طبقة خطية واحدة. W3(W2(W1x + b1) + b2) + b3 يمكن كتابتها Wx + b بمصفوفة واحدة W = W3·W2·W1. المعاملات الإضافية لا تضيف أي قدرة تعبيرية، فقط تكلفة ووقت. التنشيط بين الطبقات هو ما يجعل العمق مفيدًا.
في التدريب مع Dropout(p=0.5) صارت القيم الباقية 2 بدل 1. لماذا؟
لأن PyTorch يكبّر الباقي بمعامل 1 / (1 − p) حتى يبقى المتوسط كما هو، فلا يتغيّر مدى المخرجات بين التدريب والتقييم.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي4
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.