726 lines
34 KiB
Plaintext
726 lines
34 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` است، اما به جای دریافت بردار یکداغ (one-hot encoded)، میتواند شمارهی کلمه را به عنوان ورودی دریافت کند.\n",
|
||
"\n",
|
||
"با استفاده از لایهی تعبیه به عنوان اولین لایه در شبکهی خود، میتوانیم از مدل کیسهی کلمات (bag-of-words) به مدل **کیسهی تعبیهها** (embedding bag) تغییر دهیم، جایی که ابتدا هر کلمه در متن خود را به تعبیهی مربوطه تبدیل میکنیم و سپس یک تابع تجمعی مانند `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",
|
||
"به دلیل این معماری، مینیبچها برای شبکه ما باید به شیوه خاصی ایجاد شوند. در واحد قبلی، زمانی که از روش کیسه کلمات (bag-of-words) استفاده میکردیم، تمام تنسورهای BoW در یک مینیبچ اندازهای برابر با `vocab_size` داشتند، بدون توجه به طول واقعی دنباله متن. اما وقتی به استفاده از تعبیههای کلمات (word embeddings) میپردازیم، تعداد کلمات در هر نمونه متن متغیر خواهد بود و هنگام ترکیب این نمونهها در مینیبچها، باید از تکنیک پر کردن (padding) استفاده کنیم.\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",
|
||
"در معماری قبلی، لازم بود تمام دنبالهها را به یک طول یکسان پر کنیم تا بتوانیم آنها را در یک دسته کوچک قرار دهیم. این روش کارآمدترین راه برای نمایش دنبالههای با طول متغیر نیست - یک روش دیگر استفاده از بردار **offset** است که جابجاییهای تمام دنبالهها را که در یک بردار بزرگ ذخیره شدهاند، نگه میدارد.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"> **Note**: در تصویر بالا، یک دنباله از کاراکترها نشان داده شده است، اما در مثال ما با دنبالههای کلمات کار میکنیم. با این حال، اصل کلی نمایش دنبالهها با استفاده از بردار offset همچنان یکسان باقی میماند.\n",
|
||
"\n",
|
||
"برای کار با نمایش offset، از لایه [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html) استفاده میکنیم. این لایه مشابه `Embedding` است، اما بردار محتوا و بردار offset را به عنوان ورودی میگیرد و همچنین شامل یک لایه میانگینگیری است که میتواند `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_0$ از $(W_{-2},W_{-1},W_1,W_2)$ است.\n",
|
||
"- **اسکیپگرام پیوسته** برخلاف CBoW عمل میکند. مدل از پنجرهای از کلمات زمینه اطراف برای پیشبینی کلمه فعلی استفاده میکند.\n",
|
||
"\n",
|
||
"CBoW سریعتر است، در حالی که اسکیپگرام کندتر است اما در نمایش کلمات نادر بهتر عمل میکند.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"برای آزمایش تعبیه Word2Vec که بر روی مجموعه داده Google News پیشپردازش شده است، میتوانیم از کتابخانه **gensim** استفاده کنیم. در زیر کلماتی که بیشترین شباهت را به 'neural' دارند پیدا میکنیم:\n",
|
||
"\n",
|
||
"> **توجه:** زمانی که برای اولین بار بردارهای کلمات را ایجاد میکنید، دانلود آنها ممکن است کمی زمانبر باشد!\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": [
|
||
"نکته عالی درباره تعبیههای معنایی این است که میتوانید رمزگذاری برداری را برای تغییر معنا دستکاری کنید. برای مثال، میتوانیم بخواهیم کلمهای پیدا کنیم که نمایش برداری آن تا حد ممکن به کلمات *پادشاه* و *زن* نزدیک باشد و از کلمه *مرد* دور باشد:\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-gramهای کاراکتری موجود در هر کلمه کار میکند. مقادیر این نمایشها در هر مرحله آموزش به یک بردار میانگینگیری میشوند. اگرچه این روش محاسبات اضافی زیادی به پیشآموزش اضافه میکند، اما به تعبیههای کلمات اجازه میدهد اطلاعات زیر-کلمهای را رمزگذاری کنند.\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",
|
||
"روش دوم به نظر سادهتر میآید، بهویژه به این دلیل که چارچوب `torchtext` در PyTorch پشتیبانی داخلی برای embeddings دارد. به عنوان مثال، میتوانیم واژگان مبتنی بر 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",
|
||
"- من به یک **نمایش** در تئاتر رفتم.\n",
|
||
"- جان میخواهد با دوستانش **بازی** کند.\n",
|
||
"\n",
|
||
"تعبیههای پیشآموزششده بالا هر دو معنای کلمه «play» را در یک تعبیه نمایش میدهند. برای غلبه بر این محدودیت، باید تعبیههایی بر اساس **مدل زبان** بسازیم که بر روی یک مجموعه بزرگ از متن آموزش داده شده و *میداند* چگونه کلمات میتوانند در زمینههای مختلف کنار هم قرار گیرند. بحث درباره تعبیههای زمینهای خارج از محدوده این آموزش است، اما زمانی که درباره مدلهای زبان در واحد بعدی صحبت کنیم، به آنها بازخواهیم گشت.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**سلب مسئولیت**: \nاین سند با استفاده از سرویس ترجمه هوش مصنوعی [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-31T17:15:26+00:00",
|
||
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
||
"language_code": "fa"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 2
|
||
} |