【AIGC入门一】Transformers 模型结构详解及代码解析

Transformers 开启了NLP一个新时代,注意力模块目前各类大模型的重要结构。作为刚入门LLM的新手,怎么能不感受一下这个“变形金刚的魅力”呢?

目录

Transformers ——Attention is all You Need

背景介绍

模型结构

位置编码

代码实现:

Attention

Scaled Dot-product Attention

Multi-head Attention

Position-Wise Feed-Forward Networks

Encoder and Decoder

Add & Norm

mask 机制

参考链接


论文链接:Attention Is All You Need

Transformers ——Attention is all You Need

背景介绍

        在Transformer提出之前,NLP主要基于RNN、LSTM等算法解救相关问题。这些模型在处理长序列时面临梯度消失和梯度爆炸等问题,且这些模型是串行计算的,运行时间较长。

        Transformer 模型的提出是为了摆脱序列模型的顺序依赖性,引入了注意力机制,使得模型能够在不同位置上同时关注输入序列的各个部分,且支持并行计算。该模型的提出对深度学习和自然语言处理领域产生了深远的影响,成为了现代NLP模型的基础架构,并推动了attention 机制在各种任务中的应用。

模型结构

位置编码

        任何一门语言,单词在句子中的位置以及排列顺序是非常重要的。一个单词在句子的位置或者排列顺序不同,整个句子的意义就发生了偏差。举个例子:

小明小王500块

小王小明500块

顺序不同,债主关系就发生变化了😑

        当采用了Attention之后,句子中的词序信息就会丢失,模型就没法知道每个词在句子中的相对和绝对的位置信息。目前位置编码有多种方法:

(1)整型值标记位置,即第一个token标记为1, 第二个token标记为2。。。以此类推

         可能存在的问题:

  • 随着序列长度的增加,位置值会越来越大;
  • 推理的序列长度比训练时所用的序列长度更长,不利于模型的泛化

(2)用[0,1] 范围标记位置

        将位置值的范围限制在[0,1]之内,即在第一种的方法进行归一化操作(除以序列长度)。比如有4个token,那么位置信息就是[0, 0.33, 0.69, 1]。 但这样产生的问题是,当序列长度不同时,token间的相对距离是不一样的。

        因此,一个好的位置编码方法应该满足以下特性:

(1)可以表示一个token 在序列中的绝对位置;

(2)在序列长度不同的情况下,不同序列中token 的相对位置/ 距离要保持一直;

(3)可以扩展到更长的句子长度;

        Transformers 中选择的是sincos编码法,其公式如下所示:

        其中,pos 是token在sentence中的位置,i是维度。

代码实现:

        假设句子长度是 s, embedding的维度是d, 最终生成的PE的shape是(s, d)。公式的核心是计算pos /10000\tfrac{2i}{d_{model}}, 这里可以借助对数和指数的性质进行如下操作:

a = e^{^{loga}}

        所以可以转换成1/ 10000^{^{2i/d_{model}}} = e^{ - log(10000) * 2i /d_{model})}(可对照代码进行推导理解)

class Position_Encoding(nn.Module):
    
    def __init__(self, max_length, d_model):
        self.max_length = max_length
        self.dim = d_model
        
        pe = torch.zeros(self.max_length, self.dim)
        
        position = torch.arange(0, self.max_length).unsqueeze(1)
        
        div_term = torch.exp( torch.arange(0, self.dim ,2) * (-1) *math.log(10000) / self.dim)
        
        
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        

    def forward(self, x):
        
        #  input_embedding + position_encoding
        #....

Attention

        Attention 是将query 和key、value映射为输出值,其中query 和 key 计算一个相似度,然后以这个相似度为权重,计算value的加权和,最终得到输出。

Scaled Dot-product Attention

        论文中用的是放缩点乘注意力(scaled dot-product attention),其公式是:

Attention(Q, K, V) = softmax(\frac{QK^{}{T}}{sqrt(d_k))})

其中, 计算时需要用到的矩阵Q(查询),K(键值),V(值)是输入单词的embedding 变换或者 上一个Encoder block的输出 。注意Q、K、V的shape 会存在一定的联系(因为需要做矩阵乘法运算)。

        公式中会除以dk的平方根,从而避免内积过大。还有解释是说 softmax 在 绝对值较大的区域梯度较小,梯度下降的速度比较慢。因此希望softmax的点乘数值尽可能小。

        论文中解释了为什么点积会变大。假设q 和 k中的元素满足独立分布,且均值是0,方差为1。点积 q*k = \sum_{i=1}^{dk} q_i * k_i   的均值是0, 方差是dk 。

Multi-head Attention

        作者发现,相比直接在dmodel 维度上的 q、k、v进行attention计算 ,使用不同的、可学习的linear function 分别地对q、k、v 进行多次映射(映射的维度是dk, dk, dv)  , 然后对每一组映射的q、k、v进行attention 并行计算,并concat得到最终输出。后一种方法更有效。就像卷积层可以用多个卷积核生成多个通道的特征,在Transformers中可以用多组self attention 生成多组注意力的结果,从而增加特征表示。其计算公式和流程图如下:

        注意: head的数量 * 每一组head中q的维度 = dmodel (输入Q的维度)

Position-Wise Feed-Forward Networks

        前馈网络比较简单,是一个两层的全连接层,第一层的激活函数是ReLU,第二层不使用激活函数,对应的公式如下所示:

Encoder and Decoder

       Transformer 从结构上可以分为Encoder 和Decoder 两个部分,这两者结构上比较类似,但也存在一些差异。

        上图红色区域对应的是Encoder部分,可以看出是由 Input Embedding 、Position Encoding 和6层的EncoderLayer组成。 EncoderLayer 主要包括Multi-head Attention, Add&Norm, Feed Forward ,Add&Norm。

        上图绿色区域对应的是Decoder部分,相比Encoder,需要注意Decoder中的Multi-head Attention 有所不同。首先是Masked Multi-head Attention, 是为了实现串行推理;第二个Multi-head Attention输入的Q、K、V来自不同的地方,其中Q是Masked Multi-head Attention 的输出, K和V是Encoder 的输出。

Add & Norm

        这部分主要由Add 和 Norm 组成,其计算公式如下所示:

        Add 是一种残差结构,和ResNet中的是一样的,可以帮助网络收敛。Norm 是指Layer Norm。

mask 机制

        Transformers中比较重要的一个知识点就是mask设置。mask主要来源有两个:第一个是填充操作的空白字符(为了保证batch内句子的长度一样会进行padding操作);第二个是因为模拟串行推理需要用到mask(Decoder部分)。

        一般情况下, query 和 key都是一样的,但是在Decoder的第二个多头注意力层中,query 来自目标语言,key来自源语言。为了生成mask, 首先要知道query 和 key中<pad> 字符的分布情况,它们的形状为[n, seq_len]。如果某处是True, 表明这个地方的字符是<pad>。

src_pad_mask = x == pad_idx
dst_pad_mask = y == pad_idx

        为了实现串行推理,即某字符只能知道该字符以及该字符之前的内容,即一个下三角全1矩阵。mask矩阵需要取反,实现方式如下所示:

mask = 1 - torch.tril(torch.ones(mask_shape))

        最后根据<pad>字符分布情况分别将mask对应的行或者列置1。

参考链接

  1. GitHub - P3n9W31/transformer-pytorch: Transformer model for Chinese-English translation.
  2. PyTorch Transformer 英中翻译超详细教程 - 知乎
  3. Transformer模型详解(图解最完整版) - 知乎

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我可以为您提供基于TensorFlow 2.0的transformers模型的搭建方案。首先,您需要安装TensorFlow 2.0和transformers库,您可以通过以下命令使用pip安装: ``` pip install tensorflow==2.0.0 pip install transformers ``` 接下来,您可以参考以下代码来搭建transformers模型: ``` import tensorflow as tf from transformers import TFDistilBertModel, DistilBertConfig # 配置模型参数 config = DistilBertConfig(output_hidden_states=True) # 加载预训练模型 model = TFDistilBertModel.from_pretrained('distilbert-base-uncased', config=config) # 输入数据 input_ids = tf.keras.Input(shape=(128,), dtype=tf.int32, name='input_ids') attention_mask = tf.keras.Input(shape=(128,), dtype=tf.int32, name='attention_mask') # 获取模型输出 outputs = model(input_ids, attention_mask=attention_mask) # 添加一个全连接层 x = tf.keras.layers.Dense(768, activation='relu')(outputs[0][:, 0, :]) # 添加一个分类层 outputs = tf.keras.layers.Dense(2, activation='softmax')(x) # 构建模型 model = tf.keras.Model(inputs=[input_ids, attention_mask], outputs=outputs) # 编译模型 model.compile(optimizer=tf.keras.optimizers.Adam(), loss='categorical_crossentropy', metrics=['accuracy']) ``` 这个例子使用了DistilBert模型,您可以通过更改`TFDistilBertModel`和`DistilBertConfig`来使用其他transformers模型。同时,您也可以根据您的任务需求更改全连接层和分类层的结构。 希望这个例子对您有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值