576 lines
17 KiB
Plaintext
576 lines
17 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "NXTSugt6ieXh"
|
|
},
|
|
"source": [
|
|
"## Treniranje CBoW modela\n",
|
|
"\n",
|
|
"Ova bilježnica dio je [AI za početnike kurikuluma](http://aka.ms/ai-beginners)\n",
|
|
"\n",
|
|
"U ovom primjeru, pogledat ćemo kako trenirati CBoW jezični model kako bismo dobili vlastiti Word2Vec prostor za ugrađivanje. Koristit ćemo AG News dataset kao izvor teksta.\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": [
|
|
"Prvo učitajmo naš skup podataka i definirajmo tokenizer i vokabular. Postavit ćemo `vocab_size` na 5000 kako bismo malo ograničili izračune.\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 model\n",
|
|
"\n",
|
|
"CBoW uči predviđati riječ na temelju $2N$ susjednih riječi. Na primjer, kada je $N=1$, dobit ćemo sljedeće parove iz rečenice *I like to train networks*: (like, I), (I, like), (to, like), (like, to), (train, to), (to, train), (networks, train), (train, networks). Ovdje je prva riječ susjedna riječ koja se koristi kao ulaz, a druga riječ je ona koju predviđamo.\n",
|
|
"\n",
|
|
"Kako bismo izgradili mrežu za predviđanje sljedeće riječi, trebamo pružiti susjednu riječ kao ulaz i dobiti broj riječi kao izlaz. Arhitektura CBoW mreže izgleda ovako:\n",
|
|
"\n",
|
|
"* Ulazna riječ prolazi kroz sloj za ugrađivanje (embedding layer). Ovaj sloj za ugrađivanje bit će naša Word2Vec ugrađenost, stoga ćemo ga definirati zasebno kao varijablu `embedder`. U ovom primjeru koristit ćemo veličinu ugrađivanja = 30, iako biste možda željeli eksperimentirati s većim dimenzijama (pravi Word2Vec ima 300).\n",
|
|
"* Vektor ugrađivanja zatim se prosljeđuje kroz linearni sloj koji će predvidjeti izlaznu riječ. Stoga ima `vocab_size` neurona.\n",
|
|
"\n",
|
|
"Za izlaz, ako koristimo `CrossEntropyLoss` kao funkciju gubitka, također ćemo morati pružiti samo brojeve riječi kao očekivane rezultate, bez one-hot kodiranja.\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": [
|
|
"## Priprema podataka za treniranje\n",
|
|
"\n",
|
|
"Sada ćemo programirati glavnu funkciju koja će izračunavati CBoW parove riječi iz teksta. Ova funkcija omogućit će nam da odredimo veličinu prozora i vratit će skup parova - ulaznu i izlaznu riječ. Imajte na umu da se ova funkcija može koristiti na riječima, kao i na vektorima/tensorima - što će nam omogućiti kodiranje teksta prije nego što ga proslijedimo funkciji `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": [
|
|
"Pripremimo skup podataka za treniranje. Proći ćemo kroz sve vijesti, pozvati `to_cbow` kako bismo dobili popis parova riječi, i dodati te parove u `X` i `Y`. Radi uštede vremena, razmotrit ćemo samo prvih 10k vijesti - lako možete ukloniti ovo ograničenje ako imate više vremena za čekanje i želite dobiti bolje ugrađivanja :)\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": [
|
|
"Također ćemo pretvoriti te podatke u jedan skup podataka i kreirati dataloader:\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": [
|
|
"Također ćemo pretvoriti te podatke u jedan skup podataka i stvoriti učitavač podataka:\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": [
|
|
"Sada krenimo s pravim treniranjem. Koristit ćemo optimizer `SGD` s prilično visokom stopom učenja. Također možete isprobati i druge optimizere, poput `Adam`. Trenirat ćemo 10 epoha za početak - i možete ponovno pokrenuti ovu ćeliju ako želite još manji gubitak.\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": [
|
|
"## Isprobavanje Word2Vec-a\n",
|
|
"\n",
|
|
"Za korištenje Word2Vec-a, izdvojimo vektore koji odgovaraju svim riječima u našem vokabularu:\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": [
|
|
"Pogledajmo, na primjer, kako se riječ **Pariz** kodira u vektor:\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": [
|
|
"Zanimljivo je koristiti Word2Vec za traženje sinonima. Sljedeća funkcija će vratiti `n` najbližih riječi za dani unos. Kako bismo ih pronašli, izračunavamo normu $|w_i - v|$, gdje je $v$ vektor koji odgovara našoj ulaznoj riječi, a $w_i$ je kodiranje $i$-te riječi u vokabularu. Zatim sortiramo niz i vraćamo odgovarajuće indekse koristeći `argsort`, te uzimamo prvih `n` elemenata liste, koji kodiraju pozicije najbližih riječi u vokabularu.\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": [
|
|
"## Ključne točke\n",
|
|
"\n",
|
|
"Koristeći pametne tehnike poput CBoW-a, možemo trenirati Word2Vec model. Također možete pokušati trenirati skip-gram model koji je osposobljen za predviđanje susjedne riječi na temelju središnje, i provjeriti koliko dobro funkcionira.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Odricanje od odgovornosti**: \nOvaj dokument je preveden korištenjem AI usluge za prevođenje [Co-op Translator](https://github.com/Azure/co-op-translator). Iako nastojimo osigurati točnost, imajte na umu da automatski prijevodi mogu sadržavati pogreške ili netočnosti. Izvorni dokument na izvornom jeziku treba smatrati mjerodavnim izvorom. Za ključne informacije preporučuje se profesionalni prijevod od strane stručnjaka. Ne preuzimamo odgovornost za bilo kakve nesporazume ili pogrešne interpretacije proizašle iz korištenja ovog prijevoda.\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-30T08:01:31+00:00",
|
|
"source_file": "lessons/5-NLP/15-LanguageModeling/CBoW-PyTorch.ipynb",
|
|
"language_code": "hr"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 0
|
|
} |