Seq2Seq attention 英译法 代码实现+ 注意力机制详解 (一)

第一篇是代码实现详解
如果想看注意力机制深入理解 请点传送门注意力机制深入理解
请添加图片描述
话不多说上代码

数据加载类

from io import open
import unicodedata
import re
import random
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch import optim

# 注册驱动
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 起始标志
SOS_token = 0
EOS_token = 1


class Lang():
    def __init__(self, name):
        self.name = name
        self.word2index = {}  # 词汇对应下标 词查数词典映射关系保存
        self.index2word = {0: 'SOS', 1: 'EOS'}  # 下标对应词典 数查词映射关系保存
        self.n_words = 2  # 初始化索引,因为01已经被占了,所以初始值还是2

    def addSentence(self, sentence):
        # 添加句子函数,将句子转化为对应的数值序列
        # 这里设定为以空格为分词标准 对句子进行分割,得到对应的下标
        for word in sentence.split(' '):
            self.addWord(word)

    def addWord(self, word):
        '''
        添加词进入词典
        :param word:分词后的词
        :return:
        '''
        if word not in self.word2index:
            self.word2index[word] = self.n_words
            self.index2word[self.n_words] = word
            self.n_words += 1


def demo1_testlong():
    name = 'eng'
    sen = 'I am IronMan'
    dicts = Lang(name)
    dicts.addSentence(sen)
    print(dicts.word2index)
    print(dicts.index2word)
    print(dicts.n_words)


def unicodeToAscii(s):
    '''
    unicode万国码转换为ASCII码作为标准进行处理
    :param s:
    :return:
    '''
    return ''.join(
        c for c in unicodedata.normalize('NFD', s)
        if unicodedata.category(c) != 'Mn'
    )


def normalizesString(s):
    '''
    字符串标准化,去除不需要的特殊符号,归纳分词规则等,清洗数据
    :param s:
    :return:
    '''
    # 字符串统一大小写并去除空白符.特殊符号
    s = unicodeToAscii(s.lower().strip())
    # 将以!.?符号的str钱=前加一个空格
    s = re.sub(r"([.!?])", r" \1", s)
    # 不是正常标点符号和大小写的字符都替换成空格
    s = re.sub(r"[^a-zA-Z.!?]+", r" ", s)
    return s


def readLangs(lang1, lang2, path):
    lines = open(path, encoding='UTF-8').read().strip().split('\n')
    list1 = []
    c = 0
    list1 = [[normalizesString(s) for s in l.split('\t')] for l in lines]
    input_lang = Lang(lang1)
    output_lang = Lang(lang2)
    # print(list1)
    return input_lang, output_lang, list1


def filterPair(p, maxlenght, eng_prefixes):
    return len(p[0].split(' ')) < maxlenght and p[0].startswith(eng_prefixes) and len(p[1].split(' ')) < maxlenght


def filterPairs(pairs):
    eng_prefixes = (
        "i am ", "i m ",
        "he is", "he s ",
        "she is", "she s ",
        "you are", "you re ",
        "we are", "we re ",
        "they are", "they re "
    )
    maxlenght = 10
    return [pair for pair in pairs if filterPair(pair, maxlenght, eng_prefixes)]


def demo04_test_filterPairs(path):
    lang1 = "eng"
    lang2 = "fra"
    input_lang, output_lang, pairs_before = readLangs(lang1, lang2, path)
    print("过滤之前pairs", len(pairs_before))
    pairs_after = filterPairs(pairs_before)
    print("过滤之后pairs", len(pairs_after))

    print("过滤后的pairs前5个:", pairs_after[:5])
    print("过滤后的pairs900-905:", pairs_after[900:905])


# 文本处理 文本-->数值化
def prepareData(lang1, lang2, path):
    input_lang, output_lang, pairs = readLangs(lang1, lang2, path)
    print(len(pairs))
    pairs = filterPairs(pairs)
    print(len(pairs))
    # print(input_lang.n_words)
    for pair in pairs:
        # print(pairs)
        input_lang.addSentence(pair[0])
        output_lang.addSentence(pair[1])
        # print(input_lang.n_words)
        print(f'{pair[0]}--> {pair[1]}')
    return input_lang, output_lang, pairs


# 6-2 文本预处理函数测试:文本---> 数值化
def demo05_test_prepareData(path):
    input_lang, output_lang, pairs = prepareData('eng', 'fra', path)
    print("input_n_words:", input_lang.n_words)  # input_n_words: 2803
    print("output_n_words:", output_lang.n_words)  # output_n_words: 4345

    for i in range(3):
        print('随机选取一个语言对 random.choice(pairs)--->', random.choice(pairs))
    # random.choice(pairs)---> ['he s on his way .', 'il est en route .']route


def tensorfromSentence(lang, sentence):
    # 返回下标
    indexs = [lang.word2index[word] for word in sentence.split(' ')]
    indexs.append(EOS_token)  # 添加结束标志
    # print(indexs)
    return torch.tensor(indexs, dtype=torch.long, device=device).view(-1, 1)


def tensorfromPair(input_lang, output_lang, pair):
    # input_lang, output_lang, paris = prepareData('english', 'french', '../data/eng-fra.txt')
    input_tensor = tensorfromSentence(input_lang, pair[0])
    target_tensor = tensorfromSentence(output_lang, pair[1])
    # 最后返回它们组成的元组
    return input_tensor, target_tensor


if __name__ == '__main__':
    # demo1_testlong()
    # s = 'what are you doing? hhhh iam missing you!'
    # print(normalizesString(s))
    path = '../data/eng-fra.txt'
    # demo04_test_filterPairs(path)
    demo05_test_prepareData(path)
    # input_lang, output_lang, pairs = readLangs('english', 'french', '../data/eng-fra.txt')
    # print("input_lang:", input_lang)
    # print("output_lang:", output_lang)
    # print("过滤之前pairs", len(pairs))
    # pairs_after = filterPairs(pairs)
    # print("过滤之后pairs", len(pairs_after))
    # print("过滤后的pairs前5个:", pairs_after[:5])
    # print("过滤后的pairs900-905:", pairs_after[900:905])
    # input_lang, output_lang, pairs = readLangs('english', 'french', '../data/eng-fra.txt')
    # print(input_lang.n_words)
    # print(output_lang.n_words)
    # tensorfromSentence(input_lang, 'asdaskj aslkjda asdlkn')
    # for i in pairs:
    # print(tensorfromPair(pairs[0]))

模型构建

import random

import torch
import torch.nn as nn
import torch.nn.functional as F

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")


class EncoderRNN(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(EncoderRNN, self).__init__()
        self.input = input_size
        self.hidden = hidden_size
        self.embeding = nn.Embedding(input_size, hidden_size)
        self.gru = nn.GRU(hidden_size, hidden_size)

    def forward(self, input1, hidden):
        # input1是输入张量
        # hidden代表初始化隐藏层张量
        # embedding处理完是一个二维张量,但是gru要求输入三维张量
        output = self.embeding(input1).view(1, 1, -1)
        output, hidden = self.gru(output, hidden)  # 隐层层数
        return output, hidden

    def initHiddenTensor(self):
        # 初始化隐层张量
        return torch.zeros(1, 1, self.hidden, device=device)


def test_encoder():
    import load_data

    path = '../data/eng-fra.txt'
    input_lang, output_lang, pairs = load_data.prepareData('english', 'frensh', path)
    pair_tensor = load_data.tensorfromPair(random.choice(pairs))
    hidden_size = 25
    input_size = 20
    input = pair_tensor[0][0]
    # print(pair_tensor[0].item())
    # print(pair_tensor[0][0], pair_tensor[0][1])
    print(pair_tensor[0].shape)
    print(input)
    # 隐藏层神经元循环次数为25 所以词典不能超过25个词
    # 获得tensor 的值 print(input.item())
    if input.item() > 24:
        input = torch.tensor([6])
    hidden = torch.zeros(1, 1, hidden_size)
    encoder = EncoderRNN(input_size, hidden_size)  # 输入元素特征维度,隐层循环次数
    encoder_output, hidden = encoder(input, hidden)
    print('encoder 结构', encoder)
    print('encoderoutput.shape---->', encoder_output.shape, encoder_output)


class DecoderRNN(nn.Module):
    def __init__(self, hidden_size, output_size):
        super(DecoderRNN, self).__init__()
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.embedding = nn.Embedding(output_size, hidden_size)
        print('embedding: ->', self.embedding)
        self.gru = nn.GRU(hidden_size, hidden_size)
        self.out = nn.Linear(hidden_size, output_size)  # 做尺寸变换
        self.softmax = nn.LogSoftmax(dim=1)

    def forward(self, input, hidden):
        # print('embedding计算输出', self.embedding(input), self.embedding(input).shape)
        # print('embedding形状', self.embedding(input).shape)
        output = self.embedding(input).view(1, 1, -1)
        output = F.relu(output)
        output, hidden = self.gru(output, hidden)
        print('output---->', output.shape)
        output = self.softmax(self.out(output[0]))  # 降维
        # print('somtmax--->', output.shape)
        return output, hidden

    def initHidden(self):
        return torch.zeros(1, 1, self.hidden_size, device=device)


def test_decodeRNN():
    hidden_size = 25
    output_size = 12
    input = torch.tensor([1])  # 输入一个词
    # 一个词,25个特征
    hidden = torch.zeros(1, 1, hidden_size)
    decorator = DecoderRNN(hidden_size, output_size)
    for i in range(3):
        output, hidden = decorator(input, hidden)
        print('*' * 20)
        print('decoderRnn--->', output)
        print('shape', output.shape)


class AttnDecoderRNN(nn.Module):
    def __init__(self, hidden_size, output_size, dropout_p=0.1, max_length=10):
        '''
        :param hidden_size: 解码器GRU输入尺寸,隐层循环次数
        :param output_size: 解码器输出尺寸
        :param dropout_p: 随机失活参数
        :param max_length: 句子最大长度,词个数
        '''
        super(AttnDecoderRNN, self).__init__()
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.dropout_p = dropout_p
        self.max_length = max_length

        self.embedding = nn.Embedding(self.output_size, self.hidden_size)
        # 根据QKV计算规则
        # 1. q与k进行attention权重矩阵计算然后与v做矩阵乘法
        # 三种计算方式
        # QK纵轴拼接,做一次线性变换,使用softmax处理后的结果与v做张量乘法
        # QK纵轴拼接,做一次线性变换,在使用tanh函数激活,然后再进行内部求和,最后使用softmax处理获得的结果再与v乘
        # QK的转置做点积运算,然后除以一个缩放系数,在使用softmax处理获得结果最后与v相乘
        # 当注意力权重矩阵和v都是三维张量时,第一维度代表batch条数时,做bmm运算
        # 2.根据第一步采用的计算方式,如果是拼接方法,则需要将Q与第二步的计算结果再进行拼接
        # 如果是转置点积,一般是自注意力机制Q=K=V 不需要与Q进行拼接,因此第二部的计算方式与第一步采用的方法有关
        # 3.第三步 为了使整个attention结构按照指定尺寸输出,使用线性层作用在第二部的结果上做一个线性表隐患,得到最终对Query 也就是解码器输出的注意力矩阵

        # 采用第一步中第一种计算方式
        # 编码器输出 输入到embedding中创建词向量
        self.attn = nn.Linear(self.hidden_size * 2,
                              self.max_length)  # 第一步拼接 所以拼接完成后尺寸大小是2*hiddensize 第二个参数是句子长度,也就是batch批次数
        self.attn_combine = nn.Linear(self.hidden_size * 2, self.hidden_size)  # 这里是第四步骤所需要的线性层,用于规范输出大小,输入是第三层gru的输出
        # 输入神经元个数,输出神经元个数 输入神经元:cat(cat(Q,K)*V,Q) 所以是Q尺寸Hiddensize*2 输出维度 Hiddensize 使用输出去词典中查词 因为是seq2seq所以要求并不严格
        self.dropout = nn.Dropout(self.dropout_p)
        self.gru = nn.GRU(self.hidden_size, self.hidden_size)
        self.out = nn.Linear(self.hidden_size, self.output_size)

    def forward(self, input, hidden, encoder_outputs):
        '''
        注意力计算顺序
        :param input:输入 编码后的词tensor
        :param hidden: 初始化隐层张量(全连接层权重矩阵)
        :param encoder_outputs: 解码器的输出张量
        :return:output 输出值  hidden最后一层隐层最后一遍循环锁输出的值,atten_weights: 注意力权重矩阵
        '''
        embedded = self.embedding(input).view(1, 1, -1)  # 输入 送进embedding层创建词张量,并从原来的二维提升维度,为了加入注意力计算
        # dropout 防止过拟合
        embedded = self.dropout(embedded)

        # 进行atten计算 第一种
        # QK纵轴拼接,线性变换后送入softmax
        # print(embedded[0].shape, embedded[0])
        # print(hidden[0].shape, hidden[0])
        # Query是 解码器的输出 Key是hidden隐藏层 Value:是编码器的输出
        attn_weight = F.softmax(
            self.attn(
                torch.cat((embedded[0], hidden[0]), 1)
            ), dim=1
        )
        # cat 第一个参数,要拼接的tensor 第二参数 选择维度 0:1:列 以此类推 dim不知道什么意思
        # 第一步的后半部分 权重矩阵与v做乘法计算 并做bmm计算
        attn_applied = torch.bmm(attn_weight.unsqueeze(0), encoder_outputs.unsqueeze(0))
        # unsqueeze 升维度 bam x bma = baa
        # 第二部计算 与Q进行拼接
        output = torch.cat((embedded[0], attn_applied[0]), 1)
        # 第三步 使用线性层作用在第三步的结果上做一个线性变换并扩展维度 送给第四步
        output = self.attn_combine(output).unsqueeze(0)
        output = F.relu(output)
        # 激活后的结果送入gru输入和hidden一起传入其中
        output, hidden = self.gru(output, hidden)
        # 将结果降维并送入softmax得到最终结果
        output = F.log_softmax(self.out(output[0]), dim=1)

        return output, hidden, attn_weight


def test_AttnDecoderRnn():
    hidden_size = 25
    output_size = 10
    input = torch.tensor([3])
    # 一次输入一个字
    hidden = torch.zeros(1, 1, hidden_size)
    # encoderoutputs是encode中每一个时间步输出堆叠而成的
    # 他的形状应该是循环次数*每个词维度数
    encoder_outputs = torch.randn(10, 25)
    # 中间语义张量C
    decoder = AttnDecoderRNN(hidden_size, output_size)
    output, hidden, attnweights = decoder(input, hidden, encoder_outputs)
    print(output.shape)


if __name__ == '__main__':
    # test_AttnDecoderRnn()
    test_decodeRNN()

模型训练

import random

import seq2seq_CreateModel
import load_data
import torch
import torch.nn as nn
import torch.nn.functional as F

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
teacher_forcing_ratio = 0.5


def train(input_tensor, target_tensor, encoder, decoder, encoder_optimizer,
          decoder_optimizer, criterion, maxleght=10):
    '''
    训练函数
    :param teacher_forcing_ratio: 学习率
    :param input_tensor: English输入张量
    :param target_tensor: French目标张量
    :param encoder: 编码器obj
    :param decoder: 解码器obj
    :param encoder_optimizer: 编码器优化器方法
    :param decoder_optimizer: 解码器优化器方法
    :param criterion: 损失函数计算方法
    :param maxleght: 句子最大长度batchsize
    :return:
    '''
    # 初始化编码器 隐层张量
    encoder_hidden = encoder.initHiddenTensor()
    # 编码器解码器优化器梯度清零
    encoder_optimizer.zero_grad()
    decoder_optimizer.zero_grad()

    # 根据原文本 和目标文本张量获取对应长度
    input_length = input_tensor.size(0)  # 输入的词个数
    target_length = target_tensor.size(0)  # 目标的词个数

    # 初始化编码器输出张量  这个是用来存储编码器输出的容器
    encoder_outputs = torch.zeros(maxleght, encoder.hidden, device=device)  # 编码器输出 batch_size x 隐层循环次数
    # 设置初始损失为0
    loss = 0

    # 循环遍历输入张量索引
    for ei in range(input_length):
        # 根据索引取出对应单词张量,因为每个句子长度不一样,所以每次循环都需要根据输入句子长度来决定
        encoder_output, encoder_hidden = encoder(input_tensor[ei], encoder_hidden)  # 输入tensor一个词,还有初始化隐藏层
        # 存储编码器输出
        encoder_outputs[ei] = encoder_output[0, 0]  # 每次输出形状都是[1,1,hidden_size] 一个词,一个句子,隐层循环次数
        # 因为存储容器是二维张量,所以存储的时候降维一下

    # 初始化解码器的第一个输入
    decoder_input = torch.tensor([[load_data.SOS_token]], device=device)
    # 初始化 解码器隐层张量 也就是编码器隐层最后一层最后一遍循环的输出
    decoder_hidden = encoder_hidden

    # 根据随机数与teacher_forcing_ratio对比判断是否使用teacher_forching
    use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False

    # 如果使用teacher_forcing
    if use_teacher_forcing:
        # 循环遍历目标张量索引,如果使用正确目标进行训练
        for di in range(target_length):
            # encoder_outputs 编码器输出容器
            # decoder_input 解码器输入
            # decoder_hidden 编码器最后一个隐层最后一次循环的输出 也就是解码器隐层输入
            decoder_output, decoder_hidden, decoder_attention = decoder(decoder_input, decoder_hidden,
                                                                        encoder_outputs)
            # decoder_output:解码器最终输出 decoder_hidden最后一层隐层最后一遍循环锁输出的值, decoder_attention: 注意力权重矩阵
            loss += criterion(decoder_output, target_tensor[di])
            decoder_input = target_tensor[di]  # 因为如果第一次循环结果就错了
            # 第二次循环会受到很大的影响,那么就用正确的值输入到下次循环,让模型学习
    else:
        # 不使用正确目标值训练
        for di in range(target_length):
            decoder_output, decoder_hidden, decoder_attention = decoder(
                decoder_input, decoder_hidden, encoder_outputs)
            topv, topi = decoder_output.topk(1)  # 取出结果
            loss += criterion(decoder_output, target_tensor[di])
            # 如果输出值是结束符,就终止循环
            if topi.squeeze().item() == load_data.EOS_token:
                break
            # 如果不是终止符 就继续输入
            decoder_input = topi.squeeze().detach()  # 将top1降维作为下一次解码器的输入 因为与计算图无关,所以要从计算图中删除
            # 也就是将上一次的输入作为输入进行训练

    loss.backward()  # 开始反向传播
    encoder_optimizer.step()
    decoder_optimizer.step()
    # 更新完误差后,返回平均损失
    return loss.item() / target_length


def timeSince(since):
    import time
    import math
    now = time.time()
    s = now - since
    m = math.floor(s / 60)
    s -= m * 60
    return '%dm %ds' % (m, s)


def trainAgent(input_lang, output_lang, pairs, encoder, decoder, n_iters, print_every=1000, plot_every=100,
               learning_rate=1e-1):
    '''
    训练代理函数
    :param pairs: 数据集
    :param encoder: 编码器obj
    :param decoder: 解码器obj
    :param n_iters: 总迭代步长
    :param print_every: 打印日志间隔
    :param plot_every: 绘制损失曲线的间隔
    :param learning_rate: 学习率
    :return:
    '''
    import time
    from torch import optim
    start = time.time()
    plot_losses = []  # 损失统计曲线容器
    print_loss_total = 0  # 每个打印日志间隔总损失
    plot_loss_total = 0  # 每个绘图间隔总损失

    encoder_optimizer = optim.SGD(encoder.parameters(), lr=learning_rate)
    decoder_optimizer = optim.SGD(decoder.parameters(), lr=learning_rate)

    # 选择损失函数
    criterion = nn.NLLLoss()
    num = 0
    # 根据步长进行循环
    for iter in range(1, n_iters + 1):
        # 每次从训练集中取出一条作为训练语句
        # training_pair = load_data.tensorfromPair(input_lang, output_lang, pairs)
        # pari = random.choice(pairs)
        pari, num = choise_random_pairs(num, pairs)
        training_pair = load_data.tensorfromPair(input_lang, output_lang, pari)
        input_tensor = training_pair[0]
        target_tensor = training_pair[1]
        loss = train(input_tensor, target_tensor, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion)
        print_loss_total += loss
        plot_loss_total += loss

        # 迭代次数达到日志打印的间隔是
        if iter % print_every == 0:
            print_loss_avg = print_loss_total / print_every
            print_loss_total = 0
            print('%s (%d %d百分比) %.4f' % (timeSince(start), iter, iter / n_iters * 100, print_loss_avg))
        if iter % plot_every == 0:
            plot_loss_avg = plot_loss_total / plot_every
            plot_losses.append(plot_loss_avg)
            plot_loss_total = 0

    import matplotlib.pyplot as plt
    plt.figure()
    plt.plot(plot_losses)
    plt.savefig('./s2sloss.png')
    plt.show()


def choise_random_pairs(num, pairs):
    if num == len(pairs):
        num = 0
    else:
        num += 1
    return pairs[random.randint(0, len(pairs) - 1)], num


if __name__ == '__main__':
    hidden_size = 256
    input_lang, output_lang, pairs = load_data.prepareData('english', 'french', '../data/eng-fra.txt')
    # print(len(pairs)) 训练句子数量10599
    # print(input_lang.n_words, output_lang.n_words) 英文词典数量2803 法文词典4345
    encoder1 = seq2seq_CreateModel.EncoderRNN(input_lang.n_words, hidden_size).to(device)
    print(encoder1)
    attn_decoder1 = seq2seq_CreateModel.AttnDecoderRNN(hidden_size, output_lang.n_words, dropout_p=0.1).to(device)
    print(attn_decoder1)
    # 开始训练
    n_iters = 7500
    print_every = 100
    # for i in pairs:
    # trainAgent(input_lang, output_lang, pairs, encoder1, attn_decoder1, n_iters, print_every)
    trainAgent(input_lang=input_lang, output_lang=output_lang, pairs=pairs, encoder=encoder1,
               decoder=attn_decoder1,
               n_iters=n_iters, print_every=print_every, learning_rate=0.01)

    torch.save(encoder1.state_dict(), './encoder4.pth')
    torch.save(attn_decoder1.state_dict(), './decoder4.pth')

模型评估

import re
import random
import torch
import torch.nn
import torch.nn.functional as F
import seq2seq_CreateModel
import seq2seq_train
import load_data
from io import open
from torch import optim

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
maxlength = 10
# 起始标志
SOS_token = 0
# 结束标志
EOS_token = 1


def evaluate(lang, output_lang, encoder, decoder, sentence, max_length=maxlength):
    '''
    评估函数
    :param output_lang: 解码器实体类对象
    :param lang: 输入实体类对象
    :param encoder: 编码器
    :param decoder: 解码器
    :param sentence: 需要评估的句子
    :param max_length: 句子词最大个数
    :return:
    '''
    with torch.no_grad():
        input_tensor = load_data.tensorfromSentence(lang, sentence)  # 返回tensor 句onehot编码
        input_length = input_tensor.size()[0]
        encoder_hidden = encoder.initHiddenTensor()  # 初始化隐层权重矩阵
        encoder_outputs = torch.zeros(maxlength, encoder.hidden, device=device)  # 缓存容器
        # 编码器循环
        for ei in range(input_length):
            encoder_output, encoder_hidden = encoder(input_tensor[ei], encoder_hidden)
            encoder_outputs[ei] = encoder_output[0, 0]

        decoder_input = torch.tensor([[SOS_token]], device=device)
        decoder_hidden = encoder_hidden
        decoder_words = []
        decoder_attentions = torch.zeros(maxlength, maxlength)  # 初始化注意力权重矩阵
        # 解码器循环
        for di in range(maxlength):
            decoder_output, decoder_hidden, decoder_attention = decoder(decoder_input, decoder_hidden, encoder_outputs)
            # print(decoder_output.shape)
            decoder_attentions[di] = decoder_attention.data  # 将attention结果缓存
            topv, topi = decoder_output.data.topk(1)
            if topi.item() == EOS_token:
                decoder_words.append('<Finall>')
                break
            else:
                decoder_words.append(output_lang.index2word[topi.item()])

            decoder_input = topi.squeeze().detach()  # 将top1降维作为下一次解码器的输入 因为与计算图无关,所以要从计算图中删除

        return decoder_words, decoder_attentions[:di + 1]


def agent_evaluate(encoder, decoder, n, pairs, input_lang, output_lang):
    '''
    测试代理函数
    :param output_lang: 解码器词典对象
    :param input_lang: 编码器词典对象
    :param pairs: 数据对
    :param encoder: 编码器对象
    :param decoder: 解码器对象
    :param n: 测试次数
    :return:
    '''

    for i in range(n):
        pair = random.choice(pairs)
        print('input========>   ', pair[0])
        print('lable---->  ', pair[1])
        output_words, attentions = evaluate(input_lang, output_lang, encoder, decoder, pair[0], maxlength)
        output_sentence = ' '.join(output_words)
        print('预测值: ', output_sentence)
        print('')


def agent_evaluate_onesentence(encoder, decoder, n, sentence, input_lang, output_lang):
    '''
    测试代理函数,输入自定义英文
    :param output_lang: 解码器词典对象
    :param input_lang: 编码器词典对象
    :param pairs: 数据对
    :param encoder: 编码器对象
    :param decoder: 解码器对象
    :param n: 测试次数
    :return:
    '''
    print('input========>   ', sentence)
    output_words, attentions = evaluate(input_lang, output_lang, encoder, decoder, sentence, maxlength)
    output_sentence = ' '.join(output_words)
    print('预测值: ', output_sentence)
    print('')


if __name__ == '__main__':
    hidden = 256
    print('Please wati a minute,it\'s load data and load model......  ')
    input_lang, output_lang, pairs = load_data.prepareData('english', 'french', '../data/eng-fra.txt')
    print(input_lang.n_words, output_lang.n_words)
    path = '/tmp/pycharm_project_270/Pytorch/day_04_Project/transformer/encoder4.pth'
    path2 = '/tmp/pycharm_project_270/Pytorch/day_04_Project/transformer/decoder4.pth'
    encoder = seq2seq_CreateModel.EncoderRNN(input_lang.n_words, hidden).to(device)
    encoder.load_state_dict(torch.load(path))
    decoder = seq2seq_CreateModel.AttnDecoderRNN(hidden, output_lang.n_words, dropout_p=0.1).to(device)
    decoder.load_state_dict(torch.load(path2))
    print('model load up ')

    # 开始测试
    agent_evaluate(encoder, decoder, 500, pairs, input_lang, output_lang)
    # 自定义测试
    # agent_evaluate_onesentence(encoder, decoder, 1, 'we are friend', input_lang, output_lang)
    sentence = "what re you doing ?"
    output_words, attentions = evaluate(input_lang, output_lang, encoder, decoder, sentence=sentence)
    print(output_words, len(output_words))
    print(attentions.shape)
    import matplotlib.pyplot as plt

    plt.matshow(attentions.numpy())
    plt.show()

首先 代码是为了实现英译法 的翻译, 但是过程异常 的艰难,我们看一下数据就知道了请添加图片描述
这个是做AB实验对照组, 不对数据做过滤的情况下,1w条英文法文对,训练下产生的结果.这里埋下一个伏笔, 有一个横亘所有模型的bug我没有发现导致我找了4天都没找到loss降不下去的原因 嘤嘤嘤
请添加图片描述
这个是循环训练7500遍,英文数据集一共就2803句话…没啥可说的
因为是随机抽样 梯度并没有下去

那么是不是学习率的问题, 把0.01改成1e-1请添加图片描述
这家伙,算了把老老实实的 1e-2 7500次
请添加图片描述
训练15000次是什么样的?
请添加图片描述
行吧,误差还是没下去,在3这不走了

那么是不是teacher_forceing 这个参数的影响呢,其他参数不变
这个额参数由原来的0.5 改为0.75
请添加图片描述
貌似提高纠错的能力,对误差有一点点点乐观影响
那么增加训练轮次能不能降低loss呢?请添加图片描述
确实可以降低辖区…但是仅仅提升了0.5的误差,并不是能用的范围, 那么到底是啥到底是啥 让我的误差降低不下去?

原来是…我的解码器输入写成固定的一个词了,我的老天爷请添加图片描述
这不仅不是最离谱的,找了4天的问题,终于找到了,
并且非常离谱的是,仅仅解码器只输入1个词, 它却能学到至少3个词的回复
这里就是注意力机制的强大了 真不是强的一定半点
在这里插入图片描述

我们来看看解决bug后的atten 可视化图和bug前的attn图
在这里插入图片描述
请添加图片描述
5请添加图片描述
这是之前的, 因为bug 导致注意力不准确,但是可以看到,一个词可以关联3个词甚至更多,表明翻译的时候至少会考虑另外两个词与本身词 的关系再翻译
再来看看解决bug后lr=0.02 teacherfource=0.5 epoch=7500次
请添加图片描述
翻译验证
在这里插入图片描述
因为不懂法语,信达雅 这种评估也不会 但是仅仅7500次 已经显而易见的比之前好

那么七万五千次的效果咋样嗯?
请添加图片描述
很低
注意力矩阵咋样呢?
请添加图片描述
很优雅. 注意,这里的注意力图像全部都是对同一句话的翻译结果
在这里插入图片描述
在这里插入图片描述
参数放上

训练的经验总结请添加图片描述

未完待续,下一篇来详解atten注意力机制 自注意力机制和QKV注意力机制在seq2seq中的应用如何理解bmm等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值