Skip to content

Commit

Permalink
[slides] attention (d2l-ai#906)
Browse files Browse the repository at this point in the history
* [slides] attention

* polish
  • Loading branch information
goldmermaid authored Jul 19, 2021
1 parent 9d6f4ea commit c1326b1
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 50 deletions.
16 changes: 8 additions & 8 deletions chapter_attention-mechanisms/attention-scoring-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import torch
from torch import nn
```

## 遮蔽 softmax 操作
## [**遮蔽softmax操作**]

正如上面提到的,softmax 运算用于输出一个概率分布作为注意力权重。在某些情况下,并非所有的值都应该被纳入到注意力汇聚中。例如,为了在 :numref:`sec_machine_translation` 中高效处理小批量数据集,某些文本序列被填充了没有意义的特殊标记。为了仅将有意义的标记作为值去获取注意力汇聚,可以指定一个有效序列长度(即标记的个数),以便在计算 softmax 时过滤掉超出指定范围的位置。通过这种方式,我们可以在下面的 `masked_softmax` 函数中实现这样的 *遮蔽 softmax 操作*(masked softmax operation),其中任何超出有效长度的位置都被遮蔽并置为0。

Expand Down Expand Up @@ -79,7 +79,7 @@ def masked_softmax(X, valid_lens):
return nn.functional.softmax(X.reshape(shape), dim=-1)
```

为了演示此函数是如何工作的,考虑由两个 $2 \times 4$ 矩阵表示的样本,这两个样本的有效长度分别为 $2$ 和 $3$。经过遮蔽 softmax 操作,超出有效长度的值都被遮蔽为0。
为了[**演示此函数是如何工作**],考虑由两个 $2 \times 4$ 矩阵表示的样本,这两个样本的有效长度分别为 $2$ 和 $3$。经过遮蔽 softmax 操作,超出有效长度的值都被遮蔽为0。

```{.python .input}
masked_softmax(np.random.uniform(size=(2, 2, 4)), d2l.tensor([2, 3]))
Expand All @@ -102,7 +102,7 @@ masked_softmax(np.random.uniform(size=(2, 2, 4)),
masked_softmax(torch.rand(2, 2, 4), d2l.tensor([[1, 3], [2, 4]]))
```

## 加性注意力
## [**加性注意力**]
:label:`subsec_additive-attention`

一般来说,当查询和键是不同长度的矢量时,可以使用加性注意力作为打分函数。给定查询 $\mathbf{q} \in \mathbb{R}^q$ 和键 $\mathbf{k} \in \mathbb{R}^k$,*加性注意力*(additive attention) 的打分函数为
Expand Down Expand Up @@ -168,7 +168,7 @@ class AdditiveAttention(nn.Module):
return torch.bmm(self.dropout(self.attention_weights), values)
```

让我们用一个小子来演示上面的 `AdditiveAttention`,其中查询、键和值的形状为(批量大小、步数或标记序列长度、特征大小),实际输出为 $(2,1,20)$、$(2,10,2)$ 和 $(2,10,4)$。注意力汇聚输出的形状为(批量大小、查询的步数、值的维度)。
让我们用一个小例子来[**演示上面的`AdditiveAttention`**],其中查询、键和值的形状为(批量大小、步数或标记序列长度、特征大小),实际输出为 $(2,1,20)$、$(2,10,2)$ 和 $(2,10,4)$。注意力汇聚输出的形状为(批量大小、查询的步数、值的维度)。

```{.python .input}
queries, keys = d2l.normal(0, 1, (2, 1, 20)), d2l.ones((2, 10, 2))
Expand All @@ -195,15 +195,15 @@ attention.eval()
attention(queries, keys, values, valid_lens)
```

尽管加性注意力包含了可学习的参数,但由于本例子中每个键都是相同的,所以注意力权重是均匀的,由指定的有效长度决定。
尽管加性注意力包含了可学习的参数,但由于本例子中每个键都是相同的,所以[**注意力权重**]是均匀的,由指定的有效长度决定。

```{.python .input}
#@tab all
d2l.show_heatmaps(d2l.reshape(attention.attention_weights, (1, 1, 2, 10)),
xlabel='Keys', ylabel='Queries')
```

## 缩放点积注意力
## [**缩放点积注意力**]

使用点积可以得到计算效率更高的打分函数。但是点积操作要求查询和键具有相同的长度 $d$。假设查询和键的所有元素都是独立的随机变量,并且都满足均值为 $0$ 和方差为 $1$。那么两个向量的点积的均值为 $0$,方差为 $d$。为确保无论向量长度如何,点积的方差在不考虑向量长度的情况下仍然是 $1$,则可以使用 *缩放点积注意力*(scaled dot-product attention) 打分函数:

Expand Down Expand Up @@ -257,7 +257,7 @@ class DotProductAttention(nn.Module):
return torch.bmm(self.dropout(self.attention_weights), values)
```

为了演示上述的 `DotProductAttention`,我们使用了与先前加性注意力例子中相同的键、值和有效长度。对于点积操作,令查询的特征维度与键的特征维度大小相同。
为了[**演示上述的`DotProductAttention`**],我们使用了与先前加性注意力例子中相同的键、值和有效长度。对于点积操作,令查询的特征维度与键的特征维度大小相同。

```{.python .input}
queries = d2l.normal(0, 1, (2, 1, 2))
Expand All @@ -274,7 +274,7 @@ attention.eval()
attention(queries, keys, values, valid_lens)
```

与加性注意力演示相同,由于键包含的是相同的元素,而这些元素无法通过任何查询进行区分,因此获得了均匀的注意力权重
与加性注意力演示相同,由于键包含的是相同的元素,而这些元素无法通过任何查询进行区分,因此获得了[**均匀的注意力权重**]

```{.python .input}
#@tab all
Expand Down
12 changes: 6 additions & 6 deletions chapter_attention-mechanisms/bahdanau-attention.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ from torch import nn

## 定义注意力解码器

要用 Bahdanau 注意力实现循环神经网络编码器-解码器,我们只需重新定义解码器即可。为了更方便地显示学习的注意力权重,以下 `AttentionDecoder` 类定义了带有注意力机制的解码器基本接口
要用 Bahdanau 注意力实现循环神经网络编码器-解码器,我们只需重新定义解码器即可。为了更方便地显示学习的注意力权重,以下 `AttentionDecoder` 类定义了[**带有注意力机制的解码器基本接口**]

```{.python .input}
#@tab all
Expand All @@ -49,7 +49,7 @@ class AttentionDecoder(d2l.Decoder):
raise NotImplementedError
```

接下来,让我们在接下来的 `Seq2SeqAttentionDecoder` 类中实现带有 Bahdanau 注意力的循环神经网络解码器。初始化解码器的状态 (1)编码器在所有时间步的最终层隐藏状态(作为注意力的键和值);(2)最后一个时间步的编码器全层隐藏状态(初始化解码器的隐藏状态);(3)编码器有效长度(排除在注意力池中填充标记)。在每个解码时间步骤中,解码器上一个时间步的最终层隐藏状态将用作关注的查询。因此,注意力输出和输入嵌入都连接为循环神经网络解码器的输入。
接下来,让我们在接下来的 `Seq2SeqAttentionDecoder` 类中[**实现带有Bahdanau注意力的循环神经网络解码器**]。初始化解码器的状态 (1)编码器在所有时间步的最终层隐藏状态(作为注意力的键和值);(2)最后一个时间步的编码器全层隐藏状态(初始化解码器的隐藏状态);(3)编码器有效长度(排除在注意力池中填充标记)。在每个解码时间步骤中,解码器上一个时间步的最终层隐藏状态将用作关注的查询。因此,注意力输出和输入嵌入都连接为循环神经网络解码器的输入。

```{.python .input}
class Seq2SeqAttentionDecoder(AttentionDecoder):
Expand Down Expand Up @@ -151,7 +151,7 @@ class Seq2SeqAttentionDecoder(AttentionDecoder):
return self._attention_weights
```

接下来,我们使用包含 7 个时间步的 4 个序列输入的小批量测试我们实现的 Bahdanau 注意力解码器。
接下来,我们使用包含 7 个时间步的 4 个序列输入的小批量[**测试Bahdanau 注意力解码器**]

```{.python .input}
encoder = d2l.Seq2SeqEncoder(vocab_size=10, embed_size=8, num_hiddens=16,
Expand Down Expand Up @@ -180,7 +180,7 @@ output, state = decoder(X, state)
output.shape, len(state), state[0].shape, len(state[1]), state[1][0].shape
```

## 训练
## [**训练**]

与 :numref:`sec_seq2seq_training` 类似,我们在这里指定超参数,实例化一个带有 Bahdanau 注意力的编码器和解码器,并对这个模型进行机器翻译训练。由于新增的注意力机制,这项训练要比没有注意力机制的 :numref:`sec_seq2seq_training` 慢得多。

Expand All @@ -199,7 +199,7 @@ net = d2l.EncoderDecoder(encoder, decoder)
d2l.train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)
```

模型训练后,我们用它将几个英语句子翻译成法语并计算它们的 BLEU 分数。
模型训练后,我们用它[**将几个英语句子翻译成法语**]并计算它们的 BLEU 分数。

```{.python .input}
#@tab all
Expand All @@ -219,7 +219,7 @@ attention_weights = d2l.reshape(
(1, 1, -1, num_steps))
```

训练结束后通过可视化注意力权重,我们可以看到,每个查询都会在键值对上分配不同的权重。它显示,在每个解码步中,输入序列的不同部分被选择性地聚集在注意力池中。
训练结束后通过[**可视化注意力权重**],我们可以看到,每个查询都会在键值对上分配不同的权重。它显示,在每个解码步中,输入序列的不同部分被选择性地聚集在注意力池中。

```{.python .input}
# 加上一个包含序列结束标记
Expand Down
6 changes: 3 additions & 3 deletions chapter_attention-mechanisms/multihead-attention.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ from torch import nn

## 实现

在实现过程中,我们选择了缩放点积注意力作为每一个注意力头。为了避免计算成本和参数数量的大幅增长,我们设定 $p_q = p_k = p_v = p_o / h$。值得注意的是,如果我们将查询、键和值的线性变换的输出数量设置为 $p_q h = p_k h = p_v h = p_o$,则可以并行计算 $h$ 个头。在下面的实现中,$p_o$ 是通过参数 `num_hiddens` 指定的。
在实现过程中,我们[**选择缩放点积注意力作为每一个注意力头**]。为了避免计算成本和参数数量的大幅增长,我们设定 $p_q = p_k = p_v = p_o / h$。值得注意的是,如果我们将查询、键和值的线性变换的输出数量设置为 $p_q h = p_k h = p_v h = p_o$,则可以并行计算 $h$ 个头。在下面的实现中,$p_o$ 是通过参数 `num_hiddens` 指定的。

```{.python .input}
#@save
Expand Down Expand Up @@ -120,7 +120,7 @@ class MultiHeadAttention(nn.Module):
return self.W_o(output_concat)
```

为了允许多个头的并行计算,上面的 `MultiHeadAttention` 类使用了下面定义的两个转置函数。具体来说,`transpose_output` 函数反转了 `transpose_qkv` 函数的操作。
为了能够[**使多个头并行计算**],上面的 `MultiHeadAttention` 类使用了下面定义的两个转置函数。具体来说,`transpose_output` 函数反转了 `transpose_qkv` 函数的操作。

```{.python .input}
#@save
Expand Down Expand Up @@ -173,7 +173,7 @@ def transpose_output(X, num_heads):
return X.reshape(X.shape[0], X.shape[1], -1)
```

让我们使用键和值相同的小例子来测试我们编写的 `MultiHeadAttention` 类。多头注意力输出的形状是 (`batch_size`, `num_queries`, `num_hiddens`)。
让我们使用键和值相同的小例子来[**测试**]我们编写的 `MultiHeadAttention` 类。多头注意力输出的形状是 (`batch_size`, `num_queries`, `num_hiddens`)。

```{.python .input}
num_hiddens, num_heads = 100, 5
Expand Down
22 changes: 11 additions & 11 deletions chapter_attention-mechanisms/nadaraya-waston.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import torch
from torch import nn
```

## 生成数据集
## [**生成数据集**]

简单起见,考虑下面这个回归问题:给定的成对的“输入-输出”数据集 $\{(x_1, y_1), \ldots, (x_n, y_n)\}$,如何学习 $f$ 来预测任意新输入 $x$ 的输出 $\hat{y} = f(x)$?

Expand Down Expand Up @@ -81,7 +81,7 @@ y_hat = torch.repeat_interleave(y_train.mean(), n_test)
plot_kernel_reg(y_hat)
```

## 非参数的注意力汇聚
## [**非参数注意力汇聚**]

显然,平均汇聚忽略了输入 $x_i$。于是 Nadaraya :cite:`Nadaraya.1964` 和 Waston :cite:`Watson.1964` 提出了一个更好的想法,根据输入的位置对输出 $y_i$ 进行加权:

Expand Down Expand Up @@ -133,7 +133,7 @@ y_hat = d2l.matmul(attention_weights, y_train)
plot_kernel_reg(y_hat)
```

现在,让我们来观察注意力的权重。这里测试数据的输入相当于查询,而训练数据的输入相当于键。因为两个输入都是经过排序的,因此由观察可知“查询-键”对越接近,注意力汇聚的注意力权重就越高
现在,让我们来观察注意力的权重。这里测试数据的输入相当于查询,而训练数据的输入相当于键。因为两个输入都是经过排序的,因此由观察可知“查询-键”对越接近,注意力汇聚的[**注意力权重**]就越高

```{.python .input}
d2l.show_heatmaps(np.expand_dims(np.expand_dims(attention_weights, 0), 0),
Expand All @@ -148,7 +148,7 @@ d2l.show_heatmaps(attention_weights.unsqueeze(0).unsqueeze(0),
ylabel='Sorted testing inputs')
```

## 带参数的注意力汇聚
## [**带参数注意力汇聚**]

非参数的 Nadaraya-Watson 核回归具有 *一致性*(consistency) 的优点:如果有足够的数据,此模型会收敛到最优结果。尽管如此,我们还是可以轻松地将可学习的参数集成到注意力汇聚中。

Expand All @@ -165,7 +165,7 @@ $$\begin{aligned}f(x) &= \sum_{i=1}^n \alpha(x, x_i) y_i \\&= \sum_{i=1}^n \frac

为了更有效地计算小批量数据的注意力,我们可以利用深度学习开发框架中提供的批量矩阵乘法。

假设第一个小批量数据包含 $n$ 个矩阵 $\mathbf{X}_1,\ldots, \mathbf{X}_n$,形状为 $a\times b$,第二个小批量包含 $n$ 个矩阵 $\mathbf{Y}_1, \ldots, \mathbf{Y}_n$,形状为 $b\times c$。它们的批量矩阵乘法得到 $n$ 个矩阵 $\mathbf{X}_1\mathbf{Y}_1, \ldots, \mathbf{X}_n\mathbf{Y}_n$,形状为 $a\times c$。因此,假定两个张量的形状分别是 $(n,a,b)$ 和 $(n,b,c)$ ,它们的批量矩阵乘法输出的形状为 $(n,a,c)$。
假设第一个小批量数据包含 $n$ 个矩阵 $\mathbf{X}_1,\ldots, \mathbf{X}_n$,形状为 $a\times b$,第二个小批量包含 $n$ 个矩阵 $\mathbf{Y}_1, \ldots, \mathbf{Y}_n$,形状为 $b\times c$。它们的批量矩阵乘法得到 $n$ 个矩阵 $\mathbf{X}_1\mathbf{Y}_1, \ldots, \mathbf{X}_n\mathbf{Y}_n$,形状为 $a\times c$。因此,[**假定两个张量的形状分别是 $(n,a,b)$ 和 $(n,b,c)$ ,它们的批量矩阵乘法输出的形状为 $(n,a,c)$**]

```{.python .input}
X = d2l.ones((2, 1, 4))
Expand All @@ -180,7 +180,7 @@ Y = d2l.ones((2, 4, 6))
torch.bmm(X, Y).shape
```

在注意力机制的背景中,我们可以使用小批量矩阵乘法来计算小批量数据中值的加权平均
在注意力机制的背景中,我们可以[**使用小批量矩阵乘法来计算小批量数据中的加权平均值**]

```{.python .input}
weights = d2l.ones((2, 10)) * 0.1
Expand All @@ -197,7 +197,7 @@ torch.bmm(weights.unsqueeze(1), values.unsqueeze(-1))

### 定义模型

基于 :eqref:`eq_nadaraya-waston-gaussian-para` 中的带参数的注意力汇聚,使用小批量矩阵乘法,定义 Nadaraya-Watson 核回归的带参数版本为:
基于 :eqref:`eq_nadaraya-waston-gaussian-para` 中的[**带参数的注意力汇聚**],使用小批量矩阵乘法,定义 Nadaraya-Watson 核回归的带参数版本为:

```{.python .input}
class NWKernelRegression(nn.Block):
Expand Down Expand Up @@ -236,7 +236,7 @@ class NWKernelRegression(nn.Module):

### 训练模型

接下来,将训练数据集转换为键和值用于训练注意力模型。在带参数的注意力汇聚模型中,任何一个训练样本的输入都会和除自己以外的所有训练样本的“键-值”对进行计算,从而得到其对应的预测输出。
接下来,[**将训练数据集转换为键和值**]用于训练注意力模型。在带参数的注意力汇聚模型中,任何一个训练样本的输入都会和除自己以外的所有训练样本的“键-值”对进行计算,从而得到其对应的预测输出。

```{.python .input}
# `X_tile` 的形状: (`n_train`, `n_train`), 每一行都包含着相同的训练输入
Expand Down Expand Up @@ -265,7 +265,7 @@ values = d2l.reshape(Y_tile[(1 - d2l.eye(n_train)).type(torch.bool)],
(n_train, -1))
```

训练带参数的注意力汇聚模型时使用平方损失函数和随机梯度下降
[**训练带参数的注意力汇聚模型**]时使用平方损失函数和随机梯度下降

```{.python .input}
net = NWKernelRegression()
Expand Down Expand Up @@ -301,7 +301,7 @@ for epoch in range(5):
animator.add(epoch + 1, float(l.sum()))
```

训练完带参数的注意力汇聚模型后,我们发现,在尝试拟合带噪声的训练数据时,预测结果绘制的线不如之前非参数模型的线平滑
训练完带参数的注意力汇聚模型后,我们发现,在尝试拟合带噪声的训练数据时,[**预测结果绘制**]的线不如之前非参数模型的线平滑

```{.python .input}
# `keys` 的形状: (`n_test`, `n_train`), 每一行包含着相同的训练输入(例如:相同的键)
Expand All @@ -322,7 +322,7 @@ y_hat = net(x_test, keys, values).unsqueeze(1).detach()
plot_kernel_reg(y_hat)
```

与非参数的注意力汇聚模型相比,带参数的模型加入可学习的参数后,在输出结果的绘制图上,曲线在注意力权重较大的区域变得更不平滑。
与非参数的注意力汇聚模型相比,带参数的模型加入可学习的参数后,在输出结果的绘制图上,[**曲线在注意力权重较大的区域变得更不平滑**]

```{.python .input}
d2l.show_heatmaps(np.expand_dims(np.expand_dims(net.attention_weights, 0), 0),
Expand Down
Loading

0 comments on commit c1326b1

Please sign in to comment.