zoukankan      html  css  js  c++  java
  • 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.总结

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

  • 相关阅读:
    Algebra, Topology, Differential Calculus, and Optimization Theory For Computer Science and Machine Learning 第4章 读书笔记(待更新)
    Algebra, Topology, Differential Calculus, and Optimization Theory For Computer Science and Machine Learning 第3章 读书笔记(待更新)
    Algebra, Topology, Differential Calculus, and Optimization Theory For Computer Science and Machine Learning 第1,2章 读书笔记(待更新)
    Tkinter的Message组件
    Git 实操/配置/实践
    mysq5.7.32-win安装步骤
    行为型模式之模板方法
    结构型模式之组合模式
    结构型模式之享元模式
    结构型模式之外观模式
  • 原文地址:https://www.cnblogs.com/wys-373/p/14500558.html
Copyright © 2011-2022 走看看