【AI-NLP】Transformer理论及源码理解

news2024/10/10 8:23:35

文章目录

  • 提出背景
  • 总体结构
    • Encoder
    • Decoder
  • 细节再探
  • 源码阅读
    • 主函数部分
    • 主模型定义
    • 编码器
    • 编码层
    • 多头自注意力层
    • 前馈神经网络层
    • Masked
    • 解码器
    • 解码层
  • 参考资料

提出背景

改进了RNN的训练慢的缺点,利用self-attention机制实现并行计算。并且Transformer可以增加到非常深的深度,充分发掘DNN模型的特性,提升模型准确率。同时使用注意力机制更容易关注序列的全局特征,解决了RNN不善处理长距离依赖的问题
本质上是一个seq2seq的模型,因此可用于机器翻译,语音辨识,语音合成等问题

总体结构

整体结构由编码器与解码器组成,一个编码器中可有若干个编码层

注意,Encoder中的参数各不相同

Encoder

编码器的作用在于,输入一个向量,输出一个等长的向量

要点:

  1. 使用多头注意力机制,关注全局输入向量的资讯
  2. 使用了残差及layer normalization,避免出现梯度消失问题
  3. 使用前向神经网络,每个位置的单词对应的前馈神经网络都完全一样

Self Attention

query,key,value全由输入向量自身计算得到的一种注意力机制。每一个输入向量分别对应了查询、键、值权重矩阵,用于生成查询、键、值向量,权重矩阵为训练参数

Decoder

输入有两部分

  • 编码器最后一层的输出作为key和value
  • 同时解码器自己的输入经过多头自注意力的计算作为query

输出为对应的结果序列

结构与编码器类似,唯一的不同在于Masked Multi-Head Attention使用了掩码机制,不再关注全局的资讯,而是只考虑了当前位置以及当前位置之前的信息,存在时序性

细节再探

  1. Teacher forcing:将真实标签作为decoder的输入,依次预测结果,相当于把正确答案给解码器

  2. 掩码作用:考虑到Teacher forcing已经将正确答案输入到模型,因此在每次预测一个token时,需要将其及其之后的token先掩盖,保证训练任务和预测任务在执行自回归时,是同样的形式

  1. d k d_k dk是query和key向量的维度
    在计算注意力权重时除以 d k d_k dk,避免了随着 d k d_k dk的增大,即维度增大时,计算出的权重出现过大(softmax后的值非0即1)的情况,从而保留了数据原始的分布,可以使得梯度回传时更加平稳
  1. 使用layer normalize而非batch normalize:ln是针对一个样本内部做归一化;bn针对的是各个样本的特征做归一化,在文本长度变化较大时,这种归一化方式会造成均值方差抖动明显
  1. 多头self attention意在让根据词向量得出的query, key, value向量,进入不同的线性层(子空间),分别进行自注意力计算,捕捉全局丰富的语义信息,再将所有结果concat,拼接回原有的形状,最后再进入一个线性层得到结果
  1. 在decoder中计算自注意力时,query来自带有掩码的自注意力计算得出,而key和value来自encoder最后的输出,目的是让Decoder端的单词(token)给予Encoder端对应的单词(token)“更多的关注(attention weight)”
  1. 自注意力是基于全局信息进行计算,而忽略了时序信息(导致任意打乱句子中token的顺序,结果不变),位置编码PE解决了这一问题。PE的长度与embedding相同,使二者可以直接相加。
    计算方式是根据token的文本中的位置,PE向量中偶数位使用sin计算,奇数位使用cos计算。由于三角函数公式不受序列长度的限制,也就是可以对比所遇到序列的更长的序列进行表示

源码阅读

主函数部分

确定了模型的输入部分,由两部分组成encoder的输入,decoder的输入
将token转换成id后以tensor形式传入模型

P代表填充码,S代表起始,E代表结束。对文本进行了id映射
模型参数初始化,选用交叉熵损失函数,Adam自适应学习率优化器

主模型定义

class Transformer(nn.Module):
    def __init__(self):
        super(Transformer, self).__init__()
        self.encoder = Encoder()
        self.decoder = Decoder()
        self.projection = nn.Linear(d_model, tgt_vocab_size, bias=False)

    def forward(self, enc_inputs, dec_inputs):
        enc_outputs, enc_self_attns = self.encoder(enc_inputs)
        dec_outputs, dec_self_attns, dec_enc_attns = self.decoder(dec_inputs, enc_inputs, enc_outputs)
        dec_logits = self.projection(dec_outputs) # dec_logits : [batch_size x src_vocab_size x tgt_vocab_size]
        return dec_logits.view(-1, dec_logits.size(-1)), enc_self_attns, dec_self_attns, dec_enc_attns

由编码器,解码器及全连接层组成
编码器输入1*5的张量,输出为1*5*512的张量(批量大小为1,文本长度为5,embedding向量维度为512),及注意力权重参数6*8*5*5
(6代表编码器中编码层的数量;8代表8头注意力,计算了8次,有8个子空间;5*5代表文本内部的相关性计算的结果)
解码器输入为编码器的输出及解码层输入文本
输出为注意力计算得出的结果(1*5*512),及解码器掩码自注意力权重参数及交互注意力权重参数
将解码器的输出作为全连接层的输入,通过全连接层将512维向量压缩成7维,对应翻译文本的长度(5*7)

编码器

class Encoder(nn.Module):
    def __init__(self):
        super(Encoder, self).__init__()
        # 词向量5*512
        self.src_emb = nn.Embedding(src_vocab_size, d_model)
        # 位置编码6*512
        self.pos_emb = nn.Embedding.from_pretrained(get_sinusoid_encoding_table(src_len+1, d_model), freeze=True)
        self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)])

    def forward(self, enc_inputs):  # enc_inputs : [batch_size x source_len] [1*5]
        enc_outputs = self.src_emb(enc_inputs) + self.pos_emb(torch.LongTensor([[1, 2, 3, 4, 0]]))
        enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)
        enc_self_attns = []
        for layer in self.layers:
            enc_outputs, enc_self_attn = layer(enc_outputs, enc_self_attn_mask)
            enc_self_attns.append(enc_self_attn)
        return enc_outputs, enc_self_attns

定义词向量,位置向量,并将二者相加,传入编码层中迭代
位置向量由当前token所在文本中的位置计算得出

# n_position为文本长度,d_model为总向量维度
def get_sinusoid_encoding_table(n_position, d_model):
    def cal_angle(position, hid_idx):
        return position / np.power(10000, 2 * (hid_idx // 2) / d_model)
    def get_posi_angle_vec(position):
        return [cal_angle(position, hid_j) for hid_j in range(d_model)]

    # 6*512
    sinusoid_table = np.array([get_posi_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)

编码层

由多头自注意力层,以及前向神经网络组成
QKV均来自编码层输入的embedding
此时输入输出均为1*5*512

class EncoderLayer(nn.Module):
    def __init__(self):
        super(EncoderLayer, self).__init__()
        self.enc_self_attn = MultiHeadAttention()
        self.pos_ffn = PoswiseFeedForwardNet()

    def forward(self, enc_inputs, enc_self_attn_mask):
        # enc_input: torch.Size([1, 5, 512])
        # enc_inputs to same Q,K,V
        enc_outputs, attn = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs, enc_self_attn_mask)
        enc_outputs = self.pos_ffn(enc_outputs)  # enc_outputs: [batch_size x len_q x d_model]
        # torch.Size([1, 5, 512])
        return enc_outputs, attn

多头自注意力层

class MultiHeadAttention(nn.Module):
    def __init__(self):
        super(MultiHeadAttention, self).__init__()
        self.W_Q = nn.Linear(d_model, d_k * n_heads)
        self.W_K = nn.Linear(d_model, d_k * n_heads)
        self.W_V = nn.Linear(d_model, d_v * n_heads)
        self.linear = nn.Linear(n_heads * d_v, d_model)
        self.layer_norm = nn.LayerNorm(d_model)

    def forward(self, Q, K, V, attn_mask):
        # q: [batch_size x len_q x d_model], k: [batch_size x len_k x d_model], v: [batch_size x len_k x d_model]
        # attn_mask: [1 x 5 x 5]
        residual, batch_size = Q, Q.size(0)
        # (B, S, D) -proj-> (B, S, D) -split-> (B, S, H, W) -trans-> (B, H, S, W)
        # torch.Size([1, 8, 5, 64])
        q_s = self.W_Q(Q).view(batch_size, -1, n_heads, d_k).transpose(1, 2)  # q_s: [batch_size x n_heads x len_q x d_k]
        k_s = self.W_K(K).view(batch_size, -1, n_heads, d_k).transpose(1, 2)  # k_s: [batch_size x n_heads x len_k x d_k]
        v_s = self.W_V(V).view(batch_size, -1, n_heads, d_v).transpose(1, 2)  # v_s: [batch_size x n_heads x len_k x d_v]

        attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1) # attn_mask : [batch_size x n_heads x len_q x len_k]

        # context: [batch_size x n_heads x len_q x d_v], attn: [batch_size x n_heads x len_q(=len_k) x len_k(=len_q)]
        context, attn = ScaledDotProductAttention()(q_s, k_s, v_s, attn_mask)
        context = context.transpose(1, 2).contiguous().view(batch_size, -1, n_heads * d_v) # context: [batch_size x len_q x n_heads * d_v]
        output = self.linear(context)
        return self.layer_norm(output + residual), attn # output: [batch_size x len_q x d_model]

定义了QKV矩阵与编码层输入相乘

前馈神经网络层

在前向神经网络层中,使用一维卷积进行计算
原因在于当kernel_size=1,stride=1,padding=0时,与MLP的节点计算方式一样,变相实现了一个MLP
nn.Conv1d的输入数据格式只能以三维tensor[batch, channel, length]输入,与nn.Linear输入数据格式不同;
并且 nn.Conv1d的数据作用位置也不同,nn.Conv1d作用在第二个维度channel上,而nn.Linear作用于第三个维度

class PoswiseFeedForwardNet(nn.Module):
    def __init__(self):
        super(PoswiseFeedForwardNet, self).__init__()
        self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1)
        self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1)
        self.layer_norm = nn.LayerNorm(d_model)

    def forward(self, inputs):
        residual = inputs  # inputs : [batch_size, len_q, d_model]
        # nn.Conv1d作用于第2个维度,因此要交换位置
        output = nn.ReLU()(self.conv1(inputs.transpose(1, 2)))
        output = self.conv2(output).transpose(1, 2)
        return self.layer_norm(output + residual)

Masked

为解码层的自注意力加入掩码机制,使其无法查看当前及当前以后的序列

def get_attn_subsequent_mask(seq):
    attn_shape = [seq.size(0), seq.size(1), seq.size(1)]
    subsequent_mask = np.triu(np.ones(attn_shape), k=1)
    subsequent_mask = torch.from_numpy(subsequent_mask).byte()
    return subsequent_mask

解码器

class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        # 7*512
        self.tgt_emb = nn.Embedding(tgt_vocab_size, d_model)
        self.pos_emb = nn.Embedding.from_pretrained(get_sinusoid_encoding_table(tgt_len+1, d_model), freeze=True)
        self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)])

    def forward(self, dec_inputs, enc_inputs, enc_outputs):  # dec_inputs : [batch_size x target_len]
        dec_outputs = self.tgt_emb(dec_inputs) + self.pos_emb(torch.LongTensor([[5, 1, 2, 3, 4]]))
        dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs)
        dec_self_attn_subsequent_mask = get_attn_subsequent_mask(dec_inputs)
        # 将掩码转换为bool类型
        dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask + dec_self_attn_subsequent_mask), 0)

        dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs)

        dec_self_attns, dec_enc_attns = [], []
        for layer in self.layers:
            dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_outputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask)
            dec_self_attns.append(dec_self_attn)
            dec_enc_attns.append(dec_enc_attn)
        return dec_outputs, dec_self_attns, dec_enc_attns

解码层

定义了两层注意力,分别为自注意力和交互注意力
其中自注意力为masked
交互注意力中,Q来自decoder,KV来自encoder

class DecoderLayer(nn.Module):
    def __init__(self):
        super(DecoderLayer, self).__init__()
        self.dec_self_attn = MultiHeadAttention()
        self.dec_enc_attn = MultiHeadAttention()
        self.pos_ffn = PoswiseFeedForwardNet()

    def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask):
        dec_outputs, dec_self_attn = self.dec_self_attn(dec_inputs, dec_inputs, dec_inputs, dec_self_attn_mask)
        dec_outputs, dec_enc_attn = self.dec_enc_attn(dec_outputs, enc_outputs, enc_outputs, dec_enc_attn_mask)
        dec_outputs = self.pos_ffn(dec_outputs)
        return dec_outputs, dec_self_attn, dec_enc_attn

参考资料

  1. 李沐Transformer论文逐段精读【论文精读】
  2. 李宏毅Transformer
  3. Transformer从零详细解读(可能是你见过最通俗易懂的讲解)
  4. 源码请见:graykode/nlp-tutorial

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/190740.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用css实现简易报警灯

主题 用css来实现一个简易的报警灯效果 实现效果 实现思路 实现的核心是一个灯罩和一个灯芯。灯罩主要是使用了border-radius圆角边框,灯芯主要是radial-gradient径向渐变。再使用动画效果来实现一闪一闪的效果。让我们来一步一步实现效果。 灯罩实现 因为大…

剑指Offer 第18天 I. 二叉树的深度 II. 平衡二叉树

目录 剑指 Offer 55 - I. 二叉树的深度 剑指 Offer 55 - II. 平衡二叉树 剑指 Offer 55 - I. 二叉树的深度 输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度…

2025年突破百万套/年,又一个前装赛道成为行业新风口

汽车智能化,不仅仅是增量,还有存量升级。 作为驾驶员观察道路状况的关键车载部件,汽车后视镜(基于凸面镜)最早出现在1906年,随后逐步成为所有车辆的基本标配。不过,由于镜面设计的缺陷&#xf…

【ROS学习】节点运行管理launch文件的基本操作

launch文件的概念和作用 launch 文件是一个 XML 格式的文件,可以启动本地和远程的多个节点,还可以在参数服务器中设置参数。 launch文件的作用是:简化节点的配置与启动,提高ROS程序的启动效率。 使用场景 launch文件在ros中使用还…

mybatis中获取插入数组的主键值(自增主键,非增主键),mp

1.自增主键的第一种写法 <insert id"addKey" ><!--通过mybatis框架提供的selectKey标签获得自增产生的ID值--><selectKey resultType"java.lang.Integer" order"AFTER" keyProperty"id">select LAST_INSERT_ID()<…

【Azure 架构师学习笔记】-Azure Logic Apps(5)- 标准和使用量类型的区别

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Logic Apps】系列。 接上文【Azure 架构师学习笔记】-Azure Logic Apps&#xff08;4&#xff09;-演示2 前言 在做实验的过程中&#xff0c;发现使用“使用量”&#xff08;Consumption)类型会出现很多问题&#xff…

碰到个阿里p8,终于知道了别人为什么这么强,算是见识到了基础的天花板

基础不行一切都是浮云&#xff0c;想要建成高楼大厦&#xff0c;必需基础牢固 各大论坛和社区里也看见不少小伙伴慷慨地分享了常见的面试题和八股文&#xff0c;为此咱这里也统一做一次大整理和大归类&#xff0c;这也算是划重点了。 俗话说得好&#xff0c;他山之石&#xf…

Pointet++ Tutorial

目录 .0 前言 .1 Contributions .2 Solutions 2.1 Set Abstraction&#xff08;SA&#xff09; .3 Structure of Network 3.1 Sample layer 3.2 group 3.3 PointNet layer 3.4 分类任务在提取特征后是怎么操作的&#xff0c;loss是什么 3.5 分割任务中如何进行上采…

electron+vite+vue3.0+frorge最新打包流程

这里写自定义目录标题简介安装打包更多配置配置vue项目多平台打包mac osforge配置项简介 文章更新与2023年2月1日。 electron官网&#xff1a;https://www.electronjs.org electronforge官网&#xff1a;https://www.electronforge.io 环境要求&#xff1a;git、node14版本以…

Secure Hash Algorithm-3 (SHA-3) family

参考文献&#xff1a; Bertoni G, Daemen J, Peeters M, et al. Keccak[C]//Advances in Cryptology–EUROCRYPT 2013: 32nd Annual International Conference on the Theory and Applications of Cryptographic Techniques, Athens, Greece, May 26-30, 2013. Proceedings 32…

1分钟快速解读什么是实时渲染

古语云&#xff1a;工欲善其事必先利其器&#xff0c;在现代化的行业细分更加精确&#xff0c;不同行业为了实现更高的效率&#xff0c;也都有自己的专用软件工具或者设备。在数字孪生、虚拟仿真、设计类等行业也是如此&#xff0c;在创建3D模型时也是需要用到Unreal\Unity\May…

iptables 在 Android 抓包中的妙用

本文介绍一种在 Andorid 中实现单应用、全局、优雅的抓包方法。 此文于去年端午节编写&#xff0c;由于种种原因&#xff0c;当时藏拙并未发布。现删除一些敏感信息后分享出来&#xff0c;希望对各位有所启发。 背景 昨天在测试一个 Android APK 的时候发现使用 WiFi 的 HTTP …

软考高级系统架构师背诵要点---质量属性与架构评估

质量属性与架构评估 质量属性&#xff1a; 1.性能&#xff1a;指系统的响应能力&#xff0c;即要经过多长时间才能对某个事件做出响应&#xff0c;或者在某段时间内系统所能处理的事件个数 代表参数&#xff1a;响应时间、吞吐量 设计策略&#xff1a;优先级调度、资源调度…

【MyBatis】| MyBatis查询语句专题(核心知识)

目录 一&#xff1a;MyBatis查询语句专题 1. 返回Car对象 2. 返回List<Car> 3. 返回Map 4. 返回List<Map> 5. 返回Map<String,Map> 6. resultMap结果映射 7. 返回总记录条数 一&#xff1a;MyBatis查询语句专题 前期准备&#xff1a; 模块名&#xf…

为什么计算机需要操作系统?

当计算机只运行一个程序时&#xff0c;只需将所有的资源(CPU、内存、磁盘等)分配给这一个程序就行&#xff1b;当计算机同时运行2个或以上程序时&#xff0c;操作系统就需要充当一下角色&#xff1a; 资源分配器资源隔离与秩序维护者细节屏蔽者 一. 资源分配器 计算机的硬件…

Springboot整合第三方技术及整合案例

Springboot整合第三方技术一、Springboot整合Junit1、步骤2、classes属性二、整合Mybatis1、步骤2、常见问题三、整合Mybatis-plus1、步骤2、常见配置四、整合Druid1、步骤五、整合案例-数据层&#xff08;基础的CRUD&#xff09;1、创建springboot项目手工导入starter坐标2、配…

【正点原子FPGA连载】第二十六章gpio子系统简介 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Linux开发指南

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第二十六章gpio子…

NVDLA Xilinx FPGA Mapping

Lei WangLeiWang1999要当世界第一&#xff01;78357联系我常用的链接1. 1. 硬件系统设计概述1.1. 1.1 RTL 生成1.2. 1.2 IP Package1.2.1. 1.2.1 csb2apb1.2.2. 1.2.2 关闭 Clock Gating1.2.3. 1.2.3 IP Package1.3. 1.3 Block Design1.4. 1.4 Generate Bit HDF1.5. 1.5 Sanity…

java基础一JVM之JRE、JDK、解释器、编译器详解

1.JVM、JRE和JDK区别 1.JVM&#xff08; Java Virtual Machine &#xff09;&#xff1a; Java虚拟机&#xff0c;它是整个 Java 实现跨平台的最核心的部分&#xff0c;所有的 Java 程序会首先被编译为 .class 的类文件&#xff0c;这种类文件可以在虚拟机上执行&#xff0c;…

3.10-动态规划-01背包问题

问题描述&#xff0c;给定n种物品和一个背包。物品 i 的重量是 wi &#xff0c;其价值为 vi &#xff0c;背包的容量为 c &#xff0c;问应该如何选择装入背包中的物品&#xff0c;使得装入背包的物品总价值最大&#xff1f; 写在前面 dp数组的含义--dp[i][j]表述容量为j 已经…