.. _sec_gru: Gated Recurrent Units (GRU) =========================== Em :numref:`sec_bptt`, discutimos como os gradientes são calculados em RNNs. Em particular, descobrimos que produtos longos de matrizes podem levar para gradientes desaparecendo ou explodindo. Vamos pensar brevemente sobre como anomalias de gradiente significam na prática: - Podemos encontrar uma situação em que uma observação precoce é altamente significativo para prever todas as observações futuras. Considere o caso inventado em que a primeira observação contém uma soma de verificação e o objetivo é para discernir se a soma de verificação está correta no final da sequência. Nesse caso, a influência do primeiro token é vital. Gostaríamos de ter algum mecanismos para armazenar informações precoces vitais em uma *célula de memória*. Sem tal um mecanismo, teremos que atribuir um gradiente muito grande a esta observação, uma vez que afeta todas as observações subsequentes. - Podemos encontrar situações em que alguns tokens não carreguem observação. Por exemplo, ao analisar uma página da web, pode haver Código HTML que é irrelevante para o propósito de avaliar o sentimento transmitido na página. Gostaríamos de ter algum mecanismo para *pular* tais tokens na representação do estado latente. - Podemos encontrar situações em que haja uma quebra lógica entre as partes de uma sequência. Por exemplo, pode haver uma transição entre capítulos em um livro, ou uma transição entre um mercado de valores em baixa e em alta. Neste caso seria bom ter um meio de *redefinir* nosso estado interno representação. Vários métodos foram propostos para resolver isso. Uma das mais antigas é a memória de curto prazo longa :cite:`Hochreiter.Schmidhuber.1997` que nós iremos discutir em :numref:`sec_lstm`. A unidade recorrente fechada (GRU) :cite:`Cho.Van-Merrienboer.Bahdanau.ea.2014` é uma variante um pouco mais simplificada que muitas vezes oferece desempenho comparável e é significativamente mais rápido para computar :cite:`Chung.Gulcehre.Cho.ea.2014`. Por sua simplicidade, comecemos com o GRU. Estado Oculto Fechado --------------------- A principal distinção entre RNNs vanilla e GRUs é que o último suporta o bloqueio do estado oculto. Isso significa que temos mecanismos dedicados para quando um estado oculto deve ser *atualizado* e também quando deve ser *redefinido*. Esses mecanismos são aprendidos e atendem às questões listadas acima. Por exemplo, se o primeiro token é de grande importância aprenderemos a não atualizar o estado oculto após a primeira observação. Da mesma forma, aprenderemos a pular observações temporárias irrelevantes. Por último, aprenderemos a redefinir o estado latente sempre que necessário. Discutimos isso em detalhes abaixo. Porta de Reinicialização e a Porta de Atualização ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A primeira coisa que precisamos apresentar é a *porta de reinicialização* e a *porta de atualização*. Nós os projetamos para serem vetores com entradas em :math:`(0, 1)` para que possamos realizar combinações convexas. Por exemplo, uma porta de reinicialização nos permitiria controlar quanto do estado anterior ainda podemos querer lembrar. Da mesma forma, uma porta de atualização nos permitiria controlar quanto do novo estado é apenas uma cópia do antigo estado. Começamos projetando esses portões. :numref:`fig_gru_1` ilustra as entradas para ambos as portas de reset e atualização em uma GRU, dada a entrada da etapa de tempo atual e o estado oculto da etapa de tempo anterior. As saídas de duas portas são fornecidos por duas camadas totalmente conectadas com uma função de ativação sigmóide. .. _fig_gru_1: .. figure:: ../img/gru-1.svg Calculando a porta de reinicialização e a porta de atualização em um modelo GRU. Matematicamente, para um determinado intervalo de tempo $ t $, suponha que a entrada seja um minibatch :math:`\mathbf{X}_t \in \mathbb{R}^{n \times d}` (número de exemplos: $ n $, número de entradas: $ d $) e o estado oculto da etapa de tempo anterior é :math:`\mathbf{H}_{t-1} \in \mathbb{R}^{n \times h}` (número de unidades ocultas: $ h $). Então, reinicie o portão :math:`\mathbf{R}_t \in \mathbb{R}^{n \times h}` e atualize o portão :math:`\mathbf{Z}_t \in \mathbb{R}^{n \times h}` são calculados da seguinte forma: .. math:: \begin{aligned} \mathbf{R}_t = \sigma(\mathbf{X}_t \mathbf{W}_{xr} + \mathbf{H}_{t-1} \mathbf{W}_{hr} + \mathbf{b}_r),\\ \mathbf{Z}_t = \sigma(\mathbf{X}_t \mathbf{W}_{xz} + \mathbf{H}_{t-1} \mathbf{W}_{hz} + \mathbf{b}_z), \end{aligned} onde :math:`\mathbf{W}_{xr}, \mathbf{W}_{xz} \in \mathbb{R}^{d \times h}` e :math:`\mathbf{W}_{hr}, \mathbf{W}_{hz} \in \mathbb{R}^{h \times h}` são pesos de parâmetros e :math:`\mathbf{b}_r, \mathbf{b}_z \in \mathbb{R}^{1 \times h}` são viéses. Observe que a transmissão (consulte :numref:`subsec_broadcasting`) é acionada durante a soma. Usamos funções sigmóides (como introduzidas em :numref:`sec_mlp`) para transformar os valores de entrada no intervalo :math:`(0, 1)`. Estado Oculto do Candidato ~~~~~~~~~~~~~~~~~~~~~~~~~~ Em seguida, vamos integrar a porta de reset :math:`\mathbf {R} _t` com o mecanismo regular de atualização de estado latente in :eq:`rnn_h_with_state`. Isso leva ao seguinte *estado oculto candidato* :math:`\tilde{\mathbf{H}}_t \in \mathbb{R}^{n \times h}` no passo de tempo :math:`t`: .. math:: \tilde{\mathbf{H}}_t = \tanh(\mathbf{X}_t \mathbf{W}_{xh} + \left(\mathbf{R}_t \odot \mathbf{H}_{t-1}\right) \mathbf{W}_{hh} + \mathbf{b}_h), :label: gru_tilde_H onde :math:`\mathbf{W}_{xh} \in \mathbb{R}^{d \times h}` and :math:`\mathbf{W}_{hh} \in \mathbb{R}^{h \times h}` são parâmetros de pesos, :math:`\mathbf{b}_h \in \mathbb{R}^{1 \times h}` é o viés, e o símbolo :math:`\odot` é o operador de produto Hadamard (elemento a elemento). Aqui, usamos uma não linearidade na forma de tanh para garantir que os valores no estado oculto candidato permaneçam no intervalo :math:`(-1, 1)`. O resultado é um *candidato*, pois ainda precisamos incorporar a ação da porta de atualização. Comparando com :eq:`rnn_h_with_state`, agora a influência dos estados anteriores pode ser reduzido com o multiplicação elementar de :math:`\mathbf{R}_t` e\ :math:`\mathbf{H}_{t-1}` em :eq:`gru_tilde_H`. Sempre que as entradas na porta de reset :math:`\mathbf{R}_t` estão perto de 1, recuperamos um RNN vanilla como em :eq:`rnn_h_with_state`. Para todas as entradas da porta de reset :math:`\mathbf{R}_t` que estão próximas de 0, o estado oculto candidato é o resultado de um MLP com :math:`\mathbf{X}_t` como a entrada. Qualquer estado oculto pré-existente é, portanto, *redefinido* para os padrões. :numref:`fig_gru_2` ilustra o fluxo computacional após aplicar a porta de reset. .. _fig_gru_2: .. figure:: ../img/gru-2.svg Calculando o estado oculto candidato em um modelo GRU. Estados Escondidos ~~~~~~~~~~~~~~~~~~ Finalmente, precisamos incorporar o efeito da porta de atualização :math:`\mathbf{Z}_t`. Isso determina até que ponto o novo estado oculto :math:`\mathbf{H}_t \in \mathbb{R}^{n \times h}` é apenas o antigo estado :math:`\mathbf{H}_{t-1}` e em quanto o novo estado candidato :math:`\tilde{\mathbf{H}}_t` é usado. A porta de atualização :math:`\mathbf{Z}_t` pode ser usada para este propósito, simplesmente tomando combinações convexas elemento a elemento entre :math:`\mathbf{H}_{t-1}` e :math:`\tilde{\mathbf{H}}_t`. Isso leva à equação de atualização final para a GRU: .. math:: \mathbf{H}_t = \mathbf{Z}_t \odot \mathbf{H}_{t-1} + (1 - \mathbf{Z}_t) \odot \tilde{\mathbf{H}}_t. Sempre que a porta de atualização :math:`\mathbf{Z}_t` está próxima de 1, simplesmente mantemos o estado antigo. Neste caso, a informação de :math:`\mathbf{X}_t` é essencialmente ignorada, pulando efetivamente o passo de tempo :math:`t` na cadeia de dependências. Em contraste, sempre que :math:`\mathbf{Z}_t` está próximo de 0, o novo estado latente :math:`\mathbf{H}_t` se aproxima do estado latente candidato :math:`\tilde{\mathbf{H}}_t`. Esses projetos podem nos ajudar a lidar com o problema do gradiente de desaparecimento em RNNs e melhor capturar dependências para sequências com grandes distâncias de intervalo de tempo. Por exemplo, se a porta de atualização estiver perto de 1 para todas as etapas de tempo de uma subsequência inteira, o antigo estado oculto na etapa do tempo de seu início será facilmente retido e aprovado até o fim, independentemente do comprimento da subsequência. :numref:`fig_gru_3` ilustra o fluxo computacional depois que a porta de atualização está em ação. .. _fig_gru_3: .. figure:: ../img/gru-3.svg Computing the hidden state in a GRU model. Calculando o estado oculto em um modelo GRU. Em resumo, GRUs têm as duas características distintas a seguir: - As portas de redefinição ajudam a capturar dependências de curto prazo em sequências. - Portas de atualização ajudam a capturar dependências de longo prazo em sequências. Implementação do zero --------------------- Para entender melhor o modelo GRU, vamos implementá-lo do zero. Começamos lendo o conjunto de dados da máquina do tempo que usamos em :numref:`sec_rnn_scratch`. O código para ler o conjunto de dados é fornecido abaixo. .. raw:: html