• Transformer模型


    之前看过一些介绍Transformer的博客,最终还是决定读一下原论文。顺便找了一份源码,对比着学习。

    Transformer模型

    论文:Attention Is All You Need

    以下为个人理解,如有错误欢迎批评指正。

    1.简介

    Transformer的出现改变了以往的学习模型的基本策略。之前的各种模型,例如RNN,LSTM等,都是基于时间序列的模型,当句子长度达到一定程度的时候,无论采用什么方式,对于前边的内容的学习都会产生遗忘。而且,梯度消失或者爆炸问题也会存在。

    因此,提出了基于注意力的Transformer的模型。这个模型的好处就是采用了 编码器-解码器的结构。而且不再是深度递归的。采用的注意力模型有更高的可解释性。

    接下来详细说明Transformer。

    2.模型的整体架构

     

     

    由图中的架构,左边为编码器(Encoder) ,右边为解码器(Decoder)

    2.1 编码器

    编码器由N个子层堆叠而成,每个子层包括了一个自注意层和一个前馈层。这两个都是残差连接。

    原文使用的编码器层为6层。

    2.2解码器

    解码器同样由N个子层堆叠,每个子层包含了三部分。首先是一个自注意力层,然后是根据解码器的输出计算的注意力层,加上最后的前馈层。然后残差连接。原文中,解码器为6层。

    2.3注意力

    注意力机制是本文的重点。开始初始化三个矩阵,分别为 Q K V,,注意力计算公式为

     

     

    其中dk表示维度,缩放因子。

    本文还提到了多头注意力,就是多个Q K V拼接成的矩阵。同时计算。

    2.4 编码

    本文采用的编码方式为 序列的词嵌入编码+位置编码。

    3.源代码分析

     

    3.1 Encoder

    整个Encoder内容

    class Encoder(nn.Module):
        ''' A encoder model with self attention mechanism. '''

        def __init__(
                self, n_src_vocab, d_word_vec, n_layers, n_head, d_k, d_v,
                d_model, d_inner, pad_idx, dropout=0.1, n_position=200, scale_emb=False):

            super().__init__()

            self.src_word_emb = nn.Embedding(n_src_vocab, d_word_vec, padding_idx=pad_idx)
            self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)
            self.dropout = nn.Dropout(p=dropout)
            self.layer_stack = nn.ModuleList([
                EncoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)
                for _ in range(n_layers)])
            self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
            self.scale_emb = scale_emb
            self.d_model = d_model

        def forward(self, src_seq, src_mask, return_attns=False):

            enc_slf_attn_list = []

            # -- Forward
            enc_output = self.src_word_emb(src_seq)
            if self.scale_emb:
                enc_output *= self.d_model ** 0.5
            enc_output = self.dropout(self.position_enc(enc_output))
            enc_output = self.layer_norm(enc_output)

            for enc_layer in self.layer_stack:
                enc_output, enc_slf_attn = enc_layer(enc_output, slf_attn_mask=src_mask)
                enc_slf_attn_list += [enc_slf_attn] if return_attns else []

            if return_attns:
                return enc_output, enc_slf_attn_list
            return enc_output,

    3.1.1 输入序列编码

    这个是编码层,其中 Embedding 为词嵌入编码,PositionalEncoding 为位置编码。这两部分共同作为Encoder的输入。

    #位置编码,采用正弦和余弦 编码
    class PositionalEncoding(nn.Module):
        #论文3.5节,说明
        def __init__(self, d_hid, n_position=200):
            super(PositionalEncoding, self).__init__()

            # Not a parameter
            self.register_buffer('pos_table', self._get_sinusoid_encoding_table(n_position, d_hid))

        def _get_sinusoid_encoding_table(self, n_position, d_hid):
            ''' Sinusoid position encoding table '''
            # TODO: make it with torch instead of numpy

            def get_position_angle_vec(position):
                return [position / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)]

            sinusoid_table = np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)])
            sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2])  # dim 2i
            sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2])  # dim 2i+1

            return torch.FloatTensor(sinusoid_table).unsqueeze(0)

        def forward(self, x):
            return x + self.pos_table[:, :x.size(1)].clone().detach()

    3.1.2 编码层也就是多头注意力层+前馈层

    class EncoderLayer(nn.Module):
        ''' Compose with two layers '''

        def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
            super(EncoderLayer, self).__init__()
            self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout) #多头注意力
            self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)  #前馈

        def forward(self, enc_input, slf_attn_mask=None):

            #论文3.2.3中第二点说明,QKV来源于上一层的输出,来源一样。
            enc_output, enc_slf_attn = self.slf_attn(
                enc_input, enc_input, enc_input, mask=slf_attn_mask) 
            enc_output = self.pos_ffn(enc_output)
            return enc_output, enc_slf_attn

    3.1.3多头注意力+前馈层的实现

    #多头注意力
    class MultiHeadAttention(nn.Module):
        ''' Multi-Head Attention module '''

        def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1):
            super().__init__()

            self.n_head = n_head
            self.d_k = d_k
            self.d_v = d_v

            self.w_qs = nn.Linear(d_model, n_head * d_k, bias=False)
            self.w_ks = nn.Linear(d_model, n_head * d_k, bias=False)
            self.w_vs = nn.Linear(d_model, n_head * d_v, bias=False)
            self.fc = nn.Linear(n_head * d_v, d_model, bias=False)

            #缩放因子
            self.attention = ScaledDotProductAttention(temperature=d_k ** 0.5)

            self.dropout = nn.Dropout(dropout)
            self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)


        def forward(self, q, k, v, mask=None):

            d_k, d_v, n_head = self.d_k, self.d_v, self.n_head
            sz_b, len_q, len_k, len_v = q.size(0), q.size(1), k.size(1), v.size(1)

            residual = q

            # Pass through the pre-attention projection: b x lq x (n*dv)
            # Separate different heads: b x lq x n x dv
            q = self.w_qs(q).view(sz_b, len_q, n_head, d_k)
            k = self.w_ks(k).view(sz_b, len_k, n_head, d_k)
            v = self.w_vs(v).view(sz_b, len_v, n_head, d_v)

            # Transpose for attention dot product: b x n x lq x dv
            q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)

            if mask is not None:
                mask = mask.unsqueeze(1)   # For head axis broadcasting.

            q, attn = self.attention(q, k, v, mask=mask)

            # Transpose to move the head dimension back: b x lq x n x dv
            # Combine the last two dimensions to concatenate all the heads together: b x lq x (n*dv)
            q = q.transpose(1, 2).contiguous().view(sz_b, len_q, -1)
            q = self.dropout(self.fc(q))
            q += residual

            q = self.layer_norm(q)

            return q, attn

    #前馈层
    class PositionwiseFeedForward(nn.Module):
        ''' A two-feed-forward-layer module '''

        #论文3.3中  , linear ReLU ,linear
        def __init__(self, d_in, d_hid, dropout=0.1):
            super().__init__()
            self.w_1 = nn.Linear(d_in, d_hid) # position-wise
            self.w_2 = nn.Linear(d_hid, d_in) # position-wise
            self.layer_norm = nn.LayerNorm(d_in, eps=1e-6)
            self.dropout = nn.Dropout(dropout)

        def forward(self, x):

            residual = x

            x = self.w_2(F.relu(self.w_1(x)))
            x = self.dropout(x)
            x += residual

            x = self.layer_norm(x)

            return x

    3.2Decoder

    完整的Decoder,这个与解码器相比,只是在输入的位置编码添加了Mask ,然后DecoderLayer增加了一个注意力机制。

    class Decoder(nn.Module):
        ''' A decoder model with self attention mechanism. '''

        def __init__(
                self, n_trg_vocab, d_word_vec, n_layers, n_head, d_k, d_v,
                d_model, d_inner, pad_idx, n_position=200, dropout=0.1, scale_emb=False):

            super().__init__()

            self.trg_word_emb = nn.Embedding(n_trg_vocab, d_word_vec, padding_idx=pad_idx)
            self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)
            self.dropout = nn.Dropout(p=dropout)
            self.layer_stack = nn.ModuleList([
                DecoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)
                for _ in range(n_layers)])
            self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
            self.scale_emb = scale_emb
            self.d_model = d_model

        def forward(self, trg_seq, trg_mask, enc_output, src_mask, return_attns=False):

            dec_slf_attn_list, dec_enc_attn_list = [], []

            # -- Forward
            dec_output = self.trg_word_emb(trg_seq)
            if self.scale_emb:
                dec_output *= self.d_model ** 0.5
            dec_output = self.dropout(self.position_enc(dec_output))
            dec_output = self.layer_norm(dec_output)

            for dec_layer in self.layer_stack:
                dec_output, dec_slf_attn, dec_enc_attn = dec_layer(
                    dec_output, enc_output, slf_attn_mask=trg_mask, dec_enc_attn_mask=src_mask)
                dec_slf_attn_list += [dec_slf_attn] if return_attns else []
                dec_enc_attn_list += [dec_enc_attn] if return_attns else []

            if return_attns:
                return dec_output, dec_slf_attn_list, dec_enc_attn_list
            return dec_output,

    3.2.1 DecoderLayer的实现

    这部分的实现与Encoder的基本相同,增加的部分为enc_attn。

    我看这部分源码的时候,对于 forward 中的 slfattn ,和 encattn 的参数有一点疑问,具体的QKV的来源。

    这部分的东西在论文的3.2.3节中做了详细的解释。说明了每个注意力机制的QKV来源于那部分。

    #单个解码器层 ,包含了自注意力层,还有注意力层,以及一个前馈层。
    class DecoderLayer(nn.Module):
        ''' Compose with three layers '''

        def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
            super(DecoderLayer, self).__init__()
            self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
            self.enc_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
            self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)

        def forward(
                self, dec_input, enc_output,
                slf_attn_mask=None, dec_enc_attn_mask=None):

            dec_output, dec_slf_attn = self.slf_attn(
                dec_input, dec_input, dec_input, mask=slf_attn_mask)

            # 论文3.2.3中第一点说明,Q来自解码器上一层输出,K V 来自编码器最后一层的输出。
            dec_output, dec_enc_attn = self.enc_attn(
                dec_output, enc_output, enc_output, mask=dec_enc_attn_mask)
            dec_output = self.pos_ffn(dec_output)
            return dec_output, dec_slf_attn, dec_enc_attn

    这DecoderLay

    4.总结

    以上就是这篇论文和源码的关键部分。关于学习率以及正则化的内容可以看看原文。

  • 相关阅读:
    IP地址 子网掩码 默认网关和DNS服务器的关系
    ios下微信浏览器如何唤醒app?app已上架应用宝
    iOS: 零误差或极小误差的定时执行或延迟执行?
    iOS单例创建的一点疑惑
    Method Swizzing中一般替换方法都写在Category类别里吗?有没有别的实现方式
    相机拍照友盟检测crash是为什么?
    使用google API之前需要對input 做什麼 安全性的處理?
    关于node的聊天室错误
    Node+Deployd+MongoDB安装问题
    array.fliter无法正确过滤出我想要的数组
  • 原文地址:https://www.cnblogs.com/wys-373/p/14500558.html
Copyright © 2020-2023  润新知