机器翻译模型二单层GRU__Pytorch实现

目录

1.多层LSTM的不足及改进

2.Seq2Seq模型实现

2.1数据准备

2.2建立模型

2.3训练与测试


1.多层LSTM的不足及改进

上一节引入了多层LSTM应用于机器翻译中,我们为了应对上下文向量对较远信息保存较少的问题,引入了一个训练小技巧:即,将源端的输入序列倒序输入,这样有利于上下文向量保存较多的句子开始的信息,因为目标端翻译开始主要依赖与源端句子的开头信息,而目标端句子开头翻译的质量也直接影响后续翻译的工作(因为其翻译结果可能作为下一时间布的输入)。其模型图如下:

但是这样的作法其实还未从本质上改变模型的不足:即模型在翻译的时候,上下文向量提供给解码器的信息过少,或者说,解码器不能直接看到有用信息(除了第一个RNN模块可以看到,其他模块只能间接通过前一模块的隐藏状态),使得翻译效果大打折扣 。《Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation》这篇文章对这一点进行改进,目标端的解码器中每个RNN模型进行解码的时候,接收的不仅是上一时间步的隐状态,以及当前时间布的输入,还包括上下文向量。此外,线性层的分类也不仅仅接受解码器中每个RNN模型最后一层的输出,还包括当前时间步的词嵌入以及原始上下文向量,其模型图如下:

这样做的好处在于,每个RNN模型的解码器可以直接看到原始的上下文词向量,而非间接通过上一时间步的隐状态获取原始上下文向量信息,其次,在线性层分类中,原本只能通过RNN模型最后一层解码输出来以此判断所翻译的词,而添加词嵌入以及原始上下文向量信息,更有利于线性层做出决策。在具体的代码实现中,新添加的信息与原来的输入信息直接拼接即可。

例如:原本RNN模型的输入的为目标端词向量的维度embeding_size,如今添加了原始上下文向量(假设维度为:hidden_size),那么其输入的维度为:embedding_size+hidden_size,即输入(词嵌入,原始上下文向量)。在线性分类层的输入原本只是目标端RNN模型的隐层维度(Hidden_size),如今拼接了原始上下文向量以及词嵌入,则线性层的输入维度为:Hidden_size+hidden_size+embedding_size,即(RNN最后一层输出,原始上下文向量,词嵌入)。

另一个需要注意的地方在,RNN模型不在是LSTM,这里采用的是GRU,GRU为LSTM的变种,两者的效果差不多,但GRU的优势在于参数量较LSTM少了一些,关于GRU具体过程这里不在赘述。

2.Seq2Seq模型实现

工具:Jupyter

2.1数据准备

依旧采用和上一节一样的数据(这个过程都是一样的,不过为了比较两个模型,这里我并未在源端将句子倒序):这里就是定义Field操作,将数据集经过Field操作,建立字典,建立数据迭代器

import torch
import spacy

from torchtext.data import Field,BucketIterator
from torchtext.datasets import Multi30k
cuda=torch.cuda.is_available()
device=torch.device("cuda" if cuda else "cpu")
spacy_de=spacy.load("de_core_news_sm")
spacy_en=spacy.load("en_core_web_sm")

def de_seq(text):
    #return [word.text for word in spacy_de.tokenizer(text)][::-1]这里并未进行倒序
    return [word.text for word in spacy_de.tokenizer(text)]

def en_seq(text):
    return [word.text for word in spacy_en.tokenizer(text)]

SRC=Field(tokenize=de_seq,
         init_token="<sos>",
         eos_token="<eos>",
         lower=True)
TRG=Field(tokenize=en_seq,
         init_token="<sos>",
         eos_token="<eos>",
         lower=True)
train_data,val_data,test_data=Multi30k.splits(exts=(".de",".en"),
                                             fields=(SRC,TRG))
SRC.build_vocab(train_data,min_freq=2)
TRG.build_vocab(train_data,min_freq=2)
batch_size=128

train_iter,val_iter,test_iter=BucketIterator.splits(
    (train_data,val_data,test_data),
    batch_size=batch_size,
    device=device
)

写完测试一下:

for example in train_iter:
    print(example.src.shape,example.trg.shape)
    print(example.src.device)
    break

 结果为:

torch.Size([29, 128]) torch.Size([29, 128])
cuda:0

我个人习惯是将batch放在开头的,后面的实现我会将batch放在开头(好处在于,看生成的具体数值容易观测)

2.2建立模型

这里遵循原文,实现的GRU都是单层,单层就不存在dropout了:

import torch.nn as nn
import random
class Encoder(nn.Module):
    def __init__(self,src_vocab_size,embed_size,hidden_size,dropout=0.5):
        #src_vocab_size 为德语词库的大小 enbed_size,词向量大小
        #hidden_size 隐藏状态的维度
        
        super(Encoder,self).__init__()
        self.embedding=nn.Embedding(src_vocab_size,embed_size,padding_idx=1)
        self.rnn=nn.GRU(embed_size,hidden_size,batch_first=True)
        self.dropout=nn.Dropout(dropout)
    
    def forward(self,src):
        #src [batch seq_len]
        x_embeding=self.dropout(self.embedding(src))
        #x_embeding [batch seq_len embed_size]
        _,h_n=self.rnn(x_embeding)
        #h_n也是我们要的上下文向量,其为最后一个时间步各层的输出,即[n_layers,batch_size,hidden_size]
        #这里就是[1,batch_size,hidden_size]
        return h_n

测试:

#使其batch放前[batch seq],个人习惯
src=example.src.permute(1,0)
trg=example.trg.permute(1,0)
#测试,也是最终模型的参数
src_vocab_size=len(SRC.vocab)
trg_vocab_size=len(TRG.vocab)
embed_size=256
hidden_size=512
enModel=Encoder(src_vocab_size,embed_size,hidden_size)
if cuda:
    enModel=enModel.cuda()
print(enModel(src).shape)

输出:

torch.Size([1, 128, 512])
class Decoder(nn.Module):
    def __init__(self,trg_vocab_size,embed_size,hidden_size,dropout=0.5):
        super(Decoder,self).__init__()
        #trg_vocab_size为英语词库大小 embed_size词向量维度
        #hidden_size隐状态大小
        self.embedding=nn.Embedding(trg_vocab_size,embed_size,padding_idx=1)
        #输入多了原始上下文向量,这里我们将源端和目标端的隐状态维度都默认为hidden_size
        self.rnn=nn.GRU(embed_size+hidden_size,hidden_size,batch_first=True)
        self.classify=nn.Linear(embed_size+hidden_size*2,trg_vocab_size)
        self.dropout=nn.Dropout(dropout)
        
    def forward(self,trg_i,context,h_n):
        #trg_i为某一时间步词的输入,[bacth_size]
        #context为原始上下文向量,[bacth_size,1,hidden_size]
        #h_n为上一时间布的隐状态[1,batch_size,hidden_size]
        trg_i=trg_i.unsqueeze(1)
        #trg_i[bacth_size,1]
        trg_i_embed=self.dropout(self.embedding(trg_i))
        #trg_i_embed [bacth_size,1,embed_size]
        
        #输入rnn模块的不仅仅只有词嵌入和上一时间步的隐状态,还有原始上下向量
        input=torch.cat((trg_i_embed,context),dim=2)
        #input[bacth_size,1,embed_size+hidden_size]
        output,h_n=self.rnn(input,h_n)
        #output[batch_size,1,hidden_size]
        #h_n[1,batch_size,hidden_size]
        
        
        #原本rnn模型的输入直接带入线性分类层映射到英语空间中,这里新添原始词嵌入和原始上下文向量,即上面的input
        input=input.squeeze()
        output=output.squeeze()
        #input[bacth_size embed_size+hidden_size]
        #output[batch_szie hidden_size]
        input=torch.cat((input,output),dim=1)
        output=self.classify(input)
        #output[bacth trg_vocab_size]
        return output,h_n

测试: 

h_n=enModel(src)
#h_n[1 batch hidden_zize]
#转变为上下文向量
context=h_n.permute(1,0,2)
deModel=Decoder(trg_vocab_size,embed_size,hidden_size)
if cuda:
    deModel=deModel.cuda()
input_i=trg[:,2]#取某时间步词输入
output,h_n=deModel(input_i,context,h_n)
print(output.shape,h_n.shape)

结果:

torch.Size([128, 5893]) torch.Size([1, 128, 512])
#这一步其实就整合上面我们的测试
class Seq2Seq(nn.Module):
    def __init__(self,encoder,decoder):
        super(Seq2Seq,self).__init__()
        self.encoder=encoder
        self.decoder=decoder
        
    def forward(self,src,trg,teach_threshold=0.5):
        #src[batch seq_len]
        #trg[bacth seq_len]
        #teacher_rate 进行forcing teaching 的阈值
        trg_seq_len=trg.shape[1]
        #这一步很重要,最后一个batch大小没有batch_size大小
        batch=trg.shape[0]
        
        #设置一个tensor保存所有预测结果
        outputs_save=torch.zeros(batch,trg_seq_len,trg_vocab_size)
        if cuda:
            outputs_save=outputs_save.cuda()
        
        #获取编码层的输出
        h_n=self.encoder(src)
        #h_n[1,batch_size,hidden_size]
        context=h_n.permute(1,0,2)
        #context[batch_size,1,hidden_size]
        input=trg[:,0]
        
        #遍历每个英语的输入,代入翻译
        for t in range(1,trg_seq_len):
            #将每个时间步词典代入计算
            output,h_n=self.decoder(input,context,h_n)
            #保存rnn模型经过线性分类层的输出
            outputs_save[:,t,:]=output
            probability=random.random()
            #是否采用强制教学
            input=trg[:,t] if probability<teach_threshold else output.argmax(1)
        return outputs_save

测试:

model=Seq2Seq(enModel,deModel)
if cuda:
    model=model.cuda()
outputs=model(src,trg)
outputs[:,0,:].sum(),outputs[:,1,:].sum()

由于我们将结果保存在一个tensor中(有全部为0创建),我们测试就是看这个tensor除了第0个时间步以外,其他是否还是0(测试一个就行),结果为:

(tensor(0., device='cuda:0', grad_fn=<SumBackward0>),
 tensor(-3575.2104, device='cuda:0', grad_fn=<SumBackward0>))

2.3训练与测试

import time,math
from torch.optim import Adam
epochs=10
criterion=nn.CrossEntropyLoss(ignore_index=1)
optim=Adam(model.parameters())

这里参数初始化也是按照原文来做的: 

def init_weights(m):
    for name, param in m.named_parameters():
        nn.init.normal_(param.data, mean=0, std=0.01)
        
model.apply(init_weights)
def train(model,train_iter,criterion,optim):
    model.train()
    lossAll=0
    for example in train_iter:
        src=example.src.permute(1,0)
        trg=example.trg.permute(1,0)
        #src[batch seq_len]
        #trg[batch seq_len]
        
        optim.zero_grad()
        output=model(src,trg)
        #output[batch seq_len trg_vocab_size]
        trg_vocab_size=output.shape[-1]
        output=output[:,1:,:].reshape(-1,trg_vocab_size)
        #output[bacth*(seq_len-1),trg_vocab_size]
        trg=trg[:,1:].reshape(-1)
        #trg[bacth*(seq_len-1)]
        
        loss=criterion(output,trg)
        loss.backward()
        optim.step()
        
        lossAll+=loss.item()
    return lossAll/len(train_iter)
def evaluate(model,val_iter,criterion):
    model.eval()
    lossAll=0
    
    with torch.no_grad():
        for example in val_iter:
            src=example.src.permute(1,0)
            trg=example.trg.permute(1,0)
            #src[batch seq_len]
            #trg[batch seq_len]
            
            output=model(src,trg)
            #output[batch seq_len trg_vocab_size]
            trg_vocab_size=output.shape[-1]
            output=output[:,1:,:].reshape(-1,trg_vocab_size)
            #output[bacth*(seq_len-1),trg_vocab_size]
            trg=trg[:,1:].reshape(-1)
            #trg[bacth*(seq_len-1)]
            loss=criterion(output,trg)
            lossAll+=loss.item()

    return lossAll/len(val_iter)
def epoch_time(start_time, end_time):
    elapsed_time = end_time - start_time
    elapsed_mins = int(elapsed_time / 60)
    elapsed_secs = int(elapsed_time - (elapsed_mins * 60))
    return elapsed_mins, elapsed_secs
for epoch in range(epochs):
    start_time = time.time()
    train_loss = train(model,train_iter,criterion,optim)
    valid_loss = evaluate(model,val_iter,criterion)
    end_time = time.time()
    epoch_mins, epoch_secs = epoch_time(start_time, end_time)
    
    
    print(f'Epoch: {epoch+1:02} | Time: {epoch_mins}m {epoch_secs}s')
    print(f'\tTrain Loss: {train_loss:.3f} | Train PPL: {math.exp(train_loss):7.3f}')
    print(f'\t Val. Loss: {valid_loss:.3f} |  Val. PPL: {math.exp(valid_loss):7.3f}')

最终的性能(测试集上困惑度结果为18)是优于多层LSTM的(21),这里并为展示到最后,虽然这样效果会比多层LSTM好一点,但是我们发现收敛的速度也很慢,原因在于其增加了太多的训练参数,增加的模型的复杂度。也可以尝试将源端倒序,结合模型1的技巧与模型2的思想,效果更佳。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
GRU是一种循环神经网络(RNN)模型,在自然语言处理和其他序列数据处理任务中得到广泛应用。下面是一个使用GRU模型进行文本分类的Python示例代码: ```python import tensorflow as tf from tensorflow.keras.layers import GRU, Dense # 构建 GRU 模型 model = tf.keras.Sequential([ GRU(64, return_sequences=True), GRU(32), Dense(1, activation='sigmoid') ]) # 编译模型 model.compile(loss='binary_crossentropy', optimizer=tf.keras.optimizers.Adam(1e-4), metrics=['accuracy']) # 训练模型 model.fit(train_data, train_labels, epochs=10, validation_data=(val_data, val_labels)) # 评估模型 test_loss, test_acc = model.evaluate(test_data, test_labels) print('Test Loss:', test_loss) print('Test Accuracy:', test_acc) ``` 通过GRU模型,我们可以在文本分类等任务中获得优秀的性能。此外,GRU模型还可以应用于时间序列预测、机器翻译等任务。与LSTM模型相比,GRU模型具有更快的计算速度和较少的内存需求。不过,在某些情况下,GRU模型可能无法捕捉到长期的依赖关系,因为它只有两个门控单元(更新门和重置门),而LSTM模型有三个门控单元(输入门、遗忘门和输出门)。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Python中的GRU模型](https://blog.csdn.net/qq_33885122/article/details/131040146)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值