![]() | ![]() | ![]() | ![]() |
Introdução
As redes neurais recorrentes (RNN) são uma classe de redes neurais poderosas para modelar dados de sequência, como séries temporais ou linguagem natural.
Esquematicamente, uma camada RNN utiliza um for
loop para repetir os Timesteps de uma sequência, mantendo ao mesmo tempo um estado interno que codifica a informação sobre os Timesteps tem visto até agora.
A API Keras RNN foi projetada com foco em:
Facilidade de uso: o built-in
keras.layers.RNN
,keras.layers.LSTM
,keras.layers.GRU
camadas permitem-lhe criar rapidamente modelos recorrentes sem ter que fazer escolhas de configuração difíceis.Facilidade de personalização: Você também pode definir a sua própria camada de células RNN (a parte interna do
for
circular) com comportamento personalizado, e usá-lo com o genéricokeras.layers.RNN
camada (ofor
si loop). Isso permite que você prototipe rapidamente diferentes ideias de pesquisa de maneira flexível com o mínimo de código.
Configurar
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
Camadas RNN integradas: um exemplo simples
Existem três camadas RNN integradas no Keras:
keras.layers.SimpleRNN
, um RNN totalmente ligado, onde a saída do passo de tempo anterior é para ser alimentado para o próximo passo de tempo.keras.layers.GRU
, proposta pela primeira vez em Cho et al., 2014 .keras.layers.LSTM
, proposto pela primeira vez em Hochreiter & Schmidhuber de 1997 .
No início de 2015, Keras teve as primeiras implementações reutilizáveis em Python de código aberto de LSTM e GRU.
Aqui é um exemplo simples de uma Sequential
modelo que processa as sequências de números inteiros, cada número inteiro incorpora num vector 64-dimensional, em seguida, processa a sequência de vectores utilizando um LSTM
camada.
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 _________________________________________________________________
Os RNNs integrados suportam vários recursos úteis:
- Dropout recorrente, através dos
dropout
erecurrent_dropout
argumentos - Capacidade de processar uma sequência de entrada no sentido inverso, através do
go_backwards
argumento - Loop desenrolar (o que pode levar a um grande aumento de velocidade no processamento de sequências curtas de CPU), através do
unroll
argumento - ...e mais.
Para mais informações, consulte a documentação da API RNN .
Saídas e estados
Por padrão, a saída de uma camada RNN contém um único vetor por amostra. Este vetor é a saída da célula RNN correspondente ao último passo de tempo, contendo informações sobre toda a sequência de entrada. A forma desta saída é (batch_size, units)
onde units
corresponde ao units
argumento passados para o construtor da camada.
Uma camada RNN pode também devolver toda a sequência de saídas para cada amostra (um vector por iteração por amostra), se definir return_sequences=True
. A forma desta saída é (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 _________________________________________________________________
Além disso, uma camada RNN pode retornar seu(s) estado(s) interno(s) final(is). Os estados retornados podem ser usados para retomar a execução RNN mais tarde, ou para inicializar outra RNN . Essa configuração é comumente usada no modelo de sequência a sequência codificador-decodificador, onde o estado final do codificador é usado como o estado inicial do decodificador.
Para configurar uma camada de RNN para retornar seu estado interno, definir o return_state
parâmetro para True
ao criar a camada. Note-se que LSTM
tem 2 tensores estaduais, mas GRU
tem apenas um.
Para configurar o estado inicial da camada, basta ligar para a camada com chave argumento adicional initial_state
. Observe que a forma do estado precisa corresponder ao tamanho da unidade da camada, como no exemplo abaixo.
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 __________________________________________________________________________________________________
Camadas RNN e células RNN
Além das camadas RNN integradas, a API RNN também fornece APIs em nível de célula. Ao contrário das camadas RNN, que processam lotes inteiros de sequências de entrada, a célula RNN processa apenas um único passo de tempo.
A célula é o interior da for
lacete de uma camada RNN. Envolvendo uma célula dentro de um keras.layers.RNN
camada dá-lhe uma camada capaz de processar lotes de sequências, por exemplo, RNN(LSTMCell(10))
.
Matematicamente, RNN(LSTMCell(10))
produz o mesmo resultado que LSTM(10)
. Na verdade, a implementação dessa camada no TF v1.x foi apenas criar a célula RNN correspondente e envolvê-la em uma camada RNN. No entanto usando o built-in GRU
e LSTM
camadas permitem o uso de CuDNN e você pode ver um melhor desempenho.
Existem três células RNN integradas, cada uma delas correspondendo à camada RNN correspondente.
keras.layers.SimpleRNNCell
corresponde aoSimpleRNN
camada.keras.layers.GRUCell
corresponde aoGRU
camada.keras.layers.LSTMCell
corresponde aoLSTM
camada.
A abstração celular, juntamente com o genérico keras.layers.RNN
classe, torná-lo muito fácil de implementar personalizados arquiteturas RNN para sua pesquisa.
Statefulness de lote cruzado
Ao processar as sequências muito longas (possivelmente infinita), você pode querer usar o padrão de statefulness cross-batch.
Normalmente, o estado interno de uma camada RNN é redefinido toda vez que ela vê um novo lote (ou seja, cada amostra vista pela camada é considerada independente do passado). A camada só manterá um estado durante o processamento de uma determinada amostra.
No entanto, se você tiver sequências muito longas, é útil quebrá-las em sequências mais curtas e alimentar essas sequências mais curtas sequencialmente em uma camada RNN sem redefinir o estado da camada. Dessa forma, a camada pode reter informações sobre a totalidade da sequência, mesmo que esteja vendo apenas uma subsequência por vez.
Você pode fazer isso definindo stateful=True
no construtor.
Se você tem uma sequência s = [t0, t1, ... t1546, t1547]
, você dividi-lo em eg
s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]
Em seguida, você deve processá-lo por meio de:
lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
output = lstm_layer(s)
Quando você quiser limpar o estado, você pode usar layer.reset_states()
.
Aqui está um exemplo completo:
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()
Reutilização do Estado RNN
Os estados gravadas da camada RNN não estão incluídos nas layer.weights()
. Se você gostaria de reutilizar o estado de uma camada RNN, você pode recuperar o valor estados por layer.states
e usá-lo como o estado inicial para uma nova camada por meio da API funcional Keras como new_layer(inputs, initial_state=layer.states)
, ou subclasse de modelo.
Observe também que o modelo sequencial pode não ser usado neste caso, pois suporta apenas camadas com entrada e saída únicas, a entrada extra do estado inicial impossibilita o uso aqui.
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)
RNNs bidirecionais
Para sequências que não sejam séries temporais (por exemplo, texto), geralmente é o caso de um modelo RNN ter um desempenho melhor se não apenas processar a sequência do início ao fim, mas também de trás para frente. Por exemplo, para prever a próxima palavra em uma frase, muitas vezes é útil ter o contexto em torno da palavra, não apenas as palavras que vêm antes dela.
Keras fornece uma API fácil para você construir tais RNNs bidirecional: o keras.layers.Bidirectional
wrapper.
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 _________________________________________________________________
Sob o capô, Bidirectional
copiará a camada RNN transmitido, e inverter o go_backwards
campo da camada recentemente copiados, de modo que ele irá processar as entradas em ordem inversa.
A saída do Bidirectional
RNN será, por defeito, a concatenação da saída camada para a frente e a saída camada para trás. Se você precisa de um comportamento fusão diferente, por exemplo, concatenação, altere o merge_mode
parâmetro no Bidirectional
construtor wrapper. Para mais detalhes sobre Bidirectional
, por favor, verifique a documentação da API .
Otimização de desempenho e kernels CuDNN
No TensorFlow 2.0, as camadas LSTM e GRU integradas foram atualizadas para aproveitar os kernels CuDNN por padrão quando uma GPU está disponível. Com esta mudança, os anteriores keras.layers.CuDNNLSTM/CuDNNGRU
camadas foram reprovados, e você pode construir o seu modelo sem se preocupar com o hardware que será executado.
Desde o kernel CuDNN é construído com certos pressupostos, isso significa que a camada não será capaz de usar o kernel CuDNN se você alterar os padrões das camadas LSTM ou GRU embutidos. Por exemplo:
- Alterar a
activation
da função detanh
para outra coisa. - Alterar o
recurrent_activation
função desigmoid
para outra coisa. - Usando
recurrent_dropout
> 0. - Definir
unroll
como True, que forças LSTM / GRU para decompor o interiortf.while_loop
em um desenroladofor
loop. - Definir
use_bias
para Falso. - Usando mascaramento quando os dados de entrada não são preenchidos estritamente à direita (se a máscara corresponder a dados preenchidos estritamente à direita, CuDNN ainda pode ser usado. Este é o caso mais comum).
Para obter a lista detalhada de restrições, consulte a documentação para os LSTM e GRU camadas.
Usando kernels CuDNN quando disponíveis
Vamos construir um modelo LSTM simples para demonstrar a diferença de desempenho.
Usaremos como sequências de entrada a sequência de linhas de dígitos MNIST (tratando cada linha de pixels como um passo de tempo) e preveremos o rótulo do dígito.
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
Vamos carregar o conjunto de dados 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]
Vamos criar uma instância de modelo e treiná-la.
Nós escolhemos sparse_categorical_crossentropy
como a função de perda para o modelo. O resultado do modelo tem a forma do [batch_size, 10]
. O destino para o modelo é um vetor inteiro, cada um dos inteiros está no intervalo de 0 a 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>
Agora, vamos comparar com um modelo que não usa o kernel 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>
Ao ser executado em uma máquina com uma GPU NVIDIA e CuDNN instalados, o modelo criado com CuDNN é muito mais rápido de treinar em comparação com o modelo que usa o kernel TensorFlow normal.
O mesmo modelo habilitado para CuDNN também pode ser usado para executar inferência em um ambiente somente de CPU. O tf.device
anotação abaixo é apenas forçando a colocação do dispositivo. O modelo será executado na CPU por padrão se nenhuma GPU estiver disponível.
Você simplesmente não precisa mais se preocupar com o hardware em que está executando. Não é muito legal?
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
RNNs com entradas list/dict ou entradas aninhadas
As estruturas aninhadas permitem que os implementadores incluam mais informações em um único passo de tempo. Por exemplo, um quadro de vídeo pode ter entrada de áudio e vídeo ao mesmo tempo. A forma de dados neste caso poderia ser:
[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]
Em outro exemplo, os dados de caligrafia podem ter as coordenadas x e y para a posição atual da caneta, bem como informações de pressão. Assim, a representação de dados poderia ser:
[batch, timestep, {"location": [x, y], "pressure": [force]}]
O código a seguir fornece um exemplo de como criar uma célula RNN personalizada que aceita essas entradas estruturadas.
Defina uma célula personalizada que suporte entrada/saída aninhada
Veja Fazer novos Layers & Models via subclasse para obter mais detalhes sobre como escrever suas próprias camadas.
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}
Construir um modelo RNN com entrada/saída aninhada
Vamos construir um modelo Keras que usa um keras.layers.RNN
camada e a célula personalizado que acabamos de definir.
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"])
Treine o modelo com dados gerados aleatoriamente
Como não há um bom conjunto de dados candidato para este modelo, usamos dados aleatórios do Numpy para demonstração.
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>
Com a Keras keras.layers.RNN
camada, você é esperado apenas para definir a lógica matemática para a etapa individual dentro da seqüência, eo keras.layers.RNN
camada irá lidar com a iteração seqüência para você. É uma maneira incrivelmente poderosa de prototipar rapidamente novos tipos de RNNs (por exemplo, uma variante LSTM).
Para mais detalhes, visite os docs API .