tensorflow2.0之seq2seq+attention模型和实例

什么是seq2seq

Seq2Seq模型是输出的长度不确定时采用的模型,这种情况一般是在机器翻译的任务中出现,将一句中文翻译成英文,那么这句英文的长度有可能会比中文短,也有可能会比中文长,所以输出的长度就不确定了。除了机器翻译像人机对话,情感分类等都可以用到seq2seq

seq2seq 是一个Encoder–Decoder 结构的网络,它的输入是一个序列,输出也是一个序列, Encoder 中将一个可变长度的信号序列变为固定长度的向量表达,Decoder 将这个固定长度的向量变成可变长度的目标的信号序列。这里看看常见的encoder-decoder结构,基本思想就是利用两个RNN,一个RNN作为encoder,另一个RNN作为decoder,除了RNN外LSTM,GRU也可以使用。

如图所示这是一个基于LSTM的seq2seq模型用来做问答系统。把你的问题作为encoder进行编码当成X输入,回答作为decoder进行编码当成label输出,中间经过语义编码,把encoder的输出当作decoder的输入
在这里插入图片描述

attention机制

attention机制是模仿人类注意力而提出的一种解决问题的办法,简单地说就是从大量信息中快速筛选出高价值信息。主要用于解决LSTM/RNN模型输入序列较长的时候很难获得最终合理的向量表示问题,做法是保留LSTM的中间结果,用新的模型对其进行学习,并将其与输出进行关联,从而达到信息筛选的目的。

为什么加入attention机制

首先 Encoder 将输入编码为固定大小状态向量(hidden state)的过程实际上是一个“信息有损压缩”的过程。如果信息量越大,那么这个转化向量的过程对信息造成的损失就越大。同时,随着 sequence length的增加,意味着时间维度上的序列很长,RNN 模型也会出现梯度弥散。最后,基础的模型连接 Encoder 和 Decoder 模块的组件仅仅是一个固定大小的状态向量,这使得Decoder无法直接去关注到输入信息的更多细节。因此加入attention机制对Encoder层状态的加权从而掌握输入语句中的所有细节信息。

实例-物品评价

这次实例用到的数据是一个评价以及评价总结的文本数据。如图所示,本次只用到了其中的两列。一列是Summary作为我们的Decoder一列作为我们的Encoder。
在这里插入图片描述
第一步:我们要先读取数据,获取我们需要的这两列数据,并把Text中的停用词和一些缩写词给分开并且清除其中的特殊符号等一些用处不大的单词。

def clean_text(text, remove_stopwords=True):
    text = text.lower()
    if True:
        text = text.split()
        new_text = []
        for word in text:
            if word in contractions:
                new_text.append(contractions[word])
            else:
                new_text.append(word)
        text = " ".join(new_text)


    text = re.sub(r'https?:\/\/.*[\r\n]*', '', text, flags=re.MULTILINE)
    text = re.sub(r'\<a href', ' ', text)
    text = re.sub(r'&amp;', '', text)
    text = re.sub(r'[_"\-;%()|+&=*%.,!?:#$@\[\]/]', ' ', text)
    text = re.sub(r'<br />', ' ', text)
    text = re.sub(r'\'', ' ', text)

 
    if remove_stopwords:
        text = text.split()
        stops = set(stopwords.words("english"))
        text = [w for w in text if not w in stops]
        text = " ".join(text)
    return text

处理后的结果如图所示,评价及总结。
在这里插入图片描述
第二步:再次读取这些数据,获取数据中的所有不重复的单词做成词映射。在获取所有单词以后再加入几个特殊的单词:["", “”, “”, “”] PAD是当你的文档长度小于统一的长度以后用PAD填充。UNK是一些未知的单词用UNK填充。GO和EOS是一句话的开头和结尾便于机器的识别,让机器知道什么时候开始什么时候就可以结束了。

vocab_words = []
def count_words( text):
    for sentence in text:
        for word in sentence.split():
            if word not in vocab_words:
                vocab_words.append(word)

special_words = ["<PAD>", "<UNK>", "<GO>", "<EOS>"]
vocab_words = special_words + vocab_words
vocab2id = {word: i for i, word in enumerate(vocab_words)}
id2vocab = {i: word for i, word in enumerate(vocab_words)}

结果如图所示。
在这里插入图片描述
第三步 做完映射以后就可以把我们的文本数据进行转换了,把单词转换成对应的唯一数字。在把GO,EOS等特殊字符的向量放在每段话的评价的前面和后面,把GO放在总结评语的前面,组成我们的输入[source_input_ids, target_input_ids],把EOS再放在总结评语的后面当成我们的标签 target_output_ids,
然后再使用 keras.preprocessing.sequence.pad_sequences()方法把所有的数据长度进行统一。

def process_input_data(source_data_ids, target_indexs, vocab2id):
    source_inputs = []
    decoder_inputs, decoder_outputs = [], []
    for source, target in zip(source_data_ids, target_indexs):
        source_inputs.append([vocab2id["<GO>"]] + source + [vocab2id["<EOS>"]])
        decoder_inputs.append([vocab2id["<GO>"]] + target)
        decoder_outputs.append(target + [vocab2id["<EOS>"]])
    return source_inputs, decoder_inputs, decoder_outputs
maxlen = 10
source_input_ids = keras.preprocessing.sequence.pad_sequences(source_input_ids, padding='post', maxlen=maxlen)
target_input_ids = keras.preprocessing.sequence.pad_sequences(target_input_ids, padding='post',  maxlen=maxlen)
target_output_ids = keras.preprocessing.sequence.pad_sequences(target_output_ids, padding='post',  maxlen=maxlen)

在这里插入图片描述
***第四步:***上面的代码就已经把我们的数据处理做完了,现在我们要做的就是把建立Seq2seq模型

一:编写Encoder,这里面包含一个embedding层和一个LSTM层

class Encoder(keras.Model):
    def __init__(self, vocab_size, embedding_dim, hidden_units):
        super(Encoder, self).__init__()
        # Embedding Layer
        self.embedding = Embedding(vocab_size, embedding_dim, mask_zero=True)
        # Encode LSTM Layer
        self.encoder_lstm = LSTM(hidden_units, return_sequences=True, return_state=True, name="encode_lstm")
        
    def call(self, inputs):
        encoder_embed = self.embedding(inputs)
        encoder_outputs, state_h, state_c = self.encoder_lstm(encoder_embed)
        return encoder_outputs, state_h, state_c

二:编写Decoder,有三部分输入,一是encoder部分的每个时刻输出,二是encoder的隐藏状态输出,三是decoder的目标输入。另外decoder还包含一个Attention层,计算decoder每个输入与encoder的注意力。

class Decoder(keras.Model):
    def __init__(self, vocab_size, embedding_dim, hidden_units):
        super(Decoder, self).__init__()
        # Embedding Layer
        self.embedding = Embedding(vocab_size, embedding_dim, mask_zero=True)
        # Decode LSTM Layer
        self.decoder_lstm = LSTM(hidden_units, return_sequences=True, return_state=True, name="decode_lstm")
        # Attention Layer
        self.attention = Attention()
    
    def call(self, enc_outputs, dec_inputs, states_inputs):
        decoder_embed = self.embedding(dec_inputs)
        dec_outputs, dec_state_h, dec_state_c = self.decoder_lstm(decoder_embed, initial_state=states_inputs)
        attention_output = self.attention([dec_outputs, enc_outputs])
        
        return attention_output, dec_state_h, dec_state_c

三:把encoder和decoder模块合并,组成一个完整的seq2seq模型。

def Seq2Seq(maxlen, embedding_dim, hidden_units, vocab_size):
    """
    seq2seq model
    """
    # Input Layer
    encoder_inputs = Input(shape=(maxlen,), name="encode_input")
    decoder_inputs = Input(shape=(None,), name="decode_input")
    # Encoder Layer
    encoder = Encoder(vocab_size, embedding_dim, hidden_units)
    enc_outputs, enc_state_h, enc_state_c = encoder(encoder_inputs)
    dec_states_inputs = [enc_state_h, enc_state_c]
    # Decoder Layer
    decoder = Decoder(vocab_size, embedding_dim, hidden_units)
    attention_output, dec_state_h, dec_state_c = decoder(enc_outputs, decoder_inputs, dec_states_inputs)
    # Dense Layer
    dense_outputs = Dense(vocab_size, activation='softmax', name="dense")(attention_output)
    # seq2seq model
    model = Model(inputs=[encoder_inputs, decoder_inputs], outputs=dense_outputs)
    

到此我们的模型已经建立。
在这里插入图片描述
第五步:把我们的数据输入到我们建立好的模型当中进行训练,并保存好训练好的模型

epochs = 49
batch_size = 32
val_rate = 0.2
loss_fn = keras.losses.SparseCategoricalCrossentropy()
model.compile(loss=loss_fn, optimizer='adam')
model.fit([source_input_ids, target_input_ids], target_output_ids, 
          batch_size=batch_size, epochs=epochs, validation_split=val_rate)

代码下载:https://download.csdn.net/download/weixin_43788143/12661025

代码参考:https://blog.csdn.net/qq_35549634/article/details/106603346?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-5&spm=1001.2101.3001.4242

  • 2
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
以下是一个示例代码,用于构建带有双向LSTM编码器、单向LSTM解码器和注意力机制的Seq2Seq模型: ```python import torch import torch.nn as nn class Encoder(nn.Module): def __init__(self, input_size, hidden_size, num_layers): super(Encoder, self).__init__() self.hidden_size = hidden_size self.num_layers = num_layers self.lstm = nn.LSTM(input_size, hidden_size, num_layers, bidirectional=True) def forward(self, inputs): # inputs shape: (seq_len, batch_size, input_size) outputs, (hidden, _) = self.lstm(inputs) # outputs shape: (seq_len, batch_size, hidden_size*num_directions) # hidden shape: (num_layers*num_directions, batch_size, hidden_size) return outputs, hidden class Attention(nn.Module): def __init__(self, hidden_size): super(Attention, self).__init__() self.hidden_size = hidden_size self.attn = nn.Linear(hidden_size*2, hidden_size) self.v = nn.Linear(hidden_size, 1, bias=False) def forward(self, encoder_outputs, decoder_hidden): # encoder_outputs shape: (seq_len, batch_size, hidden_size*num_directions) # decoder_hidden shape: (num_layers, batch_size, hidden_size) seq_len = encoder_outputs.size(0) batch_size = encoder_outputs.size(1) decoder_hidden = decoder_hidden[-1] # 取最后一层的隐藏状态作为decoder的输出 # 将decoder的隐藏状态复制seq_len次,用于计算注意力权重 decoder_hidden = decoder_hidden.unsqueeze(1).repeat(1, seq_len, 1) energy = torch.tanh(self.attn(torch.cat((encoder_outputs, decoder_hidden), dim=2))) # energy shape: (seq_len, batch_size, hidden_size) attention = self.v(energy).squeeze(2) # attention shape: (seq_len, batch_size) attention_weights = torch.softmax(attention, dim=0) # attention_weights shape: (seq_len, batch_size) # 计算加权后的encoder输出作为context向量 context = torch.bmm(encoder_outputs.permute(1, 2, 0), attention_weights.unsqueeze(2)).squeeze(2) # context shape: (batch_size, hidden_size*num_directions) return context, attention_weights class Decoder(nn.Module): def __init__(self, input_size, hidden_size, output_size, num_layers): super(Decoder, self).__init__() self.hidden_size = hidden_size self.num_layers = num_layers self.lstm = nn.LSTM(input_size, hidden_size, num_layers) self.fc = nn.Linear(hidden_size, output_size) def forward(self, inputs, hidden): # inputs shape: (1, batch_size, input_size) outputs, hidden = self.lstm(inputs, hidden) # outputs shape: (1, batch_size, hidden_size) # hidden shape: (num_layers, batch_size, hidden_size) outputs = self.fc(outputs.squeeze(0)) # outputs shape: (batch_size, output_size) return outputs.unsqueeze(0), hidden class Seq2Seq(nn.Module): def __init__(self, encoder, decoder): super(Seq2Seq, self).__init__() self.encoder = encoder self.decoder = decoder def forward(self, inputs, targets, teacher_forcing_ratio=0.5): # inputs shape: (seq_len, batch_size, input_size) # targets shape: (seq_len, batch_size, output_size) seq_len = targets.size(0) batch_size = targets.size(1) output_size = self.decoder.fc.out_features encoder_outputs, encoder_hidden = self.encoder(inputs) decoder_inputs = torch.zeros(1, batch_size, output_size).to(inputs.device) decoder_hidden = encoder_hidden # 使用encoder的最后一层隐藏状态作为decoder的初始隐藏状态 outputs = torch.zeros(seq_len, batch_size, output_size).to(inputs.device) for t in range(seq_len): decoder_output, decoder_hidden = self.decoder(decoder_inputs, decoder_hidden) outputs[t] = decoder_output # 使用teacher forcing或者上一个时间步的预测结果作为下一个时间步的输入 use_teacher_forcing = True if torch.rand(1).item() < teacher_forcing_ratio else False if use_teacher_forcing: decoder_inputs = targets[t].unsqueeze(0) else: decoder_inputs = decoder_output.argmax(dim=2).unsqueeze(0) return outputs # 定义模型的参数 input_size = 100 hidden_size = 256 output_size = 10 num_layers = 2 # 创建编码器和解码器实例 encoder = Encoder(input_size, hidden_size, num_layers) decoder = Decoder(output_size, hidden_size, output_size, num_layers) # 创建Seq2Seq模型实例 model = Seq2Seq(encoder, decoder) # 定义输入和目标序列的形状 seq_len = 20 batch_size = 32 inputs = torch.randn(seq_len, batch_size, input_size) targets = torch.randint(output_size, (seq_len, batch_size)) # 将输入和目标序列传递给模型 outputs = model(inputs, targets) # 打印输出的形状 print(outputs.shape) ``` 上述代码,我们首先定义了编码器(`Encoder`)和解码器(`Decoder`)的模型结构,然后将它们组合成一个Seq2Seq模型(`Seq2Seq`),并定义了前向传播的逻辑。 在构建Seq2Seq模型时,我们使用了双向LSTM作为编码器,单向LSTM作为解码器,并添加了注意力机制(`Attention`)来帮助解码器在生成序列时关注输入序列的不同部分。 最后,我们创建了模型实例(`model`),并将输入和目标序列传递给模型进行前向传播,输出序列的预测结果。 请根据你的具体任务和数据进行相应的修改和调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辰溪0502

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值