AI-For-Beginners/translations/ar/lessons/5-NLP/16-RNN/RNNPyTorch.ipynb

479 lines
29 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# الشبكات العصبية المتكررة\n",
"\n",
"في الوحدة السابقة، كنا نستخدم تمثيلات دلالية غنية للنص، ومصنفًا خطيًا بسيطًا فوق التضمينات. ما تفعله هذه البنية هو التقاط المعنى المجمع للكلمات في الجملة، لكنها لا تأخذ في الاعتبار **ترتيب** الكلمات، لأن عملية التجميع فوق التضمينات أزالت هذه المعلومات من النص الأصلي. وبسبب عدم قدرة هذه النماذج على تمثيل ترتيب الكلمات، فإنها لا تستطيع حل المهام الأكثر تعقيدًا أو الغامضة مثل توليد النصوص أو الإجابة على الأسئلة.\n",
"\n",
"لتمثيل معنى تسلسل النصوص، نحتاج إلى استخدام بنية شبكة عصبية أخرى تُعرف باسم **الشبكة العصبية المتكررة** أو RNN. في RNN، نقوم بتمرير الجملة عبر الشبكة رمزًا واحدًا في كل مرة، وتنتج الشبكة **حالة** معينة، والتي نقوم بتمريرها مرة أخرى إلى الشبكة مع الرمز التالي.\n",
"\n",
"بالنظر إلى تسلسل المدخلات من الرموز $X_0,\\dots,X_n$، تقوم RNN بإنشاء تسلسل من كتل الشبكة العصبية، وتدرب هذا التسلسل من البداية إلى النهاية باستخدام الانتشار العكسي. كل كتلة شبكة تأخذ زوجًا $(X_i,S_i)$ كمدخل، وتنتج $S_{i+1}$ كنتيجة. الحالة النهائية $S_n$ أو المخرج $X_n$ يتم تمريرها إلى مصنف خطي لإنتاج النتيجة. جميع كتل الشبكة تشترك في نفس الأوزان، ويتم تدريبها من البداية إلى النهاية باستخدام تمريرة واحدة من الانتشار العكسي.\n",
"\n",
"نظرًا لأن متجهات الحالة $S_0,\\dots,S_n$ يتم تمريرها عبر الشبكة، فإنها تكون قادرة على تعلم الاعتماديات التسلسلية بين الكلمات. على سبيل المثال، عندما تظهر كلمة *ليس* في مكان ما في التسلسل، يمكنها تعلم نفي عناصر معينة داخل متجه الحالة، مما يؤدي إلى النفي.\n",
"\n",
"> بما أن أوزان جميع كتل RNN في الصورة مشتركة، يمكن تمثيل نفس الصورة ككتلة واحدة (على اليمين) مع حلقة تغذية راجعة متكررة، تقوم بتمرير حالة الخرج للشبكة مرة أخرى إلى المدخل.\n",
"\n",
"دعونا نرى كيف يمكن للشبكات العصبية المتكررة مساعدتنا في تصنيف مجموعة بيانات الأخبار الخاصة بنا.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading dataset...\n",
"Building vocab...\n"
]
}
],
"source": [
"import torch\n",
"import torchtext\n",
"from torchnlp import *\n",
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
"vocab_size = len(vocab)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## مصنف RNN بسيط\n",
"\n",
"في حالة RNN البسيط، كل وحدة متكررة هي شبكة خطية بسيطة، تأخذ متجه الإدخال ومتجه الحالة المدمجين، وتنتج متجه حالة جديد. يمثل PyTorch هذه الوحدة باستخدام فئة `RNNCell`، وشبكة من هذه الخلايا - كطبقة `RNN`.\n",
"\n",
"لتعريف مصنف RNN، سنقوم أولاً بتطبيق طبقة تضمين لتقليل أبعاد مفردات الإدخال، ثم نضع طبقة RNN فوقها:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"class RNNClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.rnn = torch.nn.RNN(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" x,h = self.rnn(x)\n",
" return self.fc(x.mean(dim=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **ملاحظة:** نستخدم هنا طبقة تضمين غير مدربة للتبسيط، ولكن للحصول على نتائج أفضل يمكننا استخدام طبقة تضمين مدربة مسبقًا باستخدام Word2Vec أو GloVe، كما هو موضح في الوحدة السابقة. لفهم أفضل، قد ترغب في تعديل هذا الكود ليعمل مع التضمينات المدربة مسبقًا.\n",
"\n",
"في حالتنا، سنستخدم مُحمّل بيانات مُعبأ، بحيث يحتوي كل دفعة على عدد من التسلسلات المُعبأة بنفس الطول. ستأخذ طبقة RNN تسلسلًا من مصفوفات التضمين، وتنتج مخرجاتين:\n",
"* $x$ هو تسلسل مخرجات خلايا RNN في كل خطوة\n",
"* $h$ هو الحالة المخفية النهائية للعنصر الأخير في التسلسل\n",
"\n",
"ثم نقوم بتطبيق مصنف خطي متصل بالكامل للحصول على عدد الفئات.\n",
"\n",
"> **ملاحظة:** من الصعب تدريب شبكات RNN، لأنه بمجرد فك خلايا RNN على طول التسلسل، يصبح عدد الطبقات الناتجة في عملية الانتشار العكسي كبيرًا جدًا. لذلك نحتاج إلى اختيار معدل تعلم صغير، وتدريب الشبكة على مجموعة بيانات أكبر للحصول على نتائج جيدة. قد يستغرق ذلك وقتًا طويلًا، لذا يُفضل استخدام GPU.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.3090625\n",
"6400: acc=0.38921875\n",
"9600: acc=0.4590625\n",
"12800: acc=0.511953125\n",
"16000: acc=0.5506875\n",
"19200: acc=0.57921875\n",
"22400: acc=0.6070089285714285\n",
"25600: acc=0.6304296875\n",
"28800: acc=0.6484027777777778\n",
"32000: acc=0.66509375\n",
"35200: acc=0.6790056818181818\n",
"38400: acc=0.6929166666666666\n",
"41600: acc=0.7035817307692308\n",
"44800: acc=0.7137276785714286\n",
"48000: acc=0.72225\n",
"51200: acc=0.73001953125\n",
"54400: acc=0.7372794117647059\n",
"57600: acc=0.7436631944444444\n",
"60800: acc=0.7503947368421052\n",
"64000: acc=0.75634375\n",
"67200: acc=0.7615773809523809\n",
"70400: acc=0.7662642045454545\n",
"73600: acc=0.7708423913043478\n",
"76800: acc=0.7751822916666666\n",
"80000: acc=0.7790625\n",
"83200: acc=0.7825\n",
"86400: acc=0.7858564814814815\n",
"89600: acc=0.7890513392857142\n",
"92800: acc=0.7920474137931034\n",
"96000: acc=0.7952708333333334\n",
"99200: acc=0.7982258064516129\n",
"102400: acc=0.80099609375\n",
"105600: acc=0.8037594696969697\n",
"108800: acc=0.8060569852941176\n"
]
}
],
"source": [
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)\n",
"net = RNNClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch(net,train_loader, lr=0.001)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## الذاكرة طويلة وقصيرة المدى (LSTM)\n",
"\n",
"إحدى المشاكل الرئيسية في الشبكات العصبية المتكررة التقليدية (RNNs) هي مشكلة **تلاشي التدرجات**. نظرًا لأن الشبكات العصبية المتكررة يتم تدريبها من البداية إلى النهاية في عملية تمرير خلفي واحدة، فإنها تواجه صعوبة في نقل الخطأ إلى الطبقات الأولى من الشبكة، وبالتالي لا تستطيع الشبكة تعلم العلاقات بين الرموز البعيدة. إحدى الطرق لتجنب هذه المشكلة هي تقديم **إدارة حالة صريحة** باستخدام ما يسمى بـ **البوابات**. هناك اثنتان من أكثر البنى المعروفة من هذا النوع: **الذاكرة طويلة وقصيرة المدى** (LSTM) و**وحدة الترحيل ذات البوابة** (GRU).\n",
"\n",
"![صورة توضح مثالًا على خلية ذاكرة طويلة وقصيرة المدى](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n",
"\n",
"شبكة LSTM منظمة بطريقة مشابهة للشبكات العصبية المتكررة (RNN)، ولكن هناك حالتان يتم تمريرهما من طبقة إلى أخرى: الحالة الفعلية $c$، والمتجه المخفي $h$. في كل وحدة، يتم دمج المتجه المخفي $h_i$ مع الإدخال $x_i$، وهما يتحكمان بما يحدث للحالة $c$ عبر **البوابات**. كل بوابة هي شبكة عصبية مع تفعيل سيجمويد (الإخراج في النطاق $[0,1]$)، والتي يمكن اعتبارها كقناع ثنائي عند ضربها في متجه الحالة. هناك البوابات التالية (من اليسار إلى اليمين في الصورة أعلاه):\n",
"* **بوابة النسيان** تأخذ المتجه المخفي وتحدد أي مكونات من المتجه $c$ نحتاج إلى نسيانها وأيها نمررها.\n",
"* **بوابة الإدخال** تأخذ بعض المعلومات من الإدخال والمتجه المخفي، وتدخلها في الحالة.\n",
"* **بوابة الإخراج** تحول الحالة عبر طبقة خطية مع تفعيل $\\tanh$، ثم تختار بعض مكوناتها باستخدام المتجه المخفي $h_i$ لإنتاج الحالة الجديدة $c_{i+1}$.\n",
"\n",
"يمكن اعتبار مكونات الحالة $c$ كعلامات يمكن تشغيلها أو إيقافها. على سبيل المثال، عندما نصادف اسم *Alice* في التسلسل، قد نرغب في افتراض أنه يشير إلى شخصية أنثى، ونرفع العلامة في الحالة التي تشير إلى وجود اسم أنثوي في الجملة. عندما نصادف لاحقًا عبارة *and Tom*، سنرفع العلامة التي تشير إلى وجود اسم جمع. وبالتالي، من خلال التلاعب بالحالة، يمكننا نظريًا تتبع الخصائص النحوية لأجزاء الجملة.\n",
"\n",
"> **Note**: مصدر رائع لفهم التفاصيل الداخلية لـ LSTM هو هذه المقالة الممتازة [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) بواسطة كريستوفر أولاه.\n",
"\n",
"على الرغم من أن الهيكل الداخلي لخلية LSTM قد يبدو معقدًا، إلا أن PyTorch يخفي هذه التنفيذ داخل فئة `LSTMCell`، ويوفر كائن `LSTM` لتمثيل طبقة LSTM بأكملها. وبالتالي، فإن تنفيذ مصنف LSTM سيكون مشابهًا جدًا للشبكة العصبية المتكررة البسيطة التي رأيناها أعلاه:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"class LSTMClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" x,(h,c) = self.rnn(x)\n",
" return self.fc(h[-1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.259375\n",
"6400: acc=0.25859375\n",
"9600: acc=0.26177083333333334\n",
"12800: acc=0.2784375\n",
"16000: acc=0.313\n",
"19200: acc=0.3528645833333333\n",
"22400: acc=0.3965625\n",
"25600: acc=0.4385546875\n",
"28800: acc=0.4752777777777778\n",
"32000: acc=0.505375\n",
"35200: acc=0.5326704545454546\n",
"38400: acc=0.5557552083333334\n",
"41600: acc=0.5760817307692307\n",
"44800: acc=0.5954910714285714\n",
"48000: acc=0.6118333333333333\n",
"51200: acc=0.62681640625\n",
"54400: acc=0.6404779411764706\n",
"57600: acc=0.6520138888888889\n",
"60800: acc=0.662828947368421\n",
"64000: acc=0.673546875\n",
"67200: acc=0.6831547619047619\n",
"70400: acc=0.6917897727272727\n",
"73600: acc=0.6997146739130434\n",
"76800: acc=0.707109375\n",
"80000: acc=0.714075\n",
"83200: acc=0.7209134615384616\n",
"86400: acc=0.727037037037037\n",
"89600: acc=0.7326674107142858\n",
"92800: acc=0.7379633620689655\n",
"96000: acc=0.7433645833333333\n",
"99200: acc=0.7479032258064516\n",
"102400: acc=0.752119140625\n",
"105600: acc=0.7562405303030303\n",
"108800: acc=0.76015625\n",
"112000: acc=0.7641339285714286\n",
"115200: acc=0.7677777777777778\n",
"118400: acc=0.7711233108108108\n"
]
},
{
"data": {
"text/plain": [
"(0.03487814127604167, 0.7728)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"net = LSTMClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch(net,train_loader, lr=0.001)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## التسلسلات المعبأة\n",
"\n",
"في مثالنا، كان علينا ملء جميع التسلسلات في الدفعة الصغيرة بـ \"متجهات صفرية\". على الرغم من أن ذلك يؤدي إلى بعض الهدر في الذاكرة، إلا أن المشكلة الأكثر أهمية مع الشبكات العصبية المتكررة (RNNs) هي أن خلايا إضافية يتم إنشاؤها لمعالجة العناصر المعبأة، والتي تشارك في التدريب لكنها لا تحمل أي معلومات مهمة. سيكون من الأفضل تدريب الشبكة العصبية المتكررة فقط على حجم التسلسل الفعلي.\n",
"\n",
"لتحقيق ذلك، يتم تقديم صيغة خاصة لتخزين التسلسلات المعبأة في PyTorch. لنفترض أن لدينا دفعة صغيرة معبأة تبدو كالتالي:\n",
"```\n",
"[[1,2,3,4,5],\n",
" [6,7,8,0,0],\n",
" [9,0,0,0,0]]\n",
"```\n",
"هنا يمثل الرقم 0 القيم المعبأة، ومتجه الطول الفعلي للتسلسلات المدخلة هو `[5,3,1]`.\n",
"\n",
"للتدريب الفعّال للشبكة العصبية المتكررة مع التسلسلات المعبأة، نريد أن نبدأ تدريب المجموعة الأولى من خلايا الشبكة العصبية المتكررة مع دفعة صغيرة كبيرة (`[1,6,9]`)، ثم إنهاء معالجة التسلسل الثالث، ومواصلة التدريب مع دفعات صغيرة أقصر (`[2,7]`, `[3,8]`)، وهكذا. وبالتالي، يتم تمثيل التسلسل المعبأ كمتجه واحد - في حالتنا `[1,6,9,2,7,3,8,4,5]`، ومتجه الطول (`[5,3,1]`)، والذي يمكننا من خلاله إعادة بناء الدفعة الصغيرة الأصلية بسهولة.\n",
"\n",
"لإنتاج تسلسل معبأ، يمكننا استخدام وظيفة `torch.nn.utils.rnn.pack_padded_sequence`. جميع الطبقات المتكررة، بما في ذلك RNN و LSTM و GRU، تدعم التسلسلات المعبأة كمدخلات، وتنتج مخرجات معبأة، والتي يمكن فك تشفيرها باستخدام `torch.nn.utils.rnn.pad_packed_sequence`.\n",
"\n",
"لكي نتمكن من إنتاج تسلسل معبأ، نحتاج إلى تمرير متجه الطول إلى الشبكة، وبالتالي نحتاج إلى وظيفة مختلفة لتحضير الدفعات الصغيرة:\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"def pad_length(b):\n",
" # build vectorized sequence\n",
" v = [encode(x[1]) for x in b]\n",
" # compute max length of a sequence in this minibatch and length sequence itself\n",
" len_seq = list(map(len,v))\n",
" l = max(len_seq)\n",
" return ( # tuple of three tensors - labels, padded features, length sequence\n",
" torch.LongTensor([t[0]-1 for t in b]),\n",
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v]),\n",
" torch.tensor(len_seq)\n",
" )\n",
"\n",
"train_loader_len = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=pad_length, shuffle=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"الشبكة الفعلية ستكون مشابهة جدًا لـ `LSTMClassifier` المذكور أعلاه، ولكن تمرير `forward` سيستقبل كلًا من دفعة البيانات المبطنة (padded minibatch) ومتجه أطوال التسلسل. بعد حساب التضمين (embedding)، نقوم بحساب التسلسل المضغوط (packed sequence)، ثم نمرره إلى طبقة LSTM، وبعد ذلك نقوم بفك النتيجة مرة أخرى.\n",
"\n",
"> **ملاحظة**: في الواقع، نحن لا نستخدم النتيجة المفكوكة `x`، لأننا نستخدم المخرجات من الطبقات المخفية في العمليات الحسابية التالية. لذلك، يمكننا إزالة عملية فك النتيجة تمامًا من هذا الكود. السبب في وضعها هنا هو لتسهيل تعديل هذا الكود عليك، في حال احتجت إلى استخدام مخرجات الشبكة في العمليات الحسابية المستقبلية.\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"class LSTMPackClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x, lengths):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" pad_x = torch.nn.utils.rnn.pack_padded_sequence(x,lengths,batch_first=True,enforce_sorted=False)\n",
" pad_x,(h,c) = self.rnn(pad_x)\n",
" x, _ = torch.nn.utils.rnn.pad_packed_sequence(pad_x,batch_first=True)\n",
" return self.fc(h[-1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.285625\n",
"6400: acc=0.33359375\n",
"9600: acc=0.3876041666666667\n",
"12800: acc=0.44078125\n",
"16000: acc=0.4825\n",
"19200: acc=0.5235416666666667\n",
"22400: acc=0.5559821428571429\n",
"25600: acc=0.58609375\n",
"28800: acc=0.6116666666666667\n",
"32000: acc=0.63340625\n",
"35200: acc=0.6525284090909091\n",
"38400: acc=0.668515625\n",
"41600: acc=0.6822596153846154\n",
"44800: acc=0.6948214285714286\n",
"48000: acc=0.7052708333333333\n",
"51200: acc=0.71521484375\n",
"54400: acc=0.7239889705882353\n",
"57600: acc=0.7315277777777778\n",
"60800: acc=0.7388486842105263\n",
"64000: acc=0.74571875\n",
"67200: acc=0.7518303571428572\n",
"70400: acc=0.7576988636363636\n",
"73600: acc=0.7628940217391305\n",
"76800: acc=0.7681510416666667\n",
"80000: acc=0.7728125\n",
"83200: acc=0.7772235576923077\n",
"86400: acc=0.7815393518518519\n",
"89600: acc=0.7857700892857142\n",
"92800: acc=0.7895043103448276\n",
"96000: acc=0.7930520833333333\n",
"99200: acc=0.7959072580645161\n",
"102400: acc=0.798994140625\n",
"105600: acc=0.802064393939394\n",
"108800: acc=0.8051378676470589\n",
"112000: acc=0.8077857142857143\n",
"115200: acc=0.8104600694444445\n",
"118400: acc=0.8128293918918919\n"
]
},
{
"data": {
"text/plain": [
"(0.029785829671223958, 0.8138166666666666)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"net = LSTMPackClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch_emb(net,train_loader_len, lr=0.001,use_pack_sequence=True)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **ملاحظة:** قد تكون قد لاحظت المعامل `use_pack_sequence` الذي نقوم بتمريره إلى وظيفة التدريب. حاليًا، تتطلب وظيفة `pack_padded_sequence` أن يكون متجه طول التسلسل على جهاز CPU، وبالتالي تحتاج وظيفة التدريب إلى تجنب نقل بيانات طول التسلسل إلى GPU أثناء التدريب. يمكنك الاطلاع على تنفيذ وظيفة `train_emb` في ملف [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py).\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## الشبكات العصبية المتكررة ثنائية الاتجاه ومتعددة الطبقات\n",
"\n",
"في أمثلتنا، كانت جميع الشبكات المتكررة تعمل في اتجاه واحد، من بداية التسلسل إلى نهايته. يبدو هذا طبيعيًا لأنه يشبه الطريقة التي نقرأ بها ونستمع إلى الكلام. ومع ذلك، نظرًا لأننا في العديد من الحالات العملية لدينا وصول عشوائي إلى تسلسل الإدخال، فقد يكون من المنطقي تشغيل الحساب المتكرر في كلا الاتجاهين. تُعرف هذه الشبكات باسم **الشبكات العصبية المتكررة ثنائية الاتجاه**، ويمكن إنشاؤها عن طريق تمرير المعامل `bidirectional=True` إلى منشئ RNN/LSTM/GRU.\n",
"\n",
"عند التعامل مع شبكة ثنائية الاتجاه، سنحتاج إلى متجهين للحالة المخفية، واحد لكل اتجاه. يقوم PyTorch بترميز هذه المتجهات كمتجه واحد بحجم مضاعف، وهو أمر مريح للغاية، لأنك عادةً ما تمرر الحالة المخفية الناتجة إلى طبقة خطية متصلة بالكامل، وستحتاج فقط إلى أخذ هذه الزيادة في الحجم في الاعتبار عند إنشاء الطبقة.\n",
"\n",
"الشبكة المتكررة، سواء كانت أحادية الاتجاه أو ثنائية الاتجاه، تلتقط أنماطًا معينة داخل التسلسل، ويمكنها تخزينها في متجه الحالة أو تمريرها إلى الإخراج. كما هو الحال مع الشبكات الالتفافية، يمكننا بناء طبقة متكررة أخرى فوق الطبقة الأولى لالتقاط أنماط ذات مستوى أعلى، مبنية من الأنماط ذات المستوى الأدنى التي استخرجتها الطبقة الأولى. يقودنا هذا إلى مفهوم **الشبكات العصبية المتكررة متعددة الطبقات**، والتي تتكون من شبكتين أو أكثر من الشبكات المتكررة، حيث يتم تمرير إخراج الطبقة السابقة إلى الطبقة التالية كمدخل.\n",
"\n",
"![صورة توضح شبكة عصبية متكررة طويلة وقصيرة المدى متعددة الطبقات](../../../../../translated_images/multi-layer-lstm.dd975e29bb2a59fe58b429db833932d734c81f211cad2783797a9608984acb8c.ar.jpg)\n",
"\n",
"*الصورة مأخوذة من [هذا المقال الرائع](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) بقلم Fernando López*\n",
"\n",
"يجعل PyTorch بناء مثل هذه الشبكات مهمة سهلة، حيث تحتاج فقط إلى تمرير المعامل `num_layers` إلى منشئ RNN/LSTM/GRU لبناء عدة طبقات من التكرار تلقائيًا. وهذا يعني أيضًا أن حجم متجه الحالة المخفية سيزداد بشكل متناسب، وستحتاج إلى أخذ ذلك في الاعتبار عند التعامل مع إخراج الطبقات المتكررة.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## الشبكات العصبية المتكررة لمهام أخرى\n",
"\n",
"في هذه الوحدة، رأينا أن الشبكات العصبية المتكررة يمكن استخدامها لتصنيف التسلسلات، ولكن في الواقع، يمكنها التعامل مع العديد من المهام الأخرى، مثل توليد النصوص، ترجمة النصوص، والمزيد. سنناقش هذه المهام في الوحدة التالية.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**إخلاء المسؤولية**: \nتم ترجمة هذا المستند باستخدام خدمة الترجمة بالذكاء الاصطناعي [Co-op Translator](https://github.com/Azure/co-op-translator). بينما نسعى لتحقيق الدقة، يرجى العلم أن الترجمات الآلية قد تحتوي على أخطاء أو معلومات غير دقيقة. يجب اعتبار المستند الأصلي بلغته الأصلية المصدر الرسمي. للحصول على معلومات حاسمة، يُوصى بالاستعانة بترجمة بشرية احترافية. نحن غير مسؤولين عن أي سوء فهم أو تفسيرات خاطئة تنشأ عن استخدام هذه الترجمة.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "522ee52ae3d5ae933e283286254e9a55",
"translation_date": "2025-08-28T04:26:05+00:00",
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
"language_code": "ar"
}
},
"nbformat": 4,
"nbformat_minor": 2
}