497 lines
38 KiB
Plaintext
497 lines
38 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Γενετικά δίκτυα\n",
|
||
"\n",
|
||
"Τα Επαναλαμβανόμενα Νευρωνικά Δίκτυα (RNNs) και οι παραλλαγές τους με πύλες, όπως τα Κύτταρα Μνήμης Μακράς και Βραχείας Διάρκειας (LSTMs) και οι Μονάδες Επαναλαμβανόμενης Πύλης (GRUs), παρέχουν έναν μηχανισμό για τη μοντελοποίηση γλώσσας, δηλαδή μπορούν να μάθουν τη σειρά των λέξεων και να προβλέψουν την επόμενη λέξη σε μια ακολουθία. Αυτό μας επιτρέπει να χρησιμοποιούμε τα RNNs για **γενετικές εργασίες**, όπως η απλή δημιουργία κειμένου, η μηχανική μετάφραση και ακόμη και η περιγραφή εικόνων.\n",
|
||
"\n",
|
||
"Στην αρχιτεκτονική RNN που συζητήσαμε στην προηγούμενη ενότητα, κάθε μονάδα RNN παρήγαγε την επόμενη κρυφή κατάσταση ως έξοδο. Ωστόσο, μπορούμε επίσης να προσθέσουμε μια άλλη έξοδο σε κάθε επαναλαμβανόμενη μονάδα, η οποία θα μας επιτρέπει να παράγουμε μια **ακολουθία** (που έχει το ίδιο μήκος με την αρχική ακολουθία). Επιπλέον, μπορούμε να χρησιμοποιήσουμε μονάδες RNN που δεν δέχονται είσοδο σε κάθε βήμα, αλλά απλώς λαμβάνουν κάποιο αρχικό διανύσμα κατάστασης και στη συνέχεια παράγουν μια ακολουθία εξόδων.\n",
|
||
"\n",
|
||
"Σε αυτό το σημειωματάριο, θα επικεντρωθούμε σε απλά γενετικά μοντέλα που μας βοηθούν να δημιουργούμε κείμενο. Για απλότητα, ας κατασκευάσουμε ένα **δίκτυο σε επίπεδο χαρακτήρων**, το οποίο δημιουργεί κείμενο γράμμα προς γράμμα. Κατά τη διάρκεια της εκπαίδευσης, πρέπει να πάρουμε κάποιο σώμα κειμένου και να το χωρίσουμε σε ακολουθίες γραμμάτων.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"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",
|
||
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Δημιουργία λεξιλογίου χαρακτήρων\n",
|
||
"\n",
|
||
"Για να δημιουργήσουμε ένα γενετικό δίκτυο σε επίπεδο χαρακτήρων, πρέπει να χωρίσουμε το κείμενο σε μεμονωμένους χαρακτήρες αντί για λέξεις. Η στρώση `TextVectorization` που χρησιμοποιούσαμε προηγουμένως δεν μπορεί να το κάνει αυτό, οπότε έχουμε δύο επιλογές:\n",
|
||
"\n",
|
||
"* Να φορτώσουμε το κείμενο χειροκίνητα και να κάνουμε την τοκενοποίηση \"με το χέρι\", όπως στο [επίσημο παράδειγμα του Keras](https://keras.io/examples/generative/lstm_character_level_text_generation/)\n",
|
||
"* Να χρησιμοποιήσουμε την κλάση `Tokenizer` για τοκενοποίηση σε επίπεδο χαρακτήρων.\n",
|
||
"\n",
|
||
"Θα επιλέξουμε τη δεύτερη επιλογή. Η `Tokenizer` μπορεί επίσης να χρησιμοποιηθεί για τοκενοποίηση σε λέξεις, οπότε κάποιος μπορεί να μεταβεί εύκολα από τοκενοποίηση σε επίπεδο χαρακτήρων σε επίπεδο λέξεων.\n",
|
||
"\n",
|
||
"Για να κάνουμε τοκενοποίηση σε επίπεδο χαρακτήρων, πρέπει να περάσουμε την παράμετρο `char_level=True`:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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",
|
||
"tokenizer = keras.preprocessing.text.Tokenizer(char_level=True,lower=False)\n",
|
||
"tokenizer.fit_on_texts([x['title'].numpy().decode('utf-8') for x in ds_train])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Θέλουμε επίσης να χρησιμοποιήσουμε ένα ειδικό σύμβολο για να δηλώσουμε το **τέλος της ακολουθίας**, το οποίο θα ονομάσουμε `<eos>`. Ας το προσθέσουμε χειροκίνητα στο λεξιλόγιο:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"eos_token = len(tokenizer.word_index)+1\n",
|
||
"tokenizer.word_index['<eos>'] = eos_token\n",
|
||
"\n",
|
||
"vocab_size = eos_token + 1"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Τώρα, για να κωδικοποιήσουμε κείμενο σε ακολουθίες αριθμών, μπορούμε να χρησιμοποιήσουμε:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"[[48, 2, 10, 10, 5, 44, 1, 25, 5, 8, 10, 13, 78]]"
|
||
]
|
||
},
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"tokenizer.texts_to_sequences(['Hello, world!'])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Εκπαίδευση ενός γενετικού RNN για τη δημιουργία τίτλων\n",
|
||
"\n",
|
||
"Ο τρόπος με τον οποίο θα εκπαιδεύσουμε το RNN για να δημιουργεί τίτλους ειδήσεων είναι ο εξής. Σε κάθε βήμα, θα παίρνουμε έναν τίτλο, ο οποίος θα εισάγεται σε ένα RNN, και για κάθε χαρακτήρα εισόδου θα ζητάμε από το δίκτυο να παράγει τον επόμενο χαρακτήρα εξόδου:\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"Για τον τελευταίο χαρακτήρα της ακολουθίας μας, θα ζητάμε από το δίκτυο να παράγει το token `<eos>`.\n",
|
||
"\n",
|
||
"Η κύρια διαφορά του γενετικού RNN που χρησιμοποιούμε εδώ είναι ότι θα παίρνουμε έξοδο από κάθε βήμα του RNN, και όχι μόνο από το τελικό κελί. Αυτό μπορεί να επιτευχθεί καθορίζοντας την παράμετρο `return_sequences` στο κελί του RNN.\n",
|
||
"\n",
|
||
"Έτσι, κατά τη διάρκεια της εκπαίδευσης, η είσοδος στο δίκτυο θα είναι μια ακολουθία κωδικοποιημένων χαρακτήρων συγκεκριμένου μήκους, και η έξοδος θα είναι μια ακολουθία του ίδιου μήκους, αλλά μετατοπισμένη κατά ένα στοιχείο και τερματισμένη με `<eos>`. Το minibatch θα αποτελείται από αρκετές τέτοιες ακολουθίες, και θα χρειαστεί να χρησιμοποιήσουμε **padding** για να ευθυγραμμίσουμε όλες τις ακολουθίες.\n",
|
||
"\n",
|
||
"Ας δημιουργήσουμε συναρτήσεις που θα μετασχηματίσουν το dataset για εμάς. Επειδή θέλουμε να προσθέσουμε padding στις ακολουθίες σε επίπεδο minibatch, θα ομαδοποιήσουμε πρώτα το dataset καλώντας `.batch()`, και στη συνέχεια θα χρησιμοποιήσουμε `map` για να κάνουμε τον μετασχηματισμό. Έτσι, η συνάρτηση μετασχηματισμού θα παίρνει ολόκληρο το minibatch ως παράμετρο:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def title_batch(x):\n",
|
||
" x = [t.numpy().decode('utf-8') for t in x]\n",
|
||
" z = tokenizer.texts_to_sequences(x)\n",
|
||
" z = tf.keras.preprocessing.sequence.pad_sequences(z)\n",
|
||
" return tf.one_hot(z,vocab_size), tf.one_hot(tf.concat([z[:,1:],tf.constant(eos_token,shape=(len(z),1))],axis=1),vocab_size)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Μερικά σημαντικά πράγματα που κάνουμε εδώ:\n",
|
||
"* Πρώτα εξάγουμε το πραγματικό κείμενο από το string tensor\n",
|
||
"* Το `text_to_sequences` μετατρέπει τη λίστα των συμβολοσειρών σε μια λίστα από ακέραια tensors\n",
|
||
"* Το `pad_sequences` προσθέτει padding σε αυτά τα tensors μέχρι το μέγιστο μήκος τους\n",
|
||
"* Τέλος, κάνουμε one-hot κωδικοποίηση όλων των χαρακτήρων, καθώς και τη μετατόπιση και την προσθήκη του `<eos>`. Σύντομα θα δούμε γιατί χρειαζόμαστε τους χαρακτήρες σε μορφή one-hot κωδικοποίησης\n",
|
||
"\n",
|
||
"Ωστόσο, αυτή η συνάρτηση είναι **Pythonic**, δηλαδή δεν μπορεί να μεταφραστεί αυτόματα σε υπολογιστικό γράφημα του Tensorflow. Θα έχουμε σφάλματα αν προσπαθήσουμε να χρησιμοποιήσουμε αυτή τη συνάρτηση απευθείας στη συνάρτηση `Dataset.map`. Πρέπει να περικλείσουμε αυτήν την Pythonic κλήση χρησιμοποιώντας το wrapper `py_function`:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def title_batch_fn(x):\n",
|
||
" x = x['title']\n",
|
||
" a,b = tf.py_function(title_batch,inp=[x],Tout=(tf.float32,tf.float32))\n",
|
||
" return a,b"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"> **Note**: Η διάκριση μεταξύ των Pythonic και Tensorflow συναρτήσεων μετασχηματισμού μπορεί να φαίνεται αρκετά περίπλοκη, και ίσως αναρωτιέστε γιατί δεν μετασχηματίζουμε το dataset χρησιμοποιώντας τις τυπικές συναρτήσεις της Python πριν το περάσουμε στη `fit`. Παρόλο που αυτό μπορεί σίγουρα να γίνει, η χρήση του `Dataset.map` έχει ένα μεγάλο πλεονέκτημα, καθώς ο αγωγός μετασχηματισμού δεδομένων εκτελείται χρησιμοποιώντας το υπολογιστικό γράφημα του Tensorflow, το οποίο αξιοποιεί τους υπολογισμούς της GPU και ελαχιστοποιεί την ανάγκη μεταφοράς δεδομένων μεταξύ CPU/GPU.\n",
|
||
"\n",
|
||
"Τώρα μπορούμε να κατασκευάσουμε το δίκτυο γεννήτριας και να ξεκινήσουμε την εκπαίδευση. Μπορεί να βασίζεται σε οποιοδήποτε επαναληπτικό κύτταρο που συζητήσαμε στην προηγούμενη ενότητα (απλό, LSTM ή GRU). Στο παράδειγμά μας θα χρησιμοποιήσουμε LSTM.\n",
|
||
"\n",
|
||
"Επειδή το δίκτυο λαμβάνει χαρακτήρες ως είσοδο, και το μέγεθος του λεξιλογίου είναι αρκετά μικρό, δεν χρειαζόμαστε στρώμα ενσωμάτωσης, η είσοδος κωδικοποιημένη σε one-hot μπορεί να περάσει απευθείας στο κύτταρο LSTM. Το στρώμα εξόδου θα είναι ένας ταξινομητής `Dense` που θα μετατρέπει την έξοδο του LSTM σε αριθμούς κωδικοποιημένων one-hot tokens.\n",
|
||
"\n",
|
||
"Επιπλέον, επειδή ασχολούμαστε με ακολουθίες μεταβλητού μήκους, μπορούμε να χρησιμοποιήσουμε το στρώμα `Masking` για να δημιουργήσουμε μια μάσκα που θα αγνοεί το συμπληρωμένο μέρος της συμβολοσειράς. Αυτό δεν είναι αυστηρά απαραίτητο, καθώς δεν μας ενδιαφέρει ιδιαίτερα οτιδήποτε υπερβαίνει το token `<eos>`, αλλά θα το χρησιμοποιήσουμε για να αποκτήσουμε κάποια εμπειρία με αυτόν τον τύπο στρώματος. Το `input_shape` θα είναι `(None, vocab_size)`, όπου το `None` υποδεικνύει την ακολουθία μεταβλητού μήκους, και το σχήμα εξόδου είναι επίσης `(None, vocab_size)`, όπως μπορείτε να δείτε από το `summary`:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Model: \"sequential\"\n",
|
||
"_________________________________________________________________\n",
|
||
"Layer (type) Output Shape Param # \n",
|
||
"=================================================================\n",
|
||
"masking (Masking) (None, None, 84) 0 \n",
|
||
"_________________________________________________________________\n",
|
||
"lstm (LSTM) (None, None, 128) 109056 \n",
|
||
"_________________________________________________________________\n",
|
||
"dense (Dense) (None, None, 84) 10836 \n",
|
||
"=================================================================\n",
|
||
"Total params: 119,892\n",
|
||
"Trainable params: 119,892\n",
|
||
"Non-trainable params: 0\n",
|
||
"_________________________________________________________________\n",
|
||
"15000/15000 [==============================] - 229s 15ms/step - loss: 1.5385\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<tensorflow.python.keras.callbacks.History at 0x7fa40c1245e0>"
|
||
]
|
||
},
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"model = keras.models.Sequential([\n",
|
||
" keras.layers.Masking(input_shape=(None,vocab_size)),\n",
|
||
" keras.layers.LSTM(128,return_sequences=True),\n",
|
||
" keras.layers.Dense(vocab_size,activation='softmax')\n",
|
||
"])\n",
|
||
"\n",
|
||
"model.summary()\n",
|
||
"model.compile(loss='categorical_crossentropy')\n",
|
||
"\n",
|
||
"model.fit(ds_train.batch(8).map(title_batch_fn))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Δημιουργία αποτελέσματος\n",
|
||
"\n",
|
||
"Τώρα που έχουμε εκπαιδεύσει το μοντέλο, θέλουμε να το χρησιμοποιήσουμε για να δημιουργήσουμε κάποιο αποτέλεσμα. Πρώτα απ' όλα, χρειαζόμαστε έναν τρόπο να αποκωδικοποιήσουμε κείμενο που αναπαρίσταται από μια ακολουθία αριθμών συμβόλων. Για να το κάνουμε αυτό, θα μπορούσαμε να χρησιμοποιήσουμε τη συνάρτηση `tokenizer.sequences_to_texts`. Ωστόσο, αυτή δεν λειτουργεί καλά με την τοκενoποίηση σε επίπεδο χαρακτήρων. Επομένως, θα πάρουμε ένα λεξικό συμβόλων από τον tokenizer (ονομάζεται `word_index`), θα δημιουργήσουμε έναν αντίστροφο χάρτη και θα γράψουμε τη δική μας συνάρτηση αποκωδικοποίησης:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"reverse_map = {val:key for key, val in tokenizer.word_index.items()}\n",
|
||
"\n",
|
||
"def decode(x):\n",
|
||
" return ''.join([reverse_map[t] for t in x])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Τώρα, ας προχωρήσουμε στη δημιουργία. Ξεκινάμε με μια συμβολοσειρά `start`, την κωδικοποιούμε σε μια ακολουθία `inp`, και σε κάθε βήμα καλούμε το δίκτυό μας για να προβλέψει τον επόμενο χαρακτήρα.\n",
|
||
"\n",
|
||
"Η έξοδος του δικτύου `out` είναι ένας διάνυσμα με `vocab_size` στοιχεία που αντιπροσωπεύουν τις πιθανότητες κάθε συμβόλου, και μπορούμε να βρούμε τον αριθμό του πιο πιθανό συμβόλου χρησιμοποιώντας το `argmax`. Στη συνέχεια, προσθέτουμε αυτόν τον χαρακτήρα στη λίστα των παραγόμενων συμβόλων και συνεχίζουμε τη δημιουργία. Αυτή η διαδικασία παραγωγής ενός χαρακτήρα επαναλαμβάνεται `size` φορές για να παραχθεί ο απαιτούμενος αριθμός χαρακτήρων, και τερματίζουμε νωρίτερα όταν συναντηθεί το `eos_token`.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 12,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"'Today #39;s lead to strike for the strike for the strike for the strike (AFP)'"
|
||
]
|
||
},
|
||
"execution_count": 12,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"def generate(model,size=100,start='Today '):\n",
|
||
" inp = tokenizer.texts_to_sequences([start])[0]\n",
|
||
" chars = inp\n",
|
||
" for i in range(size):\n",
|
||
" out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
|
||
" nc = tf.argmax(out)\n",
|
||
" if nc==eos_token:\n",
|
||
" break\n",
|
||
" chars.append(nc.numpy())\n",
|
||
" inp = inp+[nc]\n",
|
||
" return decode(chars)\n",
|
||
" \n",
|
||
"generate(model)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Δειγματοληψία εξόδου κατά τη διάρκεια της εκπαίδευσης\n",
|
||
"\n",
|
||
"Επειδή δεν έχουμε χρήσιμες μετρικές όπως η *ακρίβεια*, ο μόνος τρόπος να δούμε ότι το μοντέλο μας βελτιώνεται είναι μέσω της **δειγματοληψίας** παραγόμενων συμβολοσειρών κατά τη διάρκεια της εκπαίδευσης. Για να το κάνουμε αυτό, θα χρησιμοποιήσουμε **callbacks**, δηλαδή συναρτήσεις που μπορούμε να περάσουμε στη συνάρτηση `fit` και οι οποίες θα καλούνται περιοδικά κατά τη διάρκεια της εκπαίδευσης.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 13,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Epoch 1/3\n",
|
||
"15000/15000 [==============================] - 226s 15ms/step - loss: 1.2703\n",
|
||
"Today #39;s a lead in the company for the strike\n",
|
||
"Epoch 2/3\n",
|
||
"15000/15000 [==============================] - 227s 15ms/step - loss: 1.2057\n",
|
||
"Today #39;s the Market Service on Security Start (AP)\n",
|
||
"Epoch 3/3\n",
|
||
"15000/15000 [==============================] - 226s 15ms/step - loss: 1.1752\n",
|
||
"Today #39;s a line on the strike to start for the start\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<tensorflow.python.keras.callbacks.History at 0x7fa40c74e3d0>"
|
||
]
|
||
},
|
||
"execution_count": 13,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"sampling_callback = keras.callbacks.LambdaCallback(\n",
|
||
" on_epoch_end = lambda batch, logs: print(generate(model))\n",
|
||
")\n",
|
||
"\n",
|
||
"model.fit(ds_train.batch(8).map(title_batch_fn),callbacks=[sampling_callback],epochs=3)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Αυτό το παράδειγμα ήδη παράγει αρκετά καλό κείμενο, αλλά μπορεί να βελτιωθεί περαιτέρω με διάφορους τρόπους:\n",
|
||
"\n",
|
||
"* **Περισσότερο κείμενο**. Έχουμε χρησιμοποιήσει μόνο τίτλους για την εργασία μας, αλλά ίσως θέλετε να πειραματιστείτε με πλήρες κείμενο. Θυμηθείτε ότι τα RNNs δεν είναι ιδιαίτερα καλά στη διαχείριση μεγάλων ακολουθιών, οπότε έχει νόημα είτε να τα χωρίσετε σε μικρότερες προτάσεις, είτε να εκπαιδεύετε πάντα σε σταθερό μήκος ακολουθίας με κάποια προκαθορισμένη τιμή `num_chars` (π.χ. 256). Μπορείτε να δοκιμάσετε να τροποποιήσετε το παραπάνω παράδειγμα σε τέτοια αρχιτεκτονική, χρησιμοποιώντας [το επίσημο tutorial του Keras](https://keras.io/examples/generative/lstm_character_level_text_generation/) ως έμπνευση.\n",
|
||
"\n",
|
||
"* **Πολυεπίπεδο LSTM**. Έχει νόημα να δοκιμάσετε 2 ή 3 επίπεδα κυττάρων LSTM. Όπως αναφέραμε στην προηγούμενη ενότητα, κάθε επίπεδο LSTM εξάγει συγκεκριμένα μοτίβα από το κείμενο, και στην περίπτωση του γεννήτορα σε επίπεδο χαρακτήρων μπορούμε να περιμένουμε ότι το χαμηλότερο επίπεδο LSTM θα είναι υπεύθυνο για την εξαγωγή συλλαβών, ενώ τα υψηλότερα επίπεδα - για λέξεις και συνδυασμούς λέξεων. Αυτό μπορεί να υλοποιηθεί απλά περνώντας την παράμετρο αριθμού επιπέδων στον κατασκευαστή του LSTM.\n",
|
||
"\n",
|
||
"* Μπορείτε επίσης να πειραματιστείτε με **μονάδες GRU** και να δείτε ποιες αποδίδουν καλύτερα, καθώς και με **διαφορετικά μεγέθη κρυφών επιπέδων**. Πολύ μεγάλο κρυφό επίπεδο μπορεί να οδηγήσει σε υπερπροσαρμογή (π.χ. το δίκτυο θα μάθει ακριβώς το κείμενο), ενώ μικρότερο μέγεθος μπορεί να μην παράγει καλό αποτέλεσμα.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Δημιουργία μαλακού κειμένου και θερμοκρασία\n",
|
||
"\n",
|
||
"Στον προηγούμενο ορισμό της `generate`, πάντα επιλέγαμε τον χαρακτήρα με την υψηλότερη πιθανότητα ως τον επόμενο χαρακτήρα στο παραγόμενο κείμενο. Αυτό είχε ως αποτέλεσμα το κείμενο να \"επαναλαμβάνεται\" συχνά μεταξύ των ίδιων ακολουθιών χαρακτήρων ξανά και ξανά, όπως στο παρακάτω παράδειγμα:\n",
|
||
"```\n",
|
||
"today of the second the company and a second the company ...\n",
|
||
"```\n",
|
||
"\n",
|
||
"Ωστόσο, αν κοιτάξουμε την κατανομή πιθανοτήτων για τον επόμενο χαρακτήρα, μπορεί να παρατηρήσουμε ότι η διαφορά μεταξύ των υψηλότερων πιθανοτήτων δεν είναι μεγάλη, π.χ. ένας χαρακτήρας μπορεί να έχει πιθανότητα 0.2, ενώ ένας άλλος 0.19, κ.ο.κ. Για παράδειγμα, όταν ψάχνουμε τον επόμενο χαρακτήρα στη σειρά '*play*', ο επόμενος χαρακτήρας μπορεί εξίσου να είναι είτε κενό, είτε **e** (όπως στη λέξη *player*).\n",
|
||
"\n",
|
||
"Αυτό μας οδηγεί στο συμπέρασμα ότι δεν είναι πάντα \"δίκαιο\" να επιλέγουμε τον χαρακτήρα με την υψηλότερη πιθανότητα, καθώς η επιλογή του δεύτερου υψηλότερου μπορεί να οδηγήσει επίσης σε ουσιαστικό κείμενο. Είναι πιο σοφό να **δειγματοληπτούμε** χαρακτήρες από την κατανομή πιθανοτήτων που δίνεται από την έξοδο του δικτύου.\n",
|
||
"\n",
|
||
"Αυτή η δειγματοληψία μπορεί να γίνει χρησιμοποιώντας τη συνάρτηση `np.multinomial`, η οποία υλοποιεί την αποκαλούμενη **πολυωνυμική κατανομή**. Μια συνάρτηση που υλοποιεί αυτή τη **μαλακή** δημιουργία κειμένου ορίζεται παρακάτω:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 33,
|
||
"metadata": {
|
||
"scrolled": true
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"\n",
|
||
"--- Temperature = 0.3\n",
|
||
"Today #39;s strike #39; to start at the store return\n",
|
||
"On Sunday PO to Be Data Profit Up (Reuters)\n",
|
||
"Moscow, SP wins straight to the Microsoft #39;s control of the space start\n",
|
||
"President olding of the blast start for the strike to pay <b>...</b>\n",
|
||
"Little red riding hood ficed to the spam countered in European <b>...</b>\n",
|
||
"\n",
|
||
"--- Temperature = 0.8\n",
|
||
"Today countie strikes ryder missile faces food market blut\n",
|
||
"On Sunday collores lose-toppy of sale of Bullment in <b>...</b>\n",
|
||
"Moscow, IBM Diffeiting in Afghan Software Hotels (Reuters)\n",
|
||
"President Ol Luster for Profit Peaced Raised (AP)\n",
|
||
"Little red riding hood dace on depart talks #39; bank up\n",
|
||
"\n",
|
||
"--- Temperature = 1.0\n",
|
||
"Today wits House buiting debate fixes #39; supervice stake again\n",
|
||
"On Sunday arling digital poaching In for level\n",
|
||
"Moscow, DS Up 7, Top Proble Protest Caprey Mamarian Strike\n",
|
||
"President teps help of roubler stepted lessabul-Dhalitics (AFP)\n",
|
||
"Little red riding hood signs on cash in Carter-youb\n",
|
||
"\n",
|
||
"--- Temperature = 1.3\n",
|
||
"Today wits flawer ro, pSIA figat's co DroftwavesIs Talo up\n",
|
||
"On Sunday hround elitwing wint EU Powerburlinetien\n",
|
||
"Moscow, Bazz #39;s sentries olymen winnelds' next for Olympite Huc?\n",
|
||
"President lost securitys from power Elections in Smiltrials\n",
|
||
"Little red riding hood vides profit, exponituity, profitmainalist-at said listers\n",
|
||
"\n",
|
||
"--- Temperature = 1.8\n",
|
||
"Today #39;It: He deat: N.KA Asside\n",
|
||
"On Sunday i arry Par aldeup patient Wo stele1\n"
|
||
]
|
||
},
|
||
{
|
||
"ename": "KeyError",
|
||
"evalue": "0",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
||
"\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
|
||
"\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"\\n--- Temperature = {i}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgenerate_soft\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0msize\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m300\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwords\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mj\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mtemperature\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
||
"\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36mgenerate_soft\u001b[0;34m(model, size, start, temperature)\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0mchars\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0minp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0minp\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchars\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 14\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0mwords\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m'Today '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'On Sunday '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Moscow, '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'President '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Little red riding hood '\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||
"\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36mdecode\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
||
"\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36m<listcomp>\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
||
"\u001b[0;31mKeyError\u001b[0m: 0"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"def generate_soft(model,size=100,start='Today ',temperature=1.0):\n",
|
||
" inp = tokenizer.texts_to_sequences([start])[0]\n",
|
||
" chars = inp\n",
|
||
" for i in range(size):\n",
|
||
" out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
|
||
" probs = tf.exp(tf.math.log(out)/temperature).numpy().astype(np.float64)\n",
|
||
" probs = probs/np.sum(probs)\n",
|
||
" nc = np.argmax(np.random.multinomial(1,probs,1))\n",
|
||
" if nc==eos_token:\n",
|
||
" break\n",
|
||
" chars.append(nc)\n",
|
||
" inp = inp+[nc]\n",
|
||
" return decode(chars)\n",
|
||
"\n",
|
||
"words = ['Today ','On Sunday ','Moscow, ','President ','Little red riding hood ']\n",
|
||
" \n",
|
||
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
|
||
" print(f\"\\n--- Temperature = {i}\")\n",
|
||
" for j in range(5):\n",
|
||
" print(generate_soft(model,size=300,start=words[j],temperature=i))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Έχουμε εισαγάγει μία ακόμη παράμετρο που ονομάζεται **θερμοκρασία**, η οποία χρησιμοποιείται για να υποδείξει πόσο αυστηρά πρέπει να ακολουθούμε την υψηλότερη πιθανότητα. Αν η θερμοκρασία είναι 1.0, κάνουμε δίκαιη δειγματοληψία πολυωνύμου, και όταν η θερμοκρασία τείνει στο άπειρο - όλες οι πιθανότητες γίνονται ίσες, και επιλέγουμε τυχαία τον επόμενο χαρακτήρα. Στο παρακάτω παράδειγμα μπορούμε να παρατηρήσουμε ότι το κείμενο γίνεται χωρίς νόημα όταν αυξάνουμε υπερβολικά τη θερμοκρασία, και μοιάζει με \"κυκλικό\" κείμενο που παράγεται δύσκολα όταν πλησιάζει το 0.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**Αποποίηση ευθύνης**: \nΑυτό το έγγραφο έχει μεταφραστεί χρησιμοποιώντας την υπηρεσία αυτόματης μετάφρασης AI [Co-op Translator](https://github.com/Azure/co-op-translator). Παρόλο που καταβάλλουμε προσπάθειες για ακρίβεια, παρακαλούμε να έχετε υπόψη ότι οι αυτόματες μεταφράσεις ενδέχεται να περιέχουν σφάλματα ή ανακρίβειες. Το πρωτότυπο έγγραφο στη μητρική του γλώσσα θα πρέπει να θεωρείται η αυθεντική πηγή. Για κρίσιμες πληροφορίες, συνιστάται επαγγελματική ανθρώπινη μετάφραση. Δεν φέρουμε ευθύνη για τυχόν παρεξηγήσεις ή εσφαλμένες ερμηνείες που προκύπτουν από τη χρήση αυτής της μετάφρασης.\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"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"
|
||
},
|
||
"coopTranslator": {
|
||
"original_hash": "9fbb7d5fda708537649f71f5f646fcde",
|
||
"translation_date": "2025-08-29T10:37:31+00:00",
|
||
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativeTF.ipynb",
|
||
"language_code": "el"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
} |