# Redes Neuronais Recorrentes
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/31)
Nas secções anteriores, utilizámos representações semânticas ricas de texto e um classificador linear simples sobre as embeddings. Esta arquitetura captura o significado agregado das palavras numa frase, mas não considera a **ordem** das palavras, porque a operação de agregação sobre as embeddings remove esta informação do texto original. Como estes modelos não conseguem modelar a ordem das palavras, não conseguem resolver tarefas mais complexas ou ambíguas, como geração de texto ou resposta a perguntas.
Para capturar o significado de uma sequência de texto, precisamos de usar outra arquitetura de rede neural, chamada **rede neural recorrente**, ou RNN. Numa RNN, passamos a nossa frase pela rede, um símbolo de cada vez, e a rede produz um **estado**, que depois passamos novamente à rede com o próximo símbolo.

> Imagem do autor
Dada a sequência de entrada de tokens X0,...,Xn, a RNN cria uma sequência de blocos de rede neural e treina esta sequência de ponta a ponta usando retropropagação. Cada bloco de rede recebe um par (Xi,Si) como entrada e produz Si+1 como resultado. O estado final Sn ou (saída Yn) é enviado para um classificador linear para produzir o resultado. Todos os blocos da rede partilham os mesmos pesos e são treinados de ponta a ponta usando uma única passagem de retropropagação.
Como os vetores de estado S0,...,Sn são passados pela rede, esta consegue aprender as dependências sequenciais entre palavras. Por exemplo, quando a palavra *não* aparece em algum lugar da sequência, a rede pode aprender a negar certos elementos dentro do vetor de estado, resultando em negação.
> ✅ Como os pesos de todos os blocos RNN na imagem acima são partilhados, a mesma imagem pode ser representada como um único bloco (à direita) com um loop de feedback recorrente, que passa o estado de saída da rede de volta para a entrada.
## Anatomia de uma Célula RNN
Vamos ver como uma célula RNN simples é organizada. Ela aceita o estado anterior Si-1 e o símbolo atual Xi como entradas, e tem de produzir o estado de saída Si (e, por vezes, também estamos interessados noutra saída Yi, como no caso de redes generativas).
Uma célula RNN simples tem duas matrizes de pesos internas: uma transforma um símbolo de entrada (vamos chamá-la de W) e outra transforma um estado de entrada (H). Neste caso, a saída da rede é calculada como σ(W×Xi+H×Si-1+b), onde σ é a função de ativação e b é um viés adicional.
> Imagem do autor
Em muitos casos, os tokens de entrada são passados por uma camada de embedding antes de entrar na RNN para reduzir a dimensionalidade. Neste caso, se a dimensão dos vetores de entrada for *emb_size* e o vetor de estado for *hid_size*, o tamanho de W será *emb_size*×*hid_size*, e o tamanho de H será *hid_size*×*hid_size*.
## Memória de Longo e Curto Prazo (LSTM)
Um dos principais problemas das RNNs clássicas é o chamado problema de **gradientes que desaparecem**. Como as RNNs são treinadas de ponta a ponta numa única passagem de retropropagação, têm dificuldade em propagar o erro para as primeiras camadas da rede, e assim a rede não consegue aprender relações entre tokens distantes. Uma das formas de evitar este problema é introduzir **gestão explícita de estado** usando os chamados **gates**. Existem duas arquiteturas bem conhecidas deste tipo: **Memória de Longo e Curto Prazo** (LSTM) e **Unidade de Relevo com Gate** (GRU).

> Fonte da imagem a definir
A rede LSTM é organizada de forma semelhante à RNN, mas existem dois estados que são passados de camada para camada: o estado real C e o vetor oculto H. Em cada unidade, o vetor oculto Hi é concatenado com a entrada Xi, e eles controlam o que acontece ao estado C através de **gates**. Cada gate é uma rede neural com ativação sigmoide (saída no intervalo [0,1]), que pode ser vista como uma máscara bit a bit quando multiplicada pelo vetor de estado. Existem os seguintes gates (da esquerda para a direita na imagem acima):
* O **gate de esquecimento** recebe um vetor oculto e determina quais componentes do vetor C precisamos esquecer e quais passar adiante.
* O **gate de entrada** extrai alguma informação dos vetores de entrada e ocultos e insere-a no estado.
* O **gate de saída** transforma o estado através de uma camada linear com ativação *tanh*, depois seleciona alguns dos seus componentes usando um vetor oculto Hi para produzir um novo estado Ci+1.
Os componentes do estado C podem ser vistos como algumas flags que podem ser ativadas ou desativadas. Por exemplo, quando encontramos o nome *Alice* na sequência, podemos assumir que se refere a uma personagem feminina e ativar a flag no estado indicando que temos um substantivo feminino na frase. Quando encontramos posteriormente a frase *e Tom*, ativamos a flag indicando que temos um substantivo plural. Assim, manipulando o estado, podemos supostamente acompanhar as propriedades gramaticais das partes da frase.
> ✅ Um excelente recurso para entender os detalhes internos do LSTM é este ótimo artigo [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) de Christopher Olah.
## RNNs Bidirecionais e Multicamadas
Discutimos redes recorrentes que operam numa direção, do início de uma sequência até ao fim. Parece natural, porque se assemelha à forma como lemos e ouvimos discurso. No entanto, como em muitos casos práticos temos acesso aleatório à sequência de entrada, pode fazer sentido executar o cálculo recorrente em ambas as direções. Estas redes são chamadas de **RNNs bidirecionais**. Ao lidar com redes bidirecionais, precisaríamos de dois vetores de estado oculto, um para cada direção.
Uma rede recorrente, seja unidirecional ou bidirecional, captura certos padrões dentro de uma sequência e pode armazená-los num vetor de estado ou passá-los para a saída. Tal como nas redes convolucionais, podemos construir outra camada recorrente sobre a primeira para capturar padrões de nível superior e construir a partir dos padrões de baixo nível extraídos pela primeira camada. Isto leva-nos à noção de uma **RNN multicamada**, que consiste em duas ou mais redes recorrentes, onde a saída da camada anterior é passada para a próxima camada como entrada.

*Imagem retirada [deste excelente artigo](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) de Fernando López*
## ✍️ Exercícios: Embeddings
Continue a sua aprendizagem nos seguintes notebooks:
* [RNNs com PyTorch](RNNPyTorch.ipynb)
* [RNNs com TensorFlow](RNNTF.ipynb)
## Conclusão
Nesta unidade, vimos que as RNNs podem ser usadas para classificação de sequências, mas na verdade, elas podem lidar com muitas outras tarefas, como geração de texto, tradução automática e mais. Vamos considerar essas tarefas na próxima unidade.
## 🚀 Desafio
Leia alguma literatura sobre LSTMs e considere as suas aplicações:
- [Grid Long Short-Term Memory](https://arxiv.org/pdf/1507.01526v1.pdf)
- [Show, Attend and Tell: Neural Image Caption
Generation with Visual Attention](https://arxiv.org/pdf/1502.03044v2.pdf)
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/32)
## Revisão e Autoestudo
- [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) de Christopher Olah.
## [Tarefa: Notebooks](assignment.md)
---