AI-For-Beginners/translations/he/lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb

727 lines
31 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": {},
"source": [
"## הטמעות\n",
"\n",
"בדוגמה הקודמת שלנו, עבדנו עם וקטורים של שקי מילים בממדים גבוהים באורך `vocab_size`, והמרנו באופן מפורש מווקטורי ייצוג מיקום בממדים נמוכים לייצוג דל של one-hot. ייצוג ה-one-hot הזה אינו יעיל מבחינת זיכרון, בנוסף, כל מילה מטופלת באופן עצמאי, כלומר וקטורי one-hot מקודדים אינם מבטאים שום דמיון סמנטי בין מילים.\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, היא תוכל לקבל מספר מילה כקלט.\n",
"\n",
"על ידי שימוש בשכבת הטמעה כשכבה הראשונה ברשת שלנו, נוכל לעבור ממודל bag-of-words למודל **embedding bag**, שבו קודם כל נמיר כל מילה בטקסט שלנו להטמעה המתאימה שלה, ואז נחשב פונקציית צבירה כלשהי על כל ההטמעות הללו, כמו `sum`, `average` או `max`.\n",
"\n",
"![תמונה המציגה מסווג הטמעות עבור חמש מילים ברצף.](../../../../../translated_images/he/embedding-classifier-example.b77f021a7ee67eee.webp)\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, נמצא את עצמנו עם מספר משתנה של מילים בכל דוגמת טקסט, וכאשר משלבים את הדוגמאות הללו למיניבאצ'ים, נצטרך להוסיף ריפוד.\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": [
"### אימון מסווג embedding\n",
"\n",
"עכשיו, לאחר שהגדרנו את ה-dataloader בצורה נכונה, אנחנו יכולים לאמן את המודל באמצעות פונקציית האימון שהגדרנו ביחידה הקודמת:\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": [
"> **הערה**: אנו מאמנים כאן רק עבור 25k רשומות (פחות מאפוק מלא אחד) מטעמי זמן, אך ניתן להמשיך באימון, לכתוב פונקציה לאימון עבור מספר אפוקים, ולנסות עם פרמטר קצב הלמידה כדי להגיע לדיוק גבוה יותר. אתם אמורים להיות מסוגלים להגיע לדיוק של כ-90%.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### שכבת EmbeddingBag וייצוג רצפים באורך משתנה\n",
"\n",
"בארכיטקטורה הקודמת, היינו צריכים לרפד את כל הרצפים לאורך אחיד כדי להתאים אותם למיני-באטץ'. זו לא הדרך היעילה ביותר לייצג רצפים באורך משתנה - גישה אחרת תהיה להשתמש בוקטור **offset**, שמחזיק את ההיסטים של כל הרצפים המאוחסנים בוקטור גדול אחד.\n",
"\n",
"![תמונה המציגה ייצוג רצף עם היסטים](../../../../../translated_images/he/offset-sequence-representation.eb73fcefb29b46ee.webp)\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": [
"שימו לב, שבניגוד לכל הדוגמאות הקודמות, הרשת שלנו כעת מקבלת שני פרמטרים: וקטור נתונים ווקטור הזזה, שהם בגדלים שונים. באופן דומה, טוען הנתונים שלנו מספק לנו 3 ערכים במקום 2: גם וקטורי הטקסט וגם וקטורי ההזזה מסופקים כתכונות. לכן, עלינו להתאים מעט את פונקציית האימון שלנו כדי לטפל בכך:\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",
"- **Continuous bag-of-words** (CBoW) — בארכיטקטורה זו, אנו מאמנים את המודל לנבא מילה מתוך ההקשר שסביבה. בהינתן הנגרם $(W_{-2},W_{-1},W_0,W_1,W_2)$, מטרת המודל היא לנבא את $W_0$ מתוך $(W_{-2},W_{-1},W_1,W_2)$.\n",
"- **Continuous skip-gram** הוא ההפך מ-CBoW. המודל משתמש בחלון ההקשר של המילים שסביב כדי לנבא את המילה הנוכחית.\n",
"\n",
"CBoW מהיר יותר, בעוד ש-Skip-Gram איטי יותר, אך מבצע עבודה טובה יותר בייצוג מילים נדירות.\n",
"\n",
"![תמונה המציגה את האלגוריתמים CBoW ו-Skip-Gram להמרת מילים לוקטורים.](../../../../../translated_images/he/example-algorithms-for-converting-words-to-vectors.fbe9207a726922f6.webp)\n",
"\n",
"כדי להתנסות בהטמעת Word2Vec שאומנה מראש על מאגר הנתונים של Google News, נוכל להשתמש בספריית **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": [
"אנחנו יכולים גם לחשב הטמעות וקטוריות מהמילה, לשימוש באימון מודל סיווג (אנחנו מציגים רק את 20 הרכיבים הראשונים של הווקטור לצורך בהירות):\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-grams של תווים שנמצאים בתוך כל מילה. הערכים של הייצוגים הללו ממוצעים לווקטור אחד בכל שלב של האימון. למרות שזה מוסיף חישובים נוספים משמעותיים בשלב הקדם-אימון, זה מאפשר להטמעות המילים לקודד מידע על תת-מילים.\n",
"\n",
"שיטה נוספת, **GloVe**, מנצלת את הרעיון של מטריצת שכיחויות משותפות (co-occurrence matrix), ומשתמשת בשיטות נוירוניות כדי לפרק את מטריצת השכיחויות לוקטורי מילים יותר אקספרסיביים ולא ליניאריים.\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 מכילה תמיכה מובנית בהטמעות. \n",
"לדוגמה, ניתן ליצור אוצר מילים מבוסס 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-28T21:57:31+00:00",
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
"language_code": "he"
}
},
"nbformat": 4,
"nbformat_minor": 2
}