576 lines
18 KiB
Plaintext
576 lines
18 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"id": "NXTSugt6ieXh"
|
||
},
|
||
"source": [
|
||
"## אימון מודל CBoW\n",
|
||
"\n",
|
||
"מחברת זו היא חלק מתוכנית הלימודים [AI for Beginners Curriculum](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` ל-5000 כדי להגביל מעט את החישובים.\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` כפונקציית הפסד, נצטרך גם לספק רק מספרי מילים כתוצאות צפויות, ללא קידוד one-hot.\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`. מטעמי זמן, נשקול רק את 10,000 פריטי החדשות הראשונים - ניתן להסיר את ההגבלה בקלות אם יש לכם יותר זמן להמתין ורוצים לקבל הטמעות טובות יותר :)\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`. נתחיל באימון של 10 אפוקים - ואתם יכולים להריץ מחדש את התא הזה אם תרצו הפסד אפילו נמוך יותר.\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-28T21:36:17+00:00",
|
||
"source_file": "lessons/5-NLP/15-LanguageModeling/CBoW-PyTorch.ipynb",
|
||
"language_code": "he"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 0
|
||
} |