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

577 lines
21 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": [
"# Úloha klasifikácie textu\n",
"\n",
"Ako sme už spomenuli, zameriame sa na jednoduchú úlohu klasifikácie textu založenú na dátovom súbore **AG_NEWS**, kde je cieľom klasifikovať nadpisy správ do jednej zo 4 kategórií: Svet, Šport, Biznis a Veda/Technológie.\n",
"\n",
"## Dátový súbor\n",
"\n",
"Tento dátový súbor je súčasťou modulu [`torchtext`](https://github.com/pytorch/text), takže k nemu máme jednoduchý prístup.\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": [
"Tu `train_dataset` a `test_dataset` obsahujú kolekcie, ktoré vracajú dvojice štítku (číslo triedy) a textu, napríklad:\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": [
"Takže, poďme vytlačiť prvých 10 nových titulkov z nášho datasetu:\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": [
"Pretože datasety sú iterátory, ak chceme použiť údaje viackrát, musíme ich previesť na zoznam:\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": [
"## Tokenizácia\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",
"* použiť **tokenizér** na rozdelenie textu na **tokeny**\n",
"* vytvoriť **slovník** týchto tokenov.\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": [
"Pomocou slovnej zásoby môžeme ľahko zakódovať tokenizovaný reťazec do množiny čísel:\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": [
"## Reprezentácia textu pomocou 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*, *sneh* naznačovať *predpoveď počasia*, zatiaľ čo slová ako *akcie*, *dolár* by mohli odkazovať na *finančné správy*.\n",
"\n",
"**Bag of Words** (BoW) je najčastejšie používaná tradičná vektorová reprezentácia. 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",
"![Obrázok znázorňujúci, ako je reprezentácia Bag of Words uložená v pamäti.](../../../../../translated_images/sk/bag-of-words-example.606fc1738f1d7ba9.webp) \n",
"\n",
"> **Note**: Na BoW sa môžete pozerať aj ako na súčet všetkých one-hot-enkódovaných vektorov 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": 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": [
"Na výpočet vektora bag-of-words z vektorovej reprezentácie našej AG_NEWS dátovej sady môžeme použiť nasledujúcu funkciu:\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": [
"> **Poznámka:** Tu používame globálnu premennú `vocab_size` na určenie predvolenej veľkosti slovníka. Keďže veľkosť slovníka je často dosť veľká, môžeme obmedziť veľkosť slovníka na najčastejšie slová. Skúste znížiť hodnotu `vocab_size` a spustiť kód nižšie, aby ste videli, ako to ovplyvní presnosť. Mali by ste očakávať určitý pokles presnosti, ale nie dramatický, na úkor vyššieho výkonu.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Trénovanie klasifikátora BoW\n",
"\n",
"Teraz, keď sme sa naučili, ako vytvoriť reprezentáciu Bag-of-Words pre náš text, poďme na nej natrénovať klasifikátor. Najprv musíme upraviť náš dataset na trénovanie tak, aby všetky pozičné vektorové reprezentácie boli konvertované na reprezentáciu bag-of-words. To môžeme dosiahnuť tým, že funkciu `bowify` použijeme ako parameter `collate_fn` v štandardnom 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": [
"Teraz definujme jednoduchú klasifikačnú neurónovú sieť, ktorá obsahuje jednu lineárnu vrstvu. Veľkosť vstupného vektora sa rovná `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 `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": [
"Teraz definujeme štandardný tréningový cyklus v PyTorch. Keďže náš dataset je pomerne veľký, na účely výučby budeme trénovať iba jednu epochu, a niekedy dokonca menej ako jednu epochu (určenie parametra `epoch_size` nám umožňuje obmedziť tréning). Počas tréningu budeme tiež hlásiť akumulovanú tréningovú presnosť; frekvencia hlásenia je určená pomocou parametra `report_freq`.\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": [
"## 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 pre trénovanie klasifikátorov. Napríklad v bigramovej reprezentácii pridáme do slovníka všetky dvojice slov, okrem pôvodných slov.\n",
"\n",
"Nižšie je uvedený príklad, ako vytvoriť bigramovú reprezentáciu \"bag of words\" pomocou 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": [
"Hlavnou nevýhodou prístupu N-gram je, že veľkosť slovníka začína rásť extrémne rýchlo. V praxi je potrebné kombinovať reprezentáciu N-gram s niektorými technikami redukcie dimenzií, ako sú *embeddingy*, o ktorých budeme diskutovať v nasledujúcej jednotke.\n",
"\n",
"Aby sme mohli použiť reprezentáciu N-gram v našej **AG News** dátovej sade, musíme vytvoriť špeciálny ngram slovník:\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": [
"Mohli by sme použiť ten istý 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.\n",
"\n",
"> **Poznámka:** Môžete ponechať iba tie ngramy, ktoré sa v texte vyskytujú viac ako určený počet krát. Tým sa zabezpečí, že zriedkavé bigramy budú vynechané, a výrazne sa zníži dimenzionalita. Na tento účel nastavte parameter `min_freq` na vyššiu hodnotu a sledujte zmenu dĺžky slovníka.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Term Frequency Inverse Document Frequency TF-IDF\n",
"\n",
"V reprezentácii BoW sú výskyty slov rovnomerne vážené, bez ohľadu na samotné slovo. Je však zrejmé, že časté slová, ako napríklad *a*, *v*, atď., sú oveľa menej dôležité pre klasifikáciu než špecializované výrazy. V skutočnosti sú v mnohých úlohách NLP niektoré slová relevantnejšie než iné.\n",
"\n",
"**TF-IDF** znamená **frekvencia termínuinverzná frekvencia dokumentu**. 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á podľa počtu 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 v kolekcii, $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": 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": [
"## Záver\n",
"\n",
"Aj keď TF-IDF reprezentácie poskytujú váhovanie frekvencie rôznym slovám, 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). Aj keď 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 sa odporúča 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": "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-08-30T01:11:13+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "sk"
}
},
"nbformat": 4,
"nbformat_minor": 2
}