460 lines
24 KiB
Plaintext
460 lines
24 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Rekurencyjne sieci neuronowe\n",
|
|
"\n",
|
|
"W poprzednim module omówiliśmy bogate semantyczne reprezentacje tekstu. Architektura, której używaliśmy, wychwytuje zagregowane znaczenie słów w zdaniu, ale nie uwzględnia **kolejności** tych słów, ponieważ operacja agregacji, która następuje po osadzeniach, usuwa tę informację z oryginalnego tekstu. Ponieważ te modele nie są w stanie reprezentować kolejności słów, nie mogą rozwiązywać bardziej złożonych lub niejednoznacznych zadań, takich jak generowanie tekstu czy odpowiadanie na pytania.\n",
|
|
"\n",
|
|
"Aby uchwycić znaczenie sekwencji tekstu, użyjemy architektury sieci neuronowej zwanej **rekurencyjną siecią neuronową** (ang. recurrent neural network, RNN). Korzystając z RNN, przekazujemy nasze zdanie przez sieć jeden token na raz, a sieć generuje pewien **stan**, który następnie przekazujemy ponownie do sieci wraz z kolejnym tokenem.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Dla danej sekwencji wejściowej tokenów $X_0,\\dots,X_n$, RNN tworzy sekwencję bloków sieci neuronowej i trenuje tę sekwencję end-to-end za pomocą propagacji wstecznej. Każdy blok sieci przyjmuje parę $(X_i,S_i)$ jako wejście i generuje $S_{i+1}$ jako wynik. Końcowy stan $S_n$ lub wyjście $Y_n$ trafia do klasyfikatora liniowego, aby wygenerować wynik. Wszystkie bloki sieci mają te same wagi i są trenowane end-to-end w jednym przebiegu propagacji wstecznej.\n",
|
|
"\n",
|
|
"> Powyższy rysunek przedstawia rekurencyjną sieć neuronową w rozwiniętej formie (po lewej) oraz w bardziej zwartej, rekurencyjnej reprezentacji (po prawej). Ważne jest, aby zrozumieć, że wszystkie komórki RNN mają te same **współdzielone wagi**.\n",
|
|
"\n",
|
|
"Ponieważ wektory stanów $S_0,\\dots,S_n$ są przekazywane przez sieć, RNN jest w stanie uczyć się zależności sekwencyjnych między słowami. Na przykład, gdy słowo *nie* pojawia się w pewnym miejscu w sekwencji, sieć może nauczyć się negować pewne elementy wewnątrz wektora stanu.\n",
|
|
"\n",
|
|
"Wewnątrz każdej komórki RNN znajdują się dwie macierze wag: $W_H$ i $W_I$, oraz bias $b$. Na każdym kroku RNN, dla danego wejścia $X_i$ i stanu wejściowego $S_i$, stan wyjściowy jest obliczany jako $S_{i+1} = f(W_H\\times S_i + W_I\\times X_i+b)$, gdzie $f$ to funkcja aktywacji (często $\\tanh$).\n",
|
|
"\n",
|
|
"> W przypadku problemów takich jak generowanie tekstu (które omówimy w następnym rozdziale) lub tłumaczenie maszynowe, chcemy również uzyskać pewną wartość wyjściową na każdym kroku RNN. W takim przypadku istnieje dodatkowa macierz $W_O$, a wyjście jest obliczane jako $Y_i=f(W_O\\times S_i+b_O)$.\n",
|
|
"\n",
|
|
"Zobaczmy, jak rekurencyjne sieci neuronowe mogą pomóc nam w klasyfikacji naszego zbioru danych z wiadomościami.\n",
|
|
"\n",
|
|
"> W środowisku sandbox musimy uruchomić poniższą komórkę, aby upewnić się, że wymagana biblioteka jest zainstalowana, a dane są wstępnie pobrane. Jeśli pracujesz lokalnie, możesz pominąć poniższą komórkę.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import sys\n",
|
|
"!{sys.executable} -m pip install --quiet tensorflow_datasets==4.4.0\n",
|
|
"!cd ~ && wget -q -O - https://mslearntensorflowlp.blob.core.windows.net/data/tfds-ag-news.tgz | tar xz"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import tensorflow as tf\n",
|
|
"from tensorflow import keras\n",
|
|
"import tensorflow_datasets as tfds\n",
|
|
"import numpy as np\n",
|
|
"\n",
|
|
"# We are going to be training pretty large models. In order not to face errors, we need\n",
|
|
"# to set tensorflow option to grow GPU memory allocation when required\n",
|
|
"physical_devices = tf.config.list_physical_devices('GPU') \n",
|
|
"if len(physical_devices)>0:\n",
|
|
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
|
|
"\n",
|
|
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"nteract": {
|
|
"transient": {
|
|
"deleting": false
|
|
}
|
|
}
|
|
},
|
|
"source": [
|
|
"Podczas trenowania dużych modeli alokacja pamięci GPU może stanowić problem. Może być również konieczne eksperymentowanie z różnymi rozmiarami minibatchy, aby dane mieściły się w pamięci GPU, a jednocześnie trening był wystarczająco szybki. Jeśli uruchamiasz ten kod na własnej maszynie z GPU, możesz eksperymentować z dostosowaniem rozmiaru minibatchy, aby przyspieszyć trening.\n",
|
|
"\n",
|
|
"> **Note**: Wiadomo, że niektóre wersje sterowników NVidia nie zwalniają pamięci po zakończeniu trenowania modelu. W tym notatniku uruchamiamy kilka przykładów, co może prowadzić do wyczerpania pamięci w niektórych konfiguracjach, zwłaszcza jeśli przeprowadzasz własne eksperymenty w ramach tego samego notatnika. Jeśli napotkasz dziwne błędy podczas rozpoczynania trenowania modelu, warto rozważyć ponowne uruchomienie jądra notatnika.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {
|
|
"collapsed": true,
|
|
"jupyter": {
|
|
"outputs_hidden": false,
|
|
"source_hidden": false
|
|
},
|
|
"nteract": {
|
|
"transient": {
|
|
"deleting": false
|
|
}
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"batch_size = 16\n",
|
|
"embed_size = 64"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Prosty klasyfikator RNN\n",
|
|
"\n",
|
|
"W przypadku prostego RNN każda jednostka rekurencyjna jest prostą siecią liniową, która przyjmuje wektor wejściowy i wektor stanu, a następnie generuje nowy wektor stanu. W Keras można to przedstawić za pomocą warstwy `SimpleRNN`.\n",
|
|
"\n",
|
|
"Chociaż możemy przekazywać zakodowane w formacie one-hot tokeny bezpośrednio do warstwy RNN, nie jest to dobry pomysł ze względu na ich wysoką wymiarowość. Dlatego użyjemy warstwy osadzania (embedding), aby zmniejszyć wymiarowość wektorów słów, następnie warstwy RNN, a na końcu klasyfikatora `Dense`.\n",
|
|
"\n",
|
|
"> **Note**: W przypadkach, gdy wymiarowość nie jest tak wysoka, na przykład przy tokenizacji na poziomie znaków, może mieć sens bezpośrednie przekazywanie tokenów zakodowanych w formacie one-hot do komórki RNN.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Model: \"sequential\"\n",
|
|
"_________________________________________________________________\n",
|
|
"Layer (type) Output Shape Param # \n",
|
|
"=================================================================\n",
|
|
"text_vectorization (TextVect (None, None) 0 \n",
|
|
"_________________________________________________________________\n",
|
|
"embedding (Embedding) (None, None, 64) 1280000 \n",
|
|
"_________________________________________________________________\n",
|
|
"simple_rnn (SimpleRNN) (None, 16) 1296 \n",
|
|
"_________________________________________________________________\n",
|
|
"dense (Dense) (None, 4) 68 \n",
|
|
"=================================================================\n",
|
|
"Total params: 1,281,364\n",
|
|
"Trainable params: 1,281,364\n",
|
|
"Non-trainable params: 0\n",
|
|
"_________________________________________________________________\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"vocab_size = 20000\n",
|
|
"\n",
|
|
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(\n",
|
|
" max_tokens=vocab_size,\n",
|
|
" input_shape=(1,))\n",
|
|
"\n",
|
|
"model = keras.models.Sequential([\n",
|
|
" vectorizer,\n",
|
|
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
|
" keras.layers.SimpleRNN(16),\n",
|
|
" keras.layers.Dense(4,activation='softmax')\n",
|
|
"])\n",
|
|
"\n",
|
|
"model.summary()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Uwaga:** Używamy tutaj nieprzeszkolonej warstwy osadzania dla uproszczenia, ale dla lepszych wyników możemy użyć wstępnie przeszkolonej warstwy osadzania za pomocą Word2Vec, jak opisano w poprzedniej jednostce. Dobrym ćwiczeniem byłoby dostosowanie tego kodu do pracy z wstępnie przeszkolonymi osadzeniami.\n",
|
|
"\n",
|
|
"Teraz przejdźmy do trenowania naszego RNN. Ogólnie rzecz biorąc, RNN są dość trudne do trenowania, ponieważ po rozwinięciu komórek RNN wzdłuż długości sekwencji liczba warstw zaangażowanych w propagację wsteczną staje się bardzo duża. Dlatego musimy wybrać mniejszą szybkość uczenia się i trenować sieć na większym zbiorze danych, aby uzyskać dobre wyniki. Może to zająć sporo czasu, więc preferowane jest użycie GPU.\n",
|
|
"\n",
|
|
"Aby przyspieszyć proces, będziemy trenować model RNN tylko na tytułach wiadomości, pomijając opis. Możesz spróbować trenować z opisem i sprawdzić, czy uda Ci się doprowadzić model do trenowania.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Training vectorizer\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"def extract_title(x):\n",
|
|
" return x['title']\n",
|
|
"\n",
|
|
"def tupelize_title(x):\n",
|
|
" return (extract_title(x),x['label'])\n",
|
|
"\n",
|
|
"print('Training vectorizer')\n",
|
|
"vectorizer.adapt(ds_train.take(2000).map(extract_title))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"7500/7500 [==============================] - 82s 11ms/step - loss: 0.6629 - acc: 0.7623 - val_loss: 0.5559 - val_acc: 0.7995\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<tensorflow.python.keras.callbacks.History at 0x7f3e0030d350>"
|
|
]
|
|
},
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
|
"model.fit(ds_train.map(tupelize_title).batch(batch_size),validation_data=ds_test.map(tupelize_title).batch(batch_size))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"nteract": {
|
|
"transient": {
|
|
"deleting": false
|
|
}
|
|
}
|
|
},
|
|
"source": [
|
|
"> **Uwaga** że dokładność może być tutaj niższa, ponieważ trenujemy tylko na tytułach wiadomości.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Powrót do sekwencji zmiennych\n",
|
|
"\n",
|
|
"Pamiętaj, że warstwa `TextVectorization` automatycznie wypełnia sekwencje o zmiennej długości w minibatchu za pomocą tokenów wypełniających (pad tokens). Okazuje się, że te tokeny również biorą udział w treningu, co może utrudniać zbieżność modelu.\n",
|
|
"\n",
|
|
"Istnieje kilka podejść, które możemy zastosować, aby zminimalizować ilość wypełnień. Jednym z nich jest uporządkowanie zbioru danych według długości sekwencji i pogrupowanie wszystkich sekwencji według rozmiaru. Można to zrobić za pomocą funkcji `tf.data.experimental.bucket_by_sequence_length` (zobacz [dokumentację](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length)).\n",
|
|
"\n",
|
|
"Innym podejściem jest użycie **maskowania**. W Keras niektóre warstwy obsługują dodatkowe dane wejściowe, które wskazują, które tokeny powinny być brane pod uwagę podczas treningu. Aby włączyć maskowanie do naszego modelu, możemy albo dodać osobną warstwę `Masking` ([dokumentacja](https://keras.io/api/layers/core_layers/masking/)), albo określić parametr `mask_zero=True` w naszej warstwie `Embedding`.\n",
|
|
"\n",
|
|
"> **Note**: Ten trening zajmie około 5 minut na ukończenie jednej epoki na całym zbiorze danych. Możesz przerwać trening w dowolnym momencie, jeśli zabraknie Ci cierpliwości. Możesz również ograniczyć ilość danych używanych do treningu, dodając klauzulę `.take(...)` po zbiorach danych `ds_train` i `ds_test`.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"7500/7500 [==============================] - 371s 49ms/step - loss: 0.5401 - acc: 0.8079 - val_loss: 0.3780 - val_acc: 0.8822\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<tensorflow.python.keras.callbacks.History at 0x7f3dec118850>"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"def extract_text(x):\n",
|
|
" return x['title']+' '+x['description']\n",
|
|
"\n",
|
|
"def tupelize(x):\n",
|
|
" return (extract_text(x),x['label'])\n",
|
|
"\n",
|
|
"model = keras.models.Sequential([\n",
|
|
" vectorizer,\n",
|
|
" keras.layers.Embedding(vocab_size,embed_size,mask_zero=True),\n",
|
|
" keras.layers.SimpleRNN(16),\n",
|
|
" keras.layers.Dense(4,activation='softmax')\n",
|
|
"])\n",
|
|
"\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Teraz, gdy używamy maskowania, możemy trenować model na całym zbiorze danych zawierającym tytuły i opisy.\n",
|
|
"\n",
|
|
"> **Note**: Czy zauważyłeś, że używaliśmy wektoryzatora wytrenowanego na tytułach wiadomości, a nie na całym tekście artykułu? Potencjalnie może to spowodować pominięcie niektórych tokenów, dlatego lepiej byłoby ponownie wytrenować wektoryzator. Jednak wpływ tego może być bardzo niewielki, więc dla uproszczenia pozostaniemy przy wcześniej wytrenowanym wektoryzatorze.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## LSTM: Długoterminowa pamięć krótkotrwała\n",
|
|
"\n",
|
|
"Jednym z głównych problemów RNN jest **zanikanie gradientów**. RNN mogą być dość długie i mogą mieć trudności z propagowaniem gradientów aż do pierwszej warstwy sieci podczas procesu wstecznej propagacji. Gdy tak się dzieje, sieć nie jest w stanie nauczyć się relacji między odległymi tokenami. Jednym ze sposobów uniknięcia tego problemu jest wprowadzenie **jawnego zarządzania stanem** za pomocą **bramek**. Dwie najczęściej stosowane architektury, które wprowadzają bramki, to **długoterminowa pamięć krótkotrwała** (LSTM) i **jednostka przekaźnikowa z bramkami** (GRU). Tutaj omówimy LSTM.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Sieć LSTM jest zorganizowana w sposób podobny do RNN, ale istnieją dwa stany, które są przekazywane z warstwy do warstwy: rzeczywisty stan $c$ oraz ukryty wektor $h$. W każdej jednostce ukryty wektor $h_{t-1}$ jest łączony z wejściem $x_t$, a razem kontrolują, co dzieje się ze stanem $c_t$ i wyjściem $h_{t}$ za pomocą **bramek**. Każda bramka ma aktywację sigmoidalną (wyjście w zakresie $[0,1]$), którą można traktować jako maskę bitową, gdy jest mnożona przez wektor stanu. LSTM posiada następujące bramki (od lewej do prawej na powyższym obrazku):\n",
|
|
"* **bramka zapominania**, która określa, które elementy wektora $c_{t-1}$ należy zapomnieć, a które przekazać dalej.\n",
|
|
"* **bramka wejściowa**, która określa, ile informacji z wektora wejściowego i poprzedniego ukrytego wektora powinno zostać włączone do wektora stanu.\n",
|
|
"* **bramka wyjściowa**, która bierze nowy wektor stanu i decyduje, które z jego elementów zostaną użyte do wygenerowania nowego ukrytego wektora $h_t$.\n",
|
|
"\n",
|
|
"Elementy stanu $c$ można traktować jako flagi, które można włączać i wyłączać. Na przykład, gdy w sekwencji napotykamy imię *Alice*, zgadujemy, że odnosi się ono do kobiety, i podnosimy flagę w stanie, która wskazuje, że mamy rzeczownik rodzaju żeńskiego w zdaniu. Gdy później napotykamy słowa *and Tom*, podnosimy flagę, która wskazuje, że mamy rzeczownik w liczbie mnogiej. W ten sposób, manipulując stanem, możemy śledzić właściwości gramatyczne zdania.\n",
|
|
"\n",
|
|
"> **Note**: Oto świetne źródło do zrozumienia wewnętrznej struktury LSTM: [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) autorstwa Christophera Olaha.\n",
|
|
"\n",
|
|
"Choć wewnętrzna struktura komórki LSTM może wyglądać na skomplikowaną, Keras ukrywa tę implementację w warstwie `LSTM`, więc jedyne, co musimy zrobić w powyższym przykładzie, to zastąpić warstwę rekurencyjną:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"15000/15000 [==============================] - 188s 13ms/step - loss: 0.5692 - acc: 0.7916 - val_loss: 0.3441 - val_acc: 0.8870\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<tensorflow.python.keras.callbacks.History at 0x7f3d6af5c350>"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" vectorizer,\n",
|
|
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
|
" keras.layers.LSTM(8),\n",
|
|
" keras.layers.Dense(4,activation='softmax')\n",
|
|
"])\n",
|
|
"\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
|
"model.fit(ds_train.map(tupelize).batch(8),validation_data=ds_test.map(tupelize).batch(8))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Dwukierunkowe i wielowarstwowe sieci RNN\n",
|
|
"\n",
|
|
"W dotychczasowych przykładach sieci rekurencyjne działały od początku sekwencji do jej końca. Wydaje się to naturalne, ponieważ odpowiada kierunkowi, w którym czytamy lub słuchamy mowy. Jednak w scenariuszach wymagających losowego dostępu do sekwencji wejściowej bardziej sensowne jest przeprowadzenie obliczeń rekurencyjnych w obu kierunkach. Sieci RNN, które umożliwiają obliczenia w obu kierunkach, nazywane są **dwukierunkowymi** RNN, a można je stworzyć, otaczając warstwę rekurencyjną specjalną warstwą `Bidirectional`.\n",
|
|
"\n",
|
|
"> **Note**: Warstwa `Bidirectional` tworzy dwie kopie warstwy w jej wnętrzu i ustawia właściwość `go_backwards` jednej z tych kopii na `True`, co sprawia, że działa ona w przeciwnym kierunku wzdłuż sekwencji.\n",
|
|
"\n",
|
|
"Sieci rekurencyjne, jednokierunkowe lub dwukierunkowe, wychwytują wzorce w sekwencji i przechowują je w wektorach stanów lub zwracają je jako wynik. Podobnie jak w przypadku sieci konwolucyjnych, możemy zbudować kolejną warstwę rekurencyjną po pierwszej, aby wychwycić wzorce wyższego poziomu, zbudowane z wzorców niższego poziomu wyodrębnionych przez pierwszą warstwę. Prowadzi to do koncepcji **wielowarstwowej RNN**, która składa się z dwóch lub więcej sieci rekurencyjnych, gdzie wynik poprzedniej warstwy jest przekazywany do następnej warstwy jako dane wejściowe.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"*Obrazek pochodzi z [tego wspaniałego artykułu](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) autorstwa Fernando Lópeza.*\n",
|
|
"\n",
|
|
"Keras ułatwia konstruowanie takich sieci, ponieważ wystarczy dodać więcej warstw rekurencyjnych do modelu. Dla wszystkich warstw z wyjątkiem ostatniej, musimy określić parametr `return_sequences=True`, ponieważ potrzebujemy, aby warstwa zwracała wszystkie stany pośrednie, a nie tylko końcowy stan obliczeń rekurencyjnych.\n",
|
|
"\n",
|
|
"Zbudujmy dwuwarstwową dwukierunkową sieć LSTM dla naszego problemu klasyfikacji.\n",
|
|
"\n",
|
|
"> **Note** Ten kod ponownie zajmuje dość dużo czasu, ale daje najwyższą dokładność, jaką dotychczas uzyskaliśmy. Może warto poczekać i zobaczyć wynik.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"5044/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r5045/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" vectorizer,\n",
|
|
" keras.layers.Embedding(vocab_size, 128, mask_zero=True),\n",
|
|
" keras.layers.Bidirectional(keras.layers.LSTM(64,return_sequences=True)),\n",
|
|
" keras.layers.Bidirectional(keras.layers.LSTM(64)), \n",
|
|
" keras.layers.Dense(4,activation='softmax')\n",
|
|
"])\n",
|
|
"\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
|
|
" validation_data=ds_test.map(tupelize).batch(batch_size))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## RNN-y do innych zadań\n",
|
|
"\n",
|
|
"Do tej pory skupialiśmy się na używaniu RNN-ów do klasyfikacji sekwencji tekstu. Jednak mogą one obsługiwać wiele innych zadań, takich jak generowanie tekstu czy tłumaczenie maszynowe — zajmiemy się tymi zadaniami w następnej jednostce.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Zastrzeżenie**: \nTen dokument został przetłumaczony za pomocą usługi tłumaczenia AI [Co-op Translator](https://github.com/Azure/co-op-translator). Chociaż staramy się zapewnić dokładność, prosimy pamiętać, że automatyczne tłumaczenia mogą zawierać błędy lub nieścisłości. Oryginalny dokument w jego rodzimym języku powinien być uznawany za autorytatywne źródło. W przypadku informacji krytycznych zaleca się skorzystanie z profesjonalnego tłumaczenia przez człowieka. Nie ponosimy odpowiedzialności za jakiekolwiek nieporozumienia lub błędne interpretacje wynikające z użycia tego tłumaczenia.\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernel_info": {
|
|
"name": "conda-env-py37_tensorflow-py"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "py37_tensorflow",
|
|
"language": "python",
|
|
"name": "conda-env-py37_tensorflow-py"
|
|
},
|
|
"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.7.9"
|
|
},
|
|
"nteract": {
|
|
"version": "nteract-front-end@1.0.0"
|
|
},
|
|
"coopTranslator": {
|
|
"original_hash": "81351e61f619b432ff51010a4f993194",
|
|
"translation_date": "2025-08-31T13:58:55+00:00",
|
|
"source_file": "lessons/5-NLP/16-RNN/RNNTF.ipynb",
|
|
"language_code": "pl"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
} |