AI-For-Beginners/translations/fi/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": [
"# Tekstiluokittelutehtävä\n",
"\n",
"Kuten mainitsimme, keskitymme yksinkertaiseen tekstiluokittelutehtävään, joka perustuu **AG_NEWS**-aineistoon. Tehtävänä on luokitella uutisotsikot yhteen neljästä kategoriasta: Maailma, Urheilu, Liiketoiminta ja Tiede/Teknologia.\n",
"\n",
"## Aineisto\n",
"\n",
"Tämä aineisto sisältyy [`torchtext`](https://github.com/pytorch/text) -moduuliin, joten voimme käyttää sitä helposti.\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": [
"Tässä `train_dataset` ja `test_dataset` sisältävät kokoelmia, jotka palauttavat parit, joissa on luokan numero (luokan numero) ja teksti vastaavasti, esimerkiksi:\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": [
"Joten, tulostetaan ensimmäiset 10 uutta otsikkoa tietoaineistostamme:\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": [
"Koska tietojoukot ovat iteraattoreita, jos haluamme käyttää dataa useita kertoja, meidän täytyy muuntaa se listaksi:\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": [
"## Tokenisointi\n",
"\n",
"Nyt meidän täytyy muuntaa teksti **numeroiksi**, jotka voidaan esittää tensoreina. Jos haluamme sanatasoisen esityksen, meidän täytyy tehdä kaksi asiaa:\n",
"* käyttää **tokenisoijaa** jakamaan teksti **tokeneiksi**\n",
"* luoda näiden tokenien **sanasto**.\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": [
"Sanaston avulla voimme helposti koodata tokenisoidun merkkijonon numerosarjaksi:\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 -tekstin esitys\n",
"\n",
"Koska sanat välittävät merkitystä, joskus tekstin merkityksen voi päätellä pelkästään tarkastelemalla yksittäisiä sanoja, riippumatta niiden järjestyksestä lauseessa. Esimerkiksi uutisia luokitellessa sanat kuten *sää*, *lumi* viittaavat todennäköisesti *sääennusteeseen*, kun taas sanat kuten *osakkeet*, *dollari* liittyvät *talousuutisiin*.\n",
"\n",
"**Bag of Words** (BoW) -vektoriesitys on yleisimmin käytetty perinteinen vektoriesitys. Jokainen sana on yhdistetty vektorin indeksiin, ja vektorin elementti sisältää sanan esiintymiskertojen määrän tietyssä dokumentissa.\n",
"\n",
"![Kuva, joka näyttää, miten bag of words -vektoriesitys tallennetaan muistiin.](../../../../../translated_images/bag-of-words-example.606fc1738f1d7ba98a9d693e3bcd706c6e83fa7bf8221e6e90d1a206d82f2ea4.fi.png) \n",
"\n",
"> **Note**: Voit myös ajatella BoW:n olevan summa kaikista yksittäisten sanojen yksi-hot-koodatuista vektoreista tekstissä.\n",
"\n",
"Alla on esimerkki siitä, miten bag of words -esitys voidaan luoda Scikit Learn -python-kirjaston avulla:\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-datasarjan vektoriedustuksesta bag-of-words-vektorin laskemiseksi voimme käyttää seuraavaa funktiota:\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": [
"> **Huom:** Tässä käytämme globaalia `vocab_size`-muuttujaa määrittämään sanaston oletuskoko. Koska sanaston koko on usein melko suuri, voimme rajoittaa sanaston koon yleisimpiin sanoihin. Kokeile pienentää `vocab_size`-arvoa ja suorittaa alla oleva koodi, ja katso, miten se vaikuttaa tarkkuuteen. Voit odottaa jonkin verran tarkkuuden laskua, mutta ei dramaattista, paremman suorituskyvyn sijaan.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## BoW-luokittimen kouluttaminen\n",
"\n",
"Nyt kun olemme oppineet rakentamaan Bag-of-Words-esityksen tekstistämme, koulutetaan sen päälle luokitin. Ensin meidän täytyy muuntaa datamme koulutusta varten siten, että kaikki paikkavektoriesitykset muutetaan Bag-of-Words-esitykseksi. Tämä voidaan tehdä antamalla `bowify`-funktio `collate_fn`-parametrina standardille torch `DataLoader`:ille:\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": [
"Nyt määritellään yksinkertainen luokittelijaneuroverkko, joka sisältää yhden lineaarisen kerroksen. Syötevektorin koko on yhtä suuri kuin `vocab_size`, ja ulostulon koko vastaa luokkien määrää (4). Koska ratkaistavana on luokittelutehtävä, lopullinen aktivointifunktio on `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": [
"Nyt määritämme standardin PyTorch-koulutussilmukan. Koska datamme on melko suuri, opetustarkoituksiamme varten koulutamme vain yhden epookin ajan, ja joskus jopa vähemmän kuin yhden epookin ajan (määrittämällä `epoch_size`-parametrin voimme rajoittaa koulutusta). Raportoimme myös kertyneen koulutustarkkuuden koulutuksen aikana; raportoinnin tiheys määritetään käyttämällä `report_freq`-parametria.\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": [
"## BiGramit, TriGramit ja N-Gramit\n",
"\n",
"Yksi bag of words -lähestymistavan rajoitus on, että jotkut sanat kuuluvat monisanaisiin ilmauksiin. Esimerkiksi sana 'hot dog' tarkoittaa täysin eri asiaa kuin sanat 'hot' ja 'dog' muissa yhteyksissä. Jos edustamme sanoja 'hot' ja 'dog' aina samoilla vektoreilla, se voi hämmentää malliamme.\n",
"\n",
"Tämän ongelman ratkaisemiseksi käytetään usein **N-gram-edustuksia** dokumenttiluokittelumenetelmissä, joissa jokaisen sanan, kaksisanaisen tai kolmisanaisen ilmaisun esiintymistiheys on hyödyllinen ominaisuus luokittimien kouluttamisessa. Esimerkiksi bigram-edustuksessa lisäämme sanastoon kaikki sanaparit alkuperäisten sanojen lisäksi.\n",
"\n",
"Alla on esimerkki siitä, kuinka bigram bag of words -edustus voidaan luoda Scikit Learnin avulla:\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ähestymistavan suurin haittapuoli on, että sanaston koko alkaa kasvaa erittäin nopeasti. Käytännössä meidän täytyy yhdistää N-gram-esitys joihinkin dimensioiden vähentämistekniikoihin, kuten *upotuksiin*, joita käsittelemme seuraavassa osiossa.\n",
"\n",
"Jotta voimme käyttää N-gram-esitystä **AG News** -aineistossamme, meidän täytyy luoda erityinen ngram-sanasto:\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": [
"Voisimme käyttää samaa koodia kuin yllä kouluttaaksemme luokittelijan, mutta se olisi erittäin muistitehotonta. Seuraavassa osiossa koulutamme bigram-luokittelijan käyttämällä upotuksia.\n",
"\n",
"> **Huom:** Voit jättää vain ne ngrammit, jotka esiintyvät tekstissä useammin kuin määritetty määrä kertoja. Tämä varmistaa, että harvoin esiintyvät bigrammit jätetään pois, ja pienentää merkittävästi ulottuvuuksia. Tämän saavuttamiseksi aseta `min_freq`-parametri korkeammaksi ja tarkkaile sanaston pituuden muutosta.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Term Frequency Inverse Document Frequency TF-IDF\n",
"\n",
"BoW-esityksessä sanojen esiintymiset painotetaan tasaisesti riippumatta itse sanasta. On kuitenkin selvää, että yleiset sanat, kuten *a*, *in* jne., ovat paljon vähemmän tärkeitä luokittelun kannalta kuin erikoistuneet termit. Itse asiassa useimmissa NLP-tehtävissä jotkut sanat ovat merkityksellisempiä kuin toiset.\n",
"\n",
"**TF-IDF** tarkoittaa **term frequencyinverse document frequency** (termin esiintymistiheyskäänteinen dokumenttitiheys). Se on muunnelma bag of words -menetelmästä, jossa binäärisen 0/1-arvon sijaan, joka ilmaisee sanan esiintymisen dokumentissa, käytetään liukulukuarvoa, joka liittyy sanan esiintymistiheyteen korpuksessa.\n",
"\n",
"Tarkemmin määriteltynä sanan $i$ paino $w_{ij}$ dokumentissa $j$ määritellään seuraavasti:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"missä\n",
"* $tf_{ij}$ on sanan $i$ esiintymiskertojen määrä dokumentissa $j$, eli BoW-arvo, jonka olemme aiemmin nähneet\n",
"* $N$ on kokoelman dokumenttien lukumäärä\n",
"* $df_i$ on niiden dokumenttien lukumäärä, jotka sisältävät sanan $i$ koko kokoelmassa\n",
"\n",
"TF-IDF-arvo $w_{ij}$ kasvaa suhteessa siihen, kuinka monta kertaa sana esiintyy dokumentissa, ja sitä tasapainottaa niiden dokumenttien määrä korpuksessa, jotka sisältävät kyseisen sanan. Tämä auttaa huomioimaan sen, että jotkut sanat esiintyvät useammin kuin toiset. Esimerkiksi, jos sana esiintyy *jokaisessa* kokoelman dokumentissa, $df_i=N$, ja $w_{ij}=0$, jolloin nämä termit jätetään kokonaan huomiotta.\n",
"\n",
"Voit helposti luoda tekstin TF-IDF-vektorisoinnin käyttämällä Scikit Learnia:\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": [
"## Johtopäätös\n",
"\n",
"Vaikka TF-IDF-esitykset antavat sanoille painotuksia niiden esiintymistiheyden perusteella, ne eivät pysty ilmaisemaan merkitystä tai järjestystä. Kuten kuuluisa kielitieteilijä J. R. Firth totesi vuonna 1935: \"Sanalla on aina täydellinen merkitys vain kontekstissaan, eikä merkityksen tutkimista ilman kontekstia voida ottaa vakavasti.\" Kurssin myöhemmässä vaiheessa opimme, kuinka tekstistä voidaan saada kontekstuaalista tietoa käyttämällä kielimallinnusta.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Vastuuvapauslauseke**: \nTämä asiakirja on käännetty käyttämällä tekoälypohjaista käännöspalvelua [Co-op Translator](https://github.com/Azure/co-op-translator). Vaikka pyrimme tarkkuuteen, huomioithan, että automaattiset käännökset voivat sisältää virheitä tai epätarkkuuksia. Alkuperäistä asiakirjaa sen alkuperäisellä kielellä tulee pitää ensisijaisena lähteenä. Kriittisen tiedon osalta suositellaan ammattimaista ihmiskääntämistä. Emme ole vastuussa väärinkäsityksistä tai virhetulkinnoista, jotka johtuvat tämän käännöksen käytöstä.\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-28T21:58:21+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "fi"
}
},
"nbformat": 4,
"nbformat_minor": 2
}