647 lines
35 KiB
Plaintext
647 lines
35 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Εργασία ταξινόμησης κειμένου\n",
|
||
"\n",
|
||
"Σε αυτήν την ενότητα, θα ξεκινήσουμε με μια απλή εργασία ταξινόμησης κειμένου βασισμένη στο σύνολο δεδομένων **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)**: θα ταξινομήσουμε τίτλους ειδήσεων σε μία από τις 4 κατηγορίες: Κόσμος, Αθλητισμός, Επιχειρήσεις και Επιστήμη/Τεχνολογία.\n",
|
||
"\n",
|
||
"## Το Σύνολο Δεδομένων\n",
|
||
"\n",
|
||
"Για να φορτώσουμε το σύνολο δεδομένων, θα χρησιμοποιήσουμε το API **[TensorFlow Datasets](https://www.tensorflow.org/datasets)**.\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",
|
||
"\n",
|
||
"# In this tutorial, we will be training a lot of models. In order to use GPU memory cautiously,\n",
|
||
"# we will 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",
|
||
"dataset = tfds.load('ag_news_subset')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Μπορούμε τώρα να έχουμε πρόσβαση στα τμήματα εκπαίδευσης και δοκιμής του συνόλου δεδομένων χρησιμοποιώντας `dataset['train']` και `dataset['test']` αντίστοιχα:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Length of train dataset = 120000\n",
|
||
"Length of test dataset = 7600\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"ds_train = dataset['train']\n",
|
||
"ds_test = dataset['test']\n",
|
||
"\n",
|
||
"print(f\"Length of train dataset = {len(ds_train)}\")\n",
|
||
"print(f\"Length of test dataset = {len(ds_test)}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Ας εκτυπώσουμε τους πρώτους 10 νέους τίτλους από το σύνολο δεδομένων μας:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3 (Sci/Tech) -> b'AMD Debuts Dual-Core Opteron Processor' b'AMD #39;s new dual-core Opteron chip is designed mainly for corporate computing applications, including databases, Web services, and financial transactions.'\n",
|
||
"1 (Sports) -> b\"Wood's Suspension Upheld (Reuters)\" b'Reuters - Major League Baseball\\\\Monday announced a decision on the appeal filed by Chicago Cubs\\\\pitcher Kerry Wood regarding a suspension stemming from an\\\\incident earlier this season.'\n",
|
||
"2 (Business) -> b'Bush reform may have blue states seeing red' b'President Bush #39;s quot;revenue-neutral quot; tax reform needs losers to balance its winners, and people claiming the federal deduction for state and local taxes may be in administration planners #39; sights, news reports say.'\n",
|
||
"3 (Sci/Tech) -> b\"'Halt science decline in schools'\" b'Britain will run out of leading scientists unless science education is improved, says Professor Colin Pillinger.'\n",
|
||
"1 (Sports) -> b'Gerrard leaves practice' b'London, England (Sports Network) - England midfielder Steven Gerrard injured his groin late in Thursday #39;s training session, but is hopeful he will be ready for Saturday #39;s World Cup qualifier against Austria.'\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n",
|
||
"\n",
|
||
"for i,x in zip(range(5),ds_train):\n",
|
||
" print(f\"{x['label']} ({classes[x['label']]}) -> {x['title']} {x['description']}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Μετατροπή κειμένου σε διανύσματα\n",
|
||
"\n",
|
||
"Τώρα πρέπει να μετατρέψουμε το κείμενο σε **αριθμούς** που μπορούν να αναπαρασταθούν ως τελεστές (tensors). Αν θέλουμε αναπαράσταση σε επίπεδο λέξεων, πρέπει να κάνουμε δύο πράγματα:\n",
|
||
"\n",
|
||
"* Να χρησιμοποιήσουμε έναν **tokenizer** για να χωρίσουμε το κείμενο σε **tokens**.\n",
|
||
"* Να δημιουργήσουμε ένα **λεξιλόγιο** από αυτά τα tokens.\n",
|
||
"\n",
|
||
"### Περιορισμός μεγέθους λεξιλογίου\n",
|
||
"\n",
|
||
"Στο παράδειγμα του συνόλου δεδομένων AG News, το μέγεθος του λεξιλογίου είναι αρκετά μεγάλο, πάνω από 100.000 λέξεις. Γενικά, δεν χρειαζόμαστε λέξεις που εμφανίζονται σπάνια στο κείμενο — μόνο λίγες προτάσεις θα τις περιέχουν, και το μοντέλο δεν θα μάθει από αυτές. Επομένως, έχει νόημα να περιορίσουμε το μέγεθος του λεξιλογίου σε έναν μικρότερο αριθμό, περνώντας ένα όρισμα στον κατασκευαστή του vectorizer:\n",
|
||
"\n",
|
||
"Και τα δύο αυτά βήματα μπορούν να γίνουν χρησιμοποιώντας το επίπεδο **TextVectorization**. Ας δημιουργήσουμε το αντικείμενο vectorizer και στη συνέχεια ας καλέσουμε τη μέθοδο `adapt` για να περάσουμε από όλο το κείμενο και να δημιουργήσουμε ένα λεξιλόγιο:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"vocab_size = 50000\n",
|
||
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size)\n",
|
||
"vectorizer.adapt(ds_train.take(500).map(lambda x: x['title']+' '+x['description']))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"> **Σημείωση** ότι χρησιμοποιούμε μόνο ένα υποσύνολο του συνολικού συνόλου δεδομένων για να δημιουργήσουμε ένα λεξιλόγιο. Το κάνουμε αυτό για να επιταχύνουμε τον χρόνο εκτέλεσης και να μην σας κρατάμε σε αναμονή. Ωστόσο, αναλαμβάνουμε τον κίνδυνο ότι ορισμένες λέξεις από το συνολικό σύνολο δεδομένων δεν θα συμπεριληφθούν στο λεξιλόγιο και θα αγνοηθούν κατά τη διάρκεια της εκπαίδευσης. Έτσι, η χρήση του πλήρους μεγέθους λεξιλογίου και η επεξεργασία ολόκληρου του συνόλου δεδομένων κατά τη διάρκεια του `adapt` θα μπορούσε να αυξήσει την τελική ακρίβεια, αλλά όχι σημαντικά.\n",
|
||
"\n",
|
||
"Τώρα μπορούμε να έχουμε πρόσβαση στο πραγματικό λεξιλόγιο:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"['', '[UNK]', 'the', 'to', 'a', 'in', 'of', 'and', 'on', 'for']\n",
|
||
"Length of vocabulary: 5335\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"vocab = vectorizer.get_vocabulary()\n",
|
||
"vocab_size = len(vocab)\n",
|
||
"print(vocab[:10])\n",
|
||
"print(f\"Length of vocabulary: {vocab_size}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Χρησιμοποιώντας τον διανυσματοποιητή, μπορούμε εύκολα να κωδικοποιήσουμε οποιοδήποτε κείμενο σε ένα σύνολο αριθμών:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<tf.Tensor: shape=(7,), dtype=int64, numpy=array([ 112, 3695, 3, 304, 11, 1041, 1], dtype=int64)>"
|
||
]
|
||
},
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"vectorizer('I love to play with my words')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Αναπαράσταση κειμένου με τη μέθοδο Bag-of-words\n",
|
||
"\n",
|
||
"Επειδή οι λέξεις αντιπροσωπεύουν νόημα, μερικές φορές μπορούμε να κατανοήσουμε το νόημα ενός κειμένου απλώς κοιτάζοντας τις μεμονωμένες λέξεις, ανεξάρτητα από τη σειρά τους μέσα στην πρόταση. Για παράδειγμα, όταν ταξινομούμε ειδήσεις, λέξεις όπως *καιρός* και *χιόνι* είναι πιθανό να υποδεικνύουν *πρόγνωση καιρού*, ενώ λέξεις όπως *μετοχές* και *δολάριο* θα συνδέονται με *οικονομικές ειδήσεις*.\n",
|
||
"\n",
|
||
"Η **αναπαράσταση Bag-of-words** (BoW) είναι η πιο απλή και κατανοητή παραδοσιακή μέθοδος αναπαράστασης με διανύσματα. Κάθε λέξη συνδέεται με έναν δείκτη διανύσματος, και κάθε στοιχείο του διανύσματος περιέχει τον αριθμό εμφανίσεων κάθε λέξης σε ένα συγκεκριμένο έγγραφο.\n",
|
||
"\n",
|
||
" \n",
|
||
"\n",
|
||
"> **Note**: Μπορείτε επίσης να σκεφτείτε το BoW ως το άθροισμα όλων των διανυσμάτων one-hot-encoding για τις μεμονωμένες λέξεις του κειμένου.\n",
|
||
"\n",
|
||
"Παρακάτω είναι ένα παράδειγμα για το πώς να δημιουργήσετε μια αναπαράσταση Bag-of-words χρησιμοποιώντας τη βιβλιοθήκη Scikit Learn της Python:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
|
||
]
|
||
},
|
||
"execution_count": 8,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"from sklearn.feature_extraction.text import CountVectorizer\n",
|
||
"sc_vectorizer = CountVectorizer()\n",
|
||
"corpus = [\n",
|
||
" 'I like hot dogs.',\n",
|
||
" 'The dog ran fast.',\n",
|
||
" 'Its hot outside.',\n",
|
||
" ]\n",
|
||
"sc_vectorizer.fit_transform(corpus)\n",
|
||
"sc_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Μπορούμε επίσης να χρησιμοποιήσουμε τον vectorizer του Keras που ορίσαμε παραπάνω, μετατρέποντας κάθε αριθμό λέξης σε one-hot encoding και προσθέτοντας όλους αυτούς τους διανύσματα:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"array([0., 5., 0., ..., 0., 0., 0.], dtype=float32)"
|
||
]
|
||
},
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"def to_bow(text):\n",
|
||
" return tf.reduce_sum(tf.one_hot(vectorizer(text),vocab_size),axis=0)\n",
|
||
"\n",
|
||
"to_bow('My dog likes hot dogs on a hot day.').numpy()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"> **Σημείωση**: Μπορεί να σας εκπλήξει το γεγονός ότι το αποτέλεσμα διαφέρει από το προηγούμενο παράδειγμα. Ο λόγος είναι ότι στο παράδειγμα του Keras, το μήκος του διανύσματος αντιστοιχεί στο μέγεθος του λεξιλογίου, το οποίο δημιουργήθηκε από ολόκληρο το σύνολο δεδομένων AG News, ενώ στο παράδειγμα του Scikit Learn δημιουργήσαμε το λεξιλόγιο από το δείγμα κειμένου επί τόπου.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Εκπαίδευση του ταξινομητή BoW\n",
|
||
"\n",
|
||
"Τώρα που έχουμε μάθει πώς να δημιουργούμε την αναπαράσταση bag-of-words του κειμένου μας, ας εκπαιδεύσουμε έναν ταξινομητή που τη χρησιμοποιεί. Πρώτα, πρέπει να μετατρέψουμε το σύνολο δεδομένων μας σε αναπαράσταση bag-of-words. Αυτό μπορεί να επιτευχθεί χρησιμοποιώντας τη συνάρτηση `map` με τον εξής τρόπο:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 11,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"batch_size = 128\n",
|
||
"\n",
|
||
"ds_train_bow = ds_train.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)\n",
|
||
"ds_test_bow = ds_test.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Τώρα ας ορίσουμε ένα απλό νευρωνικό δίκτυο ταξινόμησης που περιέχει ένα γραμμικό επίπεδο. Το μέγεθος εισόδου είναι `vocab_size` και το μέγεθος εξόδου αντιστοιχεί στον αριθμό των κατηγοριών (4). Επειδή λύνουμε ένα πρόβλημα ταξινόμησης, η τελική συνάρτηση ενεργοποίησης είναι **softmax**:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 12,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"938/938 [==============================] - 66s 70ms/step - loss: 0.6144 - acc: 0.8427 - val_loss: 0.4416 - val_acc: 0.8697\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<keras.callbacks.History at 0x20c70a947f0>"
|
||
]
|
||
},
|
||
"execution_count": 12,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"model = keras.models.Sequential([\n",
|
||
" keras.layers.Dense(4,activation='softmax',input_shape=(vocab_size,))\n",
|
||
"])\n",
|
||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
||
"model.fit(ds_train_bow,validation_data=ds_test_bow)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Δεδομένου ότι έχουμε 4 κατηγορίες, μια ακρίβεια πάνω από 80% είναι ένα καλό αποτέλεσμα.\n",
|
||
"\n",
|
||
"## Εκπαίδευση ενός ταξινομητή ως ένα δίκτυο\n",
|
||
"\n",
|
||
"Επειδή ο vectorizer είναι επίσης ένα επίπεδο Keras, μπορούμε να ορίσουμε ένα δίκτυο που τον περιλαμβάνει και να το εκπαιδεύσουμε από άκρη σε άκρη. Με αυτόν τον τρόπο, δεν χρειάζεται να κάνουμε vectorize το σύνολο δεδομένων χρησιμοποιώντας `map`, μπορούμε απλώς να περάσουμε το αρχικό σύνολο δεδομένων στην είσοδο του δικτύου.\n",
|
||
"\n",
|
||
"> **Σημείωση**: Θα πρέπει ακόμα να εφαρμόσουμε maps στο σύνολο δεδομένων μας για να μετατρέψουμε πεδία από λεξικά (όπως `title`, `description` και `label`) σε πλειάδες. Ωστόσο, όταν φορτώνουμε δεδομένα από τον δίσκο, μπορούμε να δημιουργήσουμε ένα σύνολο δεδομένων με την απαιτούμενη δομή από την αρχή.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 13,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Model: \"model\"\n",
|
||
"_________________________________________________________________\n",
|
||
" Layer (type) Output Shape Param # \n",
|
||
"=================================================================\n",
|
||
" input_1 (InputLayer) [(None, 1)] 0 \n",
|
||
" \n",
|
||
" text_vectorization (TextVec (None, None) 0 \n",
|
||
" torization) \n",
|
||
" \n",
|
||
" tf.one_hot (TFOpLambda) (None, None, 5335) 0 \n",
|
||
" \n",
|
||
" tf.math.reduce_sum (TFOpLam (None, 5335) 0 \n",
|
||
" bda) \n",
|
||
" \n",
|
||
" dense_2 (Dense) (None, 4) 21344 \n",
|
||
" \n",
|
||
"=================================================================\n",
|
||
"Total params: 21,344\n",
|
||
"Trainable params: 21,344\n",
|
||
"Non-trainable params: 0\n",
|
||
"_________________________________________________________________\n",
|
||
"938/938 [==============================] - 73s 77ms/step - loss: 0.6057 - acc: 0.8414 - val_loss: 0.4202 - val_acc: 0.8736\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<keras.callbacks.History at 0x20c721521f0>"
|
||
]
|
||
},
|
||
"execution_count": 13,
|
||
"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",
|
||
"inp = keras.Input(shape=(1,),dtype=tf.string)\n",
|
||
"x = vectorizer(inp)\n",
|
||
"x = tf.reduce_sum(tf.one_hot(x,vocab_size),axis=1)\n",
|
||
"out = keras.layers.Dense(4,activation='softmax')(x)\n",
|
||
"model = keras.models.Model(inp,out)\n",
|
||
"model.summary()\n",
|
||
"\n",
|
||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Διγράμματα, τριγράμματα και n-γράμματα\n",
|
||
"\n",
|
||
"Ένας περιορισμός της προσέγγισης bag-of-words είναι ότι ορισμένες λέξεις αποτελούν μέρος εκφράσεων πολλαπλών λέξεων. Για παράδειγμα, η λέξη 'hot dog' έχει εντελώς διαφορετική σημασία από τις λέξεις 'hot' και 'dog' σε άλλες περιπτώσεις. Εάν εκπροσωπούμε τις λέξεις 'hot' και 'dog' πάντα χρησιμοποιώντας τους ίδιους διανύσματα, μπορεί να προκαλέσει σύγχυση στο μοντέλο μας.\n",
|
||
"\n",
|
||
"Για να αντιμετωπιστεί αυτό, συχνά χρησιμοποιούνται **αναπαραστάσεις n-γραμμάτων** σε μεθόδους ταξινόμησης εγγράφων, όπου η συχνότητα κάθε λέξης, διλέξης ή τριλέξης αποτελεί χρήσιμο χαρακτηριστικό για την εκπαίδευση ταξινομητών. Στις αναπαραστάσεις διγραμμάτων, για παράδειγμα, προσθέτουμε όλα τα ζεύγη λέξεων στο λεξιλόγιο, επιπλέον των αρχικών λέξεων.\n",
|
||
"\n",
|
||
"Παρακάτω είναι ένα παράδειγμα για το πώς να δημιουργήσετε μια αναπαράσταση bag-of-words διγραμμάτων χρησιμοποιώντας το Scikit Learn:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 14,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Vocabulary:\n",
|
||
" {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
|
||
" dtype=int64)"
|
||
]
|
||
},
|
||
"execution_count": 14,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n",
|
||
"corpus = [\n",
|
||
" 'I like hot dogs.',\n",
|
||
" 'The dog ran fast.',\n",
|
||
" 'Its hot outside.',\n",
|
||
" ]\n",
|
||
"bigram_vectorizer.fit_transform(corpus)\n",
|
||
"print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n",
|
||
"bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Το κύριο μειονέκτημα της προσέγγισης n-gram είναι ότι το μέγεθος του λεξιλογίου αρχίζει να αυξάνεται εξαιρετικά γρήγορα. Στην πράξη, χρειάζεται να συνδυάσουμε την αναπαράσταση n-gram με μια τεχνική μείωσης διαστάσεων, όπως τα *embeddings*, τα οποία θα συζητήσουμε στην επόμενη ενότητα.\n",
|
||
"\n",
|
||
"Για να χρησιμοποιήσουμε μια αναπαράσταση n-gram στο σύνολο δεδομένων μας **AG News**, πρέπει να περάσουμε την παράμετρο `ngrams` στον κατασκευαστή `TextVectorization`. Το μέγεθος του λεξιλογίου ενός bigram είναι **σημαντικά μεγαλύτερο**, στην περίπτωσή μας ξεπερνά τα 1,3 εκατομμύρια tokens! Επομένως, έχει νόημα να περιορίσουμε και τα bigram tokens σε έναν λογικό αριθμό.\n",
|
||
"\n",
|
||
"Θα μπορούσαμε να χρησιμοποιήσουμε τον ίδιο κώδικα όπως παραπάνω για να εκπαιδεύσουμε τον ταξινομητή, ωστόσο, αυτό θα ήταν πολύ αναποτελεσματικό από άποψη μνήμης. Στην επόμενη ενότητα, θα εκπαιδεύσουμε τον ταξινομητή bigram χρησιμοποιώντας embeddings. Στο μεταξύ, μπορείτε να πειραματιστείτε με την εκπαίδευση του ταξινομητή bigram σε αυτό το notebook και να δείτε αν μπορείτε να πετύχετε μεγαλύτερη ακρίβεια.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Αυτόματος Υπολογισμός Διανυσμάτων BoW\n",
|
||
"\n",
|
||
"Στο παραπάνω παράδειγμα υπολογίσαμε τα διανύσματα BoW χειροκίνητα, προσθέτοντας τις one-hot κωδικοποιήσεις των μεμονωμένων λέξεων. Ωστόσο, η τελευταία έκδοση του TensorFlow μας επιτρέπει να υπολογίζουμε αυτόματα τα διανύσματα BoW περνώντας την παράμετρο `output_mode='count` στον κατασκευαστή του vectorizer. Αυτό καθιστά τον ορισμό και την εκπαίδευση του μοντέλου μας σημαντικά πιο εύκολη:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Training vectorizer\n",
|
||
"938/938 [==============================] - 7s 7ms/step - loss: 0.5929 - acc: 0.8486 - val_loss: 0.4168 - val_acc: 0.8772\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<keras.callbacks.History at 0x20c725217c0>"
|
||
]
|
||
},
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"model = keras.models.Sequential([\n",
|
||
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='count'),\n",
|
||
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
||
"])\n",
|
||
"print(\"Training vectorizer\")\n",
|
||
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Συχνότητα όρων - αντίστροφη συχνότητα εγγράφων (TF-IDF)\n",
|
||
"\n",
|
||
"Στην αναπαράσταση BoW, οι εμφανίσεις λέξεων σταθμίζονται χρησιμοποιώντας την ίδια τεχνική ανεξάρτητα από τη λέξη. Ωστόσο, είναι προφανές ότι συχνές λέξεις όπως *το* και *σε* είναι πολύ λιγότερο σημαντικές για την ταξινόμηση σε σύγκριση με εξειδικευμένους όρους. Στις περισσότερες εργασίες επεξεργασίας φυσικής γλώσσας (NLP), ορισμένες λέξεις είναι πιο σχετικές από άλλες.\n",
|
||
"\n",
|
||
"**TF-IDF** σημαίνει **συχνότητα όρων - αντίστροφη συχνότητα εγγράφων**. Πρόκειται για μια παραλλαγή της μεθόδου bag-of-words, όπου αντί για μια δυαδική τιμή 0/1 που υποδεικνύει την εμφάνιση μιας λέξης σε ένα έγγραφο, χρησιμοποιείται μια τιμή κινητής υποδιαστολής, η οποία σχετίζεται με τη συχνότητα εμφάνισης της λέξης στο σύνολο των κειμένων.\n",
|
||
"\n",
|
||
"Πιο επίσημα, το βάρος $w_{ij}$ μιας λέξης $i$ στο έγγραφο $j$ ορίζεται ως:\n",
|
||
"$$\n",
|
||
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
|
||
"$$\n",
|
||
"όπου\n",
|
||
"* $tf_{ij}$ είναι ο αριθμός εμφανίσεων της $i$ στο $j$, δηλαδή η τιμή BoW που έχουμε δει προηγουμένως\n",
|
||
"* $N$ είναι ο αριθμός των εγγράφων στη συλλογή\n",
|
||
"* $df_i$ είναι ο αριθμός των εγγράφων που περιέχουν τη λέξη $i$ σε ολόκληρη τη συλλογή\n",
|
||
"\n",
|
||
"Η τιμή TF-IDF $w_{ij}$ αυξάνεται αναλογικά με τον αριθμό των φορών που εμφανίζεται μια λέξη σε ένα έγγραφο και μειώνεται ανάλογα με τον αριθμό των εγγράφων στο σύνολο που περιέχουν τη λέξη. Αυτό βοηθά να προσαρμοστεί το βάρος για το γεγονός ότι ορισμένες λέξεις εμφανίζονται πιο συχνά από άλλες. Για παράδειγμα, αν η λέξη εμφανίζεται σε *κάθε* έγγραφο της συλλογής, τότε $df_i=N$, και $w_{ij}=0$, και αυτοί οι όροι θα αγνοηθούν πλήρως.\n",
|
||
"\n",
|
||
"Μπορείτε εύκολα να δημιουργήσετε την αναπαράσταση TF-IDF ενός κειμένου χρησιμοποιώντας το Scikit Learn:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 16,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n",
|
||
" 0.43381609, 0. , 0. , 0. , 0. ,\n",
|
||
" 0. , 0. , 0. , 0. , 0. ,\n",
|
||
" 0. ]])"
|
||
]
|
||
},
|
||
"execution_count": 16,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
|
||
"vectorizer = TfidfVectorizer(ngram_range=(1,2))\n",
|
||
"vectorizer.fit_transform(corpus)\n",
|
||
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Στο Keras, το επίπεδο `TextVectorization` μπορεί να υπολογίσει αυτόματα τις συχνότητες TF-IDF περνώντας την παράμετρο `output_mode='tf-idf'`. Ας επαναλάβουμε τον κώδικα που χρησιμοποιήσαμε παραπάνω για να δούμε αν η χρήση του TF-IDF αυξάνει την ακρίβεια:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 17,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Training vectorizer\n",
|
||
"938/938 [==============================] - 12s 12ms/step - loss: 0.4197 - acc: 0.8662 - val_loss: 0.3432 - val_acc: 0.8849\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<keras.callbacks.History at 0x20c729dfd30>"
|
||
]
|
||
},
|
||
"execution_count": 17,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"model = keras.models.Sequential([\n",
|
||
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='tf-idf'),\n",
|
||
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
||
"])\n",
|
||
"print(\"Training vectorizer\")\n",
|
||
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\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",
|
||
"Παρόλο που οι αναπαραστάσεις TF-IDF παρέχουν βάρη συχνότητας σε διαφορετικές λέξεις, δεν μπορούν να αποδώσουν τη σημασία ή τη σειρά. Όπως είπε ο διάσημος γλωσσολόγος J. R. Firth το 1935, \"Η πλήρης σημασία μιας λέξης είναι πάντα συμφραζόμενη, και καμία μελέτη της σημασίας εκτός συμφραζομένων δεν μπορεί να ληφθεί σοβαρά.\" Αργότερα στο μάθημα, θα μάθουμε πώς να αποτυπώνουμε συμφραζόμενες πληροφορίες από κείμενο χρησιμοποιώντας μοντελοποίηση γλώσσας.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**Αποποίηση ευθύνης**: \nΑυτό το έγγραφο έχει μεταφραστεί χρησιμοποιώντας την υπηρεσία αυτόματης μετάφρασης [Co-op Translator](https://github.com/Azure/co-op-translator). Παρόλο που καταβάλλουμε προσπάθειες για ακρίβεια, παρακαλούμε να έχετε υπόψη ότι οι αυτοματοποιημένες μεταφράσεις ενδέχεται να περιέχουν σφάλματα ή ανακρίβειες. Το πρωτότυπο έγγραφο στη μητρική του γλώσσα θα πρέπει να θεωρείται η αυθεντική πηγή. Για κρίσιμες πληροφορίες, συνιστάται επαγγελματική ανθρώπινη μετάφραση. Δεν φέρουμε ευθύνη για τυχόν παρεξηγήσεις ή εσφαλμένες ερμηνείες που προκύπτουν από τη χρήση αυτής της μετάφρασης.\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"interpreter": {
|
||
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
||
},
|
||
"kernel_info": {
|
||
"name": "conda-env-py37_tensorflow-py"
|
||
},
|
||
"kernelspec": {
|
||
"display_name": "py37_tensorflow",
|
||
"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"
|
||
},
|
||
"nteract": {
|
||
"version": "nteract-front-end@1.0.0"
|
||
},
|
||
"coopTranslator": {
|
||
"original_hash": "19b43951d55b377a76209c24c1f017e4",
|
||
"translation_date": "2025-08-29T11:02:48+00:00",
|
||
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb",
|
||
"language_code": "el"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
} |