مشكلة الـ memory البعيدة
شبكات الـ RNN البسيطة اللي اتكلمنا عنها في الدرس اللي فات عندها عيب أساسي: الـ gradients بتختفي بشكل أُسّي كل ما ترجع لورا في الزمن، فتعلّم العلاقات اللي بتمتد أكتر من كام عشرة خطوة بيبقى تقريبًا مستحيل. الـ language model مبيقدرش يربط ضمير في السطر العاشر بالاسم اللي بيشاور عليه في أول سطر. والـ time series model مبيقدرش يفتكر حدث حصل من 500 خطوة وبيأثّر على الخطوة الحالية.
وشبكات الـ long short-term memory (الذاكرة طويلة-قصيرة الأمد) (LSTM) اللي قدّمها هوكرايتر وشميدهوبر سنة 1997 جت تحل المشكلة دي بإنها تزوّد architecture أنيقة جدًا: الـ cell state (حالة الخلية) اللي بتعدّي على الشبكة بأقل تعديل ممكن في كل خطوة، ومحمية بمجموعة gates (بوابات) بتتعلّم. الـ gates بتحدّد إيه اللي يتنسي، وإيه المعلومات الجديدة اللي تتخزّن، وأنهي جزء من الـ memory يطلع كـ output. والتصميم ده بيعمل “طرق سريعة للـ gradient” بتعدّي على الزمن من غير تراجع أُسّي.
الـ cell state: طريق سريع للـ memory
الابتكار الأساسي في الـ LSTM هو الفصل بين نوعين من الحالات: الـ cell state ct والـ hidden state (الحالة المخفية) ht. الـ cell state هي الـ memory الطويلة المدى؛ ماشية فوق خلية الـ LSTM بتعديلات صغيرة بس عن طريق شوية gates بتضرب، من غير التحويل غير الخطي الكامل اللي بيبوّظ الـ gradients في الـ simple RNN. أما الـ hidden state فهي الـ output القصير المدى، اللي بيتحسب من الـ cell state عن طريق ضغط tanh وoutput gate.
ورياضيًا، الـ cell state بتتحدّث بعملية جمع: ct = ft ⊙ ct−1 + it ⊙ gt. هنا ⊙ معناها الضرب عنصر بعنصر، وft هي الـ forget gate (بوابة النسيان) اللي بتقلّص الـ cell state اللي فاتت، وit هي الـ input gate (بوابة الإدخال) اللي بتتحكم في كمية المعلومات الجديدة اللي داخلة، وgt هي الـ memory المرشّحة. ولأن التحديث جمعي مش ضرب غير خطي، الـ gradients تقدر ترجع لورا من غير ما تتقلّص — مشتقة ct بالنسبة لـ ct−1 هي ft بس، وهي قيمة بتتعلّم قريبة من 1 للمحتوى اللي عايزين نحتفظ بيه.
التلات gates
التلات gates ليهم نفس الشكل: linear combination عليها sigmoid من الـ input الحالي xt والـ hidden state اللي فاتت ht−1. والـ sigmoid بتضغط الـ outputs في (0, 1)، و0 معناها “اقفل خالص” و1 معناها “عدّي كله.”
الـ forget gate بتحدّد الجزء اللي يتحفظ من الـ cell state اللي فاتت. في language model بيتابع المفرد والجمع، الـ forget gate ممكن تعيد ظبط سجل العدد في أول عبارة اسمية جديدة. والـ input gate بتحدّد قد إيه من الـ memory المرشّحة الجديدة gt يتكتب في الـ cell state. أما الـ output gate (بوابة المخرج) فبتتحكم في الجزء من الـ cell state اللي بيطلع كـ hidden state ht.
خلية الـ LSTM الواحدة فيها حوالي أربع أضعاف parameters الـ simple RNN بنفس حجم الـ hidden state (أربع matrices weights منفصلة لـ f وi وo وg). والتكلفة الحسابية حقيقية، بس القدرة على التدريب على sequences فيها علاقات بعيدة بتبرّر التكلفة دي في تقريبًا كل تطبيق عملي.
في الـ simple RNN، الـ gradient بيعدّي على لاخطية الـ tanh وmatrix الـ recurrent weights Wh في كل خطوة — سلسلة ضرب بتصغر أو بتنفجر أُسّيًا. وفي الـ LSTM، gradient الـ loss بالنسبة لـ ct−1 بيتضرب بس في الـ forget gate ft، وهي قيمة الشبكة بتتعلّم تخلّيها قريبة من 1 للذكريات المهمة. والـ constant error carousel (دوّار الخطأ الثابت) ده (زي ما هوكرايتر سمّاه) بيخلّي الـ gradients تعدّي على مئات الـ time steps من غير تراجع.
الـ GRU: بديل أبسط
تشو وزمايله اقترحوا سنة 2014 الـ gated recurrent unit (الوحدة التكرارية ذات البوابة) (GRU)، وهي نوع أبسط بيدمج الـ cell state والـ hidden state في vector حالة واحد، وبيبدّل التلات gates بتوع الـ LSTM بـ gateين بس: الـ reset gate (بوابة إعادة الضبط) rt والـ update gate (بوابة التحديث) zt.
الـ update gate zt بتعمل الدور المشترك بتاع الـ forget والـ input gates في الـ LSTM: بتتحكم قد إيه من الـ hidden state اللي فاتت يتحفظ قصاد المحتوى الجديد اللي بيتكتب. والـ reset gate rt بتتحكم الـ hidden state اللي فاتت بتأثّر قد إيه على الـ activation المرشّح ˜ht. ولما rt تقرّب من الصفر، الـ GRU بتنسى الحالة اللي فاتت خالص وبتتصرّف كـ feedforward network في الخطوة دي — مفيد لإعادة ظبط السياق عند حدود الجمل.
وفي مهام مقارنة كتير — الـ language modeling والترجمة الآلية والتعرّف على الكلام — أداء الـ GRU والـ LSTM بيبقى قريب من بعض من غير ما واحدة تغلب التانية باستمرار. والـ GRU فيها parameters أقل وبتشتغل أسرع، وده بيخلّيها جذابة لما الموارد الحسابية تبقى محدودة أو أطوال الـ sequences معتدلة. والـ LSTM ممكن تكسب بفضل الـ cell state الصريحة في المهام اللي محتاجة memory بعيدة جدًا. وعمليًا، الـ architectureين أحسن بكتير من الـ simple RNN، والاختيار بينهم بيتعامل كـ hyperparameter يتظبط.
الـ bidirectional RNNs
الـ RNN العادية بتعالج الـ sequences من الشمال لليمين: عند الـ time step t، مبتشوفش غير x1 لحد xt. بس مهام كتير بتستفيد إنها تعرف السياق كله في الاتجاهين. في الجملة “فاضت العين بالماء العذب”، إنك تعرف كلمة “الماء” اللي جاية بعد “العين” هو اللي بيحدّد “العين” هنا عضو الشوف ولا نبع المية.
الـ bidirectional RNN (BiRNN) بتشغّل شبكتين RNN منفصلتين على نفس الـ sequence: واحدة في الاتجاه الأمامي (من الشمال لليمين) والتانية في الاتجاه العكسي (من اليمين للشمال). وفي كل مكان t، الـ hidden stateين بيتدمجوا: ht = [&overrightarrow;ht ; &overleftarrow;ht]. والتمثيل ده اللي عرضه الضعف بيشفّر السياق اللي فات واللي جاي، وده بيحسّن الأداء جامد في مهام زي الـ named entity recognition والترجمة الآلية وفهم اللغة على طريقة BERT.
الـ bidirectional RNNs محتاجة الـ sequence كلها تبقى موجودة قبل ما المعالجة تبدأ — مينفعش تتستخدم للـ inference الفوري لما الـ tokens الجاية مش موجودة. وللـ language modeling التوليدي والتعرّف على الكلام في الـ real-time، بيتستخدم مسار الـ RNN الأمامي بس. والـ models اللي في الاتجاهين معيار في مهام الـ encoding (استخراج الـ features والـ classification والـ encoders بتوع الترجمة) بس مش في الـ decoding أو التوليد.
الـ deep RNNs: رص الـ layers
زي ما الـ CNNs بترص convolutional layers كتير عشان تبني representations هرمية، الـ recurrent networks ينفع تترص فوق بعض. في الـ deep RNN، الـ hidden state بتاعة الـ layer رقم l عند الـ time step t، وهي ht(l)، بتدخل كـ input للـ layer رقم l+1. وكل layer بتشتغل بنفس الدقة الزمنية، فبتعالج الـ sequence كلها، فـ LSTM من تلات layers فيها تلات مجموعات مستقلة من الـ recurrent weights، كل واحدة بتتعلّم مستوى مختلف من التجريد الزمني.
وعمليًا، 2–4 layers هو المعتاد لمهام الـ NLP؛ والتعميق أكتر نادرًا ما بيفيد في models الـ sequences وممكن يصعّب التدريب. وبين layers الـ RNN المتراصّة، الـ dropout بيتطبّق على الوصلات اللي مش تكرارية (المسارات الرأسية من ht(l) لـ input الـ layer اللي بعدها) عشان يعمل regularization للـ model. وتطبيق الـ dropout على الوصلات التكرارية هيعطّل مرور الـ gradient اللي بيخلّي الـ LSTMs فعّالة؛ ونوع متخصص اسمه الـ variational dropout بيطبّق نفس mask الـ dropout في كل time step وهو الطريقة الموصى بيها لما الـ dropout التكراري يبقى ضروري.
ومع نص العشرية التانية من الألفية التالتة، الـ deep bidirectional LSTMs بقت الـ architecture المسيطرة في الـ sequence modeling، ووصلت لنتايج متقدمة في الترجمة والتعرّف على الكلام والـ NLP. وفضلت هي المعيار لحد ما architecture الـ transformer (المحوّل) (اللي هندرسها في الوحدة 9) عدّتها سنة 2017 بإنها بدّلت الـ recurrence بالـ self-attention — آلية بتوصّل أي مكانين في الـ sequence في خطوة حسابية واحدة مهما كانت المسافة بينهم.
الـ LSTM والـ GRU بيحلّوا مشكلة الـ vanishing gradient، بس لسه بطبيعتهم تسلسليين — مينفعش تحسب الخطوة t قبل ما الخطوة t−1 تخلص. وده بيحدّ التوازي أثناء التدريب. والـ transformer بيكسر القيد ده بإنه يحسب كل الأماكن في نفس الوقت بالـ self-attention، فبيسمح بتوازي ضخم على الـ GPUs الحديثة. وفي أغلب المهام اللي الـ sequence فيها موجودة كلها (الـ NLP وتحليل الـ time series)، الـ transformers عدّت الـ LSTMs بكتير. بس للبيانات اللي بتيجي streaming (الكلام الفوري وأجهزة الـ IoT) والمهام اللي فيها قيود latency صارمة، الـ LSTM والـ GRU بيفضلوا اختيارات عملية لأنهم بيعالجوا خطوة واحدة كل مرة بحسابات محدودة.
- الـ LSTM بتحل مشكلة الـ vanishing gradient بإنها تدخّل cell state ct بتعدّي على الشبكة بتحديثات جمعية، ومحمية بتلات gates بتتعلّم (الـ forget والـ input والـ output). والتحديث الجمعي بيدّي طرق سريعة للـ gradient بتسمح بالتعلّم على مئات الخطوات.
- الـ forget gate بتقيس محتوى الـ cell state القديمة؛ والـ input gate بتتحكم في قد إيه من الـ memory المرشّحة الجديدة يتكتب؛ والـ output gate بتفلتر الـ cell state عشان تطلّع الـ hidden state. وكل الـ gates linear combinations عليها sigmoid من xt وht−1.
- الـ GRU بتبسّط الـ LSTM لـ gateين (الـ reset والـ update)، وبتدمج الـ cell state والـ hidden state في حالة واحدة. parameters أقل وتدريب أسرع وأداء قريب من الـ LSTM في أغلب المعايير. والاختيار بين الـ LSTM والـ GRU hyperparameter يتظبط.
- الـ bidirectional RNNs بتشغّل مسارين — أمامي وعكسي — وبتدمج الـ hidden stateين، فبتدّي كل مكان اطّلاع على السياق اللي فات واللي جاي. ومحتاجة الـ sequence كلها من الأول، فمتنفعش للتوليد الفوري.
- الـ deep RNNs بترص من 2 لـ 4 recurrent layers عشان تبني representations زمنية هرمية. والـ dropout بين الـ layers (مش على الوصلات التكرارية) بيعمل regularization للـ models دي. وarchitecture الـ transformer عدّت الـ deep BiLSTM في المهام اللي مش فورية بتوازي حسابي كامل على كل الأماكن.