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

577 lines
22 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": [
"# Szövegosztályozási feladat\n",
"\n",
"Ahogy említettük, egy egyszerű szövegosztályozási feladatra fogunk koncentrálni az **AG_NEWS** adathalmazon alapulva, amelynek célja a hírcímek besorolása 4 kategória egyikébe: Világ, Sport, Üzlet és Tudomány/Technológia.\n",
"\n",
"## Az adathalmaz\n",
"\n",
"Ez az adathalmaz be van építve a [`torchtext`](https://github.com/pytorch/text) modulba, így könnyen hozzáférhetünk.\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": [
"Itt a `train_dataset` és a `test_dataset` olyan gyűjteményeket tartalmaznak, amelyek párokat adnak vissza, például osztályszámot (címke) és szöveget.\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": [
"Szóval, nyomtassuk ki az adatállományunk első 10 új címsorát:\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": [
"Mivel az adathalmazok iterátorok, ha többször szeretnénk használni az adatokat, listává kell alakítanunk:\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álás\n",
"\n",
"Most a szöveget **számokká** kell alakítanunk, amelyeket tenzorként lehet ábrázolni. Ha szószintű reprezentációt szeretnénk, két dolgot kell tennünk:\n",
"* használjunk egy **tokenizálót**, hogy a szöveget **tokenekre** bontsuk\n",
"* építsünk egy **szókincset** ezekből a tokenekből.\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": [
"A szókincs használatával könnyedén kódolhatjuk a tokenizált karakterláncot egy számhalmazzá:\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": [
"## Szavak zsákja szövegábrázolás\n",
"\n",
"Mivel a szavak jelentést hordoznak, néha a szöveg jelentését pusztán az egyes szavak alapján is megérthetjük, függetlenül azok mondatbeli sorrendjétől. Például hírek osztályozásakor a *időjárás*, *hó* szavak valószínűleg *időjárás-előrejelzésre* utalnak, míg a *részvények*, *dollár* szavak inkább *pénzügyi hírekhez* kapcsolódnak.\n",
"\n",
"A **Szavak zsákja** (BoW) vektorábrázolás a leggyakrabban használt hagyományos vektorábrázolás. Minden szó egy vektorindexhez van rendelve, a vektor elemei pedig azt mutatják, hogy egy adott dokumentumban hányszor fordul elő az adott szó.\n",
"\n",
"![Kép, amely bemutatja, hogyan van ábrázolva a szavak zsákja vektor a memóriában.](../../../../../translated_images/hu/bag-of-words-example.606fc1738f1d7ba9.webp) \n",
"\n",
"> **Megjegyzés**: A BoW-t úgy is elképzelheted, mint az egyes szavak egy-egy one-hot-kódolt vektorának összegét a szövegben.\n",
"\n",
"Az alábbiakban egy példa látható arra, hogyan lehet szavak zsákja ábrázolást generálni a Scikit Learn python könyvtár segítségével:\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": [
"Az AG_NEWS adathalmaz vektoriális reprezentációjából származó bag-of-words vektor kiszámításához a következő függvényt használhatjuk:\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": [
"> **Megjegyzés:** Itt a globális `vocab_size` változót használjuk a szókincs alapértelmezett méretének megadására. Mivel a szókincs mérete gyakran elég nagy, korlátozhatjuk a szókincs méretét a leggyakoribb szavakra. Próbáld meg csökkenteni a `vocab_size` értékét, és futtasd le az alábbi kódot, hogy lásd, hogyan befolyásolja ez a pontosságot. Néhány pontosságcsökkenésre számíthatsz, de nem drámaira, a nagyobb teljesítmény érdekében.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## BoW osztályozó tanítása\n",
"\n",
"Most, hogy megtanultuk, hogyan készítsünk Bag-of-Words reprezentációt a szövegünkből, tanítsunk egy osztályozót erre az alapra. Először át kell alakítanunk az adatainkat úgy, hogy minden pozíciós vektorreprezentáció Bag-of-Words reprezentációvá legyen konvertálva. Ezt úgy érhetjük el, hogy a `bowify` függvényt `collate_fn` paraméterként adjuk meg a standard torch `DataLoader`-nek:\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": [
"Most definiáljunk egy egyszerű osztályozó neurális hálózatot, amely egy lineáris réteget tartalmaz. Az input vektor mérete megegyezik a `vocab_size` értékével, az output mérete pedig az osztályok számával (4). Mivel osztályozási feladatot oldunk meg, a végső aktivációs függvény a `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": [
"Most már definiáljuk a standard PyTorch tanítási ciklust. Mivel az adatállományunk meglehetősen nagy, oktatási célból csak egy epochon keresztül fogunk tanítani, és néha még ennél is kevesebb ideig (az `epoch_size` paraméter megadásával korlátozhatjuk a tanítást). A tanítás során felhalmozott tanítási pontosságot is jelenteni fogjuk; a jelentés gyakoriságát a `report_freq` paraméterrel lehet megadni.\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": [
"## BiGrammák, TriGrammák és N-Grammák\n",
"\n",
"A szavak zsákja (bag of words) megközelítés egyik korlátja, hogy bizonyos szavak több szóból álló kifejezések részei lehetnek. Például a „hot dog” kifejezés teljesen más jelentéssel bír, mint a „hot” és „dog” szavak külön-külön más szövegkörnyezetben. Ha a „hot” és „dog” szavakat mindig ugyanazokkal a vektorokkal ábrázoljuk, az összezavarhatja a modellünket.\n",
"\n",
"Ennek kezelésére gyakran használnak **N-gram reprezentációkat** dokumentumosztályozási módszerekben, ahol az egyes szavak, kétszavas vagy háromszavas kifejezések gyakorisága hasznos jellemző lehet az osztályozók tanításához. Például a bigram reprezentációban az eredeti szavakon túl az összes szópárt is hozzáadjuk a szókincshez.\n",
"\n",
"Az alábbiakban egy példa látható arra, hogyan lehet bigram szavak zsákja reprezentációt létrehozni a Scikit Learn segítségével:\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": [
"Az N-gram megközelítés fő hátránya, hogy a szókincs mérete rendkívül gyorsan növekedni kezd. Gyakorlatban az N-gram reprezentációt össze kell kombinálnunk valamilyen dimenziócsökkentési technikával, például *beágyazásokkal*, amelyeket a következő egységben fogunk tárgyalni.\n",
"\n",
"Ahhoz, hogy az N-gram reprezentációt használjuk az **AG News** adatállományunkban, speciális ngram szókincset kell létrehoznunk:\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": [
"A fenti kódot használhatnánk a klasszifikátor betanítására, azonban ez nagyon memóriaigényes lenne. A következő egységben bigram klasszifikátort fogunk betanítani beágyazások segítségével.\n",
"\n",
"> **Megjegyzés:** Csak azokat az ngramokat hagyhatod meg, amelyek a szövegben a megadott számnál többször fordulnak elő. Ez biztosítja, hogy a ritkán előforduló bigramok kimaradjanak, és jelentősen csökkenti a dimenziószámot. Ehhez állítsd a `min_freq` paramétert magasabb értékre, és figyeld meg a szókincs hosszának változását.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Term Frekvencia és Invertált Dokumentum Frekvencia (TF-IDF)\n",
"\n",
"A BoW (Bag of Words) reprezentációban a szavak előfordulásait egyenlő súllyal vesszük figyelembe, függetlenül a szó jelentőségétől. Azonban nyilvánvaló, hogy a gyakori szavak, mint például *a*, *az*, *és* stb., sokkal kevésbé fontosak az osztályozás szempontjából, mint a speciális kifejezések. Valójában a legtöbb NLP feladatban bizonyos szavak relevánsabbak, mint mások.\n",
"\n",
"A **TF-IDF** a **term frekvenciainvertált dokumentum frekvencia** rövidítése. Ez a Bag of Words egy változata, ahol a bináris 0/1 érték helyett, amely egy szó megjelenését jelzi egy dokumentumban, egy lebegőpontos értéket használunk, amely a szó előfordulási gyakoriságával van összefüggésben a korpuszban.\n",
"\n",
"Formálisabban, egy szó $i$ súlya egy dokumentumban $j$ az alábbiak szerint van meghatározva:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"ahol\n",
"* $tf_{ij}$ az $i$ szó előfordulásainak száma $j$-ben, vagyis a korábban látott BoW érték\n",
"* $N$ a gyűjteményben található dokumentumok száma\n",
"* $df_i$ azon dokumentumok száma, amelyek tartalmazzák az $i$ szót az egész gyűjteményben\n",
"\n",
"A TF-IDF érték $w_{ij}$ arányosan növekszik azzal, hogy egy szó hányszor fordul elő egy dokumentumban, és csökken azzal, hogy hány dokumentumban található meg a korpuszban. Ez segít korrigálni azt a tényt, hogy bizonyos szavak gyakrabban fordulnak elő, mint mások. Például, ha egy szó *minden* dokumentumban megjelenik a gyűjteményben, akkor $df_i=N$, és $w_{ij}=0$, így ezek a kifejezések teljesen figyelmen kívül maradnak.\n",
"\n",
"A Scikit Learn segítségével könnyedén létrehozhatunk TF-IDF vektorizációt szövegekhez:\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": [
"## Következtetés\n",
"\n",
"Bár a TF-IDF reprezentációk súlyt adnak a különböző szavak gyakoriságának, nem képesek kifejezni a jelentést vagy a sorrendet. Ahogy a híres nyelvész, J. R. Firth 1935-ben mondta: „Egy szó teljes jelentése mindig kontextuális, és a jelentés tanulmányozása a kontextus figyelembevétele nélkül nem vehető komolyan.” A kurzus későbbi részében megtanuljuk, hogyan lehet a szövegből származó kontextuális információt megragadni nyelvi modellezés segítségével.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Felelősség kizárása**: \nEz a dokumentum az AI fordítási szolgáltatás [Co-op Translator](https://github.com/Azure/co-op-translator) segítségével lett lefordítva. Bár törekszünk a pontosságra, kérjük, vegye figyelembe, hogy az automatikus fordítások hibákat vagy pontatlanságokat tartalmazhatnak. Az eredeti dokumentum az eredeti nyelvén tekintendő hiteles forrásnak. Kritikus információk esetén javasolt professzionális emberi fordítást igénybe venni. Nem vállalunk felelősséget semmilyen félreértésért vagy téves értelmezésért, amely a fordítás használatából eredhet.\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-29T16:40:06+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "hu"
}
},
"nbformat": 4,
"nbformat_minor": 2
}