Introducción al aprendizaje profundo (65) Red neuronal recurrente: aprendizaje de secuencia a secuencia (seq2seq)

prefacio

El contenido principal proviene del enlace 1 del blog. Enlace 2 del blog. Espero que puedas apoyar mucho al autor.
Este artículo se usa para registros para evitar el olvido.

Redes neuronales recurrentes: aprendizaje de secuencia a secuencia (seq2seq)

cursos

máquina traductora

  • Dada una oración en el idioma de origen, tradúzcala automáticamente al idioma de destino
  • Las dos oraciones pueden tener diferentes longitudes.
    inserte la descripción de la imagen aquí

seq2seq

inserte la descripción de la imagen aquí
El codificador es un RNN que lee la oración de entrada

  • Puede ser un decodificador bidireccional
    que usa otro RNN para generar

Detalles del codificador-decodificador

inserte la descripción de la imagen aquí

  • El codificador no tiene salida para RNN
  • El estado oculto del codificador en el último paso de tiempo se utiliza como el estado oculto inicial del decodificador

tren

  • El decodificador usa la oración objetivo como entrada durante el entrenamiento
    inserte la descripción de la imagen aquí

BLEU para medir qué tan buena es la secuencia generada

pn p_npagnes la precisión de predecir todos los n-gramas

  • La secuencia de etiquetas ABCDEF y la secuencia de predicción ABBCD tienen p 1 = 4 / 5 , p 2 = 3 / 4 , p 3 = 1 / 3 , p 4 = 0 p_1=4/5, p_2= 3/4, p_3 = 1 / 3, p_4=0pag1=4/5 ,pag2=3/4 ,pag3=1/3 ,pag4=0
    BLEU se define como
    inserte la descripción de la imagen aquí

Resumir

  • Seq2seq para generar otra oración a partir de una oración
  • Tanto el codificador como el decodificador son RNN
  • Use el estado oculto de tiempo final del codificador al estado oculto del decodificador inicial para completar la transferencia de información
  • BLEU se usa comúnmente para medir la calidad de la secuencia generada

Libro de texto

Como vimos en la sección sobre conjuntos de datos de traducción automática, tanto las secuencias de entrada como las de salida en la traducción automática son de longitud variable. Para resolver este tipo de problema, diseñamos una arquitectura general de "codificador-decodificador" en la sección Arquitectura de codificador-decodificador. En esta sección, usaremos dos redes neuronales recurrentes para el codificador y el decodificador y las aplicaremos a 序列到序列(sequence to sequence,seq2seq)la tarea de aprendizaje de la clase.

Siguiendo los principios de diseño de la arquitectura codificador-decodificador, un codificador de red neuronal recurrente toma una secuencia de longitud variable como entrada y la transforma en un estado oculto de forma fija. En otras palabras, la información de la secuencia de entrada se codifica en el estado oculto del codificador RNN. Para generar continuamente tokens de la secuencia de salida, un decodificador RNN independiente predice el siguiente token en función de la información codificada de la secuencia de entrada y los tokens ya vistos o generados en la secuencia de salida. La siguiente figura muestra cómo utilizar dos redes neuronales recurrentes para el aprendizaje de secuencia a secuencia en la traducción automática.

inserte la descripción de la imagen aquí
En la figura, el " " específico <eos>indica el token de fin de secuencia. El modelo deja de predecir una vez que la secuencia de salida produce este token. En el paso de tiempo de inicialización del decodificador RNN, hay dos decisiones de diseño específicas: Primero, el " " específico <bos>denota el token de inicio de secuencia, que es el primer token de la secuencia de entrada al decodificador. En segundo lugar, el estado oculto del decodificador se inicializa con el estado oculto final del codificador de red neuronal recurrente. Por ejemplo, en el diseño de (Sutskever et al., 2014), se basa en este diseño que la información de codificación de la secuencia de entrada se alimenta al decodificador para generar la secuencia de salida. En algunos otros diseños, como se muestra, el estado oculto final del codificador se incluye como parte de la secuencia de entrada del decodificador en cada paso de tiempo. De manera similar al entrenamiento del modelo de lenguaje en la Sección 8.3, se puede permitir que la etiqueta se convierta en la secuencia de salida original, desde el token de secuencia fuente “ ” “ Ils” “regardent” “.” <bos>hasta el nuevo token de secuencia “Ils” “regardant ” “.”” <eos>" para mover la posición prevista.

A continuación, construiremos el diseño anterior y entrenaremos este modelo de traducción automática basado en el conjunto de datos "inglés-francés".

import collections
import math
import torch
from torch import nn
from d2l import torch as d2l

1 codificador

Técnicamente, un codificador convierte una secuencia de entrada de longitud variable en una variable de contexto cc de forma fijac , y codifica información sobre la secuencia de entrada en esta variable de contexto. Como se muestra, se puede diseñar un codificador utilizando una red neuronal recurrente.

Considere una muestra que consta de una secuencia (el tamaño del lote es 1). Supongamos que la secuencia de entrada es x 1 , … , x T x_1, \ldots, x_TX1,,XT, donde xt x_tXtes el ttth en la secuencia de texto de entradapalabras _ en el paso de tiempottt , la red neuronal recurrente convierte la palabra unidadxt x_tXtEl vector de características de entrada xt \mathbf{x}_tXtht − 1 \mathbf{h} _{t-1}ht 1(es decir, el estado oculto del paso de tiempo anterior) se convierte en ht \mathbf{h}_tht(es decir, el estado oculto del paso actual). usando una función fff para describir la transformación realizada por la capa recurrente de la red neuronal recurrente: ht = f ( xt , ht − 1 ) . \mathbf{h}_t = f(\mathbf{x}_t, \mathbf{h}_{ t-1}).ht=f ( xt,ht 1) En
resumen, el codificador pasa la función seleccionadaqqq , convierte el estado oculto de todos los pasos de tiempo en variables de contexto: c = q ( h 1 , … , h T ) . \mathbf{c} = q(\mathbf{h}_1, \ldots, \mathbf{h} _T).C=q ( ​​h1,,hT) Por ejemplo ,
al elegirq ( h 1 , … , h T ) = h T q(\mathbf{h}_1, \ldots, \mathbf{h}_T) = \mathbf{h}_Tq ( ​​h1,,hT)=hT(como en la figura), la variable de contexto es simplemente el estado oculto h T de la secuencia de entrada en el último paso de tiempo \mathbf{h}_ThT

Hasta ahora, hemos utilizado una red neuronal recurrente unidireccional para diseñar el codificador, donde el estado oculto depende solo de la subsecuencia de entrada, que es desde el comienzo de la secuencia de entrada hasta la posición del paso de tiempo donde se encuentra el estado oculto ( incluyendo el paso de tiempo donde se encuentra el estado oculto). También podemos usar una red neuronal recurrente bidireccional para construir un codificador, donde el estado oculto depende de dos subsecuencias de entrada, las dos subsecuencias son la secuencia anterior y posterior al paso de tiempo en el que se encuentra el estado oculto (incluido el tiempo en el que se encuentra el estado oculto). se encuentra el paso), por lo que el estado oculto codifica la información de toda la secuencia.

Ahora, implementemos el codificador de red neuronal recurrente. Tenga en cuenta que solíamos 嵌入层(embedding layer)obtener el vector de características para cada token en la secuencia de entrada. El peso de la capa de incrustación es una matriz con el número de filas igual al tamaño del vocabulario de entrada ( vocab_size) y el número de columnas igual a la dimensión de los vectores de características ( embed_size). Para cualquier índice de token de entrada iii , la capa de incrustación obtiene la i-ésima parte de lamatrizi filas (a partir de 0) para devolver sus vectores de características. Además, este documento elige una unidad recurrente con compuerta multicapa para implementar el codificador.

class Seq2SeqEncoder(d2l.Encoder):
    """用于序列到序列学习的循环神经网络编码器"""
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 dropout=0, **kwargs):
        super(Seq2SeqEncoder, self).__init__(**kwargs)
        # 嵌入层
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.rnn = nn.GRU(embed_size, num_hiddens, num_layers,
                          dropout=dropout)

    def forward(self, X, *args):
        # 输出'X'的形状:(batch_size,num_steps,embed_size)
        X = self.embedding(X)
        # 在循环神经网络模型中,第一个轴对应于时间步
        X = X.permute(1, 0, 2)
        # 如果未提及状态,则默认为0
        output, state = self.rnn(X)
        # output的形状:(num_steps,batch_size,num_hiddens)
        # state的形状:(num_layers,batch_size,num_hiddens)
        return output, state

Para la descripción de las variables devueltas por la capa de bucle, consulte la sección de Implementación concisa de RNN

A continuación, ejemplificamos la implementación del codificador anterior: utilizamos un codificador de unidades recurrentes de dos capas con 16 unidades ocultas. Dado un pequeño lote de secuencias de entrada X (tamaño de lote 4, pasos de tiempo 7). Después de completar todos los pasos de tiempo, la salida del estado oculto de la última capa es un tensor ( outputdevuelto por la capa recurrente del codificador) con forma (时间步数,批量大小,隐藏单元数).

encoder = Seq2SeqEncoder(vocab_size=10, embed_size=8, num_hiddens=16,
                         num_layers=2)
encoder.eval()
X = torch.zeros((4, 7), dtype=torch.long)
output, state = encoder(X)
output.shape

producción:

torch.Size([7, 4, 16])

Dado que aquí se utilizan unidades recurrentes cerradas, la forma del estado oculto multicapa en el último paso de tiempo es (隐藏层的数量,批量大小,隐藏单元的数量). Si se utiliza una red LSTM, statela información de la celda de memoria también se incluirá en el archivo .

state.shape

producción:

torch.Size([2, 4, 16])

2 decodificadores

Como se mencionó anteriormente, la variable de contexto cc generada por el codificadorc para toda la secuencia de entradax 1 , … , x T x_1, \ldots, x_TX1,,XTpara codificar. La secuencia de salida y 1 , y 2 , … , y T ′ y_1, y_2, \ldots, y_{T'} del conjunto de datos de entrenamientoy1,y2,,yT′′, para cada paso de tiempo t ′ t't (con secuencia de entrada o paso de tiempo del codificadorttt es diferente), el decodificador emiteyt ′ y_{t'}yt′′La probabilidad de depende de la subsecuencia de salida anterior y 1 , … , yt ′ − 1 y_1, \ldots, y_{t'-1}y1,,yt -1y la variable de contexto c \mathbf{c}c, 即P ( yt ′ ∣ y 1 , … , yt ′ − 1 , c ) P(y_{t'} \mid y_1, \ldots, y_{t'-1}, \mathbf{c})P(yt′′y1,,yt -1,c )

Para modelar esta probabilidad condicional en secuencias, podemos usar otra red neuronal recurrente como decodificador. En cualquier momento, paso t ′ t^\prime en la secuencia de salidat , la RNN generaráyt ′ − 1 y_{t^\prime-1}yt -1y la variable de contexto c \mathbf{c}c como su entrada, y luego combínelos con el estado oculto anteriorst ′ − 1 \mathbf{s}_{t^\prime-1}st -1Convertir a estado oculto st ′ \mathbf{s}_{t^\prime}st′′. Por lo tanto, la función gg se puede utilizarg para representar la transformación de la capa oculta del decodificador: st ′ = g ( yt ′ − 1 , c , st ′ − 1 ) . \mathbf{s}_{t^\prime} = g(y_{t^ \prime -1}, \mathbf{c}, \mathbf{s}_{t^\prime-1}).st′′=g ( yt -1,c ,st -1)
Después de obtener el estado oculto del decodificador, podemos usar la capa de salida y la operación softmax para calcular en el paso de tiempo t ′ t^\primet salidayt ′ y_{t^\prime}yt′′La distribución de probabilidad condicional P ( yt ′ ∣ y 1 , … , yt ′ − 1 , c ) P(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \mathbf{ C})P(yt′′y1,,yt -1,c )

De acuerdo con la figura anterior, cuando implementamos el decodificador, usamos directamente el estado oculto del codificador en el último paso de tiempo para inicializar el estado oculto del decodificador. Esto requiere que el codificador y decodificador implementado mediante redes neuronales recurrentes tengan el mismo número de capas y unidades ocultas. Para contener más información sobre la secuencia de entrada codificada, las variables de contexto se concatenan con la entrada del decodificador en todos los pasos de tiempo. Para predecir la distribución de probabilidad de los tokens de salida, se utiliza una capa completamente conectada en la última capa del decodificador RNN para transformar el estado oculto.

class Seq2SeqDecoder(d2l.Decoder):
    """用于序列到序列学习的循环神经网络解码器"""
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 dropout=0, **kwargs):
        super(Seq2SeqDecoder, self).__init__(**kwargs)
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers,
                          dropout=dropout)
        self.dense = nn.Linear(num_hiddens, vocab_size)

    def init_state(self, enc_outputs, *args):
        return enc_outputs[1]

    def forward(self, X, state):
        # 输出'X'的形状:(batch_size,num_steps,embed_size)
        X = self.embedding(X).permute(1, 0, 2)
        # 广播context,使其具有与X相同的num_steps
        context = state[-1].repeat(X.shape[0], 1, 1)
        X_and_context = torch.cat((X, context), 2)
        output, state = self.rnn(X_and_context, state)
        output = self.dense(output).permute(1, 0, 2)
        # output的形状:(batch_size,num_steps,vocab_size)
        # state的形状:(num_layers,batch_size,num_hiddens)
        return output, state

A continuación, instanciamos el decodificador con los mismos hiperparámetros que en el codificador mencionado anteriormente. Como podemos ver, la forma de salida del decodificador se convierte en (批量大小,时间步数,词表大小), donde la última dimensión del tensor almacena la distribución de tokens predicha.

decoder = Seq2SeqDecoder(vocab_size=10, embed_size=8, num_hiddens=16,
                         num_layers=2)
decoder.eval()
state = decoder.init_state(encoder(X))
output, state = decoder(X, state)
output.shape, state.shape

producción

(torch.Size([4, 7, 10]), torch.Size([2, 4, 16]))

En resumen, las capas en el modelo de "codificador-decodificador" de red neuronal recurrente anterior se muestran en la siguiente figura.
inserte la descripción de la imagen aquí

3 función de pérdida

En cada paso de tiempo, el decodificador predice la distribución de probabilidad de los tokens de salida. De manera similar a los modelos de lenguaje, las distribuciones se pueden obtener utilizando softmax y se pueden optimizar mediante el cálculo de una función de pérdida de entropía cruzada. Recuerde de la sección sobre conjuntos de datos de traducción automática que se agregan tokens de relleno específicos al final de las secuencias para que las secuencias de diferentes longitudes se puedan cargar en mini lotes de la misma forma. Sin embargo, debemos excluir la predicción de fichas de relleno del cálculo de la función de pérdida.

Para hacer esto, podemos usar la siguiente función sequence_maskpara enmascarar términos irrelevantes anulándolos, de modo que cualquier cálculo posterior de predicciones irrelevantes sea un producto con cero y el resultado sea igual a cero. Por ejemplo, si dos secuencias tienen longitudes efectivas (excluidas las fichas de relleno) de 1 y 2, respectivamente, el primer elemento de la primera secuencia y los elementos restantes después de los dos primeros elementos de la segunda secuencia se borrarán a cero.

def sequence_mask(X, valid_len, value=0):
    """在序列中屏蔽不相关的项"""
    maxlen = X.size(1)
    mask = torch.arange((maxlen), dtype=torch.float32,
                        device=X.device)[None, :] < valid_len[:, None]
    X[~mask] = value
    return X

X = torch.tensor([[1, 2, 3], [4, 5, 6]])
sequence_mask(X, torch.tensor([1, 2]))

producción

tensor([[1, 0, 0],
        [4, 5, 0]])

También podemos usar esta función para enmascarar todos los términos en los últimos ejes. Estos elementos también se pueden reemplazar con valores especificados distintos de cero si se desea.

X = torch.ones(2, 3, 4)
sequence_mask(X, torch.tensor([1, 2]), value=-1)

producción

tensor([[[ 1.,  1.,  1.,  1.],
         [-1., -1., -1., -1.],
         [-1., -1., -1., -1.]],

        [[ 1.,  1.,  1.,  1.],
         [ 1.,  1.,  1.,  1.],
         [-1., -1., -1., -1.]]])

Ahora podemos enmascarar predicciones irrelevantes extendiendo la función de pérdida de entropía cruzada softmax. Inicialmente, las máscaras para todos los tokens previstos se establecen en 1. Una vez que se proporciona una longitud válida, la máscara correspondiente al token de relleno se establecerá en 0. Finalmente, la pérdida de todos los tokens se multiplica por la máscara para filtrar las predicciones irrelevantes producidas por tokens de relleno en la pérdida.

class MaskedSoftmaxCELoss(nn.CrossEntropyLoss):
    """带遮蔽的softmax交叉熵损失函数"""
    # pred的形状:(batch_size,num_steps,vocab_size)
    # label的形状:(batch_size,num_steps)
    # valid_len的形状:(batch_size,)
    def forward(self, pred, label, valid_len):
        weights = torch.ones_like(label)
        weights = sequence_mask(weights, valid_len)
        self.reduction='none'
        unweighted_loss = super(MaskedSoftmaxCELoss, self).forward(
            pred.permute(0, 2, 1), label)
        weighted_loss = (unweighted_loss * weights).mean(dim=1)
        return weighted_loss

Podemos crear tres secuencias idénticas para verificar la cordura del código y luego especificar que las longitudes efectivas de estas secuencias sean 4, 2 y 0, respectivamente. Como resultado, la pérdida de la primera secuencia debe ser el doble que la de la segunda secuencia y la pérdida de la tercera secuencia debe ser cero.

loss = MaskedSoftmaxCELoss()
loss(torch.ones(3, 4, 10), torch.ones((3, 4), dtype=torch.long),
     torch.tensor([4, 2, 0]))

producción

tensor([2.3026, 1.1513, 0.0000])

4 entrenamiento

En el siguiente proceso de entrenamiento cíclico, como se muestra en la primera figura, el token de inicio de secuencia específico (" <bos>") y la secuencia de salida original (excluyendo el token de fin de secuencia " <eos>") se empalman como la entrada del decodificador. Se llama así 强制教学(teacher forcing)porque la secuencia de salida original (etiquetas de fichas) se introduce en el decodificador. Alternativamente, use el token predicho del paso de tiempo anterior como la entrada actual al decodificador.

#@save
def train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device):
    """训练序列到序列模型"""
    def xavier_init_weights(m):
        if type(m) == nn.Linear:
            nn.init.xavier_uniform_(m.weight)
        if type(m) == nn.GRU:
            for param in m._flat_weights_names:
                if "weight" in param:
                    nn.init.xavier_uniform_(m._parameters[param])

    net.apply(xavier_init_weights)
    net.to(device)
    optimizer = torch.optim.Adam(net.parameters(), lr=lr)
    loss = MaskedSoftmaxCELoss()
    net.train()
    animator = d2l.Animator(xlabel='epoch', ylabel='loss',
                     xlim=[10, num_epochs])
    for epoch in range(num_epochs):
        timer = d2l.Timer()
        metric = d2l.Accumulator(2)  # 训练损失总和,词元数量
        for batch in data_iter:
            optimizer.zero_grad()
            X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch]
            bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0],
                          device=device).reshape(-1, 1)
            dec_input = torch.cat([bos, Y[:, :-1]], 1)  # 强制教学
            Y_hat, _ = net(X, dec_input, X_valid_len)
            l = loss(Y_hat, Y, Y_valid_len)
            l.sum().backward()      # 损失函数的标量进行“反向传播”
            d2l.grad_clipping(net, 1)
            num_tokens = Y_valid_len.sum()
            optimizer.step()
            with torch.no_grad():
                metric.add(l.sum(), num_tokens)
        if (epoch + 1) % 10 == 0:
            animator.add(epoch + 1, (metric[0] / metric[1],))
    print(f'loss {
      
      metric[0] / metric[1]:.3f}, {
      
      metric[1] / timer.stop():.1f} '
        f'tokens/sec on {
      
      str(device)}')

Ahora, en un conjunto de datos de traducción automática, podemos crear y entrenar un modelo de "codificador-decodificador" de red neuronal recurrente para el aprendizaje de secuencia a secuencia.

embed_size, num_hiddens, num_layers, dropout = 32, 32, 2, 0.1
batch_size, num_steps = 64, 10
lr, num_epochs, device = 0.005, 300, d2l.try_gpu()

train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)
encoder = Seq2SeqEncoder(len(src_vocab), embed_size, num_hiddens, num_layers,
                        dropout)
decoder = Seq2SeqDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers,
                        dropout)
net = d2l.EncoderDecoder(encoder, decoder)
train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)

producción

loss 0.019, 11451.2 tokens/sec on cuda:0

inserte la descripción de la imagen aquí

Si el error es el siguiente

Cell In[19], line 5
      2 batch_size, num_steps = 64, 10
      3 lr, num_epochs, device = 0.005, 300, d2l.try_gpu()
----> 5 train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)
      6 encoder = Seq2SeqEncoder(len(src_vocab), embed_size, num_hiddens, num_layers,
      7                         dropout)
      8 decoder = Seq2SeqDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers,
      9                         dropout)

File D:\python\lib\site-packages\d2l\torch.py:883, in load_data_nmt(batch_size, num_steps, num_examples)
    881 def load_data_nmt(batch_size, num_steps, num_examples=600):
    882     """Return the iterator and the vocabularies of the translation dataset."""
--> 883     text = preprocess_nmt(read_data_nmt())
    884     source, target = tokenize_nmt(text, num_examples)
    885     src_vocab = d2l.Vocab(source, min_freq=2,
    886                           reserved_tokens=['<pad>', '<bos>', '<eos>'])

File D:\python\lib\site-packages\d2l\torch.py:828, in read_data_nmt()
    826 data_dir = d2l.download_extract('fra-eng')
    827 with open(os.path.join(data_dir, 'fra.txt'), 'r') as f:
--> 828     return f.read()

UnicodeDecodeError: 'gbk' codec can't decode byte 0xaf in position 33: illegal multibyte sequence


Este es un problema de codificación. Puede especificar encoding='UTF-8' al abrir el archivo.

encontrar la función correspondiente

def read_data_nmt():
    """载入⼊“英语-法语”数据集。"""
    data_dir = d2l.download_extract('fra-eng')
    with open(os.path.join(data_dir, 'fra.txt'), 'r',encoding='UTF-8') as f:
        return f.read()
raw_text = read_data_nmt()

5 predicciones

Para predecir la secuencia de salida token por token, la entrada de cada decodificador en el paso de tiempo actual provendrá del token predicho en el paso de tiempo anterior. Similar al entrenamiento, el token de inicio de secuencia (" <bos>") se alimenta al decodificador en un paso de tiempo inicial. El proceso de predicción se muestra en la siguiente figura: Cuando la predicción de la secuencia de salida encuentra la palabra final de la secuencia (" <eos>"), la predicción finaliza.
inserte la descripción de la imagen aquí
Presentaremos diferentes estrategias de generación de secuencias en la siguiente sección.

#@save
def predict_seq2seq(net, src_sentence, src_vocab, tgt_vocab, num_steps,
                    device, save_attention_weights=False):
    """序列到序列模型的预测"""
    # 在预测时将net设置为评估模式
    net.eval()
    src_tokens = src_vocab[src_sentence.lower().split(' ')] + [
        src_vocab['<eos>']]
    enc_valid_len = torch.tensor([len(src_tokens)], device=device)
    src_tokens = d2l.truncate_pad(src_tokens, num_steps, src_vocab['<pad>'])
    # 添加批量轴
    enc_X = torch.unsqueeze(
        torch.tensor(src_tokens, dtype=torch.long, device=device), dim=0)
    enc_outputs = net.encoder(enc_X, enc_valid_len)
    dec_state = net.decoder.init_state(enc_outputs, enc_valid_len)
    # 添加批量轴
    dec_X = torch.unsqueeze(torch.tensor(
        [tgt_vocab['<bos>']], dtype=torch.long, device=device), dim=0)
    output_seq, attention_weight_seq = [], []
    for _ in range(num_steps):
        Y, dec_state = net.decoder(dec_X, dec_state)
        # 我们使用具有预测最高可能性的词元,作为解码器在下一时间步的输入
        dec_X = Y.argmax(dim=2)
        pred = dec_X.squeeze(dim=0).type(torch.int32).item()
        # 保存注意力权重(稍后讨论)
        if save_attention_weights:
            attention_weight_seq.append(net.decoder.attention_weights)
        # 一旦序列结束词元被预测,输出序列的生成就完成了
        if pred == tgt_vocab['<eos>']:
            break
        output_seq.append(pred)
    return ' '.join(tgt_vocab.to_tokens(output_seq)), attention_weight_seq

6 Evaluación de la secuencia predicha

Podemos evaluar la secuencia predicha comparándola con la secuencia de etiquetas real. Aunque el método propuesto por Papineni BLEU(bilingual evaluation understudy)se usó originalmente para evaluar los resultados de la traducción automática, ahora se ha usado ampliamente para medir la calidad de las secuencias de salida para muchas aplicaciones. En principio, para cualquier n-grama (n-gramas) en la secuencia predicha, la evaluación de BLEU es si este n-grama aparece en la secuencia de la etiqueta.

Definimos BLEU como:

exp ⁡ ( min ⁡ ( 0 , 1 − len label len pred ) ) ∏ norte = 1 kpn 1 / 2 norte , \exp\left(\min\left(0, 1 - \frac{\mathrm{len}_{ \text{etiqueta}}}{\mathrm{len}_{\text{pred}}}\right)\right) \prod_{n=1}^k p_n^{1/2^n},Exp( min( 0 ,1LenantesLenetiqueta) )norte = 1kpagnorte1/ 2n,

Incluya la etiqueta \mathrm{label}_{\text{label}}LenetiquetaIndica el número de tokens en la secuencia de etiquetas y len pred \mathrm{len}_{\text{pred}}LenantesIndica el número de tokens en la secuencia predicha, kkk es lannn -gramos. Además, conpn p_npagnsignifica nnLa precisión del n-grama, que es la relación de dos cantidades: la primera es el n -númeroEl número de n -gramas, el segundo esnnLa relación del número de n -gramas. Específicamente, una secuencia de etiquetas dadaA , B , C , D , E , FA, B, C, D, E, FA , B , C , D , E , F y secuencia de pronósticoA , B , B , C , DA, B, B, C, DA , B , B , C , D , tenemosp 1 = 4/5 p_1 = 4/5pag1=4/5pag 2 = 3 / 4 pag_2 = 3/4pag2=3/4pag 3 = 1 / 3 pag_3 = 1/3pag3=1/3 yp 4 = 0 p_4 = 0pag4=0 _

De acuerdo con la definición de BLEU, BLEU es cuando la secuencia predicha es exactamente la misma que la secuencia etiquetada. Además, desde nnCuanto más largo es el n -grama, más difícil es hacer coincidir, por lo que BLEU es el nnSe asigna mayor peso a la precisión de los n -gramas. Específicamente, cuandopn p_npagnCuando está fijo, pn 1/2 n p_n^{1/2^n}pagnorte1/ 2nseguirá nnaumenta a medida que n crece (el artículo original usapn 1 / n p_n^{1/n}pagnorte1/ norte). Además, dado que la secuencia predicha es más corta, el pn p_n obtenidopagnCuanto mayor sea el valor, el coeficiente antes del término multiplicativo en la ecuación anterior se utiliza para penalizar las secuencias de predicción más cortas. Por ejemplo, cuando k = 2 k=2k=2 , dada la secuencia de etiquetasA, B, C, D, E, FA, B, C, D, E, FA , B , C , D , E , F y secuencia predichaA , BA , BA , B , aunquep 1 = p 2 = 1 p_1 = p_2 = 1pag1=pag2=1 , factor de penalizaciónexp ⁡ ( 1 − 6 / 2 ) ≈ 0.14 \exp(1-6/2) \approx 0.14experiencia ( 16/2 )0.14 bajará el BLEU.

La implementación del código de BLEU es la siguiente.

def bleu(pred_seq, label_seq, k):  #@save
    """计算BLEU"""
    pred_tokens, label_tokens = pred_seq.split(' '), label_seq.split(' ')
    len_pred, len_label = len(pred_tokens), len(label_tokens)
    score = math.exp(min(0, 1 - len_label / len_pred))
    for n in range(1, k + 1):
        num_matches, label_subs = 0, collections.defaultdict(int)
        for i in range(len_label - n + 1):
            label_subs[' '.join(label_tokens[i: i + n])] += 1
        for i in range(len_pred - n + 1):
            if label_subs[' '.join(pred_tokens[i: i + n])] > 0:
                num_matches += 1
                label_subs[' '.join(pred_tokens[i: i + n])] -= 1
        score *= math.pow(num_matches / (len_pred - n + 1), math.pow(0.5, n))
    return score

Finalmente, utilizando el modelo de "codificador-decodificador" de red neuronal recurrente entrenada, se traducen varias oraciones en inglés al francés y se calcula el resultado final de BLEU.

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, attention_weight_seq = predict_seq2seq(
        net, eng, src_vocab, tgt_vocab, num_steps, device)
    print(f'{
      
      eng} => {
      
      translation}, bleu {
      
      bleu(translation, fra, k=2):.3f}')

producción:

go . => va !, bleu 1.000
i lost . => j'ai perdu ., bleu 1.000
he's calm . => il est bon ?, bleu 0.537
i'm home . => je suis chez moi debout ., bleu 0.803

7 Resumen

  • De acuerdo con el diseño de la arquitectura "codificador-decodificador", podemos usar dos redes neuronales recurrentes para diseñar un modelo de aprendizaje de secuencia a secuencia.

  • Al implementar el codificador y el decodificador, podemos usar una red neuronal recurrente multicapa.

  • Podemos usar el enmascaramiento para filtrar cálculos irrelevantes, por ejemplo, al calcular pérdidas.

  • En el entrenamiento de "codificador-decodificador", el método de aprendizaje forzado alimenta la secuencia de salida sin procesar (en lugar del resultado previsto) en el decodificador.

  • BLEU es un método de evaluación de uso común, que evalúa las predicciones midiendo el grado de coincidencia de metasintaxis entre las secuencias de predicción y las secuencias de etiquetas.

Supongo que te gusta

Origin blog.csdn.net/qq_52358603/article/details/128391591
Recomendado
Clasificación