Path: blob/master/site/pt-br/guide/keras/rnn.ipynb
25118 views
Copyright 2020 The TensorFlow Authors.
Redes Neurais Recorrentes (RNN) com Keras
Introdução
Redes neurais recorrentes (RNN - Recurrent neural networks) são uma classe de redes neurais poderosas para modelar dados sequenciais, tais como séries temporais ou linguagem natural.
Esquematicamente, uma camada RNN usa um loop for
para iterar sobre os timesteps de uma sequência, enquanto mantém um estado interno que codifica informações sobre os timesteps vistos até o momento.
A API Keras RNN foi projetada com foco em:
Facilidade de uso: as camadas incorporadas
keras.layers.RNN
,keras.layers.LSTM
,keras.layers.GRU
permitem que você construa rapidamente modelos recorrentes sem ter que tomar decisões difíceis de configuração.Facilidade de personalização: Você também pode definir sua própria camada de célula RNN (a parte interna do loop
for
) com comportamento personalizado e usá-la com a camadakeras.layers.RNN
genérica (o próprio loopfor
). Isto permite que você crie rapidamente protótipos de diferentes ideias de pesquisa de maneira flexível com o mínimo de código.
Configuração
Camadas RNN integradas: um exemplo simples
Existem três camadas RNN integradas no Keras:
keras.layers.SimpleRNN
, uma RNN totalmente conectada onde a saída do timestep anterior deve ser alimentada ao próximo timestep.keras.layers.GRU
, proposta pela primeira vez em Cho et al., 2014.keras.layers.LSTM
, proposta pela primeira vez em Hochreiter & Schmidhuber, 1997.
No início de 2015, foram criadas as primeiras implementações Python de código aberto reutilizáveis de LSTM e GRU do Keras.
Aqui está um exemplo simples de um modelo Sequential
que processa sequências de números inteiros, incorpora cada número inteiro em um vetor de 64 dimensões e, em seguida, processa a sequência de vetores usando uma camada LSTM
.
RNNs integrados oferecem suporte a vários recursos úteis:
Dropout recorrente, por meio dos argumentos
dropout
erecurrent_dropout
Capacidade de processar uma sequência de entrada ao contrário, através do argumento
go_backwards
Desenrolamento de loop (que pode causar uma grande aceleração ao processar sequências curtas na CPU), por meio do argumento
unroll
...e mais.
Para obter 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 timestep, contendo informações sobre toda a sequência de entrada. O formato desta saída é (batch_size, units)
onde units
corresponde ao argumento units
passado para o construtor da camada.
Uma camada RNN também pode retornar toda a sequência de saídas para cada amostra (um vetor por timestep por amostra), se você definir return_sequences=True
. O formato dessa saída é (batch_size, timesteps, units)
.
Além disso, uma camada RNN pode retornar seu(s) estado(s) interno(s) final(ais). Os estados retornados podem ser usados para retomar a execução da RNN posteriormente ou para inicializar outra RNN. Essa configuração é frequentemente usada no modelo sequence-to-sequence do encoder-decoder, em que o estado final do encoder é usado como o estado inicial do decoder.
Para configurar uma camada RNN para retornar seu estado interno, defina o parâmetro return_state
como True
ao criar a camada. Observe que LSTM
possui 2 tensores de estado, mas GRU
possui apenas um.
Para configurar o estado inicial da camada, basta chamar a camada com o argumento de palavra-chave adicional initial_state
. Observe que o formato do estado precisa corresponder ao tamanho da unidade da camada, como no exemplo abaixo.
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 timestep.
A célula é o interior do loop for
de uma camada RNN. Envolver uma célula dentro de uma camada keras.layers.RNN
fornece 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 empacotá-la numa camada RNN. No entanto, usar as camadas integradas GRU
e LSTM
permite o uso de CuDNN e você pode assim obter um melhor desempenho.
Existem três células RNN integradas, cada uma delas correspondendo à camada RNN correspondente.
keras.layers.SimpleRNNCell
corresponde à camadaSimpleRNN
.keras.layers.GRUCell
corresponde à camadaGRU
.keras.layers.LSTMCell
corresponde à camadaLSTM
.
A abstração de célula, juntamente com a classe genérica keras.layers.RNN
, facilita muito a implementação de arquiteturas RNN customizadas para sua pesquisa.
Statefulness entre lotes
Ao processar sequências muito longas (possivelmente infinitas), você talvez queira usar o padrão statefulness entre lotes (cross-batch statefulness).
Geralmente, o estado interno de uma camada RNN é reiniciado toda vez que ela encontra um novo lote (ou seja, cada amostra vista pela camada é considerada independente do seu passado). A camada só irá preserver um estado enquanto processa uma determinada amostra.
No entanto, se você tiver sequências muito longas, é útil dividi-las em sequências mais curtas e alimentar essas sequências mais curtas sequencialmente numa camada RNN sem reiniciar o estado da camada. Dessa forma, a camada pode reter informações sobre toda a sequência, mesmo que esteja vendo apenas uma subsequência de cada vez.
Você pode fazer isso definindo stateful=True
no construtor.
Se você tiver uma sequência s = [t0, t1, ... t1546, t1547]
, você poderia dividi-la da forma a seguir:
E depois você poderia processá-la da usando:
Quando quiser limpar o estado, você pode usar layer.reset_states()
.
Observação: Nesta configuração, a amostra
i
em um determinado lote é considerada a continuação da amostrai
do lote anterior. Isto significa que todos os lotes devem conter o mesmo número de amostras (tamanho do lote). Por exemplo, se um lote contém[sequence_A_from_t0_to_t100, sequence_B_from_t0_to_t100]
, o próximo lote deve conter[sequence_A_from_t101_to_t200, sequence_B_from_t101_to_t200]
.
Aqui está um exemplo completo:
Os estados registrados da camada RNN não são incluídos em layer.weights()
. Se quiser reutilizar o estado de uma camada RNN, você pode recuperar o valor dos estados por layer.states
e usá-lo como o estado inicial para uma nova camada através da API funcional Keras como new_layer(inputs, initial_state=layer.states)
, ou através da subclasse de um modelo.
Observe que o modelo sequencial pode não ser usado neste caso, pois ele suporta apenas camadas com entrada e saída únicas, a entrada adicional do estado inicial faz com que seja impossível usá-lo aqui.
RNNs bidirecionais
Para sequências que não sejam séries temporais (por exemplo, texto), é comum que um modelo RNN tenha 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 numa frase, geralmente é útil conhecer o contexto em torno da palavra, não apenas as palavras que vêm antes dela.
Keras fornece uma API prática para você construir RNNs bidirecionais desse tipo: o wrapper keras.layers.Bidirectional
.
Nos bastidores, Bidirectional
copiará a camada RNN passada e inverterá o campo go_backwards
da camada recém-copiada, para processar as entradas na ordem inversa.
A saída da RNN Bidirectional
será, por padrão, a concatenação da saída da camada "para frente" e da saída da camada "para trás". Se você precisar de um comportamento de fusão diferente, por exemplo, a concatenação, altere o parâmetro merge_mode
no construtor do wrapper Bidirectional
. Para mais detalhes sobre Bidirectional
, consulte a documentação da API.
Otimização de desempenho e kernels CuDNN
No TensorFlow 2.0, as camadas LSTM e GRU incorporadas foram atualizadas para aproveitar os kernels CuDNN por padrão quando uma GPU está disponível. Com essa alteração, as camadas anteriores keras.layers.CuDNNLSTM/CuDNNGRU
foram descontinuadas e você pode criar seu modelo sem se preocupar com o hardware em que ele será executado.
Já que o kernel CuDNN foi construído levando em conta determinadas suposições, isto significa que a camada não poderá usar o kernel CuDNN se você alterar os padrões das camadas LSTM ou GRU integradas. Por exemplo:
Alterar a função
activation
detanh
para outra coisa.Alterar a função
recurrent_activation
desigmoid
para outra coisa.Usar
recurrent_dropout
> 0.Definir
unroll
como True, o que força o LSTM/GRU a decompor otf.while_loop
interno em um loopfor
desenrolado.Definir
use_bias
como False.Usar mascaramento quando os dados de entrada não forem preenchidos estritamente à direita (se a máscara corresponder a dados preenchidos estritamente à direita, o CuDNN ainda pode ser usado. Este é o caso mais comum).
Para obter uma lista detalhada de restrições, consulte a documentação das camadas LSTM e GRU.
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 timestep) e faremos a previsão do rótulo do dígito.
Vamos carregar o dataset MNIST:
Vamos criar uma instância de modelo e treiná-la.
Escolhemos sparse_categorical_crossentropy
como a função de perda para o modelo. A saída do modelo tem formato de [batch_size, 10]
. O alvo para o modelo é um vetor inteiro, cada um dos inteiros está no intervalo de 0 a 9.
Agora, vamos comparar com um modelo que não usa o kernel CuDNN:
Ao rodar numa máquina com GPU NVIDIA e CuDNN instalados, o modelo construído com CuDNN pode ser treinado com muito mais rapidez em comparação com o modelo que usa o kernel TensorFlow comum.
O mesmo modelo habilitado para CuDNN também pode ser usado para rodar inferências num ambiente CPU-only. A anotação tf.device
abaixo está apenas forçando o posicionamento 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 onde está executando. Não é legal?
RNNs com entradas list/dict ou entradas aninhadas
Estruturas aninhadas permitem que os implementadores incluam mais informações num único timestep. Por exemplo, um quadro de vídeo pode ter entradas de áudio e vídeo simultâneas. A forma de dados neste caso poderia ser:
[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]
Em outro exemplo, dados de caligrafia poderiam armazenar as coordenadas x e y para a posição atual da caneta, assim como informações de pressão da caneta sobre o papel. Assim, a representação dos dados poderia ser:
[batch, timestep, {"location": [x, y], "pressure": [force]}]
O código a seguir mostra um exemplo de como criar uma célula RNN personalizada que aceite essas entradas estruturadas.
Defina uma célula personalizada que suporte entrada/saída aninhada
Veja Criando novas camadas e modelos através de subclasses para mais detalhes sobre como escrever suas próprias camadas.
Construa um modelo RNN com entrada/saída aninhada
Vamos construir um modelo Keras que use uma camada keras.layers.RNN
e a célula personalizada que acabamos de definir.
Treine o modelo com dados gerados aleatoriamente
Já que não há um bom candidato a dataset para este modelo, usamos dados aleatórios do Numpy para demonstração.
Com a camada Keras keras.layers.RNN
você só precisa definir a lógica matemática para uma etapa individual dentro da sequência, e a camada keras.layers.RNN
cuidará da iteração da sequência para você. É uma maneira incrivelmente poderosa de prototipar rapidamente novos tipos de RNNs (por exemplo, uma variante LSTM).
Para mais informações, consulte os documentos da API.