9.7 El aprendizaje de secuencia a secuencia (seq2seq) realiza la traducción automática del inglés al francés

9.7 Aprendizaje de secuencia a secuencia (seq2seq): una documentación práctica sobre aprendizaje profundo 2.0.0

notas personales

Agregar la versión del mecanismo de atención más tarde

1. Lea el conjunto de datos, o el vocabulario del diccionario, con vocabulario en inglés y francés como

tren_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)

Diccionario de inglés src_vocab, utilizado como codificador de etimología y como enc_X

Diccionario francés tgt_vocab, utilizado como etimología decodificadora y como Y (obtenido dec_X después de la enseñanza obligatoria)

2. El proceso simplificado es:

a. Primero entrene, después de que enc_X pase el proceso seq2seq (red con procesamiento de codificador y decodificador en el código), obtenga Y_hat durante el entrenamiento y use Y_hat y real Y como una función de pérdida, y luego haga una función de pérdida para entrenar los parámetros. Haga que enc_X se acerque más a la traducción de Y entrenando todos los conjuntos de datos de train_iter

b. Luego, todos los parámetros entrenados (el estado del entrenamiento también se conservará) se usan para la predicción. Por ejemplo, traduzca la oración hola palabra. Continúe colocándolo en la red del codificador para su procesamiento y obtenga los estados después. el final de la palabra Poner estados[-1] Sacarlo y ponerlo en el decodificador

En este momento, es el modo de predicción. No sabemos qué es Y, así que haga el primer valor de dec_X de <bos>, póngalo con los estados [-1] y obtenga el primer valor predicho y1 (el primero es obtener o1, dentro del decodificador Después de que la capa densa fusione las características, es y1) y h1 (el primer estado después del procesamiento del decodificador), luego coloque y1 y h1 para obtener y2 y h2, y siga recorriendo hasta el yn obtenido es <eos>, en este momento se acabó

Preguntas y respuestas: (suposición personal)

1.q: ¿Por qué el decodificador puede combinar dec_X(X) con el estado final de los estados [-1]?

a: Cierto estado (ht) también es una combinación lineal de ht-1 y xt en el momento anterior o una combinación y luego la función de activación se vuelve a no linealizar.El estado también contiene la información de X. La intersección puede no ser demasiado grande pero los parámetros finales están entrenados puede encajar bien

2. Los contextos en inglés y francés son diferentes, ¿por qué los estados pueden usarse indistintamente?

a: Tiene razón en que la información contextual es diferente en inglés y francés. En las tareas de traducción automática, la información contextual es diferente para los diferentes idiomas de origen y de destino porque involucran diferentes idiomas y contextos.

En el modelo de secuencia a secuencia, la tarea del codificador es codificar la oración del idioma de origen en un vector de contexto, que contiene la información semántica y la información de contexto de toda la oración del idioma de origen. Sin embargo, este vector de contexto no transfiere directamente la información contextual de la oración en el idioma de origen a la oración en el idioma de destino. En cambio, el codificador alimenta cada token de la oración del idioma de origen en el modelo uno por uno y produce un estado oculto en cada paso de tiempo que contiene información contextual y semántica para el token actual. Luego, el decodificador usa estos estados junto con la salida del paso de tiempo anterior para predecir el próximo token en la oración del idioma de destino.

Aunque la información contextual en los idiomas de origen y de destino es diferente, en un modelo de secuencia a secuencia, el codificador adquiere esta información procesando tokens en las oraciones del idioma de origen uno por uno en cada paso de tiempo. Estos estados contienen la información semántica y contextual de la oración, y se utilizan para generar un vector de contexto, que puede ser utilizado por el decodificador para guiar el proceso de generación del idioma de destino. Por lo tanto, aunque la información contextual de los idiomas de origen y de destino son diferentes, se pueden manejar razonablemente en el modelo de secuencia a secuencia para garantizar que las traducciones generadas se ajusten a la información semántica y contextual de los idiomas de origen y de destino. Esta oración se puede procesar razonablemente significa que los parámetros finales entrenados pueden permitirnos lograr los resultados que queremos y ajustarnos mejor a los resultados.

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


#@save
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

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

state.shape

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


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

#@save
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]))

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

#@save
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

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

#@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)}')

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)


#@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>']]#[go,.,<eos>] [9, 4, 3]
    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)#只能从bos开始因为不知道 翻译在预测中
        # 我们使用具有预测最高可能性的词元,作为解码器在下一时间步的输入
        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

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

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}')

Supongo que te gusta

Origin blog.csdn.net/qq_36632604/article/details/129905293
Recomendado
Clasificación