【多标签文本分类】代码详解Seq2Seq模型

·阅读摘要:
  本文提出经典的Seq2Seq模型,应用于机器翻译领域。但是Seq2Seq适用于很多领域,比如多标签文本分类。
·参考文献:
  [1] Sequence to Sequence Learning with Neural Networks

【注一】:本论文提出的Seq2Seq模型,引发一系列基于Seq2Seq模型的文章问世。地位类似于2014年Kim发表的TextCNN,2017年Google发表的Transformer。

【注二】:论文的内容比较简单,重点都是在讲解Seq2Seq的原理。本篇博客将从pytorch实现Seq2Seq的角度讲解用代码逻辑理解Seq2Seq。

[1] Seq2Seq模型图

  如下图,左边是编码器(Encoder),主要是把一个序列经过多层LSTM后转化为一个固定大小的隐藏层向量 H H H。右边是解码器(Decoder),也是深层LSTM,它的输入是每次产生的词 y i y_i yi与编码器的输出 H H H,解码器每次产生一个词,直到产生的词是<EOS>为止。
请添加图片描述

[2] 编码器(Encoder)

  代码如下:

代码中,参数src是源序列,参数trg是目标序列

class Encoder(nn.Module):
    def __init__(self, input_dim, emb_dim, hid_dim, n_layers, dropout):
        super().__init__()
        self.hid_dim = hid_dim
        self.n_layers = n_layers
        self.embedding = nn.Embedding(input_dim, emb_dim)
        self.rnn = nn.LSTM(emb_dim, hid_dim, n_layers, dropout = dropout)
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, src):
        #src = [src len, batch size]
        embedded = self.dropout(self.embedding(src))
        #embedded = [src len, batch size, emb dim]
        outputs, (hidden, cell) = self.rnn(embedded)
        #outputs = [src len, batch size, hid dim * n directions]
        #hidden = [n layers * n directions, batch size, hid dim]
        #cell = [n layers * n directions, batch size, hid dim]
        #outputs are always from the top hidden layer
        return hidden, cell

  编码器(Encoder)就是一个普通的双向LSTM模型,比较简单。

  正常情况下,我们使用的是最后一层,每个时间步的输出outputs

  这里,编码器(Encoder)返回的是每一层每个时间步的输出hidden与中间参数cellhidden与中间参数cell会作为解码器(Decoder)的输入。

[3] 解码器(Decoder)

  代码如下:

代码中,参数src是源序列,参数trg是目标序列

class Decoder(nn.Module):
    def __init__(self, output_dim, emb_dim, hid_dim, n_layers, dropout):
        super().__init__()     
        self.output_dim = output_dim
        self.hid_dim = hid_dim
        self.n_layers = n_layers   
        self.embedding = nn.Embedding(output_dim, emb_dim) 
        self.rnn = nn.LSTM(emb_dim, hid_dim, n_layers, dropout = dropout)
        self.fc_out = nn.Linear(hid_dim, output_dim)
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, input, hidden, cell):
        input = input.unsqueeze(0)
        #input = [1, batch size]
        embedded = self.dropout(self.embedding(input))
        #embedded = [1, batch size, emb dim]  
        output, (hidden, cell) = self.rnn(embedded, (hidden, cell))
        #output = [seq len, batch size, hid dim * n directions]
        #hidden = [n layers * n directions, batch size, hid dim]
        #cell = [n layers * n directions, batch size, hid dim]
        prediction = self.fc_out(output.squeeze(0))
        #prediction = [batch size, output dim]
        return prediction, hidden, cell

  解码器(Decoder)依然是一个LSTM层,它的输入是上一次的输出hiddencell和上一次生成的单词的词向量input

【注三】:在第一次运行Decoder的时候,用的是Encoder的输出hiddencell和开始字符<SOS>的词向量。

  到这里,其实还是有诸多疑问的,包括:

  1、解码器(Decoder)是一个词一个词蹦出来的,对于Decoder可见性的要用循环来遍历一下,这个循环怎么写的问题;

  2、词向量怎么转化成单词,由于转化的单词要立即送到Decoder里,所以这个转化操作要在模型内完成,不应该作为输出,放到外面转化。

  3、模型要返回全连接层的输出,这是个向量,便于后续做loss计算;

  4、如果Decoder出的第一个单词就错误的话,那整个Decoder出来的句子就打错特错,如何防止这种情况。

[4] Seq2Seq

  最终把Encoder和Decoder整合,才算成功,代码如下:

代码中,参数src是源序列,参数trg是目标序列

class Seq2Seq(nn.Module):
    def __init__(self, encoder, decoder, device):
        super().__init__()
        
        self.encoder = encoder
        self.decoder = decoder
        self.device = device
        
        assert encoder.hid_dim == decoder.hid_dim, \
            "Hidden dimensions of encoder and decoder must be equal!"
        assert encoder.n_layers == decoder.n_layers, \
            "Encoder and decoder must have equal number of layers!"
        
    def forward(self, src, trg, teacher_forcing_ratio = 0.5):
        #teacher_forcing_ratio is probability to use teacher forcing
        #e.g. if teacher_forcing_ratio is 0.75 we use ground-truth inputs 75% of the time
        batch_size = trg.shape[1]
        trg_len = trg.shape[0]
        trg_vocab_size = self.decoder.output_dim
        #tensor to store decoder outputs
        outputs = torch.zeros(trg_len, batch_size, trg_vocab_size).to(self.device)
        #last hidden state of the encoder is used as the initial hidden state of the decoder
        hidden, cell = self.encoder(src)
        #first input to the decoder is the <sos> tokens
        input = trg[0,:]
        for t in range(1, trg_len):  
            #insert input token embedding, previous hidden and previous cell states
            #receive output tensor (predictions) and new hidden and cell states
            output, hidden, cell = self.decoder(input, hidden, cell) 
            #place predictions in a tensor holding predictions for each token
            outputs[t] = output
            #decide if we are going to use teacher forcing or not
            teacher_force = random.random() < teacher_forcing_ratio
            #get the highest predicted token from our predictions
            top1 = output.argmax(1) 
            #if teacher forcing, use actual next token as next input
            #if not, use predicted token
            input = trg[t] if teacher_force else top1 
        return outputs

  看到以上模型,可以解决在步骤[3]中残留的问题。首先用编码器得到输出,然后以一个for循环逐次执行解码器。

【注四】:self.decoder的输出output是经过全连接层的,它的含义是概率,接下来执行output.argmax(1)就是找出其中最大概率对应的序号,这样去词典/标签集中一找就能对应得上了。

【注五】:teacher_force是一种非常好的机制,防止解码器一错再错,随机填入目标序列中的词作为输入,用以纠正。再训练阶段我们可以使用teacher_force机制,但是在验证、测试时使用teacher_force机制是不对的,我们需要设置模型的形参teacher_forcing_ratio=0

【注六】:模型最终returnoutputs是经过全连接层的!它的含义是概率!它要作为损失函数的输入计算loss的。

⭐【注七】:这样写还是有点问题,在验证、测试的时候,还是这样的话,即使teacher_forcing_ratio=0,但已经默认了生成序列的长度,这是不对的。

⭐【注八】:在验证、测试的时候,我们应该以出现终止符<EOS>为结束。对于机器翻译任务可行,但是对于多标签文本分类,应该没有效果。因为文本的最后一个单词是具有结束语义信息的,标签不具有。还要继续看论文深造。。。

[5] 结尾

  其实有时候看代码比看论文更容易理解,只是好的代码不好找。
  完整代码参考:https://github.com/bentrevett/pytorch-seq2seq/blob/master/1%20-%20Sequence%20to%20Sequence%20Learning%20with%20Neural%20Networks.ipynb

  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个使用Transformer模型实现Seq2Seq分类任务的简单代码示例: ```python import torch import torch.nn as nn import torch.nn.functional as F from torch.nn import TransformerEncoder, TransformerEncoderLayer class TransformerSeq2Seq(nn.Module): def __init__(self, vocab_size, embedding_size, num_classes, num_layers, hidden_size, dropout): super(TransformerSeq2Seq, self).__init__() # 定义输入序列的embedding层 self.embedding = nn.Embedding(vocab_size, embedding_size) # 定义Transformer编码器层 encoder_layers = TransformerEncoderLayer(embedding_size, num_heads=8, dim_feedforward=hidden_size, dropout=dropout) self.transformer_encoder = TransformerEncoder(encoder_layers, num_layers=num_layers) # 定义输出层 self.output_layer = nn.Linear(embedding_size, num_classes) def forward(self, input_seq): # 对输入序列进行embedding input_embedded = self.embedding(input_seq) # 将embedding输入到Transformer编码器中进行编码 encoder_output = self.transformer_encoder(input_embedded) # 对编码后的输出进行平均池化 avg_pool_output = torch.mean(encoder_output, dim=1) # 将平均池化后的输出送到输出层进行分类 logits = self.output_layer(avg_pool_output) # 对输出进行softmax归一化 predicted_probs = F.softmax(logits, dim=1) return predicted_probs ``` 这里定义了一个TransformerSeq2Seq模型,其中包含一个embedding层、多层Transformer编码器和一个全连接输出层。在forward方法中,首先对输入序列进行embedding,然后送入Transformer编码器进行编码,并对编码后的输出进行平均池化,最后通过输出层得到类别分数,再进行softmax归一化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

征途黯然.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值