Transformer

首先,Transformer 论文:Attention Is All You Need 必不可少

其次,Transformer 图解:https://jalammar.github.io/illustrated-transformer/ 也是必不可少

Transformer整体结构

上图就是Transformer的核心结构由编码器解码器构成:

编码器(Encoder)的主要任务是将输入序列(如一句话或一段文本)转换为一个高维的语义表示(即上下文向量),捕捉输入序列中的语法、语义和上下文信息。即将人说的话转换成机器能理解的模式

解码器(Decoder)则根据编码器生成的上下文表示,逐步生成目标序列(如翻译后的句子或生成的文本)。即按需求将机器语言转换成人听得懂的语言

所以整个模型要实现的就是一个 人话->机器语言->人话的过程

编码器

编码器实现的是将人话转换成机器语言的过程,从整体工作流程上来看可以分为以下几个主要阶段

  • 输入嵌入(Input Embedding)
    • 将输入的词序列(通常是单词或子词)映射为固定维度的向量表示(Embedding)。
    • 添加位置编码(Positional Encoding) 以保留输入序列的位置信息(因为 Transformer 本身没有顺序感知能力)。
  • 多头自注意力机制(Multi-Head Self-Attention)
    • 计算每个词与其他词之间的关系(即注意力权重),从而捕捉全局上下文信息。
    • 多头机制允许模型从不同角度关注输入序列的不同部分。
  • 前馈神经网络(Feed-Forward Network, FFN)
    • 对每个词的表示进行非线性变换,进一步提取特征。
  • 残差连接与层归一化(Residual Connection & Layer Normalization)
    • 在每一层中添加残差连接和层归一化,以缓解梯度消失问题并加速训练。
  • 堆叠多层(Stacked Layers)
    • 编码器通常由多个相同的层堆叠而成(例如 6 层),每一层都会逐步增强输入序列的表示。

详细模型结构图如下:

输入嵌入(Input Embedding)

一句话来概括在这个阶段的主要作用是将“阿里云-专有云”等词语转换成计算机所认识的语言向量,两个核心词向量位置信息。此阶段的核心内容是将词汇转换成机器所识别的向量并带上位置信息(为什么要带上位置信息呢,因为语言的表述是有强烈的顺序关系的,比如“你是我哥”和“我是你哥”的文字组成都一样,但是词汇出现的先后顺序差别表明了两个意思)。

  • 词向量:一串定长矩阵,便于计算机理解运算,一般我们会通过word2vec或者jieba分词来搞定这块的功能将文字转换成向量
  • 位置信息:一句话是由多个词汇组成的,前后的语序对整个意义有较大的影响所以会生成一个矩阵信息用来表示词汇的顺序

通过词向量位置信息的叠加,在这个阶段我们得到了一个带有位置信息的机器所能理解的语句。

多头自注意力机制(Multi-Head Self-Attention)

一句话来概括此阶段的主要功能是在输入向量的基础上,通过多维度的学习让模型能更加精准的理解句子中词语之间的关系。

  • 自注意力机制(self Attention)

在第一阶段生成向量的基础上,在此阶段会由三个矩阵$ W_q,W_k,W_v$与第一阶段得到的向量进行线性相乘得到三个关键向量,查询向量(Queries) Q、键向量(Keys) K、值向量(Values) V。

  • 查询:代表目标词的向量,用于查询与输入序列中哪些词相关
  • 键:代表输入序列中每个词的向量,用于被查询匹配
  • 值:也是代表输入序列中每个词的向量,一旦词的重要性(通过键与查询的匹配)被确定,其值向量将被用来计算最终的输出
  1. 计算相似度

模型需要判断目标词(查询)输入序列中每个词(键)之间的相关性,这是通过计算查询向量与每个键向量之间的点积来实现,点积越大,表示两个向量越相似,也就意味着输入中的这个词与目标词越相关

PS:在同一个向量空间中,具有相同意思的词比较接近,而点积就是衡量这两个向量在方向上的一致性

  1. 转换为注意力权重

由于点积的结果可能非常大,为了将其转换为一个合理的概率分布,即每个词的重要性权重,会对点积结果应用Softmax函数。Softmax能够确保所有计算出来的权重加起来等于1,每个权重的值介于0~1之间,这样,每个输入词的权重就代表了它对于目标词的相对重要性。

在这个阶段我们介绍了Q、K、V三个向量与\(W_q,W_k,W_v\)三个矩阵,具体含义如下:

变量名 含义 作用 类比
查询向量Q 当前关注的内容 表示当前元素对其余元素的兴趣 问题关注点(如某段话的主语是谁?)
键向量K 属于元素的身份特征 输入元素可被查询的关键信息 类似于词语标签(主语、谓语等)
值向量V 输入元素的实际内容 表示这个元素所提供的具体信息 结果或答案
$ W_q $ 初始化随机生成,后通过反向传播和梯度下降算法来优化的线性映射矩阵 生成查询向量Q 提问工具(明确Q的关注点)
$ W_k $ 初始化随机生成,后通过反向传播和梯度下降算法来优化的线性映射矩阵 生成键向量K 标签生成器(识别词向量的身份信息)
$ W_v $ 初始化随机生成,后通过反向传播和梯度下降算法来优化的线性映射矩阵 生成值向量V 内容提取器(获取词向量的实际含义)

在得到这些基础向量后,注意力权重计算和注意力加权求和自注意力机制的核心就在于关注度,所以我们需要得到每个元素与其他元素之间的相关性。

整个公式可以表示为:

\[SA(Q, K, V) = Softmax(\frac{QK^T}{\sqrt{d_k}})V \]

注意力权重计算:

\[Softmax(\frac{QK^T}{\sqrt{d_k}}) \]

这个阶段的核心目标是通过计算衡量出输入语句中各个元素之间的相关性(一般相关性越高越需要关注)。

对于这个公式中的每个元素其含义如下:

  • \(Q\): 由第一阶段每个词向量生成的查询向量Q组成的矩阵,形状大小\((n,d_k)\) 其中n为词向量数量,向量长度为\(d_k\)
  • \(K\): 第一阶段生成的键向量K组成的矩阵,形状大小\((n,d_k)\)
  • \(Q * K^T\): 每个词向量的Q与K进行矩阵点积运算的结果矩阵大小\((n,n)\) 表明两两元素之间的相似度
  • \(\sqrt{d_k}\): 一般称为缩放因子,\(d_k\)为键(Key)的维度,主要作用是为了防止点积运算的结果过大导致的梯度不稳定问题
  • \(Softmax\): 归一化函数,将缩放后的点积运算结果转换成概率分布,也就是注意力权重,累加之和为1

最后通过\(Softmax\)得到的矩阵就类似于一个注意力二维数组,里面每一个值表示相应两个元素之间的注意力大小也就是关注程度。

注意力加权求和

此阶段的核心目标是通过我们得到的元素之间的注意力权重矩阵与V向量矩阵进行相乘来得到最终的输出表达。将注意力权重矩阵与值向量矩阵V相乘,即为一种信息聚合操作,每个元素的输出表达都包含了其他元素的实际内容。

用代码实现上述过程:

import torch
import torch.nn.functional as F
# 假设我们已经有了每个词的嵌入向量,这里用简单的随机向量代替真实的词嵌入
# 假设嵌入大小为 4
embed_size = 4
# 输入序列 "我 喜欢 学习 机器 学习" 的嵌入表示
inputs = torch.rand((5, embed_size))
print("Inputs (Embeddings):", inputs)

# 假设 "machine" 的查询向量
query_machine = torch.rand((1, embed_size))
print("Query (Machine):", query_machine)

def attention(query, keys, values):
    # 计算查询和键的点积,除以根号下的嵌入维度来缩放
    scores = torch.matmul(query, keys.transpose(-2, -1)) / (embed_size ** 0.5)
    # 应用softmax获取注意力权重
    attn_weights = F.softmax(scores, dim=-1)
    # 计算加权和
    output = torch.matmul(attn_weights, values)
    return output, attn_weights

output, attn_weights = attention(query_machine, inputs, inputs)
print("Output (Attention applied):", output)
print("Attention Weights:", attn_weights)

结果如下:

为什么这个公式有效?

  • 点积运算的几何意义
    \(Q \cdot K\) 计算的是两个向量的 余弦相似度 (当向量归一化时):
    点积值越大 -> 方向相似 -> 相似性高
    点积值越小 -> 方向不同 -> 相似性低
  • Softmax的作用:竞争性选择
    Softmax创建一个"赢家通吃"的竞争环境:
原始分数: [2.1, 0.3, 1.8]  # "吃"对["猫", "吃", "鱼"]的关注度
softmax后: [0.45, 0.05, 0.50]  # 形成概率分布

保持相对大小关系
放大重要信号,抑制次要信号
确保权重总和为1,便于解释

  • 缩放因子的必要性:\(\sqrt{{d_k}}\)
    当向量维度\(d_k\)很大时,点积的方差会增大:
# 假设d_k=64,每个维度值在[-1,1]之间
点积方差 ≈ d_k × Var(每个维度) = 64 × (1/3) ≈ 21.3

# 经过softmax后,大的值会接近1,小的值接近0
# 导致梯度消失问题!

# 除以√d_k后:
缩放后的方差 ≈ (d_k × Var) / d_k = Var ≈ 1/3
# 梯度更加稳定!
  • 多头注意力机制

核心思想是将注意力机制“分头”进行,即在相同的数据上并行地运行多个注意力机制,然后将它们的输出合并 。这种设计允许模型在不同的表示子空间中捕获信息,从而提高了模型处理信息的能力

Transformer默认8个头,其工作过程如下:

  1. 分割: 对于每个输入,多头注意力首先将查询、键和值矩阵分割成多个“头”。这是通过将每个矩阵分割成较小的矩阵来实现的,每个较小的矩阵对应一个注意力“头”。假设原始矩阵的维度是 \(d_{model}\),那么每个头的矩阵维度将是 \(\frac{d_{model}}{h}\) ,其中 h 是头的数量。
  2. 并行注意力计算:对每个头分别计算自注意力。由于这些计算是独立的,它们可以并行执行。这样每个头都能在不同的表示子空间中捕获输入序列的信息。
  3. 拼接和线性变换:所有头的输出再被拼接起来,形成一个与原始矩阵维度相同的长矩阵。最后,通过一个线性变换调整维度,得到多头注意力的最终输出。

在实际数学模型运行态时,上述的权重计算与加权求和会以并行的方式来执行提升效率并获取到更多的文字内容特征。

计算每个词与其他词之间的关系(即注意力权重),从而捕捉全局上下文信息

多头机制允许模型从不同角度关注输入序列的不同部分

残差连接与层归一化(Residual Connection & Layer Normalization)

残差连接

残差连接是指在自注意力层后把这一层处理过的数据和这一层的原始输入相加,这种方式允许模型在增加额外处理层的同时,保留输入信息的完整性,从而在不损失重要信息的前提下,学习到输入数据的复杂特征。

神经网络层数过多过深时,其就有可能出现梯度消失或梯度爆炸的问题(通俗来说就是数值过小或过大而使计算误差过大),残差连接主要通过引入“跳跃连接”的概念通过将输入直接透传给后面的网络层来保证数据的有效传播。例如当前输入为\(x\),若无残差传递则通过该层计算函数\(F(x)\)变换后得到的输出为\(y\),可以表示为:

\[y=F(x) \]

在这种情况下,当\(F(x)\)变化过大或过小时,会导致下一层得到的输入\(y\)得到的数据有效性大打折扣,也就是说的出现了梯度消失和爆炸问题,因此残差链接改良了此公式在透传$ F(x) \(的同时带上了\) x $,让整体透传公式变为了:

\[y=F(x)+x \]

这种改进让模型更专注于去学习$ F(x) \(的增量部分的效果,额不是直接学习整个映射流。若\) F(x) \(效果差的话也能通过残差链接的模式传递\) x $来避免整个模型网络的性能降低。

  • 层归一化

在神经网络中,激活值的分布会随着训练的进行而发生变化(内部协变量偏移问题),从而导致整个训练变得不稳定。层归一化通过对每个样本的所有特征做归一化的操作,使均值为0、方差为1来稳定激活值的分布。通俗来说层归一化的作用就是将每个样本的特征做一个收敛,调整到一个可控的标准范围之内,防止其过度发散导致模型失效。即保证网络的每一层都在相似的数据分布上工作,从而提高模型训练的稳定性和速度。

前馈神经网络(Feed-Forward Network, FFN)

前馈全连接网络(FFN)对每个位置的表示进行独立的非线性变换,提升了模型的表达能力。

通过两次线性映射和一个中间的ReLU激活函数,FFN引入了必要的非线性处理,使模型能够捕捉更复杂的数据特征。这一过程对于加强模型对序列内各元素的理解至关重要,提高了模型处理各种语言任务的能力。

当某一层的输入为\(x\), 权重矩阵为\(W\),偏移量为\(b\),激活函数为\(f\)时,此层的输出可以表示为:

\[y=f(W*x + b) \]

\(W*x + b\): 表示线性变换的值

\(f\): 非线性的激活函数ReLU、Sigmoid、Tanh等

在Transformer中FFN由两层全连接层组成,使用ReLU的激活函数,所以其反馈神经网络的公式可以表示为:

\(FFN(x)=max(0,x*W_1+b_1)*W_2+b_2\)

其中,第一层的主要作用是将特征进行放大以提取更加精确的表示,第二层是进行收敛至原有的大小。

编码器小结

四个要点:

  • 1 表示位置编码,方便处理序列顺序。
  • 2 表示两层:自注意力层和前馈网络层。
  • 1 表示每一层进入下一层前都需要进行Add & Norm操作

解码器

在看解码器之前,看一个更详细的架构图,解码器多了一层:编码-解码注意力层(Encoder-Decoder Attention)

解码器是Transformer的另一重要组成,其主要功能就是对编码器阶段转换得到的包含语义等信息的向量矩阵进行解析,来输出对应的生成序列通俗来说就是将机器话转换成我们需要的人话,常见的应用场景如机器翻译、文本生成和语音识别等等。

解码器的组成主要由

  • 掩码多头自注意力机制(Masked Multi-Head Self-Attention)
  • 编码器-解码器注意力机制(Encoder-Decoder Attention)
  • 前馈神经网络(Feed-Forward Neural Network, FFN)

每层的后面都会接一个残差连接和层归一化做收敛,其中FFN、残差连接和层归一化我们已经在前文介绍过了,这边重点介绍掩码多头自注意力机制(Masked Multi-Head Self-Attention)和编码器-解码器注意力机制(Encoder-Decoder Attention)

解码器的工作流程主要有五步:输入表示、掩码多头自注意力、跨注意力模块

输入表示

和编码器类似解码器的输入表示也由词向量信息和位置编码组合来实现,不同的是编码器的输入主要专注于理解源序列,解码器则专注于生成目标序列,以“阿里云政企事业部专有云团队”来作为输入举例,如果做翻译任务的话。

编码器输入(分词处理后):[阿里云,政企事业部,专有云团队]
编码器输出(上下文的向量):[X1,X2,X3,X4]

解码器输入(启始值提示模型开始生成):[<sos>]   ->输出预测 “Alibaba Cloud”
解码器输入(结合前文输入Alibaba Cloud):[<sos>,Alibaba Cloud]  ->输出预测 “Government and Enterprise Business Unit”
解码器输入(结合前文):[<sos>,Alibaba Cloud,Government and Enterprise Business Unit]  ->输出预测
 “Dedicated Cloud Team”
最终的输出序列:[Alibaba Cloud,Government and Enterprise Business Unit,Dedicated Cloud Team]

掩码多头自注意力机制

此模块的主要作用就是对模型做一定程度上的屏蔽,让其只能关注于我们的目标序列中当前及之前词元的信息,通俗来说就是我用手把目标序列后面的输入遮住了让机器学会根据我前面说的话来预测我后面将说的话。

解码器的自注意力层设计得比较特别,与编码器的自注意力层不同,解码器的自注意力层需要处理额外的约束,即保证在生成序列的每一步仅依赖于之前的输出,而不是未来的输出。这是通过一个特定的掩蔽(masking)技术来实现的。解码器自注意力层的几个关键点。

  1. 处理序列依赖关系,解码器的自注意力层使每个输出位置可以依赖于到目前为止在目标序列中的所有先前位置。这允许模型在生成每个新词时,综合考虑已生成的序列的上下文信息。
  2. 掩蔽未来信息,为了确保在生成第 $ t $ 个词的时候不会使用到第 $ t+1 $ 及之后的词的信息,自注意力层使用一个上三角掩蔽矩阵,在实现中通常填充为负无穷或非常大的负数。这保证了在计算Softmax时未来位置的贡献被归零,从而模型无法“看到”未来的输出。
  3. 动态调整注意力焦点,通过学习的注意力权重,模型可以动态地决定在生成每个词时应更多地关注目标序列中的哪些部分。

掩码的实现原理也较为简单,即在计算注意力权重矩阵时,将未来位置的值设置为负无穷便可实现该位置的权重为0,公式表示可以写作如下表达,其中\(M\)为掩码矩阵,用于屏蔽未来信息

\[SA(Q, K, V)=Softmax(\frac{QK^T}{\sqrt{d_k}} + M)V \]

实现如下:

import torch
import torch.nn.functional as F

def decoder_self_attention(query, key, value, mask):
    """
    解码器自注意力层,带掩蔽功能。

    参数:
    - query, key, value: 形状为 (batch_size, seq_len, embed_size) 的张量
    - mask: 形状为 (seq_len, seq_len) 的张量,用于防止未来标记的注意力

    返回:
    - attention output: 形状为 (batch_size, seq_len, embed_size) 的张量
    - attention weights: 形状为 (batch_size, seq_len, seq_len) 的张量
    """
    # 计算缩放点积注意力分数
    d_k = query.size(-1) # 键向量的维度
    scores = torch.matmul(query, key.transpose(-2, -1)) / torch.sqrt(torch.tensor(d_k, dtype=torch.float32))

    print(f"mask: {mask}")
    # 应用掩蔽(将未来的标记设置为极大的负数以排除它们)
    scores = scores.masked_fill(mask, float('-inf'))
    print(f" {scores}")

    # 应用softmax获取注意力权重
    attention_weights = F.softmax(scores, dim=-1)
    print(f" {attention_weights}")

    # 使用注意力权重和值向量乘积得到输出
    attention_output = torch.matmul(attention_weights, value)
    print(f" {attention_output}")
    return attention_output, attention_weights

# 示例用法
batch_size = 1
seq_len = 3
embed_size = 64
query = torch.rand(batch_size, seq_len, embed_size)
key = torch.rand(batch_size, seq_len, embed_size)
value = torch.rand(batch_size, seq_len, embed_size)

# 生成掩蔽矩阵以阻止对未来标记的注意(使用上三角矩阵掩蔽)
mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1).bool()

# 调用函数
output, weights = decoder_self_attention(query, key, value, mask)
print("输出形状:", output.shape)
print("注意力权重形状:", weights.shape)

举例:

以下述输入为例:

# 示例用法
batch_size = 1
seq_len = 3
embed_size = 2
query = torch.rand(batch_size, seq_len, embed_size)
key = torch.rand(batch_size, seq_len, embed_size)
value = torch.rand(batch_size, seq_len, embed_size)
  1. 缩放点积分数:$ \frac{QK^T}{\sqrt{d_k}} $
tensor([[[0.0574, 0.2420, 0.2665],
         [0.1056, 0.4472, 0.4736],
         [0.0223, 0.0888, 0.1499]]])
  1. 应用掩码
 tensor([[[0.0574,   -inf,   -inf],
         [0.1056, 0.4472,   -inf],
         [0.0223, 0.0888, 0.1499]]])
  1. Softmax权重
 tensor([[[1.0000, 0.0000, 0.0000],
         [0.4154, 0.5846, 0.0000],
         [0.3120, 0.3335, 0.3545]]])
  1. 输出
tensor([[[0.6632, 0.4471],
         [0.8317, 0.6403],
         [0.7820, 0.5028]]])

解码器中的自注意力层至关重要,因为它不仅提供了处理序列内依赖关系的能力,还确保了生成过程的自回归性质,即在生成当前词的时候,只依赖于之前已经生成的词。这种机制使Transformer模型非常适合各种序列生成任务,如机器翻译、文本摘要等。

之所以有这种机制,是因为自注意力机制允许当前位置的输出与未来位置的输入产生关联,从而导致数据泄露和信息泄露的问题。而推理阶段,是不可能读到未来信息的,这样可能会导致模型在训练和推断阶段表现不一致,以及模型预测结果的不稳定性。

编码器-解码器注意力机制

编码-解码注意力层(Encoder-Decoder Attention Layer)是一种特殊的注意力机制,用于在解码器中对输入序列(编码器的输出)进行注意力计算。这个注意力层有助于解码器在生成输出序列时对输入序列的信息进行有效整合和利用,注意,这个注意力层关注的是全局的注意力计算,包括编码器输出的信息序列和解码器内部的自注意力计算。

此模块的作用主要是允许解码器关注编码器的输出(即源序列生成的上下文信息), 通过组合解码器的查询矩阵Q与编码器的键矩阵K、值矩阵V,解码器就可以动态获取编码器中的相关信息。通俗来说就是机器现在要生成语句时,能够根据原先解析数据中的语句的语义与关系来推断出下面生成哪几个词的概率较大。**

公式表达如下:

\[Attention(Q_{decoder},K_{encoder}, V_{encoder})=Softmax(\frac{Q_{decoder}K^{T}_{encoder}}{\sqrt{d_k}}+ M)V_{encoder} \]

  • $ Q_{decoder}K^{T}_{encoder} $: 用于计算解码器查询矩阵与编码器键矩阵之间的相似度的点积运算
  • $ \sqrt{d_k} $: 一般称为缩放因子,主要作用是为了防止点积运算的结果过大之后导致的梯度不稳定问题
  • Softmax: 归一化函数,将缩放后的点积运算结果转换成概率分布,也就是注意力权重,累加之和为1
  • \(V_{encoder}\): 使用注意力权重矩阵和\(V_{encoder}\)进行加权求和,得到最终的上下文向量

Transformer 解码器的核心在于其自注意力机制和与编码器的交互能力。通过掩码机制它能够在生成过程中保持因果关系;通过编码器-解码器注意力,它能够充分利用源序列的信息

那它与上面讲的解码器自注意力层有什么区别呢?

  1. 信息来源不同:编码-解码注意力层用在解码器(Decoder)中,将解码器当前位置的查询向量与编码器(Encoder)的输出进行注意力计算,而解码自注意力层用于解码器自身内部,将解码器当前位置的查询向量与解码器之前生成的位置的输出进行注意力计算。
  2. 计算方式不同:编码-解码注意层计算当前解码器位置与编码器输出序列中所有位置的注意力分数。这意味着解码器在生成每个输出位置时,都可以综合考虑整个输入序列的信息。解码自注意力层计算当前解码器位置与之前所有解码器位置的输出的注意力分数。这使得解码器可以自我关注并利用先前生成的信息来生成当前位置的输出。

用一句话总结就是:编码-解码注意层关注整个编码器输出序列,将编码器的信息传递给解码器,用于帮助解码器生成目标序列,解码自注意力层关注解码器自身先前生成的位置的信息,用于帮助解码器维护上下文并生成连贯的输出序列

Transformer的优势

并行处理能力

与传统的循环神经网络(RNN)和长短期记忆网络(LSTM)不同,Transformer完全依赖于自注意力机制,消除了序列处理中的递归结构,允许模型在处理输入数据时实现高效的并行计算。这使得训练过程大大加速,特别是在使用现代GPU和TPU等硬件时。Position Encoding的作用,就是为序列添加位置编码,以便在并行处理完以后,进行合并。

捕捉长距离依赖

Transformer通过自注意力机制能够捕捉序列中的长距离依赖关系。在自然语言处理中,这意味着模型可以有效地关联文本中相隔很远的词汇,提高对上下文的理解。

灵活的注意力分布

多头注意力机制允许Transformer在同一个模型中同时学习数据的不同表示。每个头可以专注于序列的不同方面,例如一个头关注语法结构,另一个头关注语义内容。

可扩展性

Transformer模型可以很容易地扩展到非常大的数据集和非常深的网络结构。这一特性是通过模型的简单可堆叠的架构实现的,使其在训练非常大的模型时表现出色。

当然还有其他特性,比如适用性、通用性等,总的来说,Transformer通过其独特的架构设计在效率、效果和灵活性方面提供了显著的优势,使其成为处理复杂序列数据任务的强大工具。

posted @ 2025-08-06 22:03  牛犁heart  阅读(48)  评论(0)    收藏  举报