AI-For-Beginners/translations/pcm/lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb

647 lines
25 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Text classification task\n",
"\n",
"For dis module, we go start wit one simple text classification task wey base on **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)** dataset: we go classify news headlines into one of di 4 categories: World, Sports, Business and Sci/Tech.\n",
"\n",
"## The Dataset\n",
"\n",
"To load di dataset, we go use di **[TensorFlow Datasets](https://www.tensorflow.org/datasets)** API.\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": [
"We fit access di train an test part of di dataset by usin `dataset['train']` an `dataset['test']` respekivli:\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": [
"Make we print di first 10 new headlines wey dey our dataset:\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": [
"## Text vectorization\n",
"\n",
"Now wey we wan change text to **numbers** wey fit dey represent as tensors. If we wan do am for word-level representation, we go need do two things:\n",
"\n",
"* Use **tokenizer** to break text into **tokens**.\n",
"* Build **vocabulary** for those tokens.\n",
"\n",
"### Limiting vocabulary size\n",
"\n",
"For di AG News dataset example, di vocabulary size big well-well, e pass 100k words. Normally, we no need words wey no dey show for text often — na only small sentences go get dem, and di model no go fit learn anything from dem. So e make sense to reduce di vocabulary size to smaller number by passing one argument to di vectorizer constructor:\n",
"\n",
"Both of dem steps fit dey handle with **TextVectorization** layer. Make we create di vectorizer object, then use di `adapt` method to check all di text and build di vocabulary:\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": [
"> **Note** say we dey use only small part of the whole dataset to build vocabulary. We dey do am make execution time quick and make you no wait too long. But, e get risk say some words wey dey whole dataset no go enter the vocabulary, and dem go miss during training. So, if we use the whole vocabulary size and run through all the dataset during `adapt`, e fit make the final accuracy better, but e no go too change am. \n",
"\n",
"Now we fit check the real vocabulary:\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": [
"With di vectorizer, we fit encode any text into set of numbers easy:\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 text representation\n",
"\n",
"Because say words dey carry meaning, sometimes we fit sabi wetin text dey talk just by looking at the words one by one, no matter how dem arrange for sentence. For example, if we wan classify news, words like *weather* and *snow* fit mean *weather forecast*, while words like *stocks* and *dollar* go fit mean *financial news*.\n",
"\n",
"**Bag-of-words** (BoW) vector representation na the simplest way to understand traditional vector representation. Each word dey connect to one vector index, and one vector element dey show how many times each word appear for one document.\n",
"\n",
"![Image showing how a bag of words vector representation is represented in memory.](../../../../../translated_images/pcm/bag-of-words-example.606fc1738f1d7ba9.webp) \n",
"\n",
"> **Note**: You fit also think of BoW as sum of all one-hot-encoded vectors for each word wey dey the text.\n",
"\n",
"Below na example of how to generate bag-of-words representation using the Scikit Learn python library:\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": [
"We fit use di Keras vectorizer wey we don define before, change each word number to one-hot encoding and add all di vectors together:\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": [
"> **Note**: E fit surprise you say di result no be di same as di one wey dey di previous example. Di reason be say for di Keras example, di length of di vector match di vocabulary size wey dem build from di whole AG News dataset, but for di Scikit Learn example, we build di vocabulary from di sample text as e dey happen.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Train di BoW classifier\n",
"\n",
"Now wey we don learn how to build di bag-of-words representation for our text, make we train one classifier wey go use am. First, we need to change our dataset to bag-of-words representation. We fit do dis one by using `map` function like dis:\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": [
"Make we define one simple classifier neural network wey get one linear layer. Di input size na `vocab_size`, and di output size na di number of classes (4). Because we dey solve classification task, di final activation function na **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": [
"Since we get 4 classes, accuracy wey pass 80% na beta result.\n",
"\n",
"## Train classifier as one network\n",
"\n",
"Because say di vectorizer na Keras layer too, we fit define one network wey go include am, and train am end-to-end. Dis way, we no need to dey vectorize di dataset using `map`, we fit just pass di original dataset go di input of di network.\n",
"\n",
"> **Note**: We go still need apply maps to our dataset to change fields from dictionaries (like `title`, `description` and `label`) to tuples. But, when we dey load data from disk, we fit build dataset wey get di correct structure from di beginning.\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": [
"## Bigrams, trigrams and n-grams\n",
"\n",
"One wahala wey dey bag-of-words method be say some words dey join body form multi-word expressions. For example, di word 'hot dog' mean somtin wey different from di words 'hot' and 'dog' for other context. If we dey always use di same vectors represent di words 'hot' and 'dog', e fit confuse di model.\n",
"\n",
"To solve dis mata, **n-gram representations** dey common for document classification methods, where di frequency of each word, bi-word or tri-word dey useful as feature to train classifiers. For bigram representations, for example, we go add all di word pairs join di vocabulary, plus di original words.\n",
"\n",
"See example below of how to generate bigram bag of word representation using 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": [
"Di main wahala wey dey wit di n-gram method be say di vocabulary size go dey grow too fast. For real life, we go need join di n-gram representation wit one dimensionality reduction method, like *embeddings*, wey we go talk about for di next unit.\n",
"\n",
"To use n-gram representation for our **AG News** dataset, we go need pass di `ngrams` parameter go di `TextVectorization` constructor. Di size of bigram vocabulary dey **plenty well well**, for our case e pass 1.3 million tokens! So e make sense to limit di bigram tokens too to one reasonable number.\n",
"\n",
"We fit use di same code wey we don use before to train di classifier, but e no go dey memory-efficient at all. For di next unit, we go train di bigram classifier wit embeddings. For now, you fit try train di bigram classifier for dis notebook and see whether you fit get better accuracy.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## How to calculate BoW Vectors automatic\n",
"\n",
"For di example wey dey up, we calculate BoW vectors by hand by adding di one-hot encodings of di words one by one. But now, di latest version of TensorFlow fit help us calculate BoW vectors automatic if we pass di `output_mode='count` parameter go di vectorizer constructor. Dis one go make am easy for us to define and train our model well well:\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": [
"## Term frequency - inverse document frequency (TF-IDF)\n",
"\n",
"For BoW representation, di way wey word dem dey show, dem dey use di same method take weight dem no matter di word. But e clear say word dem wey dey show well well like *a* and *in* no too get weight for classification like di special terms dem. For most NLP tasks, some words dey more important pass others.\n",
"\n",
"**TF-IDF** mean **term frequency - inverse document frequency**. E be one kind version of bag-of-words, but instead of to use binary 0/1 value wey go show if word dey for document, dem dey use floating-point value wey relate to how many times di word show for di corpus.\n",
"\n",
"To talk am well well, di weight $w_{ij}$ of one word $i$ for di document $j$ na:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"wey\n",
"* $tf_{ij}$ na how many times $i$ show for $j$, na di BoW value wey we don see before\n",
"* $N$ na di number of documents wey dey di collection\n",
"* $df_i$ na di number of documents wey get di word $i$ for di whole collection\n",
"\n",
"Di TF-IDF value $w_{ij}$ go increase as di word dey show plenty times for one document, but e go reduce based on how many documents for di corpus get di word. Dis one dey help balance di fact say some words dey show pass others. For example, if di word dey show for *every* document for di collection, $df_i=N$, and $w_{ij}=0$, dem go just ignore di word.\n",
"\n",
"You fit use Scikit Learn take create TF-IDF vectorization for text:\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": [
"For Keras, di `TextVectorization` layer fit calculate TF-IDF frequencies automatic if you pass di `output_mode='tf-idf'` parameter. Make we repeat di code we use before to see if using TF-IDF go increase accuracy:\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": [
"## Conclusion\n",
"\n",
"Even though TF-IDF dey give weight to different words based on how dem take show, e no fit represent wetin dem mean or di order wey dem take dey. As one popular linguist J. R. Firth talk for 1935, \"Di full meaning of any word dey always depend on di context, and any study of meaning wey no consider context no go make sense.\" Later for dis course, we go learn how we fit capture di context wey dey inside text using language modeling.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n\n<!-- CO-OP TRANSLATOR DISCLAIMER START -->\n**Disclaimer**: \nDis docu don use AI translation service [Co-op Translator](https://github.com/Azure/co-op-translator) take translate am. Even though we dey try make sure say e correct, abeg no forget say automatic translation fit get mistake or no too accurate. Di original docu for di language wey dem first write am na di main correct one. For important information, e better make professional human translator check am. We no go fit take blame for any misunderstanding or wrong interpretation wey fit happen because of dis translation.\n<!-- CO-OP TRANSLATOR DISCLAIMER END -->\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-11-18T19:28:11+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb",
"language_code": "pcm"
}
},
"nbformat": 4,
"nbformat_minor": 4
}