647 lines
26 KiB
Plaintext
647 lines
26 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Úloha klasifikácie textu\n",
|
|
"\n",
|
|
"V tomto module začneme jednoduchou úlohou klasifikácie textu založenou na dátovej sade **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)**: budeme klasifikovať nadpisy správ do jednej zo 4 kategórií: Svet, Šport, Biznis a Veda/Technológie.\n",
|
|
"\n",
|
|
"## Dátová sada\n",
|
|
"\n",
|
|
"Na načítanie dátovej sady použijeme 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": [
|
|
"Teraz môžeme pristupovať k tréningovej a testovacej časti dátovej sady pomocou `dataset['train']` a `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": [
|
|
"Poďme vytlačiť prvých 10 nových titulkov z nášho datasetu:\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": [
|
|
"## Vektorizácia textu\n",
|
|
"\n",
|
|
"Teraz musíme previesť text na **čísla**, ktoré môžu byť reprezentované ako tenzory. Ak chceme reprezentáciu na úrovni slov, musíme urobiť dve veci:\n",
|
|
"\n",
|
|
"* Použiť **tokenizér** na rozdelenie textu na **tokeny**.\n",
|
|
"* Vytvoriť **slovník** týchto tokenov.\n",
|
|
"\n",
|
|
"### Obmedzenie veľkosti slovníka\n",
|
|
"\n",
|
|
"V príklade s datasetom AG News je veľkosť slovníka pomerne veľká, viac ako 100 tisíc slov. Vo všeobecnosti nepotrebujeme slová, ktoré sa v texte vyskytujú zriedkavo — len niekoľko viet ich bude obsahovať, a model sa z nich nenaučí. Preto má zmysel obmedziť veľkosť slovníka na menší počet tým, že odovzdáme argument konštruktoru vektorizéra:\n",
|
|
"\n",
|
|
"Obe tieto kroky je možné zvládnuť pomocou vrstvy **TextVectorization**. Poďme vytvoriť objekt vektorizéra a potom zavolať metódu `adapt`, aby sme prešli všetok text a vytvorili slovník:\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": [
|
|
"> **Poznámka**: Používame iba podmnožinu celého datasetu na vytvorenie slovníka. Robíme to preto, aby sme urýchlili čas vykonávania a nemuseli ste dlho čakať. Avšak, týmto podstupujeme riziko, že niektoré slová z celého datasetu nebudú zahrnuté do slovníka a budú ignorované počas tréningu. Použitie celej veľkosti slovníka a prebehnutie celého datasetu počas `adapt` by malo zvýšiť konečnú presnosť, ale nie výrazne.\n",
|
|
"\n",
|
|
"Teraz môžeme pristúpiť k samotnému slovníku:\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": [
|
|
"Pomocou vektorizátora môžeme ľahko zakódovať akýkoľvek text do množiny čísel:\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": [
|
|
"## Reprezentácia textu pomocou metódy Bag-of-words\n",
|
|
"\n",
|
|
"Keďže slová nesú význam, niekedy dokážeme pochopiť význam textu len na základe jednotlivých slov, bez ohľadu na ich poradie vo vete. Napríklad pri klasifikácii správ môžu slová ako *počasie* a *sneh* naznačovať *predpoveď počasia*, zatiaľ čo slová ako *akcie* a *dolár* by mohli poukazovať na *finančné správy*.\n",
|
|
"\n",
|
|
"**Bag-of-words** (BoW) je najjednoduchšia tradičná metóda reprezentácie textu pomocou vektorov. Každé slovo je priradené k indexu vektora a prvok vektora obsahuje počet výskytov daného slova v konkrétnom dokumente.\n",
|
|
"\n",
|
|
" \n",
|
|
"\n",
|
|
"> **Note**: Na metódu BoW sa môžete pozerať aj ako na súčet všetkých vektorov s jedným aktívnym prvkom (one-hot-encoded) pre jednotlivé slová v texte.\n",
|
|
"\n",
|
|
"Nižšie je uvedený príklad, ako vytvoriť reprezentáciu Bag-of-words pomocou knižnice Scikit Learn v jazyku 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": [
|
|
"Môžeme tiež použiť Keras vektorizátor, ktorý sme definovali vyššie, previesť každé číslo slova na one-hot kódovanie a všetky tieto vektory sčítať:\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": [
|
|
"> **Poznámka**: Môže vás prekvapiť, že výsledok sa líši od predchádzajúceho príkladu. Dôvodom je, že v príklade s Kerasom dĺžka vektora zodpovedá veľkosti slovníka, ktorý bol vytvorený z celého datasetu AG News, zatiaľ čo v príklade so Scikit Learn sme slovník vytvorili na základe ukážkového textu za pochodu.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Trénovanie klasifikátora BoW\n",
|
|
"\n",
|
|
"Teraz, keď sme sa naučili, ako vytvoriť reprezentáciu textu pomocou metódy bag-of-words, poďme natrénovať klasifikátor, ktorý ju využíva. Najskôr musíme náš dataset previesť na reprezentáciu bag-of-words. To môžeme dosiahnuť použitím funkcie `map` nasledovným spôsobom:\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": [
|
|
"Teraz definujme jednoduchú klasifikačnú neurónovú sieť, ktorá obsahuje jednu lineárnu vrstvu. Veľkosť vstupu je `vocab_size` a veľkosť výstupu zodpovedá počtu tried (4). Keďže riešime klasifikačnú úlohu, konečná aktivačná funkcia je **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": [
|
|
"Keďže máme 4 triedy, presnosť nad 80 % je dobrý výsledok.\n",
|
|
"\n",
|
|
"## Trénovanie klasifikátora ako jednej siete\n",
|
|
"\n",
|
|
"Keďže vektorizér je tiež vrstva Keras, môžeme definovať sieť, ktorá ho zahŕňa, a trénovať ju od začiatku do konca. Týmto spôsobom nemusíme vektorizovať dataset pomocou `map`, stačí nám pôvodný dataset poslať na vstup siete.\n",
|
|
"\n",
|
|
"> **Poznámka**: Stále by sme museli aplikovať mapy na náš dataset, aby sme konvertovali polia zo slovníkov (ako `title`, `description` a `label`) na dvojice. Avšak pri načítavaní dát z disku môžeme hneď na začiatku vytvoriť dataset s požadovanou štruktúrou.\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": [
|
|
"## Bigramy, trigramy a n-gramy\n",
|
|
"\n",
|
|
"Jedným z obmedzení prístupu bag-of-words je, že niektoré slová sú súčasťou viacslovných výrazov. Napríklad slovo 'hot dog' má úplne iný význam ako slová 'hot' a 'dog' v iných kontextoch. Ak by sme slová 'hot' a 'dog' vždy reprezentovali rovnakými vektormi, mohlo by to zmiasť náš model.\n",
|
|
"\n",
|
|
"Na riešenie tohto problému sa často používajú **n-gramové reprezentácie** v metódach klasifikácie dokumentov, kde frekvencia každého slova, dvojslovného alebo trojslovného výrazu predstavuje užitočný prvok na trénovanie klasifikátorov. Napríklad v bigramových reprezentáciách pridáme do slovníka všetky dvojice slov, okrem pôvodných slov.\n",
|
|
"\n",
|
|
"Nižšie je uvedený príklad, ako generovať bigramovú reprezentáciu bag-of-words pomocou 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": [
|
|
"Hlavnou nevýhodou prístupu n-gramov je, že veľkosť slovníka začne rásť extrémne rýchlo. V praxi je potrebné skombinovať reprezentáciu n-gramov s technikou redukcie dimenzií, ako sú *embeddingy*, o ktorých budeme hovoriť v ďalšej jednotke.\n",
|
|
"\n",
|
|
"Aby sme mohli použiť reprezentáciu n-gramov v našej **AG News** množine údajov, musíme parameter `ngrams` odovzdať do nášho konštruktora `TextVectorization`. Dĺžka bigramového slovníka je **výrazne väčšia**, v našom prípade ide o viac ako 1,3 milióna tokenov! Preto má zmysel obmedziť aj bigramové tokeny na nejaký rozumný počet.\n",
|
|
"\n",
|
|
"Mohli by sme použiť rovnaký kód ako vyššie na trénovanie klasifikátora, avšak bolo by to veľmi neefektívne z hľadiska pamäte. V ďalšej jednotke budeme trénovať bigramový klasifikátor pomocou embeddingov. Medzitým môžete experimentovať s trénovaním bigramového klasifikátora v tomto notebooku a zistiť, či dokážete dosiahnuť vyššiu presnosť.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Automatický výpočet BoW vektorov\n",
|
|
"\n",
|
|
"V príklade vyššie sme BoW vektory počítali ručne sčítaním one-hot kódovaní jednotlivých slov. Avšak, najnovšia verzia TensorFlow nám umožňuje vypočítať BoW vektory automaticky, a to pridaním parametra `output_mode='count'` do konštruktora vektorizéra. Týmto spôsobom je definovanie a trénovanie nášho modelu výrazne jednoduchšie:\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": [
|
|
"## Frekvencia termínov - inverzná frekvencia dokumentov (TF-IDF)\n",
|
|
"\n",
|
|
"V reprezentácii BoW sa výskyty slov vážia rovnakou technikou bez ohľadu na samotné slovo. Je však zrejmé, že časté slová ako *a* a *v* sú oveľa menej dôležité pre klasifikáciu než špecializované výrazy. Pri väčšine úloh NLP sú niektoré slová relevantnejšie než iné.\n",
|
|
"\n",
|
|
"**TF-IDF** znamená **frekvencia termínov - inverzná frekvencia dokumentov**. Ide o variáciu metódy bag-of-words, kde namiesto binárnej hodnoty 0/1, ktorá označuje prítomnosť slova v dokumente, sa používa hodnota s pohyblivou desatinnou čiarkou, ktorá súvisí s frekvenciou výskytu slova v korpuse.\n",
|
|
"\n",
|
|
"Formálne je váha $w_{ij}$ slova $i$ v dokumente $j$ definovaná ako:\n",
|
|
"$$\n",
|
|
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
|
|
"$$\n",
|
|
"kde\n",
|
|
"* $tf_{ij}$ je počet výskytov $i$ v $j$, teda hodnota BoW, ktorú sme videli predtým\n",
|
|
"* $N$ je počet dokumentov v kolekcii\n",
|
|
"* $df_i$ je počet dokumentov obsahujúcich slovo $i$ v celej kolekcii\n",
|
|
"\n",
|
|
"Hodnota TF-IDF $w_{ij}$ rastie úmerne počtu výskytov slova v dokumente a je upravená počtom dokumentov v korpuse, ktoré obsahujú dané slovo, čo pomáha zohľadniť fakt, že niektoré slová sa vyskytujú častejšie než iné. Napríklad, ak sa slovo vyskytuje *v každom* dokumente kolekcie, $df_i=N$, a $w_{ij}=0$, a tieto výrazy by boli úplne ignorované.\n",
|
|
"\n",
|
|
"TF-IDF vektorizáciu textu môžete jednoducho vytvoriť pomocou 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": [
|
|
"V Keras môže vrstva `TextVectorization` automaticky vypočítať TF-IDF frekvencie pridaním parametra `output_mode='tf-idf'`. Poďme zopakovať kód, ktorý sme použili vyššie, aby sme zistili, či použitie TF-IDF zvyšuje presnosť:\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": [
|
|
"## Záver\n",
|
|
"\n",
|
|
"Aj keď reprezentácie TF-IDF poskytujú váhové hodnoty pre rôzne slová na základe ich frekvencie, nedokážu zachytiť význam alebo poradie. Ako povedal známy lingvista J. R. Firth v roku 1935: „Úplný význam slova je vždy kontextový a žiadna štúdia významu mimo kontextu nemôže byť braná vážne.“ Neskôr v kurze sa naučíme, ako zachytiť kontextové informácie z textu pomocou jazykového modelovania.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Upozornenie**: \nTento dokument bol preložený pomocou služby na automatický preklad [Co-op Translator](https://github.com/Azure/co-op-translator). Hoci sa snažíme o presnosť, upozorňujeme, že automatické preklady môžu obsahovať chyby alebo nepresnosti. Pôvodný dokument v jeho pôvodnom jazyku by mal byť považovaný za autoritatívny zdroj. Pre dôležité informácie odporúčame profesionálny ľudský preklad. Nezodpovedáme za akékoľvek nedorozumenia alebo nesprávne interpretácie vyplývajúce z použitia tohto prekladu.\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-30T01:14:52+00:00",
|
|
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb",
|
|
"language_code": "sk"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
} |