.. _sec_rnn: Redes Neurais Recorrentes (RNNs) ================================ Em :numref:`sec_language_model` introduzimos modelos de :math:`n`-gramas, onde a probabilidade condicional da palavra :math:`x_t` no passo de tempo :math:`t` depende apenas das :math:`n-1` palavras anteriores. Se quisermos incorporar o possível efeito de palavras anteriores ao passo de tempo :math:`t-(n-1)` em :math:`x_t`, precisamos aumentar :math:`n`. No entanto, o número de parâmetros do modelo também aumentaria exponencialmente com ele, pois precisamos armazenar :math:`|\mathcal{V}|^n` números para um conjunto de vocabulário :math:`\mathcal{V}`. Portanto, em vez de modelar :math:`P(x_t \mid x_{t-1}, \ldots, x_{t-n+1})`, é preferível usar um modelo de variável latente: .. math:: P(x_t \mid x_{t-1}, \ldots, x_1) \approx P(x_t \mid h_{t-1}), onde :math:`h_{t-1}` é um *estado oculto* (também conhecido como uma variável oculta) que armazena as informações da sequência até o passo de tempo :math:`t-1`. Em geral, o estado oculto em qualquer etapa :math:`t` pode ser calculado com base na entrada atual :math:`x_ {t}` e no estado oculto anterior :math:`h_ {t-1}`: .. math:: h_t = f(x_{t}, h_{t-1}). :label: eq_ht_xt Para uma função suficientemente poderosa :math:`f` em :eq:`eq_ht_xt`, o modelo de variável latente não é uma aproximação. Afinal, :math:`h_t` pode simplesmente armazenar todos os dados que observou até agora. No entanto, isso pode tornar a computação e o armazenamento caros. Lembre-se de que discutimos camadas ocultas com unidades ocultas em :numref:`chap_perceptrons`. É digno de nota que camadas ocultas e estados ocultos referem-se a dois conceitos muito diferentes. Camadas ocultas são, conforme explicado, camadas que ficam ocultas da visualização no caminho da entrada à saída. Estados ocultos são tecnicamente falando *entradas* para tudo o que fazemos em uma determinada etapa, e elas só podem ser calculadas observando os dados em etapas de tempo anteriores. *Redes neurais recorrentes* (RNNs) são redes neurais com estados ocultos. Antes de introduzir o modelo RNN, primeiro revisitamos o modelo MLP introduzido em :numref:`sec_mlp`. Redes Neurais sem Estados Ocultos --------------------------------- Vamos dar uma olhada em um MLP com uma única camada oculta. Deixe a função de ativação da camada oculta ser :math:`\phi`. Dado um minibatch de exemplos :math:`\mathbf{X} \in \mathbb{R}^{n \times d}` com tamanho de lote :math:`n` e :math:`d` entradas, a saída da camada oculta :math:`\mathbf{H} \in \mathbb{R}^{n \times h}` é calculada como .. math:: \mathbf{H} = \phi(\mathbf{X} \mathbf{W}_{xh} + \mathbf{b}_h). :label: rnn_h_without_state Em :eq:`rnn_h_without_state`, temos o parâmetro de peso :math:`\mathbf{W}_{xh} \in \mathbb{R}^{d \times h}`, o parâmetro de polarização :math:`\mathbf{b}_h \in \mathbb{R}^{1 \times h}`, e o número de unidades ocultas :math:`h`, para a camada oculta. Assim, a transmissão (ver :numref:`subsec_broadcasting`) é aplicada durante a soma. Em seguida, a variável oculta :math:`\mathbf{H}` é usada como entrada da camada de saída. A camada de saída é fornecida por .. math:: \mathbf{O} = \mathbf{H} \mathbf{W}_{hq} + \mathbf{b}_q, onde :math:`\mathbf{O} \in \mathbb{R}^{n \times q}` é a variável de saída, :math:`\mathbf{W}_{hq} \in \mathbb{R}^{h \times q}` é o parâmetro de peso, e :math:`\mathbf{b}_q \in \mathbb{R}^{1 \times q}` é o parâmetro de polarização da camada de saída. Se for um problema de classificação, podemos usar :math:`\text{softmax}(\mathbf{O})` para calcular a distribuição de probabilidade das categorias de saída. Isso é inteiramente análogo ao problema de regressão que resolvemos anteriormente em :numref:`sec_sequence`, portanto omitimos detalhes. Basta dizer que podemos escolher pares de rótulo de recurso aleatoriamente e aprender os parâmetros de nossa rede por meio de diferenciação automática e gradiente descendente estocástico. .. _subsec_rnn_w_hidden_states: Redes Neurais Recorrentes com Estados Ocultos --------------------------------------------- As coisas são totalmente diferentes quando temos estados ocultos. Vejamos a estrutura com mais detalhes. Suponha que temos um minibatch de entradas :math:`\mathbf{X}_t \in \mathbb{R}^{n \times d}` no passo de tempo :math:`t`. Em outras palavras, para um minibatch de exemplos de sequência :math:`n`, cada linha de :math:`\mathbf{X}_t` corresponde a um exemplo no passo de tempo :math:`t` da sequência. Em seguida, denote por :math:`\mathbf{H}_t \in \mathbb{R}^{n \times h}` a variável oculta do passo de tempo :math:`t`. Ao contrário do MLP, aqui salvamos a variável oculta :math:`\mathbf{H}_{t-1}` da etapa de tempo anterior e introduzimos um novo parâmetro de peso :math:`\mathbf{W}_{hh} \in \mathbb{R}^{h \times h}` para descrever como usar a variável oculta da etapa de tempo anterior na etapa de tempo atual. Especificamente, o cálculo da variável oculta da etapa de tempo atual é determinado pela entrada da etapa de tempo atual junto com a variável oculta da etapa de tempo anterior: .. math:: \mathbf{H}_t = \phi(\mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh} + \mathbf{b}_h). :label: rnn_h_with_state Comparado com :eq:`rnn_h_without_state`, :eq:`rnn_h_with_state` adiciona mais um termo :math:`\mathbf{H}_{t-1} \mathbf{W}_{hh}` e assim instancia :eq:`eq_ht_xt`. A partir da relação entre as variáveis ​​ocultas :math:`\mathbf{H}_t` e :math:`\mathbf{H}_{t-1}` de etapas de tempo adjacentes, sabemos que essas variáveis ​​capturaram e retiveram as informações históricas da sequência até sua etapa de tempo atual, assim como o estado ou a memória da etapa de tempo atual da rede neural. Portanto, essa variável oculta é chamada de *estado oculto*. Visto que o estado oculto usa a mesma definição da etapa de tempo anterior na etapa de tempo atual, o cálculo de :eq:`rnn_h_with_state` é *recorrente*. Consequentemente, redes neurais com estados ocultos com base em cálculos recorrentes são nomeados *redes neurais recorrentes*. Camadas que fazem o cálculo de :eq:`rnn_h_with_state` em RNNs são chamadas de *camadas recorrentes*. Existem muitas maneiras diferentes de construir RNNs. RNNs com um estado oculto definido por :eq:`rnn_h_with_state` são muito comuns. Para a etapa de tempo :math:`t`, a saída da camada de saída é semelhante à computação no MLP: .. math:: \mathbf{O}_t = \mathbf{H}_t \mathbf{W}_{hq} + \mathbf{b}_q. Parâmetros do RNN incluem os pesos :math:`\mathbf{W}_{xh} \in \mathbb{R}^{d \times h}, \mathbf{W}_{hh} \in \mathbb{R}^{h \times h}`, e o *bias* :math:`\mathbf{b}_h \in \mathbb{R}^{1 \times h}` da camada oculta, junto com os pesos :math:`\mathbf{W}_{hq} \in \mathbb{R}^{h \times q}` e o *bias* :math:`\mathbf{b}_q \in \mathbb{R}^{1 \times q}` da camada de saída. Vale a pena mencionar que mesmo em diferentes etapas de tempo, Os RNNs sempre usam esses parâmetros do modelo. Portanto, o custo de parametrização de um RNN não cresce à medida que o número de etapas de tempo aumenta. :numref:`fig_rnn` ilustra a lógica computacional de uma RNN em três etapas de tempo adjacentes. A qualquer momento, passo :math:`t`, o cálculo do estado oculto pode ser tratado como: i) concatenar a entrada :math:`\mathbf{X}_t` na etapa de tempo atual :math:`t` e o estado oculto :math:`\mathbf{H}_{t-1}` na etapa de tempo anterior :math:`t-1`; ii) alimentar o resultado da concatenação em uma camada totalmente conectada com a função de ativação :math:`\phi`. A saída dessa camada totalmente conectada é o estado oculto :math:`\mathbf{H}_t` do intervalo de tempo atual :math:`t`. Nesse caso, os parâmetros do modelo são a concatenação de :math:`\mathbf{W}_{xh}` e :math:`\mathbf{W}_{hh}`, e um *bias* de :math:`\mathbf{b}_h`, tudo de :eq:`rnn_h_with_state`. O estado oculto do passo de tempo atual :math:`t`, :math:`\mathbf{H}_t`, participará do cálculo do estado oculto :math:`\mathbf{H}_{t+1}` do próximo passo de tempo :math:`t+1`. Além disso, :math:`\mathbf{H}_t` também será alimentado na camada de saída totalmente conectada para calcular a saída :math:`\mathbf{O}_t` do passo de tempo atual :math:`t`. .. _fig_rnn: .. figure:: ../img/rnn.svg Uma RNN com um estado oculto. Acabamos de mencionar que o cálculo de :math:`\mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh}` para o estado oculto é equivalente a multiplicação de matriz de concatenação de :math:`\mathbf{X}_t` and :math:`\mathbf{H}_{t-1}` e concatenação de :math:`\mathbf{W}_{xh}` and :math:`\mathbf{W}_{hh}`. Embora isso possa ser comprovado pela matemática, a seguir, apenas usamos um trecho de código simples para mostrar isso. Começando por, definir as matrizes ``X``,\ ``W_xh``, ``H`` e\ ``W_hh``, cujas formas são (3, 1), (1, 4), (3, 4) e (4, 4), respectivamente. Multiplicando ``X`` por\ ``W_xh``, e ``H`` por\ ``W_hh``, respectivamente e, em seguida, adicionando essas duas multiplicações, obtemos uma matriz de forma (3, 4). .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python from mxnet import np, npx from d2l import mxnet as d2l npx.set_np() X, W_xh = np.random.normal(0, 1, (3, 1)), np.random.normal(0, 1, (1, 4)) H, W_hh = np.random.normal(0, 1, (3, 4)), np.random.normal(0, 1, (4, 4)) np.dot(X, W_xh) + np.dot(H, W_hh) .. parsed-literal:: :class: output array([[-0.21952868, 4.256434 , 4.5812645 , -5.344988 ], [ 3.4478583 , -3.0177274 , -1.6777471 , 7.535347 ], [ 2.239007 , 1.4199957 , 4.744728 , -8.421293 ]]) .. raw:: html
.. raw:: html
.. code:: python import torch from d2l import torch as d2l X, W_xh = torch.normal(0, 1, (3, 1)), torch.normal(0, 1, (1, 4)) H, W_hh = torch.normal(0, 1, (3, 4)), torch.normal(0, 1, (4, 4)) torch.matmul(X, W_xh) + torch.matmul(H, W_hh) .. parsed-literal:: :class: output tensor([[ 0.9656, -0.2889, 1.2013, 0.3412], [ 2.0865, -2.5401, 1.2020, 3.0863], [-1.5326, 0.3890, -4.6161, -0.2931]]) .. raw:: html
.. raw:: html
.. code:: python import tensorflow as tf from d2l import tensorflow as d2l X, W_xh = tf.random.normal((3, 1), 0, 1), tf.random.normal((1, 4), 0, 1) H, W_hh = tf.random.normal((3, 4), 0, 1), tf.random.normal((4, 4), 0, 1) tf.matmul(X, W_xh) + tf.matmul(H, W_hh) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Agora vamos concatenar as matrizes ``X`` e\ ``H`` ao longo das colunas (eixo 1), e as matrizes ``W_xh`` e\ ``W_hh`` ao longo das linhas (eixo 0). Essas duas concatenações resulta em matrizes de forma (3, 5) e da forma (5, 4), respectivamente. Multiplicando essas duas matrizes concatenadas, obtemos a mesma matriz de saída de forma (3, 4) como acima. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python np.dot(np.concatenate((X, H), 1), np.concatenate((W_xh, W_hh), 0)) .. parsed-literal:: :class: output array([[-0.2195287, 4.256434 , 4.5812645, -5.344988 ], [ 3.4478583, -3.0177271, -1.677747 , 7.535347 ], [ 2.2390068, 1.4199957, 4.744728 , -8.421294 ]]) .. raw:: html
.. raw:: html
.. code:: python torch.matmul(torch.cat((X, H), 1), torch.cat((W_xh, W_hh), 0)) .. parsed-literal:: :class: output tensor([[ 0.9656, -0.2889, 1.2013, 0.3412], [ 2.0865, -2.5401, 1.2020, 3.0863], [-1.5326, 0.3890, -4.6161, -0.2931]]) .. raw:: html
.. raw:: html
.. code:: python tf.matmul(tf.concat((X, H), 1), tf.concat((W_xh, W_hh), 0)) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Modelos de Linguagem em Nível de Caracteres Baseados em RNN ----------------------------------------------------------- Lembre-se que para a modelagem de linguagem em :numref:`sec_language_model`, pretendemos prever o próximo token com base em os tokens atuais e passados, assim, mudamos a sequência original em um token como os rótulos. Bengio et al. propuseram primeiro usar uma rede neural para modelagem de linguagem :cite:`Bengio.Ducharme.Vincent.ea.2003`. A seguir, ilustramos como os RNNs podem ser usadas para construir um modelo de linguagem. Deixe o tamanho do minibatch ser um e a sequência do texto ser “máquina”. Para simplificar o treinamento nas seções subsequentes, nós tokenizamos o texto em caracteres em vez de palavras e considere um *modelo de linguagem em nível de caractere*. :numref:`fig_rnn_train` demonstra como prever o próximo caractere com base nos caracteres atuais e anteriores através de um RNN para modelagem de linguagem em nível de caractere. .. _fig_rnn_train: .. figure:: ../img/rnn-train.svg Um modelo de linguagem de nível de caractere baseado no RNN. As sequências de entrada e rótulo são “machin” e “achine”, respectivamente. Durante o processo de treinamento, executamos uma operação softmax na saída da camada de saída para cada etapa de tempo e, em seguida, usamos a perda de entropia cruzada para calcular o erro entre a saída do modelo e o rótulo. Devido ao cálculo recorrente do estado oculto na camada oculta, a saída da etapa de tempo 3 em :numref:`fig_rnn_train`, :math:`\mathbf{O}_3`, é determinada pela sequência de texto “m”, “a” e “c”. Como o próximo caractere da sequência nos dados de treinamento é “h”, a perda de tempo da etapa 3 dependerá da distribuição de probabilidade do próximo caractere gerado com base na sequência de características “m”, “a”, “c” e o rótulo “h” desta etapa de tempo. Na prática, cada token é representado por um vetor :math:`d`-dimensional e usamos um tamanho de batch :math:`n>1`. Portanto, a entrada $  mathbf X_t $ no passo de tempo $ t $ será uma matriz :math:`\mathbf X_t`, que é idêntica ao que discutimos em :numref:`subsec_rnn_w_hidden_states`. .. _subsec_perplexity: Perplexidade ------------ Por último, vamos discutir sobre como medir a qualidade do modelo de linguagem, que será usado para avaliar nossos modelos baseados em RNN nas seções subsequentes. Uma maneira é verificar o quão surpreendente é o texto. Um bom modelo de linguagem é capaz de prever com tokens de alta precisão que veremos a seguir. Considere as seguintes continuações da frase “Está chovendo”, conforme proposto por diferentes modelos de linguagem: 1. “Está chovendo lá fora” 2. “Está chovendo bananeira” 3. “Está chovendo piouw; kcj pwepoiut” Em termos de qualidade, o exemplo 1 é claramente o melhor. As palavras são sensatas e logicamente coerentes. Embora possa não refletir com muita precisão qual palavra segue semanticamente (“em São Francisco” e “no inverno” seriam extensões perfeitamente razoáveis), o modelo é capaz de capturar qual tipo de palavra se segue. O exemplo 2 é consideravelmente pior ao produzir uma extensão sem sentido. No entanto, pelo menos o modelo aprendeu como soletrar palavras e algum grau de correlação entre as palavras. Por último, o exemplo 3 indica um modelo mal treinado que não ajusta os dados adequadamente. Podemos medir a qualidade do modelo calculando a probabilidade da sequência. Infelizmente, esse é um número difícil de entender e difícil de comparar. Afinal, as sequências mais curtas têm muito mais probabilidade de ocorrer do que as mais longas, portanto, avaliando o modelo na magnum opus de Tolstoy *Guerra e paz* produzirá inevitavelmente uma probabilidade muito menor do que, digamos, na novela de Saint-Exupéry *O Pequeno Príncipe*. O que falta equivale a uma média. A teoria da informação é útil aqui. Definimos entropia, surpresa e entropia cruzada quando introduzimos a regressão softmax (:numref:`subsec_info_theory_basics`) e mais sobre a teoria da informação é discutido no `apêndice online sobre teoria da informação `__. Se quisermos compactar o texto, podemos perguntar sobre prever o próximo token dado o conjunto atual de tokens. Um modelo de linguagem melhor deve nos permitir prever o próximo token com mais precisão. Assim, deve permitir-nos gastar menos bits na compressão da sequência. Então, podemos medi-lo pela perda de entropia cruzada média sobre todos os :math:`n` tokens de uma sequência: .. math:: \frac{1}{n} \sum_{t=1}^n -\log P(x_t \mid x_{t-1}, \ldots, x_1), :label: eq_avg_ce_for_lm onde :math:`P` é dado por um modelo de linguagem e :math:`x_t` é o token real observado no passo de tempo :math:`t` da sequência. Isso torna o desempenho em documentos de comprimentos diferentes comparáveis. Por razões históricas, os cientistas do processamento de linguagem natural preferem usar uma quantidade chamada *perplexidade*. Em poucas palavras, é a exponencial de :eq:`eq_avg_ce_for_lm`: .. math:: \exp\left(-\frac{1}{n} \sum_{t=1}^n \log P(x_t \mid x_{t-1}, \ldots, x_1)\right). A perplexidade pode ser melhor entendida como a média harmônica do número de escolhas reais que temos ao decidir qual ficha escolher a seguir. Vejamos alguns casos: - No melhor cenário, o modelo sempre estima perfeitamente a probabilidade do token de rótulo como 1. Nesse caso, a perplexidade do modelo é 1. - No pior cenário, o modelo sempre prevê a probabilidade do token de rótulo como 0. Nessa situação, a perplexidade é infinita positiva. - Na linha de base, o modelo prevê uma distribuição uniforme de todos os tokens disponíveis do vocabulário. Nesse caso, a perplexidade é igual ao número de tokens exclusivos do vocabulário. Na verdade, se armazenássemos a sequência sem nenhuma compressão, seria o melhor que poderíamos fazer para codificá-la. Conseqüentemente, isso fornece um limite superior não trivial que qualquer modelo útil deve superar. Nas seções a seguir, implementaremos RNNs para modelos de linguagem em nível de personagem e usaremos perplexidade para avaliar tais modelos. Resumo ------ - Uma rede neural que usa computação recorrente para estados ocultos é chamada de rede neural recorrente (RNN). - O estado oculto de uma RNN pode capturar informações históricas da sequência até o intervalo de tempo atual. - O número de parâmetros do modelo RNN não aumenta com o aumento do número de etapas de tempo. - Podemos criar modelos de linguagem em nível de caractere usando um RNN. - Podemos usar a perplexidade para avaliar a qualidade dos modelos de linguagem. Exercícios ---------- 1. Se usarmos uma RNN para prever o próximo caractere em uma sequência de texto, qual é a dimensão necessária para qualquer saída? 2. Por que as RNNs podem expressar a probabilidade condicional de um token em algum intervalo de tempo com base em todos os tokens anteriores na sequência de texto? 3. O que acontece com o gradiente se você retropropaga através de uma longa sequência? 4. Quais são alguns dos problemas associados ao modelo de linguagem descrito nesta seção? .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
.. raw:: html