【动手学深度学习】(task123)注意力机制剖析

note

  • 将注意力汇聚的输出计算可以作为值的加权平均,选择不同的注意力评分函数会带来不同的注意力汇聚操作。
  • 当查询和键是不同长度的矢量时,可以使用可加性注意力评分函数。当它们的长度相同时,使用缩放的“点-积”注意力评分函数的计算效率更高。

文章目录

  • note
  • 零、基础回顾
    • 0.0 不同人员的学习定位
    • 0.1 AI地图
    • 0.2 深度学习的应用
    • 0.3 答疑
  • 一、可视化注意力权重
    • 1.1 查询、键和值
    • 1.2 注意力的可视化
    • 1.3 小结和练习
  • 二、注意力汇聚:Nadaraya-Watson 核回归
    • 2.1 平均汇聚
    • 2.2 非参数注意力汇聚
    • 2.3 带参数注意力汇聚
  • 三、注意力评分函数
    • 3.1 掩蔽softmax操作
    • 3.2 加性注意力
    • 3.3 缩放点积注意力
  • 四、Bahdanau 注意力
  • 五、多头注意力
  • 六、自注意力和位置编码
    • 6.1 比较卷积神经网络、循环神经网络和自注意力
    • 6.2 位置编码
      • (1)绝对位置编码
      • (2)相对位置编码
  • 七、Transformer架构
  • 八、DIN序列推荐模型
  • 时间安排
  • Reference

零、基础回顾

0.0 不同人员的学习定位

  • AI相关从业人员(产品经理等):掌握What,知道名词,能干什么
  • 数据科学家、工程师:掌握What、How,手要快,能出活
  • 研究员、学生:掌握What、How、Why,除了知道有什么和怎么做,还要知道为什么,思考背后的原因,做出新的突破

0.1 AI地图

在这里插入图片描述
y轴表示可以达到的层次:由底部向上依次是

感知:了解是什么,比如能够可以看到物体,如面前的一块屏幕

推理:基于感知到的现象,想象或推测未来会发生什么

知识:根据看到的数据或者现象,形成自己的知识

规划:根据学习到的知识,做出长远的规划

  • NLP:停留在【感知层面】,如机器翻译;NLP从【符号学】的方法,到【概率模型】,到现在的【机器学习|深度学习】。
  • CV:在感知层面上,对图片做一些推理;图片里都是像素,很难用nlp的那种符号学解释,所以一般用【概率模型】和【机器学习|深度学习】。
  • 深度学习:机器学习的一种,包括CV、NLP、强化学习等。

0.2 深度学习的应用

在这里插入图片描述

  • 物体检测和分割:图片内容、物体是啥、物体位置;物体分割指每个像素属于什么,属于飞机还是人等;
  • 样式迁移:原图片+迁移风格=风格迁移后的图片
  • 文生图:如diffusion model
  • 文字生成:如ChatGPT
  • 广告点击:
    • 步骤:
      • 触发:用户输入关键词,机器先找到一些相关的广告
      • 点击率预估: 利用机器学习的模型预测用户对广告的点击率
      • 排序:利用 点击率 x 竞价 的结果进行排序呈现广告,排名高的在前面呈现
    • 模型的预测:数据 (待预测广告) → 特征提取 → 模型 → 点击率预测
    • 训练数据 (过去广告展现和用户点击) → 特征(X)和用户点击(Y) → 喂给模型训练

0.3 答疑

Q1:领域专家是什么意思?

举个例子,比如我要做农业上的物体识别,我种了一棵树,想要看今年的收成怎么样,我有很多很多土地,用人去一个个查看很费力,于是我用一个无人机,将农作物的情况拍下来,假设得到了树的一些图片,而数据科学家不知道农作物什么样的情况是好,什么样是坏,于是领域专家进行解释,比如多少叶子算是好,什么样不好。同时数据科学家将领域专家的问题翻译成机器学习能做的任务。所以可以认为领域专家提需求的人甲方,而数据科学家乙方

Q2:符号学可以和机器学习融合起来吗?

确实是可以的。目前来说,符号学深度学习有一些新的进展,以前说符号学就是做一些符号上的推理,目前深度学习如图神经网络,可以做一些比较复杂的推理。

Q3:说自然语言处理仅仅停留在感知层面似乎不太合适?因为语言的理解和产出不仅仅是感知,也涉及到语言知识和世界知识,也涉及到规划,比如机器规划下一步要做什么。

语言当然是一个很复杂的过程,我只是想说,自然语言处理我们做得还很一般,虽然能做一些感知以外的东西,但是我感觉是说,不如深度学习特别机器学习,在图片上的应用做得好一些。当然AI地图上也只是一个大致的分类

Q4:如何寻找自己领域的paper的经验吗?

因为大家如果现在去读paper的话,可能每天都有一百篇paper出来,你怎么样去找到你想要的paper,总不能天天看朋友圈推文,这样只能知道别人读过的paper,不会有自己独特的见解

Q5:以无人驾驶为例,误判率在不断下降,但误判的影响还是很严重的,有可能从已有的判断case(样例)得到修正,从而完全避免这样的错误吗?

无人驾驶中,任何一次出现的错误,都可能带来毁灭性的灾难。大家可能看到,特斯拉今天撞了,明天又撞了。所以说,无人驾驶对于错误率确实是非常注重的。

机器学习在学术界现在有很多关于uncertainty或者robustness的研究,就是说模型在数据偏移或者极端情况下会不会给出很不好的答案,我们不会特别深入去讲这个事情,但是无人驾驶这一块确实会通过大量的技术,比如说把不同的模型融合在一起,不是仅仅train一个模型,用多个模型来做投票。汽车有很多雷达、摄像头,它会通过不同的传感器来进行模型的融合,从而降低误差。

因为涉及到评价无人驾驶的特别技术,但在竞赛中我们会给大家看到如何通过融合多个模型提升精度的做法。

一、可视化注意力权重

1.1 查询、键和值

自主性的与非自主性的注意力提示解释了人类的注意力的方式,下面来看看如何通过这两种注意力提示,用神经网络来设计注意力机制的框架,

首先,考虑一个相对简单的状况,即只使用非自主性提示。要想将选择偏向于感官输入,则可以简单地使用参数化的全连接层,甚至是非参数化的最大汇聚层或平均汇聚层。

因此:

  • “是否包含自主性提示”将注意力机制与全连接层或汇聚层区别开来。
    • 查询query:在注意力机制的背景下,自主性提示。给定任何查询,注意力机制通过注意力汇聚(attention pooling)将选择引导至感官输入(sensory inputs,例如中间特征表示)。
    • 在注意力机制中,这些感官输入被称为(value)。每个值都与一个(key)配对,这可以想象为感官输入的非自主提示。如下图所示,可以通过设计注意力汇聚的方式,便于给定的查询(自主性提示)与键(非自主性提示)进行匹配,这将引导得出最匹配的值(感官输入)。

在这里插入图片描述

这个框架下的模型将成为本章的中心。然而,注意力机制的设计有许多替代方案。例如可以设计一个不可微的注意力模型,该模型可以使用强化学习方法(Mnih et al., 2014)进行训练。

1.2 注意力的可视化

平均汇聚层可以被视为输入的加权平均值,其中各输入的权重是一样的。实际上,注意力汇聚得到的是加权平均的总和值,其中权重是在给定的查询和不同的键之间计算得出的。

import torch
import matplotlib.pyplot as plt
from matplotlib_inline import backend_inline
# from d2l import torch as d2l# metrices: shape, [要显示的行数,要显示的列数,查询的数目, 键的数目]
# 可视化注意力权重
#@save
def show_heatmaps(matrices, xlabel, ylabel, titles=None, figsize=(2.5, 2.5),cmap='Reds'):"""显示矩阵热图"""backend_inline.set_matplotlib_formats('svg') # format# d2l.use_svg_display()num_rows, num_cols = matrices.shape[0], matrices.shape[1]# fig, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize,#                              sharex=True, sharey=True, squeeze=False)fig, axes = plt.subplots(num_rows, num_cols, figsize=figsize,sharex=True, sharey=True, squeeze=False)for i, (row_axes, row_matrices) in enumerate(zip(axes, matrices)):for j, (ax, matrix) in enumerate(zip(row_axes, row_matrices)):pcm = ax.imshow(matrix.detach().numpy(), cmap=cmap)if i == num_rows - 1:ax.set_xlabel(xlabel)if j == 0:ax.set_ylabel(ylabel)if titles:ax.set_title(titles[j])fig.colorbar(pcm, ax=axes, shrink=0.6);# 当查询和键相同时,注意力权重为1,否则为0
attention_weights = torch.eye(10).reshape((1, 1, 10, 10))
# 显示注意力权重
show_heatmaps(attention_weights, xlabel='Keys', ylabel='Queries')

上面的栗子,仅当查询和键相同时,注意力权重为1,否则为0。后面也经常用show_heatmaps函数来显示注意力权重。
在这里插入图片描述

1.3 小结和练习

【小结】

  • 受试者使用非自主性和自主性提示有选择性地引导注意力。前者基于突出性,后者则依赖于意识。
  • 注意力机制与全连接层或者汇聚层的区别源于增加的自主提示。
  • 由于包含了自主性提示,注意力机制与全连接的层或汇聚层不同。
  • 注意力机制通过注意力汇聚使选择偏向于值(感官输入),其中包含查询(自主性提示)和键(非自主性提示)。键和值是成对的。
  • 可视化查询和键之间的注意力权重是可行的。
  • 查询(自主提示)和键(非自主提示)之间的交互形成了注意力汇聚;注意力汇聚有选择地聚合了值(感官输入)以生成最终的输出。1964年提出的Nadaraya-Watson核回归模型是一个简单但完整的例子,可以用于演示具有注意力机制的机器学习。

【练习】
(1)在机器翻译中通过解码序列词元时,其自主性提示可能是什么?非自主性提示和感官输入又是什么?

(2)随机生成一个10 X 10矩阵并使用softmax运算来确保每行都是有效的概率分布,然后可视化输出注意力权重。

import torch
import torch.nn.functional as F
matrix = torch.randn(10, 10)
# 确保每行是有效的概率分布
softmax_matrix = F.softmax(matrix, dim = 1)
# 可视化注意力权重
show_heatmaps(softmax_matrix.unsqueeze(0).unsqueeze(0), "Keys", "Queries")

在这里插入图片描述

二、注意力汇聚:Nadaraya-Watson 核回归

  • Nadaraya-Watson核回归是具有注意力机制的机器学习范例。
  • Nadaraya-Watson核回归的注意力汇聚是对训练数据中输出的加权平均。从注意力的角度来看,分配给每个值的注意力权重取决于将值所对应的键和查询作为输入的函数。
  • 注意力汇聚可以分为非参数型和带参数型。

2.1 平均汇聚

回归问题:给定的成对的“输入-输出”数据集 { ( x 1 , y 1 ) , … , ( x n , y n ) } \{(x_1, y_1), \ldots, (x_n, y_n)\} {(x1,y1),,(xn,yn)},如何学习 f f f来预测任意新输入 x x x的输出 y ^ = f ( x ) \hat{y} = f(x) y^=f(x)

【准备数据集】根据下面的非线性函数生成一个人工数据集,其中加入的噪声项为 ϵ \epsilon ϵ y i = 2 sin ⁡ ( x i ) + x i 0.8 + ϵ , y_i = 2\sin(x_i) + x_i^{0.8} + \epsilon, yi=2sin(xi)+xi0.8+ϵ,

  • ϵ \epsilon ϵ服从均值为 0 0 0和标准差为 0.5 0.5 0.5的正态分布
  • 下面生成了 50 50 50个训练样本和 50 50 50个测试样本。为了更好地可视化之后的注意力模式,需要将训练样本进行排序。
# 导库,绘图格式设置
import torch
import torch.nn as nn
from d2l import torch as d2l
def plot_kernel_reg(y_hat):d2l.plot(x_test, [y_truth, y_hat], 'x', 'y', legend=['Truth', 'Pred'],xlim=[0, 5], ylim=[-1, 5])d2l.plt.plot(x_train, y_train, 'o', alpha=0.5)# 和上面和下面的可视化函数结果等价
def use_svg_display():"""Use the svg format to display a plot in Jupyter.Defined in :numref:`sec_calculus`"""backend_inline.set_matplotlib_formats('svg')def set_figsize(figsize=(3.5, 2.5)):"""Set the figure size for matplotlib.Defined in :numref:`sec_calculus`"""use_svg_display()d2l.plt.rcParams['figure.figsize'] = figsizedef set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):"""Set the axes for matplotlib.Defined in :numref:`sec_calculus`"""axes.set_xlabel(xlabel)axes.set_ylabel(ylabel)axes.set_xscale(xscale)axes.set_yscale(yscale)axes.set_xlim(xlim)axes.set_ylim(ylim)if legend:axes.legend(legend)axes.grid()def plot(X, Y=None, xlabel=None, ylabel=None, legend=None, xlim=None,ylim=None, xscale='linear', yscale='linear',fmts=('-', 'm--', 'g-.', 'r:'), figsize=(3.5, 2.5), axes=None):"""Plot data points.Defined in :numref:`sec_calculus`"""if legend is None:legend = []set_figsize(figsize)axes = axes if axes else d2l.plt.gca()# Return True if `X` (tensor or list) has 1 axisdef has_one_axis(X):return (hasattr(X, "ndim") and X.ndim == 1 or isinstance(X, list)and not hasattr(X[0], "__len__"))if has_one_axis(X):X = [X]if Y is None:X, Y = [[]] * len(X), Xelif has_one_axis(Y):Y = [Y]if len(X) != len(Y):X = X * len(Y)axes.cla()for x, y, fmt in zip(X, Y, fmts):if len(x):axes.plot(x, y, fmt)else:axes.plot(y, fmt)set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend)def plot_kernel_reg(y_hat):plot(x_test, [y_truth, y_hat], 'x', 'y', legend=['Truth', 'Pred'],xlim=[0, 5], ylim=[-1, 5])plt.plot(x_train, y_train, 'o', alpha=0.5);# 1. build dataset
n_train = 50  # 训练样本数
x_train, _ = torch.sort(torch.rand(n_train) * 5)   # 排序后的训练样本def f(x):return 2 * torch.sin(x) + x**0.8y_train = f(x_train) + torch.normal(0.0, 0.5, (n_train,))  # 训练样本的输出
x_test = torch.arange(0, 5, 0.1)  # 测试样本
y_truth = f(x_test)  # 测试样本的真实输出
n_test = len(x_test)  # 测试样本数# 2. 平均汇聚
y_hat = torch.repeat_interleave(y_train.mean(), n_test)
plot_kernel_reg(y_hat)

上面使用最简单的评估器——平均汇聚求所有训练样本输出值的平均值,显然没啥用,相差有点大。
在这里插入图片描述

2.2 非参数注意力汇聚

显然,平均汇聚忽略了输入 x i x_i xi
于是Nadaraya :cite:Nadaraya.1964和Watson :cite:Watson.1964提出了一个更好的想法,根据输入的位置对输出 y i y_i yi进行加权:
f ( x ) = ∑ i = 1 n K ( x − x i ) ∑ j = 1 n K ( x − x j ) y i , f(x) = \sum_{i=1}^n \frac{K(x - x_i)}{\sum_{j=1}^n K(x - x_j)} y_i, f(x)=i=1nj=1nK(xxj)K(xxi)yi,

其中 K K K(kernel)。上面公式所描述的估计器被称为Nadaraya-Watson核回归(Nadaraya-Watson kernel regression)。
在这里插入图片描述

但受此启发,我们可以上图中的注意力机制框架的角度重写 :eqref:eq_nadaraya-watson,成为一个更加通用的注意力汇聚(attention pooling)公式:

f ( x ) = ∑ i = 1 n α ( x , x i ) y i , f(x) = \sum_{i=1}^n \alpha(x, x_i) y_i, f(x)=i=1nα(x,xi)yi,

其中 x x x是查询, ( x i , y i ) (x_i, y_i) (xi,yi)是键值对。比较上面公式和平均汇聚公式,注意力汇聚是 y i y_i yi的加权平均。
将查询 x x x和键 x i x_i xi之间的关系建模为注意力权重(attention weight) α ( x , x i ) \alpha(x, x_i) α(x,xi),如上一个公式所示,这个权重将被分配给每一个对应值 y i y_i yi。对于任何查询,模型在所有键值对注意力权重都是一个有效的概率分布:它们是非负的,并且总和为1。

为了更好地理解注意力汇聚,下面考虑一个高斯核(Gaussian kernel),其定义为:

K ( u ) = 1 2 π exp ⁡ ( − u 2 2 ) . K(u) = \frac{1}{\sqrt{2\pi}} \exp(-\frac{u^2}{2}). K(u)=2π 1exp(2u2).

将高斯核代入 第一个公式和第二个公式可以得到:

f ( x ) = ∑ i = 1 n α ( x , x i ) y i = ∑ i = 1 n exp ⁡ ( − 1 2 ( x − x i ) 2 ) ∑ j = 1 n exp ⁡ ( − 1 2 ( x − x j ) 2 ) y i = ∑ i = 1 n s o f t m a x ( − 1 2 ( x − x i ) 2 ) y i . \begin{aligned} f(x) &=\sum_{i=1}^n \alpha(x, x_i) y_i\\ &= \sum_{i=1}^n \frac{\exp\left(-\frac{1}{2}(x - x_i)^2\right)}{\sum_{j=1}^n \exp\left(-\frac{1}{2}(x - x_j)^2\right)} y_i \\&= \sum_{i=1}^n \mathrm{softmax}\left(-\frac{1}{2}(x - x_i)^2\right) y_i. \end{aligned} f(x)=i=1nα(x,xi)yi=i=1nj=1nexp(21(xxj)2)exp(21(xxi)2)yi=i=1nsoftmax(21(xxi)2)yi.

在上面公式中,如果一个键 x i x_i xi越是接近给定的查询 x x x,那么分配给这个键对应值 y i y_i yi的注意力权重就会越大,也就“获得了更多的注意力”。

值得注意的是,Nadaraya-Watson核回归是一个非参数模型。因此,上面公式是非参数的注意力汇聚(nonparametric attention pooling)模型。下面将基于这个非参数的注意力汇聚模型来绘制预测结果。从绘制的结果会发现新的模型预测线是平滑的,并且比平均汇聚的预测更接近真实。

# 非参数注意力汇聚(Nd核回归)# X_repeat的形状:(n_test,n_train),
# 每一行都包含着相同的测试输入(例如:同样的查询)
X_repeat = x_test.repeat_interleave(n_train).reshape((-1, n_train))
# x_train包含着键。attention_weights的形状:(n_test,n_train),# 每一行都包含着要在给定的每个查询的值(y_train)之间分配的注意力权重
attention_weights = nn.functional.softmax(-(X_repeat - x_train)**2 / 2, dim=1)# y_hat的每个元素都是值的加权平均值,其中的权重是注意力权重
y_hat = torch.matmul(attention_weights, y_train)
plot_kernel_reg(y_hat)# 注意力权重可视化
show_heatmaps(attention_weights.unsqueeze(0).unsqueeze(0),xlabel='Sorted training inputs',ylabel='Sorted testing inputs')

在这里插入图片描述
可看到上图的效果比平均汇聚好点。
在这里插入图片描述

2.3 带参数注意力汇聚

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

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

# 批量矩阵乘法
X = torch.ones((3, 1, 5))
Y = torch.ones((3, 5, 9))
# torch.Size([3, 1, 9])
torch.bmm(X, Y).shape# 小批量矩阵乘法
weights = torch.ones((2, 10)) * 0.1
values = torch.arange(20.0).reshape((2, 10))
torch.bmm(weights.unsqueeze(1), values.unsqueeze(-1)).shape
# torch.Size([2, 1, 1])

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

例如,与2.2模型略有不同,在下面的查询 x x x和键 x i x_i xi之间的距离乘以可学习参数 w w w

f ( x ) = ∑ i = 1 n α ( x , x i ) y i = ∑ i = 1 n exp ⁡ ( − 1 2 ( ( x − x i ) w ) 2 ) ∑ j = 1 n exp ⁡ ( − 1 2 ( ( x − x j ) w ) 2 ) y i = ∑ i = 1 n s o f t m a x ( − 1 2 ( ( x − x i ) w ) 2 ) y i . \begin{aligned}f(x) &= \sum_{i=1}^n \alpha(x, x_i) y_i \\&= \sum_{i=1}^n \frac{\exp\left(-\frac{1}{2}((x - x_i)w)^2\right)}{\sum_{j=1}^n \exp\left(-\frac{1}{2}((x - x_j)w)^2\right)} y_i \\&= \sum_{i=1}^n \mathrm{softmax}\left(-\frac{1}{2}((x - x_i)w)^2\right) y_i.\end{aligned} f(x)=i=1nα(x,xi)yi=i=1nj=1nexp(21((xxj)w)2)exp(21((xxi)w)2)yi=i=1nsoftmax(21((xxi)w)2)yi.

# 1. 定义模型
class NWKernelRegression(nn.Module):def __init__(self, **kwargs):super().__init__(**kwargs)self.w = nn.Parameter(torch.rand((1,), requires_grad=True))def forward(self, queries, keys, values):# queries和attention_weights的形状为(查询个数,“键-值”对个数)queries = queries.repeat_interleave(keys.shape[1]).reshape((-1, keys.shape[1]))self.attention_weights = nn.functional.softmax(-((queries - keys) * self.w)**2 / 2, dim=1)# values的形状为(查询个数,“键-值”对个数)return torch.bmm(self.attention_weights.unsqueeze(1),values.unsqueeze(-1)).reshape(-1)# 2. model train
# X_tile的形状:(n_train,n_train),每一行都包含着相同的训练输入
X_tile = x_train.repeat((n_train, 1))
# Y_tile的形状:(n_train,n_train),每一行都包含着相同的训练输出
Y_tile = y_train.repeat((n_train, 1))
# keys的形状:('n_train','n_train'-1)
keys = X_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))
# values的形状:('n_train','n_train'-1)
values = Y_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))net = NWKernelRegression()
loss = nn.MSELoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=0.5)
animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[1, 5])for epoch in range(5):trainer.zero_grad()l = loss(net(x_train, keys, values), y_train)l.sum().backward()trainer.step()print(f'epoch {epoch + 1}, loss {float(l.sum()):.6f}')animator.add(epoch + 1, float(l.sum()))

使用平方损失函数和随机梯度下降进行训练。
在这里插入图片描述
分析上图:在拟合带噪声的数据时,预测的曲线没有之前非参数模型的预测曲线平滑。因为前者加入可学习的参数后,曲线在注意力权重较大的地方变得更加不平滑。

show_heatmaps(net.attention_weights.unsqueeze(0).unsqueeze(0),xlabel='Sorted training inputs',ylabel='Sorted testing inputs')

在这里插入图片描述

三、注意力评分函数

【小结】

  • 将注意力汇聚的输出计算可以作为值的加权平均,选择不同的注意力评分函数会带来不同的注意力汇聚操作。
  • 当查询和键是不同长度的矢量时,可以使用可加性注意力评分函数。当它们的长度相同时,使用缩放的“点-积”注意力评分函数的计算效率更高。

第二节使用了高斯核来对查询和键之间的关系建模。高斯核指数部分可以视为注意力评分函数(attention scoring function),简称评分函数(scoring function),然后把这个函数的输出结果输入到softmax函数中进行运算。通过上述步骤,将得到与键对应的值的概率分布(即注意力权重)。最后,注意力汇聚的输出就是基于这些注意力权重的值的加权和。

从宏观来看,上述算法可以用来实现对应的注意力机制框架。说明了如何将注意力汇聚的输出计算成为值的加权和,其中 a a a表示注意力评分函数。由于注意力权重是概率分布,因此加权和其本质上是加权平均值
在这里插入图片描述
用数学语言描述,假设有一个查询 q ∈ R q \mathbf{q} \in \mathbb{R}^q qRq m m m个“键-值”对 ( k 1 , v 1 ) , … , ( k m , v m ) (\mathbf{k}_1,\mathbf{v}_1), \ldots, (\mathbf{k}_m, \mathbf{v}_m) (k1,v1),,(km,vm),其中 k i ∈ R k \mathbf{k}_i \in \mathbb{R}^k kiRk v i ∈ R v \mathbf{v}_i \in \mathbb{R}^v viRv。注意力汇聚函数 f f f就被表示成值的加权和:

f ( q , ( k 1 , v 1 ) , … , ( k m , v m ) ) = ∑ i = 1 m α ( q , k i ) v i ∈ R v , f(\mathbf{q}, (\mathbf{k}_1, \mathbf{v}_1), \ldots, (\mathbf{k}_m, \mathbf{v}_m)) = \sum_{i=1}^m \alpha(\mathbf{q}, \mathbf{k}_i) \mathbf{v}_i \in \mathbb{R}^v, f(q,(k1,v1),,(km,vm))=i=1mα(q,ki)viRv,

其中查询 q \mathbf{q} q和键 k i \mathbf{k}_i ki的注意力权重(标量)是通过注意力评分函数 a a a将两个向量映射成标量,再经过softmax运算得到的:

α ( q , k i ) = s o f t m a x ( a ( q , k i ) ) = exp ⁡ ( a ( q , k i ) ) ∑ j = 1 m exp ⁡ ( a ( q , k j ) ) ∈ R . \alpha(\mathbf{q}, \mathbf{k}_i) = \mathrm{softmax}(a(\mathbf{q}, \mathbf{k}_i)) = \frac{\exp(a(\mathbf{q}, \mathbf{k}_i))}{\sum_{j=1}^m \exp(a(\mathbf{q}, \mathbf{k}_j))} \in \mathbb{R}. α(q,ki)=softmax(a(q,ki))=j=1mexp(a(q,kj))exp(a(q,ki))R.

正如上图所示,选择不同的注意力评分函数 a a a会导致不同的注意力汇聚操作。本节将介绍两个流行的评分函数,稍后将用他们来实现更复杂的注意力机制。

3.1 掩蔽softmax操作

超过有效长度的位置被掩蔽并置为0。

import math
from torch import nn
import torch# mask softmax
#@save
def sequence_mask(X, valid_len, value=0):"""Mask irrelevant entries in sequences.Defined in :numref:`sec_seq2seq_decoder`"""maxlen = X.size(1)mask = torch.arange((maxlen), dtype=torch.float32,device=X.device)[None, :] < valid_len[:, None]X[~mask] = valuereturn Xdef masked_softmax(X, valid_lens):"""通过在最后一个轴上掩蔽元素来执行softmax操作"""# X:3D张量,valid_lens:1D或2D张量if valid_lens is None:return nn.functional.softmax(X, dim=-1)else:shape = X.shapeif valid_lens.dim() == 1:valid_lens = torch.repeat_interleave(valid_lens, shape[1])else:valid_lens = valid_lens.reshape(-1)# 最后一轴上被掩蔽的元素使用一个非常大的负值替换,从而其softmax输出为0X = sequence_mask(X.reshape(-1, shape[-1]), valid_lens,value=-1e6)return nn.functional.softmax(X.reshape(shape), dim=-1)# 经过softmax操作,超过有效长度的值都被掩蔽为0
# two sample: (2 X 4), 该两个样本的有效长度分别为2和3
masked_softmax(torch.rand(2, 2, 4), torch.tensor([2, 3]))# 用二维向量,指定样本的每一行的有效长度
masked_softmax(torch.rand(2, 2, 4), torch.tensor([[1, 3], [2, 4]]))
# 结果如下:
tensor([[[1.0000, 0.0000, 0.0000, 0.0000],[0.5260, 0.2365, 0.2375, 0.0000]],[[0.3495, 0.6505, 0.0000, 0.0000],[0.3788, 0.2290, 0.2348, 0.1573]]])

3.2 加性注意力

使用场景:当查询和键是不同长度的矢量时,可以使用加性注意力作为评分函数。
给定查询 q ∈ R q \mathbf{q} \in \mathbb{R}^q qRq和键 k ∈ R k \mathbf{k} \in \mathbb{R}^k kRk加性注意力(additive attention)的评分函数为
a ( q , k ) = w v ⊤ tanh ( W q q + W k k ) ∈ R , a(\mathbf q, \mathbf k) = \mathbf w_v^\top \text{tanh}(\mathbf W_q\mathbf q + \mathbf W_k \mathbf k) \in \mathbb{R}, a(q,k)=wvtanh(Wqq+Wkk)R,
其中可学习的参数是 W q ∈ R h × q \mathbf W_q\in\mathbb R^{h\times q} WqRh×q W k ∈ R h × k \mathbf W_k\in\mathbb R^{h\times k} WkRh×k w v ∈ R h \mathbf w_v\in\mathbb R^{h} wvRh
如上面公式所示,将查询和键连结起来后输入到一个多层感知机(MLP)中,感知机包含一个隐藏层,其隐藏单元数是一个超参数 h h h。通过使用 tanh ⁡ \tanh tanh作为激活函数,并且禁用偏置项。

# subsec_additive-attention
# 加性注意力
#@save
class AdditiveAttention(nn.Module):"""加性注意力"""def __init__(self, key_size, query_size, num_hiddens, dropout, **kwargs):super(AdditiveAttention, self).__init__(**kwargs)self.W_k = nn.Linear(key_size, num_hiddens, bias=False)self.W_q = nn.Linear(query_size, num_hiddens, bias=False)self.w_v = nn.Linear(num_hiddens, 1, bias=False)self.dropout = nn.Dropout(dropout)def forward(self, queries, keys, values, valid_lens):queries, keys = self.W_q(queries), self.W_k(keys)# 在维度扩展后,# queries的形状:(batch_size,查询的个数,1,num_hidden)# key的形状:(batch_size,1,“键-值”对的个数,num_hiddens)# 使用广播方式进行求和features = queries.unsqueeze(2) + keys.unsqueeze(1)features = torch.tanh(features)# self.w_v仅有一个输出,因此从形状中移除最后那个维度。# scores的形状:(batch_size,查询的个数,“键-值”对的个数)scores = self.w_v(features).squeeze(-1)self.attention_weights = masked_softmax(scores, valid_lens)# values的形状:(batch_size,“键-值”对的个数,值的维度)return torch.bmm(self.dropout(self.attention_weights), values)# 栗子
queries, keys = torch.normal(0, 1, (2, 1, 20)), torch.ones((2, 10, 2))
# values的小批量,两个值矩阵是相同的
values = torch.arange(40, dtype=torch.float32).reshape(1, 10, 4).repeat(2, 1, 1)
valid_lens = torch.tensor([2, 6])attention = AdditiveAttention(key_size=2, query_size=20, num_hiddens=8,dropout=0.1)
attention.eval()
attention(queries, keys, values, valid_lens)
show_heatmaps(attention.attention_weights.reshape((1, 1, 2, 10)),xlabel='Keys', ylabel='Queries')

其中查询、键和值的形状为(批量大小,步数或词元序列长度,特征大小),实际输出为 ( 2 , 1 , 20 ) (2,1,20) (2,1,20) ( 2 , 10 , 2 ) (2,10,2) (2,10,2) ( 2 , 10 , 4 ) (2,10,4) (2,10,4)。注意力汇聚输出的形状为(批量大小,查询的步数,值的维度)。虽然加性注意力包含了可学习的参数,但由于本例子中每个键都是相同的, 所以注意力权重是均匀的,由指定的有效长度决定。
在这里插入图片描述

3.3 缩放点积注意力

使用点积可以得到计算效率更高的评分函数,但是点积操作要求查询和键具有相同的长度 d d d
假设查询和键的所有元素都是独立的随机变量,并且都满足零均值和单位方差,那么两个向量的点积的均值为 0 0 0,方差为 d d d

为确保无论向量长度如何,点积的方差在不考虑向量长度的情况下仍然是 1 1 1,我们再将点积除以 d \sqrt{d} d ,则缩放点积注意力(scaled dot-product attention)评分函数为:

a ( q , k ) = q ⊤ k / d . a(\mathbf q, \mathbf k) = \mathbf{q}^\top \mathbf{k} /\sqrt{d}. a(q,k)=qk/d .

在实践中,我们通常从小批量的角度来考虑提高效率,例如基于 n n n个查询和 m m m个键-值对计算注意力,其中查询和键的长度为 d d d,值的长度为 v v v
查询 Q ∈ R n × d \mathbf Q\in\mathbb R^{n\times d} QRn×d、键 K ∈ R m × d \mathbf K\in\mathbb R^{m\times d} KRm×d和值 V ∈ R m × v \mathbf V\in\mathbb R^{m\times v} VRm×v的缩放点积注意力是:

s o f t m a x ( Q K ⊤ d ) V ∈ R n × v . \mathrm{softmax}\left(\frac{\mathbf Q \mathbf K^\top }{\sqrt{d}}\right) \mathbf V \in \mathbb{R}^{n\times v}. softmax(d QK)VRn×v.

下面的缩放点积注意力的实现使用了暂退法进行模型正则化。

#@save
class DotProductAttention(nn.Module):"""缩放点积注意力"""def __init__(self, dropout, **kwargs):super(DotProductAttention, self).__init__(**kwargs)self.dropout = nn.Dropout(dropout)# queries的形状:(batch_size,查询的个数,d)# keys的形状:(batch_size,“键-值”对的个数,d)# values的形状:(batch_size,“键-值”对的个数,值的维度)# valid_lens的形状:(batch_size,)或者(batch_size,查询的个数)def forward(self, queries, keys, values, valid_lens=None):d = queries.shape[-1]# 设置transpose_b=True为了交换keys的最后两个维度scores = torch.bmm(queries, keys.transpose(1,2)) / math.sqrt(d)self.attention_weights = masked_softmax(scores, valid_lens)return torch.bmm(self.dropout(self.attention_weights), values)queries = torch.normal(0, 1, (2, 1, 2))
attention = DotProductAttention(dropout=0.5)
attention.eval()
attention(queries, keys, values, valid_lens)
  • 使用与先前加性注意力例子中相同的键、值和有效长度。 对于点积操作,我们令查询的特征维度与键的特征维度大小相同。
  • 与加性注意力演示相同,由于键包含的是相同的元素, 而这些元素无法通过任何查询进行区分,因此获得了均匀的注意力权重。

四、Bahdanau 注意力

  • 在预测词元时,如果不是所有输入词元都是相关的,那么具有Bahdanau注意力的循环神经网络编码器-解码器会有选择地统计输入序列的不同部分。这是通过将上下文变量视为加性注意力池化的输出来实现的。
  • 在循环神经网络编码器-解码器中,Bahdanau注意力将上一时间步的解码器隐状态视为查询,在所有时间步的编码器隐状态同时视为键和值。

在这里插入图片描述

五、多头注意力

【小结】

  • 多头注意力融合了来自于多个注意力汇聚的不同知识,这些知识的不同来源于相同的查询、键和值的不同的子空间表示。
  • 基于适当的张量操作,可以实现多头注意力的并行计算。

在这里插入图片描述
在实现多头注意力之前,让我们用数学语言将这个模型形式化地描述出来。
给定查询 q ∈ R d q \mathbf{q} \in \mathbb{R}^{d_q} qRdq、键 k ∈ R d k \mathbf{k} \in \mathbb{R}^{d_k} kRdk和值 v ∈ R d v \mathbf{v} \in \mathbb{R}^{d_v} vRdv,每个注意力头 h i \mathbf{h}_i hi i = 1 , … , h i = 1, \ldots, h i=1,,h)的计算方法为:

h i = f ( W i ( q ) q , W i ( k ) k , W i ( v ) v ) ∈ R p v , \mathbf{h}_i = f(\mathbf W_i^{(q)}\mathbf q, \mathbf W_i^{(k)}\mathbf k,\mathbf W_i^{(v)}\mathbf v) \in \mathbb R^{p_v}, hi=f(Wi(q)q,Wi(k)k,Wi(v)v)Rpv,

其中,可学习的参数包括 W i ( q ) ∈ R p q × d q \mathbf W_i^{(q)}\in\mathbb R^{p_q\times d_q} Wi(q)Rpq×dq W i ( k ) ∈ R p k × d k \mathbf W_i^{(k)}\in\mathbb R^{p_k\times d_k} Wi(k)Rpk×dk W i ( v ) ∈ R p v × d v \mathbf W_i^{(v)}\in\mathbb R^{p_v\times d_v} Wi(v)Rpv×dv,以及代表注意力汇聚的函数 f f f f f f可以是第三节中的加性注意力和缩放点积注意力。多头注意力的输出需要经过另一个线性转换,它对应着 h h h个头连结后的结果,因此其可学习参数是 W o ∈ R p o × h p v \mathbf W_o\in\mathbb R^{p_o\times h p_v} WoRpo×hpv
W o [ h 1 ⋮ h h ] ∈ R p o . \mathbf W_o \begin{bmatrix}\mathbf h_1\\\vdots\\\mathbf h_h\end{bmatrix} \in \mathbb{R}^{p_o}. Wo h1hh Rpo.

基于这种设计,每个头都可能会关注输入的不同部分,可以表示比简单加权平均值更复杂的函数。

# !/usr/bin/python
# -*- coding: utf-8 -*-
import math
import torch
import torch.nn as nn
from d2l import torch as d2l
from torchinfo import summary# multi-head
#@save
class MultiHeadAttention(nn.Module):"""多头注意力"""def __init__(self, key_size, query_size, value_size, num_hiddens,num_heads, dropout, bias=False, **kwargs):super(MultiHeadAttention, self).__init__(**kwargs)self.num_heads = num_headsself.attention = d2l.DotProductAttention(dropout)self.W_q = nn.Linear(query_size, num_hiddens, bias=bias)self.W_k = nn.Linear(key_size, num_hiddens, bias=bias)self.W_v = nn.Linear(value_size, num_hiddens, bias=bias)self.W_o = nn.Linear(num_hiddens, num_hiddens, bias=bias)def forward(self, queries, keys, values, valid_lens):# queries,keys,values的形状:# (batch_size,查询或者“键-值”对的个数,num_hiddens)# valid_lens 的形状:# (batch_size,)或(batch_size,查询的个数)# 经过变换后,输出的queries,keys,values 的形状:# (batch_size*num_heads,查询或者“键-值”对的个数,# num_hiddens/num_heads)queries = transpose_qkv(self.W_q(queries), self.num_heads)keys = transpose_qkv(self.W_k(keys), self.num_heads)values = transpose_qkv(self.W_v(values), self.num_heads)if valid_lens is not None:# 在轴0,将第一项(标量或者矢量)复制num_heads次,# 然后如此复制第二项,然后诸如此类。valid_lens = torch.repeat_interleave(valid_lens, repeats=self.num_heads, dim=0)# output的形状:(batch_size*num_heads,查询的个数,# num_hiddens/num_heads)output = self.attention(queries, keys, values, valid_lens)# output_concat的形状:(batch_size,查询的个数,num_hiddens)output_concat = transpose_output(output, self.num_heads)return self.W_o(output_concat)#@save
def transpose_qkv(X, num_heads):"""为了多注意力头的并行计算而变换形状"""# 输入X的形状:(batch_size,查询或者“键-值”对的个数,num_hiddens)# 输出X的形状:(batch_size,查询或者“键-值”对的个数,num_heads,# num_hiddens/num_heads)X = X.reshape(X.shape[0], X.shape[1], num_heads, -1)# 输出X的形状:(batch_size,num_heads,查询或者“键-值”对的个数,# num_hiddens/num_heads)X = X.permute(0, 2, 1, 3)# 最终输出的形状:(batch_size*num_heads,查询或者“键-值”对的个数,# num_hiddens/num_heads)return X.reshape(-1, X.shape[2], X.shape[3])#@save
def transpose_output(X, num_heads):"""逆转transpose_qkv函数的操作"""X = X.reshape(-1, num_heads, X.shape[1], X.shape[2])X = X.permute(0, 2, 1, 3)return X.reshape(X.shape[0], X.shape[1], -1)num_hiddens, num_heads = 100, 5
attention = MultiHeadAttention(num_hiddens, num_hiddens, num_hiddens,num_hiddens, num_heads, 0.5)
attention.eval()batch_size, num_queries = 2, 4
num_kvpairs, valid_lens =  6, torch.tensor([3, 2])
X = torch.ones((batch_size, num_queries, num_hiddens))
Y = torch.ones((batch_size, num_kvpairs, num_hiddens))
attention(X, Y, Y, valid_lens).shape
print("=========test==========")

六、自注意力和位置编码

  • 在自注意力中,查询、键和值都来自同一组输入。
  • 卷积神经网络和自注意力都拥有并行计算的优势,而且自注意力的最大路径长度最短。但是因为其计算复杂度是关于序列长度的二次方,所以在很长的序列中计算会非常慢。
  • 为了使用序列的顺序信息,可以通过在输入表示中添加位置编码,来注入绝对的或相对的位置信息。

6.1 比较卷积神经网络、循环神经网络和自注意力

给定一个由词元组成的输入序列 x 1 , … , x n \mathbf{x}_1, \ldots, \mathbf{x}_n x1,,xn,其中任意 x i ∈ R d \mathbf{x}_i \in \mathbb{R}^d xiRd 1 ≤ i ≤ n 1 \leq i \leq n 1in)。该序列的自注意力输出为一个长度相同的序列 y 1 , … , y n \mathbf{y}_1, \ldots, \mathbf{y}_n y1,,yn,其中:

y i = f ( x i , ( x 1 , x 1 ) , … , ( x n , x n ) ) ∈ R d \mathbf{y}_i = f(\mathbf{x}_i, (\mathbf{x}_1, \mathbf{x}_1), \ldots, (\mathbf{x}_n, \mathbf{x}_n)) \in \mathbb{R}^d yi=f(xi,(x1,x1),,(xn,xn))Rd

根据之前定义的注意力汇聚函数 f f f f ( x ) = ∑ i = 1 n α ( x , x i ) y i f(x)=\sum_{i=1}^n \alpha\left(x, x_i\right) y_i f(x)=i=1nα(x,xi)yi
下面的代码片段是基于多头注意力对一个张量完成自注意力的计算,张量的形状为(批量大小,时间步的数目或词元序列的长度, d d d)。输出与输入的张量形状相同。
在这里插入图片描述


6.2 位置编码

在处理词元序列时,循环神经网络是逐个的重复地处理词元的,而自注意力则因为并行计算而放弃了顺序操作。为了使用序列的顺序信息,通过在输入表示中添加位置编码(positional encoding)来注入绝对的或相对的位置信息。位置编码可以通过学习得到也可以直接固定得到。接下来描述的是基于正弦函数和余弦函数的固定位置编码( (Vaswani et al., 2017)。)。

假设输入表示 X ∈ R n × d \mathbf{X} \in \mathbb{R}^{n \times d} XRn×d包含一个序列中 n n n个词元的 d d d维嵌入表示。位置编码使用相同形状的位置嵌入矩阵 P ∈ R n × d \mathbf{P} \in \mathbb{R}^{n \times d} PRn×d输出 X + P \mathbf{X} + \mathbf{P} X+P,矩阵第 i i i行、第 2 j 2j 2j列和 2 j + 1 2j+1 2j+1列上的元素为:

p i , 2 j = sin ⁡ ( i 1000 0 2 j / d ) , p i , 2 j + 1 = cos ⁡ ( i 1000 0 2 j / d ) . \begin{aligned} p_{i, 2j} &= \sin\left(\frac{i}{10000^{2j/d}}\right),\\p_{i, 2j+1} &= \cos\left(\frac{i}{10000^{2j/d}}\right).\end{aligned} pi,2jpi,2j+1=sin(100002j/di),=cos(100002j/di).

在解释这个设计之前,让我们先在下面的PositionalEncoding类中实现它。

(1)绝对位置编码

(2)相对位置编码

除了捕获绝对位置信息之外,上述的位置编码还允许模型学习得到输入序列中相对位置信息。这是因为对于任何确定的位置偏移 δ \delta δ,位置 i + δ i + \delta i+δ处的位置编码可以线性投影位置 i i i处的位置编码来表示。

这种投影的数学解释是,令 ω j = 1 / 1000 0 2 j / d \omega_j = 1/10000^{2j/d} ωj=1/100002j/d,对于任何确定的位置偏移 δ \delta δ,中的任何一对 ( p i , 2 j , p i , 2 j + 1 ) (p_{i, 2j}, p_{i, 2j+1}) (pi,2j,pi,2j+1)都可以线性投影到
( p i + δ , 2 j , p i + δ , 2 j + 1 ) (p_{i+\delta, 2j}, p_{i+\delta, 2j+1}) (pi+δ,2j,pi+δ,2j+1)

[ cos ⁡ ( δ ω j ) sin ⁡ ( δ ω j ) − sin ⁡ ( δ ω j ) cos ⁡ ( δ ω j ) ] [ p i , 2 j p i , 2 j + 1 ] = [ cos ⁡ ( δ ω j ) sin ⁡ ( i ω j ) + sin ⁡ ( δ ω j ) cos ⁡ ( i ω j ) − sin ⁡ ( δ ω j ) sin ⁡ ( i ω j ) + cos ⁡ ( δ ω j ) cos ⁡ ( i ω j ) ] = [ sin ⁡ ( ( i + δ ) ω j ) cos ⁡ ( ( i + δ ) ω j ) ] = [ p i + δ , 2 j p i + δ , 2 j + 1 ] , \begin{aligned} &\begin{bmatrix} \cos(\delta \omega_j) & \sin(\delta \omega_j) \\ -\sin(\delta \omega_j) & \cos(\delta \omega_j) \\ \end{bmatrix} \begin{bmatrix} p_{i, 2j} \\ p_{i, 2j+1} \\ \end{bmatrix}\\ =&\begin{bmatrix} \cos(\delta \omega_j) \sin(i \omega_j) + \sin(\delta \omega_j) \cos(i \omega_j) \\ -\sin(\delta \omega_j) \sin(i \omega_j) + \cos(\delta \omega_j) \cos(i \omega_j) \\ \end{bmatrix}\\ =&\begin{bmatrix} \sin\left((i+\delta) \omega_j\right) \\ \cos\left((i+\delta) \omega_j\right) \\ \end{bmatrix}\\ =& \begin{bmatrix} p_{i+\delta, 2j} \\ p_{i+\delta, 2j+1} \\ \end{bmatrix}, \end{aligned} ===[cos(δωj)sin(δωj)sin(δωj)cos(δωj)][pi,2jpi,2j+1][cos(δωj)sin(iωj)+sin(δωj)cos(iωj)sin(δωj)sin(iωj)+cos(δωj)cos(iωj)][sin((i+δ)ωj)cos((i+δ)ωj)][pi+δ,2jpi+δ,2j+1],

2 × 2 2\times 2 2×2投影矩阵不依赖于任何位置的索引 i i i

七、Transformer架构

  • Transformer是编码器-解码器架构的一个实践,尽管在实际情况中编码器或解码器可以单独使用。
  • 在Transformer中,多头自注意力用于表示输入序列和输出序列,不过解码器必须通过掩蔽机制来保留自回归属性。
  • Transformer中的残差连接和层规范化是训练非常深度模型的重要工具。
  • Transformer模型中基于位置的前馈网络使用同一个多层感知机,作用是对所有序列位置的表示进行转换。

在这里插入图片描述

# !/usr/bin/python
# -*- coding: utf-8 -*-
import math
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l#@save
class PositionWiseFFN(nn.Module):"""基于位置的前馈网络"""def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,**kwargs):super(PositionWiseFFN, self).__init__(**kwargs)self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)self.relu = nn.ReLU()self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)def forward(self, X):return self.dense2(self.relu(self.dense1(X)))ffn = PositionWiseFFN(4, 4, 8)
ffn.eval()
ffn(torch.ones((2, 3, 4)))[0]# resnet
ln = nn.LayerNorm(2)
bn = nn.BatchNorm1d(2)
X = torch.tensor([[1, 2], [2, 3]], dtype=torch.float32)
# 在训练模式下计算X的均值和方差
print('layer norm:', ln(X), '\nbatch norm:', bn(X))#@save
class AddNorm(nn.Module):"""残差连接后进行层规范化"""def __init__(self, normalized_shape, dropout, **kwargs):super(AddNorm, self).__init__(**kwargs)self.dropout = nn.Dropout(dropout)self.ln = nn.LayerNorm(normalized_shape)def forward(self, X, Y):return self.ln(self.dropout(Y) + X)add_norm = AddNorm([3, 4], 0.5)
add_norm.eval()
add_norm(torch.ones((2, 3, 4)), torch.ones((2, 3, 4))).shape# encoder
#@save
class EncoderBlock(nn.Module):"""Transformer编码器块"""def __init__(self, key_size, query_size, value_size, num_hiddens,norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,dropout, use_bias=False, **kwargs):super(EncoderBlock, self).__init__(**kwargs)self.attention = d2l.MultiHeadAttention(key_size, query_size, value_size, num_hiddens, num_heads, dropout,use_bias)self.addnorm1 = AddNorm(norm_shape, dropout)self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens)self.addnorm2 = AddNorm(norm_shape, dropout)def forward(self, X, valid_lens):Y = self.addnorm1(X, self.attention(X, X, X, valid_lens))return self.addnorm2(Y, self.ffn(Y))X = torch.ones((2, 100, 24))
valid_lens = torch.tensor([3, 2])
encoder_blk = EncoderBlock(24, 24, 24, 24, [100, 24], 24, 48, 8, 0.5)
encoder_blk.eval()
encoder_blk(X, valid_lens).shape#@save
class TransformerEncoder(d2l.Encoder):"""Transformer编码器"""def __init__(self, vocab_size, key_size, query_size, value_size,num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,num_heads, num_layers, dropout, use_bias=False, **kwargs):super(TransformerEncoder, self).__init__(**kwargs)self.num_hiddens = num_hiddensself.embedding = nn.Embedding(vocab_size, num_hiddens)self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)self.blks = nn.Sequential()for i in range(num_layers):self.blks.add_module("block"+str(i),EncoderBlock(key_size, query_size, value_size, num_hiddens,norm_shape, ffn_num_input, ffn_num_hiddens,num_heads, dropout, use_bias))def forward(self, X, valid_lens, *args):# 因为位置编码值在-1和1之间,# 因此嵌入值乘以嵌入维度的平方根进行缩放,# 然后再与位置编码相加。X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))self.attention_weights = [None] * len(self.blks)for i, blk in enumerate(self.blks):X = blk(X, valid_lens)self.attention_weights[i] = blk.attention.attention.attention_weightsreturn Xencoder = TransformerEncoder(200, 24, 24, 24, 24, [100, 24], 24, 48, 8, 2, 0.5)
encoder.eval()
encoder(torch.ones((2, 100), dtype=torch.long), valid_lens).shape# decoder
class DecoderBlock(nn.Module):"""解码器中第i个块"""def __init__(self, key_size, query_size, value_size, num_hiddens,norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,dropout, i, **kwargs):super(DecoderBlock, self).__init__(**kwargs)self.i = iself.attention1 = d2l.MultiHeadAttention(key_size, query_size, value_size, num_hiddens, num_heads, dropout)self.addnorm1 = AddNorm(norm_shape, dropout)self.attention2 = d2l.MultiHeadAttention(key_size, query_size, value_size, num_hiddens, num_heads, dropout)self.addnorm2 = AddNorm(norm_shape, dropout)self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens,num_hiddens)self.addnorm3 = AddNorm(norm_shape, dropout)def forward(self, X, state):enc_outputs, enc_valid_lens = state[0], state[1]# 训练阶段,输出序列的所有词元都在同一时间处理,# 因此state[2][self.i]初始化为None。# 预测阶段,输出序列是通过词元一个接着一个解码的,# 因此state[2][self.i]包含着直到当前时间步第i个块解码的输出表示if state[2][self.i] is None:key_values = Xelse:key_values = torch.cat((state[2][self.i], X), axis=1)state[2][self.i] = key_valuesif self.training:batch_size, num_steps, _ = X.shape# dec_valid_lens的开头:(batch_size,num_steps),# 其中每一行是[1,2,...,num_steps]dec_valid_lens = torch.arange(1, num_steps + 1, device=X.device).repeat(batch_size, 1)else:dec_valid_lens = None# 自注意力X2 = self.attention1(X, key_values, key_values, dec_valid_lens)Y = self.addnorm1(X, X2)# 编码器-解码器注意力。# enc_outputs的开头:(batch_size,num_steps,num_hiddens)Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)Z = self.addnorm2(Y, Y2)return self.addnorm3(Z, self.ffn(Z)), statedecoder_blk = DecoderBlock(24, 24, 24, 24, [100, 24], 24, 48, 8, 0.5, 0)
decoder_blk.eval()
X = torch.ones((2, 100, 24))
state = [encoder_blk(X, valid_lens), valid_lens, [None]]
decoder_blk(X, state)[0].shapeclass TransformerDecoder(d2l.AttentionDecoder):def __init__(self, vocab_size, key_size, query_size, value_size,num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,num_heads, num_layers, dropout, **kwargs):super(TransformerDecoder, self).__init__(**kwargs)self.num_hiddens = num_hiddensself.num_layers = num_layersself.embedding = nn.Embedding(vocab_size, num_hiddens)self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)self.blks = nn.Sequential()for i in range(num_layers):self.blks.add_module("block"+str(i),DecoderBlock(key_size, query_size, value_size, num_hiddens,norm_shape, ffn_num_input, ffn_num_hiddens,num_heads, dropout, i))self.dense = nn.Linear(num_hiddens, vocab_size)def init_state(self, enc_outputs, enc_valid_lens, *args):return [enc_outputs, enc_valid_lens, [None] * self.num_layers]def forward(self, X, state):X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))self._attention_weights = [[None] * len(self.blks) for _ in range (2)]for i, blk in enumerate(self.blks):X, state = blk(X, state)# 解码器自注意力权重self._attention_weights[0][i] = blk.attention1.attention.attention_weights# “编码器-解码器”自注意力权重self._attention_weights[1][i] = blk.attention2.attention.attention_weightsreturn self.dense(X), state@propertydef attention_weights(self):return self._attention_weights# train
num_hiddens, num_layers, dropout, batch_size, num_steps = 32, 2, 0.1, 64, 10
lr, num_epochs, device = 0.005, 200, d2l.try_gpu()
ffn_num_input, ffn_num_hiddens, num_heads = 32, 64, 4
key_size, query_size, value_size = 32, 32, 32
norm_shape = [32]train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)encoder = TransformerEncoder(len(src_vocab), key_size, query_size, value_size, num_hiddens,norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,num_layers, dropout)
decoder = TransformerDecoder(len(tgt_vocab), key_size, query_size, value_size, num_hiddens,norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,num_layers, dropout)
net = d2l.EncoderDecoder(encoder, decoder)
d2l.train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)# BLUE metric
engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .']
fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .']for eng, fra in zip(engs, fras):translation, dec_attention_weight_seq = d2l.predict_seq2seq(net, eng, src_vocab, tgt_vocab, num_steps, device, True)print(f'{eng} => {translation}, ',f'bleu {d2l.bleu(translation, fra, k=2):.3f}')enc_attention_weights = torch.cat(net.encoder.attention_weights, 0).reshape((num_layers, num_heads,-1, num_steps))
enc_attention_weights.shaped2l.show_heatmaps(enc_attention_weights.cpu(), xlabel='Key positions',ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)],figsize=(7, 3.5))
d2l.plt.show()

在这里插入图片描述

#
dec_attention_weights_2d = [head[0].tolist()for step in dec_attention_weight_seqfor attn in step for blk in attn for head in blk]
dec_attention_weights_filled = torch.tensor(pd.DataFrame(dec_attention_weights_2d).fillna(0.0).values)
dec_attention_weights = dec_attention_weights_filled.reshape((-1, 2, num_layers, num_heads, num_steps))
dec_self_attention_weights, dec_inter_attention_weights = \dec_attention_weights.permute(1, 2, 3, 0, 4)
dec_self_attention_weights.shape, dec_inter_attention_weights.shape# Plusonetoincludethebeginning-of-sequencetoken
d2l.show_heatmaps(dec_self_attention_weights[:, :, :, :len(translation.split()) + 1],xlabel='Key positions', ylabel='Query positions',titles=['Head %d' % i for i in range(1, 5)], figsize=(7, 3.5))
d2l.plt.show()

在这里插入图片描述

d2l.show_heatmaps(dec_inter_attention_weights, xlabel='Key positions',ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)],figsize=(7, 3.5))
d2l.plt.show()
print("==============test===========")

在这里插入图片描述

八、DIN序列推荐模型

先求出注意力矩阵,和初始历史item序列矩阵相乘,得到加权后的矩阵,和target矩阵拼接后直接进入dice和sigmoid激活函数。

class DIN( nn.Module ):def __init__( self, n_items, dim = 128, t = 64 ):super( DIN, self ).__init__()# 随机初始化所有物品向量self.items = nn.Embedding( n_items, dim, max_norm = 1 )# 直接通过linear从2*dim转为out_dim为1self.fliner = nn.Linear( dim * 2, 1 )# 注意力计算中的线性层self.attention_liner = nn.Linear( dim, t )self.h = init.xavier_uniform_( Parameter( torch.empty( t, 1 ) ) )#初始化一个BN层,在dice计算时会用到self.BN = nn.BatchNorm1d( 1 )#Dice激活函数def Dice( self, embs, a = 0.1 ):prob = torch.sigmoid( self.BN( embs ) )return prob * embs + ( 1 - prob ) * a * embs#注意力计算def attention( self, embs ):# embs: [ batch_size, k ]#[ batch_size, t ]embs = self.attention_liner( embs )#[ batch_size, t ]embs = torch.relu( embs )#[ batch_size, 1 ]embs = torch.matmul( embs, self.h )#[ batch_size, 1 ]atts = torch.softmax( embs, dim=1 )return attsdef forward(self, x, item, isTrain = True):# [ batch_size, len_seqs, dim ]item_embs = self.items( x )# [ batch_size, len_seqs, 1 ]atts = self.attention( item_embs )# [ batch_size, dim], 其中(item_embs * atts)的shape: [batch_size, len_seqs, emb_dim], 对len_seqs即5个历史序列进行加权求和sumWeighted = torch.sum( item_embs * atts, dim = 1 )# [ batch_size, dim]one_item = self.items(item)   # target item(only one)# [ batch_size, dim*2 ] : dim = 1表示横向拼接out = torch.cat( [ sumWeighted, one_item ], dim = 1 )# 训练时采取dropout来防止过拟合if isTrain: out = F.dropout(out)# [ batch_size, 1 ]out = self.fliner( out )# 类比sigmoid functionout = self.Dice( out )# [ batch_size ]out = torch.squeeze( out )logit = torch.sigmoid( out )return logit

时间安排

打卡日:19号周日、21号周二、23号周四、28号周二、30号周四。

内容任务预估天数任务时间完成情况
task1dl基础+CP10-注意力机制(一)一天3月19号周日完成
task2CP10-注意力机制(二)10.1-10.4两天3月20、21号周二
task3CP10-注意力机制(三) 10.5-10.7两天3月22号周三、23号周四
task4CP14-预训练(一)14.8-14.9两天3月24号周五、25号周六
task5CP14-预训练(二) 14.10两天3月26周日、27、28周二
task6CP15-NLP应用15.4-15.5两天3月29号、30号周四

Reference

[1] 动手学深度学习.李沐
[2] 动手学深度学习-文本处理
[3] 动手学深度学习-注意力机制CP10
[4] https://discuss.d2l.ai
[5] 教材:https://zh-v2.d2l.ai/
[6] 视频: https://space.bilibili.com/1567748478/channel/seriesdetail?sid=358497
[7] 笔记:https://github.com/MLNLP-World/DeepLearning-MuLi-Notes/tree/main/notes
[8] 竞赛:https://tianchi.aliyun.com/competition/entrance/231784/introduction?spm=5176.12281973.0.0.7c47106baWMBl3
[9] OpenI:https://openi.pcl.ac.cn/Datawhale/d2l
[10] 注意力汇聚:Nadaraya-Watson 核回归——动手学dl的讨论区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/24764.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

普通人应该怎么赚钱,下班后可在家操作的四个兼职副业分享

大家好&#xff0c;我是蝶衣王的小编 不少朋友发觉&#xff0c;单靠薪水好像已经很难再存下钱了&#xff0c;甚至有时候还不够一个月的开销。实际上&#xff0c;如果你想要提升月收益&#xff0c;你可以选择发展副业。那发展什么副业才能赚钱呢 有小伙伴要问&#xff1a;我要…

疫情期间谁干谁赚钱的三个小副业,普通人也能干,基本不用你投资

一切生意的本质&#xff0c;都是低买高卖&#xff0c;只是有时&#xff0c;这个利差来自货物&#xff0c;有时利差来自服务而已。 疫情过后&#xff0c;三个生意千万不要干&#xff0c;谁干谁倒闭&#xff0c;天王老子救不了你。还有三个生意&#xff0c;谁干谁发财&#xff0…

有稳定工作,千万不要裸辞摆地摊

千万别因为看抖音看视频看多了&#xff0c;听信别人说摆摊的上万的&#xff0c;那都搏流量和收徒用的&#xff0c;不过也有人赚的&#xff0c;要付出很多时间的那种&#xff0c;哈哈&#xff01; 五月份从一家百人规模互联网公司设计岗裸辞&#xff0c;干起来摆地摊&#xff0c…

工作只能解决温饱,副业才是你的未来

欢迎关注勤于奋、每天12点准时更新国外LEAD相关技术 今天这个标题&#xff0c;有几个人理解&#xff1f; 本来这个标题都取好了&#xff0c;而且也想跟大家分享一些知识&#xff0c;结果突然我QQ被强制下线&#xff0c;原来是被举报了&#xff0c;我真的是服了。 真的把我吓了一…

非常适合金融人的副业,不用坐班,时间自由!

最近在论坛上看到一个测试&#xff0c;特扎心&#xff1a; 以下三种情况&#xff0c;哪个让你最绝望&#xff1f; ❶ 月薪4500&#xff0c;花呗欠了10000 ❷ 被领导骂到哭&#xff0c;因为没钱不敢裸职 ❸ 租房子的中介公司突然倒闭&#xff0c;房东逼你搬出去&#xff0c;…

该怎么选择副业,三条建议形成自己的副业思维

受经济环境的影响&#xff0c;许多年轻人觉得原来稳定的工作不那么稳定&#xff0c;看着周围的朋友因为企业破产和失业&#xff0c;生活变得没有信心&#xff0c;也想找到自己的副业&#xff0c;在紧急情况下赚更多的钱。所以&#xff0c;年轻人在选择副业时也面临着很多困惑&a…

辞职文案火了,程序员的辞职理由要命不要钱。

今日刷微博&#xff0c;一位在丽江乞讨的姑娘火了&#xff0c;因为她的乞讨理由是&#xff0c;“本人美貌如花&#xff0c;所以不想上班&#xff0c;在此乞讨”…… 敢用这么奇葩的乞讨理由&#xff0c;这位姑娘背后的真实目的&#xff0c;我们暂按下不表。不过不想上班的理由有…

微信为什么使用 SQLite 保存聊天记录?

概要 SQLite 是一个被大家低估的数据库&#xff0c;但有些人认为它是一个不适合生产环境使用的玩具数据库。事实上&#xff0c;SQLite 是一个非常可靠的数据库&#xff0c;它可以处理 TB 级的数据&#xff0c;但它没有网络层。接下来&#xff0c;本文将与大家共同探讨 SQLite 在…

Linux找回用户密码

Linux中有两种用户模式&#xff0c;root用户和普通用户&#xff0c;这两种方法找回密码的方式各不相同&#xff0c;我们来分别在两种用户模式下完成找回密码的任务。 一.root用户找回密码 Linux拥有7个运行级别&#xff08;0&#xff1a;关机、1&#xff1a;单用户、2&#xf…

Postgresql忘记密码,如何重置密码

一、前言 系统&#xff1a;windows系统 64位 Postgresql 版本&#xff1a;Postgresql 14 今天想测试一个PostgresSQL语法的 SQL&#xff0c;但是打开PostgresSQL之后沉默了。密码是什么&#xff1f; 想来当初知识安装随便玩一下&#xff0c;也没有太留意记一下密码&#xff0c;…

Centos8忘记密码了怎么办?Centos8修改密码超详细超实用方法

1. 重启Centos8&#xff0c;出现以下内容时赶紧按几下“上下方向键PgUp和PgDn”&#xff08;首先要确保鼠标在界面内&#xff09;&#xff0c;使其停留在这个界面&#xff0c;然后选中第一行。 2. 按键盘上的“e”&#xff0c;进入编辑模式&#xff08;edit select item&#…

Linux忘记密码的找回方法

(1)在登录前的界面按下“e”编辑&#xff0c;如图&#xff1a; &#xff08;2&#xff09;找到linux16所在的那一行的下面一行&#xff1a;&#xff08;如果没找到linux16就按着键盘向下的箭头就能看见了&#xff09; &#xff08;3&#xff09;在UTF-8后面按下空格后加上这句话…

打开PPT的密码忘记了,如何找回?

PPT文件设置了打开密码&#xff0c;在打开PPT文件之后就会有提示框&#xff0c;需要输入PPT密码才能够打开PPT文件&#xff0c;但是我们如果忘记了PPT密码的话&#xff0c;那就没办法打开PPT文件了。 PPT文件如果设置了加密&#xff0c;一旦忘记密码&#xff0c;也只能通过找回…

PostgreSQL忘记密码

一、如果你忘记密码&#xff0c;而你曾经登录时保存过密码&#xff0c;按C:\Users\Terry Computer\AppData\Roaming\postgresql打开&#xff0c;其中Terry Computer是你的电脑名&#xff0c;AppData一般为隐藏项目。用记事本打开pgpass.conf就可以看到你保存过的用户名和密码。…

Linux(CentOS7)找回Root密码

1.开机之前&#xff0c;在这个界面按e&#xff0c;进入编辑模式(要快一点&#xff0c;不然就错过了) 2.然后进入这个界面后&#xff1a; 3.往下找到 以linux16开头的一段&#xff1a; 4.在UTF-8后面输入 init/bin/sh 输入完成后&#xff0c;直接按快捷键: Ctrlx 进入单用户模式…

Linux找回密码

Linux找回密码 1.开启的时候要尽快点击键盘上下键&#xff0c;选中上面一个&#xff0c;然后输入 e 2.然后点击键盘上下键&#xff0c;找到linux16开头这一行&#xff0c;在行的最后输入&#xff1a;init/bin/sh 3.接着&#xff0c;输入完成后&#xff0c;直接按快捷键&#…

第二届开源云原生开发者日招募令发布!(内含福利)

随着云技术的日渐成熟 云原生逐渐从“概念”层面转变为落地“应用” 开发者从传统繁复的代码工作中解放出来 从信息化执行转向数字创新 用技术视野更新业务模式 本届开源云原生开发者日&#xff08;Open Source Cloud Native Developer Day&#xff09;是微软 与 英特尔 联合发…

操作系统复习

这里写目录标题 操作系统复习一,引论1.3 os的基本特征&#x1f36a;并发共享虚拟异步之间的关系 1.4 操作系统运行环境硬件支持操作系统内核支撑功能资源管理功能 中断与异常 1.5 操作系统的主要功能&#x1f31d;处理机管理存储器管理设备管理文件管理接口管理用户接口程序接口…

AR智能眼镜会成未来趋势

简述 目前&#xff0c;5G时代已经开启&#xff0c;5G人工智能也给音视频产业发展带来许多新的课题。工信部电子信息司副司长乔跃山在会上表示&#xff0c;当前&#xff0c;音视频正在加快与人工智能、5G信息显示等领域的融合&#xff0c;不断催生新业态和商业模式&#xff0c;…

AR(增强现实)技术是什么,未来发展会怎样

目录 一、AR是什么&#xff1f;二、AR技术有什么用&#xff1f;三、AR技术未来的发展趋势 当代科技领域中&#xff0c;增强现实&#xff08;Augmented Reality&#xff0c;简称AR&#xff09;技术正逐渐崭露头角&#xff0c;成为引领未来科技发展的一股重要力量。AR技术是一种通…