726 lines
43 KiB
Plaintext
726 lines
43 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## এম্বেডিংস\n",
|
||
"\n",
|
||
"আমাদের আগের উদাহরণে, আমরা উচ্চ-মাত্রার ব্যাগ-অফ-ওয়ার্ডস ভেক্টর নিয়ে কাজ করেছিলাম যার দৈর্ঘ্য ছিল `vocab_size`, এবং আমরা স্পষ্টভাবে নিম্ন-মাত্রার পজিশনাল রিপ্রেজেন্টেশন ভেক্টর থেকে স্পার্স ওয়ান-হট রিপ্রেজেন্টেশনে রূপান্তর করছিলাম। এই ওয়ান-হট রিপ্রেজেন্টেশন মেমোরি-সাশ্রয়ী নয়, তাছাড়া প্রতিটি শব্দকে একে অপরের থেকে স্বাধীনভাবে বিবেচনা করা হয়, অর্থাৎ ওয়ান-হট এনকোডেড ভেক্টরগুলো শব্দগুলোর মধ্যে কোনো অর্থবোধক সাদৃশ্য প্রকাশ করে না।\n",
|
||
"\n",
|
||
"এই ইউনিটে, আমরা **News AG** ডেটাসেট নিয়ে আরও অনুসন্ধান চালিয়ে যাব। শুরু করার জন্য, চলুন ডেটা লোড করি এবং আগের নোটবুক থেকে কিছু সংজ্ঞা নিয়ে আসি।\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Loading dataset...\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\train.csv: 29.5MB [00:01, 18.8MB/s] \n",
|
||
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\test.csv: 1.86MB [00:00, 11.2MB/s] \n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Building vocab...\n",
|
||
"Vocab size = 95812\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"import torch\n",
|
||
"import torchtext\n",
|
||
"import numpy as np\n",
|
||
"from torchnlp import *\n",
|
||
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
||
"vocab_size = len(vocab)\n",
|
||
"print(\"Vocab size = \",vocab_size)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## এমবেডিং কী?\n",
|
||
"\n",
|
||
"**এমবেডিং** ধারণাটি হলো শব্দগুলোকে নিম্ন-মাত্রার ঘন ভেক্টর দিয়ে উপস্থাপন করা, যা কোনোভাবে একটি শব্দের অর্থবোধক অর্থকে প্রতিফলিত করে। আমরা পরে আলোচনা করব কীভাবে অর্থবোধক শব্দ এমবেডিং তৈরি করা যায়, তবে আপাতত এমবেডিংকে একটি শব্দ ভেক্টরের মাত্রা কমানোর একটি পদ্ধতি হিসেবে ভাবুন।\n",
|
||
"\n",
|
||
"তাহলে, এমবেডিং লেয়ার একটি শব্দকে ইনপুট হিসেবে নেবে এবং নির্দিষ্ট `embedding_size` আকারের একটি আউটপুট ভেক্টর তৈরি করবে। এক অর্থে, এটি `Linear` লেয়ারের মতোই, তবে এক-হট এনকোডেড ভেক্টর নেওয়ার পরিবর্তে এটি একটি শব্দ নম্বরকে ইনপুট হিসেবে নিতে সক্ষম হবে।\n",
|
||
"\n",
|
||
"আমাদের নেটওয়ার্কে প্রথম লেয়ার হিসেবে এমবেডিং লেয়ার ব্যবহার করে, আমরা ব্যাগ-অফ-ওয়ার্ডস থেকে **এমবেডিং ব্যাগ** মডেলে পরিবর্তন করতে পারি, যেখানে আমরা প্রথমে আমাদের টেক্সটের প্রতিটি শব্দকে সংশ্লিষ্ট এমবেডিংয়ে রূপান্তর করি এবং তারপর সেই সমস্ত এমবেডিংয়ের উপর কিছু সামগ্রিক ফাংশন গণনা করি, যেমন `sum`, `average` বা `max`।\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"আমাদের ক্লাসিফায়ার নিউরাল নেটওয়ার্কটি এমবেডিং লেয়ার দিয়ে শুরু হবে, তারপর অ্যাগ্রিগেশন লেয়ার, এবং তার উপরে একটি লিনিয়ার ক্লাসিফায়ার:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class EmbedClassifier(torch.nn.Module):\n",
|
||
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
||
" super().__init__()\n",
|
||
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
||
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
||
"\n",
|
||
" def forward(self, x):\n",
|
||
" x = self.embedding(x)\n",
|
||
" x = torch.mean(x,dim=1)\n",
|
||
" return self.fc(x)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### ভেরিয়েবল সিকোয়েন্স সাইজ নিয়ে কাজ করা\n",
|
||
"\n",
|
||
"এই আর্কিটেকচারের ফলে, আমাদের নেটওয়ার্কের জন্য মিনিব্যাচ তৈরি করতে একটি নির্দিষ্ট পদ্ধতি অনুসরণ করতে হবে। আগের ইউনিটে, যখন ব্যাগ-অফ-ওয়ার্ডস (BoW) ব্যবহার করা হচ্ছিল, তখন একটি মিনিব্যাচের সব BoW টেনসরের আকার সমান ছিল `vocab_size`, আমাদের টেক্সট সিকোয়েন্সের প্রকৃত দৈর্ঘ্য যাই হোক না কেন। কিন্তু যখন আমরা ওয়ার্ড এম্বেডিং-এ চলে যাব, তখন প্রতিটি টেক্সট নমুনায় ভিন্ন সংখ্যক শব্দ থাকবে, এবং সেই নমুনাগুলোকে একত্রিত করে মিনিব্যাচ তৈরি করার সময় আমাদের কিছু প্যাডিং প্রয়োগ করতে হবে।\n",
|
||
"\n",
|
||
"এটি করা যেতে পারে ডেটাসোর্সে `collate_fn` ফাংশন সরবরাহ করার একই কৌশল ব্যবহার করে:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def padify(b):\n",
|
||
" # b is the list of tuples of length batch_size\n",
|
||
" # - first element of a tuple = label, \n",
|
||
" # - second = feature (text sequence)\n",
|
||
" # build vectorized sequence\n",
|
||
" v = [encode(x[1]) for x in b]\n",
|
||
" # first, compute max length of a sequence in this minibatch\n",
|
||
" l = max(map(len,v))\n",
|
||
" return ( # tuple of two tensors - labels and features\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",
|
||
" )\n",
|
||
"\n",
|
||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### এম্বেডিং ক্লাসিফায়ার প্রশিক্ষণ\n",
|
||
"\n",
|
||
"এখন যেহেতু আমরা সঠিক ডাটালোডার সংজ্ঞায়িত করেছি, আমরা পূর্ববর্তী ইউনিটে সংজ্ঞায়িত প্রশিক্ষণ ফাংশন ব্যবহার করে মডেলটি প্রশিক্ষণ দিতে পারি:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6415625\n",
|
||
"6400: acc=0.6865625\n",
|
||
"9600: acc=0.7103125\n",
|
||
"12800: acc=0.726953125\n",
|
||
"16000: acc=0.739375\n",
|
||
"19200: acc=0.75046875\n",
|
||
"22400: acc=0.7572321428571429\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(0.889799795315499, 0.7623160588611644)"
|
||
]
|
||
},
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
||
"train_epoch(net,train_loader, lr=1, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"> **নোট**: আমরা এখানে সময়ের স্বার্থে শুধুমাত্র ২৫,০০০ রেকর্ডের জন্য প্রশিক্ষণ দিচ্ছি (একটি পূর্ণ এপোকের চেয়ে কম), তবে আপনি প্রশিক্ষণ চালিয়ে যেতে পারেন, কয়েকটি এপোকের জন্য প্রশিক্ষণের একটি ফাংশন লিখতে পারেন এবং উচ্চতর সঠিকতা অর্জনের জন্য লার্নিং রেট প্যারামিটার নিয়ে পরীক্ষা করতে পারেন। আপনি প্রায় ৯০% সঠিকতা অর্জন করতে সক্ষম হবেন।\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### EmbeddingBag লেয়ার এবং ভেরিয়েবল-দৈর্ঘ্যের সিকোয়েন্স উপস্থাপনা\n",
|
||
"\n",
|
||
"পূর্ববর্তী আর্কিটেকচারে, সমস্ত সিকোয়েন্সকে একই দৈর্ঘ্যে প্যাড করতে হতো যাতে সেগুলোকে একটি মিনিব্যাচে ফিট করানো যায়। এটি ভেরিয়েবল দৈর্ঘ্যের সিকোয়েন্স উপস্থাপনার সবচেয়ে কার্যকর পদ্ধতি নয় - আরেকটি পদ্ধতি হতে পারে **অফসেট** ভেক্টর ব্যবহার করা, যা একটি বড় ভেক্টরে সংরক্ষিত সমস্ত সিকোয়েন্সের অফসেট ধারণ করবে।\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"> **Note**: উপরের ছবিতে, আমরা একটি ক্যারেক্টারের সিকোয়েন্স দেখিয়েছি, কিন্তু আমাদের উদাহরণে আমরা শব্দের সিকোয়েন্স নিয়ে কাজ করছি। তবে, অফসেট ভেক্টর দিয়ে সিকোয়েন্স উপস্থাপনার সাধারণ নীতিটি একই থাকে।\n",
|
||
"\n",
|
||
"অফসেট উপস্থাপনার সাথে কাজ করার জন্য, আমরা [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html) লেয়ার ব্যবহার করি। এটি `Embedding`-এর মতো, তবে এটি কন্টেন্ট ভেক্টর এবং অফসেট ভেক্টরকে ইনপুট হিসেবে নেয়, এবং এটি একটি অ্যাভারেজিং লেয়ার অন্তর্ভুক্ত করে, যা `mean`, `sum` বা `max` হতে পারে।\n",
|
||
"\n",
|
||
"এখানে একটি পরিবর্তিত নেটওয়ার্ক রয়েছে যা `EmbeddingBag` ব্যবহার করে:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class EmbedClassifier(torch.nn.Module):\n",
|
||
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
||
" super().__init__()\n",
|
||
" self.embedding = torch.nn.EmbeddingBag(vocab_size, embed_dim)\n",
|
||
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
||
"\n",
|
||
" def forward(self, text, off):\n",
|
||
" x = self.embedding(text, off)\n",
|
||
" return self.fc(x)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"ডেটাসেটটি প্রশিক্ষণের জন্য প্রস্তুত করতে, আমাদের একটি রূপান্তর ফাংশন প্রদান করতে হবে যা অফসেট ভেক্টর প্রস্তুত করবে:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def offsetify(b):\n",
|
||
" # first, compute data tensor from all sequences\n",
|
||
" x = [torch.tensor(encode(t[1])) for t in b]\n",
|
||
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
||
" o = [0] + [len(t) for t in x]\n",
|
||
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
||
" return ( \n",
|
||
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
||
" torch.cat(x), # text \n",
|
||
" o\n",
|
||
" )\n",
|
||
"\n",
|
||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"আগের সমস্ত উদাহরণের বিপরীতে, আমাদের নেটওয়ার্ক এখন দুটি প্যারামিটার গ্রহণ করে: ডেটা ভেক্টর এবং অফসেট ভেক্টর, যা ভিন্ন আকারের। অনুরূপভাবে, আমাদের ডেটা লোডারও আমাদের ২টির পরিবর্তে ৩টি মান প্রদান করে: টেক্সট এবং অফসেট ভেক্টর উভয়ই বৈশিষ্ট্য হিসাবে প্রদান করা হয়। সুতরাং, আমাদের প্রশিক্ষণ ফাংশনটি সামান্য সামঞ্জস্য করতে হবে যাতে এটি পরিচালনা করা যায়:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6153125\n",
|
||
"6400: acc=0.6615625\n",
|
||
"9600: acc=0.6932291666666667\n",
|
||
"12800: acc=0.715078125\n",
|
||
"16000: acc=0.7270625\n",
|
||
"19200: acc=0.7382291666666667\n",
|
||
"22400: acc=0.7486160714285715\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(22.771553103007037, 0.7551983365323096)"
|
||
]
|
||
},
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
||
"\n",
|
||
"def train_epoch_emb(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.CrossEntropyLoss(),epoch_size=None, report_freq=200):\n",
|
||
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
|
||
" loss_fn = loss_fn.to(device)\n",
|
||
" net.train()\n",
|
||
" total_loss,acc,count,i = 0,0,0,0\n",
|
||
" for labels,text,off in dataloader:\n",
|
||
" optimizer.zero_grad()\n",
|
||
" labels,text,off = labels.to(device), text.to(device), off.to(device)\n",
|
||
" out = net(text, off)\n",
|
||
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
|
||
" loss.backward()\n",
|
||
" optimizer.step()\n",
|
||
" total_loss+=loss\n",
|
||
" _,predicted = torch.max(out,1)\n",
|
||
" acc+=(predicted==labels).sum()\n",
|
||
" count+=len(labels)\n",
|
||
" i+=1\n",
|
||
" if i%report_freq==0:\n",
|
||
" print(f\"{count}: acc={acc.item()/count}\")\n",
|
||
" if epoch_size and count>epoch_size:\n",
|
||
" break\n",
|
||
" return total_loss.item()/count, acc.item()/count\n",
|
||
"\n",
|
||
"\n",
|
||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## সেমান্টিক এম্বেডিংস: Word2Vec\n",
|
||
"\n",
|
||
"আমাদের আগের উদাহরণে, মডেলের এম্বেডিং লেয়ার শব্দগুলোকে ভেক্টর রূপে মানচিত্রে পরিণত করতে শিখেছিল, তবে এই উপস্থাপনায় খুব বেশি সেমান্টিক অর্থ ছিল না। এমন একটি ভেক্টর উপস্থাপনা শেখা ভালো হবে, যেখানে একই ধরনের শব্দ বা সমার্থক শব্দগুলো এমন ভেক্টরের সাথে সম্পর্কিত হবে, যেগুলো কিছু ভেক্টর দূরত্ব (যেমন ইউক্লিডিয়ান দূরত্ব) অনুযায়ী একে অপরের কাছাকাছি থাকবে।\n",
|
||
"\n",
|
||
"এটি করতে, আমাদের একটি নির্দিষ্ট পদ্ধতিতে বড় পরিমাণে টেক্সটের উপর আমাদের এম্বেডিং মডেলকে প্রি-ট্রেন করতে হবে। সেমান্টিক এম্বেডিংস ট্রেন করার প্রথম পদ্ধতিগুলোর একটি হলো [Word2Vec](https://en.wikipedia.org/wiki/Word2vec)। এটি দুটি প্রধান আর্কিটেকচারের উপর ভিত্তি করে তৈরি, যা শব্দগুলোর একটি বিতরণকৃত উপস্থাপনা তৈরি করতে ব্যবহৃত হয়:\n",
|
||
"\n",
|
||
" - **কন্টিনিউয়াস ব্যাগ-অফ-ওয়ার্ডস** (CBoW) — এই আর্কিটেকচারে, আমরা মডেলকে আশেপাশের প্রসঙ্গ থেকে একটি শব্দ অনুমান করতে প্রশিক্ষণ দিই। ngram $(W_{-2},W_{-1},W_0,W_1,W_2)$ দেওয়া হলে, মডেলের লক্ষ্য হলো $(W_{-2},W_{-1},W_1,W_2)$ থেকে $W_0$ অনুমান করা।\n",
|
||
" - **কন্টিনিউয়াস স্কিপ-গ্রাম** CBoW-এর বিপরীত। মডেল আশেপাশের প্রসঙ্গের উইন্ডো ব্যবহার করে বর্তমান শব্দটি অনুমান করে।\n",
|
||
"\n",
|
||
"CBoW দ্রুততর, তবে স্কিপ-গ্রাম ধীর হলেও কম ঘন ঘন ব্যবহৃত শব্দগুলোর উপস্থাপনা করতে ভালো কাজ করে।\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"Google News ডেটাসেটে প্রি-ট্রেন করা word2vec এম্বেডিং নিয়ে পরীক্ষা করার জন্য, আমরা **gensim** লাইব্রেরি ব্যবহার করতে পারি। নিচে 'neural' শব্দটির সাথে সবচেয়ে মিল থাকা শব্দগুলো খুঁজে বের করা হয়েছে:\n",
|
||
"\n",
|
||
"> **Note:** যখন আপনি প্রথমবার শব্দ ভেক্টর তৈরি করেন, সেগুলো ডাউনলোড করতে কিছুটা সময় লাগতে পারে!\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import gensim.downloader as api\n",
|
||
"w2v = api.load('word2vec-google-news-300')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"neuronal -> 0.7804799675941467\n",
|
||
"neurons -> 0.7326500415802002\n",
|
||
"neural_circuits -> 0.7252851724624634\n",
|
||
"neuron -> 0.7174385190010071\n",
|
||
"cortical -> 0.6941086649894714\n",
|
||
"brain_circuitry -> 0.6923246383666992\n",
|
||
"synaptic -> 0.6699118614196777\n",
|
||
"neural_circuitry -> 0.6638563275337219\n",
|
||
"neurochemical -> 0.6555314064025879\n",
|
||
"neuronal_activity -> 0.6531826257705688\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"for w,p in w2v.most_similar('neural'):\n",
|
||
" print(f\"{w} -> {p}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"আমরা শব্দ থেকে ভেক্টর এম্বেডিংও গণনা করতে পারি, যা শ্রেণীবিন্যাস মডেল প্রশিক্ষণে ব্যবহৃত হবে (স্পষ্টতার জন্য আমরা ভেক্টরের প্রথম ২০টি উপাদানই দেখাচ্ছি):\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
|
||
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
|
||
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
|
||
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
|
||
" dtype=float32)"
|
||
]
|
||
},
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"w2v.word_vec('play')[:20]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"সেমান্টিক এম্বেডিংসের একটি চমৎকার দিক হল আপনি ভেক্টর এনকোডিংকে পরিবর্তন করে সেমান্টিক্স পরিবর্তন করতে পারেন। উদাহরণস্বরূপ, আমরা এমন একটি শব্দ খুঁজতে বলতে পারি, যার ভেক্টর উপস্থাপন *king* এবং *woman* শব্দের যতটা সম্ভব কাছাকাছি হবে, এবং *man* শব্দ থেকে যতটা সম্ভব দূরে থাকবে:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"('queen', 0.7118192911148071)"
|
||
]
|
||
},
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"CBoW এবং Skip-Grams উভয়ই \"পূর্বাভাসমূলক\" এম্বেডিং, কারণ এগুলো শুধুমাত্র স্থানীয় প্রসঙ্গগুলোকেই বিবেচনায় নেয়। Word2Vec গ্লোবাল প্রসঙ্গের সুবিধা নেয় না।\n",
|
||
"\n",
|
||
"**FastText**, Word2Vec-এর উপর ভিত্তি করে কাজ করে, যেখানে প্রতিটি শব্দ এবং সেই শব্দের মধ্যে থাকা চরিত্র n-grams-এর জন্য ভেক্টর উপস্থাপন শিখানো হয়। এই উপস্থাপনাগুলোর মান প্রতিটি প্রশিক্ষণ ধাপে গড় করে একটি ভেক্টরে রূপান্তরিত হয়। যদিও এটি প্রি-ট্রেনিংয়ে অতিরিক্ত গণনার প্রয়োজন করে, এটি শব্দ এম্বেডিং-এ সাব-ওয়ার্ড তথ্য এনকোড করতে সক্ষম করে।\n",
|
||
"\n",
|
||
"আরেকটি পদ্ধতি, **GloVe**, কো-অকারেন্স ম্যাট্রিক্সের ধারণাকে কাজে লাগায় এবং নিউরাল পদ্ধতি ব্যবহার করে কো-অকারেন্স ম্যাট্রিক্সকে আরও বেশি প্রকাশক্ষম এবং নন-লিনিয়ার শব্দ ভেক্টরে রূপান্তরিত করে।\n",
|
||
"\n",
|
||
"আপনি উদাহরণটি নিয়ে পরীক্ষা করতে পারেন FastText এবং GloVe এম্বেডিং ব্যবহার করে, কারণ gensim বিভিন্ন ধরনের শব্দ এম্বেডিং মডেল সমর্থন করে।\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## PyTorch-এ প্রি-ট্রেইনড এমবেডিং ব্যবহার করা\n",
|
||
"\n",
|
||
"আমরা উপরের উদাহরণটি পরিবর্তন করে আমাদের এমবেডিং লেয়ারে সেমান্টিক্যাল এমবেডিং, যেমন Word2Vec, দিয়ে ম্যাট্রিক্সটি প্রি-পপুলেট করতে পারি। আমাদের মনে রাখতে হবে যে প্রি-ট্রেইনড এমবেডিং এবং আমাদের টেক্সট কর্পাসের ভোকাবুলারিগুলো সম্ভবত মিলবে না, তাই আমরা অনুপস্থিত শব্দগুলোর জন্য ওজনগুলো র্যান্ডম মান দিয়ে ইনিশিয়ালাইজ করব:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 11,
|
||
"metadata": {
|
||
"tags": []
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Embedding size: 300\n",
|
||
"Populating matrix, this will take some time...Done, found 41080 words, 54732 words missing\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"embed_size = len(w2v.get_vector('hello'))\n",
|
||
"print(f'Embedding size: {embed_size}')\n",
|
||
"\n",
|
||
"net = EmbedClassifier(vocab_size,embed_size,len(classes))\n",
|
||
"\n",
|
||
"print('Populating matrix, this will take some time...',end='')\n",
|
||
"found, not_found = 0,0\n",
|
||
"for i,w in enumerate(vocab.get_itos()):\n",
|
||
" try:\n",
|
||
" net.embedding.weight[i].data = torch.tensor(w2v.get_vector(w))\n",
|
||
" found+=1\n",
|
||
" except:\n",
|
||
" net.embedding.weight[i].data = torch.normal(0.0,1.0,(embed_size,))\n",
|
||
" not_found+=1\n",
|
||
"\n",
|
||
"print(f\"Done, found {found} words, {not_found} words missing\")\n",
|
||
"net = net.to(device)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"এখন চলুন আমাদের মডেল প্রশিক্ষণ করি। লক্ষ্য করুন যে মডেল প্রশিক্ষণ করতে যে সময় লাগে তা পূর্ববর্তী উদাহরণের তুলনায় উল্লেখযোগ্যভাবে বেশি, কারণ এম্বেডিং লেয়ারের আকার বড় হওয়ার কারণে প্যারামিটারের সংখ্যা অনেক বেশি। এছাড়াও, এই কারণে, যদি আমরা ওভারফিটিং এড়াতে চাই, তাহলে আমাদের আরও উদাহরণে মডেল প্রশিক্ষণ করতে হতে পারে।\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 12,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6359375\n",
|
||
"6400: acc=0.68109375\n",
|
||
"9600: acc=0.7067708333333333\n",
|
||
"12800: acc=0.723671875\n",
|
||
"16000: acc=0.73625\n",
|
||
"19200: acc=0.7463541666666667\n",
|
||
"22400: acc=0.7560714285714286\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(214.1013875559821, 0.7626759436980166)"
|
||
]
|
||
},
|
||
"execution_count": 12,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"আমাদের ক্ষেত্রে আমরা যথেষ্ট পরিমাণে সঠিকতার বৃদ্ধি দেখতে পাই না, যা সম্ভবত ভিন্ন ভিন্ন শব্দভাণ্ডারের কারণে। \n",
|
||
"ভিন্ন শব্দভাণ্ডারের সমস্যাটি সমাধান করতে, আমরা নিম্নলিখিত সমাধানগুলোর মধ্যে একটি ব্যবহার করতে পারি: \n",
|
||
"* আমাদের শব্দভাণ্ডারে ভিত্তি করে word2vec মডেল পুনরায় প্রশিক্ষণ করা \n",
|
||
"* প্রি-ট্রেইন করা word2vec মডেলের শব্দভাণ্ডার ব্যবহার করে আমাদের ডেটাসেট লোড করা। ডেটাসেট লোড করার সময় ব্যবহৃত শব্দভাণ্ডার নির্দিষ্ট করা যেতে পারে। \n",
|
||
"\n",
|
||
"দ্বিতীয় পদ্ধতিটি সহজতর মনে হয়, বিশেষত কারণ PyTorch `torchtext` ফ্রেমওয়ার্ক এম্বেডিংয়ের জন্য বিল্ট-ইন সাপোর্ট প্রদান করে। আমরা, উদাহরণস্বরূপ, GloVe-ভিত্তিক শব্দভাণ্ডার নিম্নলিখিতভাবে তৈরি করতে পারি: \n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 14,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"100%|█████████▉| 399999/400000 [00:15<00:00, 25411.14it/s]\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"vocab = torchtext.vocab.GloVe(name='6B', dim=50)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"লোড করা ভোকাবুলারিতে নিম্নলিখিত মৌলিক কার্যক্রমগুলো রয়েছে:\n",
|
||
"* `vocab.stoi` ডিকশনারি আমাদের একটি শব্দকে তার ডিকশনারি সূচকে রূপান্তর করতে সাহায্য করে\n",
|
||
"* `vocab.itos` বিপরীত কাজটি করে - একটি সংখ্যাকে শব্দে রূপান্তর করে\n",
|
||
"* `vocab.vectors` হলো এমবেডিং ভেক্টরগুলোর অ্যারে, তাই একটি শব্দ `s`-এর এমবেডিং পেতে হলে আমাদের `vocab.vectors[vocab.stoi[s]]` ব্যবহার করতে হবে\n",
|
||
"\n",
|
||
"এখানে এমবেডিং নিয়ে কাজ করার একটি উদাহরণ দেওয়া হলো, যা সমীকরণটি প্রদর্শন করে **kind-man+woman = queen** (আমি এটি কাজ করানোর জন্য সহগটি একটু পরিবর্তন করেছি):\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"'queen'"
|
||
]
|
||
},
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"# get the vector corresponding to kind-man+woman\n",
|
||
"qvec = vocab.vectors[vocab.stoi['king']]-vocab.vectors[vocab.stoi['man']]+1.3*vocab.vectors[vocab.stoi['woman']]\n",
|
||
"# find the index of the closest embedding vector \n",
|
||
"d = torch.sum((vocab.vectors-qvec)**2,dim=1)\n",
|
||
"min_idx = torch.argmin(d)\n",
|
||
"# find the corresponding word\n",
|
||
"vocab.itos[min_idx]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"এই এম্বেডিংগুলি ব্যবহার করে শ্রেণীবিন্যাসকারীকে প্রশিক্ষণ দেওয়ার জন্য, আমাদের প্রথমে GloVe শব্দভাণ্ডার ব্যবহার করে আমাদের ডেটাসেট এনকোড করতে হবে:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 16,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def offsetify(b):\n",
|
||
" # first, compute data tensor from all sequences\n",
|
||
" x = [torch.tensor(encode(t[1],voc=vocab)) for t in b] # pass the instance of vocab to encode function!\n",
|
||
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
||
" o = [0] + [len(t) for t in x]\n",
|
||
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
||
" return ( \n",
|
||
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
||
" torch.cat(x), # text \n",
|
||
" o\n",
|
||
" )"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"যেমনটি আমরা উপরে দেখেছি, সমস্ত ভেক্টর এম্বেডিং `vocab.vectors` ম্যাট্রিক্সে সংরক্ষিত হয়। এটি এম্বেডিং লেয়ারের ওজনগুলিতে সেই ওজনগুলি সহজ কপি করার মাধ্যমে লোড করা অত্যন্ত সহজ করে তোলে:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 17,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"net = EmbedClassifier(len(vocab),len(vocab.vectors[0]),len(classes))\n",
|
||
"net.embedding.weight.data = vocab.vectors\n",
|
||
"net = net.to(device)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"এখন চলুন আমাদের মডেল প্রশিক্ষণ করি এবং দেখি আমরা ভালো ফলাফল পাই কিনা:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 18,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6271875\n",
|
||
"6400: acc=0.68078125\n",
|
||
"9600: acc=0.7030208333333333\n",
|
||
"12800: acc=0.71984375\n",
|
||
"16000: acc=0.7346875\n",
|
||
"19200: acc=0.7455729166666667\n",
|
||
"22400: acc=0.7529464285714286\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(35.53972978646833, 0.7575175943698017)"
|
||
]
|
||
},
|
||
"execution_count": 18,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)\n",
|
||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"আমাদের ডেটাসেটের কিছু শব্দ প্রি-ট্রেইনড GloVe ভোকাবুলারিতে অনুপস্থিত থাকার কারণে আমরা যথেষ্ট পরিমাণে সঠিকতার বৃদ্ধি দেখতে পাচ্ছি না, এবং তাই সেগুলো কার্যত উপেক্ষা করা হয়। এই সমস্যাটি কাটিয়ে উঠতে, আমরা আমাদের ডেটাসেটের উপর নিজস্ব এম্বেডিং ট্রেইন করতে পারি।\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## প্রসঙ্গগত এম্বেডিংস\n",
|
||
"\n",
|
||
"প্রথাগত প্রি-ট্রেইনড এম্বেডিং উপস্থাপনাগুলোর, যেমন Word2Vec, একটি প্রধান সীমাবদ্ধতা হলো শব্দার্থ বিভ্রান্তির সমস্যা। যদিও প্রি-ট্রেইনড এম্বেডিংস কিছুটা শব্দের অর্থকে প্রসঙ্গ অনুযায়ী ধারণ করতে পারে, একটি শব্দের সব সম্ভাব্য অর্থ একই এম্বেডিংয়ে এনকোড করা হয়। এটি ডাউনস্ট্রিম মডেলগুলোর ক্ষেত্রে সমস্যা সৃষ্টি করতে পারে, কারণ অনেক শব্দ, যেমন 'play', এর অর্থ প্রসঙ্গ অনুযায়ী ভিন্ন হতে পারে।\n",
|
||
"\n",
|
||
"উদাহরণস্বরূপ, 'play' শব্দটি এই দুটি বাক্যে ভিন্ন অর্থ বহন করে:\n",
|
||
"- আমি থিয়েটারে একটি **play** দেখতে গিয়েছিলাম।\n",
|
||
"- জন তার বন্ধুদের সাথে **play** করতে চায়।\n",
|
||
"\n",
|
||
"উপরের প্রি-ট্রেইনড এম্বেডিংস 'play' শব্দের এই দুটি অর্থকে একই এম্বেডিংয়ে উপস্থাপন করে। এই সীমাবদ্ধতা কাটিয়ে উঠতে, আমাদের **ভাষার মডেল** ভিত্তিক এম্বেডিং তৈরি করতে হবে, যা একটি বড় টেক্সট কর্পাসে প্রশিক্ষিত হয় এবং *জানে* কীভাবে শব্দগুলো বিভিন্ন প্রসঙ্গে একত্রিত হতে পারে। প্রসঙ্গগত এম্বেডিং নিয়ে আলোচনা এই টিউটোরিয়ালের পরিধির বাইরে, তবে আমরা পরবর্তী ইউনিটে ভাষার মডেল নিয়ে আলোচনা করার সময় এ বিষয়ে ফিরে আসব।\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": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
||
},
|
||
"kernelspec": {
|
||
"display_name": "py37_pytorch",
|
||
"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": "f50b026abce5cf36783a560ea72cb9b1",
|
||
"translation_date": "2025-08-28T12:27:42+00:00",
|
||
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
||
"language_code": "bn"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 2
|
||
} |