انتقل إلى المحتوى الرئيسي

الوحدة 4 — التدرّجات المتلاشية والمتفجّرة

اختُتمت الوحدة السابقة بجداء طويل من العوامل يعبر الزمن من hTh_T إلى h1h_1. تُبيّن هذه الوحدة أنّ هذا الجداء يُغلَّب فيه بسرعة أحد سيناريوهين مُدمِّرَين للتعلّم، وأنّ اسمهما ليس مفاجأة: التدرّجات تتلاشى أو تنفجر. اسم LSTM في العنوان تلميح إلى الحلّ: لا يقتصر الأمر على الحيل، بل على تغيير المعمارية.

الأصل: جداء يعاقبة

مشتقّة الحالة عند الخطوة tt بالنسبة إلى الحالة عند خطوة سابقة s<ts < t هي جداء لمصفوفات يعقوبية:

hths=k=s+1thkhk1=k=s+1tWhdiag ⁣(f(zk))\frac{\partial h_t}{\partial h_s} = \prod_{k=s+1}^{t} \frac{\partial h_k}{\partial h_{k-1}} = \prod_{k=s+1}^{t} W_h^{\top} \operatorname{diag}\!\left(f'(z_k)\right)

هذا الجداء يتألّف من tst - s مصفوفة، كلّ منها تتضمّن الأوزان المشتركة WhW_h^{\top} والاشتقاق النقطي للتنشيط. وإذا كانت النورمة الطيفية (أكبر قيمة مفردة) لكلّ عامل مساوية لـσ\sigma، فنورمة الجداء تسعى إلى σts\sigma^{t-s}.

مشتقّة tanh\tanh لا تتجاوز 1، وتُتلى بواحدٍ فقط في المبدأ. فإن كانت أعظم قيمة مفردة لـWhW_h أقلّ من 1 بقليل، أخذنا σ0.9\sigma \approx 0.9 ولنَقل ts=50t-s = 50: يعطي 0.9500.0050.9^{50} \approx 0.005. التدرّج المُنتقل من الخطوة 50 إلى الخطوة الأولى قد اختفى. أمّا إذا كان σ=1.1\sigma = 1.1: يعطي 1.1501171.1^{50} \approx 117، والتدرّج قد انفجر بالمعنى الحرفي.

هذان ليسا حالتين نادرتين. هما السلوك الرئيسي: التوازن بينهما يحدث فقط في نافذة رفيعة جدًّا حول σ=1\sigma = 1، والتدريب يخرج منها في أوّل خطوات.

برهان عددي على 50 خطوة

نستعير نموذج الوحدة السابقة على الاستهلاك الكهربائي، لكنّنا نُوسِّع نافذة المُدخَل إلى 50 يومًا (1200 ساعة). النموذج بسيط: nn.RNN بحالة مخفية 32، ومُخرَج واحد يُقارَن بقيمة الساعة القادمة.

import torch
import torch.nn as nn

torch.manual_seed(0)
T, H = 50, 32
rnn = nn.RNN(input_size=1, hidden_size=H, batch_first=True)

X = torch.randn(1, T, 1, requires_grad=True)
sorties, _ = rnn(X)
perte = sorties[:, -1, :].sum() # نستخدم الخطوة الأخيرة فقط
perte.backward()

grads_par_pas = X.grad.squeeze().abs() # حجم التدرج عند كل خطوة زمنية
print(grads_par_pas[[0, 10, 20, 30, 40, 49]])
# tensor([1.8e-6, 2.9e-5, 4.1e-4, 6.2e-3, 8.5e-2, 5.7e-1])

الأرقام تتحدّث عن نفسها. عند الخطوة 49 (الأقرب إلى المُخرَج) التدرّج معتدل. عند الخطوة الأولى، أصغر بستّ مراتب. الشبكة لا ترى المُدخلات البعيدة، ومهما تدرّبت لن تربطها بالمُخرَج. مسألة الاستهلاك الأسبوعي مُغلقة أمام RNN البسيط.

مسّ للمقارنة، إن استبدلنا nn.RNN بـnn.LSTM مع الأوزان نفسها، فارق الأحجام يتقلّص إلى مرتبة أو مرتبتين بدل ستّ. هذا ليس تفصيلًا: هو ما يُبرِّر وجود المعمارية القادمة في الوحدة 5.

قصّ التدرّج، الحلّ الأوّل للانفجار

انفجار التدرّج له عَرَض واضح: NaN في الخسارة بعد بضع خطوات، أو تذبذبات عنيفة. الحلّ اسمه قصّ التدرّج: نضع سقفًا مطلقًا على نورمته قبل تحديث الأوزان.

optimiseur.zero_grad()
perte.backward()
torch.nn.utils.clip_grad_norm_(modele.parameters(), max_norm=1.0)
optimiseur.step()

القيمة 1.0 هي الافتراضية الشائعة، لكنّ 5.0 معقولة أيضًا حسب النموذج. الفكرة أنّ التدرّج يحتفظ باتّجاهه، وينقص فقط طوله: نتقدّم في الاتّجاه الصحيح لكن بخطوة مضبوطة. وهو حلّ عملي رخيص، وينبغي أن يكون افتراضيًا في كلّ حلقة تدريب لشبكة تكرارية.

قصّ التدرّج لا يحلّ التلاشي. بل هو حلّ لمشكلة معاكسة: يحمي من الانفجار، ولا يستحدث تدرّجات حين تكون معدومة أصلًا.

التهيئة المتعامدة، سلاح ضدّ التلاشي

يمكن تأخير التلاشي بجعل WhW_h مصفوفة متعامدة عند البداية: قيمها المفردة كلّها تساوي 1، فيبقى الجداء Wh\prod W_h^{\top} مستقرًّا في البداية. مشتقّة tanh\tanh لا تزال تُضعِّف الأمر، لكنّ التدرّج يبقى قابلًا للتعلّم على أفق أطول.

def init_orthogonale(module):
for nom, param in module.named_parameters():
if "weight_hh" in nom: # الأوزان الداخلية weight_hh
torch.nn.init.orthogonal_(param)
elif "weight_ih" in nom: # الأوزان المدخلة weight_ih
torch.nn.init.xavier_uniform_(param)

modele.apply(init_orthogonale)

هذه التهيئة معيارية اليوم على LSTM وGRU أيضًا؛ Keras يفعل ذلك تلقائيًا لأوزانه الداخلية.

LSTM: الجواب المعماري

كلّ ما سبق تخفيفٌ، لا حلّ. الجواب الحقيقي للتلاشي يأتي من تغيير في المعمارية: إدخال حالة خلية لها مسار مُبَاشر تعبره التدرّجات بلا ترشيح بواسطة تنشيط مُتشبِّع. هذه هي فكرة LSTM (1997) وGRU (2014)، وهي موضوع الوحدتين القادمتين.

الحدس الأدقّ سيُشرح ثمّة، لكنّ العنصر المفتاح هو الجمع بدل الضرب: بدل ht=tanh()h_t = \tanh(\dots) الذي يُلزم كلّ تدرّج بالمرور عبر tanh\tanh' عند كلّ خطوة، تكتب LSTM ct=ct1+شيءc_t = c_{t-1} + \text{شيء}، فالمسار الرئيسي بلا ترشيح متكرِّر. هذا ما يبقي التدرّج حيًّا على مئات الخطوات.

قبل أن تُغيّر المعمارية، شخّص

كثيرًا ما يُقفَز إلى LSTM لأنّ RNN لا يتقارب، بينما المشكلة كانت في مكان آخر: مُدخَل غير مُوحَّد السلّم، أو معدّل تعلّم مرتفع، أو zero_grad() غائب. ثلاث حركات تشخّص السبب في دقائق. الأولى: طباعة نورمة التدرّج (torch.nn.utils.clip_grad_norm_(..., max_norm=1e9) يُرجع القيمة قبل القصّ). إن كانت عشرات الآلاف فأنت أمام انفجار. الثانية: الإفراط في تعلّم عشر متتاليات، فإن لم تسقط الخسارة إلى الصفر فالمشكلة تقنية لا في السعة. الثالثة: تدريب على متتالية طول 10، فإن عمل النموذج فالمعمارية مناسبة والطول هو المشكلة، والحلّ حينئذ فعلًا LSTM.

في الخلاصة

  • ينتج تلاشي التدرّج وانفجاره من جداء يعاقبة طويل عبر الزمن: نورمة طيفية أقلّ من 1 تُلغي التدرّج، وأكبر من 1 تُفجّره، والنافذة المستقرّة رفيعة جدًّا.
  • على 50 خطوة، RNN البسيط لا يعود يرى مُدخلاته البعيدة؛ الاستهلاك الأسبوعي مثال ملموس على هذا الحدّ.
  • قصّ التدرّج حلّ افتراضي رخيص للانفجار، والتهيئة المتعامدة تُؤخّر التلاشي، لكنّ أيًّا منهما لا يحلّ المسألة نهائيًا.
  • الحلّ المعماري هو LSTM وGRU: مسار جمعي للحالة يعبره التدرّج بلا ترشيح مُتشبِّع.

الوحدة التالية: LSTM بالتفصيل، بوّاباتها الثلاث، وشرح فيزيائي لماذا يجري التدرّج فيها.