{ "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$ নেটওয়ার্কের মাধ্যমে পাঠানো হয়, এটি শব্দগুলোর মধ্যে ক্রমাগত নির্ভরতা শিখতে সক্ষম হয়। উদাহরণস্বরূপ, যখন *not* শব্দটি ক্রমের কোথাও উপস্থিত হয়, এটি অবস্থা ভেক্টরের নির্দিষ্ট উপাদানগুলোকে অস্বীকার করতে শিখতে পারে, যার ফলে নেতিবাচকতা তৈরি হয়।\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", "ক্লাসিক্যাল RNN-এর প্রধান সমস্যাগুলোর একটি হলো **vanishing gradients** সমস্যা। যেহেতু RNN-গুলো একবারে এক ব্যাক-প্রোপাগেশন পাসে এন্ড-টু-এন্ড প্রশিক্ষিত হয়, এটি নেটওয়ার্কের প্রথম স্তরগুলোতে ত্রুটি প্রেরণ করতে সমস্যায় পড়ে, এবং এর ফলে নেটওয়ার্ক দূরবর্তী টোকেনগুলোর মধ্যে সম্পর্ক শিখতে পারে না। এই সমস্যাটি এড়ানোর একটি উপায় হলো **explicit state management** প্রবর্তন করা, যা **gates** ব্যবহার করে করা হয়। এই ধরনের দুটি সবচেয়ে পরিচিত আর্কিটেকচার হলো: **Long Short Term Memory** (LSTM) এবং **Gated Relay Unit** (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$ এর সাথে সংযুক্ত হয়, এবং তারা **gates** এর মাধ্যমে স্টেট $c$-তে কী ঘটবে তা নিয়ন্ত্রণ করে। প্রতিটি গেট হলো সিগময়েড অ্যাক্টিভেশন সহ একটি নিউরাল নেটওয়ার্ক (আউটপুট $[0,1]$ পরিসরে), যা স্টেট ভেক্টরের সাথে গুণ করলে বিটওয়াইজ মাস্ক হিসেবে ভাবা যেতে পারে। উপরের ছবিতে বাম থেকে ডান দিকে নিম্নলিখিত গেটগুলো রয়েছে:\n", "* **forget gate** হিডেন ভেক্টর গ্রহণ করে এবং নির্ধারণ করে, ভেক্টর $c$ এর কোন উপাদানগুলো ভুলে যেতে হবে এবং কোনগুলো প্রেরণ করতে হবে।\n", "* **input gate** ইনপুট এবং হিডেন ভেক্টর থেকে কিছু তথ্য গ্রহণ করে এবং স্টেটে যোগ করে।\n", "* **output gate** স্টেটকে $\\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 ক্লাসিফায়ার বাস্তবায়নটি উপরে দেখা সাধারণ RNN-এর মতোই হবে:\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 কার্যকরভাবে প্রশিক্ষণ দেওয়ার জন্য, আমরা প্রথমে বড় মিনিব্যাচ (`[1,6,9]`) দিয়ে RNN সেলগুলোর প্রশিক্ষণ শুরু করতে চাই, কিন্তু তারপর তৃতীয় সিকোয়েন্সের প্রসেসিং শেষ করে ছোট মিনিব্যাচ দিয়ে প্রশিক্ষণ চালিয়ে যেতে চাই (`[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", "> **Note**: আমরা আসলে আনপ্যাক করা ফলাফল `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-তে স্থানান্তর করা এড়িয়ে চলে। আপনি [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py) ফাইলের মধ্যে `train_emb` ফাংশনের বাস্তবায়ন দেখতে পারেন।\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## দ্বিমুখী এবং বহুস্তর RNN\n", "\n", "আমাদের উদাহরণগুলোতে, সব পুনরাবৃত্ত নেটওয়ার্ক একদিকে কাজ করেছে, একটি সিকোয়েন্সের শুরু থেকে শেষ পর্যন্ত। এটি স্বাভাবিক মনে হয়, কারণ এটি আমাদের পড়া এবং কথা শোনার পদ্ধতির সাথে সাদৃশ্যপূর্ণ। তবে, অনেক বাস্তব ক্ষেত্রে আমরা ইনপুট সিকোয়েন্সে র‍্যান্ডম অ্যাক্সেস পাই, তাই পুনরাবৃত্ত গণনা উভয় দিকেই চালানো যুক্তিসঙ্গত হতে পারে। এই ধরনের নেটওয়ার্ককে **দ্বিমুখী** RNN বলা হয়, এবং এগুলো তৈরি করা যায় RNN/LSTM/GRU কনস্ট্রাক্টরে `bidirectional=True` প্যারামিটার পাস করে।\n", "\n", "দ্বিমুখী নেটওয়ার্কের ক্ষেত্রে, আমাদের দুইটি হিডেন স্টেট ভেক্টরের প্রয়োজন হবে, প্রতিটি দিকের জন্য একটি। PyTorch এই ভেক্টরগুলোকে একটি বড় আকারের ভেক্টর হিসেবে এনকোড করে, যা বেশ সুবিধাজনক, কারণ সাধারণত আপনি এই হিডেন স্টেটকে একটি সম্পূর্ণ সংযুক্ত লিনিয়ার লেয়ারে পাস করবেন, এবং লেয়ার তৈরি করার সময় এই আকার বৃদ্ধিকে বিবেচনায় নিতে হবে।\n", "\n", "পুনরাবৃত্ত নেটওয়ার্ক, একমুখী বা দ্বিমুখী, একটি সিকোয়েন্সের নির্দিষ্ট প্যাটার্নগুলো ধারণ করে এবং সেগুলো স্টেট ভেক্টরে সংরক্ষণ করতে পারে বা আউটপুটে পাস করতে পারে। কনভোলিউশনাল নেটওয়ার্কের মতো, আমরা প্রথম স্তরের উপর আরেকটি পুনরাবৃত্ত স্তর তৈরি করতে পারি, যা নিম্ন-স্তরের প্যাটার্ন থেকে উচ্চ-স্তরের প্যাটার্ন ধারণ করে। এটি আমাদের **বহুস্তর RNN** ধারণার দিকে নিয়ে যায়, যা দুই বা তার বেশি পুনরাবৃত্ত নেটওয়ার্ক নিয়ে গঠিত, যেখানে পূর্ববর্তী স্তরের আউটপুট পরবর্তী স্তরের ইনপুট হিসেবে পাস করা হয়।\n", "\n", "![একটি বহুস্তর দীর্ঘ-স্বল্প-মেয়াদী-মেমরি RNN দেখানো চিত্র](../../../../../translated_images/bn/multi-layer-lstm.dd975e29bb2a59fe.webp)\n", "\n", "*ছবি [এই অসাধারণ পোস্ট](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) থেকে, লেখক Fernando López*\n", "\n", "PyTorch এই ধরনের নেটওয়ার্ক তৈরি করাকে সহজ করে তোলে, কারণ আপনাকে শুধু RNN/LSTM/GRU কনস্ট্রাক্টরে `num_layers` প্যারামিটার পাস করতে হবে, যা স্বয়ংক্রিয়ভাবে একাধিক স্তরের পুনরাবৃত্তি তৈরি করবে। এর অর্থ হলো হিডেন/স্টেট ভেক্টরের আকার অনুপাতে বৃদ্ধি পাবে, এবং পুনরাবৃত্ত স্তরের আউটপুট পরিচালনা করার সময় আপনাকে এটি বিবেচনায় নিতে হবে।\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## অন্যান্য কাজের জন্য RNNs\n", "\n", "এই ইউনিটে, আমরা দেখেছি যে RNNs সিকোয়েন্স ক্লাসিফিকেশনের জন্য ব্যবহার করা যেতে পারে, তবে বাস্তবে, এগুলো আরও অনেক কাজ পরিচালনা করতে সক্ষম, যেমন টেক্সট জেনারেশন, মেশিন ট্রান্সলেশন, এবং আরও অনেক কিছু। আমরা পরবর্তী ইউনিটে সেই কাজগুলো বিবেচনা করব।\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n---\n\n**অস্বীকৃতি**: \nএই নথিটি AI অনুবাদ পরিষেবা [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-28T12:20:36+00:00", "source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb", "language_code": "bn" } }, "nbformat": 4, "nbformat_minor": 2 }