当前位置: 首页 > news >正文

Transformer笔记

Transformer笔记

文章目录

  • Transformer笔记
    • 模型架构
    • 核心技术
      • 多头注意力机制
        • 概念
        • 数学概念
        • 单头注意力机制
        • 代码
      • 基于位置的前馈网络
      • 残差连接和层规范化
    • 编码器
    • 解码器

特点:Transformer模型完全基于注意力机制,没有任何卷积层或循环神经网络。之前Transformer最初应用于文本数据上的序列到序列上的学习,现在已经推广到各种现代深度学习中,例如语言、视觉、语音和强化学习领域。

模型架构

在这里插入图片描述

左边的架构为编码器,右边的架构为解码器。两者都是基于自注意力的模块叠加而成,源输入序列和目标输出序列表示将embedding(嵌入)和Positional(位置编码)相加再输入到编码器和解码器中。

Transformer的编码器除输入部分外是由多个相同的层叠加而成的,每个层都有两个子层,第一个子层为多头注意力机制,第二个子层为基于位置的前馈神经网络。每个子层都采用了残差连接,在每个子层的残差连接的加法计算后都采用了层规范化。

Transformer的解码器也是有多个相同的层叠加而成的,每个层有三个子层,在两个子层的基础上插入了一个新的子层,称为掩蔽的多头注意力机制。与编码器在多头注意力上的不同点,在多头注意力中,键和值来自于编码器的输出,查询来自与目前解码器层的输出。

我们先分开讲一下Transformer中几个核心的技术。

核心技术

多头注意力机制

概念

在实践中,如果给定了相同的查询、键和值的集合时,我们希望模型可以基于相同的注意力机制学习不同的行为,然后将不同的行为作为知识组合起来,捕获序列内各中范围的依赖关系。因此,允许注意力机制组合使用查询、键和值的不同来表示子空间可能会捕获到更丰富的信息。大白话就是,让词向量可以捕获到周围更有利于他的信息来更新他的词向量,使得他的词向量对他自身的描述更加的贴切。

譬如,在一个句子中,我们可以有不同的意思,举个例子:“我让你意思意思,没想到你没明白我的意思”。如果我们直接对意思进行词向量的表述,那么意思的词向量唯一,并不能表达出真正的语义,这时我们可以通过注意力机制,通过嵌入向量的形式来对意思的本身的词向量进行动态融合上下文的加权信息,找到一个更贴合意思的词向量。Transformer通过自注意力机制,动态融合上下文信息,为每个‘意思’生成独特的、上下文相关的表示,从而准确捕捉其实际含义。

在这里插入图片描述

多头注意力机制,顾名思义是采用了多头的这一特性,需要有多个头来学习不同的信息。假设现在我们有h个注意力头,若使用多头注意力机制我们可以将h组的不同线性投影来变换查询、键和值,然后,将h组变换后的查询、键和值将并行的送到注意力汇聚中,将h个注意力汇聚的输出连接到一起,最后通过一个可学习的线性投影进行变换,以生成最终的输出。

数学概念

在实现多头注意力之前,让我们用数学语言将这个模型形式化地描述出来。
给定查询 q ∈ R d q \mathbf{q} \in \mathbb{R}^{d_q} qRdq、键 k ∈ R d k \mathbf{k} \in \mathbb{R}^{d_k} kRdk和值 v ∈ R d v \mathbf{v} \in \mathbb{R}^{d_v} vRdv,每个注意力头 h i \mathbf{h}_i hi i = 1 , … , h i = 1, \ldots, h i=1,,h)的计算方法为: h i = f ( W i ( q ) q , W i ( k ) k , W i ( v ) v ) ∈ R p v , \mathbf{h}_i = f(\mathbf W_i^{(q)}\mathbf q, \mathbf W_i^{(k)}\mathbf k,\mathbf W_i^{(v)}\mathbf v) \in \mathbb R^{p_v}, hi=f(Wi(q)q,Wi(k)k,Wi(v)v)Rpv,

其中,可学习的参数包括 W i ( q ) ∈ R p q × d q \mathbf W_i^{(q)}\in\mathbb R^{p_q\times d_q} Wi(q)Rpq×dq W i ( k ) ∈ R p k × d k \mathbf W_i^{(k)}\in\mathbb R^{p_k\times d_k} Wi(k)Rpk×dk W i ( v ) ∈ R p v × d v \mathbf W_i^{(v)}\in\mathbb R^{p_v\times d_v} Wi(v)Rpv×dv,以及代表注意力汇聚的函数 f f f f f f是缩放点积注意力评分函数。

在这里插入图片描述

多头注意力的输出需要经过另一个线性转换,对应上图的Linear层,它对应着 h h h个头连结后的结果,因此其可学习参数是 W o ∈ R p o × h p v \mathbf W_o\in\mathbb R^{p_o\times h p_v} WoRpo×hpv

W o [ h 1 ⋮ h h ] ∈ R p o . \mathbf W_o \begin{bmatrix}\mathbf h_1\\\vdots\\\mathbf h_h\end{bmatrix} \in \mathbb{R}^{p_o}. Wo h1hh Rpo.

基于这种设计,每个头都可能会关注输入的不同部分,可以表示比简单加权平均值更复杂的函数。

单头注意力机制

下面我们来详细介绍一下这个函数:查询-关键之间的点积注意力关联,可以看做若Q和K的关联越密切说明查询与关键之间的点积值越大,为了数值稳定又同时将Q-K的点积除根号dk,再经过softmax函数,就可以得到针对于特定的Query,哪一个Key所占比例越大可以说明之间的关系越密切。

在上述的步骤我们通过softmax函数了解到,每个token查询到了关系最密切的Key,那么我们接着可以通过这个比例乘value,找到每个词向量所对应的嵌入向量,通过将原向量和嵌入向量的相加,我们即可以得到信息量更加丰富的词向量。

在这里插入图片描述
在这里插入图片描述

多头注意力机制则是在此基础上增加头的数量,使得每一个词向量都会都会经过不同的头来学习到不同的嵌入向量,再将这些嵌入向量更新到原有的此词向量上,每个头则代表学习到的不同知识。

在这里插入图片描述

在这里我们也顺带讲一下掩盖的多头注意力机制,顾名思义,掩盖就是遮住了一部分数据,不想让机器学习到后续文本的信息,即当识别第t个token时,不希望模型去学习t个token之后的token,防止后方的token来影响前方的token。

代码
#@save
class MultiHeadAttention(nn.Module):
    """多头注意力"""
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 num_heads, dropout, bias=False, **kwargs):
        super(MultiHeadAttention, self).__init__(**kwargs)
        self.num_heads = num_heads
        self.attention = d2l.DotProductAttention(dropout)
        self.W_q = nn.Linear(query_size, num_hiddens, bias=bias)
        self.W_k = nn.Linear(key_size, num_hiddens, bias=bias)
        self.W_v = nn.Linear(value_size, num_hiddens, bias=bias)
        self.W_o = nn.Linear(num_hiddens, num_hiddens, bias=bias)

    def forward(self, queries, keys, values, valid_lens):
        # queries,keys,values的形状: (batch_size,查询或者“键-值”对的个数,num_hiddens)
        # valid_lens 的形状:
        # (batch_size,)或(batch_size,查询的个数)
        # 经过变换后,输出的queries,keys,values 的形状:
        # (batch_size*num_heads,查询或者“键-值”对的个数,
        # num_hiddens/num_heads)
        queries = transpose_qkv(self.W_q(queries), self.num_heads)
        keys = transpose_qkv(self.W_k(keys), self.num_heads)
        values = transpose_qkv(self.W_v(values), self.num_heads)

        if valid_lens is not None:
            # 在轴0,将第一项(标量或者矢量)复制num_heads次,
            # 然后如此复制第二项,然后诸如此类。
            valid_lens = torch.repeat_interleave(
                valid_lens, repeats=self.num_heads, dim=0)

        # output的形状:(batch_size*num_heads,查询的个数, num_hiddens/num_heads)
        output = self.attention(queries, keys, values, valid_lens)

        # output_concat的形状:(batch_size,查询的个数,num_hiddens)
        output_concat = transpose_output(output, self.num_heads)
        return self.W_o(output_concat)

利用多头并行计算,利用转置函数来处理数据。

#@save
def transpose_qkv(X, num_heads):
    """为了多注意力头的并行计算而变换形状"""
    # 输入X的形状:(batch_size,查询或者“键-值”对的个数,num_hiddens)
    # 输出X的形状:(batch_size,查询或者“键-值”对的个数,num_heads,
    # num_hiddens/num_heads)
    X = X.reshape(X.shape[0], X.shape[1], num_heads, -1)

    # 输出X的形状:(batch_size,num_heads,查询或者“键-值”对的个数,
    # num_hiddens/num_heads)
    X = X.permute(0, 2, 1, 3)

    # 最终输出的形状:(batch_size*num_heads,查询或者“键-值”对的个数,
    # num_hiddens/num_heads)
    return X.reshape(-1, X.shape[2], X.shape[3])


#@save
def transpose_output(X, num_heads):
    """逆转transpose_qkv函数的操作"""
    X = X.reshape(-1, num_heads, X.shape[1], X.shape[2])
    X = X.permute(0, 2, 1, 3)
    return X.reshape(X.shape[0], X.shape[1], -1)

通过对多头注意力机制的了解,得知多头注意力融合了多个注意力汇聚的不同知识,这些知识的不同来源于相同的查询、键和值的不同的表示子空间。

基于位置的前馈网络

基于位置的前馈网络对序列中的所有位置的表示进行变换时使用的是同一个多层感知机(MLP),这就是称前馈网络是基于位置的(positionwise)的原因。在下面的实现中,输入X的形状(批量大小,时间步数或序列长度,隐单元数或特征维度)将被一个两层的感知机转换成形状为(批量大小,时间步数,ffn_num_outputs)的输出张量。

#@save
class PositionWiseFFN(nn.Module):
    """基于位置的前馈网络"""
    def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,
                 **kwargs):
        super(PositionWiseFFN, self).__init__(**kwargs)
        self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)
        self.relu = nn.ReLU()
        self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)

    def forward(self, X):
        return self.dense2(self.relu(self.dense1(X)))

残差连接和层规范化

使用残差连接可以有效防梯度消失,从而得到更深的网络。

层规范化和批量规范化的目标是相同的,但层规范化是基于特征维度进行规范化。批量规范化在计算机视觉中被广泛应用,但在自然语言处理任务中批量规范化通常不如层规范化的效果好。

batch_normalization对不同样本的同一维度进行归一化。

layer_normalization 对同一example的不同feature 进行归一化。

为什么使用ln而不使用bn呢?

因为在nlp处理中,会出现词向量长度不一样的情况,使用ln,可以适应不同的词向量长度。

编码器

在这里插入图片描述

在输入部分可以注意到有一个Positional Encoding的部分,这部分称做为位置编码。在文章的开始讲述到Transformer是完全基于注意力机制的,根据上述对注意力机制的了解,注意力机制并没有学习到词向量的位置信息,如果没有位置编码的话,正序和倒装句得到的词向量则可能会相同,所以我们在此输入部分增加一个位置编码来保存序列信息。

解码器

Decoder和Encoder的区别:最下层增加了一个mask的多头注意力子层。

在这里插入图片描述

masked多头注意力的原因:在输出时,一个句子的后续单词是不可知的,所以不能将后续的单词计入影响。实现mask的方法:在计算权重时,会将t时刻后的值替换为很大的负数,指数变换后为0。

再看上面的Transformer块,注意它k,v来自于编码器的结果,q来自于解码器,这里的q可以去询问每个编码器输出的量来与之结合。

参考资料:
Vaswani, Ashish, et al. “Attention is All You Need.” Advances in Neural Information Processing Systems (NIPS), 2017.
直观解释注意力机制,Transformer的核心
Transformer论文逐段精读【论文精读】

相关文章:

  • Tailwind CSS 和 UnoCSS简单比较
  • 项目2 数据可视化--- 第十五章 生成数据
  • Golang | 每日一练 (2)
  • 全面理解-c++的auto自动类型推导
  • MongoDB数据库使用及常见问题
  • 1.力扣热题100
  • 【ARM 开发】理解 BootROM:从底层启动到安全部署的指南
  • iOS 上自定义编译 FFmpeg
  • 【c++】c++内存管理
  • LeetCode 1287.有序数组中出现次数超过25%的元素:遍历
  • 20250214 随笔 Nginx 负载均衡在数据库中的应用
  • 车辆路径问题(VRP)分支定价算法中的分支策略
  • 【C++ 算法竞赛函数速查表】
  • Leetcode 3458. Select K Disjoint Special Substrings
  • 算法学习036 C++最长上升子序列LIS 动态规划DP算法实现最长上升子序列 中小学算法思维学习 比赛算法题解 信奥算法解析
  • Wireshark 输出 数据包列表本身的值
  • SpringBoot中集成SaToken
  • vue3开发打年兽功能
  • 【论文笔记】On Generative Agents in Recommendation
  • DeepSeek 本地部署方法介绍
  • 新修订的《餐饮业促进和经营管理办法》公布,商务部解读
  • “免签圈”扩容,旅游平台:今年以来巴西等国入境游订单显著增加
  • 国际奥委会举办研讨会,聚焦如何杜绝操纵比赛
  • 风雨天涯梦——《袁保龄公牍》发微
  • 220名“特朗普币”持有者花1.48亿美元,获邀与特朗普共进晚餐
  • 习近平在中拉论坛第四届部长级会议开幕式的主旨讲话(全文)