AI-For-Beginners/translations/pcm/lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.i...

577 lines
20 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Text classification task\n",
"\n",
"As we don tok before, we go focus on simple text classification task wey base on **AG_NEWS** dataset, wey be to classify news headlines into one out of 4 categories: World, Sports, Business and Sci/Tech.\n",
"\n",
"## The Dataset\n",
"\n",
"Dis dataset dey already inside [`torchtext`](https://github.com/pytorch/text) module, so e go dey easy for us to access am.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"import torchtext\n",
"import os\n",
"import collections\n",
"os.makedirs('./data',exist_ok=True)\n",
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here, `train_dataset` and `test_dataset` get collection wey dey return pair of label (number of class) and text, for example:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(3,\n",
" \"Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\\\band of ultra-cynics, are seeing green again.\")"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(train_dataset)[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Make we print di first 10 new headlines wey dey our dataset:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"**Sci/Tech** -> Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\band of ultra-cynics, are seeing green again.\n",
"**Sci/Tech** -> Carlyle Looks Toward Commercial Aerospace (Reuters) Reuters - Private investment firm Carlyle Group,\\which has a reputation for making well-timed and occasionally\\controversial plays in the defense industry, has quietly placed\\its bets on another part of the market.\n",
"**Sci/Tech** -> Oil and Economy Cloud Stocks' Outlook (Reuters) Reuters - Soaring crude prices plus worries\\about the economy and the outlook for earnings are expected to\\hang over the stock market next week during the depth of the\\summer doldrums.\n",
"**Sci/Tech** -> Iraq Halts Oil Exports from Main Southern Pipeline (Reuters) Reuters - Authorities have halted oil export\\flows from the main pipeline in southern Iraq after\\intelligence showed a rebel militia could strike\\infrastructure, an oil official said on Saturday.\n",
"**Sci/Tech** -> Oil prices soar to all-time record, posing new menace to US economy (AFP) AFP - Tearaway world oil prices, toppling records and straining wallets, present a new economic menace barely three months before the US presidential elections.\n"
]
}
],
"source": [
"for i,x in zip(range(5),train_dataset):\n",
" print(f\"**{classes[x[0]]}** -> {x[1]}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Bikos datasets na iterators, if we wan use di data plenty times, we go need convert am to list:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
"train_dataset = list(train_dataset)\n",
"test_dataset = list(test_dataset)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Tokenization\n",
"\n",
"Now we gatz turn text to **numbers** wey fit show as tensors. If we wan represent am for word-level, we gatz do two things:\n",
"* use **tokenizer** to break text into **tokens**\n",
"* build one **vocabulary** of those tokens.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['he', 'said', 'hello']"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n",
"tokenizer('He said: hello')"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" counter.update(tokenizer(line))\n",
"vocab = torchtext.vocab.vocab(counter, min_freq=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Wit vocabulary, we fit easy encode our tokenized string into set of numbers:\n"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocab size if 95810\n"
]
},
{
"data": {
"text/plain": [
"[599, 3279, 97, 1220, 329, 225, 7368]"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vocab_size = len(vocab)\n",
"print(f\"Vocab size if {vocab_size}\")\n",
"\n",
"stoi = vocab.get_stoi() # dict to convert tokens to indices\n",
"\n",
"def encode(x):\n",
" return [stoi[s] for s in tokenizer(x)]\n",
"\n",
"encode('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 mean 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*, *snow* fit show say na *weather forecast*, while words like *stocks*, *dollar* go fit mean *financial news*.\n",
"\n",
"**Bag of Words** (BoW) vector representation na di most common traditional vector representation wey people dey use. Each word dey connect to one vector index, and di vector element dey show how many times one word appear for one document.\n",
"\n",
"![Image wey dey show how bag of words vector representation dey show for memory.](../../../../../translated_images/pcm/bag-of-words-example.606fc1738f1d7ba9.webp) \n",
"\n",
"> **Note**: You fit also think of BoW like sum of all one-hot-encoded vectors for di individual words wey dey inside di text.\n",
"\n",
"Below na example of how to generate bag of word representation using di Scikit Learn python library:\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.feature_extraction.text import CountVectorizer\n",
"vectorizer = CountVectorizer()\n",
"corpus = [\n",
" 'I like hot dogs.',\n",
" 'The dog ran fast.',\n",
" 'Its hot outside.',\n",
" ]\n",
"vectorizer.fit_transform(corpus)\n",
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To calculate bag-of-words vector from di vector wey represent our AG_NEWS dataset, we fit use dis function:\n"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"tensor([2., 1., 2., ..., 0., 0., 0.])\n"
]
}
],
"source": [
"vocab_size = len(vocab)\n",
"\n",
"def to_bow(text,bow_vocab_size=vocab_size):\n",
" res = torch.zeros(bow_vocab_size,dtype=torch.float32)\n",
" for i in encode(text):\n",
" if i<bow_vocab_size:\n",
" res[i] += 1\n",
" return res\n",
"\n",
"print(to_bow(train_dataset[0][1]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Note:** Na global `vocab_size` variable we dey use here to set default size of the vocabulary. Because vocabulary size dey big wella sometimes, we fit limit the size of the vocabulary to the words wey people dey use pass. Try reduce `vocab_size` value and run the code wey dey below, make you see how e go affect the accuracy. You go expect small drop for accuracy, but e no go too bad, instead e go make performance better.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Train BoW classifier\n",
"\n",
"Now we don sabi how to build Bag-of-Words representation for our text, make we train classifier on top am. First, we go need change our dataset for training so dat all positional vector representations go turn to bag-of-words representation. We fit do dis by passing `bowify` function as `collate_fn` parameter to standard torch `DataLoader`:\n"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"from torch.utils.data import DataLoader\n",
"import numpy as np \n",
"\n",
"# this collate function gets list of batch_size tuples, and needs to \n",
"# return a pair of label-feature tensors for the whole minibatch\n",
"def bowify(b):\n",
" return (\n",
" torch.LongTensor([t[0]-1 for t in b]),\n",
" torch.stack([to_bow(t[1]) for t in b])\n",
" )\n",
"\n",
"train_loader = DataLoader(train_dataset, batch_size=16, collate_fn=bowify, shuffle=True)\n",
"test_loader = DataLoader(test_dataset, batch_size=16, collate_fn=bowify, shuffle=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Make we define one simple classifier neural network wey get one linear layer. The size of di input vector na `vocab_size`, and di output size na di number of classes (4). Because we dey solve classification task, di final activation function na `LogSoftmax()`.\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"net = torch.nn.Sequential(torch.nn.Linear(vocab_size,4),torch.nn.LogSoftmax(dim=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we go define standard PyTorch training loop. Because our dataset big well well, for our teaching purpose we go train only for one epoch, and sometimes even for less than one epoch (we fit use `epoch_size` parameter take limit training). We go also report the training accuracy wey we don gather during training; the time wey we go dey report am na `report_freq` parameter go specify am.\n"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"def train_epoch(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.NLLLoss(),epoch_size=None, report_freq=200):\n",
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
" net.train()\n",
" total_loss,acc,count,i = 0,0,0,0\n",
" for labels,features in dataloader:\n",
" optimizer.zero_grad()\n",
" out = net(features)\n",
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
" loss.backward()\n",
" optimizer.step()\n",
" total_loss+=loss\n",
" _,predicted = torch.max(out,1)\n",
" acc+=(predicted==labels).sum()\n",
" count+=len(labels)\n",
" i+=1\n",
" if i%report_freq==0:\n",
" print(f\"{count}: acc={acc.item()/count}\")\n",
" if epoch_size and count>epoch_size:\n",
" break\n",
" return total_loss.item()/count, acc.item()/count"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.8028125\n",
"6400: acc=0.8371875\n",
"9600: acc=0.8534375\n",
"12800: acc=0.85765625\n"
]
},
{
"data": {
"text/plain": [
"(0.026090790722161722, 0.8620069296375267)"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"train_epoch(net,train_loader,epoch_size=15000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## BiGrams, TriGrams and N-Grams\n",
"\n",
"One wahala wey dey bag of words approach be say some words dey join body for multi word expressions. For example, di word 'hot dog' get completely different meaning from di words 'hot' and 'dog' for other context. If we dey represent di words 'hot' and 'dog' always wit di same vectors, e fit confuse our model.\n",
"\n",
"To solve dis mata, **N-gram representations** dey often dey use for document classification methods, where di frequency of each word, bi-word or tri-word go be useful feature to train classifiers. For bigram representation, for example, we go add all di word pairs join di vocabulary, plus di original words.\n",
"\n",
"Below na example of how to generate bigram bag of word representation using Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 26,
"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": 26,
"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 for N-gram method na say di vocabulary size go dey grow too fast. For real life, we go need join N-gram representation wit some techniques wey go reduce di dimension, 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 build special ngram vocabulary:\n"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Bigram vocabulary length = 1308842\n"
]
}
],
"source": [
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" l = tokenizer(line)\n",
" counter.update(torchtext.data.utils.ngrams_iterator(l,ngrams=2))\n",
" \n",
"bi_vocab = torchtext.vocab.vocab(counter, min_freq=1)\n",
"\n",
"print(\"Bigram vocabulary length = \",len(bi_vocab))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We fit use di same code wey dey up train di classifier, but e go chop plenty memory. For di next unit, we go train bigram classifier wey dey use embeddings.\n",
"\n",
"> **Note:** You fit only keep those ngrams wey show for di text pass di number wey you set. Dis one go make sure say di bigrams wey no dey show well go comot, and e go reduce di dimensionality well well. To do am, set `min_freq` parameter to higher value, and check how di length of vocabulary go change.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Term Frequency Inverse Document Frequency TF-IDF\n",
"\n",
"For BoW representation, e no dey matter how important one word be, dem dey give all word same weight. But e clear say some word wey dey show plenty like *a*, *in*, etc. no too dey important for classification like special terms. For most NLP work, some word dey more relevant pass others.\n",
"\n",
"**TF-IDF** mean **term frequencyinverse document frequency**. E be one kind version of bag of words, but instead of binary 0/1 value wey dey show say word dey inside document, dem dey use floating-point value wey relate to how many times word show for corpus.\n",
"\n",
"To explain am well, the weight $w_{ij}$ of one word $i$ for document $j$ na:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"where\n",
"* $tf_{ij}$ na how many times $i$ show for $j$, wey be the BoW value wey we don talk before\n",
"* $N$ na the number of documents wey dey the collection\n",
"* $df_i$ na the number of documents wey get the word $i$ for the whole collection\n",
"\n",
"TF-IDF value $w_{ij}$ go increase as the word dey show plenty for one document, but e go reduce based on how many documents for the corpus get the word. This one dey help balance am because some word dey show plenty pass others. For example, if the word dey show for *every* document for the collection, $df_i=N$, and $w_{ij}=0$, dem go just ignore those kind terms.\n",
"\n",
"You fit use Scikit Learn to create TF-IDF vectorization for text:\n"
]
},
{
"cell_type": "code",
"execution_count": 28,
"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": 28,
"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": [
"## Conclusion\n",
"\n",
"Even though TF-IDF dey give weight to how words dey show for text, e no fit show wetin the words mean or how dem take arrange. As one popular linguist J. R. Firth talk for 1935, “The full meaning of any word na from the context e dey come, and you no fit study meaning wey no get context seriously.” Later for this course, we go learn how to use language modeling take capture context from text.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n\n<!-- CO-OP TRANSLATOR DISCLAIMER START -->\n**Disclaimer**: \nDis dokyument don use AI transleshion service [Co-op Translator](https://github.com/Azure/co-op-translator) do di transleshion. Even as we dey try make am accurate, abeg make you sabi say automatik transleshion fit get mistake or no dey correct well. Di original dokyument for im native language na di one wey you go take as di correct source. For important informashon, e good make you use professional human transleshion. We no go fit take blame for any misunderstanding or wrong meaning wey fit happen because you use dis transleshion.\n<!-- CO-OP TRANSLATOR DISCLAIMER END -->\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "7b9040985e748e4e2d4c689892456ad7",
"translation_date": "2025-11-18T19:29:08+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "pcm"
}
},
"nbformat": 4,
"nbformat_minor": 2
}