Task 4

作者: 挠头的三棱镜 | 来源:发表于2020-02-19 20:27 被阅读0次

    Task 4

    Seq2Seq以及机器翻译

    Seq2Seq也就是“序列到序列”模型,是一类用于解决输入为序列,输出为序列的生成式任务的模型。

    其中目前最通用的架构即为Encoder-Decoder,即 编码器-解码器 架构。

    Seq2Seq的一个经典任务即为机器翻译,它要求输入一个语言的句子(单词序列),输出目标语言的同义句子(翻译)。

    Encoder-Decoder架构的一个优点是,输出序列的长度可以和输入序列不一样(即不需要字字对应),这很好的解决了翻译的两个语言的句子通常长度不一致的问题。

    Image Name

    它的思想在于通过一个Encoder编码器提取出序列的低维(Dense)抽象编码信息(向量),再通过Decoder把其中的信息解码并转码为目标领域的序列。

    机器翻译最新进展与对应模型:

    https://github.com/sebastianruder/NLP-progress/blob/master/english/machine_translation.md

    神经翻译模型列表:

    https://github.com/jonsafari/nmt-list

    使用RNN实现一个用于机器翻译的Encoder-Decoder结构模型

    数据预处理

    首先需要一个不同语言的语句对数据集:

    中英17万条语句对:https://www.kesci.com/home/dataset/5dbbc1a1080dc300371ec4e6/files

    机器翻译英法数据17万条:https://www.kesci.com/home/dataset/5ddde161ca27f8002c4a5a26

    520万个中英文平行语料:https://www.kesci.com/home/dataset/5de5fcafca27f8002c4ca993

    字符在计算机里是以编码的形式存在,我们通常所用的空格是 \x20 ,是在标准ASCII可见字符 0x20~0x7e 范围内。 而 \xa0 属于 latin1 (ISO/IEC_8859-1)中的扩展字符集字符,代表不间断空白符nbsp(non-breaking space),超出gbk编码范围,是需要去除的特殊字符。再数据预处理的过程中,我们首先需要对数据进行清洗。

    将需要分隔的标点与单词用空格隔开,去除掉不需要的符号。

    分词

    外语通常看情况可以直接用空格,或者使用分词库。

    中文需要使用分词库(jieba等)。

    注意:如果使用词嵌入,最好使用与训练词向量时相同的分词方法。

    建立词典

    注意特殊字符

    统一输入序列的长度,多余的部分用<pad>-0填充,在后续训练时不会传梯度。-需要使用MaskSoftmaxCELoss的原因。

    结构

    Image Name

    训练时会将真实序列(Ground truth)作为Decoder的输入。

    (训练时的输出会预测错,避免影响训练效果)

    Image Name

    预测是会把t-1时间步的输出作为t时间步的输入。

    Encoder

    class Seq2SeqEncoder(d2l.Encoder):
        def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                     dropout=0, **kwargs):
            super(Seq2SeqEncoder, self).__init__(**kwargs)
            self.num_hiddens=num_hiddens
            self.num_layers=num_layers
            self.embedding = nn.Embedding(vocab_size, embed_size)
            self.rnn = nn.LSTM(embed_size,num_hiddens, num_layers, dropout=dropout)
       
    #Encoder的隐藏层使用0初始化
        def begin_state(self, batch_size, device):
            return [torch.zeros(size=(self.num_layers, batch_size, self.num_hiddens),  device=device),
                    torch.zeros(size=(self.num_layers, batch_size, self.num_hiddens),  device=device)]
        def forward(self, X, *args):
            #嵌入层,将index序列转为对应的embed_size维嵌入向量(独热编码or词向量)
            X = self.embedding(X) # X shape: (batch_size, seq_len, embed_size)
            X = X.transpose(0, 1)  # RNN的第0轴需要是 序列长度
            # state = self.begin_state(X.shape[1], device=X.device)
            out, state = self.rnn(X)
            # The shape of out is (seq_len, batch_size, num_hiddens).
            # 对于lstm,state包含隐藏层和记忆细胞(memory cell)
            # of the last time step, the shape is (num_layers, batch_size, num_hiddens)
            #也就是把全部时间步的状态都返回在state里?
            return out, state
    

    Decoder

    image.png
    class Seq2SeqDecoder(d2l.Decoder):
        def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                     dropout=0, **kwargs):
            super(Seq2SeqDecoder, self).__init__(**kwargs)
            self.embedding = nn.Embedding(vocab_size, embed_size)
            self.rnn = nn.LSTM(embed_size,num_hiddens, num_layers, dropout=dropout)
            self.dense = nn.Linear(num_hiddens,vocab_size)
    
        def init_state(self, enc_outputs, *args):
            return enc_outputs[1] #encoder的lstm的最后一时间步的隐藏状态
    
        def forward(self, X, state):
            X = self.embedding(X).transpose(0, 1)
            out, state = self.rnn(X, state)
            # 为简化损失函数,将batch_size转到第0维
            out = self.dense(out).transpose(0, 1)
            return out, state
    

    将pad的位置进行掩盖 - 变长句子适应

    def SequenceMask(X, X_len,value=0):
    #将X中,x_len数值对应索引及其之后位置用value进行掩盖
        maxlen = X.size(1)
        mask = torch.arange(maxlen)[None, :].to(X_len.device) < X_len[:, None]   
        X[~mask]=value
        return X
    
    class MaskedSoftmaxCELoss(nn.CrossEntropyLoss):
        # pred shape: (batch_size, seq_len, vocab_size)
        # label shape: (batch_size, seq_len)
        # valid_length shape: (batch_size, )
        def forward(self, pred, label, valid_length):
            # the sample weights shape should be (batch_size, seq_len)
            #保留的位置的权重设为1,不保留设为0
            weights = torch.ones_like(label)
            weights = SequenceMask(weights, valid_length).float()
            self.reduction='none'
            output=super(MaskedSoftmaxCELoss, self).forward(pred.transpose(1,2), label)
            #不保留的位置乘以0,就去掉了这处的损失
            #然后再对batch取平均
            return (output*weights).mean(dim=1)
    

    训练

    def train_ch7(model, data_iter, lr, num_epochs, device):  
        model.to(device)
        optimizer = optim.Adam(model.parameters(), lr=lr)
        loss = MaskedSoftmaxCELoss()
        tic = time.time()
        for epoch in range(1, num_epochs+1):
            l_sum, num_tokens_sum = 0.0, 0.0
            for batch in data_iter:
                optimizer.zero_grad()
                # 一个batch:(输入序列,输入长度,目标序列,目标长度)
                X, X_vlen, Y, Y_vlen = [x.to(device) for x in batch]
                #目标序列的t-1时间步的Ground truth作为t的输入,bos不需要输入
                #eos不需要训练?
                Y_input, Y_label, Y_vlen = Y[:,:-1], Y[:,1:], Y_vlen-1
                
                Y_hat, _ = model(X, Y_input, X_vlen, Y_vlen)
                l = loss(Y_hat, Y_label, Y_vlen).sum()
                l.backward()
    
                with torch.no_grad():
                    d2l.grad_clipping_nn(model, 5, device)
                num_tokens = Y_vlen.sum().item()
                optimizer.step()
                l_sum += l.sum().item()
                num_tokens_sum += num_tokens
            if epoch % 50 == 0:
                print("epoch {0:4d},loss {1:.3f}, time {2:.1f} sec".format( 
                      epoch, (l_sum/num_tokens_sum), time.time()-tic))
                tic = time.time()
    

    Beam Search - 集束搜索

    若直接采用贪心方法,在每个时间步选择概率最大的结果输出,不一定能得到全局最优解。

    在Beam Search中只有一个参数B,叫做beam width(集束宽),用来表示在每一次挑选top B的结果。

    image

    最终输出B个概率最高的序列

    seq2seq 中的 beam search 算法过程是怎样的? - 习翔宇的回答 - 知乎 https://www.zhihu.com/question/54356960/answer/375588742

    seq2seq中的beam search算法过程 - 忆臻的文章 - 知乎 https://zhuanlan.zhihu.com/p/28048246

    注意力机制

    视觉注意力机制是人类视觉所特有的大脑信号处理机制。人类视觉通过快速扫描全局图像,获得需要重点关注的目标区域,也就是一般所说的注意力焦点,而后对这一区域投入更多注意力资源,以获取更多所需要关注目标的细节信息,而抑制其他无用信息。

    深度学习中的注意力机制从本质上讲和人类的选择性视觉注意力机制类似,核心目标也是从众多信息中选择出对当前任务目标更关键的信息。

    深度学习中的注意力模型(2017版) - 张俊林的文章 - 知乎 https://zhuanlan.zhihu.com/p/37601161

    attention机制:又称为注意力机制,顾名思义,是一种能让模型对重要信息重点关注并充分学习吸收的技术,它不算是一个完整的模型,应当是一种技术,能够作用于任何序列模型中。

    Attention机制简单总结 - 邱震宇的文章 - 知乎 https://zhuanlan.zhihu.com/p/46313756

    无论是RNN还是CNN都在抽取以及保留全局上下文信息上,对过长的序列遇到了瓶颈。

    Attention可以跨过很长的区域,建立起某个上文与当前输入的联系

    例:一个代词与间隔了很多单词的命名实体的联系(同一个指代)

    实际做法是通过对每个(当前输入,上一时间步输出)(query-查询),通过之前所有输入(key)计算出对应隐藏状态(value)对于当前上下文的联系强度(贡献,注意力联系),即注意力分数(权重),并加权求和求出当前上下文。

    Image Name preview

    浅谈Attention注意力机制及其实现 - 空字符的文章 - 知乎 https://zhuanlan.zhihu.com/p/67909876

    描述

    相似度计算(注意力打分)

    img

    nlp中的Attention注意力机制+Transformer详解 - JayLou的文章 - 知乎 https://zhuanlan.zhihu.com/p/53682800

    然后通过一个softmax得到注意力分布(概率分布)

    最后加权平均

    点积注意力

    Q\in R^{m\times d}即m个queries,K\in R^{n\times d}即n个keys\\\alpha(Q,K)=QK^T/\sqrt{d}

    MLP注意力

    W_k\in R^{h\times d_k},W_q\in R^{h\times d_q},v\in R^h\\\alpha(q,k)=v^Ttanh(W_kk+W_qq)

    将key 和 value 在特征的维度上合并(concatenate),然后送至 a single hidden layer perceptron 这层中 hidden layer 为 ℎ and 输出的size为 1 .隐层激活函数为tanh,无偏置.

    引入注意力机制的Seq2seq模型

    Image Name Image Name

    注意力机制只需要改动Decoder的实现

    • the encoder outputs of all timesteps:encoder输出的各个状态,被用于attetion layer的memory部分,有相同的key和values

    • the hidden state of the encoder’s final timestep:编码器最后一个时间步的隐藏状态,被用于初始化decoder 的hidden state

    • the encoder valid length: 编码器的有效长度,借此,注意层不会考虑编码器输出中的填充标记(Paddings)

    在解码的每个时间步,我们使用解码器的最后一个RNN层的输出作为注意层的query。然后,将注意力模型的输出与输入嵌入向量连接起来,输入到RNN层。虽然RNN层隐藏状态也包含来自解码器的历史信息,但是attention model的输出显式地选择了enc_valid_len以内的编码器输出,这样attention机制就会尽可能排除其他不相关的信息。

    class Seq2SeqAttentionDecoder(d2l.Decoder):
        def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                     dropout=0, **kwargs):
            super(Seq2SeqAttentionDecoder, self).__init__(**kwargs)
            self.attention_cell = MLPAttention(num_hiddens,num_hiddens, dropout)
            self.embedding = nn.Embedding(vocab_size, embed_size)
            self.rnn = nn.LSTM(embed_size+ num_hiddens,num_hiddens, num_layers, dropout=dropout)
            self.dense = nn.Linear(num_hiddens,vocab_size)
    
        def init_state(self, enc_outputs, enc_valid_len, *args):
            outputs, hidden_state = enc_outputs
    
            # Transpose outputs to (batch_size, seq_len, hidden_size)
            return (outputs.permute(1,0,-1), hidden_state, enc_valid_len)
            
        def forward(self, X, state):
            enc_outputs, hidden_state, enc_valid_len = state
            #("X.size",X.size())
            X = self.embedding(X).transpose(0,1)
    #         print("Xembeding.size2",X.size())
            outputs = []
            for l, x in enumerate(X):
                # query shape: (batch_size, 1, hidden_size)
                # select hidden state of the last rnn layer as query
                query = hidden_state[0][-1].unsqueeze(1) # np.expand_dims(hidden_state[0][-1], axis=1)
                # context has same shape as query
    #             print("query enc_outputs, enc_outputs:\n",query.size(), enc_outputs.size(), enc_outputs.size())
                context = self.attention_cell(query, enc_outputs, enc_outputs, enc_valid_len)
                # Concatenate on the feature dimension
    #             print("context.size:",context.size())
                x = torch.cat((context, x.unsqueeze(1)), dim=-1)
                # Reshape x to (1, batch_size, embed_size+hidden_size)
    #             print("rnn",x.size(), len(hidden_state))
                out, hidden_state = self.rnn(x.transpose(0,1), hidden_state)
                outputs.append(out)
            outputs = self.dense(torch.cat(outputs, dim=0))
            return outputs.transpose(0, 1), [enc_outputs, hidden_state,
                                            enc_valid_len]
    

    Transformer

    transformer本质上是一个完全只使用(多头)注意力机制 (而不是RNN或CNN)构建 Encoder-Decoder 结构的模型。

    详解Transformer (Attention Is All You Need) - 刘岩的文章 - 知乎 https://zhuanlan.zhihu.com/p/48508221

    https://mp.weixin.qq.com/s/RLxWevVWHXgX-UcoxDS70w

    image.png

    Encoder的输出,会和每一层的Decoder进行结合。

    具体结构:

    1. Transformer blocks:将seq2seq模型重的循环网络替换为了Transformer Blocks,该模块包含一个多头注意力层(Multi-head Attention Layers)以及两个position-wise feed-forward networks(FFN)。对于解码器来说,另一个多头注意力层被用于接受编码器的隐藏状态。
    2. Add and norm:多头注意力层和前馈网络的输出被送到两个“add and norm”层进行处理,该层包含残差结构以及层归一化。
    3. Position encoding:由于自注意力层并没有区分元素的顺序,所以一个位置编码层被用于向序列元素里添加位置信息。

    多头注意力

    Fig. 10.3.2 自注意力结构

    自注意力机制:每个时间步的Q,K,V相同。

    image

    如上文,将输入单词转化成嵌入向量;

    根据嵌入向量得到 qkv 三个向量;

    为每个向量计算一个score: [图片上传失败...(image-b96ae8-1582115228360)]
    为了梯度的稳定,Transformer使用了score归一化,即除以 [图片上传失败...(image-edfe87-1582115228360)]
    对score施以softmax激活函数;

    softmax点乘Value值 v,得到加权的每个输入向量的评分 v

    相加之后得到最终的输出结果 zz=\sum v

    img

    作者:刘岩
    链接:https://zhuanlan.zhihu.com/p/48508221
    来源:知乎
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    多头注意力: Image Name

    多头注意力层包含h个并行的自注意力层

    位置编码

    论文给出的编码公式如下:

    [图片上传失败...(image-959a89-1582115228360)]

    [图片上传失败...(image-987224-1582115228360)]

    在上式中,pos表示单词的位置, i表示单词的维度。

    作者这么设计的原因是考虑到在NLP任务重,除了单词的绝对位置,单词的相对位置也非常重要。根据公式 [图片上传失败...(image-8ad92d-1582115228360)] 以及[图片上传失败...(image-b742b7-1582115228360)] ,这表明位置 kp 的位置向量可以表示为位置 k 的特征向量的线性变化,这为模型捕捉单词之间的相对位置关系提供了非常大的便利。

    相关文章

      网友评论

          本文标题:Task 4

          本文链接:https://www.haomeiwen.com/subject/fvrzfhtx.html