.. _sec_transformer: Transformador ============= Comparamos CNNs, RNNs e autoatenção em :numref:`subsec_cnn-rnn-self-attention`. Notavelmente, a auto-atenção desfruta de computação paralela e do comprimento máximo de caminho mais curto. Portanto, naturalmente, é atraente projetar profundamente arquiteturas usando auto-atenção. Ao contrário dos modelos anteriores de autoatenção que ainda contam com RNNs para representações de entrada cite:\ ``Cheng.Dong.Lapata.2016,Lin.Feng.Santos.ea.2017,Paulus.Xiong.Socher.2017``, o modelo do transformador é exclusivamente baseado em mecanismos de atenção sem qualquer camada convolucional ou recorrente :cite:`Vaswani.Shazeer.Parmar.ea.2017`. Embora originalmente propostos para aprendizagem de sequência para sequência em dados de texto, os transformadores têm sido difundidos em uma ampla gama de aplicações modernas de aprendizagem profunda, como nas áreas de linguagem, visão, fala e aprendizagem por reforço. Modelo ------ Como uma instância da arquitetura codificador-decodificador, a arquitetura geral do transformador é apresentada em :numref:`fig_transformer`. Como podemos ver, o transformador é composto por um codificador e um decodificador. Diferente de Atenção Bahdanau para o aprendizado de sequência para sequência em :numref:`fig_s2s_attention_details`, os *embeddings* de sequência de entrada (origem) e saída (destino) são adicionados com codificação posicional antes de serem alimentados no codificador e no decodificador que empilham módulos baseados em autoatenção. .. _fig_transformer: .. figure:: ../img/transformer.svg :width: 500px A arquitetura do transformador. Agora fornecemos uma visão geral da arquitetura do transformador em :numref:`fig_transformer`. Em um alto nível, o codificador do transformador é uma pilha de várias camadas idênticas, onde cada camada tem duas subcamadas (qualquer uma é denotada como :math:`\mathrm{sublayer}`). O primeiro é um pooling de autoatenção com várias *heads* e o segundo é uma rede *feed-forward* posicional. Especificamente, na autoatenção do codificador, as consultas, as chaves e os valores são todos provenientes das saídas da camada do codificador anterior. Inspirado no design ResNet em :numref:`sec_resnet`, uma conexão residual é empregada em torno de ambas as subcamadas. No transformador, para qualquer entrada :math:`\mathbf{x} \in \mathbb{R}^d` em qualquer posição da sequência, exigimos que :math:`\mathrm{sublayer}(\mathbf{x}) \in \mathbb{R}^d` para que a conexão residual :math:`\mathbf{x} + \mathrm{sublayer}(\mathbf{x}) \in \mathbb{R}^d` seja viável. Esta adição da conexão residual é imediatamente seguida pela normalização da camada :cite:`Ba.Kiros.Hinton.2016`. Como resultado, o codificador do transformador produz uma representação vetorial :math:`d`-dimensional para cada posição da sequência de entrada. O decodificador do transformador também é uma pilha de várias camadas idênticas com conexões residuais e normalizações de camada. Além das duas subcamadas descritas no codificador, o decodificador insere uma terceira subcamada, conhecida como atenção do codificador-decodificador, entre esses dois. Na atenção do codificador-decodificador, as consultas são das saídas da camada do decodificador anterior e as chaves e valores são das saídas do codificador do transformador. Na autoatenção do decodificador, consultas, chaves e valores são todos provenientes das saídas da camada do decodificador anterior. No entanto, cada posição no decodificador só pode atender a todas as posições no decodificador até aquela posição. Essa atenção *mascarada* preserva a propriedade auto-regressiva, garantindo que a previsão dependa apenas dos tokens de saída que foram gerados. Já descrevemos e implementamos a atenção multi-head com base em produtos escalonados em :numref:`sec_multihead-attention` e codificação posicional em :numref:`subsec_positional-encoding`. A seguir, implementaremos o restante do modelo do transformador. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python import math import pandas as pd from mxnet import autograd, np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np() .. raw:: html
.. raw:: html
.. code:: python import math import pandas as pd import torch from torch import nn from d2l import torch as d2l .. raw:: html
.. raw:: html
Redes *Positionwise Feed-Forward* --------------------------------- A rede feed-forward *positionwise* transforma a representação em todas as posições de sequência usando o mesmo MLP. É por isso que o chamamos de *positionwise*. Na implementação abaixo, o ``X`` de entrada com forma (tamanho do lote, número de etapas de tempo ou comprimento da sequência em tokens, número de unidades ocultas ou dimensão do recurso) será transformado por um MLP de duas camadas em um tensor de saída de forma (tamanho do lote, número de passos de tempo, ``ffn_num_outputs``). .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python #@save class PositionWiseFFN(nn.Block): def __init__(self, ffn_num_hiddens, ffn_num_outputs, **kwargs): super(PositionWiseFFN, self).__init__(**kwargs) self.dense1 = nn.Dense(ffn_num_hiddens, flatten=False, activation='relu') self.dense2 = nn.Dense(ffn_num_outputs, flatten=False) def forward(self, X): return self.dense2(self.dense1(X)) .. raw:: html
.. raw:: html
.. code:: python #@save class PositionWiseFFN(nn.Module): def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs, **kwargs): super(PositionWiseFFN, self).__init__(**kwargs) self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens) self.relu = nn.ReLU() self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs) def forward(self, X): return self.dense2(self.relu(self.dense1(X))) .. raw:: html
.. raw:: html
O seguinte exemplo mostra que a dimensão mais interna de um tensor muda para o número de saídas em a rede *feed-forward* posicionada. Uma vez que o mesmo MLP se transforma em todas as posições, quando as entradas em todas essas posições são as mesmas, suas saídas também são idênticas. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python ffn = PositionWiseFFN(4, 8) ffn.initialize() ffn(np.ones((2, 3, 4)))[0] .. parsed-literal:: :class: output array([[ 0.00239431, 0.00927085, -0.00021069, -0.00923989, -0.0082903 , -0.00162741, 0.00659031, 0.00023905], [ 0.00239431, 0.00927085, -0.00021069, -0.00923989, -0.0082903 , -0.00162741, 0.00659031, 0.00023905], [ 0.00239431, 0.00927085, -0.00021069, -0.00923989, -0.0082903 , -0.00162741, 0.00659031, 0.00023905]]) .. raw:: html
.. raw:: html
.. code:: python ffn = PositionWiseFFN(4, 4, 8) ffn.eval() ffn(torch.ones((2, 3, 4)))[0] .. parsed-literal:: :class: output tensor([[-0.0037, -0.1095, -0.1937, -0.4333, -0.3985, 0.6414, 0.2048, 1.1088], [-0.0037, -0.1095, -0.1937, -0.4333, -0.3985, 0.6414, 0.2048, 1.1088], [-0.0037, -0.1095, -0.1937, -0.4333, -0.3985, 0.6414, 0.2048, 1.1088]], grad_fn=) .. raw:: html
.. raw:: html
Conexão residual e normalização de camada ----------------------------------------- Agora vamos nos concentrar no componente “add & norm” em :numref:`fig_transformer` Como descrevemos no início desta seção, esta é uma conexão residual imediatamente seguido pela normalização da camada. Ambos são essenciais para arquiteturas profundas eficazes. Em :numref:`sec_batch_norm`, explicamos como a normalização em lote recentraliza e redimensiona os exemplos dentro um minibatch. A normalização de camada é igual à normalização em lote, exceto que a primeira normaliza em toda a dimensão do recurso. Apesar de suas aplicações difundidas em visão computacional, a normalização em lote é geralmente empiricamente menos eficaz do que a normalização de camada em tarefas de processamento de linguagem natural, cujas entradas são frequentemente sequências de comprimento variável. O fragmento de código a seguir compara a normalização em diferentes dimensões por normalização de camada e normalização de lote. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python ln = nn.LayerNorm() ln.initialize() bn = nn.BatchNorm() bn.initialize() X = np.array([[1, 2], [2, 3]]) # Compute mean and variance from `X` in the training mode with autograd.record(): print('layer norm:', ln(X), '\nbatch norm:', bn(X)) .. parsed-literal:: :class: output layer norm: [[-0.99998 0.99998] [-0.99998 0.99998]] batch norm: [[-0.99998 -0.99998] [ 0.99998 0.99998]] .. raw:: html
.. raw:: html
.. code:: python ln = nn.LayerNorm(2) bn = nn.BatchNorm1d(2) X = torch.tensor([[1, 2], [2, 3]], dtype=torch.float32) # Compute mean and variance from `X` in the training mode print('layer norm:', ln(X), '\nbatch norm:', bn(X)) .. parsed-literal:: :class: output layer norm: tensor([[-1.0000, 1.0000], [-1.0000, 1.0000]], grad_fn=) batch norm: tensor([[-1.0000, -1.0000], [ 1.0000, 1.0000]], grad_fn=) .. raw:: html
.. raw:: html
Agora podemos implementar a classe ``AddNorm`` usando uma conexão residual seguida pela normalização da camada. O *dropout* também é aplicado para regularização. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python #@save class AddNorm(nn.Block): def __init__(self, dropout, **kwargs): super(AddNorm, self).__init__(**kwargs) self.dropout = nn.Dropout(dropout) self.ln = nn.LayerNorm() def forward(self, X, Y): return self.ln(self.dropout(Y) + X) .. raw:: html
.. raw:: html
.. code:: python #@save class AddNorm(nn.Module): def __init__(self, normalized_shape, dropout, **kwargs): super(AddNorm, self).__init__(**kwargs) self.dropout = nn.Dropout(dropout) self.ln = nn.LayerNorm(normalized_shape) def forward(self, X, Y): return self.ln(self.dropout(Y) + X) .. raw:: html
.. raw:: html
A conexão residual requer que as duas entradas sejam da mesma forma de modo que o tensor de saída também tenha a mesma forma após a operação de adição. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python add_norm = AddNorm(0.5) add_norm.initialize() add_norm(np.ones((2, 3, 4)), np.ones((2, 3, 4))).shape .. parsed-literal:: :class: output (2, 3, 4) .. raw:: html
.. raw:: html
.. code:: python add_norm = AddNorm([3, 4], 0.5) # Normalized_shape is input.size()[1:] add_norm.eval() add_norm(torch.ones((2, 3, 4)), torch.ones((2, 3, 4))).shape .. parsed-literal:: :class: output torch.Size([2, 3, 4]) .. raw:: html
.. raw:: html
*Encoder* --------- Com todos os componentes essenciais para montar o *encoder* do transformador, vamos começar implementando uma única camada dentro do *encoder*. A seguinte classe ``EncoderBlock`` contém duas subcamadas: autoatenção com várias *heads* e redes de alimentação em posição posicionada, onde uma conexão residual seguida pela normalização da camada é empregada em torno de ambas as subcamadas. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python #@save class EncoderBlock(nn.Block): def __init__(self, num_hiddens, ffn_num_hiddens, num_heads, dropout, use_bias=False, **kwargs): super(EncoderBlock, self).__init__(**kwargs) self.attention = d2l.MultiHeadAttention( num_hiddens, num_heads, dropout, use_bias) self.addnorm1 = AddNorm(dropout) self.ffn = PositionWiseFFN(ffn_num_hiddens, num_hiddens) self.addnorm2 = AddNorm(dropout) def forward(self, X, valid_lens): Y = self.addnorm1(X, self.attention(X, X, X, valid_lens)) return self.addnorm2(Y, self.ffn(Y)) .. raw:: html
.. raw:: html
.. code:: python #@save class EncoderBlock(nn.Module): def __init__(self, key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, dropout, use_bias=False, **kwargs): super(EncoderBlock, self).__init__(**kwargs) self.attention = d2l.MultiHeadAttention( key_size, query_size, value_size, num_hiddens, num_heads, dropout, use_bias) self.addnorm1 = AddNorm(norm_shape, dropout) self.ffn = PositionWiseFFN( ffn_num_input, ffn_num_hiddens, num_hiddens) self.addnorm2 = AddNorm(norm_shape, dropout) def forward(self, X, valid_lens): Y = self.addnorm1(X, self.attention(X, X, X, valid_lens)) return self.addnorm2(Y, self.ffn(Y)) .. raw:: html
.. raw:: html
Como podemos ver, qualquer camada no *encoder* do transformador não altera a forma de sua entrada. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python X = np.ones((2, 100, 24)) valid_lens = np.array([3, 2]) encoder_blk = EncoderBlock(24, 48, 8, 0.5) encoder_blk.initialize() encoder_blk(X, valid_lens).shape .. parsed-literal:: :class: output (2, 100, 24) .. raw:: html
.. raw:: html
.. code:: python X = torch.ones((2, 100, 24)) valid_lens = torch.tensor([3, 2]) encoder_blk = EncoderBlock(24, 24, 24, 24, [100, 24], 24, 48, 8, 0.5) encoder_blk.eval() encoder_blk(X, valid_lens).shape .. parsed-literal:: :class: output torch.Size([2, 100, 24]) .. raw:: html
.. raw:: html
Na seguinte implementação do *encoder* de transformador, empilhamos instâncias ``num_layers`` das classes\ ``EncoderBlock`` acima. Uma vez que usamos a codificação posicional fixa cujos valores estão sempre entre -1 e 1, nós multiplicamos os valores dos *embeddings* de entrada aprendíveis pela raiz quadrada da dimensão de incorporação para redimensionar antes de resumir a incorporação de entrada e a codificação posicional. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python #@save class TransformerEncoder(d2l.Encoder): def __init__(self, vocab_size, num_hiddens, ffn_num_hiddens, num_heads, num_layers, dropout, use_bias=False, **kwargs): super(TransformerEncoder, self).__init__(**kwargs) self.num_hiddens = num_hiddens self.embedding = nn.Embedding(vocab_size, num_hiddens) self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout) self.blks = nn.Sequential() for _ in range(num_layers): self.blks.add( EncoderBlock(num_hiddens, ffn_num_hiddens, num_heads, dropout, use_bias)) def forward(self, X, valid_lens, *args): # Since positional encoding values are between -1 and 1, the embedding # values are multiplied by the square root of the embedding dimension # to rescale before they are summed up X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens)) self.attention_weights = [None] * len(self.blks) for i, blk in enumerate(self.blks): X = blk(X, valid_lens) self.attention_weights[ i] = blk.attention.attention.attention_weights return X .. raw:: html
.. raw:: html
.. code:: python #@save class TransformerEncoder(d2l.Encoder): def __init__(self, vocab_size, key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout, use_bias=False, **kwargs): super(TransformerEncoder, self).__init__(**kwargs) self.num_hiddens = num_hiddens self.embedding = nn.Embedding(vocab_size, num_hiddens) self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout) self.blks = nn.Sequential() for i in range(num_layers): self.blks.add_module("block"+str(i), EncoderBlock(key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, dropout, use_bias)) def forward(self, X, valid_lens, *args): # Since positional encoding values are between -1 and 1, the embedding # values are multiplied by the square root of the embedding dimension # to rescale before they are summed up X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens)) self.attention_weights = [None] * len(self.blks) for i, blk in enumerate(self.blks): X = blk(X, valid_lens) self.attention_weights[ i] = blk.attention.attention.attention_weights return X .. raw:: html
.. raw:: html
Abaixo, especificamos hiperparâmetros para criar um *encoder* de transformador de duas camadas. A forma da saída do *encoder* do transformador é (tamanho do lote, número de etapas de tempo, ``num_hiddens``). .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python encoder = TransformerEncoder(200, 24, 48, 8, 2, 0.5) encoder.initialize() encoder(np.ones((2, 100)), valid_lens).shape .. parsed-literal:: :class: output (2, 100, 24) .. raw:: html
.. raw:: html
.. code:: python encoder = TransformerEncoder( 200, 24, 24, 24, 24, [100, 24], 24, 48, 8, 2, 0.5) encoder.eval() encoder(torch.ones((2, 100), dtype=torch.long), valid_lens).shape .. parsed-literal:: :class: output torch.Size([2, 100, 24]) .. raw:: html
.. raw:: html
*Decoder* --------- Conforme mostrado em :numref:`fig_transformer`, o *decoder* do transformador é composto de várias camadas idênticas. Cada camada é implementada na seguinte classe ``DecoderBlock``, que contém três subcamadas: autoatenção do *decoder*, atenção do *encoder-decoder* e redes *feed-forward* posicionais. Essas subcamadas empregam uma conexão residual em torno delas seguida pela normalização da camada. Como descrevemos anteriormente nesta seção, na autoatenção do *decoder* de várias *heads* mascarada (a primeira subcamada), as consultas, as chaves e os valores vêm todos das saídas da camada do *decoder* anterior. Ao treinar modelos de sequência para sequência, os tokens em todas as posições (etapas de tempo) da sequência de saída são conhecidos. No entanto, durante a predição, a sequência de saída é gerada token por token; assim, em qualquer etapa de tempo do *decoder*, apenas os tokens gerados podem ser usados ​​na autoatenção do *decoder*. Para preservar a auto-regressão no *decoder*, sua autoatenção mascarada especifica ``dec_valid_lens`` para que qualquer consulta atenda apenas a todas as posições no *decoder* até a posição de consulta. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python class DecoderBlock(nn.Block): # The `i`-th block in the decoder def __init__(self, num_hiddens, ffn_num_hiddens, num_heads, dropout, i, **kwargs): super(DecoderBlock, self).__init__(**kwargs) self.i = i self.attention1 = d2l.MultiHeadAttention(num_hiddens, num_heads, dropout) self.addnorm1 = AddNorm(dropout) self.attention2 = d2l.MultiHeadAttention(num_hiddens, num_heads, dropout) self.addnorm2 = AddNorm(dropout) self.ffn = PositionWiseFFN(ffn_num_hiddens, num_hiddens) self.addnorm3 = AddNorm(dropout) def forward(self, X, state): enc_outputs, enc_valid_lens = state[0], state[1] # During training, all the tokens of any output sequence are processed # at the same time, so `state[2][self.i]` is `None` as initialized. # When decoding any output sequence token by token during prediction, # `state[2][self.i]` contains representations of the decoded output at # the `i`-th block up to the current time step if state[2][self.i] is None: key_values = X else: key_values = np.concatenate((state[2][self.i], X), axis=1) state[2][self.i] = key_values if autograd.is_training(): batch_size, num_steps, _ = X.shape # Shape of `dec_valid_lens`: (`batch_size`, `num_steps`), where # every row is [1, 2, ..., `num_steps`] dec_valid_lens = np.tile(np.arange(1, num_steps + 1, ctx=X.ctx), (batch_size, 1)) else: dec_valid_lens = None # Self-attention X2 = self.attention1(X, key_values, key_values, dec_valid_lens) Y = self.addnorm1(X, X2) # Encoder-decoder attention. Shape of `enc_outputs`: # (`batch_size`, `num_steps`, `num_hiddens`) Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens) Z = self.addnorm2(Y, Y2) return self.addnorm3(Z, self.ffn(Z)), state .. raw:: html
.. raw:: html
.. code:: python class DecoderBlock(nn.Module): # The `i`-th block in the decoder def __init__(self, key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, dropout, i, **kwargs): super(DecoderBlock, self).__init__(**kwargs) self.i = i self.attention1 = d2l.MultiHeadAttention( key_size, query_size, value_size, num_hiddens, num_heads, dropout) self.addnorm1 = AddNorm(norm_shape, dropout) self.attention2 = d2l.MultiHeadAttention( key_size, query_size, value_size, num_hiddens, num_heads, dropout) self.addnorm2 = AddNorm(norm_shape, dropout) self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens) self.addnorm3 = AddNorm(norm_shape, dropout) def forward(self, X, state): enc_outputs, enc_valid_lens = state[0], state[1] # During training, all the tokens of any output sequence are processed # at the same time, so `state[2][self.i]` is `None` as initialized. # When decoding any output sequence token by token during prediction, # `state[2][self.i]` contains representations of the decoded output at # the `i`-th block up to the current time step if state[2][self.i] is None: key_values = X else: key_values = torch.cat((state[2][self.i], X), axis=1) state[2][self.i] = key_values if self.training: batch_size, num_steps, _ = X.shape # Shape of `dec_valid_lens`: (`batch_size`, `num_steps`), where # every row is [1, 2, ..., `num_steps`] dec_valid_lens = torch.arange( 1, num_steps + 1, device=X.device).repeat(batch_size, 1) else: dec_valid_lens = None # Self-attention X2 = self.attention1(X, key_values, key_values, dec_valid_lens) Y = self.addnorm1(X, X2) # Encoder-decoder attention. Shape of `enc_outputs`: # (`batch_size`, `num_steps`, `num_hiddens`) Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens) Z = self.addnorm2(Y, Y2) return self.addnorm3(Z, self.ffn(Z)), state .. raw:: html
.. raw:: html
Para facilitar as operações de produtos escalonados na atenção do *encoder-decoder* e operações de adição nas conexões residuais, a dimensão do recurso (``num_hiddens``) do *decoder* é a mesma do *encoder*. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python decoder_blk = DecoderBlock(24, 48, 8, 0.5, 0) decoder_blk.initialize() X = np.ones((2, 100, 24)) state = [encoder_blk(X, valid_lens), valid_lens, [None]] decoder_blk(X, state)[0].shape .. parsed-literal:: :class: output (2, 100, 24) .. raw:: html
.. raw:: html
.. code:: python decoder_blk = DecoderBlock(24, 24, 24, 24, [100, 24], 24, 48, 8, 0.5, 0) decoder_blk.eval() X = torch.ones((2, 100, 24)) state = [encoder_blk(X, valid_lens), valid_lens, [None]] decoder_blk(X, state)[0].shape .. parsed-literal:: :class: output torch.Size([2, 100, 24]) .. raw:: html
.. raw:: html
Agora construímos todo o *decoder* do transformador composto por instâncias ``num_layers`` de\ ``DecoderBlock``. No final, uma camada totalmente conectada calcula a previsão para todos os possíveis tokens de saída ``vocab_size``. Ambos os pesos de autoatenção do *decoder* e os pesos de atenção do *encoder-decoder* são armazenados para visualização posterior. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python class TransformerDecoder(d2l.AttentionDecoder): def __init__(self, vocab_size, num_hiddens, ffn_num_hiddens, num_heads, num_layers, dropout, **kwargs): super(TransformerDecoder, self).__init__(**kwargs) self.num_hiddens = num_hiddens self.num_layers = num_layers self.embedding = nn.Embedding(vocab_size, num_hiddens) self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout) self.blks = nn.Sequential() for i in range(num_layers): self.blks.add( DecoderBlock(num_hiddens, ffn_num_hiddens, num_heads, dropout, i)) self.dense = nn.Dense(vocab_size, flatten=False) def init_state(self, enc_outputs, enc_valid_lens, *args): return [enc_outputs, enc_valid_lens, [None] * self.num_layers] def forward(self, X, state): X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens)) self._attention_weights = [[None] * len(self.blks) for _ in range (2)] for i, blk in enumerate(self.blks): X, state = blk(X, state) # Decoder self-attention weights self._attention_weights[0][ i] = blk.attention1.attention.attention_weights # Encoder-decoder attention weights self._attention_weights[1][ i] = blk.attention2.attention.attention_weights return self.dense(X), state @property def attention_weights(self): return self._attention_weights .. raw:: html
.. raw:: html
.. code:: python class TransformerDecoder(d2l.AttentionDecoder): def __init__(self, vocab_size, key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout, **kwargs): super(TransformerDecoder, self).__init__(**kwargs) self.num_hiddens = num_hiddens self.num_layers = num_layers self.embedding = nn.Embedding(vocab_size, num_hiddens) self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout) self.blks = nn.Sequential() for i in range(num_layers): self.blks.add_module("block"+str(i), DecoderBlock(key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, dropout, i)) self.dense = nn.Linear(num_hiddens, vocab_size) def init_state(self, enc_outputs, enc_valid_lens, *args): return [enc_outputs, enc_valid_lens, [None] * self.num_layers] def forward(self, X, state): X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens)) self._attention_weights = [[None] * len(self.blks) for _ in range (2)] for i, blk in enumerate(self.blks): X, state = blk(X, state) # Decoder self-attention weights self._attention_weights[0][ i] = blk.attention1.attention.attention_weights # Encoder-decoder attention weights self._attention_weights[1][ i] = blk.attention2.attention.attention_weights return self.dense(X), state @property def attention_weights(self): return self._attention_weights .. raw:: html
.. raw:: html
Treinamento ----------- Vamos instanciar um modelo de *encoder-decoder* seguindo a arquitetura do transformador. Aqui nós especificamos que tanto o *encoder* do transformador quanto o *decoder* do transformador têm 2 camadas usando a atenção de 4 *heads*. Semelhante a :numref:`sec_seq2seq_training`, nós treinamos o modelo do transformador para aprendizado de sequência para sequência no conjunto de dados de tradução automática inglês-francês. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python num_hiddens, num_layers, dropout, batch_size, num_steps = 32, 2, 0.1, 64, 10 lr, num_epochs, device = 0.005, 200, d2l.try_gpu() ffn_num_hiddens, num_heads = 64, 4 train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps) encoder = TransformerEncoder( len(src_vocab), num_hiddens, ffn_num_hiddens, num_heads, num_layers, dropout) decoder = TransformerDecoder( len(tgt_vocab), num_hiddens, ffn_num_hiddens, num_heads, num_layers, dropout) net = d2l.EncoderDecoder(encoder, decoder) d2l.train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device) .. parsed-literal:: :class: output loss 0.030, 2578.5 tokens/sec on gpu(0) .. figure:: output_transformer_5722f1_120_1.svg .. raw:: html
.. raw:: html
.. code:: python num_hiddens, num_layers, dropout, batch_size, num_steps = 32, 2, 0.1, 64, 10 lr, num_epochs, device = 0.005, 200, d2l.try_gpu() ffn_num_input, ffn_num_hiddens, num_heads = 32, 64, 4 key_size, query_size, value_size = 32, 32, 32 norm_shape = [32] train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps) encoder = TransformerEncoder( len(src_vocab), key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout) decoder = TransformerDecoder( len(tgt_vocab), key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout) net = d2l.EncoderDecoder(encoder, decoder) d2l.train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device) .. parsed-literal:: :class: output loss 0.031, 5006.3 tokens/sec on cuda:0 .. figure:: output_transformer_5722f1_123_1.svg .. raw:: html
.. raw:: html
Após o treinamento, nós usamos o modelo do transformador para traduzir algumas frases em inglês para o francês e calcular suas pontuações BLEU. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .'] fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .'] for eng, fra in zip(engs, fras): translation, dec_attention_weight_seq = d2l.predict_seq2seq( net, eng, src_vocab, tgt_vocab, num_steps, device, True) print(f'{eng} => {translation}, ', f'bleu {d2l.bleu(translation, fra, k=2):.3f}') .. parsed-literal:: :class: output go . => va !, bleu 1.000 i lost . => j'ai perdu ., bleu 1.000 he's calm . => il est calme !, bleu 0.783 i'm home . => je suis chez moi suis bien ., bleu 0.640 .. raw:: html
.. raw:: html
.. code:: python engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .'] fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .'] for eng, fra in zip(engs, fras): translation, dec_attention_weight_seq = d2l.predict_seq2seq( net, eng, src_vocab, tgt_vocab, num_steps, device, True) print(f'{eng} => {translation}, ', f'bleu {d2l.bleu(translation, fra, k=2):.3f}') .. parsed-literal:: :class: output go . => va !, bleu 1.000 i lost . => j’ai perdu ., bleu 0.687 he's calm . => il est calme ., bleu 1.000 i'm home . => je suis chez moi ., bleu 1.000 .. raw:: html
.. raw:: html
Vamos visualizar os pesos de atenção do transformador ao traduzir a última frase em inglês para o francês. A forma dos pesos de autoatenção do *encoder* é (número de camadas do codificador, número de *heads* de atenção, ``num_steps`` ou número de consultas,\ ``num_steps`` ou número de pares de valores-chave). .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python enc_attention_weights = np.concatenate(net.encoder.attention_weights, 0).reshape((num_layers, num_heads, -1, num_steps)) enc_attention_weights.shape .. parsed-literal:: :class: output (2, 4, 10, 10) .. raw:: html
.. raw:: html
.. code:: python enc_attention_weights = torch.cat(net.encoder.attention_weights, 0).reshape((num_layers, num_heads, -1, num_steps)) enc_attention_weights.shape .. parsed-literal:: :class: output torch.Size([2, 4, 10, 10]) .. raw:: html
.. raw:: html
Na autoatenção do *encoder*, tanto as consultas quanto as chaves vêm da mesma sequência de entrada. Como os tokens de preenchimento não têm significado, com o comprimento válido especificado da sequência de entrada, nenhuma consulta atende às posições dos tokens de preenchimento. A seguir, duas camadas de pesos de atenção de várias cabeças são apresentadas linha por linha. Cada *head* participa independentemente com base em subespaços de representação separados de consultas, chaves e valores. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python d2l.show_heatmaps( enc_attention_weights, xlabel='Key positions', ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)], figsize=(7, 3.5)) .. figure:: output_transformer_5722f1_147_0.svg .. raw:: html
.. raw:: html
.. code:: python d2l.show_heatmaps( enc_attention_weights.cpu(), xlabel='Key positions', ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)], figsize=(7, 3.5)) .. figure:: output_transformer_5722f1_150_0.svg .. raw:: html
.. raw:: html
Para visualizar os pesos de autoatenção do *decoder* e os pesos de atenção do *encoder-decoder*, precisamos de mais manipulações de dados. Por exemplo, preenchemos os pesos de atenção mascarados com zero. Observe que os pesos de atenção do *decoder* e os pesos de atenção do *encoder-decoder* têm as mesmas consultas: o token de início de sequência seguido pelos tokens de saída. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python dec_attention_weights_2d = [np.array(head[0]).tolist() for step in dec_attention_weight_seq for attn in step for blk in attn for head in blk] dec_attention_weights_filled = np.array( pd.DataFrame(dec_attention_weights_2d).fillna(0.0).values) dec_attention_weights = dec_attention_weights_filled.reshape((-1, 2, num_layers, num_heads, num_steps)) dec_self_attention_weights, dec_inter_attention_weights = \ dec_attention_weights.transpose(1, 2, 3, 0, 4) dec_self_attention_weights.shape, dec_inter_attention_weights.shape .. parsed-literal:: :class: output ((2, 4, 9, 10), (2, 4, 9, 10)) .. raw:: html
.. raw:: html
.. code:: python dec_attention_weights_2d = [head[0].tolist() for step in dec_attention_weight_seq for attn in step for blk in attn for head in blk] dec_attention_weights_filled = torch.tensor( pd.DataFrame(dec_attention_weights_2d).fillna(0.0).values) dec_attention_weights = dec_attention_weights_filled.reshape((-1, 2, num_layers, num_heads, num_steps)) dec_self_attention_weights, dec_inter_attention_weights = \ dec_attention_weights.permute(1, 2, 3, 0, 4) dec_self_attention_weights.shape, dec_inter_attention_weights.shape .. parsed-literal:: :class: output (torch.Size([2, 4, 6, 10]), torch.Size([2, 4, 6, 10])) .. raw:: html
.. raw:: html
Devido à propriedade auto-regressiva da autoatenção do *decoder* nenhuma consulta atende aos pares de valores-chave após a posição da consulta. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python # Plus one to include the beginning-of-sequence token d2l.show_heatmaps( dec_self_attention_weights[:, :, :, :len(translation.split()) + 1], xlabel='Key positions', ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)], figsize=(7, 3.5)) .. figure:: output_transformer_5722f1_165_0.svg .. raw:: html
.. raw:: html
.. code:: python # Plus one to include the beginning-of-sequence token d2l.show_heatmaps( dec_self_attention_weights[:, :, :, :len(translation.split()) + 1], xlabel='Key positions', ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)], figsize=(7, 3.5)) .. figure:: output_transformer_5722f1_168_0.svg .. raw:: html
.. raw:: html
Semelhante ao caso da autoatenção do *encoder*, por meio do comprimento válido especificado da sequência de entrada, nenhuma consulta da sequência de saída atende a esses tokens de preenchimento da sequência de entrada. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python d2l.show_heatmaps( dec_inter_attention_weights, xlabel='Key positions', ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)], figsize=(7, 3.5)) .. figure:: output_transformer_5722f1_174_0.svg .. raw:: html
.. raw:: html
.. code:: python d2l.show_heatmaps( dec_inter_attention_weights, xlabel='Key positions', ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)], figsize=(7, 3.5)) .. figure:: output_transformer_5722f1_177_0.svg .. raw:: html
.. raw:: html
Embora a arquitetura do transformador tenha sido proposta originalmente para o aprendizado de sequência a sequência, como descobriremos mais tarde neste livro, ou o *encoder* do transformador ou o *decoder* do transformador geralmente é usado individualmente para diferentes tarefas de *deep learning*. Resumo ------ - O transformador é uma instância da arquitetura do *encoder-decoder*, embora o *encoder* ou *decoder* possam ser usados individualmente na prática. - No transformador, a autoatenção com várias *heads* é usada para representar a sequência de entrada e a sequência de saída, embora o *decoder* tenha que preservar a propriedade auto-regressiva por meio de uma versão mascarada. - Ambas as conexões residuais e a normalização da camada no transformador são importantes para treinar um modelo muito profundo. - A rede *feed-forward* posicional no modelo do transformador transforma a representação em todas as posições de sequência usando o mesmo MLP. Exercícios ---------- 1. Treine um transformador mais profundo nos experimentos. Como isso afeta a velocidade de treinamento e o desempenho da tradução? 2. É uma boa ideia substituir a atenção do produto escalonado com atenção aditiva no transformador? Por quê? 3. Para modelagem de linguagem, devemos usar o *encoder* do transformador, o *decoder* ou ambos? Como projetar este método? 4. Quais podem ser os desafios para os transformadores se as sequências de entrada forem muito longas? Por quê? 5. Como melhorar a eficiência computacional e de memória de transformadores? Dica: você pode consultar o artigo de pesquisa de Tay et al. :cite:`Tay.Dehghani.Bahri.ea.2020`. 6. Como podemos projetar modelos baseados em transformadores para tarefas de classificação de imagens sem usar CNNs? Dica: você pode consultar o transformador de visão :cite:`Dosovitskiy.Beyer.Kolesnikov.ea.2021`. .. raw:: html
mxnetpytorch
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
.. raw:: html