AI-For-Beginners/translations/fa/lessons/5-NLP/15-LanguageModeling/CBoW-PyTorch.ipynb

576 lines
20 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

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": {
"id": "NXTSugt6ieXh"
},
"source": [
"## آموزش مدل CBoW\n",
"\n",
"این نوت‌بوک بخشی از [برنامه درسی هوش مصنوعی برای مبتدیان](http://aka.ms/ai-beginners) است.\n",
"\n",
"در این مثال، ما به آموزش مدل زبانی CBoW می‌پردازیم تا فضای تعبیه Word2Vec خودمان را ایجاد کنیم. برای این کار از مجموعه داده AG News به عنوان منبع متن استفاده خواهیم کرد.\n"
]
},
{
"cell_type": "code",
"source": [
"import torch\n",
"import torchtext\n",
"import os\n",
"import collections\n",
"import builtins\n",
"import random\n",
"import numpy as np"
],
"metadata": {
"id": "q-UiiJUKaxHj"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")"
],
"metadata": {
"id": "TFbR8CZaTZ1q"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"ابتدا بیایید مجموعه داده خود را بارگذاری کنیم و توکنایزر و واژگان را تعریف کنیم. ما مقدار `vocab_size` را به ۵۰۰۰ تنظیم می‌کنیم تا محاسبات کمی محدود شود.\n"
],
"metadata": {
"id": "HIwC7lI5T-ov"
}
},
{
"cell_type": "code",
"source": [
"def load_dataset(ngrams = 1, min_freq = 1, vocab_size = 5000 , lines_cnt = 500):\n",
" tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n",
" print(\"Loading dataset...\")\n",
" test_dataset, train_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
" train_dataset = list(train_dataset)\n",
" test_dataset = list(test_dataset)\n",
" classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n",
" print('Building vocab...')\n",
" counter = collections.Counter()\n",
" for i, (_, line) in enumerate(train_dataset):\n",
" counter.update(torchtext.data.utils.ngrams_iterator(tokenizer(line),ngrams=ngrams))\n",
" if i == lines_cnt:\n",
" break\n",
" vocab = torchtext.vocab.Vocab(collections.Counter(dict(counter.most_common(vocab_size))), min_freq=min_freq)\n",
" return train_dataset, test_dataset, classes, vocab, tokenizer"
],
"metadata": {
"id": "wdZuygtgiuLG"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"train_dataset, test_dataset, _, vocab, tokenizer = load_dataset()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "4d1nU1gsivGu",
"outputId": "949fe272-ae0e-49f5-c373-6703458b3a74"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Loading dataset...\n",
"Building vocab...\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"def encode(x, vocabulary, tokenizer = tokenizer):\n",
" return [vocabulary[s] for s in tokenizer(x)]"
],
"metadata": {
"id": "1XDYNhG8ToFV"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "LIlQk6_PaHVY"
},
"source": [
"## مدل CBoW\n",
"\n",
"مدل CBoW یاد می‌گیرد که یک کلمه را بر اساس $2N$ کلمات همسایه پیش‌بینی کند. برای مثال، وقتی $N=1$ باشد، از جمله *I like to train networks* جفت‌های زیر را خواهیم داشت: (like,I)، (I, like)، (to, like)، (like,to)، (train,to)، (to, train)، (networks, train)، (train,networks). در اینجا، کلمه اول کلمه همسایه‌ای است که به عنوان ورودی استفاده می‌شود و کلمه دوم همان کلمه‌ای است که پیش‌بینی می‌کنیم.\n",
"\n",
"برای ساخت یک شبکه که کلمه بعدی را پیش‌بینی کند، باید کلمه همسایه را به عنوان ورودی ارائه دهیم و شماره کلمه را به عنوان خروجی دریافت کنیم. معماری شبکه CBoW به صورت زیر است:\n",
"\n",
"* کلمه ورودی از لایه تعبیه عبور می‌کند. این لایه تعبیه همان تعبیه Word2Vec ما خواهد بود، بنابراین آن را به صورت جداگانه به عنوان متغیر `embedder` تعریف می‌کنیم. در این مثال از اندازه تعبیه = 30 استفاده می‌کنیم، هرچند ممکن است بخواهید با ابعاد بالاتر آزمایش کنید (Word2Vec واقعی دارای 300 است).\n",
"* بردار تعبیه سپس به یک لایه خطی منتقل می‌شود که کلمه خروجی را پیش‌بینی می‌کند. بنابراین این لایه دارای تعداد نورون‌های `vocab_size` است.\n",
"\n",
"برای خروجی، اگر از `CrossEntropyLoss` به عنوان تابع خطا استفاده کنیم، باید فقط شماره کلمات را به عنوان نتایج مورد انتظار ارائه دهیم، بدون استفاده از کدگذاری یک‌داغ.\n"
]
},
{
"cell_type": "code",
"source": [
"vocab_size = len(vocab)\n",
"\n",
"embedder = torch.nn.Embedding(num_embeddings = vocab_size, embedding_dim = 30)\n",
"model = torch.nn.Sequential(\n",
" embedder,\n",
" torch.nn.Linear(in_features = 30, out_features = vocab_size),\n",
")\n",
"\n",
"print(model)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "akKTcKQKkfl2",
"outputId": "da687e3e-a8ec-4c1a-e456-ab8cd6ac7dad"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Sequential(\n",
" (0): Embedding(5002, 30)\n",
" (1): Linear(in_features=30, out_features=5002, bias=True)\n",
")\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Nud6jgGPaHVa"
},
"source": [
"## آماده‌سازی داده‌های آموزشی\n",
"\n",
"حالا بیایید تابع اصلی را برنامه‌نویسی کنیم که زوج‌های کلمات CBoW را از متن محاسبه می‌کند. این تابع به ما امکان می‌دهد اندازه پنجره را مشخص کنیم و مجموعه‌ای از زوج‌ها - کلمه ورودی و خروجی - را برگرداند. توجه داشته باشید که این تابع می‌تواند هم روی کلمات و هم روی بردارها/تنسورها استفاده شود - که به ما اجازه می‌دهد متن را رمزگذاری کنیم، قبل از اینکه آن را به تابع `to_cbow` ارسال کنیم.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "x-dsXygOieXn",
"outputId": "c2218280-e540-40ba-9546-efe48d0d714f"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[['like', 'I'], ['to', 'I'], ['I', 'like'], ['to', 'like'], ['train', 'like'], ['I', 'to'], ['like', 'to'], ['train', 'to'], ['networks', 'to'], ['like', 'train'], ['to', 'train'], ['networks', 'train'], ['to', 'networks'], ['train', 'networks']]\n",
"[[232, 172], [5, 172], [172, 232], [5, 232], [0, 232], [172, 5], [232, 5], [0, 5], [1202, 5], [232, 0], [5, 0], [1202, 0], [5, 1202], [0, 1202]]\n"
]
}
],
"source": [
"def to_cbow(sent,window_size=2):\n",
" res = []\n",
" for i,x in enumerate(sent):\n",
" for j in range(max(0,i-window_size),min(i+window_size+1,len(sent))):\n",
" if i!=j:\n",
" res.append([sent[j],x])\n",
" return res\n",
"\n",
"print(to_cbow(['I','like','to','train','networks']))\n",
"print(to_cbow(encode('I like to train networks', vocab)))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "XVaaDLjaaHVb"
},
"source": [
"بیایید مجموعه داده آموزشی را آماده کنیم. ما تمام اخبار را مرور می‌کنیم، `to_cbow` را فراخوانی می‌کنیم تا لیست جفت‌های کلمات را دریافت کنیم و آن جفت‌ها را به `X` و `Y` اضافه می‌کنیم. به خاطر صرفه‌جویی در زمان، فقط ۱۰ هزار خبر اول را در نظر می‌گیریم - شما می‌توانید به راحتی این محدودیت را حذف کنید اگر زمان بیشتری برای انتظار دارید و می‌خواهید تعبیه‌های بهتری دریافت کنید :)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "54b-Gd9TieXo"
},
"outputs": [],
"source": [
"X = []\n",
"Y = []\n",
"for i, x in zip(range(10000), train_dataset):\n",
" for w1, w2 in to_cbow(encode(x[1], vocab), window_size = 5):\n",
" X.append(w1)\n",
" Y.append(w2)\n",
"\n",
"X = torch.tensor(X)\n",
"Y = torch.tensor(Y)"
]
},
{
"cell_type": "markdown",
"source": [
"ما همچنین آن داده‌ها را به یک مجموعه داده تبدیل می‌کنیم و یک بارگذار داده ایجاد می‌کنیم:\n"
],
"metadata": {
"id": "cwWy0PzXWhN5"
}
},
{
"cell_type": "code",
"source": [
"class SimpleIterableDataset(torch.utils.data.IterableDataset):\n",
" def __init__(self, X, Y):\n",
" super(SimpleIterableDataset).__init__()\n",
" self.data = []\n",
" for i in range(len(X)):\n",
" self.data.append( (Y[i], X[i]) )\n",
" random.shuffle(self.data)\n",
"\n",
" def __iter__(self):\n",
" return iter(self.data)"
],
"metadata": {
"id": "mfoAcGPFZU8p"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "e4NQ_-5waHVc"
},
"source": [
"ما همچنین آن داده‌ها را به یک مجموعه داده تبدیل می‌کنیم و یک بارگذار داده ایجاد می‌کنیم:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "AbLUcojlieXo"
},
"outputs": [],
"source": [
"ds = SimpleIterableDataset(X, Y)\n",
"dl = torch.utils.data.DataLoader(ds, batch_size = 256)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "pKQr7sXeaHVc"
},
"source": [
"حالا بیایید آموزش واقعی را شروع کنیم. ما از بهینه‌ساز `SGD` با نرخ یادگیری نسبتاً بالا استفاده خواهیم کرد. همچنین می‌توانید با سایر بهینه‌سازها مانند `Adam` آزمایش کنید. ما ابتدا برای ۱۰ دوره آموزش خواهیم داد - و اگر می‌خواهید خطای کمتری داشته باشید، می‌توانید این سلول را دوباره اجرا کنید.\n"
]
},
{
"cell_type": "code",
"source": [
"def train_epoch(net, dataloader, lr = 0.01, optimizer = None, loss_fn = torch.nn.CrossEntropyLoss(), epochs = None, report_freq = 1):\n",
" optimizer = optimizer or torch.optim.Adam(net.parameters(), lr = lr)\n",
" loss_fn = loss_fn.to(device)\n",
" net.train()\n",
"\n",
" for i in range(epochs):\n",
" total_loss, j = 0, 0, \n",
" for labels, features in dataloader:\n",
" optimizer.zero_grad()\n",
" features, labels = features.to(device), labels.to(device)\n",
" out = net(features)\n",
" loss = loss_fn(out, labels)\n",
" loss.backward()\n",
" optimizer.step()\n",
" total_loss += loss\n",
" j += 1\n",
" if i % report_freq == 0:\n",
" print(f\"Epoch: {i+1}: loss={total_loss.item()/j}\")\n",
"\n",
" return total_loss.item()/j"
],
"metadata": {
"id": "HeeCYKr_KF1w"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"train_epoch(net = model, dataloader = dl, optimizer = torch.optim.SGD(model.parameters(), lr = 0.1), loss_fn = torch.nn.CrossEntropyLoss(), epochs = 10)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "KVgwGtDHgDlT",
"outputId": "2447833f-f0e3-4566-c33d-addbfe2f451d"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Epoch: 1: loss=5.664632366860172\n",
"Epoch: 2: loss=5.632101973960962\n",
"Epoch: 3: loss=5.610399051405015\n",
"Epoch: 4: loss=5.594621561080262\n",
"Epoch: 5: loss=5.582538017415446\n",
"Epoch: 6: loss=5.572900234519603\n",
"Epoch: 7: loss=5.564951676341915\n",
"Epoch: 8: loss=5.558288112064614\n",
"Epoch: 9: loss=5.552576955031129\n",
"Epoch: 10: loss=5.547634165194347\n"
]
},
{
"output_type": "execute_result",
"data": {
"text/plain": [
"5.547634165194347"
]
},
"metadata": {},
"execution_count": 16
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "W8u2qXZmaHVd"
},
"source": [
"## امتحان Word2Vec\n",
"\n",
"برای استفاده از Word2Vec، بیایید بردارهای مربوط به تمام کلمات موجود در واژگان خود را استخراج کنیم:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "r8TatcXjkU_t"
},
"outputs": [],
"source": [
"vectors = torch.stack([embedder(torch.tensor(vocab[s])) for s in vocab.itos], 0)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3OcX21UOaHVd"
},
"source": [
"بیایید ببینیم، به عنوان مثال، چگونه کلمه **پاریس** به یک بردار رمزگذاری می‌شود:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "bz6tAeLzieXp",
"outputId": "5b20850e-4342-45e9-f840-cfac2b4d61d8"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"tensor([-0.0915, 2.1224, -0.0281, -0.6819, 1.1219, 0.6458, -1.3704, -1.3314,\n",
" -1.1437, 0.4496, 0.2301, -0.3515, -0.8485, 1.0481, 0.4386, -0.8949,\n",
" 0.5644, 1.0939, -2.5096, 3.2949, -0.2601, -0.8640, 0.1421, -0.0804,\n",
" -0.5083, -1.0560, 0.9753, -0.5949, -1.6046, 0.5774],\n",
" grad_fn=<EmbeddingBackward>)\n"
]
}
],
"source": [
"paris_vec = embedder(torch.tensor(vocab['paris']))\n",
"print(paris_vec)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "pHTJlaeYaHVd"
},
"source": [
"جالب است که از Word2Vec برای یافتن مترادف‌ها استفاده کنیم. تابع زیر `n` کلمه نزدیک به ورودی داده شده را بازمی‌گرداند. برای یافتن آن‌ها، نُرم $|w_i - v|$ را محاسبه می‌کنیم، که در آن $v$ بردار مربوط به کلمه ورودی ما است و $w_i$ کدگذاری کلمه $i$-ام در واژگان است. سپس آرایه را مرتب کرده و شاخص‌های مربوطه را با استفاده از `argsort` بازمی‌گردانیم و اولین `n` عنصر لیست را که موقعیت کلمات نزدیک در واژگان را کدگذاری می‌کنند، انتخاب می‌کنیم.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "NlZyi-_olFar",
"outputId": "b5dbb163-88c4-4d5a-eaf2-6751f700e98c"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"['microsoft', 'quoted', 'lp', 'rate', 'top']"
]
},
"metadata": {},
"execution_count": 56
}
],
"source": [
"def close_words(x, n = 5):\n",
" vec = embedder(torch.tensor(vocab[x]))\n",
" top5 = np.linalg.norm(vectors.detach().numpy() - vec.detach().numpy(), axis = 1).argsort()[:n]\n",
" return [ vocab.itos[x] for x in top5 ]\n",
"\n",
"close_words('microsoft')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "-dQq7xeAln0U",
"outputId": "66f768c3-c248-4bfd-ce4f-c8ffc6d0dd0d"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"['basketball', 'lot', 'sinai', 'states', 'healthdaynews']"
]
},
"metadata": {},
"execution_count": 51
}
],
"source": [
"close_words('basketball')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "fJXqK26b29sa",
"outputId": "78f0baba-ffd0-485a-dd87-0a12bedfd7fa"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"['funds', 'travel', 'sydney', 'japan', 'business']"
]
},
"metadata": {},
"execution_count": 77
}
],
"source": [
"close_words('funds')"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "My0VeTDd3Ji8"
},
"source": [
"## نکات مهم\n",
"\n",
"با استفاده از تکنیک‌های هوشمندانه مانند CBoW، می‌توانیم مدل Word2Vec را آموزش دهیم. همچنین می‌توانید مدل skip-gram را امتحان کنید که برای پیش‌بینی کلمات همسایه با توجه به کلمه مرکزی آموزش داده می‌شود و ببینید عملکرد آن چقدر خوب است.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**سلب مسئولیت**: \nاین سند با استفاده از سرویس ترجمه هوش مصنوعی [Co-op Translator](https://github.com/Azure/co-op-translator) ترجمه شده است. در حالی که ما تلاش می‌کنیم دقت را حفظ کنیم، لطفاً توجه داشته باشید که ترجمه‌های خودکار ممکن است شامل خطاها یا نادرستی‌ها باشند. سند اصلی به زبان اصلی آن باید به عنوان منبع معتبر در نظر گرفته شود. برای اطلاعات حساس، توصیه می‌شود از ترجمه حرفه‌ای انسانی استفاده کنید. ما مسئولیتی در قبال سوء تفاهم‌ها یا تفسیرهای نادرست ناشی از استفاده از این ترجمه نداریم.\n"
]
}
],
"metadata": {
"colab": {
"collapsed_sections": [],
"name": "CBoW-PyTorch.ipynb",
"provenance": []
},
"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"
},
"orig_nbformat": 4,
"gpuClass": "standard",
"coopTranslator": {
"original_hash": "36df28efe3fe40b6fb0a7fa48fe3ea82",
"translation_date": "2025-08-31T16:55:55+00:00",
"source_file": "lessons/5-NLP/15-LanguageModeling/CBoW-PyTorch.ipynb",
"language_code": "fa"
}
},
"nbformat": 4,
"nbformat_minor": 0
}