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

576 lines
22 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",
"Αυτό το σημειωματάριο αποτελεί μέρος του [Προγράμματος Σπουδών AI για Αρχάριους](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": [
"Πρώτα ας φορτώσουμε το σύνολο δεδομένων μας και να ορίσουμε τον tokenizer και το λεξιλόγιο. Θα ορίσουμε το `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",
"* Η λέξη εισόδου περνάει μέσα από το επίπεδο ενσωμάτωσης (embedding layer). Αυτό το επίπεδο ενσωμάτωσης θα είναι η ενσωμάτωση Word2Vec μας, επομένως θα το ορίσουμε ξεχωριστά ως μεταβλητή `embedder`. Σε αυτό το παράδειγμα, θα χρησιμοποιήσουμε μέγεθος ενσωμάτωσης = 30, αν και μπορείτε να πειραματιστείτε με μεγαλύτερες διαστάσεις (το πραγματικό Word2Vec έχει 300).\n",
"* Το διάνυσμα ενσωμάτωσης θα περάσει στη συνέχεια σε ένα γραμμικό επίπεδο (linear layer) που θα προβλέπει την έξοδο λέξης. Επομένως, αυτό έχει `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`. Για λόγους χρόνου, θα εξετάσουμε μόνο τα πρώτα 10k ειδήσεις - μπορείτε εύκολα να αφαιρέσετε τον περιορισμό σε περίπτωση που έχετε περισσότερο χρόνο να περιμένετε και θέλετε να αποκτήσετε καλύτερες ενσωματώσεις :)\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Αυτό το έγγραφο έχει μεταφραστεί χρησιμοποιώντας την υπηρεσία αυτόματης μετάφρασης AI [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-29T10:41:18+00:00",
"source_file": "lessons/5-NLP/15-LanguageModeling/CBoW-PyTorch.ipynb",
"language_code": "el"
}
},
"nbformat": 4,
"nbformat_minor": 0
}