# transformer

天才在左 (BERT),疯子在右 (GPT)

transformer 最初由 Google 于 2017 年提出,并且发了一篇经典论文 (attention is all you need),最开始用于机器翻译任务,后续发现在图像,视频,音频上都十分有效。是继 CNN,RNN 后另一 nb 模型。

对于 RNN 来说,计算隐藏状态 $$h_t$$ 必须依赖于上一个状态 $$h_{t-1}$$, 使得并行度较差。可能会丢弃很早期的 ht 信息。

https://arxiv.org/html/1706.03762v7

https://github.com/tensorflow/tensor2tensor

Transformer 是一种避免重复出现的模型架构,而是完全依赖注意力机制来绘制输入和输出之间的全局依赖关系。Transformer 允许显著提高并行化水平,并且在 8 个 P100 GPU 上经过短短 12 小时的训练后,就可以达到翻译质量的新水平。

Transformer 是第一个完全依赖自我注意来计算其输入和输出的表示,而不使用序列对齐 RNN 或卷积的转导模型。

先科普一些前置知识~

# 矩阵运算

**1、** 转置

E_1 = \begin{bmatrix} 1 \\ 0 \\ 0 \\ 0 \end{bmatrix}$$,$$E_1^{T} = [1,\, 0,\, 0,\, 0]

2、运算

x1=E1+P1=[1000]+[0.00.10.20.3]=[1.00.10.20.3]\mathbf{x}_1 = \mathbf{E}_1 + \mathbf{P}_1 = \begin{bmatrix}1 \\ 0 \\ 0 \\ 0\end{bmatrix} + \begin{bmatrix}0.0 \\ 0.1 \\ 0.2 \\ 0.3\end{bmatrix} = \begin{bmatrix}1.0 \\ 0.1 \\ 0.2 \\ 0.3\end{bmatrix}

x1=[0.50.20.01.01.00.00.20.1][1.00.10.20.3]=[0.51.0+0.20.1+0.00.2+1.00.31.01.0+0.00.1+0.20.2+0.10.3]=[0.821.07]x_1 = \begin{bmatrix} 0.5 & 0.2 & 0.0 & 1.0 \\ 1.0 & 0.0 & 0.2 & 0.1 \end{bmatrix} \begin{bmatrix} 1.0 \\ 0.1 \\ 0.2 \\ 0.3 \end{bmatrix} = \begin{bmatrix}0.5\cdot1.0 + 0.2\cdot0.1 + 0.0\cdot0.2 + 1.0\cdot0.3 \\1.0\cdot1.0 + 0.0\cdot0.1 + 0.2\cdot0.2 + 0.1\cdot0.3 \end{bmatrix} = \begin{bmatrix}0.82 \\ 1.07\end{bmatrix}

矩阵乘法 $$(m\times n)\cdot (n\times k) => (m\times k)$$ ,上图例子 2 行 4 列矩阵 点乘 4 行 1 列矩阵,结果是 2 行 1 列的矩阵。如果第一个矩阵的列数不等于第二个矩阵的行数,那么无法执行点乘操作。

# softmax

假设输入向量为 $$(\mathbf {z} = [z_1, z_2, \cdots, z_k])$$,其中 k 是向量的维度

σ(zi)=ezij=1kezjfor i=1,2,,k\sigma(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{k} e^{z_j}} \quad \text{for } i = 1, 2, \cdots, k

  • 输出范围:输出的每个元素都在 $$(0, 1)$$ 区间内,因为指数函数 $$e^z_i}$$ 总是大于 0 的,并且分母 $$\sum_{j=1}{k e^{z_j}$$ 是所有指数项的和,也大于 0。
  • 总和为 1:$$\sum_i=1}{k \sigma (z_i) = 1$$,这使得 Softmax 函数的输出可以被解释为概率分布。
  • 单调递增:对于输入向量中的每个元素 $$z_i$$,$$\sigma (z_i)$$ 是关于 $$z_i$$ 的单调递增函数。也就是说,$$z_I$$ 越大,$$\sigma (z_i)$$ 的值就越大。

在神经网络的最后一层,通常会使用 Softmax 函数将网络的输出转换为概率分布,每个类别的概率表示样本属于该类别的可能性。然后可以选择概率最大的类别作为预测结果。

假设 $$z=[0.5,0.8,0.6]$$,$$\sigma (z_1)=\frace{0.5}e{0.5+e^0.8}+e{0.6} = P(y = 1|\vec{z}) $$

# Seq2Seq

序列到 **** 序列模型(Sequence-to-Sequence,Seq2Seq)的输入是一个序列,输出也是一个序列,这种模型在语音和语言领域的任务中非常常见,例如:

  • 机器翻译:输入 “Deep learning”,输出 “深度学习”。
  • 语音识别:输入 “一段音频声波信号”,输出一段文字,如 “深度学习”。
  • 问答与对话:输入 “中国的首都是哪个城市?”,输出 “北京”。

Seq2Seq 模型的输入包括两部分数据:需要分类的评论语句 “这个行李箱不够结实,用几次轮子就坏了” 和人工加入的提示问题 “这个评论是正向还是负向?” 模型的输出是分类的标签 “负向”。由此可见,一个典型的文本分类问题,可以通过加提示词的方式编程一对问答,转化成了序列到序列的任务。

# 网络结构

Transformer 是一个基于多头自注意力的序列到序列的模型,包括编码器和解码器两部分,左边是编码器,右边是解码器,都是层次堆叠式的结构,图中的 Nx 代表有 N 个 encode layer 和 N 个 decode layer

image-20250701014647147

对于 encode layer 和 decode layer 中的其他部分,我们可以先忽略不看,抽象出如下的结构。此时主要的结构就只剩四个部分,分别是:inputs,Nx encode layer,Nx decode layer,和 outputs。后面会逐一拆解这四个部分。

image-20250701014700314

# 网络结构 - inputs

对于输入到 encode layer 中的 inputs,会经历两个步骤,即先进行 embedding,进行分词后获取特征向量,在根据位置编码,将 embedding 后的向量和位置编码叠加,得到最终的 encode layer 输入。一般称为这层为嵌入层。

image-20250701014709921

举例,假设目前有一句话:“我爱机器学习”。分词之后,变为了 机器学习 。这里的分词可以简单理解为 tokenizer,将文本分割成一个个独立单元,即 token。可以参考豆包的分词器 (https://console.volcengine.com/ark/region:ark+cn-beijing/tokenCalculator)。但是不同的模型有不同的分词器,会导致分词结果不一致。分词后,经过 embedding,这四个词分别对应了 $$x_1,x_2,x_3,x_4$$ 这四个特征向量

豆包 - 1.5 分词器

image-20250701014717907

Deepseek-R1 分词器

image-20250701014725042

# 网络结构 - outputs

transformer 的输出,是预测下一个词的概率。概率通过前文提到的 softmax 得出

举例,比如目前已经有的输出是 我爱 ,那么下一个词的概率预测可能如下。此时概率最高的为机器,选择概率最高的 Token 作为预测的下一个单词。当然这只是举例,实际会涉及到词表等其他内容。

机器 xxx 学习
0.05 0.7 0.05 0.2

# 网络结构 - encode-decode layer

Seq2Seq 模型往往采用编码器 - 解码器(Encoder-Decoder)的结构设计,主要应用于异步的序列到序列的深度学习任务。其中编码器负责理解和抽象输入序列的信息,然后传递给解码器生成输出序列。解码器生成输出序列时需要两部分信息输入:一是编码器传递过来的抽象了全局输入的信息;二是解码器在上一个阶段的输出信息。

image-20250701014732741

基于编码器 - 解码器结构实现的机器翻译任务示意,模型的输入是 “Deep learning”,编码器会先将这个输入计算成向量表示,解码器则会逐字逐句的生成输出:深度学习。在这个过程中,当解码器生成 “度” 时,它的输入既包含编码器传递过来的向量表示,又包含解码器上一阶段生成的输出 “深”,依此类推。其中<BOS > 是一个特殊的向量,表示编码器启动工作。

image-20250701014739121

Seq2Seq 模型使用完整的编码器 - 解码器结构完成模型构建,但编码器 - 解码器也经常单独使用。编码器常被用于语义提取任务,可以得到比 Word2Vec 模型更丰富的语义信息。

# 网络结构 - encode layer

Transformer 编码器由 N 个编码层堆叠而成,嵌入层输出的字符级别的特征表示首先被传入第一层的 Encoder Layer,经过自注意计算后,得到向量序列 [o1,o2,o3,o4]。这个输出会被作为输入,传递到上一层的 Encoder Layer,每层的 Encoder Layer 的计算逻辑是相同的,依此类推,最后一层的输出被视为 Transformer Encoder 输出的特征编码向量。

每个编码层包含四个模块:多头注意力层(Multi-Head Attention)、加与规范层(Add&Norm)、前馈层(Feed Forward)和加与规范层(Add&Norm)。其中多头注意力层的核心计算逻辑是自注意力机制

image-20250701014750925

# 网络结构 - decode layer

与编码器基本一致,但是多头注意力会变为带掩码的多头注意力。带掩码的多头注意力只根据当前位置和之前的输入片段来计算影响力,会盖住后面的词。在生成模型的场景,我们还不知道后面会生成哪些内容,只知道之前生成了哪些内容。

其余内容和 encode layer 中基本一致,只是输出的时候加了线性变换和 softmax 输出了下一个词的概率。

到这里你已经是精通 transformer 的大佬啦,快休息一下吧 (

接下里,我们会深入拆解多头注意力这个模块,来解释为什么 attention is all you need

# 什么是多头注意力

# Q,K,V

自注意力模型(Self-Attention Model)的设计思想来源于解决循环神经网络在解决长序列数据时遇到的问题:

1)如何计算更全局的信息依赖,而不局限于距离的远近?

2)如何使得计算可以并行化,而不是只能串行进行?

为了解决如上两个问题,自注意力模型采用查询 - 键 - 值(Query-Key-Value,QKV)的模式。对于输入序列 $$\mathbf X=[\mathbfx}_1,…,\mathbf{x}_L]\in \mathbb{R}{L\times D_{}$$,每一个输入 $$x_i$$ 都有三个向量表示:查询向量 $$\mathbfq}_i\in \mathbb{R}{D_{k}$$、键向量 $$\mathbfk}_i\in \mathbb{R}{D_{k}$$ 和值向量 $$\mathbfv}_i\in \mathbb{R}{D_{v}$$。

# 计算 k,q,v

同样是上面的例子,我爱机器学习,分词后,四个 token 都变为向量,即 embedding 操作,假设这里维度为 4,那么可以得到 4 个 4*1 的矩阵。实际在 BERT 中,维度一般为 768。接着定义三个可学习的权重矩阵

  • W_Q \in \mathbb{R}^{d_{model} \times d_k}$$:用于将输入转换为Q矩阵。这里$$d_{model} $$是指维度,此处为4。
  • W_V \in \mathbb{R}^{d_{model} \times d_v}$$:用于将输入转换为V矩阵。

通过矩阵乘法将输入 $$\mathbf {X}$$ 分别与 $$W_Q$$、$$W_K$$ 、$$W_V$$ 相乘,得到 Q、K、V 矩阵:

  • 计算 Q 矩阵:$$\mathbfQ} = \mathbf{X}W_Q \in \mathbb{R}{n \times d_k$$,$$n \times dk$$, 其中 n 代表矩阵的行数,$$d_k$$ 代表矩阵的列数 (即输入向量的维度)
  • 计算 K 矩阵:$$\mathbfK} = \mathbf{X}W_K \in \mathbb{R}{n \times d_k$$
  • 计算 V 矩阵:$$\mathbfV} = \mathbf{X}W_V \in \mathbb{R}{n \times d_v$$

这里的矩阵乘法是逐行进行的,即对于输入序列中的每个词向量,都与相应的权重矩阵相乘,得到对应的 Q、K、V 向量。

此时 对应 $$Q_1,K_1,V_1$$,同理 学习 对应 $$Q_4,K_4,V_4$$

# attention

其中 $$q_i$$ 和 $$k_i$$ 用来计算序列中其它输入对本输入的影响力,$$v_i$$ 是本输入的向量表示,输入 xj 对输入 xi 的影响力使用 $$q_j \cdot {k_i}$$ 表示。影响力越大,说明处理输入 xi 的时候需要重点考虑输入 xj 的信息。这样模型在处理当前输入时,它的注意力可以在整个输入序列上不受约束的扫描,从而得到对处理当前内容最有帮助的信息。

image-20250701014803096

模型在处理输入 xi 时,先计算其它输入对 xi 的影响 $$(q_j \cdot k_i})$$,用 $$\alpha_{ij}$$ 表示;然后将 $$\alpha_{ij}$$ 经过 Softmax 后得到的值 $$\hat {\alpha}_{ij}$$ 与值向量 vi 相乘,就得到 xi 经过整个序列信息影响后的向量表示 zi,即 $$\mathbf {z}_i = \sum_{j = 1}{L \hat{\alpha}_{ij} \mathbf{v}_j$$

\alpha_{ij}$$代表在第一个词视角里,第一个词和第二个词相似度是多少。 在该例子里,`我`分别和其他几个词做点积,得到$$\alpha_{11},\alpha_{12},\alpha_{13},\alpha_{14}$$,然后经过softmax之后,再分别和$$v_1,v_2,v_3,v_4$$相乘,在相加,得到$$z_1$$。 此时z1包含了全部的上下文信息 在反过来看这张图,此时分词后输入的四个词向量$$x_1,x_2,x_3,x_4$$就变为了包含有上下文信息的向量$$z_1,z_2,z_3,z_4$$,z1-z4就是head attention 的输出内容 现在对比论文中的公式,就能明白什么是注意力了。唯一不同的是,这个公式中除以了$$\sqrt{d_k}$$,进行缩放。 $$\text{Attention}(Q, K, V) = \text{softmax}(\frac{QK^T}{\sqrt{d_k}})V

image-20250701014810094

那么为什么要缩放,为什么要乘以 k 的转置 (这两条看不懂可以直接跳过)

1、因为矩阵运算规则,第一个矩阵列数不等于第二个矩阵的行数的时候,不能计算点积

假设 Q 的维度是 $$(m, d_k)$$,K 的维度是 $$ (n, d_k)$$,$$K^T$$ 的维度就变为 $$(d_k, n)$$ ,那么 $$ (QK^T)$$ 得到的结果矩阵维度是 $$(m, n) $$。通过 $$(QK^T) $$ 计算,能衡量不同词之间的关联程度

2、除以 $$ \sqrt {d_k}$$ 主要是为了解决点积运算在高维空间中可能出现的数值不稳定问题

  • 点积结果随维度增大而增大:在高维空间中,当 (Q) 和 (K) 的维度 (d_k) 较大时,(Q) 和 (K) 做点积 (QK^T) 后,结果的数值范围会随着维度的增大而增大。假设 (Q) 和 (K) 中的元素是独立同分布的随机变量,且均值为 0、方差为 1。根据方差的性质,两个向量点积的方差会随着向量维度 (d_k) 的增加而线性增大。也就是说,维度越高,点积的结果可能会变得非常大。
  • Softmax 函数饱和:在计算注意力分数时,需要对 (QK^T) 的结果应用 Softmax 函数来得到注意力权重。Softmax 函数的表达式为 (\textsoftmax}(x_i) = \frac{e{x_i}\sum_{j=1}{n e^x_j}}\),当 \(x_i\) 的值非常大时,\(e{x_i) 会变得极其大,可能会导致数值溢出。即使没有溢出,Softmax 函数也会趋近于饱和状态,使得大部分权重集中在少数几个值上,其他值的权重几乎为 0。这样会使得注意力分布变得非常尖锐,模型难以学习到有效的信息。
  • 梯度消失:在反向传播过程中,Softmax 函数的饱和会导致梯度变得非常小,使得模型的参数更新非常缓慢,甚至可能出现梯度消失的问题,从而影响模型的训练效果和收敛速度。

最后来看这张图,下半部分就能理解啦

image-20250701014816648

# 位置编码

刚刚讲解 attention 的时候,一直没有提位置编码,只是说了位置编码和输入编码叠加后作为输入。

Transformer 编码器使用自注意力机制处理数据,虽然能够大幅度加强模型的并行计算能力,但是其忽略了文本序列中 token 之间的位置关系,从而导致建模的过程中出现偏差。因此需要使用位置编码(Position Embedding)进行校正,标记输入的文本序列中每个 token 的位置,这个信息对于理解语言是很关键的。例如在机器翻译中,“我喜欢你” 和 “你喜欢我” 词相同但顺序不同,语义迥异,位置编码能让模型感知这种顺序差异,正确翻译。

Transformer 使用三角函数(正弦或者余弦)来编码位置信息。假设位置编码的维度为 D,则每一维的值的计算公式为

\begin{align} PE(pos, 2i) = \sin\left(\frac{t}{10000^{2i/D}}\right)\\ PE(pos, 2i + 1) = \cos\left(\frac{t}{10000^{2i/D}}\right) \end{align}

其中 t 是指当前词在句子中的位置,$$0⩽i⩽\frac {D}{2}$$ 为编码向量的维数。在偶数维,使用正弦编码;在奇数维,使用余弦编码

其中 pos 表示位置索引(从 0 开始 ), i 表示维度索引(从 0 开始 ) 。

只要输入序列长度不变,维度不变,那么生成的位置编码矩阵就是相同的。本质就是在输入向量的基础上加了一个行数列数相同的矩阵。

用 python 可以表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np

# 序列长度
n = 4
# 嵌入维度
d_model = 8

# 初始化位置编码矩阵
positional_encoding = np.zeros((n, d_model))

# 计算位置编码
for pos in range(n):
for i in range(0, d_model, 2):
# 偶数维度
positional_encoding[pos, i] = np.sin(pos / (10000 ** (i / d_model)))
# 奇数维度
positional_encoding[pos, i + 1] = np.cos(pos / (10000 ** (i / d_model)))

# 打印位置编码矩阵
print(positional_encoding)

举个例子,仍然是我 爱 机器 学习,输入序列长度为 4,每个序列维度为 8,此时是 4 个 $$8 \times 1$$ 的矩阵

具体计算过程

  • 计算 (pos = 0) 时的编码值
    • 对于偶数维度 2i (i = 0 时, 2i = 0):

PE(0,0)=sin(0100002×0/8)=sin(0)=0PE(0, 0) = \sin\left(\frac{0}{10000^{2\times0/8}}\right) = \sin(0) = 0

  • 对于奇数维度 2i + 1 (i = 0 时, 2i + 1 = 1):

PE(0,1)=cos(0100002×0/8)=cos(0)=1PE(0, 1) = \cos\left(\frac{0}{10000^{2\times0/8}}\right) = \cos(0) = 1

  • 继续计算其他维度:

当 i = 1, 2i = 2 时,

PE(0,2)=sin(0100002×1/8)=sin(0)=0PE(0, 2) = \sin\left(\frac{0}{10000^{2\times1/8}}\right) = \sin(0) = 0

当 i = 1 , 2i + 1 = 3 时,

PE(0,3)=cos(0100002×1/8)=cos(0)=1PE(0, 3) = \cos\left(\frac{0}{10000^{2\times1/8}}\right) = \cos(0) = 1

当 i = 2, 2i = 4 时,

PE(0,4)=sin(0100002×2/8)=sin(0)=0PE(0, 4) = \sin\left(\frac{0}{10000^{2\times2/8}}\right) = \sin(0) = 0

当 i = 2 , 2i + 1 = 5 时,

PE(0,5)=cos(0100002×2/8)=cos(0)=1PE(0, 5) = \cos\left(\frac{0}{10000^{2\times2/8}}\right) = \cos(0) = 1

当 i = 3, 2i = 6 时,

PE(0,4)=sin(0100002×3/8)=sin(0)=0PE(0, 4) = \sin\left(\frac{0}{10000^{2\times3/8}}\right) = \sin(0) = 0

当 i = 3, 2i + 1 = 7 时,

PE(0,5)=cos(0100002×3/8)=cos(0)=1PE(0, 5) = \cos\left(\frac{0}{10000^{2\times3/8}}\right) = \cos(0) = 1

得到第一行 $$[\text {0 1 0 1 0 1 0 1}]$$

  • 计算 pos = 1 时的编码值
    • 对于偶数维度 2i ( i = 0 时, 2i = 0 ):

PE(1,0)=sin(1100002×0/8)=sin(1)0.84147PE(1, 0) = \sin\left(\frac{1}{10000^{2\times0/8}}\right) = \sin(1) \approx 0.84147

  • 对于奇数维度 2i + 1 ( i = 0 时, 2i + 1 = 1 ):

PE(1,1)=cos(1100002×0/8)=cos(1)0.54030PE(1, 1) = \cos\left(\frac{1}{10000^{2\times0/8}}\right) = \cos(1) \approx 0.54030

  • 当 i = 1 , 2i = 2 时,

PE(1,2)=sin(1100002×1/8)0.0998334PE(1, 2) = \sin\left(\frac{1}{10000^{2\times1/8}}\right) \approx0.0998334

  • 当 i = 1, 2i + 1 = 3 时,

PE(1,3)=cos(1100002×1/8)=0.99500PE(1, 3) = \cos\left(\frac{1}{10000^{2\times1/8}}\right) =0.99500

按照此方式,可计算出每个位置 (pos = 0, 1, 2, 3)在各个维度上的位置编码值,最终组成一个 $$4 \times 8$$ 的位置编码矩阵。 实际应用中,可借助 Python 等编程语言和相关科学计算库(如 NumPy )实现上述计算过程,快速得到位置编码矩阵。

[010101010.841470.540300.099830.995000.010000.999950.001001.000000.909300.416150.198670.980070.020000.999800.002001.000000.141120.989990.295520.955340.030000.999550.003001.00000]\begin{bmatrix} 0 & 1 & 0 & 1 & 0 & 1 & 0 & 1 \\ 0.84147 & 0.54030 & 0.09983 & 0.99500 & 0.01000 & 0.99995 & 0.00100 & 1.00000 \\ 0.90930 & -0.41615 & 0.19867 & 0.98007 & 0.02000 & 0.99980 & 0.00200 & 1.00000 \\ 0.14112 & -0.98999 & 0.29552 & 0.95534 & 0.03000 & 0.99955 & 0.00300 & 1.00000 \end{bmatrix}

不仅能体现绝对位置,还能有效捕捉元素间相对位置关系 。以正弦位置编码为例,通过特定频率的正弦和余弦函数计算编码值,不同位置的编码值存在数学关联,能反映相对距离等关系 。比如在文本摘要任务中,模型可依据位置编码判断句子中不同成分相对位置,识别关键信息与辅助信息相对位置,提炼摘要 。

# multi-head attention

通过 attention 章节,我们计算出了单个注意力机制。但实际 transformer 中使用了多组自注意力计算的组合,每组注意力计算被称为一个 "头",不同组注意力的计算是相互独立的。

在多头注意力中,为每个头都定义一组独立的查询、键和值权重矩阵。例如,对于有 h 个头的多头注意力,会有 h 组权重矩阵 $$ (W_qi)、(W_ki)、(W_v^i)$$,其中 $$(i = 1, 2, \cdots, h)$$。

将输入分别与每个头的查询、键和值权重矩阵相乘,得到每个头的查询、键和值向量。

对于每个头,按照单头注意力的计算方式,计算注意力得分,并通过 softmax 函数进行归一化,得到注意力权重。

注意力权重与对应头的值向量相乘,并进行加权求和,得到每个头的输出。

Z=MultiHeadAttention(X)(head1head2headH)W\mathbf{Z} = \text{MultiHeadAttention}(\mathbf{X}) \triangleq (\text{head}_1 \oplus \text{head}_2 \oplus \ldots \oplus \text{head}_H)\mathbf{W}

image-20250701014833366

由于我们前面除了 h,为了保持输入的维度依然是原始的 $$ d_\text{model}}$$ {(H\timesd_k)\ti或者是mes另一个特定的维度d_{\text通过乘以矩阵{model$$ \mathbf{W}\in\mathbb{R}}}$$,可以将拼接后的 $$ H\times d_k$$ 维度的向量映射回 $$ d_{\text {model}}$$ 维度

image-20250701014841088

假设输入序列长度为 3,嵌入维度 $$(d_{model} = 8)$$,每个头对应的维度 $$(d_k = d_v = 4)$$(因为有 2 个头,$$(8 \div 2 = 4)$$

定义权重矩阵

  • 头 1 的权重矩阵:
    • WQ1R8×4W_Q^1 \in \mathbb{R}^{8\times4}

    • WK1R8×4W_K^1 \in \mathbb{R}^{8\times4}

    • WV1R8×4W_V^1 \in \mathbb{R}^{8\times4}

  • 头 2 的权重矩阵:
    • WQ2R8×4W_Q^2 \in \mathbb{R}^{8\times4}

    • WK2R8×4W_K^2 \in \mathbb{R}^{8\times4}

    • WV2R8×4W_V^2 \in \mathbb{R}^{8\times4}

输出权重矩阵 $$W_O \in \mathbbR}{8\times8$$

计算每个头的 Q、K、V 矩阵

  • 头 1:

    • \mathbf{Q}_1=\mathbf{X}W_Q^1 \\ \mathbf{K}_1=\mathbf{X}W_K^1 \\ \mathbf{V}_1=\mathbf{X}W_V^1 \\$$,结果是一个$$ 3\times4$$ 的矩阵。
    • \mathbf{Q}_2=\mathbf{X}W_Q^2 \\ \mathbf{K}_2=\mathbf{X}W_K^2 \\ \mathbf{V}_2=\mathbf{X}W_V^2 \\$$,结果是一个$$ 3\times4$$ 的矩阵。
  • 计算注意力分数:$$\textAttention}(Q_1, K_1, V_1)=\frac{\mathbf{Q}_1\mathbf{K}_1T\sqrt{d_k}}=\frac{\mathbf{Q}_1\mathbf{K}_1T{\sqrt {4}}$$,这里得到的是一个 3*3 的矩阵。

  • 对注意力分数应用 softmax 函数得到注意力权重:$$\mathbf {A}_1 = \text {softmax}(\text {Score}_1)$$。

  • 计算头 1 的输出:$$\mathbf {Z}_1=\mathbf {A}_1\mathbf {V}_1$$,这是一个 3*4 的矩阵。

同理计算头 2 的输出 $$\mathbf {Z}_2=\mathbf {A}_2\mathbf {V}_2$$

将头 1 和头 2 的输出按列拼接起来,得到一个 $$3\times8 $$ 的矩阵 $$ \mathbf {Z}$$:

Z=[Z11Z12Z13Z14Z21Z22Z23Z24Z21Z22Z23Z24Z31Z32Z33Z34Z31Z32Z33Z34Z41Z42Z43Z44]\mathbf{Z} = \begin{bmatrix} \mathbf{Z}_{11} & \mathbf{Z}_{12} & \mathbf{Z}_{13} & \mathbf{Z}_{14} & \mathbf{Z}_{21} & \mathbf{Z}_{22} & \mathbf{Z}_{23} & \mathbf{Z}_{24} \\ \mathbf{Z}_{21} & \mathbf{Z}_{22} & \mathbf{Z}_{23} & \mathbf{Z}_{24} & \mathbf{Z}_{31} & \mathbf{Z}_{32} & \mathbf{Z}_{33} & \mathbf{Z}_{34} \\ \mathbf{Z}_{31} & \mathbf{Z}_{32} & \mathbf{Z}_{33} & \mathbf{Z}_{34} & \mathbf{Z}_{41} & \mathbf{Z}_{42} & \mathbf{Z}_{43} & \mathbf{Z}_{44} \end{bmatrix}

线性变换得到最终输出

将拼接后的矩阵 Z 与输出权重矩阵 $$w_o $$ 相乘,得到最终的多头注意力输出 $$ \mathbf {O}$$:

\mathbf{O}=\mathbf{Z}W_O$$,这是一个 $$3\times8$$的矩阵。 #### masked multi-head attention Masked Self-Attention与Self-Attention的主要区别在于:Masked Self-Attention只根据当前位置和之前的输入片段来计算影响力,而不是使用每个qi和所有输入片段的ki来计算影响力。也就是说,我们只能根据历史信息预测现在,而不能根据未来信息预测现在。如图所示,第二个输入只会根据第一个输入的情况计算影响力,而不会去计算与第三和第四个输入的影响力。在生成模型的场景,我们还不知道后面会生成哪些内容,只知道之前生成了哪些内容。 通过将输入到softmax的值设置为$$−∞$$来实现屏蔽。比如-1e^10 ![image-20250701014850743](https://kbshire-1308981697.cos.ap-shanghai.myqcloud.com/img/image-20250701014850743.png) **解码器需要接收编码器传递过来的全局信息**。如果将编码器的全局信息传递到解码器呢?一个常用的设计是使用Cross Attention结构。将解码器本时刻的输入向量作为qi,按照自注意力的计算方法,qi分别与编码器中的ki和vi进行计算,从而实现将编码器生成的全局信息引入到解码器中。 ![image-20250701014858312](https://kbshire-1308981697.cos.ap-shanghai.myqcloud.com/img/image-20250701014858312.png) ![image-20250701014905535](https://kbshire-1308981697.cos.ap-shanghai.myqcloud.com/img/image-20250701014905535.png) ### add && norm 每个的Transformer Encoder Layer包含2个加与规范层(Add&Norm),其作用是通过加入残差连接和层规范化两个组件,使得网络训练更加稳定,收敛性更好。 这里以第1个加与规范层为例,假设多头自注意力的输入和输出分别为$$X∈R^{L×D}$$和$$Z∈R^{L×D}$$,那么加与规范层可以表示为 L=LayerNorm(X+Z) 其中$$L∈R^{L×D}$$,LayerNorm表示层规范化。接下来,向量L将经过前馈层和第2个加与规范层的计算,获得本层Transformer Encoder的输出向量$$O∈R^{L×D}$$。 #### norm 归一化是将数据映射到特定数值范围或分布的技术 。Transformer 使用层归一化,对层内所有神经元输出进行归一化,使其均值和方差稳定 。计算时先求这一层所有激活值的均值 $$\mu$$和方差 $$\sigma$$ ,再用它们调整输出 。 ### FFN 线性变换层:通常由两个线性变换构成。第一个线性变换将输入映射到更高维度空间,一般来说,输入维度为模型的嵌入维度 $$d_{model}$$,输出维度为一个较大的值,比如$$ 4d_{model}$$ ,这一步是为了增加模型的表达能力,让模型能够捕捉更复杂的特征 。第二个线性变换再将数据从高维空间映射回与输入相同的维度$$d_{model}$$,主要是为了适配后续模块对输入维度的要求。 激活函数:在两个线性变换之间,通常使用 ReLU(Rectified Linear Unit)作为激活函数 。ReLU 函数表达式为 $$f(x)=\max(0, x)$$ ,即输入为正时输出等于输入值,输入为负时输出为 0 。它能为网络引入非线性因素,使得模型可以学习到更复杂的模式,还能加速训练过程、缓解梯度消失问题。 先对输入进行第一个线性变换,得到一个高维向量,然后应用 ReLU 激活函数进行非线性变换,最后通过第二个线性变换得到输出。用数学公式表示为: $$x1=xW_!+b_1 \\ x2=ReLU(x_1) \\ output=x2W_2+b_2

x 是 FFN 层的输入,$$W_1,b_1$$ 是第一个线性变换的权重矩阵和偏置向量,$$W_2,b_2$$ 是第二个线性变换的权重矩阵和偏置向量 。

输出:经过上述计算后得到的输出,会传递到后续模块,如在 Transformer 编码器中,会接着进行层归一化(Layer Normalization);在解码器中,会作为下一个模块的输入继续处理。

# linear

假设模型的隐藏维度 $$ d_{model}=512$$,词汇表大小 $$ V = 10000$$,那么线性层的权重矩阵形状为 $$(d_{model}, V)=(512, 10000)$$。通过矩阵乘法,将解码器层输出的 512 维特征向量映射为 10000 维的向量,每个维度对应词汇表中的一个单词。

虽然线性层本身只进行线性变换,但在后续通常会接一个非线性激活函数(如 softmax 函数),将线性层的输出转换为概率分布。这样可以使得模型能够对不同的输出类别进行概率估计,便于进行分类或生成任务。

举例:

输入是一个长度为 n = 3 的句子,模型的嵌入维度 $$d_{model}=4$$

1、输入数据

假设经过多头注意力层处理后的输出作为前馈神经网络层的输入 $$\mathbf {X}$$,它是一个 $$3\times4$$ 的矩阵:

X=[123456789101112]\mathbf{X} = \begin{bmatrix} 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 \\ 9 & 10 & 11 & 12 \end{bmatrix}

2. 定义参数

  • 第一个线性变换的权重矩阵 $$W_1$$ 是一个 $$4\times16$$ 的矩阵(这里假设将输入维度映射到 $$4d_{model}=16$$,偏置向量 $$b _1$$ 是一个长度为 16 的向量。
  • 第二个线性变换的权重矩阵 $$W_2$$ 是一个 $$16\times4$$ 的矩阵(将高维空间映射回输入维度),偏置向量 $$ b_2$$ 是一个长度为 4 的向量。

为了方便计算,我们简单设定这些参数:

W1=[1111111111111111111111111111111111111111111111111111111111111111]W_1 = \begin{bmatrix} 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 \end{bmatrix}

b1=[0000000000000000]b_1 = \begin{bmatrix} 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \end{bmatrix}

W2=[1111111111111111111111111111111111111111111111111111111111111111]W_2 = \begin{bmatrix} 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \end{bmatrix}

b2=[0000]b_2 = \begin{bmatrix} 0 & 0 & 0 & 0 \end{bmatrix}

3. 第一个线性变换

计算 $$\mathbf {x}_1 = \mathbf {X} W_1 + b_1$$

x1=[1×1+2×1+3×1+4×11×1+2×1+3×1+4×15×1+6×1+7×1+8×15×1+6×1+7×1+8×19×1+10×1+11×1+12×19×1+10×1+11×1+12×1]=[101026264242]\mathbf{x}_1 = \begin{bmatrix} 1\times1 + 2\times1 + 3\times1 + 4\times1 & \cdots & 1\times1 + 2\times1 + 3\times1 + 4\times1 \\ 5\times1 + 6\times1 + 7\times1 + 8\times1 & \cdots & 5\times1 + 6\times1 + 7\times1 + 8\times1 \\ 9\times1 + 10\times1 + 11\times1 + 12\times1 & \cdots & 9\times1 + 10\times1 + 11\times1 + 12\times1 \end{bmatrix} = \begin{bmatrix} 10 & \cdots & 10 \\ 26 & \cdots & 26 \\ 42 & \cdots & 42 \end{bmatrix}

此时矩阵为 $$3\times4 \cdot 4 \times 16 = 3\times16$$

4. 应用 ReLU 激活函数

计算 $$\mathbf {x}_2 = \text {ReLU}(\mathbf {x}_1)$$,由于 $$\mathbf {x}_1$$ 中的元素都大于 0,所以 $$ \mathbf {x}_2 = \mathbf {x}_1$$。

5. 第二个线性变换

计算 $$ \mathbf {output} = \mathbf {x}_2W_2 + b_2$$:

output=[10×1++10×110×1++10×110×1++10×110×1++10×126×1++26×126×1++26×126×1++26×126×1++26×142×1++42×142×1++42×142×1++42×142×1++42×1]=[160160160160416416416416672672672672]\mathbf{output} = \begin{bmatrix} 10\times1 + \cdots + 10\times1 & 10\times1 + \cdots + 10\times1 & 10\times1 + \cdots + 10\times1 & 10\times1 + \cdots + 10\times1 \\ 26\times1 + \cdots + 26\times1 & 26\times1 + \cdots + 26\times1 & 26\times1 + \cdots + 26\times1 & 26\times1 + \cdots + 26\times1 \\ 42\times1 + \cdots + 42\times1 & 42\times1 + \cdots + 42\times1 & 42\times1 + \cdots + 42\times1 & 42\times1 + \cdots + 42\times1 \end{bmatrix} = \begin{bmatrix} 160 & 160 & 160 & 160 \\ 416 & 416 & 416 & 416 \\ 672 & 672 & 672 & 672 \end{bmatrix}

此时矩阵为 $$3\times 16 \cdot 16\times4 = 3\times4$$

经过两次变化,得到和输入一样行和列的矩阵

# 流程总述

最后,用一个集合版的例子,来说明 transformer 中每一层的输入和输出,来帮助理解这个简单的模型

# encode layer

仍然采用 ** . 机器 学习 ** 这个例子来举例,序列长度为 4,其中 embedding 后的维度为 4,采用二头注意力机制。

# 1、根据输入向量,得到 Q、K、V

需要将输入的四个词转换为嵌入向量。假设每个词会被映射为一个 4 维的向量。因此,输入可以表示为一个 $$4 \times 4$$ 的矩阵 $$\mathbf {X}$$,成为词嵌入向量。

假设 $$\mathbf {X} = \begin {bmatrix} 1 & 2 & 3 & 4 \ 5 & 6 & 7 & 8 \ 9 & 10 & 11 & 12 \ 13 & 14 & 15 & 16 \end {bmatrix}$$

此时位置矩阵为:$$\mathbf {P} = \begin {bmatrix} 0 & 1 & 1 & 1 \ 0.84147098 & 0.54030231 & 0.00999983 & 0.99995 \ 0.90929743 & -0.41614684 & 0.01999867 & 0.99980001 \ 0.14112001 & -0.9899925 & 0.0299955 & 0.99955003 \end {bmatrix}$$

此时嵌入层的输出为 $$M+P=\begin {bmatrix} 1 & 3 & 4 & 5 \ 5.84147098 & 6.54030231 & 7.00999983 & 8.99995 \ 9.90929743 & 9.58385316 & 11.01999867 & 12.99980001 \ 13.14112001 & 13.0100075 & 15.0299955 & 16.99955003 \end {bmatrix}$$

此时嵌入层结束,进入 attention 模块

定义头 1 和头 2 的权重矩阵

  • 头 1 的权重矩阵:
    • WQ1R4×2W_Q^1 \in \mathbb{R}^{4\times2}

    • WK1R4×2W_K^1 \in \mathbb{R}^{4\times2}

    • WV1R4×2W_V^1 \in \mathbb{R}^{4\times2}

  • 头 2 的权重矩阵:
    • WQ2R4×2W_Q^2 \in \mathbb{R}^{4\times2}

    • WK2R4×2W_K^2 \in \mathbb{R}^{4\times2}

    • WV2R4×2W_V^2 \in \mathbb{R}^{4\times2}

计算每个头的 Q、K、V 矩阵

  • 头 1:
    • \mathbf{Q}_1=\mathbf{X}W_Q^1 \\ \mathbf{K}_1=\mathbf{X}W_K^1 \\ \mathbf{V}_1=\mathbf{X}W_V^1 \\$$,结果是一个$$ 4\times2$$ 的矩阵。
    • \mathbf{Q}_2=\mathbf{X}W_Q^2 \\ \mathbf{K}_2=\mathbf{X}W_K^2 \\ \mathbf{V}_2=\mathbf{X}W_V^2 \\$$,结果是一个$$ 4\times2$$ 的矩阵。
\text{Attention}(Q_1, K_1, V_1)=\frac{\mathbf{Q}_1\mathbf{K}_1^T}{\sqrt{d_k}}=\frac{\mathbf{Q}_1\mathbf{K}_1^T}{\sqrt{2}}$$,此时Q1矩阵是 4*2,K矩阵是2*4,最终得到$$4 \times 4$$的矩阵 $$\text{Attention}(Q_2, K_2, V_2)=\frac{\mathbf{Q}_2\mathbf{K}_2^T}{\sqrt{d_k}}=\frac{\mathbf{Q}_2\mathbf{K}_2^T}{\sqrt{2}}$$,同理 分别计算两个softmax函数得到注意力权重 $$\mathbf{A}_1 = \text{softmax}(Attention(Q_1,K_1,V_1)))

A2=softmax(Attention(Q2,K2,V2)))\mathbf{A}_2 = \text{softmax}(Attention(Q_2,K_2,V_2)))

计算头 1 的输出:$$\mathbf {Z}_1=\mathbf {A}_1\mathbf {V}_1$$ ,此时相当于一个概率 (0-1) 的数字 × 一个矩阵,$$Z_2$$ 同理,此时 $$Z_1,Z_2$$ 都是 4*2 的矩阵

z_1 = \begin{bmatrix} z_{11} & z_{12} \\ z_{21} & z_{22} \\ z_{31} & z_{32} \\ z_{41} & z_{42} \end{bmatrix}$$,$$z_2 = \begin{bmatrix} z_{13} & z_{14} \\ z_{23} & z_{24} \\ z_{33} & z_{34} \\ z_{43} & z_{44} \end{bmatrix}

将 Z1 和 Z2 拼接,得到 Z,$$\mathbf {Z} = \begin {bmatrix} z_{11} & z_{12} & z_{13} & z_{14} \ z_{21} & z_{22} & z_{23} & z_{24} \ z_{31} & z_{32} & z_{33} & z_{34} \ z_{41} & z_{42} & z_{43} & z_{44} \end {bmatrix}$$

最后将拼接后的矩阵 Z 与输出权重矩阵 $$w_o $$ 相乘,得到最终的多头注意力输出 $$ \mathbf {O}$$,$$w_o$$ 是一个 4*4 的矩阵

\mathbf{O}=\mathbf{Z}W_O,这是一个 4\times4$$的矩阵 进行残差链接和归一化 将多头注意力机制的输出Z与嵌入层的输入X进行残差连接,即$$z'' = z + X$$。 注意这里X是要加位置编码的 层归一化:对残差连接后的结果$$z''$$进行层归一化操作,归一化其均值和方差,以加速模型收敛并提高模型的稳定性。层归一化的公式为$$LN(z'') = \frac{z'' - \mu}{\sqrt{\sigma^2 + \epsilon}} \cdot \gamma + \beta$$,其中$$\mu$$和$$\sigma^2$$分别是$$z''$$在特征维度上的均值和方差,$$\epsilon$$是一个小常数防止分母为零,$$\gamma$$和$$\beta$$是可学习的参数。使得每一行的均值为 0,方差为 1。 前馈神经网络$$FFN(z) = max(0, zW_1 + b_1)W_2 + b_2$$,其中w1,b1,w2,b2是 FFN 的权重和偏置,$$max(0,zW_1 + b_1)$$是 ReLU 激活函数。FFN 可以进一步对特征进行变换和提取,增加模型的非线性表达能力。 编码器层(Encoder Layer)的输出会被输入到解码器层(Decoder Layer)的第二个多头注意力机制(即编码器 - 解码器注意力机制,Encoder - Decoder Attention)中,具体作为该机制的键(Key,K)和值(Value,V)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import torch
import torch.nn as nn

# 序列长度
n = 4
# 头的数量
h = 2
# 每个头的特征维度
d_k = 2
# 模型的嵌入维度
d_model = 4

# 假设已经得到拼接后的矩阵 z
z = torch.randn(n, h * d_k)

# 定义权重矩阵 W0
W0 = nn.Linear(h * d_k, d_model)

# 矩阵 z 乘以权重矩阵 W0
Z_out = W0(z)

print("矩阵 z 的形状:", z.shape)
print("权重矩阵 W0 的形状:", W0.weight.shape)
print("最终输出 Z_out 的形状:", Z_out.shape)

#### decode layer 还是一样的例子**`我`****,****`爱`****.****`机器`****,****`学习`** decode layer 的输入最开始为空,有一个占位符 。接着计算得到一个词的概率,我 概率最大,得到输出 我 接着把 我 再次作为输入 以此类推,最后有个结束符 ### 一些论文原话和思考 编码器一般Nx=6,$$d_{model}=512

解码器一般 Nx=6

h=8 即 8 头注意力,$$d_k=d_v=d_{model}/h=8$$

# layernorm && batchnorm

沿批次维度(batch dimension)归一化,对每个特征通道,计算小批量(mini - batch)里所有样本的均值和方差,同一批次、同一通道的样本用相同统计量归一化 。比如在图像任务的 CNN 中,一个批次里所有图片的同一颜色通道(如 R 通道 )会一起算均值和方差。

沿特征维度(feature dimension)归一化,对单个样本的所有特征计算均值和方差,每个样本独立归一化,和批次里其他样本无关 。像处理文本的 RNN、Transformer 中,每个句子(样本 )自身的特征会单独算统计量。

  • BatchNorm:更适配卷积神经网络(CNN)等结构,是计算机视觉(CV)领域标配,能有效减轻内部协变量偏移,加速训练,给模型轻微正则化效果,对初始权重不敏感 。但不适合以 RNN 为基础的动态网络、时序模型(输入序列长度可能不同,不同时间步隐层看到的输入统计量难统一 ),也受限于小批次场景。
  • LayerNorm:适合循环神经网络(RNN)、Transformer 等处理变长序列的模型,是自然语言处理(NLP)领域标配 。在处理可变长度序列(如不同长度句子 )、小批次或在线学习(样本实时输入,批次不稳定 )时更灵活,能应对样本间差异大的情况 。

batchnorm: 归一化操作:用通道的全局均值和方差,对该通道下所有样本的特征做归一化:

yb,c,h,w=γcxb,c,h,wμcσc2+ϵ+βcγcβc是可学习的缩放和偏移参数;ϵ是防止除零的小常数)y_{b,c,h,w} = \gamma_c \cdot \frac{x_{b,c,h,w} - \mu_c}{\sqrt{\sigma_c^2 + \epsilon}} + \beta_c \gamma_c、\beta_c是可学习的缩放和偏移参数;\epsilon 是防止除零的小常数 )

layermorm:- 归一化操作:用样本内的局部均值和方差,对该样本的当前层所有特征做归一化:

yb,l,d=γdxb,l,dμb,lσb,l2+ϵ+βdγdβd是可学习的缩放和偏移参数;ϵ是防止除零的小常数)y_{b,l,d} = \gamma_d \cdot \frac{x_{b,l,d} - \mu_{b,l}}{\sqrt{\sigma_{b,l}^2 + \epsilon}} + \beta_d \gamma_d、\beta_d是可学习的缩放和偏移参数;\epsilon 是防止除零的小常数 )

# 为什么要使用 Q,K,V

查询矩阵 Q 代表了当前需要关注的位置信息,键矩阵 K 表示序列中各个位置的特征标识,值矩阵 V 则包含了这些位置的具体特征内容。通过计算 Q 和 K 之间的相似度,模型可以确定在当前位置应该关注序列中的哪些部分,然后根据这些关注度对 V 进行加权求和,从而得到当前位置的上下文信息。

这种并行计算的方式使得模型在处理长序列时具有较高的效率,避免了传统循环神经网络(RNN)在处理长序列时的梯度消失和计算效率低下的问题。

  • Q(Query,查询):可以理解为是一个 “问题” 或者 “请求”。在文本处理中,当我们要生成一个新的单词或者理解某个单词的上下文时,就会发出一个 “查询”。比如在翻译 “我喜欢苹果” 这句话时,当要翻译 “苹果” 这个词,模型就会发出一个 Q,去询问关于 “苹果” 的相关信息,比如在源语言句子中 “苹果” 和其他词的关系等。
  • K(Key,键):可以看作是用来 “匹配” 或 “索引” 信息的标识。就像我们在字典中通过索引查找单词一样,K 就是帮助我们在输入信息中找到相关内容的关键。在 Transformer 里,K 是从输入序列中提取出来的一些特征标识,当 Q 发出 “问题” 后,K 就会帮助确定输入序列中哪些部分与这个 “问题” 相关。例如在处理 “我喜欢苹果” 这句话时,每个词都会被转化为一个 K,用来表示这个词自身的一些特征以及它在句子中的位置等信息。
  • V(Value,值):是与 K 相对应的实际 “信息内容”。当通过 Q 和 K 找到相关的部分后,V 就提供了具体要使用的信息。还是以 “我喜欢苹果” 为例,V 可能包含了 “苹果” 这个词的语义信息、它所代表的实物的一些特征信息等,这些信息会被用来帮助生成翻译或者其他相关的输出。

# 交叉注意力机制

通过使用编码器的 K 和 V 矩阵,解码器可以 “关注” 到输入序列的不同部分,从而获取输入序列中与当前生成步骤相关的信息。

编码器在处理输入序列时,已经学习到了输入序列中各个位置之间的依赖关系,并将这些信息编码到 K 和 V 矩阵中。解码器通过交叉注意力机制利用这些 K 和 V 矩阵,可以在生成输出序列时考虑到输入序列中的长距离依赖,从而生成更连贯、准确的输出。

# BERT

BERT (Bidirectional Encoder Representations from Transformers)以 Transformer Encoder 为网络基本组件,其中每层的 Encoder Layer 的模型细节同 Transfomer Encoder Layer 是一样的

预训练模型(pre-training model)是先通过一批语料进行训练模型,然后在这个初步训练好的模型基础上,再继续训练或者另作他用。这样的理解基本上是对的,预训练模型的训练和使用分别对应两个阶段:预训练阶段(pre-training)和 微调(fune-tuning)阶段。

预训练阶段一般会在超大规模的语料上,采用无监督(unsupervised)或者弱监督(weak-supervised)的方式训练模型,经过大规模语料的” 洗礼”,预训练模型能够学习到语言相关的知识,比如句法,语法知识等等。

微调阶段是利用预训练好的模型,去定制化地训练某些任务,使得预训练模型” 更懂” 这个任务。例如,利用预训练好的模型继续训练文本分类任务,将会获得比较好的一个分类结果,直观地想,预训练模型已经懂得了语言的知识,在这些知识基础上去学习文本分类任务将会事半功倍。利用预训练模型去微调的一些任务 (例如前述文本分类) 被称为下游任务(down-stream)。

预训练模型极大地推动了自然语言处理领域的发展,推动了自然语言处理任务不断达到更好的效果,并开启了自然语言处理任务的训练新范式:预训练 + 微调。

# BERT network architecture

BERT (Bidirectional Encoder Representations from Transformers)以 Transformer Encoder 为网络基本组件,其中每层的 Encoder Layer 的模型细节同 Transfomer Encoder Layer 是一样的

image-20250701015043671

相比于 Transformer,BERT 模型的嵌入层多了一个分段编码(Segment Embedding)。即 BERT 的输入 Embedding 是由三部分相加获得:token 编码(Token Embedding)、分段编码(Segment Embedding)、位置编码(Positional Embedding)。

token 编码,位置编码与 transformer 基本一致

但与 Transformer 不同的是,BERT 模型的位置编码初始化并不是按照三角函数进行初始化的,而是将位置编码作为可学习的参数,

# 分段编码

BERT 模型支持同时输入多个语句,从而建模语句级别的任务,例如用于判断两句话是否相似的文本匹配任务。多个语句之间是通过拼接成一段话输入至 BERT 模型的,因此可以通过引入一个新的编码:分段编码(Segment Embedding)用于标记输入句子,帮助模型区分输入的不同句子。

例如在图 2.1 中,用 A 表示第一个句子,用 B 表示第 2 个句子,则会用分段编码 EA 表示标记句子 A, EB 表示标记句子 B。

在分别获取 token 编码、位置编码和分段编码后,将这三者进行相加获取最终的输入向量,此时该向量即同时包含了这三者的信息,能够帮助 BERT 模型更好地进行建模 NLP 任务。

# 输入句子的形式

BERT 模型支持输入 1 到多个句子,从而进行不同的任务,但输入的语句默认需要遵循一定的规则。图 2.2 展示了 BERT 模型在输入 1 条语句和 2 条语句时的输入形式。默认情况下,语句开头需要拼接一个 [CLS] token,不同的句子中间需要用 [SEP] token 进行拼接。

一般情况下, [CLS] 位置输出的向量能够通过自注意力的结构学习到整个句子的语义,所以其可以被视为输入语句的语义向量,基于该向量可以进行不同的下游任务,例如文本分类。

image-20250701015050861

# BERT 模型的预训练任务

在预训练阶段,BERT 模型使用了两个预训练任务:掩码语言模型(Masked Language Model)和下一个句子预测(Next Sentence Prediction),在大规模无标注文本语料上进行预训练(pre-train),从而得到融合了双向内容的通用语义表示模型。下面我们进一步介绍这两个预训练任务。

# 掩码语言模型

掩码语言模型(Masked Language Model,一般简记为 MLM)是指在语句中屏蔽部分 token,让模型去自主地预测这些 token。

具体来讲,在训练语料中,会选择一批 token 替换为 Mask token,MLM 任务旨在利用语句的上下文双向信息,恢复语句中被 Mask 的 token,以帮助模型学习语言知识,比较像一个完形填空的任务。图 3.1 给出了关于 MLM 任务一个示例,其中在预测单词 model 的时候,模型将会利用单词 model 前后的序列信息进行推断被 Masking 的单词,特别是在看到 pre-training 和 natural language processing 等信息时,比较容易能够推断出这个单词就是 model。

image-20250701015100158

在 BERT 模型的预训练阶段,总共 Masking 掉语料中 15% 的 token。但是这里存在一个问题:在 fine-tune 阶段以及预测时的输入文本中并不包含 Mask token,这样就造成了预训练阶段和 fine-tune / 预测阶段的 GAP。所以 BERT 在这批被 Mask 的 token 中采用了不同的策略,具体如下:

  • 80% 的 token 被替换为 Mask token
  • 10% 的 token 被随机替换为其他词
  • 10% 的 token 保持不变

# 下一句子预测

下一个句子预测(Next Sentence Prediction,一般简记为 NSP),是指判断输入的两句话在原始文档中是否是前后相邻的关系。

具体来讲,在 BERT 的训练语料中,部分输入文本是由无关的两段语句进行拼接而成,部分输入文本是由本来就前后相邻的两段语句拼接而成。NSP 任务旨在去识别一个输入文本是否是相邻的两句话拼接而成。

image-20250701015107830

可以看到, The pre-training model is a milestoneIt gets a lot of breakthroughts in natural language processing 显然具有紧密的邻接关系,因此其是相邻的;但其和 We had a unforgettable weekend. 语义完全不同,因此不是相邻的。

以上两个预训练任务 MLM 和 NSP 任务能够帮助 BERT 模型学习语言相关的知识,比如语法、句法等。基于预训练后的 BERT 模型进行下游任务微调,能够帮助下有任务获得更好的效果。

# 预训练模型拓展

前边提到 BERT 模型是基于 Transformer Encoder 结构的预训练模型,其推动着 NLP 领域在很多任务上有了新的突破,同时引领出了预训练 + 微调的 NLP 模型新范式,具有里程碑式的意义。

但不止于此,随着相关研究人员在预训练方向的研究,NLP 领域涌现了更多优秀的 Transformer 类预训练模型,多次刷新了不同 NLP 任务的 SOTA(State of the Art),极大地推动了自然语言处理的进展。

相比 BERT,RoBERTa 模型采用了更多训练数据,更大的训练批次,更长的训练时间,同时采用了动态掩码策略,并去除了 NSP 任务等策略,获得进一步的提升。

Ernie 是由百度提出,其通过引入 token、短语和实体这三种级别的 Knowledge Masking 预训练任务帮助模型学习语言知识,在多项任务上超越了 BERT。

ELECTRA 模型基于对抗生成网络的思路,使用生成器对 Mask 后的 token 进行预测,并让判别器预测该序列的 token 是否被替换(RTD 任务),取代了 BERT 中的 MLM 任务进行训练,并获得进一步的提高。

同时还有更多地优化模型被提出,包括 SpanBERT、XLNet、MacBERT 等等

https://aistudio.baidu.com/projectdetail/9054372

# NLP 任务

比较常见的经典 NLP 任务包括以下四类:

  • 分类式任务:给定一串文本,判断该文本的类别标签
  • 问答式任务:给定问题和文档,要求从文档中抽取出问题的答案
  • 序列标注式任务:给定一串文本,输出对应的标签序列
  • 生成式任务:给定一串文本,同时要求模型输出一串文本

# 1.1 分类式任务

一般来讲,分类式任务根据输入语句的数量是 1 句话还是 2 句话,可以将分类式任务分为单句分类任务和句对分类任务。

其中,比较常见的单句分类任务包括文本类别分类,情感极性分类等,此类任务基于 BERT 模型可以按照 图 1.1a 进行建模,即构造输入序列后,使用 [CLS] token 位置的输出向量进行单句文本分类。

比较常见的句对分类任务是文本匹配,其主要用于判断输入的两句话在语义上是否相似。其经典的建模方式如 图 1.1b 所示, 和单句分类任务不同之处在于,句对分类任务需要对两句话使用 [SEP] token 拼接为一串序列,然后输入 BERT 模型,其同样采用 [CLS] token 位置的输出向量进行单句文本分类。

# 1.2 问答式任务

问答式任务是指在给定问题和文档,要求从文档中抽取出问题的答案,比较常见的任务是智能问答和阅读理解。

该类任务的建模方式如 图 1.1c 所示,使用 [SEP] token 将问题和文档进行拼接后输入 BERT 模型,同时从输出的序列向量中,去定位答案的起始和结束位置。

# 1.3 序列标注式任务

序列标注式任务:给定一串文本,输出对应的标签序列,比较常见的任务是命名实体识别、文本分词、词性标注等序列到序列的任务。

该类任务的建模方式如 图 1.1d 所示,输入一串文本后,根据输入文本的序列向量去预测文本序列中每个 token 的标签。

image-20250701015133278

# GPT

https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf

# introduction

先在没有标号的数据上生成预训练模型(Generative Pre-Training),然后在有标号的子任务数据上训练一个分辨的微调模型

当时最成功的使用无监督文本的模型还是词嵌入模型

仅保留 Transformer 的 Masked Multi-Head Attention(屏蔽未来词),实现单向语言建模 910。

位置编码改用可学习向量(非 Transformer 的固定正弦编码)

使用无监督预训练和监督微调相结合的半监督方法,用于语言理解任务。

使用 Transformer [62],它已被证明在机器翻译、文档生成和句法解析等各种任务上表现强劲。与循环网络等其他选择相比,这种模型选择为我们提供了处理文本中长期依赖关系的更结构化的记忆,从而在多样化的任务中实现了稳健的迁移性能。

1、Semi-supervised learning

在半监督学习中,模型利用未标记数据提供的附加信息来提高性能。通过使用标记和未标记数据,模型可以从标记示例中学习,并从未标记示例中进行泛化。这种方法在标记数据稀缺但未标记数据丰富的情况下特别有用。

1、unsupervised pre-training

无监督预训练是半监督学习的一个特例,模型不需要已知的标签或注释来学习,而是通过从数据中自动学习模式来推断它们。,其目标是找到一个好的初始化点,而不是修改监督学习目标。预训练作为一种正则化方案,可以使深度神经网络更好地泛化。

# framework

训练程序包括两个阶段。第一个阶段是在大量文本语料库上学习一个高容量的语言模型。接下来是微调阶段,我们使用标记数据将模型适应于判别任务。

# unsupervised pre-training

给定一个未标记的标记语料库 U={u1,…,un},我们使用标准语言建模目标来最大化以下似然:

L1(U)=ilogP(uiuik,,ui1;Θ)L_1(\mathcal{U}) = \sum_{i} \log P(u_i \mid u_{i - k}, \ldots, u_{i - 1}; \boldsymbol{\Theta})

其中 k 是上下文窗口的大小,条件概率 P 使用具有参数 Θ 的神经网络进行建模。这些参数使用随机梯度下降进行训练。

使用多层 Transformer 解码器作为语言模型,它是 transformer 的一个变体。该模型对输入上下文标记应用多头自注意力操作,然后是位置前馈层,以产生目标标记的输出分布:

h0=UWe+Wphl=transformer_block(hl1)i[1,n]P(u)=softmax(hnWeT)h_0 = UW_e + W_p \\ h_l = \text{transformer\_block}(h_{l-1}) \quad \forall i \in [1, n] \\ P(u) = \text{softmax}(h_n W_e^T)

U=(u−k,…,u−1) 是标记的上下文向量,n 是层数,We 是标记嵌入矩阵,Wp 是位置嵌入矩阵。

# supervised fine-tuning

我们假设有一个标记数据集 C,其中每个实例包含一系列输入标记 x1,…,xm,以及一个标签 y。输入通过我们预训练的模型传递,以获得最终 transformer 块的激活 $$h_l^m$$,然后将其输入到添加的线性输出层中,该层具有参数 Wy 以预测 y:

P(yx1,,xm)=softmax(hlmWy)这给了我们以下最大化目标:L2(C)=(x,y)logP(yx1,,xm)P(y \mid x^1, \ldots, x^m) = \text{softmax}(h_l^m W_y) \\这给了我们以下最大化目标:\\ L_2(\mathcal{C}) = \sum_{(x, y)} \log P(y \mid x^1, \ldots, x^m)

image-20250701015212225

使用 BooksCorpus 数据集,包含大约 7,000 本未出版的书籍,数据主要从电子书分发平台 Smashwords 抓取。 BERT 预训练时除了 BooksCorpus 数据集(8 亿词元)外,还使用了英文维基百科(English Wikipedia, 25 亿词元),所以 BERT 的训练资料大概为 GPT 的四倍。

# GPT-2

https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf

GPT-2 的整体设计思想相较于 GPT-1 没有变化,但通过模型规模的扩展和数据集的优化,在零样本学习(Zero-Shot Learning)上迈出了一大步。此前该领域的模型或受限于架构或受限于规模,性能远不如 GPT-2。

GPT-2 使用了 WebText 数据集进行训练。WebText 的文本来源是 4500 万个经过 Reddit 用户过滤后的网页链接(至少有 3 karma,karma 可以当成点赞),经过去重和清理后,最终包含 800 万篇文档,总计约 40GB 的文本(GPT-1 数据集的大小约为 1GB)。为了避免评估数据的 “泄漏”,数据集还特意去除了常见的数据来源(比如维基百科)。

  • 用了更大的数据集 ,数据是 WebText
  • 训练了一个 15 亿个参数的 Transformer 模型
  • zero-shot(无任何微调)在 8 个语言模型数据集中的 7 个上刷新了成绩,模型能够以 zero-shot 设置的情况下在不同的任务上表现良好

# GPT-3

https://arxiv.org/pdf/2005.14165.pdf

GPT-3 的训练数据集来自 Common Crawl、WebText2、Books1、Books2 和 Wikipedia

image-20250701015235092

  • 参数量达 1750 亿,训练数据量 45TB,首次实现 小样本学习(Few-Shot)36。
  • 在超过 24 项 NLP 任务中接近或超越 SOTA,包括翻译、推理、创作等 3。
  • 揭示 模型性能与规模呈幂律关系,奠定大模型时代基础
模型 参数量 训练数据 核心突破 开源状态
GPT 1.17 亿 BookCorpus 预训练 + 微调范式 完全开源
GPT-2 15 亿 WebText (800 万网页) 零样本多任务能力 分阶段开源
GPT-3 1750 亿 Common Crawl 等混合 小样本学习、规模幂律效应 仅通过 API 开放

# GPT-4

https://ar5iv.labs.arxiv.org/html/2303.08774

https://originshq.com/blog/paper-gpt4/

https://zhuanlan.zhihu.com/p/614957283

https://zhuanlan.zhihu.com/p/18104675680

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

John Doe WeChat Pay

WeChat Pay

John Doe Alipay

Alipay

John Doe PayPal

PayPal