AI-For-Beginners/translations/hu/lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.ipynb

414 lines
22 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Generatív hálózatok\n",
"\n",
"A Rekurrens Neurális Hálózatok (RNN-ek) és azok kapuzott cellaváltozatai, mint például a Hosszú Rövid Távú Memóriacellák (LSTM-ek) és a Kapuzott Rekurrens Egységek (GRU-k), lehetőséget nyújtanak a nyelvi modellezésre, azaz képesek megtanulni a szavak sorrendjét, és előrejelzéseket adni a következő szóra egy sorozatban. Ez lehetővé teszi, hogy az RNN-eket **generatív feladatokra** használjuk, például egyszerű szöveggenerálásra, gépi fordításra, sőt akár képfeliratozásra is.\n",
"\n",
"Az előző egységben tárgyalt RNN architektúrában minden RNN egység a következő rejtett állapotot adta ki eredményként. Azonban hozzáadhatunk egy másik kimenetet is minden rekurrens egységhez, amely lehetővé teszi, hogy egy **sorozatot** adjunk ki (amely megegyezik az eredeti sorozat hosszával). Továbbá használhatunk olyan RNN egységeket is, amelyek nem fogadnak bemenetet minden lépésnél, hanem csak egy kezdeti állapotvektort vesznek, és ezután egy kimeneti sorozatot generálnak.\n",
"\n",
"Ebben a jegyzetfüzetben egyszerű generatív modellekre fogunk összpontosítani, amelyek segítenek szöveget generálni. Az egyszerűség kedvéért építsünk egy **karakter-szintű hálózatot**, amely betűről betűre generál szöveget. Az edzés során szükségünk lesz egy szövegkorpuszra, amelyet betűsorozatokra bontunk.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading dataset...\n",
"Building vocab...\n"
]
}
],
"source": [
"import torch\n",
"import torchtext\n",
"import numpy as np\n",
"from torchnlp import *\n",
"train_dataset,test_dataset,classes,vocab = load_dataset()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Karakter szókincs létrehozása\n",
"\n",
"Ahhoz, hogy karakter szintű generatív hálózatot építsünk, a szöveget szavak helyett egyedi karakterekre kell bontani. Ezt egy másik tokenizáló definiálásával érhetjük el:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocabulary size = 82\n",
"Encoding of 'a' is 1\n",
"Character with code 13 is c\n"
]
}
],
"source": [
"def char_tokenizer(words):\n",
" return list(words) #[word for word in words]\n",
"\n",
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" counter.update(char_tokenizer(line))\n",
"vocab = torchtext.vocab.vocab(counter)\n",
"\n",
"vocab_size = len(vocab)\n",
"print(f\"Vocabulary size = {vocab_size}\")\n",
"print(f\"Encoding of 'a' is {vocab.get_stoi()['a']}\")\n",
"print(f\"Character with code 13 is {vocab.get_itos()[13]}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lássuk a példát arra, hogyan kódolhatjuk a szöveget az adatállományunkból:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"tensor([ 0, 1, 2, 2, 3, 4, 5, 6, 3, 7, 8, 1, 9, 10, 3, 11, 2, 1,\n",
" 12, 3, 7, 1, 13, 14, 3, 15, 16, 5, 17, 3, 5, 18, 8, 3, 7, 2,\n",
" 1, 13, 14, 3, 19, 20, 8, 21, 5, 8, 9, 10, 22, 3, 20, 8, 21, 5,\n",
" 8, 9, 10, 3, 23, 3, 4, 18, 17, 9, 5, 23, 10, 8, 2, 2, 8, 9,\n",
" 10, 24, 3, 0, 1, 2, 2, 3, 4, 5, 9, 8, 8, 5, 25, 10, 3, 26,\n",
" 12, 27, 16, 26, 2, 27, 16, 28, 29, 30, 1, 16, 26, 3, 17, 31, 3, 21,\n",
" 2, 5, 9, 1, 23, 13, 32, 16, 27, 13, 10, 24, 3, 1, 9, 8, 3, 10,\n",
" 8, 8, 27, 16, 28, 3, 28, 9, 8, 8, 16, 3, 1, 28, 1, 27, 16, 6])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def enc(x):\n",
" return torch.LongTensor(encode(x,voc=vocab,tokenizer=char_tokenizer))\n",
"\n",
"enc(train_dataset[0][1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Generatív RNN tanítása\n",
"\n",
"Az RNN szöveg generálására való tanításának módja a következő. Minden lépésben veszünk egy `nchars` hosszúságú karakterláncot, és megkérjük a hálózatot, hogy minden bemeneti karakterhez generálja a következő kimeneti karaktert:\n",
"\n",
"![Kép, amely az 'HELLO' szó RNN általi generálását mutatja.](../../../../../translated_images/hu/rnn-generate.56c54afb52f9781d.webp)\n",
"\n",
"A konkrét helyzettől függően előfordulhat, hogy speciális karaktereket is be kell vonnunk, például *sorvége* `<eos>`. A mi esetünkben azonban csak végtelen szöveg generálására szeretnénk tanítani a hálózatot, ezért minden szekvencia méretét fixen `nchars` tokenre állítjuk. Ennek megfelelően minden tanítási példában `nchars` bemenet és `nchars` kimenet lesz (a bemeneti szekvencia egy szimbólummal balra eltolva). Egy minibatch több ilyen szekvenciából fog állni.\n",
"\n",
"A minibatch-ek generálásának módja az, hogy minden `l` hosszúságú hírszövegből előállítjuk az összes lehetséges bemenet-kimenet kombinációt (ezek száma `l-nchars` lesz). Ezek egy minibatch-et alkotnak, és a minibatch-ek mérete minden tanítási lépésnél eltérő lesz.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(tensor([[ 0, 1, 2, ..., 28, 29, 30],\n",
" [ 1, 2, 2, ..., 29, 30, 1],\n",
" [ 2, 2, 3, ..., 30, 1, 16],\n",
" ...,\n",
" [20, 8, 21, ..., 1, 28, 1],\n",
" [ 8, 21, 5, ..., 28, 1, 27],\n",
" [21, 5, 8, ..., 1, 27, 16]]),\n",
" tensor([[ 1, 2, 2, ..., 29, 30, 1],\n",
" [ 2, 2, 3, ..., 30, 1, 16],\n",
" [ 2, 3, 4, ..., 1, 16, 26],\n",
" ...,\n",
" [ 8, 21, 5, ..., 28, 1, 27],\n",
" [21, 5, 8, ..., 1, 27, 16],\n",
" [ 5, 8, 9, ..., 27, 16, 6]]))"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nchars = 100\n",
"\n",
"def get_batch(s,nchars=nchars):\n",
" ins = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
" outs = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
" for i in range(len(s)-nchars):\n",
" ins[i] = enc(s[i:i+nchars])\n",
" outs[i] = enc(s[i+1:i+nchars+1])\n",
" return ins,outs\n",
"\n",
"get_batch(train_dataset[0][1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Most nézzük meg a generátorhálózat meghatározását. Ez bármelyik ismétlődő cellán alapulhat, amelyet az előző egységben tárgyaltunk (egyszerű, LSTM vagy GRU). Példánkban LSTM-et fogunk használni.\n",
"\n",
"Mivel a hálózat karaktereket kap bemenetként, és a szókincs mérete viszonylag kicsi, nincs szükség beágyazási rétegre, az egy-hot-kódolt bemenet közvetlenül az LSTM cellába kerülhet. Azonban, mivel karakterek számát adjuk meg bemenetként, ezeket egy-hot-kódolni kell, mielőtt az LSTM-hez továbbítanánk. Ezt a `one_hot` függvény meghívásával végezzük el a `forward` lépés során. A kimeneti kódoló egy lineáris réteg lesz, amely az elrejtett állapotot egy-hot-kódolt kimenetté alakítja.\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"class LSTMGenerator(torch.nn.Module):\n",
" def __init__(self, vocab_size, hidden_dim):\n",
" super().__init__()\n",
" self.rnn = torch.nn.LSTM(vocab_size,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, vocab_size)\n",
"\n",
" def forward(self, x, s=None):\n",
" x = torch.nn.functional.one_hot(x,vocab_size).to(torch.float32)\n",
" x,s = self.rnn(x,s)\n",
" return self.fc(x),s"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A képzés során szeretnénk képesek lenni generált szövegeket mintázni. Ehhez definiálni fogjuk a `generate` függvényt, amely egy `size` hosszúságú kimeneti sztringet állít elő, az `start` kezdeti sztringből kiindulva.\n",
"\n",
"A működése a következőképpen zajlik. Először az egész kezdő sztringet átadjuk a hálózaton, majd megkapjuk az `s` kimeneti állapotot és a következő előrejelzett karaktert, `out`. Mivel az `out` egy one-hot kódolt érték, az `argmax` segítségével meghatározzuk a karakter indexét, `nc`, a szókészletben, majd az `itos` segítségével kiderítjük a tényleges karaktert, és hozzáadjuk az eredményként kapott karakterek listájához, `chars`. Ezt a karaktergenerálási folyamatot `size` alkalommal megismételjük, hogy előállítsuk a szükséges számú karaktert.\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"def generate(net,size=100,start='today '):\n",
" chars = list(start)\n",
" out, s = net(enc(chars).view(1,-1).to(device))\n",
" for i in range(size):\n",
" nc = torch.argmax(out[0][-1])\n",
" chars.append(vocab.get_itos()[nc])\n",
" out, s = net(nc.view(1,-1),s)\n",
" return ''.join(chars)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Most kezdjük el a tanulást! A tanulási ciklus szinte ugyanaz, mint az összes korábbi példánkban, de az pontosság helyett minden 1000 epoch után mintavételezett generált szöveget nyomtatunk ki.\n",
"\n",
"Különös figyelmet kell fordítani arra, hogyan számítjuk ki a veszteséget. A veszteséget az egy-hot-kódolt kimenet `out` és a várt szöveg `text_out` alapján kell kiszámítani, amely a karakterindexek listája. Szerencsére a `cross_entropy` függvény az első argumentumként a nem normalizált hálózati kimenetet, a második argumentumként pedig az osztályszámot várja, ami pontosan az, amivel rendelkezünk. Emellett automatikusan átlagol a minibatch méretén.\n",
"\n",
"A tanulást a `samples_to_train` minták számával korlátozzuk, hogy ne kelljen túl sokáig várni. Bátorítunk, hogy kísérletezz és próbálj ki hosszabb tanulást, akár több epochon keresztül is (ebben az esetben egy másik ciklust kellene létrehoznod ehhez a kódhoz).\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Current loss = 4.398899078369141\n",
"today sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr s\n",
"Current loss = 2.161320447921753\n",
"today and to the tor to to the tor to to the tor to to the tor to to the tor to to the tor to to the tor t\n",
"Current loss = 1.6722588539123535\n",
"today and the court to the could to the could to the could to the could to the could to the could to the c\n",
"Current loss = 2.423795223236084\n",
"today and a second to the conternation of the conternation of the conternation of the conternation of the \n",
"Current loss = 1.702607274055481\n",
"today and the company to the company to the company to the company to the company to the company to the co\n",
"Current loss = 1.692358136177063\n",
"today and the company to the company to the company to the company to the company to the company to the co\n",
"Current loss = 1.9722288846969604\n",
"today and the control the control the control the control the control the control the control the control \n",
"Current loss = 1.8705692291259766\n",
"today and the second to the second to the second to the second to the second to the second to the second t\n",
"Current loss = 1.7626899480819702\n",
"today and a security and a security and a security and a security and a security and a security and a secu\n",
"Current loss = 1.5574463605880737\n",
"today and the company and the company and the company and the company and the company and the company and \n",
"Current loss = 1.5620026588439941\n",
"today and the be that the be the be that the be the be that the be the be that the be the be that the be t\n"
]
}
],
"source": [
"net = LSTMGenerator(vocab_size,64).to(device)\n",
"\n",
"samples_to_train = 10000\n",
"optimizer = torch.optim.Adam(net.parameters(),0.01)\n",
"loss_fn = torch.nn.CrossEntropyLoss()\n",
"net.train()\n",
"for i,x in enumerate(train_dataset):\n",
" # x[0] is class label, x[1] is text\n",
" if len(x[1])-nchars<10:\n",
" continue\n",
" samples_to_train-=1\n",
" if not samples_to_train: break\n",
" text_in, text_out = get_batch(x[1])\n",
" optimizer.zero_grad()\n",
" out,s = net(text_in)\n",
" loss = torch.nn.functional.cross_entropy(out.view(-1,vocab_size),text_out.flatten()) #cross_entropy(out,labels)\n",
" loss.backward()\n",
" optimizer.step()\n",
" if i%1000==0:\n",
" print(f\"Current loss = {loss.item()}\")\n",
" print(generate(net))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ez a példa már most is elég jó szöveget generál, de több módon is tovább lehet javítani:\n",
"\n",
"* **Jobb minibatch generálás**. Az adatokat úgy készítettük elő a tanításhoz, hogy egy mintából egy minibatch-et generáltunk. Ez nem ideális, mert a minibatch-ek mérete eltérő, és néhány esetben nem is lehet őket létrehozni, mivel a szöveg kisebb, mint `nchars`. Ráadásul a kis minibatch-ek nem terhelik eléggé a GPU-t. Ésszerűbb lenne egy nagy szövegrészt venni az összes mintából, majd létrehozni az összes input-output párt, összekeverni őket, és egyenlő méretű minibatch-eket generálni.\n",
"\n",
"* **Többrétegű LSTM**. Érdemes kipróbálni 2 vagy 3 rétegű LSTM cellákat. Ahogy az előző egységben említettük, az LSTM minden rétege bizonyos mintázatokat von ki a szövegből, és karakter szintű generátor esetén várható, hogy az alacsonyabb LSTM szint a szótagokért felel, míg a magasabb szintek a szavakért és szókapcsolatokért. Ezt egyszerűen megvalósíthatjuk azzal, hogy a rétegek számát paraméterként adjuk át az LSTM konstruktorának.\n",
"\n",
"* Érdemes lehet kísérletezni **GRU egységekkel**, hogy megnézzük, melyik teljesít jobban, valamint **különböző rejtett réteg méretekkel**. Túl nagy rejtett réteg túltanuláshoz vezethet (például a hálózat megtanulja a szöveget szó szerint), míg a kisebb méret nem biztos, hogy jó eredményt hoz.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lágy szöveg generálás és hőmérséklet\n",
"\n",
"Az előző `generate` definícióban mindig a legnagyobb valószínűségű karaktert választottuk ki következő karakterként a generált szövegben. Ennek eredményeként a szöveg gyakran \"ismételte\" ugyanazokat a karakter-szekvenciákat újra és újra, mint ebben a példában:\n",
"```\n",
"today of the second the company and a second the company ...\n",
"```\n",
"\n",
"Azonban, ha megnézzük a következő karakter valószínűségi eloszlását, előfordulhat, hogy a legnagyobb valószínűségek közötti különbség nem túl nagy, például egy karakter valószínűsége lehet 0.2, míg egy másiké 0.19, stb. Például, ha a '*play*' szekvencia következő karakterét keressük, a következő karakter lehet egyaránt szóköz vagy **e** (mint a *player* szóban).\n",
"\n",
"Ez arra a következtetésre vezet minket, hogy nem mindig \"igazságos\" a magasabb valószínűségű karaktert választani, mert a második legnagyobb valószínűségű karakter választása is értelmes szöveghez vezethet. Bölcsebb, ha **mintát veszünk** a hálózat kimenete által adott valószínűségi eloszlásból.\n",
"\n",
"Ez a mintavétel a `multinomial` függvénnyel végezhető el, amely az úgynevezett **multinomiális eloszlást** valósítja meg. Az alábbiakban definiálva van egy függvény, amely ezt a **lágy** szöveg generálást valósítja meg:\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--- Temperature = 0.3\n",
"Today and a company and complete an all the land the restrational the as a security and has provers the pay to and a report and the computer in the stand has filities and working the law the stations for a company and with the company and the final the first company and refight of the state and and workin\n",
"\n",
"--- Temperature = 0.8\n",
"Today he oniis its first to Aus bomblaties the marmation a to manan boogot that pirate assaid a relaid their that goverfin the the Cappets Ecrotional Assonia Cition targets it annight the w scyments Blamity #39;s TVeer Diercheg Reserals fran envyuil that of ster said access what succers of Dour-provelith\n",
"\n",
"--- Temperature = 1.0\n",
"Today holy they a 11 will meda a toket subsuaties, engins for Chanos, they's has stainger past to opening orital his thempting new Nattona was al innerforder advan-than #36;s night year his religuled talitatian what the but with Wednesday to Justment will wemen of Mark CCC Camp as Timed Nae wome a leaders\n",
"\n",
"--- Temperature = 1.3\n",
"Today gpone 2.5 fech atcusion poor cocles toparsdorM.cht Line Pamage put 43 his calt lowed to the book, that has authh-the silia rruch ailing to'ory andhes beutirsimi- Aefffive heading offil an auf eacklets is charged evis, Gunymy oy) Mony has it after-sloythyor loveId out filme, the Natabl -Najuntaxiggs \n",
"\n",
"--- Temperature = 1.8\n",
"Today plary, P.slan chly\\401 mardregationly #39;t 8.1Mide) closes ,filtcon alfly playin roven!\\grea.-QFBEP: Iss onfarchQ/itilia CCf Zivesigntwasta orce.-Peul-aw.uicrin of fuglinfsut aftaningwo, MIEX awayew Aice Woiduar Corvagiugge oppo esig ThusBratourid canthly-RyI.co lagitems\\eexciaishes.conBabntusmor I\n",
"\n"
]
}
],
"source": [
"def generate_soft(net,size=100,start='today ',temperature=1.0):\n",
" chars = list(start)\n",
" out, s = net(enc(chars).view(1,-1).to(device))\n",
" for i in range(size):\n",
" #nc = torch.argmax(out[0][-1])\n",
" out_dist = out[0][-1].div(temperature).exp()\n",
" nc = torch.multinomial(out_dist,1)[0]\n",
" chars.append(vocab.get_itos()[nc])\n",
" out, s = net(nc.view(1,-1),s)\n",
" return ''.join(chars)\n",
" \n",
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
" print(f\"--- Temperature = {i}\\n{generate_soft(net,size=300,start='Today ',temperature=i)}\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Bevezettünk egy újabb paramétert, amelyet **hőmérsékletnek** nevezünk, és amely azt jelzi, mennyire ragaszkodjunk a legmagasabb valószínűséghez. Ha a hőmérséklet 1,0, akkor tisztességes multinomiális mintavételt végzünk, és amikor a hőmérséklet végtelenre nő - minden valószínűség egyenlővé válik, és véletlenszerűen választjuk ki a következő karaktert. Az alábbi példában megfigyelhetjük, hogy a szöveg értelmetlenné válik, amikor túlzottan növeljük a hőmérsékletet, és \"ciklusos\", keményen generált szövegre hasonlít, amikor közelebb kerül a 0-hoz.\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, a [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": "7673cd150d96c74c6d6011460094efb4",
"translation_date": "2025-08-29T15:46:55+00:00",
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.ipynb",
"language_code": "hu"
}
},
"nbformat": 4,
"nbformat_minor": 4
}