Учебное пособие по RNN (рекуррентной нейронной сети): пример TensorFlow
⚡ Умное резюме
Рекуррентные нейронные сети добавляют состояние памяти к обычным нейронам, передавая каждый выходной сигнал обратно в качестве следующего входного, чтобы модель могла обучаться на основе порядка. В этом пошаговом руководстве показана реализация такой сети в TensorFlow и прогнозирование временного ряда.

Зачем нам нужна рекуррентная нейронная сеть (RNN)?
Рекуррентная нейронная сеть (RNN) позволяет моделировать блоки памяти для сохранения данных и моделирования краткосрочных зависимостей. Он также используется при прогнозировании временных рядов для выявления корреляций и закономерностей данных. Это также помогает получать результаты прогнозирования для последовательных данных, обеспечивая поведение, аналогичное человеческому мозгу.
Структура Искусственная нейронная сеть Это относительно простая модель, в основном основанная на умножении матриц. На первом этапе входные данные умножаются на изначально случайные веса и смещение, преобразованные с помощью функции активации, а выходные значения используются для прогнозирования. Этот шаг позволяет оценить, насколько сеть отличается от реальности.
Применяемая метрика – это потери. Чем выше функция потерь, тем глупее модель. Чтобы улучшить знание сети, необходима некоторая оптимизация путем корректировки весов сети. Стохастический градиентный спуск — это метод, используемый для изменения значений весов в правильном направлении. После внесения корректировок сеть может использовать еще один пакет данных для проверки своих новых знаний.
К счастью, ошибка меньше, чем раньше, но всё ещё недостаточно мала. Этап оптимизации выполняется итеративно до тех пор, пока ошибка не будет минимизирована, то есть пока не станет невозможно получить дополнительную информацию.tracТед.
Проблема с моделями этого типа в том, что у них нет памяти. Это означает, что вход и выход независимы. Другими словами, модель не заботится о том, что было раньше. Возникает некоторый вопрос, когда вам нужно предсказать временные ряды или предложения, поскольку сети необходима информация об исторических данных или прошлых словах.
Чтобы решить эту проблему, был разработан новый тип архитектуры: рекуррентная нейронная сеть (далее RNN).
Что такое рекуррентная нейронная сеть (RNN)?
Рекуррентная нейронная сеть (РНН) — это класс искусственных нейронных сетей, в которых связи между различными узлами образуют ориентированный граф, обеспечивающий динамическое поведение во времени. Она помогает моделировать последовательные данные, полученные из сетей прямого распространения. Она работает аналогично человеческому мозгу, выдавая прогнозные результаты.
Рекуррентная нейронная сеть очень похожа на традиционную нейронную сеть, за исключением того, что к нейронам добавляется состояние памяти. Вычисление включения памяти простое.
Представьте себе простую модель всего с одним нейроном, получающим на вход пакет данных. В традиционной нейронной сети модель выдает результат, умножая входные данные на вес и функцию активации. В рекуррентной нейронной сети этот результат возвращается сам себе определённое количество раз. Мы называем это шаг времени Время, в течение которого выходные данные становятся входными данными для следующего матричного умножения.
Например, на рисунке ниже вы можете видеть, что сеть состоит из одного нейрона. Сеть вычисляет умножение матриц между входными данными и весом и добавляет нелинейность с помощью функции активации. Он становится выходом в момент t-1. Этот вывод является входом второго матричного умножения.
Рекуррентная нейронная сеть (RNN)
Ниже мы кодируем простую RNN в TensorFlow, чтобы понять шаг, а также форму выходных данных.
Сеть состоит из:
- Четыре входа
- Шесть нейронов
- 2-кратные шаги
Сеть будет работать, как показано на рисунке ниже.
Сеть называется «рекуррентной», потому что она выполняет одну и ту же операцию в каждом квадрате активации. Прежде чем использовать функцию активации, сеть вычислила веса входных данных и предыдущих выходных данных.
Примечание к версии: Код в этом руководстве ориентирован на API графов TensorFlow 1.x. В TensorFlow 2.x тот же скрипт продолжает работать после выполнения команды. import tensorflow.compat.v1 as tf плюс tf.disable_v2_behavior(), потому что заполнители, сессии и модуль rnn_cell теперь находятся в другом месте. tf.compat.v1. Идиоматический эквивалент версии 2.x — это tf.keras.layers.SimpleRNN.
import numpy as np import tensorflow as tf n_inputs = 4 n_neurons = 6 n_timesteps = 2 The data is a sequence of a number from 0 to 9 and divided into three batches of data. ## Data X_batch = np.array([ [[0, 1, 2, 5], [9, 8, 7, 4]], # Batch 1 [[3, 4, 5, 2], [0, 0, 0, 0]], # Batch 2 [[6, 7, 8, 5], [6, 5, 4, 2]], # Batch 3 ])
Мы можем построить сеть с заполнителем для данных, повторяющимся этапом и выходными данными.
Определите заполнитель для данных
X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs])
Вот:
- Нет: Неизвестно и будет соответствовать размеру пакета.
- n_timesteps: сколько раз сеть отправит выходные данные обратно в нейрон.
- n_inputs: количество входных данных на пакет
Определить рекуррентную сеть
Как указано на рисунке выше, сеть состоит из 6 нейронов. Сеть вычислит два скалярных произведения:
- Входные данные с первым набором весов (т. е. 6: равно количеству нейронов)
- Предыдущий результат со вторым набором весов (т.е. 6: соответствует количеству выходных данных)
Обратите внимание, что во время первой прямой связи значения предыдущего вывода равны нулям, поскольку у нас нет доступных значений.
Объектом для построения RNN является tf.contrib.rnn.BasicRNNCell с аргументом num_units для определения количества входных данных.
basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)
Теперь, когда сеть определена, вы можете вычислить выходные данные и состояния.
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
Этот объект использует внутренний цикл для умножения матриц необходимое количество раз.
Обратите внимание, что рекуррентный нейрон является функцией всех входных сигналов предыдущих временных шагов. Именно так сеть формирует свою собственную память. Информация из предыдущего момента времени может распространяться в будущее. В этом и заключается магия рекуррентной нейронной сети.
## Define the shape of the tensor X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs]) ## Define the network basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons) outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) init = tf.global_variables_initializer() init = tf.global_variables_initializer() with tf.Session() as sess: init.run() outputs_val = outputs.eval(feed_dict={X: X_batch}) print(states.eval(feed_dict={X: X_batch})) [[ 0.38941205 -0.9980438 0.99750966 0.7892596 0.9978241 0.9999997 ] [ 0.61096436 0.7255889 0.82977575 -0.88226104 0.29261455 -0.15597084] [ 0.62091285 -0.87023467 0.99729395 -0.58261937 0.9811445 0.99969864]]
Для наглядности выводите значения предыдущего состояния. Выведенные выше данные показывают результат из последнего состояния. Теперь выведите весь результат, и вы заметите, что состояния представляют собой предыдущие результаты каждой партии. То есть, предыдущий результат содержит информацию обо всей последовательности.
print(outputs_val) print(outputs_val.shape) [[[-0.75934666 -0.99537754 0.9735819 -0.9722234 -0.14234993 -0.9984044 ] [ 0.99975264 -0.9983206 0.9999993 -1. -0.9997506 -1. ]] [[ 0.97486496 -0.98773265 0.9969686 -0.99950117 -0.7092863 -0.99998885] [ 0.9326837 0.2673438 0.2808514 -0.7535883 -0.43337247 0.5700631 ]] [[ 0.99628735 -0.9998728 0.99999213 -0.99999976 -0.9884324 -1. ] [ 0.99962527 -0.9467421 0.9997403 -0.99999714 -0.99929446 -0.9999795 ]]] (3, 2, 6)
Приведенное ниже представление с аннотациями разбивает один и тот же напечатанный тензор на отдельные пакеты.
Выходные данные имеют форму (3, 2, 6):
- 3: Количество партий
- 2: Номер временного шага
- 6: Количество нейронов
Оптимизация рекуррентной нейронной сети идентична традиционной нейронной сети. Более подробно вы увидите, как оптимизировать код, в следующей части этого руководства по рекуррентной нейронной сети.
Приложения РНН
RNN имеет множество применений, особенно когда речь идет о предсказании будущего. В финансовой отрасли RNN может быть полезна для прогнозирования цен на акции или знака направления фондового рынка (т. е. положительного или отрицательного).
RNN полезен для беспилотного автомобиля, поскольку он может избежать автомобильной аварии, предугадав траекторию движения транспортного средства.
RNN широко используется в анализе текста, субтитрах к изображениям, анализе настроений и машинном переводе. Например, можно использовать рецензию на фильм, чтобы понять чувства, которые зритель испытал после просмотра фильма. Автоматизация этой задачи очень полезна, когда у кинокомпании недостаточно времени для просмотра, маркировки, консолидации и анализа рецензий. Машина может выполнять работу с более высоким уровнем точности.
Ограничения RNN
Теоретически RNN должен передавать информацию до нескольких раз. Однако довольно сложно распространить всю эту информацию, если временной шаг слишком велик. Когда сеть имеет слишком много глубоких слоев, она становится необучаемой. Эта проблема называется: исчезающая проблема градиента. Если вы помните, нейронная сеть обновляет вес, используя алгоритм градиентного спуска. Градиенты становятся меньше, когда сеть переходит на более низкие уровни.
В заключение, градиенты остаются постоянными, а значит, возможности для улучшения отсутствуют. Модель обучается на основе изменения градиента; это изменение влияет на выходные данные сети. Однако, если разница в градиенте слишком мала (т.е. веса изменяются незначительно), сеть не может ничему научиться, и, следовательно, выходные данные также не улучшаются. Таким образом, сеть, сталкивающаяся с проблемой исчезающего градиента, не может сойтись к хорошему решению.
Улучшение ЛСТМ
Для преодоления потенциальной проблемы затухания градиента, с которой сталкиваются рекуррентные нейронные сети (RNN), три исследователя — Хохрайтер, Шмидхубер и Бенджио — усовершенствовали RNN, используя архитектуру, называемую долговременной кратковременной памятью (LSTM). Вкратце, LSTM предоставляет сети релевантную информацию из прошлого для более позднего времени. Машина использует более совершенную архитектуру для выбора и переноса информации в более поздний период времени.
Архитектура LSTM доступна в TensorFlow, tf.contrib.rnn.LSTMCell. LSTM выходит за рамки данного руководства. Вы можете обратиться к официальной документации. документации для дополнительной информации
В TensorFlow 2.x доступ к этой ячейке осуществляется следующим образом: tf.compat.v1.nn.rnn_cell.LSTMCell, в то время как в новом коде обычно используется tf.keras.layers.LSTM или более светозащищенный tf.keras.layers.GRU, что позволяет достичь сопоставимой точности при меньшем количестве параметров.
RNN во временных рядах
В этом уроке по TensorFlow RNN вы будете использовать рекуррентную нейронную сеть (RNN) с данными временных рядов. Временные ряды зависят от предыдущего времени, то есть прошлые значения содержат важную информацию, на основе которой сеть может обучаться. Идея прогнозирования временных рядов заключается в оценке будущих значений ряда, например, цены акций, температуры, ВВП и так далее.
Подготовка данных для Keras RNN и временных рядов может быть немного сложной. Прежде всего, цель состоит в том, чтобы предсказать следующее значение ряда, то есть вы будете использовать прошлую информацию для оценки значения в момент t + 1. Метка равна входной последовательности и сдвинута на один период вперед. Во-вторых, количество входных данных устанавливается равным 1, т. е. одно наблюдение за раз. Наконец, шаг по времени равен последовательности числового значения. Например, если вы установите временной шаг равным 10, входная последовательность будет возвращаться десять раз подряд.
Посмотрите на график ниже: мы представили данные временного ряда слева и воображаемую входную последовательность справа. Вы создаете функцию для возврата набора данных со случайным значением для каждого дня с января 2001 г. по декабрь 2016 г.
# To plot pretty figures %matplotlib inline import matplotlib import matplotlib.pyplot as plt import pandas as pd def create_ts(start = '2001', n = 201, freq = 'M'): rng = pd.date_range(start=start, periods=n, freq=freq) ts = pd.Series(np.random.uniform(-18, 18, size=len(rng)), rng).cumsum() return ts ts= create_ts(start = '2001', n = 192, freq = 'M') ts.tail(5)
Результат
2016-08-31 -93.459631 2016-09-30 -95.264791 2016-10-31 -95.551935 2016-11-30 -105.879611 2016-12-31 -123.729319 Freq: M, dtype: float64
ts = create_ts(start = '2001', n = 222) # Left plt.figure(figsize=(11,4)) plt.subplot(121) plt.plot(ts.index, ts) plt.plot(ts.index[90:100], ts[90:100], "b-", linewidth=3, label="A training instance") plt.title("A time series (generated)", fontsize=14) # Right plt.subplot(122) plt.title("A training instance", fontsize=14) plt.plot(ts.index[90:100], ts[90:100], "b-", markersize=8, label="instance") plt.plot(ts.index[91:101], ts[91:101], "bo", markersize=10, label="target", markerfacecolor='red') plt.legend(loc="upper left") plt.xlabel("Time") plt.show()
Правая часть графика показывает все временные ряды. Они начинаются с 2001 года и заканчиваются в 2019 году. Нет смысла загружать все данные в сеть; вместо этого необходимо создать пакет данных длиной, равной временному шагу. Этот пакет будет переменной X. Переменная Y — это то же самое, что и X, но сдвинутая на один период (т.е. вы хотите спрогнозировать t+1).
Оба вектора имеют одинаковую длину. Вы можете увидеть это в правой части графика выше. Линия представляет десять значений ввода X, а красные точки — десять значений метки Y. Обратите внимание, что метка начинается на один период раньше X и заканчивается на один период позже.
Создайте RNN для прогнозирования временных рядов в TensorFlow
Теперь в этом обучении RNN пришло время создать свою первую RNN для прогнозирования приведенного выше ряда. Вам необходимо указать некоторые гиперпараметры (параметры модели, т. е. количество нейронов и т. д.) для модели:
- Количество входов: 1
- Шаг по времени (окна во временных рядах): 10
- Количество нейронов: 120
- Количество выходов: 1
Ваша нейронная сеть будет обучаться на основе последовательности данных за 10 дней и будет содержать 120 рекуррентных нейронов. Вы подаете на вход модели один параметр, то есть данные за один день. Вы можете изменять значения, чтобы проверить, улучшилась ли модель.
Перед построением модели необходимо разделить набор данных на обучающую и тестовую выборки. Полный набор данных содержит 222 точки; первые 201 точку будут использованы для обучения модели, а последние 21 — для её тестирования.
После определения обучающей и тестовой выборок необходимо создать объект, содержащий пакеты данных. В этих пакетах содержатся значения X и Y. Помните, что значения X отстают на один период. Поэтому используются первые 200 наблюдений, а шаг по времени равен 10. Объект X_batches должен содержать 20 пакетов размером 10*1. Объект Y_batches имеет ту же форму, что и объект X_batches, но с отставанием на один период.
Шаг 1) Создайте поезд и протестируйте
Прежде всего, вы преобразуете серию в NumPy множество; затем вы определяете окна (т. е. количество времени, в течение которого сеть будет учиться), количество входных, выходных данных и размер набора поездов, как показано в примере TensorFlow RNN ниже.
series = np.array(ts) n_windows = 20 n_input = 1 n_output = 1 size_train = 201
После этого вы просто разделяете массив на два набора данных.
## Split data train = series[:size_train] test = series[size_train:] print(train.shape, test.shape) (201,) (21,)
Шаг 2) Создайте функцию для возврата X_batches и y_batches.
Чтобы упростить задачу, вы можете создать функцию, которая возвращает два разных массива: один для X_batches и один для y_batches.
Давайте напишем функцию RNN TensorFlow для создания пакетов.
Обратите внимание, что партии X отстают на один период (мы принимаем значение t-1). Выходные данные функции должны иметь три измерения. Первые размеры равны количеству пакетов, вторые — размеру окон, а последнее — количеству входных данных.
Сложная часть — правильно выбрать точки данных. Для точек данных X вы выбираете наблюдения от t = 1 до t = 200, а для точки данных Y вы возвращаете наблюдения от t = 2 до 201. Как только у вас есть правильные точки данных, можно легко изменить форму. сериал.
Чтобы построить объект с пакетами, вам необходимо разделить набор данных на десять пакетов одинаковой длины (т. е. 20). Вы можете использовать метод изменения формы и передать -1, чтобы серия была аналогична размеру пакета. Значение 20 — это количество наблюдений в каждой партии, а 1 — количество входных данных.
Вам нужно сделать тот же шаг, но для метки.
Обратите внимание, что вам нужно сдвинуть данные на то количество раз, на которое вы хотите сделать прогноз. Например, если вы хотите спрогнозировать на один день вперед, то сдвиньте ряд на 1. Если вы хотите спрогнозировать на два дня, то сдвиньте данные на 2.
x_data = train[:size_train-1]: Select all the training instance minus one day X_batches = x_data.reshape(-1, windows, input): create the right shape for the batch e.g (10, 20, 1) def create_batches(df, windows, input, output): ## Create X x_data = train[:size_train-1] # Select the data X_batches = x_data.reshape(-1, windows, input) # Reshape the data ## Create y y_data = train[n_output:size_train] y_batches = y_data.reshape(-1, windows, output) return X_batches, y_batches
Теперь, когда функция определена, вы можете вызвать ее для создания пакетов, как показано в примере RNN ниже.
X_batches, y_batches = create_batches(df = train,
windows = n_windows,
input = n_input,
output = n_output)
Вы можете распечатать форму, чтобы убедиться в правильности размеров.
print(X_batches.shape, y_batches.shape)
(10, 20, 1) (10, 20, 1)
Вам необходимо создать тестовый набор только с одним пакетом данных и 20 наблюдениями.
Обратите внимание, что вы прогнозируете дни за днями, это означает, что второе прогнозируемое значение будет основано на истинном значении первого дня (t+1) набора тестовых данных. Фактически, истинная стоимость будет известна.
Если вы хотите спрогнозировать время t+2 (т. е. на два дня вперед), вам необходимо использовать прогнозируемое значение t+1; если вы собираетесь предсказать время t+3 (на три дня вперед), вам нужно использовать прогнозируемые значения t+1 и t+2. Логично, что трудно точно предсказать t+n дней вперед.
X_test, y_test = create_batches(df = test, windows = 20,input = 1, output = 1)
print(X_test.shape, y_test.shape)
(10, 20, 1) (10, 20, 1)
Хорошо, размер пакета готов, вы можете построить архитектуру RNN. Помните, у вас 120 рекуррентных нейронов.
Шаг 3) Построить модель
Чтобы создать модель, вам необходимо определить три части:
- Переменная с тензорами
- РНН
- Потери и оптимизация
Шаг 3.1) Переменные показатели
Вам необходимо указать переменные X и y соответствующей формы. Этот шаг тривиален. Тензор имеет ту же размерность, что и объекты X_batches и y_batches.
Например, тензор X является заполнителем (см. руководство по этому вопросу). Введение в TensorFlow чтобы освежить ваше представление об объявлении переменных) имеет три измерения:
- Примечание: размер партии
- n_windows: Длина окон, т.е. количество раз, когда модель смотрит назад.
- n_input: количество входов
Результат:
tf.placeholder(tf.float32, [None, n_windows, n_input])
## 1. Construct the tensors X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output])
Шаг 3.2) Создайте РНС
Во второй части этого примера RNN TensorFlow вам необходимо определить архитектуру сети. Как и раньше, вы используете объект BasicRNNCell и динамический_rnn из оценщика TensorFlow.
## 2. create the model
basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu)
rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
Следующая часть немного сложнее, но позволяет ускорить вычисления. Вам необходимо преобразовать выходные данные прогона в плотный слой, а затем преобразовать его снова, чтобы он имел тот же размер, что и входные данные.
stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])
Шаг 3.3) Создайте потери и оптимизацию
Оптимизация модели зависит от выполняемой задачи. В предыдущем уроке мы рассмотрели... CNNВ первом уроке по RNN ваша задача заключалась в классификации изображений, а во втором – в классификации. Вам предлагается сделать прогноз для непрерывной переменной по сравнению с классом.
Это различие важно, поскольку оно изменит задачу оптимизации. Задача оптимизации непрерывной переменной состоит в минимизации среднеквадратической ошибки. Чтобы построить эти метрики в TF, вы можете использовать:
tf.reduce_sum(tf.square(выходные данные – y))
Остальная часть кода RNN такая же, как и раньше; вы используете оптимизатор Адама для уменьшения потерь (т. е. MSE):
tf.train.AdamOptimizer(learning_rate=learning_rate)
оптимизатор.минимизировать(потеря)
Вот и все, вы можете собрать все воедино, и ваша модель готова к обучению.
tf.reset_default_graph() r_neuron = 120 ## 1. Construct the tensors X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output]) ## 2. create the model basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output]) ## 3. Loss + optimization learning_rate = 0.001 loss = tf.reduce_sum(tf.square(outputs - y)) optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss) init = tf.global_variables_initializer()
Вы будете обучать модель, используя 1500 эпох, и печатать потери каждые 150 итераций. После обучения модели вы оцениваете ее на тестовом наборе и создаете объект, содержащий прогнозы, как показано в приведенном ниже примере рекуррентной нейронной сети.
iteration = 1500 with tf.Session() as sess: init.run() for iters in range(iteration): sess.run(training_op, feed_dict={X: X_batches, y: y_batches}) if iters % 150 == 0: mse = loss.eval(feed_dict={X: X_batches, y: y_batches}) print(iters, "\tMSE:", mse) y_pred = sess.run(outputs, feed_dict={X: X_test}) 0 MSE: 502893.34 150 MSE: 13839.129 300 MSE: 3964.835 450 MSE: 2619.885 600 MSE: 2418.772 750 MSE: 2110.5923 900 MSE: 1887.9644 1050 MSE: 1747.1377 1200 MSE: 1556.3398 1350 MSE: 1384.6113
Наконец, в этом руководстве по глубокому обучению RNN вы можете построить график фактического значения ряда с прогнозируемым значением. Если ваша модель исправлена, прогнозируемые значения должны быть помещены поверх фактических значений.
Как видите, модель нуждается в улучшении. Вы можете изменить гиперпараметры, такие как окна, размер пакета или количество рекуррентных нейронов.
plt.title("Forecast vs Actual", fontsize=14) plt.plot(pd.Series(np.ravel(y_test)), "bo", markersize=8, label="Actual", color='green') plt.plot(pd.Series(np.ravel(y_pred)), "r.", markersize=8, label="Forecast", color='red') plt.legend(loc="lower left") plt.xlabel("Time") plt.show()
Прогноз против факта





