Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

自注意力

上一章,我们通过位置嵌入让 GPT 模型知道了每个词元在哪里。接下来,就需要让模型在此基础上和周边的词元邻居互动起来。

RNN 模型通过隐状态将前面词元的信息传递给当前词元做参考。而 GPT 模型的并行处理模式同步处理所有词元,每个词元都不知道其他词元的状况。我们需要像用位置向量告诉模型词元位置一样,显式地告诉模型当前词元和所有其他词元的关联。

举例来说,人类是怎样阅读理解的?比如我们读这句话:

小明买了苹果,没有买香蕉,因为它很新鲜。

当我们读到“它”的时候,我们会回顾前面的内容,并把注意力更多地落在“苹果”上,而不是“香蕉”上。这种根据当前内容,回顾并权衡上下文的能力,就是我们要模拟的,称为注意力机制(Attention Mechanism)。

注意力机制

注意力机制的核心思想是:在处理每个词元时,先回头看一眼上下文中所有词元,并根据相关程度给它们分配不同的权重:

  • 相关程度高的词元,权重大、贡献多;

  • 相关程度低的词元,权重小、贡献少。

自注意力

自注意力(Self-Attention)是注意力机制的一种:词元关注的不是别的序列,而是词元自己所在的序列本身。每个词元都同时扮演两个角色:

  • 关注者:别的词元对我有什么影响?

  • 被关注者:我对别的词元有什么影响?

注意力权重

那么我们怎么衡量两个词元的相关程度?一个简单而有效的办法就是计算它们词向量的点积(Dot Product):

scoreij=xi⋅xj\text{score}_{ij} = x_i \cdot x_j

点积的几何意义是两个向量方向的一致性:方向越接近,点积越大,说明两个词元越相关。

把所有词元两两计算点积,就得到一个注意力分数矩阵(Attention Scores),格式为 (上下文尺寸, 上下文尺寸)。其中第 ii 行第 jj 列的数值,表示第 ii 个词元对第 jj 个词元的相关程度。

注意力分数矩阵的数值范围不受控制,直接用于计算会很不稳定。我们用 Softmax 激活函数把每一行转换成概率分布,就得到注意力权重(Attention Weights):每一行的所有权重之和为 1。

自注意力权重示意图:每个词元对上下文中所有词元的权重,每一行经过 Softmax,和为 1
图例:自注意力权重

数据集

字符编码数据集

激活函数

高斯误差线性单元激活函数(GELU)

损失函数

GPT 交叉熵损失函数

模型

GPT 嵌入层

GPT 注意力层

我们新增 GPT 注意力层(GPT Attention Layer)来为每个词元的嵌入向量(词向量 + 位置向量)添加来自其他词元的注意力:

  • 计算注意力分数矩阵:scores=X⋅XT\text{scores} = X \cdot X^T;

  • 缩放:乘以 1/d1 / \sqrt{d},防止数值过大。这里 dd 是嵌入尺寸 embedding_size;

  • 计算注意力权重:用 Softmax 激活函数将每一行转换成概率分布;

  • 加权求和:注意力权重乘以嵌入向量,得到融合上下文的新的注意力向量(Attention Vector)。

最终,我们得到缩放点积注意力(Scaled Dot-Product Attention):

Attention(X)=softmax(X⋅XTd)X\text{Attention}(X) = \text{softmax}\left(\frac{X \cdot X^T}{\sqrt{d}}\right) X

这就是 Transformer 论文中的核心公式。

代码里在公式之外还多了一步:加权求和的结果会再经过一个线性层 self.output(输出投影),才作为注意力层的最终输出。

GPT 前馈网络层

GPT Transformer 模块

现在,我们拥有了完整的 Transformer 模块:注意力层 + 前馈网络层。

  • 注意力层:让词元之间互相交流,融合上下文信息;

  • 前馈网络层:对融合后的注意力向量进一步提炼特征。

加入自注意力的 GPT 基础模块示意图:Transformer 模块由注意力层和前馈网络层组成
图例:加入自注意力的 GPT 基础模块

GPT 输出层

GPT 模型

GPT 训练器

超参数

学习率

批大小

轮数

上下文尺寸

嵌入尺寸

Transformer 模块数

建模

训练

评估

prediction:	1742 steps, each (4, 32, 65)
loss:	2.872809682593143

推理

ROMEO:::::M::MM:MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM

本章,我们初步实现了 GPT 模型最核心的机制:自注意力。词元之间终于可以互相看见彼此,每个位置都融合了整个上下文的相关信息。Transformer 模块也从单纯的前馈网络,升级为“注意力 + 前馈网络”的完整结构。

但是推理结果却出人意料,完全失效。这是因为目前的自注意力有一个致命漏洞:每个词元可以看到所有位置,包括它后面的词元。相当于模型在训练时已经偷看答案,在推理时就会露馅。

下一章,我们将探讨怎样堵上这个漏洞。

课后练习

  1. GPT 注意力层的输出投影(self.output 线性层)有什么作用?试试把它去掉,观察训练效果有什么变化?