الوحدة 2 — العصبون التكراري وحالته المخفية
انتهت الوحدة السابقة إلى تشخيصٍ واحد: يفتقر النموذج الكثيف إلى الذاكرة. تُقدّم هذه الوحدة الوحدةَ الأساس التي تحلّ ذلك، وتُبنى منها كلّ الشبكات التكرارية اللاحقة، من nn.RNN البسيط إلى LSTM وGRU.
صيغة الاستدعاء الذاتي
في كلّ خطوة زمنية ، يُحدِّث العصبون التكراري حالة مخفية من أمرين: مُدخَل تلك اللحظة ، وحالته السابقة .
الحدّ هو ما يُفرِّق هذه الوحدة عن العصبون العادي: تُحمَل ذاكرة الماضي في ، وتُدمج مع مُدخَل الحاضر في مجموع موزون واحد قبل التنشيط، وهو في الغالب. أمّا الأوزان و و فهي نفسها في كلّ خطوة، وهذا التقاسم مركزيّ: بدونه لكان عدد الوسائط متناسبًا مع طول المتتالية، ولانهار التعميم على الأطوال المتغيّرة.
بداية التسلسل تتطلّب حالة أوّليّة . الاصطلاح المألوف هو صفر مُوتّري، والمكتبات تفعل ذلك تلقائيًا؛ ويُمكن تعلّم حين تكون بداية المتتالية تحمل معنى ثابتًا (وضع افتراضي، مكان بادئ).
دفع الشبكة في الزمن
الأسلم لفهم الشبكة التكرارية أن نُصوّرها مدفوعةً في الزمن: يُنشَر العصبون على نسخة، واحدة لكلّ خطوة، تتّصل الواحدة بالتالية عبر الحالة . كلّ نسخة تستقبل مُدخَل خطوتها وتُنتج حالتها ومُخرَجها.
هذه القراءة تكشف حقيقة عميقة: الشبكة التكرارية هي شبكة عميقة جدًّا، عمقها هو طول المتتالية. متتالية من 168 خطوة تعني شبكة من 168 طبقة. لكنّها ليست شبكة كثيفة عادية بذلك العمق: فكلّ «طبقة» تستعمل الأوزان نفسها. ومن هنا تنبع فرصتها — عدد وسائط معقول — ومصيبتها — التدرّجات تعبر جداءات طويلة من المشتقّات، وهو ما ستكشفه الوحدة 4.
أشكال المُوتّرات، الاصطلاح الذي يجب حفظه
كلّ مكتبة تحترم اصطلاحًا واحدًا للشبكات التكرارية: (batch, temps, variables)، ويُختصر بـ(N, T, F). الرمز لعدد المتتاليات في الدفعة، و لطول المتتالية، و لعدد المتغيّرات في كلّ خطوة زمنية.
على الفيل الأحمر: دفعة من 32 نافذة أسبوعية على متغيّر وحيد kwh تُعطي شكل (32, 168, 1). وإن أضفنا الحرارة صار (32, 168, 2). الحالة المخفية بدورها لها شكل (N, hidden)، حيث hidden هو حجم المخفية الذي تختاره أنت.
import torch
import torch.nn as nn
rnn = nn.RNN(input_size=2, hidden_size=64, batch_first=True)
X = torch.randn(32, 168, 2) # (batch, temps, variables)
sorties, h_finale = rnn(X)
print(sorties.shape) # torch.Size([32, 168, 64])
print(h_finale.shape) # torch.Size([1, 32, 64])
الوسيط batch_first=True غير افتراضي وينبغي كتابته صراحة: بدونه ينتظر PyTorch (temps, batch, variables) ويعطي رسائل خطأ عصيّة. أمّا sorties فتحوي لكلّ خطوة زمنية، وh_finale تحوي فقط ، حالة النهاية.
return_sequences: مُخرَج واحد أم متتالية مُخرَجات
قرارٌ صغير في الظاهر، عميق في الأثر: هل نحتفظ بحالة كلّ خطوة أم بحالة النهاية وحدها؟ الاصطلاح في Keras اسمه صريح: return_sequences. في PyTorch، تُنتج nn.RNN الأمرين معًا وتختار أنت.
يتحدّد الاختيار بالمهمّة. لتصنيف جملة كاملة (تحليل مشاعر مثلًا)، حالة النهاية تكفي: هي تلخّص المتتالية كلّها. لتوسيم كلّ خطوة (تعريف الأسماء في نصّ، أو التنبّؤ بالساعة القادمة عند كلّ ساعة تدريب) لا بدّ من كامل المتتالية. في الفيل الأحمر، حين نتنبّأ بالساعة فقط، تكفي حالة النهاية؛ وحين نتنبّأ بالأربع والعشرين ساعة القادمة معًا، سنُعيد قراءة المتتالية بمُخرَج مُدمج، أو نُطعم مرمِّزًا وفاكّ ترميز كما في الوحدة 8.
# نموذج مبسط: من نافذة أسبوعية إلى تنبؤ ساعة واحدة.
class PredicteurUneHeure(nn.Module):
def __init__(self, n_vars=2, hidden=64):
super().__init__()
self.rnn = nn.RNN(n_vars, hidden, batch_first=True)
self.tete = nn.Linear(hidden, 1) # طبقة إخراج
def forward(self, x):
# x: (N, 168, 2). ننتقي h_T لأن التنبؤ يعني حالة النهاية.
_, h_T = self.rnn(x) # h_T شكل (1, N, hidden)
h_T = h_T.squeeze(0) # (N, hidden)
return self.tete(h_T) # (N, 1)
مصيدة كلاسيكية هنا: h_T يخرج بشكل (num_layers, N, hidden) حتى لو كانت num_layers=1، ومن ثَمّ استدعاء squeeze(0). نسيان هذا يُنتج طبقة Linear تعتقد أنّها تعالج طبقة ثانية غير موجودة، مع رسالة خطأ فارقة.
عدد الوسائط، وكيفية قراءته
لصيغة الاستدعاء عدد وسائط قابل للحساب في السطر ذاته. مع input_size=F وhidden_size=H:
مع و، هذا يعطي وسيطًا. أضف طبقة الإخراج ، وستحصل على شبكة صغيرة جدًّا، أخفّ بمراتب من نموذج كثيف يعالج نافذة مُسطَّحة من 168 خطوة. تشارك الأوزان ليس تفصيلًا هندسيًا بل ما يجعل التعميم إلى متتاليات جديدة ممكنًا.
هذه الصيغة تعمّم مباشرة إلى LSTM (بأربع كتل وسائط بدل واحدة، بوّابة لكلّ منها) وإلى GRU (بثلاث كتل). ستُقاس عليها الوحدتان 5 و6 حين نُوازن بين النموذجين بميزانية وسائط متكافئة.
أشيع الأخطاء في هذه الوحدة يظهر في الحقبة الأولى ورسالته غامضة: «شكل غير متوقّع». الحلّ عادةً في محاذاة الأبعاد، لا في المعمارية. عوّد نفسك على طباعة x.shape قبل وبعد كلّ طبقة تكرارية، وقارنها بما تنتظره الطبقة التالية. تخصيص خمس دقائق لهذا يوفّر ساعات من التنقيح على نموذج «صحيح لكنّه لا يتعلّم»، لأنّ محاور موتِّرك مقلوبة.
في الخلاصة
- الصيغة تُلخِّص الشبكة التكرارية؛ تقاسم الأوزان بين الخطوات مركزيّ ولا يُتفاوَض عليه.
- دفع الشبكة في الزمن يجعل من الشبكة التكرارية شبكة عميقة بعمقها = طول المتتالية، مع الأوزان نفسها في كلّ طبقة.
- الاصطلاح الكوني للأشكال هو
(batch, temps, variables)؛ اكتبbatch_first=Trueصراحة، وطباعة الأشكال قبل التدريب توفّر ساعات. - الاختيار بين حالة النهاية أو المتتالية يتحدّد بالمهمّة؛ عدد الوسائط صغير، وهو ما يفسّر قدرة الشبكة على التعميم على أطوال جديدة.
الوحدة التالية: كيف تُحسَب التدرّجات عبر الزمن، وما ثمن ذلك على متتالية طويلة.