Aula 3, LSTM

Esta aula apresenta a LSTM, a célula recorrente que resolveu o problema da memória longa. A sua ideia central é uma esteira de memória, o estado da célula, controlada por portões. Vamos ver, com uma demonstração direta, a LSTM segurar uma informação por muitos passos enquanto a RNN simples a esquece.

A aula anterior terminou com um diagnóstico claro, a RNN simples esquece o que está distante, porque o gradiente some ao voltar por muitos passos. A LSTM, proposta por Hochreiter e Schmidhuber em 1997, foi criada justamente para curar esse mal, e por décadas foi a arquitetura dominante para sequências, de tradução a reconhecimento de fala.

A sacada da LSTM é separar a memória do fluxo principal. Além do estado escondido, ela mantém um estado de célula, uma espécie de esteira por onde a informação pode viajar quase sem alteração, passo após passo. Portões aprendem o que apagar, o que escrever e o que ler dessa esteira. Nesta aula você vai entender esses portões e ver, na prática, a esteira de memória preservando um valor por vinte e cinco passos.


Objetivos

Ao final desta aula, você deve ser capaz de:

  • Explicar a ideia do estado de célula como uma esteira de memória.
  • Descrever a função dos portões de esquecimento, de entrada e de saída.
  • Implementar a passagem para frente de uma célula LSTM.
  • Mostrar por que a LSTM preserva memória onde a RNN simples falha.

Teoria

A LSTM acrescenta à RNN um segundo estado, o estado de célula $c_t$, que carrega a memória de longo prazo. O fluxo de informação por esse estado é regulado por três portões, cada um uma pequena rede com ativação sigmoide que produz valores entre 0 e 1, funcionando como válvulas.

O portão de esquecimento decide quanto da memória anterior manter. O portão de entrada decide quanto da nova informação escrever na memória. E o portão de saída decide quanto da memória revelar no estado escondido, que é o que sai da célula. O ponto crucial é que a memória anterior pode passar quase intacta quando o portão de esquecimento fica perto de 1, criando um caminho por onde o gradiente flui sem encolher. É isso que evita o gradiente que some.

flowchart LR
    C0[c anterior] --> F[portão de esquecimento]
    X[entrada e h anterior] --> F
    X --> I[portão de entrada]
    X --> O[portão de saída]
    F --> C1[c novo, a memória]
    I --> C1
    C1 --> O
    O --> H[h novo, a saída]

Comparada à RNN simples, a LSTM tem mais parâmetros e é mais cara de treinar, mas a recompensa é a capacidade de aprender dependências longas. Essa troca valeu tanto a pena que a LSTM se tornou, por muito tempo, sinônimo de modelagem de sequências.

Explicação Intuitiva

Pense no estado de célula como uma esteira transportadora correndo ao longo de toda a sequência. A informação colocada na esteira pode seguir até o fim quase sem mudança, a não ser que algum portão decida alterá-la. Os portões são operários ao lado da esteira, um pode jogar fora o que está passando, outro pode adicionar algo novo, e um terceiro escolhe o que mostrar para fora naquele instante.

Essa é a diferença para a RNN simples, que reescreve toda a sua memória a cada passo, diluindo o passado. A LSTM, ao contrário, pode optar por não mexer na esteira, preservando uma informação importante por quanto tempo for preciso. É como a diferença entre tentar guardar um número de cabeça enquanto faz outras contas, e anotá-lo em um papel que fica ali, intacto, até você precisar.

Explicação Matemática

A cada passo, a LSTM combina a entrada $x_t$ com o estado escondido anterior $h_{t-1}$ para calcular os três portões e uma candidata a nova memória. Usando $\sigma$ para a sigmoide e concatenando entrada e estado, as equações são

\[f_t = \sigma(W_f [x_t, h_{t-1}] + b_f), \quad i_t = \sigma(W_i [x_t, h_{t-1}] + b_i), \quad o_t = \sigma(W_o [x_t, h_{t-1}] + b_o),\] \[\tilde{c}_t = \tanh(W_g [x_t, h_{t-1}] + b_g).\]

A memória é então atualizada combinando o que se mantém e o que se escreve, e o estado escondido é a parte revelada dessa memória:

\[c_t = f_t \odot c_{t-1} + i_t \odot \tilde{c}_t, \qquad h_t = o_t \odot \tanh(c_t).\]

A primeira equação é o coração do método. Quando $f_t \approx 1$ e $i_t \approx 0$, temos $c_t \approx c_{t-1}$, ou seja, a memória atravessa o passo sem alteração. Esse caminho aditivo, e não multiplicativo como na RNN, é o que mantém o gradiente vivo ao longo de muitos passos.

Exemplo Prático

Em vez de treinar uma LSTM, que é mais custoso, vamos demonstrar diretamente por que ela funciona, ajustando os portões à mão. Colocamos um valor na memória, configuramos o portão de esquecimento perto de 1 e o de entrada perto de 0, e alimentamos a célula com zeros por vinte e cinco passos, observando o que acontece com a memória.

O contraste com a RNN simples é gritante. Na RNN, um sinal inicial de valor 1 decai para cerca de 0,03 em cinco passos e chega a praticamente 0 em vinte e cinco. Na LSTM, a mesma memória inicial de valor 1 ainda vale cerca de 0,94 após vinte e cinco passos. A esteira preservou a informação. O código está no notebook notebooks/modulo-05/03-lstm.ipynb, então abra-o ao lado para acompanhar.

Código Comentado

import numpy as np


def sigmoide(z):
    return 1 / (1 + np.exp(-z))


# Demonstração 1: a RNN simples esquece.
# Com peso recorrente contrativo e entrada zero, o estado decai a cada passo.
Wh = 0.5
h = 1.0                                 # sinal inicial na memória
print("RNN simples (entrada zero após o início):")
for t in [0, 5, 25]:
    valor = 1.0
    for _ in range(t):
        valor = np.tanh(Wh * valor)
    print(f"  passo {t:2d}: h = {valor:.4f}")


# Demonstração 2: a LSTM preserva.
# Portão de esquecimento alto (f ~ 1) e de entrada baixo (i ~ 0) seguram a memória.
def lstm_passo(x, h, c, bf=6.0, bi=-6.0, bo=2.0):
    f = sigmoide(bf)                    # esquecimento perto de 1
    i = sigmoide(bi)                    # entrada perto de 0
    o = sigmoide(bo)                    # saída moderada
    g = np.tanh(x)
    c = f * c + i * g                   # memória quase intacta
    h = o * np.tanh(c)
    return h, c


c = 1.0; h = 0.0                        # memória inicial guardada na célula
print("\nLSTM (mesma situação, entrada zero):")
for passo in range(26):
    if passo in (0, 5, 25):
        print(f"  passo {passo:2d}: c = {c:.4f}")
    h, c = lstm_passo(0.0, h, c)

Ao rodar, a RNN mostra o seu estado caindo de 1,0 para 0,03 e depois para 0, enquanto a LSTM mantém a memória em cerca de 0,94 mesmo após vinte e cinco passos. Essa é a tradução concreta da esteira de memória, e explica por que a LSTM aprende dependências que a RNN simples não alcança. Na prática, claro, os portões são aprendidos pelo treino, e não fixados à mão, mas a demonstração revela o mecanismo que torna esse aprendizado possível.

Exercícios

1) Conceitual: Descreva a função de cada um dos três portões da LSTM, com suas palavras. 2) Conceitual: Por que a atualização aditiva do estado de célula ajuda contra o gradiente que some, em comparação com a atualização da RNN simples? 3) Prático: Mude o viés do portão de esquecimento para um valor que o deixe perto de 0,5 e observe como a memória passa a decair. 4) Prático: Faça a célula escrever algo no meio da sequência, ajustando o portão de entrada em um passo, e veja a memória mudar. 5) Extensão: Pesquise a variante da LSTM com conexões de espiar, as peephole connections, e descreva o que elas acrescentam.

Projeto da Aula

Mostre, lado a lado, a memória de uma RNN simples e a de uma LSTM. A entrega é um experimento que injeta uma informação no início de uma sequência longa e acompanha, passo a passo, quanto dessa informação sobrevive em cada arquitetura, apresentando o resultado em uma tabela ou em um gráfico.

Considere o projeto pronto quando você tiver a curva de retenção das duas arquiteturas e um parágrafo explicando, a partir das equações, por que a LSTM preserva e a RNN esquece. Se quiser ir além, treine uma LSTM de verdade com PyTorch na tarefa de lembrar o primeiro bit em sequências longas, e confirme que ela supera a RNN da aula anterior.

Leituras Recomendadas

  • O artigo original da LSTM, de Hochreiter e Schmidhuber, de 1997.
  • O texto Understanding LSTM Networks, de Christopher Olah, com diagramas muito didáticos dos portões.
  • Capítulos sobre LSTM em Goodfellow e colegas, Deep Learning.

Referências Científicas

As referências abaixo são reais e estão registradas em references/referencias.bib. As chaves entre parênteses são as do BibTeX.

  • Hochreiter, S., e Schmidhuber, J. (1997). Long Short-Term Memory. Neural Computation, 9(8), 1735-1780. (hochreiter1997lstm)
  • Bengio, Y., Simard, P., e Frasconi, P. (1994). Learning Long-Term Dependencies with Gradient Descent is Difficult. IEEE TNN, 5(2), 157-166. (bengio1994longterm)
  • Goodfellow, I., Bengio, Y., e Courville, A. (2016). Deep Learning. MIT Press. (goodfellow2016deep)