AI-For-Beginners/translations/et/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": [
"# Teksti klassifitseerimise ülesanne\n",
"\n",
"Nagu mainitud, keskendume lihtsale teksti klassifitseerimise ülesandele, mis põhineb **AG_NEWS** andmestikul. Ülesandeks on klassifitseerida uudiste pealkirjad ühte neljast kategooriast: Maailm, Sport, Äri ja Teadus/Tehnoloogia.\n",
"\n",
"## Andmestik\n",
"\n",
"See andmestik on integreeritud [`torchtext`](https://github.com/pytorch/text) moodulisse, seega saame sellele hõlpsasti ligi.\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": [
"Siin sisaldavad `train_dataset` ja `test_dataset` kogumikke, mis tagastavad vastavalt paare, mis koosnevad sildist (klassi number) ja tekstist, näiteks:\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": [
"Niisiis, prindime välja meie andmekogumi esimesed 10 uut pealkirja:\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": [
"Kuna andmekogumid on iteraatorid, peame andmete mitmekordseks kasutamiseks need loendiks teisendama:\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": [
"## Tokeniseerimine\n",
"\n",
"Nüüd peame teksti teisendama **numbriteks**, mida saab esitada tensoritena. Kui soovime sõnatasemel esindust, peame tegema kahte asja:\n",
"* kasutama **tokenisaatorit**, et jagada tekst **tokeniteks**\n",
"* koostama nende tokenite **sõnavara**.\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": [
"Kasutades sõnavara, saame hõlpsasti kodeerida oma tokeniseeritud stringi numbrite kogumiks:\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": [
"## Sõnade kott tekstiesitus\n",
"\n",
"Kuna sõnad kannavad tähendust, saame mõnikord teksti tähenduse välja selgitada, vaadates lihtsalt üksikuid sõnu, olenemata nende järjekorrast lauses. Näiteks uudiste klassifitseerimisel viitavad sõnad nagu *ilm*, *lumi* tõenäoliselt *ilmaennustusele*, samas kui sõnad nagu *aktsiad*, *dollar* viitavad *finantsuudistele*.\n",
"\n",
"**Sõnade koti** (BoW) vektoriesitus on kõige sagedamini kasutatav traditsiooniline vektoriesitus. Iga sõna on seotud vektori indeksiga, vektori element sisaldab sõna esinemiste arvu antud dokumendis.\n",
"\n",
"![Pilt, mis näitab, kuidas sõnade koti vektoriesitust mälus kujutatakse.](../../../../../translated_images/et/bag-of-words-example.606fc1738f1d7ba9.webp) \n",
"\n",
"> **Note**: BoW-d võib mõelda ka kui kõigi teksti üksiksõnade ühekuumkodeeritud vektorite summat.\n",
"\n",
"Allpool on näide, kuidas luua sõnade koti esitus, kasutades Scikit Learn Python'i teeki:\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": [
"AG_NEWS andmekogumi vektorkujutise põhjal bag-of-words vektori arvutamiseks saame kasutada järgmist funktsiooni:\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": [
"> **Märkus:** Siin kasutame globaalset `vocab_size` muutujat, et määrata sõnavara vaikimisi suurus. Kuna sõnavara suurus on sageli üsna suur, saame piirata sõnavara suurust kõige sagedasemate sõnadega. Proovi vähendada `vocab_size` väärtust ja käivita allolev kood, et näha, kuidas see mõjutab täpsust. Võid oodata mõningast täpsuse langust, kuid mitte dramaatilist, parema jõudluse nimel.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## BoW klassifikaatori treenimine\n",
"\n",
"Nüüd, kui oleme õppinud, kuidas luua tekstist Bag-of-Words esitus, treenime selle põhjal klassifikaatori. Kõigepealt peame oma treeningandmestiku ümber töötlema nii, et kõik positsioonivektori esitused muudetakse Bag-of-Words esituseks. Seda saab teha, kui edastada `bowify` funktsioon `collate_fn` parameetrina standardsele torch `DataLoader`-ile:\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": [
"Nüüd defineerime lihtsa klassifitseeriva närvivõrgu, mis sisaldab ühte lineaarset kihti. Sisendvektori suurus on võrdne `vocab_size`-ga ja väljundi suurus vastab klasside arvule (4). Kuna lahendame klassifitseerimisülesannet, on lõplikuks aktivatsioonifunktsiooniks `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": [
"Nüüd defineerime standardse PyTorchi treeningtsükli. Kuna meie andmekogum on üsna suur, treenime õpetamise eesmärgil ainult ühe epohhi jooksul ja mõnikord isegi vähem kui ühe epohhi jooksul (parameetri `epoch_size` määramine võimaldab meil treeningut piirata). Samuti raporteerime treeningu ajal kogunenud treeningtäpsust; raporteerimise sagedus määratakse parameetriga `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": [
"## BiGrammid, TriGrammid ja N-Grammid\n",
"\n",
"Üks sõnakoti lähenemise piirang on see, et mõned sõnad kuuluvad mitmesõnalistesse väljenditesse. Näiteks sõnal 'hot dog' on täiesti erinev tähendus võrreldes sõnadega 'hot' ja 'dog' teistes kontekstides. Kui me esindame sõnu 'hot' ja 'dog' alati samade vektoritega, võib see meie mudelit segadusse ajada.\n",
"\n",
"Selle probleemi lahendamiseks kasutatakse sageli **N-grammi esindusi** dokumentide klassifitseerimise meetodites, kus iga sõna, kahe- või kolmesõnalise kombinatsiooni sagedus on kasulik tunnus klassifikaatorite treenimiseks. Näiteks bigrammi esinduses lisame sõnavarasse kõik sõnapaarid, lisaks algsetele sõnadele.\n",
"\n",
"Allpool on näide, kuidas luua bigrammi sõnakoti esindust, kasutades Scikit Learn'i:\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": [
"N-gram lähenemise peamine puudus on see, et sõnavara suurus hakkab väga kiiresti kasvama. Praktikas peame N-grami esitusviisi kombineerima mõne dimensioonide vähendamise tehnikaga, näiteks *embeddings*, mida arutame järgmises osas.\n",
"\n",
"Et kasutada N-grami esitusviisi meie **AG News** andmestikus, peame looma spetsiaalse ngrami sõnavara:\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": [
"Me võiksime kasutada sama koodi nagu ülalpool klassifikaatori treenimiseks, kuid see oleks väga mälumahukas. Järgmises osas treenime bigrammide klassifikaatorit, kasutades sisendvektoreid.\n",
"\n",
"> **Märkus:** Jätta saab ainult need ngrammid, mis esinevad tekstis rohkem kui määratud arv kordi. See tagab, et harvad bigrammid jäetakse välja ja vähendab oluliselt dimensioonide arvu. Selleks seadke `min_freq` parameeter kõrgemale väärtusele ja jälgige sõnavara pikkuse muutust.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Term Frequency Inverse Document Frequency TF-IDF\n",
"\n",
"BoW-esituses on sõnade esinemised võrdselt kaalutud, sõltumata sõnast endast. Siiski on selge, et sagedased sõnad, nagu *a*, *in* jne, on klassifitseerimise jaoks palju vähem olulised kui spetsialiseeritud terminid. Tegelikult on enamikus NLP ülesannetes mõned sõnad olulisemad kui teised.\n",
"\n",
"**TF-IDF** tähistab **term frequencyinverse document frequency** (termini sagedusdokumendi pöördsagedus). See on bag of words'i variatsioon, kus binaarse 0/1 väärtuse asemel, mis näitab sõna esinemist dokumendis, kasutatakse ujuvpunkti väärtust, mis on seotud sõna esinemissagedusega korpuses.\n",
"\n",
"Formaalsemalt on sõna $i$ kaal $w_{ij}$ dokumendis $j$ defineeritud järgmiselt:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"kus\n",
"* $tf_{ij}$ on sõna $i$ esinemiste arv dokumendis $j$, st BoW väärtus, mida oleme varem näinud\n",
"* $N$ on dokumentide arv kogumis\n",
"* $df_i$ on dokumentide arv, mis sisaldavad sõna $i$ kogu kogumis\n",
"\n",
"TF-IDF väärtus $w_{ij}$ suureneb proportsionaalselt sõna esinemiste arvuga dokumendis ja on tasakaalustatud korpuses olevate dokumentide arvuga, mis sisaldavad seda sõna. See aitab korrigeerida fakti, et mõned sõnad esinevad sagedamini kui teised. Näiteks, kui sõna esineb *iga* dokumendis kogumis, siis $df_i=N$ ja $w_{ij}=0$, ning need terminid jäetakse täielikult kõrvale.\n",
"\n",
"TF-IDF vektoriseerimist saab hõlpsasti luua Scikit Learn'i abil:\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": [
"## Kokkuvõte\n",
"\n",
"Kuigi TF-IDF esitlused annavad erinevatele sõnadele sageduskaalu, ei suuda need esitada tähendust ega järjekorda. Nagu kuulus lingvist J. R. Firth ütles 1935. aastal: „Sõna täielik tähendus on alati kontekstuaalne ja ühtegi tähenduse uurimist väljaspool konteksti ei saa tõsiselt võtta.” Kursuse käigus õpime hiljem, kuidas tekstist kontekstuaalset teavet keelemudelite abil tabada.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Lahtiütlus**: \nSee dokument on tõlgitud AI tõlketeenuse [Co-op Translator](https://github.com/Azure/co-op-translator) abil. Kuigi püüame tagada täpsust, palume arvestada, et automaatsed tõlked võivad sisaldada vigu või ebatäpsusi. Algne dokument selle algses keeles tuleks pidada autoriteetseks allikaks. Olulise teabe puhul soovitame kasutada professionaalset inimtõlget. Me ei vastuta selle tõlke kasutamisest tulenevate arusaamatuste või valesti tõlgenduste eest.\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-10-11T12:46:59+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "et"
}
},
"nbformat": 4,
"nbformat_minor": 2
}