![]() | ![]() | ![]() | ![]() |
Введение
Рекуррентные нейронные сети (RNN) — это класс нейронных сетей, которые эффективны для моделирования данных последовательности, таких как временные ряды или естественный язык.
Схематически слой РНН использует for
цикла для перебора временных шагов последовательности, сохраняя при этом внутреннее состояние , которое кодирует информацию о временных шагов он видел до сих пор.
Keras RNN API разработан с упором на:
Простота использования: встроенный
keras.layers.RNN
,keras.layers.LSTM
,keras.layers.GRU
слои позволяют быстро создавать повторяющиеся модели без необходимости делать трудный выбор конфигурации.Простота настройки: Вы также можете определить свой собственный слой РНН клеток (внутреннюю часть из
for
цикла) с настраиваемыми поведением, и использовать его с общимkeras.layers.RNN
слоем (for
самого цикла). Это позволяет быстро и гибко создавать прототипы различных исследовательских идей с минимальным кодом.
Настраивать
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
Встроенные слои RNN: простой пример
В Keras есть три встроенных слоя RNN:
keras.layers.SimpleRNN
, полностью подключенный-РНН , где выход из предыдущего временного шага должен быть подан в следующем временном шаге.keras.layers.GRU
, впервые предложена в Cho и др., 2014 .keras.layers.LSTM
, первый предложенный в Hochreiter & Шмидхубера, 1997 .
В начале 2015 года у Keras были первые многоразовые реализации Python с открытым исходным кодом LSTM и GRU.
Вот простой пример Sequential
модели , которая обрабатывает последовательность целых чисел, каждое целое число вкладывается в 64-мерный вектор, затем обрабатывает последовательность векторов с использованием LSTM
слоя.
model = keras.Sequential()
# Add an Embedding layer expecting input vocab of size 1000, and
# output embedding dimension of size 64.
model.add(layers.Embedding(input_dim=1000, output_dim=64))
# Add a LSTM layer with 128 internal units.
model.add(layers.LSTM(128))
# Add a Dense layer with 10 units.
model.add(layers.Dense(10))
model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding (Embedding) (None, None, 64) 64000 _________________________________________________________________ lstm (LSTM) (None, 128) 98816 _________________________________________________________________ dense (Dense) (None, 10) 1290 ================================================================= Total params: 164,106 Trainable params: 164,106 Non-trainable params: 0 _________________________________________________________________
Встроенные RNN поддерживают ряд полезных функций:
- Рецидив отсев, через
dropout
иrecurrent_dropout
аргументов - Возможность обрабатывать входную последовательность в обратном направлении, через
go_backwards
аргумент - Размотка цикл (который может привести к большому ускорению при обработке коротких последовательностей на CPU), с помощью
unroll
аргумент - ...и больше.
Для получения дополнительной информации см документации API РНН .
Выходы и состояния
По умолчанию выходные данные слоя RNN содержат один вектор на выборку. Этот вектор представляет собой выходные данные ячейки RNN, соответствующие последнему временному шагу и содержащие информацию обо всей входной последовательности. Форма этого выхода (batch_size, units)
, где units
соответствует units
аргументу , передаваемых в конструктор слоя.
РНН слой также может возвращать всю последовательность выходов для каждого образца (один вектора за временный шаг на один образец), если установить return_sequences=True
. Форма этого выхода (batch_size, timesteps, units)
.
model = keras.Sequential()
model.add(layers.Embedding(input_dim=1000, output_dim=64))
# The output of GRU will be a 3D tensor of shape (batch_size, timesteps, 256)
model.add(layers.GRU(256, return_sequences=True))
# The output of SimpleRNN will be a 2D tensor of shape (batch_size, 128)
model.add(layers.SimpleRNN(128))
model.add(layers.Dense(10))
model.summary()
Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_1 (Embedding) (None, None, 64) 64000 _________________________________________________________________ gru (GRU) (None, None, 256) 247296 _________________________________________________________________ simple_rnn (SimpleRNN) (None, 128) 49280 _________________________________________________________________ dense_1 (Dense) (None, 10) 1290 ================================================================= Total params: 361,866 Trainable params: 361,866 Non-trainable params: 0 _________________________________________________________________
Кроме того, уровень RNN может возвращать свое конечное внутреннее состояние. Возвращенные состояния могут быть использованы для продолжения выполнения РНН позже, или для инициализации другого РНН . Этот параметр обычно используется в модели последовательностей кодер-декодер, где конечное состояние кодера используется в качестве начального состояния декодера.
Чтобы настроить слой РНН , чтобы вернуть свое внутреннее состояние, установите return_state
параметр в значение True
при создании слоя. Обратите внимание , что LSTM
имеет 2 государственных тензоры, но GRU
имеет только один.
Для того, чтобы настроить начальное состояние слоя, просто вызовите слой с дополнительным аргументом ключевым словом initial_state
. Обратите внимание, что форма состояния должна соответствовать размеру единицы слоя, как в примере ниже.
encoder_vocab = 1000
decoder_vocab = 2000
encoder_input = layers.Input(shape=(None,))
encoder_embedded = layers.Embedding(input_dim=encoder_vocab, output_dim=64)(
encoder_input
)
# Return states in addition to output
output, state_h, state_c = layers.LSTM(64, return_state=True, name="encoder")(
encoder_embedded
)
encoder_state = [state_h, state_c]
decoder_input = layers.Input(shape=(None,))
decoder_embedded = layers.Embedding(input_dim=decoder_vocab, output_dim=64)(
decoder_input
)
# Pass the 2 states to a new LSTM layer, as initial state
decoder_output = layers.LSTM(64, name="decoder")(
decoder_embedded, initial_state=encoder_state
)
output = layers.Dense(10)(decoder_output)
model = keras.Model([encoder_input, decoder_input], output)
model.summary()
Model: "model" __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== input_1 (InputLayer) [(None, None)] 0 __________________________________________________________________________________________________ input_2 (InputLayer) [(None, None)] 0 __________________________________________________________________________________________________ embedding_2 (Embedding) (None, None, 64) 64000 input_1[0][0] __________________________________________________________________________________________________ embedding_3 (Embedding) (None, None, 64) 128000 input_2[0][0] __________________________________________________________________________________________________ encoder (LSTM) [(None, 64), (None, 33024 embedding_2[0][0] __________________________________________________________________________________________________ decoder (LSTM) (None, 64) 33024 embedding_3[0][0] encoder[0][1] encoder[0][2] __________________________________________________________________________________________________ dense_2 (Dense) (None, 10) 650 decoder[0][0] ================================================================================================== Total params: 258,698 Trainable params: 258,698 Non-trainable params: 0 __________________________________________________________________________________________________
Слои RNN и ячейки RNN
Помимо встроенных слоев RNN, RNN API также предоставляет API на уровне ячеек. В отличие от слоев RNN, которые обрабатывают целые пакеты входных последовательностей, ячейка RNN обрабатывает только один временной шаг.
Клетка является внутренней частью for
петли слоя РНН. Обертывание ячейки внутри keras.layers.RNN
слоя дает слой , способному обрабатывать партии последовательностей, например , RNN(LSTMCell(10))
.
Математически, RNN(LSTMCell(10))
дает тот же результат , как LSTM(10)
. На самом деле, реализация этого уровня в TF v1.x просто создавала соответствующую ячейку RNN и оборачивала ее в слой RNN. Однако с помощью встроенного в GRU
и LSTM
слоев позволяют использовать CuDNN и вы можете увидеть более высокую производительность.
Есть три встроенных ячейки RNN, каждая из которых соответствует соответствующему слою RNN.
keras.layers.SimpleRNNCell
соответствуетSimpleRNN
слою.keras.layers.GRUCell
соответствуетGRU
слоя.keras.layers.LSTMCell
соответствуетLSTM
слою.
Клетка абстракция, вместе с родовым keras.layers.RNN
класса, сделать это очень легко реализовать пользовательские РНН архитектуры для ваших исследований.
Межпакетная отслеживание состояния
При обработке очень длинных последовательностей (возможно , бесконечное), вы можете использовать шаблон кросс-периодического statefulness.
Обычно внутреннее состояние уровня RNN сбрасывается каждый раз, когда он видит новый пакет (т. е. предполагается, что каждый образец, видимый слоем, не зависит от прошлого). Слой будет поддерживать состояние только при обработке данного семпла.
Однако, если у вас есть очень длинные последовательности, полезно разбить их на более короткие последовательности и последовательно передать эти более короткие последовательности на уровень RNN без сброса состояния слоя. Таким образом, слой может сохранить информацию обо всей последовательности, даже если он видит только одну подпоследовательность за раз.
Вы можете сделать это, установив с stateful=True
в конструкторе.
Если есть последовательность s = [t0, t1, ... t1546, t1547]
, вы бы разделить его на , например ,
s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]
Затем вы обработаете его через:
lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
output = lstm_layer(s)
Если вы хотите , чтобы очистить состояние, вы можете использовать layer.reset_states()
.
Вот полный пример:
paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)
lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
output = lstm_layer(paragraph3)
# reset_states() will reset the cached state to the original initial_state.
# If no initial_state was provided, zero-states will be used by default.
lstm_layer.reset_states()
Повторное использование состояния RNN
Записанные состояния слоя РНН не включены в layer.weights()
. Если вы хотите повторно использовать состояние из слоя РНН, вы можете получить значение состояний с помощью layer.states
и использовать его в качестве исходного состояния для нового слоя с помощью функционального API Keras как new_layer(inputs, initial_state=layer.states)
или подкласс модели.
Также обратите внимание, что последовательная модель может не использоваться в этом случае, поскольку она поддерживает только слои с одним входом и выходом, дополнительный вход начального состояния делает невозможным использование здесь.
paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)
lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
existing_state = lstm_layer.states
new_lstm_layer = layers.LSTM(64)
new_output = new_lstm_layer(paragraph3, initial_state=existing_state)
Двунаправленные RNN
Для последовательностей, отличных от временных рядов (например, текста), модель RNN часто может работать лучше, если она обрабатывает последовательность не только от начала до конца, но и в обратном направлении. Например, чтобы предсказать следующее слово в предложении, часто полезно иметь контекст вокруг слова, а не только слова, которые стоят перед ним.
Keras предоставляет простой API для вас , чтобы построить такую двунаправленную RNNs: в keras.layers.Bidirectional
обертку.
model = keras.Sequential()
model.add(
layers.Bidirectional(layers.LSTM(64, return_sequences=True), input_shape=(5, 10))
)
model.add(layers.Bidirectional(layers.LSTM(32)))
model.add(layers.Dense(10))
model.summary()
Model: "sequential_2" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= bidirectional (Bidirectional (None, 5, 128) 38400 _________________________________________________________________ bidirectional_1 (Bidirection (None, 64) 41216 _________________________________________________________________ dense_3 (Dense) (None, 10) 650 ================================================================= Total params: 80,266 Trainable params: 80,266 Non-trainable params: 0 _________________________________________________________________
Под капотом Bidirectional
будет копировать слой РНН , переданный в, и флип go_backwards
поле вновь скопированного слоя, таким образом , что он будет обрабатывать входные данные в обратном порядке.
Выход Bidirectional
RNN будет, по умолчанию, конкатенации выходного сигнала прямого слоя и выходом обратного слоя. Если вам нужно другое поведение слияния, например , конкатенация, изменить merge_mode
параметр в Bidirectional
обертке конструктора. Для получения более подробной информации о Bidirectional
, пожалуйста , проверьте в API документации .
Оптимизация производительности и ядра CuDNN
В TensorFlow 2.0 встроенные уровни LSTM и GRU были обновлены, чтобы использовать ядра CuDNN по умолчанию, когда доступен графический процессор. С этим изменением, предшествующие keras.layers.CuDNNLSTM/CuDNNGRU
слои устарели, и вы можете построить модель , не заботясь о начинке он будет работать дальше.
Так как ядро CuDNN построено с некоторыми допущениями, это означает , что слой не будет иметь возможности использовать ядро CuDNN , если вы измените значения по умолчанию из встроенных слоев LSTM или ГРУ. Например:
- Изменение
activation
функции отtanh
к чему - то еще. - Изменение
recurrent_activation
функции отsigmoid
к чему - то еще. - Использование
recurrent_dropout
> 0. - Установка
unroll
к истинному, что силы LSTM / ГРУ , чтобы разложить внутреннююtf.while_loop
в непрокатаннуюfor
цикла. - Установка
use_bias
Ложь. - Использование маскирования, когда входные данные не заполнены строго справа (если маска соответствует данным, дополненным строго справа, CuDNN все равно можно использовать. Это наиболее распространенный случай).
Подробный список ограничений, обратитесь к документации по LSTM и ГРУ слоев.
Использование ядер CuDNN, когда они доступны
Давайте создадим простую модель LSTM, чтобы продемонстрировать разницу в производительности.
Мы будем использовать в качестве входных последовательностей последовательность строк цифр MNIST (обрабатывая каждую строку пикселей как временной шаг) и предскажем метку цифры.
batch_size = 64
# Each MNIST image batch is a tensor of shape (batch_size, 28, 28).
# Each input sequence will be of size (28, 28) (height is treated like time).
input_dim = 28
units = 64
output_size = 10 # labels are from 0 to 9
# Build the RNN model
def build_model(allow_cudnn_kernel=True):
# CuDNN is only available at the layer level, and not at the cell level.
# This means `LSTM(units)` will use the CuDNN kernel,
# while RNN(LSTMCell(units)) will run on non-CuDNN kernel.
if allow_cudnn_kernel:
# The LSTM layer with default options uses CuDNN.
lstm_layer = keras.layers.LSTM(units, input_shape=(None, input_dim))
else:
# Wrapping a LSTMCell in a RNN layer will not use CuDNN.
lstm_layer = keras.layers.RNN(
keras.layers.LSTMCell(units), input_shape=(None, input_dim)
)
model = keras.models.Sequential(
[
lstm_layer,
keras.layers.BatchNormalization(),
keras.layers.Dense(output_size),
]
)
return model
Давайте загрузим набор данных MNIST:
mnist = keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
sample, sample_label = x_train[0], y_train[0]
Давайте создадим экземпляр модели и обучим его.
Мы выбираем sparse_categorical_crossentropy
как функции потерь для модели. Выход модели имеет форму [batch_size, 10]
. Целью модели является целочисленный вектор, каждое целое число находится в диапазоне от 0 до 9.
model = build_model(allow_cudnn_kernel=True)
model.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer="sgd",
metrics=["accuracy"],
)
model.fit(
x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)
938/938 [==============================] - 6s 5ms/step - loss: 0.9510 - accuracy: 0.7029 - val_loss: 0.5633 - val_accuracy: 0.8209 <keras.callbacks.History at 0x7fc9942efad0>
Теперь давайте сравним с моделью, которая не использует ядро CuDNN:
noncudnn_model = build_model(allow_cudnn_kernel=False)
noncudnn_model.set_weights(model.get_weights())
noncudnn_model.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer="sgd",
metrics=["accuracy"],
)
noncudnn_model.fit(
x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)
938/938 [==============================] - 34s 35ms/step - loss: 0.3894 - accuracy: 0.8846 - val_loss: 0.5677 - val_accuracy: 0.8045 <keras.callbacks.History at 0x7fc945fa2650>
При работе на машине с графическим процессором NVIDIA и установленным CuDNN модель, построенная с помощью CuDNN, намного быстрее обучается по сравнению с моделью, использующей обычное ядро TensorFlow.
Ту же модель с поддержкой CuDNN также можно использовать для выполнения логических выводов в среде, использующей только ЦП. tf.device
аннотации ниже только вынудив размещение устройства. Модель будет работать на процессоре по умолчанию, если нет доступного графического процессора.
Вам просто больше не нужно беспокоиться об оборудовании, на котором вы работаете. Разве это не круто?
import matplotlib.pyplot as plt
with tf.device("CPU:0"):
cpu_model = build_model(allow_cudnn_kernel=True)
cpu_model.set_weights(model.get_weights())
result = tf.argmax(cpu_model.predict_on_batch(tf.expand_dims(sample, 0)), axis=1)
print(
"Predicted result is: %s, target result is: %s" % (result.numpy(), sample_label)
)
plt.imshow(sample, cmap=plt.get_cmap("gray"))
Predicted result is: [3], target result is: 5
RNN с входными данными list/dict или вложенными входными данными
Вложенные структуры позволяют разработчикам включать больше информации за один временной шаг. Например, видеокадр может иметь аудио- и видеовход одновременно. Форма данных в этом случае может быть:
[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]
В другом примере данные рукописного ввода могут иметь как координаты x, так и y для текущего положения пера, а также информацию о нажиме. Таким образом, представление данных может быть:
[batch, timestep, {"location": [x, y], "pressure": [force]}]
В следующем коде приведен пример создания пользовательской ячейки RNN, которая принимает такие структурированные входные данные.
Определите пользовательскую ячейку, которая поддерживает вложенный ввод/вывод
См Создание новых слоев & Модели с помощью подклассов для получения подробной информации о написании собственных слоев.
class NestedCell(keras.layers.Layer):
def __init__(self, unit_1, unit_2, unit_3, **kwargs):
self.unit_1 = unit_1
self.unit_2 = unit_2
self.unit_3 = unit_3
self.state_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]
self.output_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]
super(NestedCell, self).__init__(**kwargs)
def build(self, input_shapes):
# expect input_shape to contain 2 items, [(batch, i1), (batch, i2, i3)]
i1 = input_shapes[0][1]
i2 = input_shapes[1][1]
i3 = input_shapes[1][2]
self.kernel_1 = self.add_weight(
shape=(i1, self.unit_1), initializer="uniform", name="kernel_1"
)
self.kernel_2_3 = self.add_weight(
shape=(i2, i3, self.unit_2, self.unit_3),
initializer="uniform",
name="kernel_2_3",
)
def call(self, inputs, states):
# inputs should be in [(batch, input_1), (batch, input_2, input_3)]
# state should be in shape [(batch, unit_1), (batch, unit_2, unit_3)]
input_1, input_2 = tf.nest.flatten(inputs)
s1, s2 = states
output_1 = tf.matmul(input_1, self.kernel_1)
output_2_3 = tf.einsum("bij,ijkl->bkl", input_2, self.kernel_2_3)
state_1 = s1 + output_1
state_2_3 = s2 + output_2_3
output = (output_1, output_2_3)
new_states = (state_1, state_2_3)
return output, new_states
def get_config(self):
return {"unit_1": self.unit_1, "unit_2": unit_2, "unit_3": self.unit_3}
Создайте модель RNN с вложенным вводом/выводом
Давайте построим модель Keras , которая использует keras.layers.RNN
слой и пользовательские ячейки мы только что определили.
unit_1 = 10
unit_2 = 20
unit_3 = 30
i1 = 32
i2 = 64
i3 = 32
batch_size = 64
num_batches = 10
timestep = 50
cell = NestedCell(unit_1, unit_2, unit_3)
rnn = keras.layers.RNN(cell)
input_1 = keras.Input((None, i1))
input_2 = keras.Input((None, i2, i3))
outputs = rnn((input_1, input_2))
model = keras.models.Model([input_1, input_2], outputs)
model.compile(optimizer="adam", loss="mse", metrics=["accuracy"])
Обучите модель с помощью случайно сгенерированных данных
Поскольку для этой модели нет подходящего набора данных-кандидата, для демонстрации мы используем случайные данные Numpy.
input_1_data = np.random.random((batch_size * num_batches, timestep, i1))
input_2_data = np.random.random((batch_size * num_batches, timestep, i2, i3))
target_1_data = np.random.random((batch_size * num_batches, unit_1))
target_2_data = np.random.random((batch_size * num_batches, unit_2, unit_3))
input_data = [input_1_data, input_2_data]
target_data = [target_1_data, target_2_data]
model.fit(input_data, target_data, batch_size=batch_size)
10/10 [==============================] - 1s 26ms/step - loss: 0.7316 - rnn_1_loss: 0.2590 - rnn_1_1_loss: 0.4725 - rnn_1_accuracy: 0.1016 - rnn_1_1_accuracy: 0.0328 <keras.callbacks.History at 0x7fc5686e6f50>
С Keras keras.layers.RNN
слоем, Вы только ожидать , чтобы определить математическую логику для отдельного шага в последовательности, и keras.layers.RNN
слой будет обрабатывать итерацию последовательности для вас. Это невероятно мощный способ быстрого создания прототипов новых типов RNN (например, варианта LSTM).
Для получения более подробной информации, пожалуйста , посетите API Docs .