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

479 lines
30 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"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، ابتدا یک لایه تعبیه (embedding) اعمال می‌کنیم تا ابعاد واژگان ورودی کاهش یابد و سپس یک لایه 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",
"یکی از مشکلات اصلی شبکه‌های عصبی بازگشتی کلاسیک، مشکل معروف **گرادیان‌های ناپدیدشونده** است. از آنجا که شبکه‌های بازگشتی در یک مرحله پس‌انتشار به صورت انتها به انتها آموزش داده می‌شوند، انتقال خطا به لایه‌های ابتدایی شبکه دشوار است و در نتیجه شبکه نمی‌تواند روابط بین توکن‌های دور از هم را یاد بگیرد. یکی از روش‌های جلوگیری از این مشکل، معرفی **مدیریت صریح حالت** با استفاده از **دروازه‌ها** است. دو معماری شناخته‌شده در این زمینه عبارتند از: **حافظه کوتاه‌مدت بلند** (LSTM) و **واحد بازپخش دروازه‌دار** (GRU).\n",
"\n",
"![تصویری که یک سلول حافظه کوتاه‌مدت بلند را نشان می‌دهد](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n",
"\n",
"شبکه LSTM به شکلی مشابه شبکه بازگشتی سازماندهی شده است، اما دو حالت وجود دارد که از لایه‌ای به لایه دیگر منتقل می‌شوند: حالت واقعی $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",
"> **توجه**: یک منبع عالی برای درک ساختار داخلی LSTM این مقاله فوق‌العاده [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) نوشته Christopher Olah است.\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",
"در مثال ما، مجبور بودیم تمام دنباله‌ها در مینی‌بچ را با بردارهای صفر پر کنیم. این کار باعث اتلاف حافظه می‌شود، اما در RNNها مسئله مهم‌تر این است که سلول‌های اضافی RNN برای آیتم‌های ورودی پرشده ایجاد می‌شوند که در فرآیند آموزش شرکت می‌کنند، اما اطلاعات ورودی مهمی را حمل نمی‌کنند. بهتر است RNN فقط بر اساس اندازه واقعی دنباله آموزش داده شود.\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",
"برای اینکه بتوانیم RNN را به‌طور مؤثر با دنباله‌های پرشده آموزش دهیم، می‌خواهیم آموزش گروه اول سلول‌های RNN را با مینی‌بچ بزرگ (`[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` هم دسته‌های کوچک پد شده و هم بردار طول دنباله‌ها دریافت می‌شوند. پس از محاسبه تعبیه، دنباله بسته را محاسبه می‌کنیم، آن را به لایه 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",
"![تصویری که یک شبکه بازگشتی چندلایه با حافظه کوتاه‌مدت-بلندمدت را نشان می‌دهد](../../../../../lessons/5-NLP/16-RNN/images/multi-layer-lstm.jpg)\n",
"\n",
"*تصویر از [این پست فوق‌العاده](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) نوشته فرناندو لوپز*\n",
"\n",
"PyTorch ساخت چنین شبکه‌هایی را به کاری آسان تبدیل کرده است، زیرا تنها کافی است پارامتر `num_layers` را به سازنده RNN/LSTM/GRU ارسال کنید تا چندین لایه بازگشتی به صورت خودکار ساخته شوند. این همچنین به این معناست که اندازه بردار حالت/مخفی به طور متناسب افزایش می‌یابد و باید این موضوع را هنگام مدیریت خروجی لایه‌های بازگشتی در نظر بگیرید.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## شبکه‌های عصبی بازگشتی (RNN) برای وظایف دیگر\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-31T17:09:27+00:00",
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
"language_code": "fa"
}
},
"nbformat": 4,
"nbformat_minor": 2
}