07-Seq2Seq英译法案例

发布于:2025-06-30 ⋅ 阅读:(12) ⋅ 点赞:(0)

Seq2Seq英译法案例

1 任务目的:

目的: 给定一段英文,翻译为法文
典型的文本分类(token分类)任务: 每个时间步去预测应该属于哪个法文单词

2 数据格式

  • 注意:两列数据,第一列是英文文本,第二列是法文文本,中间用制表符号"\t"隔开

i am from brazil .  je viens du bresil .
i am from france .  je viens de france .
i am from russia .  je viens de russie .
i am frying fish .  je fais frire du poisson .
i am not kidding .  je ne blague pas .

3 任务实现流程

1. 获取数据:案例中是直接给定的
2. 数据预处理: 脏数据清洗、数据格式转换、数据源Dataset的构造、数据迭代器Dataloader的构造
3. 模型搭建: 编码器和解码器等一系列模型
4. 模型评估(测试)
5. 模型上线---API接口

4 数据预处理

4.1 定义样本清洗函数和构建字典

目的:

样本清洗函数: 将脏数据进行清洗,以免影响模型训练
构建字典:一方面是为了将文本进行数字表示,还有一方面进行解码的时候将预测索引数字映射为真实的文本

样本清洗函数代码实现

# 文本清洗工具函数
def normalizeString(s):
    """字符串规范化函数, 参数s代表传入的字符串"""
    s = s.lower().strip()
    # 在.!?前加一个空格  这里的\1表示第一个分组   正则中的\num
    s = re.sub(r"([.!?])", r" \1", s)
    # s = re.sub(r"([.!?])", r" ", s)
    # 使用正则表达式将字符串中 不是 大小写字母和正常标点的都替换成空格
    s = re.sub(r"[^a-zA-Z.!?]+", r" ", s)
    return s

构建字典代码实现

def my_getdata():
    # 1.读取数据
    with open(data_path, 'r' , encoding='utf-8') as fr:
        sentens_str = fr.read()
    sentences = sentens_str.strip().split('\n')
​
    # 2.构建数据源pair
    my_pairs = [[normalizeString(s) for s in l.split('\t')] for l in sentences]
​
    # 3.1 初始化两个字典
    english_word2index = {"SOS": 0, "EOS": 1}
    english_word_n = 2
    french_word2index =  {"SOS": 0, "EOS": 1}
    french_word_n = 2
    # 3.2遍历my_pairs
    for pair in my_pairs:
        for word in pair[0].split(' '):
            if word not in english_word2index:
                english_word2index[word] = english_word_n
                english_word_n += 1
                # english_word2index[word] = len(english_word2index)
​
        for word in pair[1].split(' '):
            if word not in french_word2index:
                french_word2index[word] = french_word_n
                french_word_n += 1
​
    english_index2word = {v: k for k, v in english_word2index.items()}
    french_index2word = {v: k for k, v in french_word2index.items()}
    return english_word2index, english_index2word, english_word_n, french_word2index, french_index2word, french_word_n, my_pairs
4.2 构建自己的数据源DataSet

目的:

使用Pytorch框架,一般遵从一个规矩:使用DataSet方法构造数据源,来让模型进行使用
构造数据源的过程中:必须继承torch.utils.data.Dataset类,必须构造两个魔法方法:__len__(), __getitem__()
__len__(): 一般返回的是样本的总个数,我们可以直接len(dataset对象)直接就可以获得结果
__getitem__(): 可以根据某个索引取出样本值,我们可以直接用dataset对象[index]来直接获得结果

代码实现:

# 3.构建数据源Dataset
class Seq2SeqDaset(Dataset):
    def __init__(self, my_pairs):
        self.my_pairs = my_pairs
        self.sample_len = len(my_pairs)
​
    def __len__(self):
        return self.sample_len
​
    def __getitem__(self, index):
        # 1.index异常值处理[0, self.sample_len-1]
        index = min(max(index, 0), self.sample_len-1)
​
        # 2. 根据index取出样本数据
        x = self.my_pairs[index][0]
        y = self.my_pairs[index][1]
​
        # 3.进行文本数据数字化的转换
        x1 = [english_word2index[word] for word in x.split(' ')]
        tensor_x = torch.tensor(x1, dtype=torch.long, device=device)
​
        y1 = [french_word2index[word] for word in y.split(' ')]
        y1.append(EOS_token)
        tensor_y = torch.tensor(y1, dtype=torch.long, device=device)
​
        return tensor_x, tensor_y
4.3 构建数据源Dataloader

目的:

为了将Dataset我们上一步构建的数据源,进行再次封装,变成一个迭代器,可以进行for循环,而且,可以自动为我们dataset里面的数据进行增维(bath_size),也可以随机打乱我们的取值顺序

代码实现:

# 4.构建数据迭代器dataloader
def get_dataloader():
    # 1.实例化dataset
    my_dataset = Seq2SeqDaset(my_pairs)
    # 2.实例化dataloader
    my_dataloader = DataLoader(dataset=my_dataset, batch_size=1, shuffle=True)
​
    return my_dataloader

5 模型搭建

5.1 搭建编码器GRU模型
  • 注意事项

GRU模型在实例化的时候,默认batch_first=False,因此,需要小心输入数据的形状
因为: dataloader返回的结果x---》shape--〉[batch_size, seq_len, input_size], 所以课堂上代码和讲义稍微有点不同,讲义是默认的batch_first=False,而我们的代码是batch_first=True,这样做的目的,可以直接承接x的输入。

  • 代码实现

# 5. 构建GRU编码器模型
class EncoderGRU(nn.Module):
    def __init__(self, vocab_size, hidden_size):
        super().__init__()
        # 1.vocab_size代表英文单词的总个数(去重)
        self.vocab_size = vocab_size
        # 2. hidden_size词嵌入维度/隐藏层输出维度(我们让他相等)
        self.hidden_size = hidden_size
        # 3.定义Embedding层,目的:将每个词汇进行向量表示
        self.embed = nn.Embedding(self.vocab_size, self.hidden_size)
        # 4.定义GRU层第一个self.hidden_size实际上是embedding的输出结果词嵌入维度
        # 4.定义GRU层第二个self.hidden_size实是我们指定的GRU模型的输出维度,只不过这里GRU输入和输出一样
        self.gru = nn.GRU(self.hidden_size, self.hidden_size, batch_first=True)
    def forward(self, input, hidden):
        # 1.input-->[1, 6]需要经过embedding--》[1,6, 256]
        input_x = self.embed(input)
        # 2.将input_x和hidden送入GRU模型
        output, hidden = self.gru(input_x, hidden)
        return output, hidden
​
    def inithidden(self):
        return torch.zeros(1, 1, self.hidden_size, device=device)

编码器模型测试

# 测试编码器模型
def test_EncoderGRU():
    # 获取数据
    my_dataloader = get_dataloader()
    # 实例化模型
    my_encoder = EncoderGRU(vocab_size=english_word_n, hidden_size=256)
    my_encoder.to(device)
    # 初始化h0
    h0 = my_encoder.inithidden()
​
    for i, (x, y) in enumerate(my_dataloader):
        print(f'x---》{x.shape}')
        output, hn = my_encoder(x, h0)
        print(f'output--》{output.shape}')
        print(f'hn--》{hn.shape}')
        break
5.2 搭建解码器无Attention模型
  • 代码实现

#6. 构建没有attention的GRU解码器
class DecoderGRU(nn.Module):
    def __init__(self, vocab_size, hidden_size):
        super().__init__()
        # vocab_size代表解码器中法语单词的词汇总量(去重)
        self.vocab_size = vocab_size
        # hidden_size词嵌入维度
        self.hidden_size = hidden_size
        # Embedding层
        self.embed = nn.Embedding(self.vocab_size, self.hidden_size)
​
        # GRU层:这里定义GRU模型的输入和输出形状一样
        self.gru = nn.GRU(self.hidden_size,  self.hidden_size, batch_first=True)
​
        # 定义输出层:判断法语单词属于self.vocab_size里面的哪一个
        self.out = nn.Linear(self.hidden_size, self.vocab_size)
​
        # 定义softmax层
        self.softmax = nn.LogSoftmax(dim=-1)
​
    def forward(self, input, hidden):
        # input输入一般是一个字,解码的时候,是一个字符一个字符解码的
        # 1. input-->[1, 1]-->embeding之后--》[1, 1, hidden_size]-->[1, 1, 256]
        input_x = self.embed(input)
        # 2. relu激活函数使用
        input_x = F.relu(input_x)
        # 3. 将数据送入GRU模型:input_x-->[1,1,256],hidden:[1, 1, 256]
        # output-->[1, 1, 256]
        output, hidden = self.gru(input_x, hidden)
​
        # 4. 将output结果取最后一个词隐藏层输出送入linear层
        # output-->[1, vocab_size]-->[1, 4345]
        output = self.out(output[0])
        return self.softmax(output), hidden
​
    def inithidden(self):
        return torch.zeros(1, 1, self.hidden_size, device=device)

代码测试

# 测试没有attention的解码器
def test_DecoderGRU():
    # 1.实例化dataset
    mydataset = Seq2SeqDaset(my_pairs)
    # 2.实例化dataloader
    my_dataloader = DataLoader(dataset=mydataset, batch_size=1, shuffle=True)
​
    # 3.实例化编码器模型
    my_encoder = EncoderGRU(vocab_size=english_word_n, hidden_size=256)
    # my_encoder = EncoderGRU(vocab_size=english_word_n, hidden_size=256).to(device)
    my_encoder.to(device)
    # 4. 实例化解码器模型
    my_decoder = DecoderGRU(vocab_size=french_word_n, hidden_size=256)
    my_decoder.to(device)
​
    # 5.将数据送入模型
    for x, y in my_dataloader:
        print(x)
        print(f'x--->{x.shape}')
        print(f'y--->{y.shape}')
        print(f'y--->{y}')
        # 5.1将x英文原始输入送入编码器模型得到编码结果;hidden就是C
        output, hidden = my_encoder(input=x, hidden=my_encoder.inithidden())
        # 5.2基于C开始一个字符一个字符的去解码
        for i in range(y.shape[1]):
            # print(f'y[0]-->{y[0]}')
            # print(f'y[0][i]-->{y[0][i]}')
            temp = y[0][i].view(1, -1)
            output, hidden = my_decoder(input=temp, hidden=hidden)
            print(f'output--》{output.shape}')
        break
5.3 搭建解码器带Attention模型
  • 注意事项

带Attention:需要有三个参数:Q、K、V,在本次案例中Q上一时间步预测的真实结果;K:上一时间步隐藏层输出的结果;V代表编码器的输出结果

  • 代码实现

# 7.带attention的解码器
class AttentionDecoderGRU(nn.Module):
    def __init__(self, vocab_size, hidden_size, dropout_p=0.1, max_length=MAX_LENGTH):
        super().__init__()
        # vocab_size:属于解码器端,代表法语的总的单词个数
        self.vocab_size = vocab_size
        # hidden_size:代表词嵌入的维度
        self.hidden_size = hidden_size
        # 随机失活概率
        self.dropout_p = dropout_p
        # 最大句子长度:因为训练语料里面不管英文还是法文最大句子长度都不超过10,我们这里限定最大长度,目的是方便计算注意力
        self.max_length = max_length
​
        # 定义Embedding层
        self.embed = nn.Embedding(self.vocab_size, self.hidden_size)
        # 计算注意力的第一个全连接层:得到权重分数
        self.attn = nn.Linear(self.hidden_size*2, self.max_length)
        # 随机失活层
        self.droupout = nn.Dropout(p=self.dropout_p)
        # 计算注意力的第二个全连接层:让注意力按照指定维度输出
        self.attn_combin = nn.Linear(self.hidden_size*2, self.hidden_size)
        # 定义GRU层
        self.gru = nn.GRU(self.hidden_size, self.hidden_size, batch_first=True)
        # 定义输出层
        self.out = nn.Linear(self.hidden_size, self.vocab_size)
​
        # 定义softmax层
        self.softmax = nn.LogSoftmax(dim=-1)
​
    def forward(self, input, hidden, encoder_output):
        # input-->query--》解码器输入某个词[1, 1]
        # hidden-->key--》上一时间步隐藏层输出[1, 1, 256]
        # encoder_output--->value-->编码器的输出结果[10, 256]--[max_length, 256]
        # 1. 将input送入embedding-->input_x-->[1,1,256]--query(真正)
        input_x = self.embed(input)
        # 1.1对input_x进行dropout
        input_x = self.droupout(input_x)
        # 2. 计算注意力权重分数self.attn_weight-->[1, 10]-->[1, max_length]
        attn_weight = F.softmax(self.attn(torch.cat((input_x[0], hidden[0]), dim=-1)), dim=-1)
        # 3. 将注意力权重和Value相乘:[1, 1,10]*[1,10,256]-->self.attn_applied-->[1, 1,256]
        attn_applied = torch.bmm(attn_weight.unsqueeze(0), encoder_output.unsqueeze(0))
        # 4.将query和self.attn_applied结果拼接之后,再经过线性的变换self.output1-->[1, 1, 256]
        output1 = self.attn_combin(torch.cat((input_x[0], attn_applied[0]), dim=-1)).unsqueeze(0)
        # 5. 经过relu激活函数
        relu_output = F.relu(output1)
        # 6. 将self.relu_output,以及hidden送入GRU模型中-->gru_output-->[1, 1,256]
        gru_output, hidden = self.gru(relu_output , hidden)
​
        # 7.将gru的结果送入输出层,得到最后的预测结果output-->[1, 4345]
        output = self.out(gru_output[0])
        return self.softmax(output), hidden, attn_weight
​
    def inithidden(self):
        return torch.zeros(1, 1, self.hidden_size, device=device)

模型测试

# 测试带attention的解码器
def test_AttenDecoder():
    # 1.实例化dataset
    mydataset = Seq2SeqDaset(my_pairs)
    # 2.实例化dataloader
    my_dataloader = DataLoader(dataset=mydataset, batch_size=1, shuffle=True)
​
    # 3.实例化编码器模型
    my_encoder = EncoderGRU(vocab_size=english_word_n, hidden_size=256)
    # my_encoder = EncoderGRU(vocab_size=english_word_n, hidden_size=256).to(device)
    my_encoder.to(device)
​
   # 4.实例化解码器模型
    my_attenDecoder = AttentionDecoderGRU(vocab_size=french_word_n, hidden_size=256)
    my_attenDecoder.to(device)
​
    #5.循环数据送入模型
    for x, y in my_dataloader:
        print(f'x--》{x.shape}')
        print(f'y--》{y.shape}')
        # 1.将x送入编码器模型得到结果
        h0 = my_encoder.inithidden()
        encoder_output, hidden = my_encoder(input=x, hidden=h0)
        # print(f'encoder_output--》{encoder_output.shape}')
        # 2.将编码的结果进行处理,统一长度,方便计算注意力
        encoder_output_c = torch.zeros(MAX_LENGTH, my_encoder.hidden_size, device=device)
        # print(f'encoder_output_c--》{encoder_output_c.shape}')
​
        # 2.1将真实的编码的输出 结果赋值到encoder_output_c中,多余的都是用0来表示
        for i in range(encoder_output.shape[1]):
            # print(f'encoder_output[0][i]-->{encoder_output[0][i].shape}')
            # print(f'encoder_output[0, i]-->{encoder_output[0, i].shape}')
            encoder_output_c[i] = encoder_output[0][i]
        # 3.测试:进行解码应用
        for j in range(y.shape[1]):
            temp = y[0][j].view(1, -1)
            output, hidden, attn_weight = my_attenDecoder(temp, hidden, encoder_output_c)
            print(f'output--》{output.shape}')
            print(f'hidden--》{hidden.shape}')
            print(f'attn_weight--》{attn_weight.shape}')
            print("*"*80)
        break

6 模型训练

基本过程

1.获取数据
2.构建数据源Dataset
3.构建数据迭代器Dataloader
4.实例化自定义的模型: 编码器模型和解码器模型
5.实例化损失函数对象
6.实例化优化器对象: 编码器优化器和解码器优化器
7.定义打印日志参数
8.开始训练
8.1 实现外层大循环epoch
(可以在这构建数据迭代器Dataloader)
8.2 内部遍历数据迭代球dataloader
8.3 将数据送入模型得到输出结果
8.4 计算损失
8.5 梯度清零: optimizer.zero_grad()
8.6 反向传播: loss.backward()
8.7 参数更新(梯度更新): optimizer.step()
8.8 打印训练日志
9. 保存模型: torch.save(model.state_dict(), "model_path")

6.1 模型训练代码实现

# 8.构建模型的训练函数
def train_seq2seq():
    # 1.实例化dataset
    mydataset = Seq2SeqDaset(my_pairs)
    # 2.实例化dataloader
    my_dataloader = DataLoader(dataset=mydataset, batch_size=1, shuffle=True)
​
    # 3.实例化编码器模型
    my_encoder = EncoderGRU(vocab_size=english_word_n, hidden_size=256)
    # my_encoder = EncoderGRU(vocab_size=english_word_n, hidden_size=256).to(device)
    my_encoder.to(device)
​
    # 4.实例化解码器模型
    my_attenDecoder = AttentionDecoderGRU(vocab_size=french_word_n, hidden_size=256)
    my_attenDecoder.to(device)
​
    # 5.实例化优化器
    encoder_optimizer = optim.Adam(my_encoder.parameters(), lr=mylr)
    decoder_optimizer = optim.Adam(my_attenDecoder.parameters(), lr=mylr)
​
    # 6.实例化损失对象
    crossentropy = nn.NLLLoss()
​
    # 7.定义一个空列表list--》存储损失值,画图
    plot_loss_list = []
​
    # 8. 进入外层循环
    for epoch_idx in range(epochs):
        # 初始化损失值为0
        print_loss_total, plot_loss_total = 0.0, 0.0
        start_time = time.time()
        # 进入内部循环
        for i, (x, y) in enumerate(tqdm(my_dataloader), start=1):
            myloss = Train_Iters(x, y, my_encoder,
                                 my_attenDecoder,encoder_optimizer,
                                 decoder_optimizer,crossentropy)
            # print(f'主训练了函数的myloss--》{myloss}')
            print_loss_total += myloss
            plot_loss_total += myloss
​
            # 打印日志
            # 每隔1000步打印损失
            if i % 10 == 0:
                print_loss_avg = print_loss_total / 1000
​
                print_loss_total = 0
                use_time = time.time() - start_time
                print(f'当前的轮次%d,平均损失%.4f,时间%.2f'%(epoch_idx+1, print_loss_avg.item()*100, use_time))
​
            # 每隔100步保留损失,画图
            if i % 10 == 0:
                plot_loss_avg = plot_loss_total / 100
                # 如果画图报错:放到CPU--》plot_loss_avg.cpu().detach().numpy()
                plot_loss_list.append(plot_loss_avg.cpu().detach().numpy())
                plot_loss_total = 0
​
        # 保存模型
        torch.save(my_encoder.state_dict(), './ai19_model/my_encoder_%s.pth'%(epoch_idx+1))
        torch.save(my_attenDecoder.state_dict(), './ai19_model/my_decoder_%s.pth'%(epoch_idx+1))
​
​
    # 画图
    plt.figure()
    plt.plot(plot_loss_list)
    plt.savefig("./ai19_seq2se1_loss.png")
    plt.show()
​
    return plot_loss_list

6.2 模型训练内部迭代函数代码实现

# 定义内部迭代函数
def Train_Iters(x, y, my_encoder, my_attenDecoder, encoder_optimizer, decoder_optimizer, crossentropy):
    # 1.将x送入编码器得到编码的结果
    # print(f'x-->{x.shape}')
    # print(f'y-->{y.shape}')
    h0 = my_encoder.inithidden()
    encoder_output, encoder_hidden = my_encoder(x, h0)
    # print(f'encoder_output--》{encoder_output.shape}')
    # print(f'encoder_hidden--》{encoder_hidden.shape}')
    # 2. 定义解码器的参数
    # 2.1 中间语意张量C:value
    encoder_output_c = torch.zeros(MAX_LENGTH, my_encoder.hidden_size, device=device)
    for i in range(x.shape[1]):
        encoder_output_c[i] = encoder_output[0][i]
    # 2.2 解码器的初始化的hidden, key
    decoder_hidden = encoder_hidden
    # 2.3 解码器的初始化输出:query
    input_y = torch.tensor([[SOS_token]], dtype=torch.long, device=device)
​
    # 3.定义一个初始化的损失
    my_loss = 0.0
    # 4.选择性的使用teacher_forcing策略
    teacher_forcing = True if random.random() < teacher_forcing_ratio else False
    # 5.开始计算损失
    if teacher_forcing:
        for i in range(y.shape[1]):
            # output_y--》[1, 4345]
            output_y, decoder_hidden, attn_weight =my_attenDecoder(input_y, decoder_hidden, encoder_output_c)
            # 根据预测结果计算损失
            target_y = y[0][i].view(1)
            # print(f'target_y--》{target_y}')
​
            my_loss = my_loss + crossentropy(output_y, target_y)
            # print(f'my_loss--》{my_loss}')
            # 将真实的下一个单词当作input_y
            input_y = y[0][i].view(1, -1)
            # print(f'input_y--》{input_y}')
    else:
        for i in range(y.shape[1]):
            # output_y--》[1, 4345]
            output_y, decoder_hidden, attn_weight = my_attenDecoder(input_y, decoder_hidden, encoder_output_c)
            # 根据预测结果计算损失
            target_y = y[0][i].view(1)
            my_loss = my_loss + crossentropy(output_y, target_y)
            topv, topi = output_y.topk(1)
            # 如果output_y预测的最大值对应的索引刚好等EOS,直接终止
            if topi.squeeze().item() == EOS_token:
                break
            # 将预测结果的当作下一个input_y
            input_y = topi.detach()
​
    # 6. 梯度清零
    encoder_optimizer.zero_grad()
    decoder_optimizer.zero_grad()
    # 7. 反向传播
    my_loss.backward()
    # 8.梯度更新
    encoder_optimizer.step()
    decoder_optimizer.step()
​
    return my_loss / y.shape[1]

7 模型预测

基本过程

1.获取数据
2.数据预处理
3.实例化模型: 编码器和解码器
4.加载模型训练好的参数: model.load_state_dict(torch.load("model_path"))
5.with torch.no_grad():
6.将数据送入模型进行预测(注意:张量的形状变换)

主代码实现

# 9.定义模型评估/预测函数
def test_Seq2Seq_Evaluate():
    # 1.加载训练好的编码器模型
    my_encoder = EncoderGRU(vocab_size=english_word_n, hidden_size=256).to(device)
    my_encoder.load_state_dict(torch.load("./ai19_model/my_encoder_1.pth"))
    # my_encoder.to(device)
    print(my_encoder)
    # 2.加载训练好的解码器模型
    my_decoder = AttentionDecoderGRU(vocab_size=french_word_n, hidden_size=256).to(device)
    my_decoder.load_state_dict(torch.load("./ai19_model/my_decoder_1.pth"))
    # my_decoder.load_state_dict(torch.load("./ai19_model/my_decoder_1.pth", map_location="cpu"), strict=False)
    print(my_decoder)
    print('*'*80)
    # 3.准备样本
​
    my_samplepairs =[['i m impressed with your french .', 'je suis impressionne par votre francais .'],
                     ['i m more than a friend .', 'je suis plus qu une amie .'],
                     ['she is beautiful like her mother .', 'elle est belle comme sa mere .']]
    print('my_samplepairs--->', len(my_samplepairs))
​
    # 4. 将样本输入模型得到结果
    for index, pair in enumerate(my_samplepairs[2:]):
        x = pair[0]
        y = pair[1]
        # 需要对x英文文本进行处理
        x_word2id = [english_word2index[word] for word in x.split(' ')]
        # print(f'x_word2id--》{x_word2id}')
        tensor_x = torch.tensor([x_word2id], dtype=torch.long, device=device)
        # print(f'tensor_x-->{tensor_x}')
        decoder_words, attention_weights = evaluate_seq2seq(tensor_x, my_encoder, my_decoder)
        decoder_french = ' '.join(decoder_words)
        print("*"*80)
        print(f'原始的英文输入文本是---》{x}')
        print(f'原始的法文标签文本是---》{y}')
        print(f'模型预测的法文---》{decoder_french}')

内部评估代码实现

def evaluate_seq2seq(x, encoder, decoder):
    # x代表当前需要翻译的英文文本
    # encoder代表编码器模型
    # decoder代表解码器模型
    # 1. 将x送入编码器得到编码结果
    # print(f'x---》{x.shape}')
    h0 = encoder.inithidden()
    encoder_outputs, encoder_hidden = encoder(x, h0)
    # 2. 准备解码的参数
    # 2.1 编码器结果--Value
    encoder_outputs_c = torch.zeros(MAX_LENGTH, encoder.hidden_size, device=device)
    for index in range(x.shape[1]):
        encoder_outputs_c[index] = encoder_outputs[0][index]
    # 2.2 解码器的key
    decode_hidden = encoder_hidden
​
    # 2.3 解码器的原始输入
    input_y = torch.tensor([[SOS_token]], dtype=torch.long, device=device)
​
    # 3.准备变量
    # 3.1 存储模型的预测结果
    decoder_words = []
    # 3.2 初始化一个全0的权重矩阵:存储每一步解码出来的注意力权重
    attention_weights = torch.zeros(MAX_LENGTH, MAX_LENGTH, device=device)
    # 4.进行模型的预测
    index = 0
​
    for i in range(MAX_LENGTH):
        output_y, decode_hidden, atten = decoder(input_y, decode_hidden, encoder_outputs_c)
        # 获取output_y预测的最大概率值对应索引
        topv, topi  = output_y.topk(1)
        # print(f'topi--》{topi}')
        # print(f'atten-->{atten}')
        # 将真实的注意力权重赋值给attention_weights
        # print(f' attention_weights[i]-->{ attention_weights[i]}')
        attention_weights[i] = atten
        if topi.item() == EOS_token:
            decoder_words.append("<EOS>")
            break
        else:
            decoder_words.append(french_index2word[topi.item()])
        input_y = topi.detach()
        index = i
​
    print(f'attention_weights--》{attention_weights}')
    return decoder_words, attention_weights[:index+1]

注意力图

# 10.注意力图展示
def test_attention_plot():
    # 1.加载训练好的编码器模型
    my_encoder = EncoderGRU(vocab_size=english_word_n, hidden_size=256).to(device)
    my_encoder.load_state_dict(torch.load("./ai19_model/my_encoder_1.pth"))
    # my_encoder.to(device)
    print(my_encoder)
    # 2.加载训练好的解码器模型
    my_decoder = AttentionDecoderGRU(vocab_size=french_word_n, hidden_size=256).to(device)
    my_decoder.load_state_dict(torch.load("./ai19_model/my_decoder_1.pth"))
    # my_decoder.load_state_dict(torch.load("./ai19_model/my_decoder_1.pth", map_location="cpu"), strict=False)
    print(my_decoder)
    print('*' * 80)
    sentence = "we re both teachers ."
    # 需要对x英文文本进行处理
    x_word2id = [english_word2index[word] for word in sentence.split(' ')]
    # print(f'x_word2id--》{x_word2id}')
    tensor_x = torch.tensor([x_word2id], dtype=torch.long, device=device)
    # print(f'tensor_x-->{tensor_x}')
    decoder_words, attention_weights = evaluate_seq2seq(tensor_x, my_encoder, my_decoder)
​
    plt.matshow(attention_weights.cpu().detach().numpy())
    plt.savefig('./ai19_attention.png')
    plt.show()


网站公告

今日签到

点亮在社区的每一天
去签到