معاينة مختبر آمنة
RNNTF
هذي معاينة منقّحة للقراءة فقط؛ ما فيه أي شيء يشتغل داخل الصفحة.
قراءة فقط
معاينة الدفتر
RNNTF
> **ملاحظة بيئة التشغيل المدمجة:** هالمعاينة تستخدم عيّنة صغيرة وثابتة وآمنة من ناحية الحقوق عشان تكون النتايج قابلة للتكرار. النتايج بالحجم الكامل تحتاج مجموعة البيانات أو النموذج الموثّق بالدرس داخل بيئة خارجية معتمدة.
# الشبكات العصبية المتكررة (RNN)
في الوحدة اللي راحت أخذنا التمثيلات الدلالية الغنية للنص. هالبنية تلتقط المعنى المجمّع لكلمات الجملة، لكنها ما تراعي **ترتيب** الكلمات؛ لأن عملية التجميع اللي تجي بعد التضمينات تشيل معلومات الترتيب من النص الأصلي. وبما إن هالنماذج ما تقدر تمثّل ترتيب الكلمات، فهي ما تحل المهام الأكثر تعقيدًا أو غموضًا، مثل توليد النص والإجابة عن الأسئلة.
عشان نلتقط معنى تسلسل نصي، بنستخدم بنية اسمها **الشبكة العصبية المتكررة** (Recurrent Neural Network أو RNN). مع RNN نمرر الجملة عبر الشبكة رمزًا رمزًا، وتنتج الشبكة **حالة** نرجع نمررها لها مع الرمز اللي بعده.
> **وصف الشكل:** صورة توضّح مثالًا على توليد شبكة عصبية متكررة.
إذا كان تسلسل رموز الإدخال هو $X_0,\dots,X_n$، تنشئ RNN تسلسلًا من كتل الشبكة العصبية وتدرّبه كاملًا باستخدام الانتشار العكسي. كل كتلة تستقبل الزوج $(X_i,S_i)$ وتنتج $S_{i+1}$. بعدها تدخل الحالة الأخيرة $S_n$، أو المخرج $Y_n$، في مصنّف خطي يعطي النتيجة. كل كتل الشبكة تشترك في الأوزان نفسها، ونتدرّب عليها من طرف إلى طرف بتمرير واحد للانتشار العكسي.
> الشكل اللي فوق يعرض RNN بعد فكها على امتداد التسلسل، على اليسار، ويعرضها بتمثيل متكرر مختصر، على اليمين. المهم نعرف إن خلايا RNN كلها تستخدم **أوزانًا مشتركة**.
لأن متجهات الحالة $S_0,\dots,S_n$ تنتقل عبر الشبكة، تقدر RNN تتعلّم العلاقات المتتابعة بين الكلمات. مثلًا، إذا ظهرت كلمة *not* في أي موضع من التسلسل، تقدر تتعلّم إنها تنفي عناصر معيّنة داخل متجه الحالة.
داخل كل خلية RNN مصفوفتا أوزان، $W_H$ و$W_I$، وانحياز $b$. في كل خطوة من RNN، وبوجود الإدخال $X_i$ وحالة الإدخال $S_i$، نحسب حالة المخرج بالصيغة $S_{i+1} = f(W_H\times S_i + W_I\times X_i+b)$، حيث $f$ هي دالة التنشيط، وغالبًا تكون $\tanh$.
> في مسائل مثل توليد النص، اللي بناخذه في الوحدة الجاية، أو الترجمة الآلية، نبي بعد قيمة مخرج عند كل خطوة من RNN. بهالحالة نضيف مصفوفة $W_O$، ونحسب المخرج بالصيغة $Y_i=f(W_O\times S_i+b_O)$.
الحين خلونا نشوف كيف تساعدنا الشبكات العصبية المتكررة في تصنيف مجموعة البيانات الإخبارية.
> في بيئة sandbox، لازم نشغّل الخلية الجاية عشان نتأكد إن المكتبة المطلوبة مثبّتة وإن البيانات محمّلة مسبقًا. إذا كنتم تشغّلون الدفتر محليًا، تخطّوا الخلية الجاية.
# Dependencies are supplied by this edition's pinned runtime profile.import tensorflow as tf
from tensorflow import keras
import numpy as np
_course_titles = tf.constant([
"Regional leaders agree on a public health plan",
"The home team wins the basketball final",
"Markets rise after a strong business report",
"Researchers release an efficient intelligence model",
"Diplomats begin a new round of peace talks",
"The tennis champion returns after an injury",
"Retail sales improve with consumer confidence",
"A software update improves privacy",
])
_course_descriptions = tf.constant([
"World policy and international cooperation",
"Sports results and player development",
"Business revenue and economic indicators",
"Science and technology research",
"World diplomacy and humanitarian aid",
"Sports training and competition",
"Business investment and employment",
"Science security and technology",
])
_course_labels = tf.constant([0, 1, 2, 3, 0, 1, 2, 3], dtype=tf.int64)
_course_records = {"title": _course_titles, "description": _course_descriptions, "label": _course_labels}
ds_train = tf.data.Dataset.from_tensor_slices(_course_records).take(6)
ds_test = tf.data.Dataset.from_tensor_slices(_course_records).skip(6)وقت تدريب النماذج الكبيرة، قد تواجهنا مشكلة في تخصيص ذاكرة GPU. وقد نحتاج نجرّب أحجامًا مختلفة للدفعة المصغّرة عشان تدخل البيانات في ذاكرة GPU ويظل التدريب سريعًا بما يكفي. إذا تشغّلون الكود على جهازكم وفيه GPU، جرّبوا تعدّلون حجم الدفعة لتسرّعون التدريب.
> **ملاحظة:** بعض إصدارات برامج تشغيل NVidia معروفة بأنها ما تحرر الذاكرة بعد تدريب النموذج. وإحنا نشغّل أكثر من مثال في هالدفتر، فممكن تنفد الذاكرة في بعض الإعدادات، خصوصًا إذا أضفتوا تجاربكم في الدفتر نفسه. إذا طلعت لكم أخطاء غريبة مع بداية التدريب، جرّبوا تعيدون تشغيل نواة الدفتر.
batch_size = 16
embed_size = 64## مصنّف RNN بسيط
في RNN البسيطة، كل وحدة متكررة عبارة عن شبكة خطية بسيطة تستقبل متجه إدخال ومتجه حالة، وتنتج متجه حالة جديدًا. يمثّل Keras هالوحدة بالطبقة `SimpleRNN`.
نقدر نمرر الرموز بترميز one-hot مباشرة إلى طبقة RNN، لكن هالفكرة مب مناسبة بسبب عدد أبعادها الكبير. لذلك بنستخدم طبقة تضمين تقلّل أبعاد متجهات الكلمات، وبعدها طبقة RNN، وفي الأخير مصنّف `Dense`.
> **ملاحظة:** إذا كان عدد الأبعاد مب كبير، مثل وقت تجزئة النص على مستوى المحارف، قد يكون تمرير الرموز بترميز one-hot مباشرة إلى خلية RNN خيارًا منطقيًا.
vocab_size = 20000
vectorizer = keras.layers.TextVectorization(
max_tokens=vocab_size)
model = keras.models.Sequential([
keras.Input(shape=(), dtype=tf.string),
vectorizer,
keras.layers.Embedding(vocab_size, embed_size),
keras.layers.SimpleRNN(16),
keras.layers.Dense(4,activation='softmax')
])
model.summary()> **ملاحظة:** نستخدم هنا طبقة تضمين ما تدرّبت من قبل عشان نبسّط المثال. ولنتائج أفضل، نقدر نستخدم طبقة تضمين مدرّبة مسبقًا بـ Word2Vec، مثل ما شرحنا في الوحدة اللي راحت. ومن التمارين المفيدة إنكم تعدّلون الكود عشان يشتغل مع تضمينات مدرّبة مسبقًا.
الحين ندرّب RNN. تدريب RNN صعب عمومًا؛ لأن فك خلايا RNN على امتداد التسلسل ينتج عددًا كبيرًا من الطبقات المشاركة في الانتشار العكسي. لذلك نحتاج معدل تعلّم أصغر وحجمًا أكبر من مجموعة البيانات عشان نطلع بنتائج زينة. التدريب قد يطوّل، فالأفضل تستخدمون GPU.
عشان نسرّع الشغلة، بندرّب نموذج RNN على عناوين الأخبار بس ونستبعد الوصف. جرّبوا تدخلون الوصف في التدريب وشوفوا هل ينجح النموذج في التعلّم.
def extract_title(x):
return x['title']
def tupelize_title(x):
return (extract_title(x),x['label'])
print('Training vectorizer')
vectorizer.adapt(ds_train.take(2000).map(extract_title))model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')
model.fit(ds_train.map(tupelize_title).batch(batch_size),validation_data=ds_test.map(tupelize_title).batch(batch_size))> **ملاحظة:** غالبًا بتكون الدقة هنا أقل؛ لأننا ندرّب على عناوين الأخبار بس.
## نرجع للتسلسلات متفاوتة الطول
تذكّروا إن طبقة `TextVectorization` تضيف تلقائيًا رموز حشو إلى التسلسلات متفاوتة الطول داخل الدفعة المصغّرة. لكن هالرموز تدخل في التدريب بعد، وقد تصعّب وصول النموذج إلى التقارب.
عندنا أكثر من طريقة نقلّل فيها الحشو. وحدة منها إننا نرتّب مجموعة البيانات بحسب طول التسلسل ونجمع التسلسلات المتقاربة بالحجم. نقدر نسوي هالشي بالدالة `tf.data.experimental.bucket_by_sequence_length`؛ شوفوا [التوثيق](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length).
والطريقة الثانية استخدام **القناع**. بعض طبقات Keras تستقبل مدخلًا إضافيًا يحدد وش الرموز اللي تدخل في حساب التدريب. عشان نضيف القناع إلى نموذجنا، إما نحط طبقة `Masking` مستقلة، مثل ما تشرح [الوثائق](https://keras.io/api/layers/core_layers/masking/)، أو نضبط المعامل `mask_zero=True` في طبقة `Embedding`.
> **ملاحظة:** يحتاج هالتدريب قرابة 5 دقائق عشان يكمّل حقبة وحدة على مجموعة البيانات كلها. وقفوا التدريب بأي وقت إذا ما بغيتوا تنتظرون، أو قلّلوا كمية بيانات التدريب بإضافة `.take(...)` بعد مجموعتي البيانات `ds_train` و`ds_test`.
def extract_text(x):
return x['title']+' '+x['description']
def tupelize(x):
return (extract_text(x),x['label'])
model = keras.models.Sequential([
keras.Input(shape=(), dtype=tf.string),
vectorizer,
keras.layers.Embedding(vocab_size,embed_size,mask_zero=True),
keras.layers.SimpleRNN(16),
keras.layers.Dense(4,activation='softmax')
])
model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')
model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))الحين بعد ما استخدمنا القناع، نقدر ندرّب النموذج على مجموعة البيانات الكاملة للعناوين والأوصاف.
> **ملاحظة:** هل لاحظتوا إن أداة تحويل النص اللي نستخدمها تدرّبت على عناوين الأخبار، مب على نص المقال كامل؟ ممكن يخلي هالشي بعض الرموز تُتجاهل، ولذلك الأفضل نعيد تدريب أداة التحويل. لكن أثره غالبًا بسيط، فبنكمل بالأداة المدرّبة مسبقًا عشان نبسّط المثال.
## LSTM: الذاكرة طويلة قصيرة المدى
من أبرز مشاكل RNN مشكلة **تلاشي التدرجات**. قد تمتد RNN لمسافة طويلة جدًا، ويصعب عليها توصيل التدرجات إلى أول طبقة وقت الانتشار العكسي. إذا صار هالشي، ما تقدر الشبكة تتعلّم العلاقات بين الرموز المتباعدة. من طرق معالجة المشكلة إضافة **إدارة صريحة للحالة** باستخدام **بوابات**. وأشهر بنيتين تستخدمان البوابات هما **الذاكرة طويلة قصيرة المدى** (Long Short-Term Memory أو LSTM) و**وحدة التكرار ذات البوابات** (Gated Recurrent Unit أو GRU). وهنا بنركّز على LSTM.
> **وصف الشكل:** صورة توضح مثالًا على خلية ذاكرة طويلة وقصيرة المدى
تنظيم شبكة LSTM قريب من RNN، لكن فيها حالتان تنتقلان من طبقة للي بعدها: الحالة $c$ والمتجه المخفي $h$. في كل وحدة، ندمج المتجه المخفي $h_{t-1}$ مع الإدخال $x_t$، ومن خلال **البوابات** يتحكمان باللي يصير للحالة $c_t$ والمخرج $h_{t}$. كل بوابة تستخدم تنشيط sigmoid، ومخرجها يقع ضمن $[0,1]$. وإذا ضربنا هالمخرج في متجه الحالة، نقدر نتعامل معه كأنه قناع يحدد وش يمر ووش يتوقف. بوابات LSTM، من اليسار لليمين في الصورة، هي:
* **بوابة النسيان**: تحدد وش من مكوّنات المتجه $c_{t-1}$ ننساه، ووش نخليه يمر.
* **بوابة الإدخال**: تحدد كمية المعلومات اللي نضيفها إلى متجه الحالة من متجهي الإدخال والحالة المخفية السابقة.
* **بوابة الإخراج**: تأخذ متجه الحالة الجديد وتحدد أي مكوّنات منه نستخدمها لإنتاج المتجه المخفي الجديد $h_t$.
نقدر نتخيّل مكوّنات الحالة $c$ مثل أعلام تنفتح وتنقفل. مثلًا، إذا مر علينا الاسم *Alice* في التسلسل، نفترض إنه يدل على امرأة ونفعّل في الحالة علمًا يقول إن الجملة فيها اسم مؤنث. وإذا مرّت بعده العبارة *and Tom*، نفعّل علمًا يدل على الجمع. بهالتحكم في الحالة، تقدر الشبكة تتابع الخصائص النحوية للجملة.
> **ملاحظة:** إذا تبغون تفهمون تفاصيل LSTM من الداخل، فمقال Christopher Olah [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) مورد ممتاز.
مع إن بنية خلية LSTM الداخلية تبدو معقّدة، يخفي Keras تفاصيل التنفيذ داخل طبقة `LSTM`. لذلك كل اللي علينا في المثال اللي فوق إننا نستبدل الطبقة المتكررة:
model = keras.models.Sequential([
keras.Input(shape=(), dtype=tf.string),
vectorizer,
keras.layers.Embedding(vocab_size, embed_size),
keras.layers.LSTM(8),
keras.layers.Dense(4,activation='softmax')
])
model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')
model.fit(ds_train.map(tupelize).batch(8),validation_data=ds_test.map(tupelize).batch(8))> **ملاحظة:** تدريب LSTM بطيء بعد، وممكن ما تشوفون ارتفاعًا واضحًا في الدقة ببداية التدريب. استمروا بالتدريب مدة أطول عشان توصلون إلى دقة زينة.
## شبكات RNN ثنائية الاتجاه ومتعددة الطبقات
في أمثلتنا إلى الحين كانت الشبكات المتكررة تمشي من بداية التسلسل إلى نهايته. هالشي يبدو طبيعيًا لأنه نفس اتجاه قراءتنا وسماعنا للكلام. لكن في الحالات اللي تتطلب وصولًا عشوائيًا إلى تسلسل الإدخال، الأنسب نشغّل الحساب المتكرر بالاتجاهين. نسمي هذي شبكات RNN **ثنائية الاتجاه**، وننشئها بتغليف الطبقة المتكررة داخل طبقة خاصة اسمها `Bidirectional`.
> **ملاحظة:** تنشئ طبقة `Bidirectional` نسختين من الطبقة اللي بداخلها، ثم تضبط الخاصية `go_backwards` على `True` في وحدة من النسختين عشان تمشي بعكس اتجاه التسلسل.
شبكة RNN، سواء كانت أحادية الاتجاه أو ثنائية، تلتقط أنماطًا داخل التسلسل وتخزّنها في متجهات الحالة أو ترجعها مخرجات. ومثل الشبكات العصبية الالتفافية، نقدر نبني طبقة متكررة ثانية بعد الأولى عشان تلتقط أنماطًا أعلى مستوى انطلاقًا من الأنماط البسيطة اللي استخرجتها الطبقة الأولى. من هنا تجينا فكرة **RNN متعددة الطبقات**: شبكتان متكررتان أو أكثر، يكون مخرج كل طبقة منها مدخلًا للطبقة اللي بعدها.
> **وصف الشكل:** صورة توضّح شبكة RNN متعددة الطبقات من نوع LSTM
*الصورة مأخوذة من [هالتدوينة](https://docs.pytorch.org/docs/stable/generated/torch.nn.LSTM.html) للكاتب Fernando López.*
يسهّل Keras بناء هالشبكات؛ نضيف ببساطة طبقات متكررة أكثر إلى النموذج. وفي كل الطبقات ما عدا الأخيرة، نضبط المعامل `return_sequences=True`؛ لأننا نحتاج الطبقة ترجع الحالات الوسيطة كلها، مب الحالة الأخيرة من الحساب المتكرر بس.
خلونا نبني لمسألة التصنيف شبكة LSTM ثنائية الاتجاه من طبقتين.
> **ملاحظة:** هالكود يطوّل مرة ثانية، لكنه يحقق أعلى دقة شفناها إلى الحين؛ لذلك يمكن تستاهل النتيجة الانتظار.
model = keras.models.Sequential([
keras.Input(shape=(), dtype=tf.string),
vectorizer,
keras.layers.Embedding(vocab_size, 128, mask_zero=True),
keras.layers.Bidirectional(keras.layers.LSTM(64,return_sequences=True)),
keras.layers.Bidirectional(keras.layers.LSTM(64)),
keras.layers.Dense(4,activation='softmax')
])
model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')
model.fit(ds_train.map(tupelize).batch(batch_size),
validation_data=ds_test.map(tupelize).batch(batch_size))## استخدام RNN في مهام ثانية
إلى الحين ركّزنا على استخدام RNN لتصنيف تسلسلات النص. لكنها تتعامل بعد مع مهام أكثر، مثل توليد النص والترجمة الآلية — وبناخذ هالمهام في الوحدة الجاية.
حذفنا المخرجات وعدّادات التشغيل والودجات والمحتوى النشط وقت الاستيراد. شغّل الدفاتر بس في بيئة خارجية تثق فيها.
سجّل تطبيقك
التسجيل اختياري، يفيدك تتذكر وش طبّقت، ولا يمنع إكمال الدورة.