460 lines
35 KiB
Plaintext
460 lines
35 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Επαναλαμβανόμενα Νευρωνικά Δίκτυα\n",
|
||
"\n",
|
||
"Στην προηγούμενη ενότητα, καλύψαμε πλούσιες σημασιολογικές αναπαραστάσεις κειμένου. Η αρχιτεκτονική που χρησιμοποιούσαμε αποτύπωνε τη συνολική σημασία των λέξεων σε μια πρόταση, αλλά δεν λάμβανε υπόψη τη **σειρά** των λέξεων, επειδή η λειτουργία συσσωμάτωσης που ακολουθεί τις ενσωματώσεις αφαιρεί αυτή την πληροφορία από το αρχικό κείμενο. Επειδή αυτά τα μοντέλα δεν μπορούν να αναπαραστήσουν τη σειρά των λέξεων, δεν μπορούν να λύσουν πιο σύνθετα ή αμφίσημα προβλήματα, όπως η δημιουργία κειμένου ή η απάντηση σε ερωτήσεις.\n",
|
||
"\n",
|
||
"Για να αποτυπώσουμε τη σημασία μιας ακολουθίας κειμένου, θα χρησιμοποιήσουμε μια αρχιτεκτονική νευρωνικού δικτύου που ονομάζεται **επαναλαμβανόμενο νευρωνικό δίκτυο** (recurrent neural network ή RNN). Όταν χρησιμοποιούμε ένα RNN, περνάμε την πρότασή μας μέσα από το δίκτυο μία λέξη τη φορά, και το δίκτυο παράγει μια **κατάσταση**, την οποία στη συνέχεια περνάμε ξανά στο δίκτυο μαζί με την επόμενη λέξη.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"Δεδομένης της εισόδου μιας ακολουθίας λέξεων $X_0,\\dots,X_n$, το RNN δημιουργεί μια ακολουθία μπλοκ νευρωνικού δικτύου και εκπαιδεύει αυτή την ακολουθία από άκρη σε άκρη χρησιμοποιώντας οπισθοδιάδοση. Κάθε μπλοκ δικτύου λαμβάνει ένα ζεύγος $(X_i,S_i)$ ως είσοδο και παράγει το $S_{i+1}$ ως αποτέλεσμα. Η τελική κατάσταση $S_n$ ή η έξοδος $Y_n$ περνάει σε έναν γραμμικό ταξινομητή για να παραχθεί το αποτέλεσμα. Όλα τα μπλοκ του δικτύου μοιράζονται τα ίδια βάρη και εκπαιδεύονται από άκρη σε άκρη με μία διαδικασία οπισθοδιάδοσης.\n",
|
||
"\n",
|
||
"> Η παραπάνω εικόνα δείχνει το επαναλαμβανόμενο νευρωνικό δίκτυο σε ξεδιπλωμένη μορφή (αριστερά) και σε πιο συμπαγή επαναλαμβανόμενη αναπαράσταση (δεξιά). Είναι σημαντικό να κατανοήσουμε ότι όλα τα RNN Cells έχουν τα ίδια **μοιραζόμενα βάρη**.\n",
|
||
"\n",
|
||
"Επειδή οι διανυσματικές καταστάσεις $S_0,\\dots,S_n$ περνούν μέσα από το δίκτυο, το RNN είναι σε θέση να μάθει διαδοχικές εξαρτήσεις μεταξύ των λέξεων. Για παράδειγμα, όταν η λέξη *όχι* εμφανίζεται κάπου στην ακολουθία, μπορεί να μάθει να αναιρεί ορισμένα στοιχεία μέσα στο διανυσματικό χώρο της κατάστασης.\n",
|
||
"\n",
|
||
"Εσωτερικά, κάθε RNN cell περιέχει δύο πίνακες βαρών: $W_H$ και $W_I$, και μια προκατάληψη $b$. Σε κάθε βήμα του RNN, δεδομένης της εισόδου $X_i$ και της κατάστασης εισόδου $S_i$, η έξοδος της κατάστασης υπολογίζεται ως $S_{i+1} = f(W_H\\times S_i + W_I\\times X_i+b)$, όπου $f$ είναι μια συνάρτηση ενεργοποίησης (συχνά $\\tanh$).\n",
|
||
"\n",
|
||
"> Για προβλήματα όπως η δημιουργία κειμένου (που θα καλύψουμε στην επόμενη ενότητα) ή η μηχανική μετάφραση, θέλουμε επίσης να πάρουμε κάποια τιμή εξόδου σε κάθε βήμα του RNN. Σε αυτή την περίπτωση, υπάρχει και ένας άλλος πίνακας $W_O$, και η έξοδος υπολογίζεται ως $Y_i=f(W_O\\times S_i+b_O)$.\n",
|
||
"\n",
|
||
"Ας δούμε πώς τα επαναλαμβανόμενα νευρωνικά δίκτυα μπορούν να μας βοηθήσουν να ταξινομήσουμε το σύνολο δεδομένων ειδήσεων.\n",
|
||
"\n",
|
||
"> Για το περιβάλλον sandbox, πρέπει να εκτελέσουμε το παρακάτω κελί για να βεβαιωθούμε ότι η απαιτούμενη βιβλιοθήκη είναι εγκατεστημένη και τα δεδομένα έχουν προφορτωθεί. Εάν εργάζεστε τοπικά, μπορείτε να παραλείψετε το παρακάτω κελί.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import sys\n",
|
||
"!{sys.executable} -m pip install --quiet tensorflow_datasets==4.4.0\n",
|
||
"!cd ~ && wget -q -O - https://mslearntensorflowlp.blob.core.windows.net/data/tfds-ag-news.tgz | tar xz"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import tensorflow as tf\n",
|
||
"from tensorflow import keras\n",
|
||
"import tensorflow_datasets as tfds\n",
|
||
"import numpy as np\n",
|
||
"\n",
|
||
"# We are going to be training pretty large models. In order not to face errors, we need\n",
|
||
"# to set tensorflow option to grow GPU memory allocation when required\n",
|
||
"physical_devices = tf.config.list_physical_devices('GPU') \n",
|
||
"if len(physical_devices)>0:\n",
|
||
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
|
||
"\n",
|
||
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"nteract": {
|
||
"transient": {
|
||
"deleting": false
|
||
}
|
||
}
|
||
},
|
||
"source": [
|
||
"Όταν εκπαιδεύουμε μεγάλα μοντέλα, η κατανομή μνήμης GPU μπορεί να γίνει πρόβλημα. Επίσης, ίσως χρειαστεί να πειραματιστούμε με διαφορετικά μεγέθη minibatch, ώστε τα δεδομένα να χωράνε στη μνήμη της GPU μας, ενώ η εκπαίδευση παραμένει αρκετά γρήγορη. Εάν εκτελείτε αυτόν τον κώδικα στη δική σας μηχανή GPU, μπορείτε να πειραματιστείτε με την προσαρμογή του μεγέθους minibatch για να επιταχύνετε την εκπαίδευση.\n",
|
||
"\n",
|
||
"> **Σημείωση**: Ορισμένες εκδόσεις των οδηγών NVidia είναι γνωστό ότι δεν απελευθερώνουν τη μνήμη μετά την εκπαίδευση του μοντέλου. Εκτελούμε αρκετά παραδείγματα σε αυτό το notebook, και αυτό μπορεί να προκαλέσει εξάντληση της μνήμης σε ορισμένες ρυθμίσεις, ειδικά αν κάνετε δικά σας πειράματα στο ίδιο notebook. Εάν αντιμετωπίσετε περίεργα σφάλματα όταν ξεκινάτε την εκπαίδευση του μοντέλου, ίσως θελήσετε να επανεκκινήσετε τον πυρήνα του notebook.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"metadata": {
|
||
"collapsed": true,
|
||
"jupyter": {
|
||
"outputs_hidden": false,
|
||
"source_hidden": false
|
||
},
|
||
"nteract": {
|
||
"transient": {
|
||
"deleting": false
|
||
}
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"batch_size = 16\n",
|
||
"embed_size = 64"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Απλός ταξινομητής RNN\n",
|
||
"\n",
|
||
"Στην περίπτωση ενός απλού RNN, κάθε επαναλαμβανόμενη μονάδα είναι ένα απλό γραμμικό δίκτυο, το οποίο λαμβάνει έναν διανύσμα εισόδου και έναν διανύσμα κατάστασης, και παράγει έναν νέο διανύσμα κατάστασης. Στο Keras, αυτό μπορεί να αναπαρασταθεί από το επίπεδο `SimpleRNN`.\n",
|
||
"\n",
|
||
"Αν και μπορούμε να περάσουμε απευθείας one-hot κωδικοποιημένα tokens στο επίπεδο RNN, αυτό δεν είναι καλή ιδέα λόγω της υψηλής διαστατικότητάς τους. Επομένως, θα χρησιμοποιήσουμε ένα επίπεδο ενσωμάτωσης (embedding layer) για να μειώσουμε τη διαστατικότητα των διανυσμάτων λέξεων, ακολουθούμενο από ένα επίπεδο RNN, και τέλος έναν ταξινομητή `Dense`.\n",
|
||
"\n",
|
||
"> **Σημείωση**: Σε περιπτώσεις όπου η διαστατικότητα δεν είναι τόσο υψηλή, για παράδειγμα όταν χρησιμοποιούμε κωδικοποίηση σε επίπεδο χαρακτήρων, μπορεί να έχει νόημα να περάσουμε απευθείας one-hot κωδικοποιημένα tokens στο RNN cell.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Model: \"sequential\"\n",
|
||
"_________________________________________________________________\n",
|
||
"Layer (type) Output Shape Param # \n",
|
||
"=================================================================\n",
|
||
"text_vectorization (TextVect (None, None) 0 \n",
|
||
"_________________________________________________________________\n",
|
||
"embedding (Embedding) (None, None, 64) 1280000 \n",
|
||
"_________________________________________________________________\n",
|
||
"simple_rnn (SimpleRNN) (None, 16) 1296 \n",
|
||
"_________________________________________________________________\n",
|
||
"dense (Dense) (None, 4) 68 \n",
|
||
"=================================================================\n",
|
||
"Total params: 1,281,364\n",
|
||
"Trainable params: 1,281,364\n",
|
||
"Non-trainable params: 0\n",
|
||
"_________________________________________________________________\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"vocab_size = 20000\n",
|
||
"\n",
|
||
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(\n",
|
||
" max_tokens=vocab_size,\n",
|
||
" input_shape=(1,))\n",
|
||
"\n",
|
||
"model = keras.models.Sequential([\n",
|
||
" vectorizer,\n",
|
||
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
||
" keras.layers.SimpleRNN(16),\n",
|
||
" keras.layers.Dense(4,activation='softmax')\n",
|
||
"])\n",
|
||
"\n",
|
||
"model.summary()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"> **Σημείωση:** Εδώ χρησιμοποιούμε ένα μη εκπαιδευμένο embedding layer για απλότητα, αλλά για καλύτερα αποτελέσματα μπορούμε να χρησιμοποιήσουμε ένα προεκπαιδευμένο embedding layer με χρήση του Word2Vec, όπως περιγράφηκε στην προηγούμενη ενότητα. Θα ήταν μια καλή άσκηση να προσαρμόσετε αυτόν τον κώδικα ώστε να λειτουργεί με προεκπαιδευμένα embeddings.\n",
|
||
"\n",
|
||
"Τώρα ας εκπαιδεύσουμε το RNN μας. Τα RNN γενικά είναι αρκετά δύσκολο να εκπαιδευτούν, επειδή όταν τα RNN cells ξεδιπλώνονται κατά μήκος του μήκους της ακολουθίας, ο αριθμός των επιπέδων που εμπλέκονται στην οπισθοδιάδοση γίνεται πολύ μεγάλος. Επομένως, χρειάζεται να επιλέξουμε έναν μικρότερο ρυθμό εκμάθησης και να εκπαιδεύσουμε το δίκτυο σε ένα μεγαλύτερο σύνολο δεδομένων για να έχουμε καλά αποτελέσματα. Αυτό μπορεί να πάρει αρκετό χρόνο, οπότε η χρήση GPU είναι προτιμότερη.\n",
|
||
"\n",
|
||
"Για να επιταχύνουμε τη διαδικασία, θα εκπαιδεύσουμε το μοντέλο RNN μόνο στους τίτλους ειδήσεων, παραλείποντας την περιγραφή. Μπορείτε να δοκιμάσετε να εκπαιδεύσετε με την περιγραφή και να δείτε αν μπορείτε να κάνετε το μοντέλο να εκπαιδευτεί.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {
|
||
"scrolled": true
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Training vectorizer\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"def extract_title(x):\n",
|
||
" return x['title']\n",
|
||
"\n",
|
||
"def tupelize_title(x):\n",
|
||
" return (extract_title(x),x['label'])\n",
|
||
"\n",
|
||
"print('Training vectorizer')\n",
|
||
"vectorizer.adapt(ds_train.take(2000).map(extract_title))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"7500/7500 [==============================] - 82s 11ms/step - loss: 0.6629 - acc: 0.7623 - val_loss: 0.5559 - val_acc: 0.7995\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<tensorflow.python.keras.callbacks.History at 0x7f3e0030d350>"
|
||
]
|
||
},
|
||
"execution_count": 6,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||
"model.fit(ds_train.map(tupelize_title).batch(batch_size),validation_data=ds_test.map(tupelize_title).batch(batch_size))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"nteract": {
|
||
"transient": {
|
||
"deleting": false
|
||
}
|
||
}
|
||
},
|
||
"source": [
|
||
"> **Σημείωση** ότι η ακρίβεια είναι πιθανό να είναι χαμηλότερη εδώ, επειδή εκπαιδευόμαστε μόνο σε τίτλους ειδήσεων.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Επανεξέταση ακολουθιών μεταβλητών \n",
|
||
"\n",
|
||
"Θυμηθείτε ότι το layer `TextVectorization` θα προσθέσει αυτόματα pad tokens σε ακολουθίες μεταβλητού μήκους μέσα σε ένα minibatch. Αποδεικνύεται ότι αυτοί οι tokens συμμετέχουν επίσης στην εκπαίδευση και μπορούν να δυσκολέψουν τη σύγκλιση του μοντέλου.\n",
|
||
"\n",
|
||
"Υπάρχουν αρκετές προσεγγίσεις που μπορούμε να ακολουθήσουμε για να ελαχιστοποιήσουμε την ποσότητα padding. Μία από αυτές είναι να αναδιατάξουμε το dataset με βάση το μήκος της ακολουθίας και να ομαδοποιήσουμε όλες τις ακολουθίες ανά μέγεθος. Αυτό μπορεί να γίνει χρησιμοποιώντας τη συνάρτηση `tf.data.experimental.bucket_by_sequence_length` (δείτε [τεκμηρίωση](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length)). \n",
|
||
"\n",
|
||
"Μια άλλη προσέγγιση είναι να χρησιμοποιήσουμε **masking**. Στο Keras, ορισμένα layers υποστηρίζουν επιπλέον είσοδο που δείχνει ποιοι tokens πρέπει να ληφθούν υπόψη κατά την εκπαίδευση. Για να ενσωματώσουμε το masking στο μοντέλο μας, μπορούμε είτε να προσθέσουμε ένα ξεχωριστό layer `Masking` ([τεκμηρίωση](https://keras.io/api/layers/core_layers/masking/)), είτε να ορίσουμε την παράμετρο `mask_zero=True` στο layer `Embedding`.\n",
|
||
"\n",
|
||
"> **Note**: Αυτή η εκπαίδευση θα διαρκέσει περίπου 5 λεπτά για να ολοκληρωθεί μία εποχή σε ολόκληρο το dataset. Μπορείτε να διακόψετε την εκπαίδευση οποιαδήποτε στιγμή αν χάσετε την υπομονή σας. Επίσης, μπορείτε να περιορίσετε την ποσότητα δεδομένων που χρησιμοποιούνται για την εκπαίδευση, προσθέτοντας τη δήλωση `.take(...)` μετά τα datasets `ds_train` και `ds_test`.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"7500/7500 [==============================] - 371s 49ms/step - loss: 0.5401 - acc: 0.8079 - val_loss: 0.3780 - val_acc: 0.8822\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<tensorflow.python.keras.callbacks.History at 0x7f3dec118850>"
|
||
]
|
||
},
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"def extract_text(x):\n",
|
||
" return x['title']+' '+x['description']\n",
|
||
"\n",
|
||
"def tupelize(x):\n",
|
||
" return (extract_text(x),x['label'])\n",
|
||
"\n",
|
||
"model = keras.models.Sequential([\n",
|
||
" vectorizer,\n",
|
||
" keras.layers.Embedding(vocab_size,embed_size,mask_zero=True),\n",
|
||
" keras.layers.SimpleRNN(16),\n",
|
||
" keras.layers.Dense(4,activation='softmax')\n",
|
||
"])\n",
|
||
"\n",
|
||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Τώρα που χρησιμοποιούμε τη μέθοδο της απόκρυψης, μπορούμε να εκπαιδεύσουμε το μοντέλο σε ολόκληρο το σύνολο δεδομένων των τίτλων και περιγραφών.\n",
|
||
"\n",
|
||
"> **Σημείωση**: Έχετε παρατηρήσει ότι χρησιμοποιούμε έναν vectorizer που έχει εκπαιδευτεί στους τίτλους των ειδήσεων και όχι στο πλήρες κείμενο του άρθρου; Αυτό ενδεχομένως να προκαλέσει την παράβλεψη ορισμένων tokens, οπότε είναι καλύτερο να επανεκπαιδεύσουμε τον vectorizer. Ωστόσο, η επίδραση μπορεί να είναι πολύ μικρή, γι' αυτό θα συνεχίσουμε να χρησιμοποιούμε τον προηγούμενο προ-εκπαιδευμένο vectorizer για λόγους απλότητας.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## LSTM: Μνήμη μακράς και βραχείας διάρκειας\n",
|
||
"\n",
|
||
"Ένα από τα κύρια προβλήματα των RNNs είναι η **εξασθένιση των βαθμίδων (vanishing gradients)**. Τα RNNs μπορεί να είναι αρκετά μεγάλα και να δυσκολεύονται να μεταφέρουν τις βαθμίδες πίσω μέχρι το πρώτο επίπεδο του δικτύου κατά τη διάρκεια της οπισθοδιάδοσης. Όταν συμβαίνει αυτό, το δίκτυο δεν μπορεί να μάθει σχέσεις μεταξύ απομακρυσμένων συμβόλων. Ένας τρόπος να αποφευχθεί αυτό το πρόβλημα είναι η εισαγωγή της **ρητής διαχείρισης κατάστασης** μέσω της χρήσης **πυλών**. Οι δύο πιο συνηθισμένες αρχιτεκτονικές που εισάγουν πύλες είναι η **μνήμη μακράς και βραχείας διάρκειας** (LSTM) και η **μονάδα πύλης αναμετάδοσης** (GRU). Εδώ θα καλύψουμε τα LSTMs.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"Ένα δίκτυο LSTM είναι οργανωμένο με τρόπο παρόμοιο με ένα RNN, αλλά υπάρχουν δύο καταστάσεις που μεταφέρονται από επίπεδο σε επίπεδο: η πραγματική κατάσταση $c$ και το κρυφό διάνυσμα $h$. Σε κάθε μονάδα, το κρυφό διάνυσμα $h_{t-1}$ συνδυάζεται με την είσοδο $x_t$, και μαζί ελέγχουν τι συμβαίνει στην κατάσταση $c_t$ και στην έξοδο $h_{t}$ μέσω **πυλών**. Κάθε πύλη έχει ενεργοποίηση sigmoid (έξοδος στο εύρος $[0,1]$), η οποία μπορεί να θεωρηθεί ως δυαδική μάσκα όταν πολλαπλασιάζεται με το διάνυσμα κατάστασης. Τα LSTMs περιλαμβάνουν τις εξής πύλες (από αριστερά προς τα δεξιά στην παραπάνω εικόνα):\n",
|
||
"* **Πύλη διαγραφής (forget gate)**, η οποία καθορίζει ποια στοιχεία του διανύσματος $c_{t-1}$ πρέπει να ξεχάσουμε και ποια να περάσουμε.\n",
|
||
"* **Πύλη εισόδου (input gate)**, η οποία καθορίζει πόση πληροφορία από το διάνυσμα εισόδου και το προηγούμενο κρυφό διάνυσμα πρέπει να ενσωματωθεί στο διάνυσμα κατάστασης.\n",
|
||
"* **Πύλη εξόδου (output gate)**, η οποία παίρνει το νέο διάνυσμα κατάστασης και αποφασίζει ποια από τα στοιχεία του θα χρησιμοποιηθούν για την παραγωγή του νέου κρυφού διανύσματος $h_t$.\n",
|
||
"\n",
|
||
"Τα στοιχεία της κατάστασης $c$ μπορούν να θεωρηθούν ως σημαίες που μπορούν να ενεργοποιηθούν ή να απενεργοποιηθούν. Για παράδειγμα, όταν συναντάμε το όνομα *Alice* στη σειρά, υποθέτουμε ότι αναφέρεται σε γυναίκα και ενεργοποιούμε τη σημαία στην κατάσταση που δηλώνει ότι έχουμε ένα θηλυκό ουσιαστικό στην πρόταση. Όταν στη συνέχεια συναντάμε τις λέξεις *and Tom*, ενεργοποιούμε τη σημαία που δηλώνει ότι έχουμε ένα πληθυντικό ουσιαστικό. Έτσι, μέσω της διαχείρισης της κατάστασης μπορούμε να παρακολουθούμε τις γραμματικές ιδιότητες της πρότασης.\n",
|
||
"\n",
|
||
"> **Note**: Εδώ υπάρχει μια εξαιρετική πηγή για την κατανόηση της εσωτερικής λειτουργίας των LSTMs: [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) από τον Christopher Olah.\n",
|
||
"\n",
|
||
"Παρόλο που η εσωτερική δομή μιας κυψέλης LSTM μπορεί να φαίνεται περίπλοκη, το Keras κρύβει αυτή την υλοποίηση μέσα στο επίπεδο `LSTM`, οπότε το μόνο που χρειάζεται να κάνουμε στο παραπάνω παράδειγμα είναι να αντικαταστήσουμε το επαναλαμβανόμενο επίπεδο:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"15000/15000 [==============================] - 188s 13ms/step - loss: 0.5692 - acc: 0.7916 - val_loss: 0.3441 - val_acc: 0.8870\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<tensorflow.python.keras.callbacks.History at 0x7f3d6af5c350>"
|
||
]
|
||
},
|
||
"execution_count": 8,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"model = keras.models.Sequential([\n",
|
||
" vectorizer,\n",
|
||
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
||
" keras.layers.LSTM(8),\n",
|
||
" keras.layers.Dense(4,activation='softmax')\n",
|
||
"])\n",
|
||
"\n",
|
||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||
"model.fit(ds_train.map(tupelize).batch(8),validation_data=ds_test.map(tupelize).batch(8))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": []
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Διπλής κατεύθυνσης και πολυεπίπεδα RNNs\n",
|
||
"\n",
|
||
"Στα παραδείγματα που έχουμε δει μέχρι τώρα, τα αναδρομικά δίκτυα λειτουργούν από την αρχή μιας ακολουθίας μέχρι το τέλος. Αυτό μας φαίνεται φυσικό, καθώς ακολουθεί την ίδια κατεύθυνση με την οποία διαβάζουμε ή ακούμε ομιλία. Ωστόσο, για σενάρια που απαιτούν τυχαία πρόσβαση στην ακολουθία εισόδου, έχει περισσότερο νόημα να εκτελείται ο αναδρομικός υπολογισμός και προς τις δύο κατευθύνσεις. Τα RNNs που επιτρέπουν υπολογισμούς και προς τις δύο κατευθύνσεις ονομάζονται **διπλής κατεύθυνσης** RNNs, και μπορούν να δημιουργηθούν τυλίγοντας το αναδρομικό επίπεδο με ένα ειδικό επίπεδο `Bidirectional`.\n",
|
||
"\n",
|
||
"> **Note**: Το επίπεδο `Bidirectional` δημιουργεί δύο αντίγραφα του επιπέδου που περιέχει και ορίζει την ιδιότητα `go_backwards` του ενός από αυτά τα αντίγραφα σε `True`, ώστε να εκτελείται προς την αντίθετη κατεύθυνση κατά μήκος της ακολουθίας.\n",
|
||
"\n",
|
||
"Τα αναδρομικά δίκτυα, είτε μονής είτε διπλής κατεύθυνσης, εντοπίζουν μοτίβα μέσα σε μια ακολουθία και τα αποθηκεύουν σε διανύσματα κατάστασης ή τα επιστρέφουν ως έξοδο. Όπως και με τα συνελικτικά δίκτυα, μπορούμε να κατασκευάσουμε ένα ακόμη αναδρομικό επίπεδο μετά το πρώτο, για να εντοπίσουμε μοτίβα υψηλότερου επιπέδου, τα οποία προκύπτουν από μοτίβα χαμηλότερου επιπέδου που εξήγαγε το πρώτο επίπεδο. Αυτό μας οδηγεί στην έννοια του **πολυεπίπεδου RNN**, το οποίο αποτελείται από δύο ή περισσότερα αναδρομικά δίκτυα, όπου η έξοδος του προηγούμενου επιπέδου περνάει ως είσοδος στο επόμενο επίπεδο.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"*Εικόνα από [αυτή την εξαιρετική ανάρτηση](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) του Fernando López.*\n",
|
||
"\n",
|
||
"Το Keras καθιστά την κατασκευή αυτών των δικτύων μια εύκολη διαδικασία, καθώς το μόνο που χρειάζεται είναι να προσθέσετε περισσότερα αναδρομικά επίπεδα στο μοντέλο. Για όλα τα επίπεδα εκτός από το τελευταίο, πρέπει να ορίσουμε την παράμετρο `return_sequences=True`, επειδή χρειαζόμαστε το επίπεδο να επιστρέφει όλες τις ενδιάμεσες καταστάσεις και όχι μόνο την τελική κατάσταση του αναδρομικού υπολογισμού.\n",
|
||
"\n",
|
||
"Ας κατασκευάσουμε ένα δίκτυο LSTM διπλής κατεύθυνσης με δύο επίπεδα για το πρόβλημα ταξινόμησής μας.\n",
|
||
"\n",
|
||
"> **Note** Αυτός ο κώδικας χρειάζεται ξανά αρκετό χρόνο για να ολοκληρωθεί, αλλά μας δίνει την υψηλότερη ακρίβεια που έχουμε δει μέχρι τώρα. Ίσως, λοιπόν, αξίζει να περιμένουμε και να δούμε το αποτέλεσμα.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"5044/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r5045/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"model = keras.models.Sequential([\n",
|
||
" vectorizer,\n",
|
||
" keras.layers.Embedding(vocab_size, 128, mask_zero=True),\n",
|
||
" keras.layers.Bidirectional(keras.layers.LSTM(64,return_sequences=True)),\n",
|
||
" keras.layers.Bidirectional(keras.layers.LSTM(64)), \n",
|
||
" keras.layers.Dense(4,activation='softmax')\n",
|
||
"])\n",
|
||
"\n",
|
||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
|
||
" validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## RNNs για άλλες εργασίες\n",
|
||
"\n",
|
||
"Μέχρι τώρα, έχουμε επικεντρωθεί στη χρήση των RNNs για την ταξινόμηση ακολουθιών κειμένου. Ωστόσο, μπορούν να διαχειριστούν πολλές περισσότερες εργασίες, όπως η δημιουργία κειμένου και η μετάφραση γλώσσας — θα εξετάσουμε αυτές τις εργασίες στην επόμενη ενότητα.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**Αποποίηση Ευθύνης**: \nΑυτό το έγγραφο έχει μεταφραστεί χρησιμοποιώντας την υπηρεσία αυτόματης μετάφρασης AI [Co-op Translator](https://github.com/Azure/co-op-translator). Παρόλο που καταβάλλουμε κάθε προσπάθεια για ακρίβεια, παρακαλούμε να έχετε υπόψη ότι οι αυτόματες μεταφράσεις ενδέχεται να περιέχουν σφάλματα ή ανακρίβειες. Το πρωτότυπο έγγραφο στη μητρική του γλώσσα θα πρέπει να θεωρείται η αυθεντική πηγή. Για κρίσιμες πληροφορίες, συνιστάται επαγγελματική ανθρώπινη μετάφραση. Δεν φέρουμε ευθύνη για τυχόν παρεξηγήσεις ή εσφαλμένες ερμηνείες που προκύπτουν από τη χρήση αυτής της μετάφρασης.\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernel_info": {
|
||
"name": "conda-env-py37_tensorflow-py"
|
||
},
|
||
"kernelspec": {
|
||
"display_name": "py37_tensorflow",
|
||
"language": "python",
|
||
"name": "conda-env-py37_tensorflow-py"
|
||
},
|
||
"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.7.9"
|
||
},
|
||
"nteract": {
|
||
"version": "nteract-front-end@1.0.0"
|
||
},
|
||
"coopTranslator": {
|
||
"original_hash": "81351e61f619b432ff51010a4f993194",
|
||
"translation_date": "2025-08-29T10:50:32+00:00",
|
||
"source_file": "lessons/5-NLP/16-RNN/RNNTF.ipynb",
|
||
"language_code": "el"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
} |