بعد أن تعرّفت العصبون ودوال التنشيط، الخطوة التالية هي رؤية كيف تنتشر البيانات فعليًا عبر الشبكة من المدخل إلى المخرَج. هذه الصفحة تتعمّد عدم استخدام PyTorch — لتفهم ما يحدث قبل أن يغطّيه الإطار.
ما هو الانتشار الأمامي؟
Forward propagation = مسار البيانات من المدخل، عبر الطبقات المخفية، حتى المخرَج. عكس الانتشار العكسي الذي يُحدَّث فيه الأوزان.
كل طبقة تطبّق نفس العملية:
z = W @ a_prev + b # تحويل خطّي
a = activation(z) # دالة تنشيط
حيث a_prev هو مخرَج الطبقة السابقة (أو المدخل x نفسه في الطبقة الأولى).
بنية الشبكة في هذا الدرس
Input (2) → Hidden (3, ReLU) → Output (1, Sigmoid) → ŷ
- مدخل: ميزتان (
x₁,x₂). - طبقة مخفية واحدة بثلاث خلايا، تنشيط ReLU.
- مخرَج: خلية واحدة مع Sigmoid (احتمال ينتمي للصنف 1).
الأوزان والانحيازات (مُعرّفة يدويًا للتوضيح)
في الواقع، الأوزان تبدأ عشوائية وتُحدَّث بالتدرّج. هنا نُعيّنها يدويًا لنتمكّن من الحساب.
import numpy as np
# الأوزان والانحيازات
W1 = np.array([
[ 0.5, -0.3], # أوزان الخلية المخفية h₁
[-0.2, 0.8], # أوزان الخلية المخفية h₂
[ 0.4, 0.1], # أوزان الخلية المخفية h₃
])
b1 = np.array([0.1, -0.2, 0.0])
W2 = np.array([[0.6, 0.5, -0.4]]) # شكل (1, 3) — خلية مخرَج واحدة
b2 = np.array([0.05])
الأشكال مهمة:
| المصفوفة | الشكل | المعنى |
|---|---|---|
x | (2,) | ميزتان |
W1 | (3, 2) | 3 خلايا مخفية × 2 مدخلات |
b1 | (3,) | انحياز لكل خلية مخفية |
W2 | (1, 3) | خلية مخرَج واحدة × 3 مدخلات |
b2 | (1,) | انحياز المخرَج |
قاعدة شكل المصفوفات: إذا كانت
a_prevشكله(n,)وWشكله(m, n)، فالنتيجةW @ a_prevشكله(m,). هذا الترتيب يسمّى shape convention: الأوزان تُكتب بالشكل(out, in).
الطبقة الأولى (المخفية)
الحساب اليدوي
المُعطى: x = [2.0, 3.0].
الخلية المخفية h₁:
z₁ = (0.5 × 2.0) + (-0.3 × 3.0) + 0.1
= 1.0 + (-0.9) + 0.1
= 0.2
الخلية المخفية h₂:
z₂ = (-0.2 × 2.0) + (0.8 × 3.0) + (-0.2)
= -0.4 + 2.4 + (-0.2)
= 1.8
الخلية المخفية h₃:
z₃ = (0.4 × 2.0) + (0.1 × 3.0) + 0.0
= 0.8 + 0.3
= 1.1
الحساب بمصفوفات
x = np.array([2.0, 3.0])
z1 = W1 @ x + b1
print("z1 =", z1) # [0.2, 1.8, 1.1]
تطبيق ReLU (هنا كلها موجبة، فلا شيء يُقصّ):
a1 = np.maximum(0, z1)
print("a1 =", a1) # [0.2, 1.8, 1.1]
ملاحظة: مع مدخلات سالبة كافية لقيم ما قبل التنشيط، ستُرجع ReLU صفرًا. هنا كل
zᵢموجب فلا يحدث قصّ.
الطبقة الثانية (المخرَج)
الحساب اليدوي
z_out = (0.6 × 0.2) + (0.5 × 1.8) + (-0.4 × 1.1) + 0.05
= 0.12 + 0.90 + (-0.44) + 0.05
= 0.63
الحساب بمصفوفات
z2 = W2 @ a1 + b2
print("z2 =", z2) # [0.63]
تحويل لاحتمال عبر Sigmoid
ŷ = σ(0.63) = 1 / (1 + e^(-0.63)) ≈ 0.6525
a2 = 1.0 / (1.0 + np.exp(-z2))
print("a2 =", a2) # ≈ [0.6525]
الكود الكامل للانتشار الأمامي
import numpy as np
# المدخل
x = np.array([2.0, 3.0])
# أوزان الطبقة المخفية
W1 = np.array([
[ 0.5, -0.3],
[-0.2, 0.8],
[ 0.4, 0.1],
])
b1 = np.array([0.1, -0.2, 0.0])
# أوزان طبقة المخرَج
W2 = np.array([[0.6, 0.5, -0.4]])
b2 = np.array([0.05])
def relu(z):
return np.maximum(0, z)
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
# === الطبقة المخفية ===
z1 = W1 @ x + b1
a1 = relu(z1)
# === طبقة المخرَج ===
z2 = W2 @ a1 + b2
y_hat = sigmoid(z2)
print("z1 =", z1) # [0.2, 1.8, 1.1]
print("a1 =", a1) # [0.2, 1.8, 1.1]
print("z2 =", z2) # [0.63]
print("ŷ =", y_hat) # ≈ [0.6525]
ماذا عن دفعات (batches)؟
في الواقع لا نُمرّر عيّنة واحدة، بل دفعة (batch) لتجنّب ضوضاء التدرّج.
X shape: (batch_size, n_features)
# 4 عيّنات في وقت واحد
X = np.array([
[2.0, 3.0],
[1.0, 0.5],
[-1.0, 2.0],
[0.5, -0.5],
])
# الأوزان تبقى كما هي
z1_batch = X @ W1.T + b1 # لاحظ التبديل
a1_batch = relu(z1_batch)
z2_batch = a1_batch @ W2.T + b2
y_hat_batch = sigmoid(z2_batch)
print("شكل z1:", z1_batch.shape) # (4, 3)
print("شكل y_hat:", y_hat_batch.shape) # (4, 1)
print("احتمالات:\n", y_hat_batch)
ملاحظة الأشكال: مع
Xبالشكل(batch, features)، يصبحWبالشكل(out, in)كما اعتدنا، فيُحسبX @ W.T(تبديل W). المخرَج بالشكل(batch, out).
الشكل العام (Pseudocode)
أي شبكة عصبية تتكوّن من تكرار نفس العملية:
a⁰ = x # المدخل
for layer in [1, 2, ..., L]:
zˡ = Wˡ · a^(l-1) + bˡ # تحويل خطّي
aˡ = activationˡ(zˡ) # تنشيط
ŷ = a^L # المخرَج
ملخّص البُنى
| الرمز | المعنى |
|---|---|
x أو a⁰ | المدخل |
Wˡ | أوزان الطبقة l (شكل (out_l, in_l)) |
bˡ | انحياز الطبقة l (شكل (out_l,)) |
zˡ | pre-activation (قبل التنشيط) |
aˡ | activation (مخرَج الطبقة بعد التنشيط) |
ŷ أو a^L | تنبّؤ الشبكة |
أخطاء شائعة
- قلب شكل W: إذا كان
Wبالشكل(in, out)، يجب ضربه بترتيب مختلف. ثبّت قاعدة:Wبالشكل(out, in)، فالعملية دائمًاinput @ W.T + b. - نسيان التبديل للأوزان عند استخدام دفعات:
X @ WبدلX @ W.Tينتج شكلًا خاطئًا. - عدم تطابق الأشكال بين الطبقات:
W1ينتجa1بطول 3، فيجب أن يكونW2بالشكل(out, 3). القلم والورق قبل الكود. - افتراض أن ReLU ستُغيّر دائمًا النتيجة: إذا كانت كل
zموجبة، ReLU لا تُغيّر شيئًا. في هذه الحالة الشبكة تعمل كأنها خطّية في هذه المنطقة.
الخطوات التالية
- دوال الخسارة — كيف نقيس كم يخطئ هذا التنبّؤ
ŷ. - الانتشار العكسي — كيف نعرف أيّ وزن مسؤول عن الخطأ.
- Optimizers و Regularization — كيف نُحدِّث الأوزان بناءً على التدرّجات.
- دوال التنشيط — ما هي الدوال وما متى نستخدم كل واحدة.
🔍 في PyTorch، كل ما سبق يحدث داخل
model(x). سترى ذلك في PyTorch: Model و nn.Module.