AI-For-Beginners/translations/cs/lessons/5-NLP/16-RNN/RNNPyTorch.ipynb

479 lines
24 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": [
"# Rekurentní neuronové sítě\n",
"\n",
"V předchozím modulu jsme používali bohaté sémantické reprezentace textu a jednoduchý lineární klasifikátor nad embeddingy. Tato architektura zachycuje agregovaný význam slov ve větě, ale nezohledňuje **pořadí** slov, protože operace agregace nad embeddingy tuto informaci z původního textu odstranila. Protože tyto modely nedokážou modelovat pořadí slov, nemohou řešit složitější nebo nejednoznačné úkoly, jako je generování textu nebo odpovídání na otázky.\n",
"\n",
"Abychom zachytili význam textové sekvence, musíme použít jinou architekturu neuronové sítě, která se nazývá **rekurentní neuronová síť** (RNN). V RNN procházíme větou sítí po jednom symbolu a síť produkuje nějaký **stav**, který poté předáváme síti znovu spolu s dalším symbolem.\n",
"\n",
"Při zpracování vstupní sekvence tokenů $X_0,\\dots,X_n$ RNN vytváří sekvenci bloků neuronové sítě a trénuje tuto sekvenci end-to-end pomocí zpětné propagace. Každý blok sítě přijímá dvojici $(X_i,S_i)$ jako vstup a produkuje $S_{i+1}$ jako výstup. Konečný stav $S_n$ nebo výstup $X_n$ se předává do lineárního klasifikátoru, který vytvoří výsledek. Všechny bloky sítě sdílejí stejné váhy a jsou trénovány end-to-end během jedné zpětné propagace.\n",
"\n",
"Protože stavové vektory $S_0,\\dots,S_n$ procházejí sítí, je schopna naučit se sekvenční závislosti mezi slovy. Například když se někde v sekvenci objeví slovo *not*, síť se může naučit negovat určité prvky ve stavovém vektoru, což vede k negaci.\n",
"\n",
"> Protože váhy všech bloků RNN na obrázku jsou sdílené, stejný obrázek lze reprezentovat jako jeden blok (vpravo) s rekurentní zpětnou vazbou, která předává výstupní stav sítě zpět na vstup.\n",
"\n",
"Podívejme se, jak nám rekurentní neuronové sítě mohou pomoci klasifikovat naši datovou sadu zpráv.\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",
"from torchnlp import *\n",
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
"vocab_size = len(vocab)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Jednoduchý RNN klasifikátor\n",
"\n",
"V případě jednoduché RNN je každá rekurentní jednotka jednoduchá lineární síť, která přijímá zřetězený vstupní vektor a stavový vektor a vytváří nový stavový vektor. PyTorch reprezentuje tuto jednotku pomocí třídy `RNNCell` a sítě takových buněk jako vrstvu `RNN`.\n",
"\n",
"Pro definici RNN klasifikátoru nejprve použijeme vrstvu embedding ke snížení dimenzionality vstupní slovní zásoby a poté na ni navážeme vrstvu RNN:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"class RNNClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.rnn = torch.nn.RNN(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" x,h = self.rnn(x)\n",
" return self.fc(x.mean(dim=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Note:** Používáme zde netrénovanou vrstvu embedding pro jednoduchost, ale pro ještě lepší výsledky můžeme použít předtrénovanou vrstvu embedding s Word2Vec nebo GloVe embeddingy, jak bylo popsáno v předchozí kapitole. Pro lepší pochopení můžete tento kód upravit tak, aby pracoval s předtrénovanými embeddingy.\n",
"\n",
"V našem případě použijeme datový loader s paddingem, takže každá dávka bude obsahovat několik sekvencí stejné délky s paddingem. Vrstva RNN vezme sekvenci embedding tensorů a vytvoří dva výstupy:\n",
"* $x$ je sekvence výstupů RNN buněk v každém kroku\n",
"* $h$ je konečný skrytý stav pro poslední prvek sekvence\n",
"\n",
"Poté aplikujeme plně propojený lineární klasifikátor, abychom získali počet tříd.\n",
"\n",
"> **Note:** RNN jsou poměrně obtížné na trénování, protože jakmile jsou RNN buňky rozvinuty podél délky sekvence, výsledný počet vrstev zapojených do zpětné propagace je poměrně velký. Proto je potřeba zvolit malou rychlost učení a trénovat síť na větším datasetu, aby se dosáhlo dobrých výsledků. Může to trvat poměrně dlouho, takže je preferováno použití GPU.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.3090625\n",
"6400: acc=0.38921875\n",
"9600: acc=0.4590625\n",
"12800: acc=0.511953125\n",
"16000: acc=0.5506875\n",
"19200: acc=0.57921875\n",
"22400: acc=0.6070089285714285\n",
"25600: acc=0.6304296875\n",
"28800: acc=0.6484027777777778\n",
"32000: acc=0.66509375\n",
"35200: acc=0.6790056818181818\n",
"38400: acc=0.6929166666666666\n",
"41600: acc=0.7035817307692308\n",
"44800: acc=0.7137276785714286\n",
"48000: acc=0.72225\n",
"51200: acc=0.73001953125\n",
"54400: acc=0.7372794117647059\n",
"57600: acc=0.7436631944444444\n",
"60800: acc=0.7503947368421052\n",
"64000: acc=0.75634375\n",
"67200: acc=0.7615773809523809\n",
"70400: acc=0.7662642045454545\n",
"73600: acc=0.7708423913043478\n",
"76800: acc=0.7751822916666666\n",
"80000: acc=0.7790625\n",
"83200: acc=0.7825\n",
"86400: acc=0.7858564814814815\n",
"89600: acc=0.7890513392857142\n",
"92800: acc=0.7920474137931034\n",
"96000: acc=0.7952708333333334\n",
"99200: acc=0.7982258064516129\n",
"102400: acc=0.80099609375\n",
"105600: acc=0.8037594696969697\n",
"108800: acc=0.8060569852941176\n"
]
}
],
"source": [
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)\n",
"net = RNNClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch(net,train_loader, lr=0.001)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Long Short Term Memory (LSTM)\n",
"\n",
"Jedním z hlavních problémů klasických RNN je takzvaný problém **mizících gradientů**. Protože se RNN trénují end-to-end v jednom průchodu zpětnou propagací, mají potíže s propagací chyby do prvních vrstev sítě, a tím pádem se síť nemůže naučit vztahy mezi vzdálenými tokeny. Jedním ze způsobů, jak tento problém obejít, je zavést **explicitní správu stavu** pomocí takzvaných **bran**. Existují dvě nejznámější architektury tohoto typu: **Long Short Term Memory** (LSTM) a **Gated Relay Unit** (GRU).\n",
"\n",
"![Obrázek ukazující příklad buňky Long Short Term Memory](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n",
"\n",
"LSTM síť je organizována podobně jako RNN, ale existují dva stavy, které se předávají z vrstvy do vrstvy: aktuální stav $c$ a skrytý vektor $h$. V každé jednotce se skrytý vektor $h_i$ spojí s vstupem $x_i$, a společně ovládají, co se stane se stavem $c$ prostřednictvím **bran**. Každá brána je neuronová síť se sigmoidovou aktivací (výstup v rozmezí $[0,1]$), kterou lze chápat jako bitovou masku při násobení stavovým vektorem. Na obrázku výše jsou následující brány (zleva doprava):\n",
"* **brána zapomínání** bere skrytý vektor a určuje, které komponenty vektoru $c$ je třeba zapomenout a které propustit dál.\n",
"* **vstupní brána** bere určité informace ze vstupu a skrytého vektoru a vkládá je do stavu.\n",
"* **výstupní brána** transformuje stav pomocí lineární vrstvy s $\\tanh$ aktivací, poté vybere některé jeho komponenty pomocí skrytého vektoru $h_i$, aby vytvořila nový stav $c_{i+1}$.\n",
"\n",
"Komponenty stavu $c$ lze chápat jako určité příznaky, které lze zapnout a vypnout. Například když v sekvenci narazíme na jméno *Alice*, můžeme předpokládat, že se jedná o ženskou postavu, a aktivovat příznak ve stavu, že máme v větě ženské podstatné jméno. Když dále narazíme na frázi *and Tom*, aktivujeme příznak, že máme množné číslo podstatného jména. Manipulací se stavem tak můžeme údajně sledovat gramatické vlastnosti částí věty.\n",
"\n",
"> **Note**: Skvělým zdrojem pro pochopení interní struktury LSTM je tento výborný článek [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) od Christophera Olaha.\n",
"\n",
"Ačkoli vnitřní struktura LSTM buňky může vypadat složitě, PyTorch tuto implementaci skrývá uvnitř třídy `LSTMCell` a poskytuje objekt `LSTM` pro reprezentaci celé vrstvy LSTM. Implementace klasifikátoru LSTM bude tedy velmi podobná jednoduchému RNN, který jsme viděli výše:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"class LSTMClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" x,(h,c) = self.rnn(x)\n",
" return self.fc(h[-1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.259375\n",
"6400: acc=0.25859375\n",
"9600: acc=0.26177083333333334\n",
"12800: acc=0.2784375\n",
"16000: acc=0.313\n",
"19200: acc=0.3528645833333333\n",
"22400: acc=0.3965625\n",
"25600: acc=0.4385546875\n",
"28800: acc=0.4752777777777778\n",
"32000: acc=0.505375\n",
"35200: acc=0.5326704545454546\n",
"38400: acc=0.5557552083333334\n",
"41600: acc=0.5760817307692307\n",
"44800: acc=0.5954910714285714\n",
"48000: acc=0.6118333333333333\n",
"51200: acc=0.62681640625\n",
"54400: acc=0.6404779411764706\n",
"57600: acc=0.6520138888888889\n",
"60800: acc=0.662828947368421\n",
"64000: acc=0.673546875\n",
"67200: acc=0.6831547619047619\n",
"70400: acc=0.6917897727272727\n",
"73600: acc=0.6997146739130434\n",
"76800: acc=0.707109375\n",
"80000: acc=0.714075\n",
"83200: acc=0.7209134615384616\n",
"86400: acc=0.727037037037037\n",
"89600: acc=0.7326674107142858\n",
"92800: acc=0.7379633620689655\n",
"96000: acc=0.7433645833333333\n",
"99200: acc=0.7479032258064516\n",
"102400: acc=0.752119140625\n",
"105600: acc=0.7562405303030303\n",
"108800: acc=0.76015625\n",
"112000: acc=0.7641339285714286\n",
"115200: acc=0.7677777777777778\n",
"118400: acc=0.7711233108108108\n"
]
},
{
"data": {
"text/plain": [
"(0.03487814127604167, 0.7728)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"net = LSTMClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch(net,train_loader, lr=0.001)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sekvence s balením\n",
"\n",
"V našem příkladu jsme museli doplnit všechny sekvence v minibatch nulovými vektory. I když to vede k určitému plýtvání pamětí, u RNN je kritičtější, že se vytvářejí další buňky RNN pro doplněné vstupní položky, které se účastní trénování, ale nenesou žádné důležité vstupní informace. Bylo by mnohem lepší trénovat RNN pouze na skutečnou velikost sekvence.\n",
"\n",
"K tomu je v PyTorch zaveden speciální formát pro ukládání doplněných sekvencí. Předpokládejme, že máme vstupní doplněný minibatch, který vypadá takto:\n",
"```\n",
"[[1,2,3,4,5],\n",
" [6,7,8,0,0],\n",
" [9,0,0,0,0]]\n",
"```\n",
"Zde 0 představuje doplněné hodnoty a skutečný vektor délek vstupních sekvencí je `[5,3,1]`.\n",
"\n",
"Abychom mohli efektivně trénovat RNN s doplněnými sekvencemi, chceme začít trénovat první skupinu buněk RNN s velkým minibatch (`[1,6,9]`), ale poté ukončit zpracování třetí sekvence a pokračovat v trénování s menšími minibatch (`[2,7]`, `[3,8]`) a tak dále. Takto je balená sekvence reprezentována jako jeden vektor v našem případě `[1,6,9,2,7,3,8,4,5]` a vektor délek (`[5,3,1]`), ze kterého můžeme snadno rekonstruovat původní doplněný minibatch.\n",
"\n",
"K vytvoření balené sekvence můžeme použít funkci `torch.nn.utils.rnn.pack_padded_sequence`. Všechny rekurentní vrstvy, včetně RNN, LSTM a GRU, podporují balené sekvence jako vstup a produkují balený výstup, který lze dekódovat pomocí `torch.nn.utils.rnn.pad_packed_sequence`.\n",
"\n",
"Abychom mohli vytvořit balenou sekvenci, musíme předat vektor délek do sítě, a proto potřebujeme jinou funkci pro přípravu minibatch:\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"def pad_length(b):\n",
" # build vectorized sequence\n",
" v = [encode(x[1]) for x in b]\n",
" # compute max length of a sequence in this minibatch and length sequence itself\n",
" len_seq = list(map(len,v))\n",
" l = max(len_seq)\n",
" return ( # tuple of three tensors - labels, padded features, length sequence\n",
" torch.LongTensor([t[0]-1 for t in b]),\n",
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v]),\n",
" torch.tensor(len_seq)\n",
" )\n",
"\n",
"train_loader_len = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=pad_length, shuffle=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Skutečná síť by byla velmi podobná `LSTMClassifier` výše, ale při průchodu `forward` obdrží jak vycpaný minibatch, tak vektor délek sekvencí. Po výpočtu embeddingu vytvoříme zabalenou sekvenci, předáme ji vrstvě LSTM a poté výsledek znovu rozbalíme.\n",
"\n",
"> **Note**: Ve skutečnosti nerozbalený výsledek `x` nepoužíváme, protože v následujících výpočtech využíváme výstup z ukrytých vrstev. Proto můžeme rozbalování z tohoto kódu úplně odstranit. Důvod, proč jej zde uvádíme, je, abyste mohli tento kód snadno upravit, pokud byste potřebovali použít výstup sítě v dalších výpočtech.\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"class LSTMPackClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x, lengths):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" pad_x = torch.nn.utils.rnn.pack_padded_sequence(x,lengths,batch_first=True,enforce_sorted=False)\n",
" pad_x,(h,c) = self.rnn(pad_x)\n",
" x, _ = torch.nn.utils.rnn.pad_packed_sequence(pad_x,batch_first=True)\n",
" return self.fc(h[-1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.285625\n",
"6400: acc=0.33359375\n",
"9600: acc=0.3876041666666667\n",
"12800: acc=0.44078125\n",
"16000: acc=0.4825\n",
"19200: acc=0.5235416666666667\n",
"22400: acc=0.5559821428571429\n",
"25600: acc=0.58609375\n",
"28800: acc=0.6116666666666667\n",
"32000: acc=0.63340625\n",
"35200: acc=0.6525284090909091\n",
"38400: acc=0.668515625\n",
"41600: acc=0.6822596153846154\n",
"44800: acc=0.6948214285714286\n",
"48000: acc=0.7052708333333333\n",
"51200: acc=0.71521484375\n",
"54400: acc=0.7239889705882353\n",
"57600: acc=0.7315277777777778\n",
"60800: acc=0.7388486842105263\n",
"64000: acc=0.74571875\n",
"67200: acc=0.7518303571428572\n",
"70400: acc=0.7576988636363636\n",
"73600: acc=0.7628940217391305\n",
"76800: acc=0.7681510416666667\n",
"80000: acc=0.7728125\n",
"83200: acc=0.7772235576923077\n",
"86400: acc=0.7815393518518519\n",
"89600: acc=0.7857700892857142\n",
"92800: acc=0.7895043103448276\n",
"96000: acc=0.7930520833333333\n",
"99200: acc=0.7959072580645161\n",
"102400: acc=0.798994140625\n",
"105600: acc=0.802064393939394\n",
"108800: acc=0.8051378676470589\n",
"112000: acc=0.8077857142857143\n",
"115200: acc=0.8104600694444445\n",
"118400: acc=0.8128293918918919\n"
]
},
{
"data": {
"text/plain": [
"(0.029785829671223958, 0.8138166666666666)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"net = LSTMPackClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch_emb(net,train_loader_len, lr=0.001,use_pack_sequence=True)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Poznámka:** Možná jste si všimli parametru `use_pack_sequence`, který předáváme trénovací funkci. Funkce `pack_padded_sequence` aktuálně vyžaduje, aby tensor délky sekvence byl na zařízení CPU, a proto trénovací funkce musí zabránit přesunu dat o délce sekvence na GPU během trénování. Můžete se podívat na implementaci funkce `train_emb` v souboru [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py).\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Obousměrné a vícevrstvé RNN\n",
"\n",
"V našich příkladech všechny rekurentní sítě pracovaly jedním směrem, od začátku sekvence až do jejího konce. To se zdá přirozené, protože to připomíná způsob, jakým čteme nebo posloucháme řeč. Nicméně v mnoha praktických případech máme náhodný přístup k vstupní sekvenci, takže může dávat smysl provádět rekurentní výpočty v obou směrech. Takové sítě se nazývají **obousměrné** RNN a lze je vytvořit předáním parametru `bidirectional=True` konstruktoru RNN/LSTM/GRU.\n",
"\n",
"Při práci s obousměrnou sítí budeme potřebovat dva vektory skrytého stavu, jeden pro každý směr. PyTorch tyto vektory kóduje jako jeden vektor dvojnásobné velikosti, což je velmi praktické, protože výsledný skrytý stav obvykle předáváme plně propojené lineární vrstvě, a stačí pouze zohlednit toto zvýšení velikosti při vytváření vrstvy.\n",
"\n",
"Rekurentní síť, ať už jednosměrná nebo obousměrná, zachycuje určité vzory v rámci sekvence a může je uložit do stavu nebo předat do výstupu. Stejně jako u konvolučních sítí můžeme na první vrstvu postavit další rekurentní vrstvu, která zachytí vzory na vyšší úrovni, vytvořené z nízkoúrovňových vzorů extrahovaných první vrstvou. To nás přivádí k pojmu **vícevrstvé RNN**, která se skládá ze dvou nebo více rekurentních sítí, kde výstup předchozí vrstvy je předán jako vstup do následující vrstvy.\n",
"\n",
"![Obrázek zobrazující vícevrstvou dlouhodobou krátkodobou paměťovou RNN](../../../../../translated_images/multi-layer-lstm.dd975e29bb2a59fe58b429db833932d734c81f211cad2783797a9608984acb8c.cs.jpg)\n",
"\n",
"*Obrázek z [tohoto skvělého článku](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) od Fernanda Lópeze*\n",
"\n",
"PyTorch usnadňuje konstrukci takových sítí, protože stačí předat parametr `num_layers` konstruktoru RNN/LSTM/GRU, aby se automaticky vytvořilo několik vrstev rekurence. To také znamená, že velikost skrytého/stavového vektoru se úměrně zvýší, což je třeba zohlednit při práci s výstupem rekurentních vrstev.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNNs pro jiné úkoly\n",
"\n",
"V této jednotce jsme viděli, že RNNs mohou být použity pro klasifikaci sekvencí, ale ve skutečnosti zvládnou mnohem více úkolů, jako je generování textu, strojový překlad a další. Těmito úkoly se budeme zabývat v další jednotce.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Prohlášení**: \nTento dokument byl přeložen pomocí služby pro automatický překlad [Co-op Translator](https://github.com/Azure/co-op-translator). I když se snažíme o přesnost, mějte prosím na paměti, že automatické překlady mohou obsahovat chyby nebo nepřesnosti. Původní dokument v jeho původním jazyce by měl být považován za autoritativní zdroj. Pro důležité informace se doporučuje profesionální lidský překlad. Nenese odpovědnost za žádné nedorozumění nebo nesprávné interpretace vyplývající z použití tohoto překladu.\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": "522ee52ae3d5ae933e283286254e9a55",
"translation_date": "2025-08-29T16:21:20+00:00",
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
"language_code": "cs"
}
},
"nbformat": 4,
"nbformat_minor": 2
}