跳转至

Attention and Transformers⚓︎

1941 个字 预计阅读时间 10 分钟

  • 注意力(attention):一种在向量集合上操作的原语 (primitive)

  • Transformer:一种在各处使用注意力的神经网络架构

现在 Transformers 已无处不在,但它们都是从 RNNs 的分支发展起来的,所以接下来也将从 RNNs 出发来引出注意力和 Transformer 的介绍。

Attention⚓︎

Sequence to Sequence with RNNs⚓︎

使用 RNNs 解决序列到序列 (seq2seq) 的问题时,我们可以采用一种叫做编码器 - 解码器(encoder-decoder) 的架构。顾名思义,整个网络被分成了两部分:

注:已知输入序列为 \(x_1, \dots, x_T\),输出序列为 \(y_1, \dots, y_T\);并且假设具体要完成的任务是从英文到意文的机器翻译。

  • 编码器\(h_t = f_W(x_t, h_{t-1})\)

    根据最后一个隐含状态预测出:

    • 初始解码器向量(initial decoder vector) \(s_0\)
    • 上下文向量(context vector) \(c\)(通常 \(c = h_T\)
  • 解码器\(s_t = g_U(y_{t-1}, s_{t-1}, c)\)

也许读者发现,输入序列的瓶颈来自固定长度的上下文向量 \(c\)。尽管对上例而言影响不大,但假如 \(T = 1000\) 时该怎么办呢?解决方案是:在输出的每一步中,都要回过头来看整个输入序列——这也正是注意力机制的思路。

Seq2Seq with RNNs and Attention⚓︎

相较于前一版架构,这次我们得好好考虑 \(c\) 是怎么计算出来的。

  • 根据初始解码器状态 \(s_0\) 和编码器的隐含状态,计算每个隐含状态对应的对齐分数(alignment score)(标量)

    \[ e_{t, i} = f_{att}(s_{t-1}, h_i) \]

    其中 \(f_{att}\) 作为线性层

  • 归一化对齐分数,得到注意权重,即满足 \(0 < a_{t, i} < 1, \sum_i a_{t, i} = 1\)

  • 计算上下文向量,它是一个关于隐含状态的加权和

    \[ c_t = \sum_i a_{t, i} h_i \]
  • 在解码器中使用上下文向量:

    \[ s_t = g_U(y_{t-1}, s_{t-1}, c_t) \]

    其中 \(g_U\) RNN 单元(比如 LSTMGRU 等)

直觉上看,上下文向量关注的是输入序列的相关部分。比如意大利语 "vediamo" = 英语 "we see",所以可能 \(a_{11} = a_{12} = 0.45, a_{13} = a_{14} = 0.05\)

好消息是:这些步骤都是可微的,这意味着可以用到反向传播!

再回过头来看解码器:

  • 使用 \(s_1\) 计算新的上下文向量 \(c_2\)
  • 计算新的对齐分数 \(e_{2, i}\) 和注意权重 \(a_{2, i}\)
  • 继续使用上下文向量计算新的隐含状态:\(s_t = g_U(y_{t-1}, s_{t-1}, c_t)\)
  • 重复上述过程,直到输出 \<STOP> 之类的 token 时停止

由于在解码器的每个时间步中使用了不同的上下文向量,因此:

  • 输入序列不会因只有单个向量而受阻
  • 每个时间步中,上下文向量会“查看”输入序列的不同部分

仍然回到前面机器翻译的任务,假设:

  • 输入:"The agreement on the European Economic Area was signed in August 1992."
  • 输出:"L'accord sur la zone économique européenne a été signé en août 1992."

我们可以得到以下关于注意权重的可视化结果:

  • 蓝色和紫色部分:对角注意意味着词语按顺序对应
  • 黄色部分:注意找到了其他词序(也就是有问题)

回到整体的编码器 - 解码器架构上,我们再来确定一些术语:

  • 查询向量(query vector):解码器的 RNN 状态(上图绿色部分)
  • 数据向量(data vector):编码器的 RNN 状态(上图蓝色部分)
  • 输出向量(output vector):上下文状态(上图黄色部分)

每一个查询会关注所有数据向量,并得到一个输出向量。

Attention Layer⚓︎

我们将上述过程进行更加形式化的表述:

  • 输入:
    • 查询向量:\(q\)\(D_Q\)
    • 数据向量:\(X\)\(N_X \times D_X\)
  • 计算:
    • 相似度(similarity):\(e\)\(N_X\),其中 \(e_i = f_{att}(q, X_i)\)
    • 注意权重:\(a = \text{softmax}(e)\)\(N_X\)
    • 输出向量:\(y = \sum_i a_i X_i\)\(D_X\)

我们要对以上过程做一些改动:

  • 计算相似度时使用比例点积,即

    \[ e_i = q \cdot X_i / \sqrt{D_X} \]
    • 之所以要除以 \(\sqrt{D_X}\),是因为相似度太大会导致 softmax 饱和并产生消失的梯度;回想一下 \(a \cdot b = |a||b| \cos(\text{angle})\),假设 \(a, b\) 是维度为 \(D\) 的常量向量,那么 \(|a| = (\sum_i a^2)^{1/2} = a \sqrt{D}\)
  • 使用多个查询向量,即 \(Q\)\(N_Q \times D_X\),从而改变了后续的计算

    • 相似度:\(E = QX^T / \sqrt{D_X}\)\(N_Q \times N_X\),其中 \(E_{ij} = Q_i \cdot X_j / \sqrt{D_X}\)
    • 注意权重:\(A = \text{softmax}(E, \text{dim}=1)\)\(N_Q \times N_X\)
    • 输出向量:\(Y = AX\)\(N_Q \times D_X\),其中 \(Y_i \sum_i A_{ij} X_j\)
  • 分离

    • 键矩阵 \(W_K\)\(D_X \times D_Q\),值矩阵 \(W_V\)\(D_X \times D_V\)
    • 在数据向量上作用这两个矩阵,可分别得到键(\(K = XW_K\)\(N_X \times D_Q\))和值(\(V = XW_V\)\(N_X \times D_V\)
    • 相似度变为基于的计算:\(E = QK^T / \sqrt{D_Q}\)\(N_Q \times N_X\),其中 \(E_{ij} = Q_i \cdot K_j / \sqrt{D_X}\)
    • 输出向量则变为基于的计算:\(Y = AX\)\(N_Q \times D_X\),其中 \(Y_i \sum_i A_{ij} X_j\)

经过这三个改变后,我们得到了更通用的注意层,如下所示:

  • softmax 用来归一化每一列,使得每个列能表示每个查询预测关于的分布
  • 输出是关于的线性组合,加权来自注意权重
  • 每个查询产生一个输出,该输出是一个关于数据向量的混合信息

这个注意层已经跟 RNN 没什么关系了,它是一个独立的神经网络层,可以直接插入到神经网络架构中。

有时这又称为交叉注意层(cross-attention layer),因为它有两组输入(查询向量和数据向量

Self-Attention Layer⚓︎

上述注意层还有一种更常见的变体,叫做自注意层(self-attention layer)。它的不同之处在于只有一个输入来源,即输入向量,而它对应原来注意层中的数据向量。而查询的计算类似键和值的计算,也是让输入向量和一个查询矩阵相乘得到。下面列出修改后的各项参数:

  • 输入:
    • 输入向量:\(X\)\(N \times D_{\text{in}}\)
    • 键矩阵:\(W_K\)\(D_{\text{in}}, D_{\text{out}}\)
    • 值矩阵:\(W_V\)\(D_{\text{in}}, D_{\text{out}}\)
    • 查询矩阵:\(W_Q\)\(D_{\text{in}}, D_{\text{out}}\)
  • 计算:
    • 查询:\(Q = XW_Q\)\(N \times D_{\text{out}}\)
    • 键:\(K = XW_K\)\(N \times D_{\text{out}}\)
    • 值:\(V = XW_V\)\(N \times D_{\text{out}}\)
    • 相似度:\(E = QK^T / \sqrt{D_Q}\)\(N \times N\),其中 \(E_{ij} = Q_i \cdot K_j / \sqrt{D_Q}\)
    • 注意权重:\(A = \text{softmax}(E, \text{dim}=1)\)\(N \times N\)
    • 输出向量:\(Y = AV\)\(N \times D_{\text{out}}\),其中 \(Y_i = \sum_j A_{ij} V_j\)

对应的示意图如下:

  • 每个输入产生一个输出,该输出是所有输入的混合信息
  • 形状上变得更简单了

    • \(N\) 个输入向量,每个都是 \(D_{\text{in}}\) 维的
    • 几乎总是满足 \(D_Q = D_V = D_{\text{out}}\)
  • 对于每个输入,计算查询、键和值向量

    • 通常融合为一个矩阵乘法:\(\begin{bmatrix}Q & K & V\end{bmatrix} = X\begin{bmatrix}W_Q & W_K & W_V\end{bmatrix}\)\([N \times 3 D_{\text{out}}] = [N \times D_{\text{in}}] [D_{\text{in}} \times 3 D_{\text{out}}]\)
  • 对每个键和每个查询,计算相似度

  • 归一化每一列,用于表示每个查询预测关于键的分布
  • 输出是关于值的线性组合,加权来自注意权重

注意到,调换输入顺序后,查询、键、值、相似度、注意权重和输出的顺序也发生了对应的改变,但始终和输入顺序对应。这说明自注意是排列等变的(permutation equivariant),即 \(F(\sigma(X)) = \sigma(F(X))\),这意味着自注意能在向量集合上正常运作。

自注意的一个问题是无法得知输入序列的顺序。解决方案是在每个输入上加一个位置编码(position encoding),这是一个表示关于索引的固定函数的向量。

Masked Self-Attention Layer⚓︎

Multiheaded Self-Attention Layer⚓︎

Self-Attention == 4 Matrix Multiplication⚓︎

Processing Sequences⚓︎

Transformer⚓︎

Transformers for Language Modeling (LLM)⚓︎

Vision Transformers (ViT)⚓︎

Others⚓︎

评论区

如果大家有什么问题或想法,欢迎在下方留言~