460 lines
32 KiB
Plaintext
460 lines
32 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Рекурентні нейронні мережі\n",
|
||
"\n",
|
||
"У попередньому модулі ми розглядали багаті семантичні представлення тексту. Архітектура, яку ми використовували, захоплює агреговане значення слів у реченні, але не враховує **порядок** слів, оскільки операція агрегації, яка слідує за вбудовуваннями, видаляє цю інформацію з оригінального тексту. Через те, що ці моделі не можуть представляти порядок слів, вони не здатні вирішувати більш складні або неоднозначні завдання, такі як генерація тексту або відповіді на запитання.\n",
|
||
"\n",
|
||
"Щоб захопити значення послідовності тексту, ми використаємо архітектуру нейронної мережі, яка називається **рекурентна нейронна мережа** (RNN). Використовуючи RNN, ми пропускаємо наше речення через мережу по одному токену за раз, і мережа генерує певний **стан**, який ми потім передаємо в мережу разом із наступним токеном.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"З огляду на вхідну послідовність токенів $X_0,\\dots,X_n$, RNN створює послідовність блоків нейронної мережі та навчає цю послідовність від початку до кінця за допомогою зворотного поширення. Кожен блок мережі приймає пару $(X_i,S_i)$ як вхід і генерує $S_{i+1}$ як результат. Кінцевий стан $S_n$ або вихід $Y_n$ передається в лінійний класифікатор для отримання результату. Усі блоки мережі мають однакові ваги та навчаються від початку до кінця за допомогою одного проходу зворотного поширення.\n",
|
||
"\n",
|
||
"> На зображенні вище показано рекурентну нейронну мережу в розгорнутій формі (зліва) і в більш компактному рекурентному представленні (справа). Важливо розуміти, що всі RNN-комірки мають однакові **спільні ваги**.\n",
|
||
"\n",
|
||
"Оскільки вектори стану $S_0,\\dots,S_n$ передаються через мережу, RNN здатна навчатися послідовним залежностям між словами. Наприклад, коли слово *не* з'являється десь у послідовності, мережа може навчитися заперечувати певні елементи у векторі стану.\n",
|
||
"\n",
|
||
"Всередині кожна RNN-комірка містить дві матриці ваг: $W_H$ і $W_I$, а також зміщення $b$. На кожному кроці RNN, враховуючи вхід $X_i$ і вхідний стан $S_i$, вихідний стан обчислюється як $S_{i+1} = f(W_H\\times S_i + W_I\\times X_i+b)$, де $f$ — це функція активації (часто $\\tanh$).\n",
|
||
"\n",
|
||
"> Для задач, таких як генерація тексту (яку ми розглянемо в наступному уроці) або машинний переклад, ми також хочемо отримувати певне значення виходу на кожному кроці RNN. У цьому випадку додається ще одна матриця $W_O$, і вихід обчислюється як $Y_i=f(W_O\\times S_i+b_O)$.\n",
|
||
"\n",
|
||
"Давайте подивимося, як рекурентні нейронні мережі можуть допомогти нам класифікувати наш набір даних новин.\n",
|
||
"\n",
|
||
"> Для пісочниці необхідно виконати наступну комірку, щоб переконатися, що потрібна бібліотека встановлена, а дані попередньо завантажені. Якщо ви працюєте локально, цей крок можна пропустити.\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": [
|
||
"Коли ви тренуєте великі моделі, розподіл пам'яті GPU може стати проблемою. Також може виникнути потреба експериментувати з різними розмірами мініпакетів, щоб дані вміщувалися в пам'ять GPU, а навчання було достатньо швидким. Якщо ви запускаєте цей код на власній машині з GPU, ви можете експериментувати з налаштуванням розміру мініпакетів для прискорення навчання.\n",
|
||
"\n",
|
||
"> **Примітка**: Відомо, що певні версії драйверів NVidia не звільняють пам'ять після навчання моделі. У цьому ноутбуці ми запускаємо кілька прикладів, і це може призвести до вичерпання пам'яті в певних налаштуваннях, особливо якщо ви проводите власні експерименти в межах того ж ноутбука. Якщо ви стикаєтеся з дивними помилками під час запуску навчання моделі, можливо, вам слід перезапустити ядро ноутбука.\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": [
|
||
"## Простий класифікатор RNN\n",
|
||
"\n",
|
||
"У випадку простого RNN кожен рекурентний блок є простою лінійною мережею, яка приймає вхідний вектор і вектор стану, а потім створює новий вектор стану. У Keras це можна представити за допомогою шару `SimpleRNN`.\n",
|
||
"\n",
|
||
"Хоча ми можемо передавати токени, закодовані у форматі one-hot, безпосередньо до шару RNN, це не найкраща ідея через їх високу розмірність. Тому ми використаємо шар embedding для зменшення розмірності векторів слів, після чого додамо шар RNN і, нарешті, класифікатор `Dense`.\n",
|
||
"\n",
|
||
"> **Note**: У випадках, коли розмірність не є надто високою, наприклад, при використанні токенізації на рівні символів, може бути доцільно передавати токени, закодовані у форматі one-hot, безпосередньо до 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": [
|
||
"> **Примітка:** Тут ми використовуємо необучений шар вбудовування для простоти, але для кращих результатів можна використовувати попередньо навчений шар вбудовування за допомогою Word2Vec, як описано в попередньому розділі. Було б корисно, якби ви адаптували цей код для роботи з попередньо навченими вбудовуваннями.\n",
|
||
"\n",
|
||
"Тепер давайте навчимо нашу RNN. Загалом, RNN досить складно навчати, оскільки після розгортання RNN-комірок уздовж довжини послідовності кількість шарів, задіяних у зворотному поширенні, стає дуже великою. Тому нам потрібно вибрати меншу швидкість навчання та тренувати мережу на більшому наборі даних, щоб отримати хороші результати. Це може зайняти досить багато часу, тому бажано використовувати GPU.\n",
|
||
"\n",
|
||
"Щоб прискорити процес, ми будемо навчати модель RNN лише на заголовках новин, пропускаючи опис. Ви можете спробувати навчати з описом і перевірити, чи вдасться вам змусити модель навчатися.\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": [
|
||
"> **Примітка** точність, ймовірно, буде нижчою тут, оскільки ми тренуємося лише на заголовках новин.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Перегляд послідовностей змінної довжини\n",
|
||
"\n",
|
||
"Пам'ятайте, що шар `TextVectorization` автоматично доповнює послідовності змінної довжини в мініпакеті токенами для заповнення. Виявляється, ці токени також беруть участь у навчанні, і вони можуть ускладнити збіжність моделі.\n",
|
||
"\n",
|
||
"Існує кілька підходів, які ми можемо застосувати, щоб мінімізувати кількість заповнення. Один із них — це впорядкування набору даних за довжиною послідовності та групування всіх послідовностей за розміром. Це можна зробити за допомогою функції `tf.data.experimental.bucket_by_sequence_length` (див. [документацію](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length)).\n",
|
||
"\n",
|
||
"Інший підхід — використання **маскування**. У Keras деякі шари підтримують додатковий вхід, який показує, які токени слід враховувати під час навчання. Щоб інтегрувати маскування в нашу модель, ми можемо або додати окремий шар `Masking` ([документація](https://keras.io/api/layers/core_layers/masking/)), або вказати параметр `mask_zero=True` у нашому шарі `Embedding`.\n",
|
||
"\n",
|
||
"> **Note**: Навчання займе приблизно 5 хвилин для завершення однієї епохи на всьому наборі даних. Ви можете перервати навчання в будь-який момент, якщо вам не вистачає терпіння. Також ви можете обмежити кількість даних, які використовуються для навчання, додавши `.take(...)` після наборів даних `ds_train` і `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": [
|
||
"Тепер, коли ми використовуємо маскування, ми можемо тренувати модель на всьому наборі даних заголовків і описів.\n",
|
||
"\n",
|
||
"> **Примітка**: Ви помітили, що ми використовували векторизатор, натренований на заголовках новин, а не на всьому тексті статті? Потенційно це може призвести до того, що деякі токени будуть ігноруватися, тому краще перевчити векторизатор. Однак це може мати лише дуже незначний вплив, тому для простоти ми залишимося з попередньо натренованим векторизатором.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## LSTM: Довготривала короткочасна пам'ять\n",
|
||
"\n",
|
||
"Однією з головних проблем RNN є **згасання градієнтів**. RNN можуть бути досить довгими, і їм може бути складно передавати градієнти назад до першого шару мережі під час зворотного поширення. Коли це трапляється, мережа не може навчитися встановлювати зв’язки між віддаленими токенами. Один із способів уникнути цієї проблеми — це впровадження **явного управління станом** за допомогою **гейтів**. Дві найпоширеніші архітектури, які використовують гейти, — це **довготривала короткочасна пам'ять** (LSTM) і **гейтована релейна одиниця** (GRU). Тут ми розглянемо LSTM.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"Мережа LSTM організована подібно до RNN, але є два стани, які передаються від шару до шару: фактичний стан $c$ і прихований вектор $h$. У кожній одиниці прихований вектор $h_{t-1}$ комбінується з вхідним значенням $x_t$, і разом вони контролюють, що відбувається зі станом $c_t$ і виходом $h_{t}$ через **гейти**. Кожен гейт має сигмоїдну активацію (вихід у діапазоні $[0,1]$), яку можна уявити як побітову маску при множенні на вектор стану. LSTM мають такі гейти (зліва направо на зображенні вище):\n",
|
||
"* **гейт забування**, який визначає, які компоненти вектора $c_{t-1}$ потрібно забути, а які пропустити.\n",
|
||
"* **вхідний гейт**, який визначає, скільки інформації з вхідного вектора та попереднього прихованого вектора слід включити до вектора стану.\n",
|
||
"* **вихідний гейт**, який бере новий вектор стану та вирішує, які його компоненти будуть використані для створення нового прихованого вектора $h_t$.\n",
|
||
"\n",
|
||
"Компоненти стану $c$ можна уявити як прапорці, які можна вмикати та вимикати. Наприклад, коли ми зустрічаємо ім’я *Аліса* в послідовності, ми припускаємо, що це стосується жінки, і піднімаємо прапорець у стані, який вказує, що в реченні є жіночий іменник. Коли ми далі зустрічаємо слова *і Том*, ми піднімаємо прапорець, який вказує, що в нас є множинний іменник. Таким чином, маніпулюючи станом, ми можемо відстежувати граматичні властивості речення.\n",
|
||
"\n",
|
||
"> **Примітка**: Ось чудовий ресурс для розуміння внутрішньої структури LSTM: [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) від Крістофера Олаха.\n",
|
||
"\n",
|
||
"Хоча внутрішня структура комірки LSTM може виглядати складною, Keras приховує цю реалізацію всередині шару `LSTM`, тому єдине, що нам потрібно зробити в прикладі вище, — це замінити рекурентний шар:\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": [
|
||
"## Двонаправлені та багатошарові RNN\n",
|
||
"\n",
|
||
"У наших попередніх прикладах рекурентні мережі працюють від початку послідовності до її кінця. Це здається природним, оскільки відповідає напрямку, в якому ми читаємо або слухаємо мовлення. Однак у сценаріях, які потребують випадкового доступу до вхідної послідовності, логічніше виконувати рекурентні обчислення в обох напрямках. RNN, які дозволяють обчислення в обох напрямках, називаються **двонаправленими** RNN, і їх можна створити, обгорнувши рекурентний шар спеціальним шаром `Bidirectional`.\n",
|
||
"\n",
|
||
"> **Note**: Шар `Bidirectional` створює дві копії шару всередині себе та встановлює властивість `go_backwards` для однієї з цих копій у значення `True`, змушуючи її рухатися в протилежному напрямку вздовж послідовності.\n",
|
||
"\n",
|
||
"Рекурентні мережі, однонаправлені чи двонаправлені, захоплюють шаблони в межах послідовності та зберігають їх у векторі станів або повертають як вихідні дані. Як і у випадку з згортковими мережами, ми можемо створити ще один рекурентний шар після першого, щоб захопити шаблони вищого рівня, побудовані з шаблонів нижчого рівня, які витягує перший шар. Це приводить нас до поняття **багатошарової RNN**, яка складається з двох або більше рекурентних мереж, де вихід попереднього шару передається наступному шару як вхідні дані.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"*Зображення з [цієї чудової статті](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) Фернандо Лопеса.*\n",
|
||
"\n",
|
||
"Keras робить побудову таких мереж простою задачею, оскільки вам потрібно лише додати більше рекурентних шарів до моделі. Для всіх шарів, окрім останнього, необхідно вказати параметр `return_sequences=True`, оскільки нам потрібно, щоб шар повертав усі проміжні стани, а не лише фінальний стан рекурентного обчислення.\n",
|
||
"\n",
|
||
"Давайте створимо двошарову двонаправлену LSTM для нашої задачі класифікації.\n",
|
||
"\n",
|
||
"> **Note** цей код знову займає досить багато часу для виконання, але він дає найвищу точність, яку ми бачили до цього. Тож, можливо, варто почекати і побачити результат.\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) для інших завдань\n",
|
||
"\n",
|
||
"До цього моменту ми зосереджувалися на використанні RNN для класифікації текстових послідовностей. Однак вони здатні виконувати набагато більше завдань, таких як генерація тексту та машинний переклад — ці завдання ми розглянемо в наступному розділі.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**Відмова від відповідальності**: \nЦей документ був перекладений за допомогою сервісу автоматичного перекладу [Co-op Translator](https://github.com/Azure/co-op-translator). Хоча ми прагнемо до точності, будь ласка, майте на увазі, що автоматичні переклади можуть містити помилки або неточності. Оригінальний документ на його рідній мові слід вважати авторитетним джерелом. Для критичної інформації рекомендується професійний людський переклад. Ми не несемо відповідальності за будь-які непорозуміння або неправильні тлумачення, що виникають внаслідок використання цього перекладу.\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-30T10:28:21+00:00",
|
||
"source_file": "lessons/5-NLP/16-RNN/RNNTF.ipynb",
|
||
"language_code": "uk"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
} |