479 lines
31 KiB
Plaintext
479 lines
31 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Рекурентне неуронске мреже\n",
|
||
"\n",
|
||
"У претходном модулу, користили смо богате семантичке репрезентације текста и једноставни линеарни класификатор на врху уграђених вектора. Оно што ова архитектура ради јесте да хвата агрегирано значење речи у реченици, али не узима у обзир **редослед** речи, јер операција агрегирања на врху уграђених вектора уклања ову информацију из оригиналног текста. Пошто ови модели не могу да моделирају редослед речи, они не могу да реше сложеније или двосмислене задатке као што су генерисање текста или одговарање на питања.\n",
|
||
"\n",
|
||
"Да бисмо ухватили значење секвенце текста, потребно је да користимо другу архитектуру неуронске мреже, која се назива **рекурентна неуронска мрежа**, или RNN. У RNN, реченицу пропуштамо кроз мрежу један симбол по један, а мрежа производи неко **стање**, које затим поново прослеђујемо мрежи са следећим симболом.\n",
|
||
"\n",
|
||
"Дат секвенцу токена $X_0,\\dots,X_n$, RNN креира секвенцу блокова неуронске мреже и тренира ову секвенцу од почетка до краја користећи назадно пропагацију. Сваки блок мреже узима пар $(X_i,S_i)$ као улаз и производи $S_{i+1}$ као резултат. Коначно стање $S_n$ или излаз $X_n$ иде у линеарни класификатор да би произвео резултат. Сви блокови мреже деле исте тежине и тренирају се од почетка до краја користећи један пролаз назадне пропагације.\n",
|
||
"\n",
|
||
"Пошто се вектори стања $S_0,\\dots,S_n$ прослеђују кроз мрежу, она је у стању да научи секвенцијалне зависности између речи. На пример, када се реч *не* појави негде у секвенци, мрежа може научити да негира одређене елементе унутар вектора стања, што резултира негацијом.\n",
|
||
"\n",
|
||
"> Пошто су тежине свих RNN блокова на слици заједничке, иста слика може бити представљена као један блок (са десне стране) са рекурентном повратном петљом, која враћа излазно стање мреже назад на улаз.\n",
|
||
"\n",
|
||
"Хајде да видимо како рекурентне неуронске мреже могу да нам помогну да класификујемо наш скуп података о вестима.\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": [
|
||
"## Прост РНН класификатор\n",
|
||
"\n",
|
||
"У случају простог РНН-а, свака рекурентна јединица је једноставна линеарна мрежа која узима конкатенирани вектор улаза и вектор стања, и производи нови вектор стања. PyTorch представља ову јединицу помоћу класе `RNNCell`, а мрежу таквих ћелија као `RNN` слој.\n",
|
||
"\n",
|
||
"Да бисмо дефинисали РНН класификатор, прво ћемо применити слој за уграђивање (embedding layer) како бисмо смањили димензионалност улазног речника, а затим ћемо додати РНН слој изнад њега:\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": [
|
||
"> **Напомена:** Овде користимо необучени слој за уграђивање ради једноставности, али за још боље резултате можемо користити унапред обучени слој за уграђивање са Word2Vec или GloVe уграђивањима, као што је описано у претходној јединици. За боље разумевање, можда ћете желети да прилагодите овај код да ради са унапред обученим уграђивањима.\n",
|
||
"\n",
|
||
"У нашем случају, користићемо пуњач података са попуњавањем (padded data loader), тако да ће сваки пакет имати одређен број секвенци исте дужине. RNN слој ће узети секвенцу тензора уграђивања и произвести два излаза:\n",
|
||
"* $x$ је секвенца излаза RNN ћелија на сваком кораку\n",
|
||
"* $h$ је коначно скривено стање за последњи елемент секвенце\n",
|
||
"\n",
|
||
"Затим примењујемо потпуно повезан линеарни класификатор да бисмо добили број класа.\n",
|
||
"\n",
|
||
"> **Напомена:** RNN-ови су прилично тешки за тренирање, јер када се RNN ћелије развију дуж дужине секвенце, резултујући број слојева укључених у повратну пропагацију је прилично велики. Због тога је потребно изабрати малу стопу учења и тренирати мрежу на већем скупу података како би се добили добри резултати. То може трајати доста времена, па је пожељно користити 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": [
|
||
"## Дугорочно краткорочно памћење (LSTM)\n",
|
||
"\n",
|
||
"Један од главних проблема класичних РНМ (RNN) је такозвани проблем **нестајања градијената**. Пошто се РНМ тренирају од почетка до краја у једном пролазу уназад, тешко им је да пренесу грешку до првих слојева мреже, и због тога мрежа не може да научи односе између удаљених токена. Један од начина да се избегне овај проблем је увођење **експлицитног управљања стањем** коришћењем такозваних **врата**. Постоје две најпознатије архитектуре овог типа: **Дугорочно краткорочно памћење** (LSTM) и **Јединица са контролисаним преносом** (GRU).\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"LSTM мрежа је организована на начин сличан РНМ, али постоје два стања која се преносе из слоја у слој: стварно стање $c$ и скривени вектор $h$. У свакој јединици, скривени вектор $h_i$ се конкатенише са улазом $x_i$, и они контролишу шта се дешава са стањем $c$ преко **врата**. Свака врата су неуронска мрежа са сигмоидном активацијом (излаз у опсегу $[0,1]$), која се може посматрати као битмаска када се помножи са вектором стања. Постоје следећа врата (слева надесно на слици изнад):\n",
|
||
"* **врата за заборав** узимају скривени вектор и одређују које компоненте вектора $c$ треба заборавити, а које пропустити.\n",
|
||
"* **врата за унос** узимају неке информације из улаза и скривеног вектора и убацују их у стање.\n",
|
||
"* **врата за излаз** трансформишу стање преко неког линеарног слоја са $\\tanh$ активацијом, а затим бирају неке од његових компоненти користећи скривени вектор $h_i$ како би произвели ново стање $c_{i+1}$.\n",
|
||
"\n",
|
||
"Компоненте стања $c$ могу се посматрати као неке заставице које се могу укључити и искључити. На пример, када наиђемо на име *Алиса* у низу, можемо претпоставити да се односи на женски лик и подићи заставицу у стању која означава да имамо женску именицу у реченици. Када касније наиђемо на фразу *и Том*, подићи ћемо заставицу која означава да имамо множину именица. Тако, манипулишући стањем, можемо наводно пратити граматичке особине делова реченице.\n",
|
||
"\n",
|
||
"> **Напомена**: Одличан ресурс за разумевање унутрашње структуре LSTM-а је овај сјајан чланак [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) аутора Кристофера Олаха.\n",
|
||
"\n",
|
||
"Иако унутрашња структура LSTM ћелије може изгледати сложено, PyTorch скрива ову имплементацију унутар класе `LSTMCell` и пружа објекат `LSTM` за представљање целог LSTM слоја. Стога ће имплементација LSTM класификатора бити прилично слична једноставном РНМ-у који смо видели изнад:\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": [
|
||
"## Паковане секвенце\n",
|
||
"\n",
|
||
"У нашем примеру, морали смо да попунимо све секвенце у минибатчу нултим векторима. Иако то доводи до одређеног губитка меморије, код РНН-а је још критичније то што се додатне РНН ћелије креирају за попуњене ставке, које учествују у тренингу, али не носе никакве важне информације о улазима. Било би много боље тренирати РНН само до стварне величине секвенце.\n",
|
||
"\n",
|
||
"Да бисмо то постигли, у PyTorch-у је уведен посебан формат за складиштење попуњених секвенци. Претпоставимо да имамо улазни попуњени минибатч који изгледа овако:\n",
|
||
"```\n",
|
||
"[[1,2,3,4,5],\n",
|
||
" [6,7,8,0,0],\n",
|
||
" [9,0,0,0,0]]\n",
|
||
"```\n",
|
||
"Овде 0 представља попуњене вредности, а стварни вектор дужина улазних секвенци је `[5,3,1]`.\n",
|
||
"\n",
|
||
"Да бисмо ефикасно тренирали РНН са попуњеним секвенцама, желимо да започнемо тренинг прве групе РНН ћелија са великим минибатчом (`[1,6,9]`), али затим да завршимо обраду треће секвенце и наставимо тренинг са мањим минибатчевима (`[2,7]`, `[3,8]`), и тако даље. Тако је пакована секвенца представљена као један вектор - у нашем случају `[1,6,9,2,7,3,8,4,5]`, и вектор дужина (`[5,3,1]`), из којих можемо лако реконструисати оригинални попуњени минибатч.\n",
|
||
"\n",
|
||
"Да бисмо произвели паковану секвенцу, можемо користити функцију `torch.nn.utils.rnn.pack_padded_sequence`. Сви рекурентни слојеви, укључујући РНН, LSTM и GRU, подржавају паковане секвенце као улаз и производе паковани излаз, који се може декодирати помоћу `torch.nn.utils.rnn.pad_packed_sequence`.\n",
|
||
"\n",
|
||
"Да бисмо могли да произведемо паковану секвенцу, потребно је да проследимо вектор дужина мрежи, и зато нам је потребна другачија функција за припрему минибатчева:\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": [
|
||
"Стварна мрежа би била веома слична `LSTMClassifier` изнад, али `forward` пролаз ће примати и попуњен мини-бач и вектор дужина секвенци. Након израчунавања уграђивања, израчунавамо упаковану секвенцу, прослеђујемо је LSTM слоју, а затим резултат враћамо у оригинални облик.\n",
|
||
"\n",
|
||
"> **Напомена**: У ствари, не користимо распаковани резултат `x`, јер користимо излаз из скривених слојева у наредним израчунавањима. Стога можемо у потпуности уклонити распакивање из овог кода. Разлог зашто га овде постављамо је да бисте могли лако да измените овај код, у случају да вам затреба излаз мреже у даљим израчунавањима.\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": [
|
||
"> **Напомена:** Можда сте приметили параметар `use_pack_sequence` који прослеђујемо функцији за тренирање. Тренутно, функција `pack_padded_sequence` захтева да низ дужина буде на CPU уређају, и стога функција за тренирање мора избегавати премештање података о дужини низа на GPU током тренирања. Можете погледати имплементацију функције `train_emb` у датотеци [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py).\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Двосмерне и вишеслојне РНН\n",
|
||
"\n",
|
||
"У нашим примерима, све рекурентне мреже су радиле у једном смеру, од почетка секвенце до краја. То изгледа природно, јер подсећа на начин на који читамо и слушамо говор. Међутим, пошто у многим практичним случајевима имамо случајни приступ улазној секвенци, може имати смисла да рекурентна обрада иде у оба смера. Такве мреже се називају **двосмерне** РНН, и могу се креирати прослеђивањем параметра `bidirectional=True` конструктору RNN/LSTM/GRU.\n",
|
||
"\n",
|
||
"Када радимо са двосмерном мрежом, биће нам потребна два вектора скривеног стања, по један за сваки смер. PyTorch кодира те векторе као један вектор двоструко веће величине, што је прилично згодно, јер обично прослеђујете резултујуће скривено стање у потпуно повезан линеарни слој, и само треба да узмете у обзир ово повећање величине приликом креирања слоја.\n",
|
||
"\n",
|
||
"Рекурентна мрежа, једносмерна или двосмерна, хвата одређене обрасце унутар секвенце, и може их складиштити у вектор стања или проследити у излаз. Као и код конволуционих мрежа, можемо изградити још један рекурентни слој на врху првог како бисмо ухватили обрасце вишег нивоа, изграђене од образаца нижег нивоа које је извукао први слој. Ово нас доводи до концепта **вишеслојне РНН**, која се састоји од два или више рекурентних мрежа, где се излаз претходног слоја прослеђује следећем слоју као улаз.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"*Слика из [овог дивног чланка](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) аутора Фернанда Лопеза*\n",
|
||
"\n",
|
||
"PyTorch олакшава конструисање таквих мрежа, јер само треба да проследите параметар `num_layers` конструктору RNN/LSTM/GRU да бисте аутоматски изградили неколико слојева рекуренције. Ово такође значи да ће величина скривеног/стања вектора пропорционално расти, и то треба узети у обзир приликом руковања излазом рекурентних слојева.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## РНН за друге задатке\n",
|
||
"\n",
|
||
"У овој јединици смо видели да се РНН могу користити за класификацију секвенци, али у ствари, могу обрађивати много више задатака, као што су генерисање текста, машински превод и други. Те задатке ћемо размотрити у наредној јединици.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**Одрицање од одговорности**: \nОвај документ је преведен коришћењем услуге за превођење помоћу вештачке интелигенције [Co-op Translator](https://github.com/Azure/co-op-translator). Иако се трудимо да превод буде тачан, молимо вас да имате у виду да аутоматизовани преводи могу садржати грешке или нетачности. Оригинални документ на његовом изворном језику треба сматрати меродавним извором. За критичне информације препоручује се професионални превод од стране људи. Не преузимамо одговорност за било каква погрешна тумачења или неспоразуме који могу настати услед коришћења овог превода.\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-30T01:01:16+00:00",
|
||
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
|
||
"language_code": "sr"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 2
|
||
} |