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.

因果注意力

上一章,我们实现了自注意力机制,词元之间终于可以看到彼此。但是我们也发现了一个致命漏洞:每个词元都可以看到所有位置,包括它后面的词元。

这意味着什么?

假设模型正在处理第 3 个词元,准备预测第 4 个词元。但是这时它不仅能看到第 1、2 个词元,还能看到第 4、5、6 个词元,也就是答案本身。可以想象训练时模型轻松作弊,损失值很漂亮;但是推理时答案根本还不存在,模型立即露馅。

这就像一个学生写作业偷看标准答案,平时成绩很好。但是到了考场上没有答案可抄,就会原形毕露。

因果掩码

解决办法其实很直接:把未来的信息遮盖起来。

具体来说,在计算出注意力分数矩阵之后,把矩阵中所有未来位置的数值替换成一个极大的负数(比如 -109)。这样经过 Softmax 之后,这些位置的注意力权重就趋近于 0,相当于被完全忽略,不参与预测。

这种处理方式称为因果掩码(Causal Mask):每个位置只能看到自己和之前的位置,不能看到之后的位置。这正好符合语言生成的因果关系,下一个词只能由前面的词决定。

因果注意力权重示意图:注意力权重矩阵只有对角线及其以下有数值,对角线以上全部为 0
图例:因果注意力权重

下三角矩阵

如何实现因果掩码?

观察注意力分数矩阵的格式:(上下文尺寸, 上下文尺寸)。第 ii 行第 jj 列表示第 ii 个词元相对于第 jj 个词元的相关程度:

  • 当 j≤ij \leq i 时:第 jj 个词元在第 ii 个词元之前,或者就是本身,应该保留;

  • 当 j>ij > i 时:第 jj 个词元在第 ii 个词元之后,应该屏蔽。

也就是说,我们只需要保留矩阵的对角线和对角线以下的下三角形部分,把对角线以上的上三角形部分全部替换成 -109。NumPy 提供了现成的下三角函数 np.tril,可以生成一个下三角为 1、上三角为 0 的矩阵,可以直接用它作为掩码。

因果注意力

使用因果掩码后的自注意力,称为因果注意力(Causal Attention),或者掩码自注意力(Masked Self-Attention)。这是 GPT 这类仅解码器(Decoder-Only)架构的标准配置。

数据集

字符编码数据集

下三角矩阵层

我们将因果掩码设计成单独的下三角矩阵层(Tril Layer):

  • 前向传播:用 np.tril 生成下三角掩码,把上三角位置的数值替换成 -109;

  • 反向传播:梯度只通过下三角位置回传。上三角位置没有参与前向传播,所以梯度为 0。

激活函数

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

损失函数

GPT 交叉熵损失函数

模型

GPT 嵌入层

GPT 注意力层

在 GPT 注意力层,我们使用下三角矩阵掩码屏蔽掉注意力分数矩阵 scores 的未来位置。

weights = self.softmax(self.mask(scores))

GPT 前馈网络层

GPT Transformer 模块

GPT 输出层

GPT 模型

GPT 训练器

超参数

学习率

批大小

轮数

上下文尺寸

嵌入尺寸

Transformer 模块数

建模

训练

评估

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

推理

ROMEO:
Famtany at!--will and, though this him at.

ISTARIO:
Hen ifthat not mid'dses mepion.

LERMIONA:
Sordecer, wor I tontlouce alsucitend waseres, It what you sor ande bust trune do's to whave with Me
In keal dies the is my antas;
I fill wak selse that of this

本章,我们用因果掩码堵上了自注意力偷看答案的漏洞。至此,自注意力机制的核心已经完整。从推理结果可以看出,我们已经可以看到明显的句式和戏剧结构。

课后练习

  1. 思考一下为什么下三角矩阵层里要用 -109 来填充上三角的未来位置?试试把它改成 0,观察一下训练效果有什么变化?