AI-For-Beginners/translations/lt/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": [
"# Teksto klasifikavimo užduotis\n",
"\n",
"Kaip jau minėjome, mes sutelksime dėmesį į paprastą teksto klasifikavimo užduotį, pagrįstą **AG_NEWS** duomenų rinkiniu, kurio tikslas yra klasifikuoti naujienų antraštes į vieną iš 4 kategorijų: Pasaulis, Sportas, Verslas ir Mokslas/Technologijos.\n",
"\n",
"## Duomenų rinkinys\n",
"\n",
"Šis duomenų rinkinys yra integruotas į [`torchtext`](https://github.com/pytorch/text) modulį, todėl galime lengvai jį pasiekti.\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": [
"Čia `train_dataset` ir `test_dataset` yra kolekcijos, kurios grąžina poras: etiketę (klasės numerį) ir tekstą atitinkamai, pavyzdžiui:\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": [
"Taigi, atspausdinkime pirmąsias 10 naujų antraščių iš mūsų duomenų rinkinio:\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": [
"Kadangi duomenų rinkiniai yra iteratoriai, jei norime naudoti duomenis kelis kartus, turime juos konvertuoti į sąrašą:\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": [
"## Tokenizacija\n",
"\n",
"Dabar turime paversti tekstą į **skaičius**, kuriuos galima atvaizduoti kaip tensorius. Jei norime žodžių lygmens reprezentacijos, turime atlikti du dalykus:\n",
"* naudoti **tokenizatorių**, kad tekstas būtų padalintas į **tokenus**\n",
"* sukurti tų tokenų **žodyną**.\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": [
"Naudodami žodyną, galime lengvai užkoduoti savo suskaidytą eilutę į skaičių rinkinį:\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": [
"## Žodžių maišo teksto reprezentacija\n",
"\n",
"Kadangi žodžiai perteikia prasmę, kartais teksto prasmę galime suprasti tiesiog pažvelgę į atskirus žodžius, nepaisant jų tvarkos sakinyje. Pavyzdžiui, klasifikuojant naujienas, tokie žodžiai kaip *oras*, *sniegas* greičiausiai nurodys *orų prognozę*, o žodžiai kaip *akcijos*, *doleris* būtų susiję su *finansinėmis naujienomis*.\n",
"\n",
"**Žodžių maišo** (BoW) vektorinė reprezentacija yra dažniausiai naudojama tradicinė vektorinė reprezentacija. Kiekvienas žodis yra susietas su vektoriaus indeksu, o vektoriaus elementas nurodo, kiek kartų žodis pasirodo tam tikrame dokumente.\n",
"\n",
"![Vaizdas, rodantis, kaip žodžių maišo vektorinė reprezentacija saugoma atmintyje.](../../../../../lessons/5-NLP/13-TextRep/images/bag-of-words-example.png) \n",
"\n",
"> **Note**: Taip pat galite galvoti apie BoW kaip apie visų vieno žodžio koduotų vektorių sumą tekste.\n",
"\n",
"Žemiau pateiktas pavyzdys, kaip sukurti žodžių maišo reprezentaciją naudojant Scikit Learn python biblioteką:\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": [
"Norėdami apskaičiuoti žodžių maišo vektorių iš mūsų AG_NEWS duomenų rinkinio vektorinės reprezentacijos, galime naudoti šią funkciją:\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": [
"> **Pastaba:** Čia naudojame globalųjį kintamąjį `vocab_size`, kad nurodytume numatytąjį žodyno dydį. Kadangi dažnai žodyno dydis yra gana didelis, galime apriboti žodyno dydį iki dažniausiai vartojamų žodžių. Pabandykite sumažinti `vocab_size` reikšmę ir paleisti žemiau pateiktą kodą, kad pamatytumėte, kaip tai veikia tikslumą. Turėtumėte tikėtis tam tikro tikslumo sumažėjimo, tačiau ne drastiško, mainais už didesnį našumą.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Mokome BoW klasifikatorių\n",
"\n",
"Dabar, kai išmokome sukurti Bag-of-Words (BoW) reprezentaciją mūsų tekstui, apmokykime klasifikatorių, naudodami šią reprezentaciją. Pirmiausia turime konvertuoti savo duomenų rinkinį mokymui taip, kad visos pozicinės vektorinės reprezentacijos būtų paverstos į Bag-of-Words reprezentaciją. Tai galima padaryti perduodant funkciją `bowify` kaip `collate_fn` parametrą standartiniam 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": [
"Dabar apibrėžkime paprastą klasifikatoriaus neuroninį tinklą, kuris turi vieną linijinį sluoksnį. Įvesties vektoriaus dydis yra lygus `vocab_size`, o išvesties dydis atitinka klasių skaičių (4). Kadangi sprendžiame klasifikavimo užduotį, galutinė aktyvavimo funkcija yra `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": [
"Dabar apibrėšime standartinį PyTorch mokymo ciklą. Kadangi mūsų duomenų rinkinys yra gana didelis, mokymo tikslais treniruosime tik vieną epochą, o kartais net mažiau nei vieną epochą (nustatant `epoch_size` parametrą galima apriboti mokymą). Taip pat pranešime apie sukauptą mokymo tikslumą mokymo metu; pranešimo dažnis nustatomas naudojant `report_freq` parametrą.\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": [
"## BiGramai, TriGramai ir N-Gramai\n",
"\n",
"Viena iš maišo žodžių metodo apribojimų yra ta, kad kai kurie žodžiai sudaro daugiakalbius posakius. Pavyzdžiui, žodis „hot dog“ turi visiškai kitokią reikšmę nei žodžiai „hot“ ir „dog“ kitame kontekste. Jei žodžius „hot“ ir „dog“ visada atvaizduosime tais pačiais vektoriais, tai gali suklaidinti mūsų modelį.\n",
"\n",
"Norint tai išspręsti, dokumentų klasifikavimo metodai dažnai naudoja **N-gramų reprezentacijas**, kur kiekvieno žodžio, dviejų žodžių ar trijų žodžių dažnis yra naudinga savybė mokant klasifikatorius. Pavyzdžiui, bigramų reprezentacijoje mes į žodyną pridėsime visas žodžių poras, be originalių žodžių.\n",
"\n",
"Žemiau pateiktas pavyzdys, kaip sukurti bigramų maišo žodžių reprezentaciją naudojant 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": [
"Pagrindinis N-gram metodo trūkumas yra tas, kad žodyno dydis pradeda augti itin greitai. Praktikoje reikia derinti N-gram reprezentaciją su tam tikromis dimensijų mažinimo technikomis, tokiomis kaip *embedding'ai*, apie kurias kalbėsime kitame skyriuje.\n",
"\n",
"Norint naudoti N-gram reprezentaciją mūsų **AG News** duomenų rinkinyje, reikia sukurti specialų ngram žodyną:\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": [
"Tą patį kodą galėtume naudoti klasifikatoriui mokyti, tačiau tai būtų labai neefektyvu atminties atžvilgiu. Kitame skyriuje mokysime bigramų klasifikatorių naudodami įterpimus.\n",
"\n",
"> **Pastaba:** Galite palikti tik tuos ngramus, kurie tekste pasirodo daugiau nei nurodytą kartų skaičių. Tai užtikrins, kad retai pasitaikantys bigramai bus praleisti, ir žymiai sumažins dimensionalumą. Norėdami tai padaryti, nustatykite `min_freq` parametrą į didesnę reikšmę ir stebėkite, kaip keičiasi žodyno ilgis.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Termų dažnio ir atvirkštinio dokumentų dažnio metodas (TF-IDF)\n",
"\n",
"BoW (maišo žodžių) reprezentacijoje žodžių pasikartojimai yra vertinami vienodai, nepriklausomai nuo paties žodžio. Tačiau akivaizdu, kad dažnai pasitaikantys žodžiai, tokie kaip *a*, *in* ir pan., yra daug mažiau svarbūs klasifikacijai nei specializuoti terminai. Iš tiesų, daugelyje NLP užduočių kai kurie žodžiai yra reikšmingesni nei kiti.\n",
"\n",
"**TF-IDF** reiškia **termų dažnioatvirkštinio dokumentų dažnio metodą**. Tai yra maišo žodžių variacija, kur vietoj dvejetainės 0/1 reikšmės, nurodančios žodžio pasirodymą dokumente, naudojama slankiojo kablelio reikšmė, susijusi su žodžio pasikartojimo dažniu korpuse.\n",
"\n",
"Formaliau, žodžio $i$ svoris dokumente $j$ apibrėžiamas taip:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"kur:\n",
"* $tf_{ij}$ yra žodžio $i$ pasikartojimų skaičius dokumente $j$, t. y. BoW reikšmė, kurią jau aptarėme\n",
"* $N$ yra dokumentų skaičius kolekcijoje\n",
"* $df_i$ yra dokumentų, kuriuose yra žodis $i$, skaičius visoje kolekcijoje\n",
"\n",
"TF-IDF reikšmė $w_{ij}$ didėja proporcingai žodžio pasikartojimų skaičiui dokumente ir yra koreguojama pagal dokumentų skaičių korpuse, kuriuose yra tas žodis. Tai padeda atsižvelgti į tai, kad kai kurie žodžiai pasitaiko dažniau nei kiti. Pavyzdžiui, jei žodis pasirodo *kiekviename* kolekcijos dokumente, $df_i=N$, ir $w_{ij}=0$, todėl tokie terminai būtų visiškai ignoruojami.\n",
"\n",
"TF-IDF tekstų vektorizaciją galite lengvai sukurti naudodami 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": [
"## Išvada\n",
"\n",
"Nors TF-IDF reprezentacijos suteikia skirtingiems žodžiams dažnio svorį, jos nesugeba perteikti prasmės ar tvarkos. Kaip garsus lingvistas J. R. Firth 1935 m. pasakė: „Visapusiška žodžio prasmė visada yra kontekstinė, ir joks prasmės tyrimas, atskirtas nuo konteksto, negali būti laikomas rimtu.“ Vėliau kurse sužinosime, kaip iš teksto išgauti kontekstinę informaciją naudojant kalbos modeliavimą.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\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-31T14:09:52+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "lt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}