647 lines
32 KiB
Plaintext
647 lines
32 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",
|
||
"Тепер нам потрібно перетворити текст у **числа**, які можна представити як тензори. Якщо ми хочемо представлення на рівні слів, необхідно виконати два кроки:\n",
|
||
"\n",
|
||
"* Використати **токенізатор**, щоб розбити текст на **токени**.\n",
|
||
"* Побудувати **словник** цих токенів.\n",
|
||
"\n",
|
||
"### Обмеження розміру словника\n",
|
||
"\n",
|
||
"У прикладі з набором даних AG News розмір словника досить великий — понад 100 тисяч слів. Загалом, нам не потрібні слова, які рідко зустрічаються в тексті — лише кілька речень їх міститимуть, і модель не зможе навчитися на них. Тому має сенс обмежити розмір словника до меншого числа, передавши аргумент до конструктора векторизатора:\n",
|
||
"\n",
|
||
"Обидва ці кроки можна виконати за допомогою шару **TextVectorization**. Давайте створимо об'єкт векторизатора, а потім викличемо метод `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": [
|
||
"## Представлення тексту за методом \"Мішок слів\"\n",
|
||
"\n",
|
||
"Оскільки слова передають значення, іноді ми можемо зрозуміти сенс тексту, просто дивлячись на окремі слова, незалежно від їхнього порядку в реченні. Наприклад, при класифікації новин слова, такі як *погода* і *сніг*, ймовірно, вказуватимуть на *прогноз погоди*, тоді як слова *акції* і *долар* будуть відноситися до *фінансових новин*.\n",
|
||
"\n",
|
||
"**Мішок слів** (BoW) — це найпростіше для розуміння традиційне представлення векторів. Кожне слово пов'язане з індексом вектора, а елемент вектора містить кількість появ кожного слова в даному документі.\n",
|
||
"\n",
|
||
" \n",
|
||
"\n",
|
||
"> **Note**: Ви також можете думати про BoW як про суму всіх векторів з одним активним елементом (one-hot-encoded) для окремих слів у тексті.\n",
|
||
"\n",
|
||
"Нижче наведено приклад того, як створити представлення \"Мішок слів\" за допомогою бібліотеки 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": [
|
||
"Ми також можемо використовувати векторизатор Keras, який ми визначили вище, перетворюючи кожен номер слова в one-hot кодування і додаючи всі ці вектори разом:\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",
|
||
"Тепер, коли ми навчилися створювати представлення тексту у вигляді \"мішка слів\", давайте навчимо класифікатор, який його використовує. Спочатку нам потрібно перетворити наш набір даних у представлення \"мішка слів\". Це можна зробити за допомогою функції `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",
|
||
"Оскільки векторизатор також є шаром Keras, ми можемо визначити мережу, яка включає його, і навчати її від початку до кінця. Таким чином, нам не потрібно векторизувати набір даних за допомогою `map`, ми можемо просто передати оригінальний набір даних на вхід мережі.\n",
|
||
"\n",
|
||
"> **Примітка**: Нам все одно доведеться застосовувати `map` до нашого набору даних, щоб перетворити поля зі словників (таких як `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",
|
||
"Одним із обмежень підходу \"мішок слів\" є те, що деякі слова є частиною багатослівних виразів. Наприклад, слово \"гарячий хот-дог\" має зовсім інше значення, ніж слова \"гарячий\" і \"пес\" у інших контекстах. Якщо ми завжди представлятимемо слова \"гарячий\" і \"пес\" за допомогою однакових векторів, це може заплутати нашу модель.\n",
|
||
"\n",
|
||
"Щоб вирішити цю проблему, у методах класифікації документів часто використовуються **n-грамні представлення**, де частота кожного слова, пари слів або трійки слів є корисною ознакою для навчання класифікаторів. Наприклад, у біграмному представленні ми додамо до словника всі пари слів, окрім початкових слів.\n",
|
||
"\n",
|
||
"Нижче наведено приклад того, як створити біграмне представлення \"мішка слів\" за допомогою 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-грам полягає в тому, що розмір словника починає зростати надзвичайно швидко. На практиці нам потрібно поєднувати представлення n-грам із технікою зменшення розмірності, такою як *вбудовування* (embeddings), про яку ми поговоримо в наступному розділі.\n",
|
||
"\n",
|
||
"Щоб використовувати представлення n-грам у нашому наборі даних **AG News**, нам потрібно передати параметр `ngrams` до конструктора `TextVectorization`. Довжина словника біграм є **значно більшою**, у нашому випадку це понад 1,3 мільйона токенів! Тому має сенс обмежити кількість токенів біграм до якогось розумного числа.\n",
|
||
"\n",
|
||
"Ми могли б використати той самий код, що й вище, для навчання класифікатора, однак це було б дуже неефективно з точки зору пам’яті. У наступному розділі ми будемо навчати класифікатор біграм, використовуючи вбудовування. Тим часом ви можете експериментувати з навчанням класифікатора біграм у цьому ноутбуці та перевірити, чи зможете досягти вищої точності.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Автоматичний розрахунок векторів BoW\n",
|
||
"\n",
|
||
"У наведеному вище прикладі ми обчислювали вектори BoW вручну, підсумовуючи one-hot кодування окремих слів. Однак, остання версія TensorFlow дозволяє автоматично обчислювати вектори BoW, передаючи параметр `output_mode='count` до конструктора векторизатора. Це значно спрощує визначення та навчання нашої моделі:\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 (мішок слів) частота появи слів зважується однаково незалежно від самого слова. Однак очевидно, що часто вживані слова, такі як *a* і *in*, набагато менш важливі для класифікації, ніж спеціалізовані терміни. У більшості завдань обробки природної мови деякі слова є більш релевантними, ніж інші.\n",
|
||
"\n",
|
||
"**TF-IDF** означає **частота терміну - обернена частота документа**. Це варіація мішка слів, де замість двійкового значення 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 надають ваги частотам різних слів, вони не здатні передати значення чи порядок. Як сказав відомий лінгвіст Дж. Р. Ферт у 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-30T10:44:16+00:00",
|
||
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb",
|
||
"language_code": "uk"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
} |