注意力机制和自注意力机制的区别在于什么(Transformer:注意力机制(attention)和自注意力机制(self-attention)的学习总结)
前言
由于我对注意力机制和自注意力机制的原理和区别不是很清楚 。因此 ,对相关内容进行了深入学习 。在阅读多个博客后 ,我自己总结出了本篇文章 ,分享给各位读者朋友 。由于我才疏学浅 ,如果理解有偏差 ,则望各位读者朋友及时指出 。
⌣
¨
\ddot\smile
⌣¨本文主要分为两个部分:
第一部分重点介绍了注意力机制的原理 、公式演变 、评分函数以及多头注意力 。
第二部分重点介绍了自注意力机制的原理 、优缺点以及和注意力机制的区别 ,并最后给出了单头和多头自注意力机制的图片示例 。
建议顺序阅读 ,也可以在目录中选择感兴趣的章节本文的参考博客如下 ,其中大部分理解来自于动手学深度学习的注意力机制部分 。如果读者在阅读完本文后仍不理解 ,可以阅读该资料 ,细节方面会更多 。
参考资料:
1.动手学深度学习
2.作者:张俊林 ,深度学习中的注意力机制(2017版)
3.作者:太阳花的小绿豆 ,self_attention和mutil self_attention的原理1. 注意力机制
注意力机制其实是源自于人对于外部信息的处理能力 。由于人每一时刻接受的信息都是无比的庞大且复杂,远远超过人脑的处理能力 ,因此人在处理信息的时候 ,会将注意力放在需要关注的信息上,对于其他无关的外部信息进行过滤 ,这种处理方式被称为注意力机制 。
1.1非自主提示和自主提示
针对于注意力机制的引起方式 ,可以分为两类 ,一种是非自主提示 ,另一种是自主提示 。其中非自主提示指的是由于物体本身的特征十分突出引起的注意力倾向 ,自主提示指的是经过先验知识的介入下 ,对具有先验权重的物体引起的注意力倾向 。换句话说 ,可以理解为非自主提示源自于物体本身 ,而自主提示源自于一种主观倾向。举例说明如下:
当我们第一眼看到上图时 ,我们便会首先将注意力集中到兔子身上 。这是因为 ,整张图中兔子的特征十分的突出 ,让人一眼就关注到兔子身上 。这种引起注意力的方式便是非自主提示。在看到兔子之后 ,我们便想兔子在干嘛,从而我们就会关注兔子的行为 。此时兔子在吃草 ,这时我们便把注意力集中在兔子周边的草上 。这种引起注意力机制的方式便是自主提示 ,其中"兔子在干嘛"则是我们主观意识 。另外,为加深理解 ,再引用一下动手学深度学习中的例子解释:
此时我们面前有五个物体 ,分别是报纸 ,论文 ,咖啡 ,笔记本和书 。首先 ,我们会关注在咖啡身上 ,因为只有咖啡是红色 ,而其他物体是黑白 。那么红色的咖啡由于其显眼的特征 ,就成了注意力机制的非自主提示 。
喝完咖啡后 ,十分精神 ,想看本书 。此时 ,通过"想看书"这种意识,我们将注意力放到了书上 。这种通过主观意识引起注意力的方式称为自主提示 。
1.2 查询 ,键和值
根据自主提示和非自主提示来设计注意力机制 。首先考虑简单情况 ,即只考虑非自主提示的话,只需要对所有物体的特征信息(非自主提示)进行简单的全连接层 ,甚至是无参数的平均汇聚层或者最大汇聚层 ,就可以提取出需要感兴趣的物体 。
下图是平均汇聚方法的示例图 ,最后结果是所有物体向量的平均加权和 。
而如果考虑自主提示的话 ,我们就需要设计一种通过查询(Query) ,键(Key)和值(Value) 来实现注意力机制的方法。其中Query指的是自主提示 ,即主观意识的特征向量 ,Key指的是非自主提示 ,即物体的突出特征信息向量 ,Value则是代表物体本身的特征向量 。
注意力机制是通过Query与Key的注意力汇聚(指的是对Query和Key的相关性进行建模 ,实现池化筛选或者分配权重) ,实现对Value的注意力权重分配 ,生成最终的输出结果 。如下图所示:
另外,还有一种理解方式。我们可以将查询 ,键和值理解为一种软寻址(Soft Addressing)
Value可以看作存储器存储的内容 ,Key看作是存储器的地址 。当Key==Query时,则取出Key地址对应存储器中的Value值 ,这被称为硬寻址 。而软寻址则是通过计算Key和Query的相似度来进行寻址 ,这种方法不只是获取一个Key地址中存储器的Value值 ,而是获取所有的存储器中的Value值的加权和 。至于每个Value的权重(重要程度) ,是通过Key和Query相似度计算得到的 ,最终的输出是所有Value值和其权重的加权和 。如下图所示:
1.3 注意力机制的公式
假设有一组数据训练数据
{
[
x
1
,
y
1
]
,
[
x
2
,
y
2
]
.
.
.
[
x
50
,
y
50
]
}
\{[x_1,y_1],[x_2,y_2]...[x_{50}, y_{50}]\}
{[x1,y1],[x2,y2]...[x50,y50]} 。我们需要通过这些数据训练出一个函数f
f
f ,从而满足y
i
=
f
(
x
i
)
,
i
=
1
,
2
,
3...50
y_i=f(x_i) ,i=1,2,3...50
yi=f(xi) ,i=1,2,3...50 。先假定函数f
f
f为s
i
n
sin
sin函数 。本示例的目的是通过训练数据{
[
x
1
,
y
1
]
,
[
x
2
,
y
2
]
.
.
.
[
x
50
,
y
50
]
}
\{[x_1,y_1],[x_2,y_2]...[x_{50}, y_{50}]\}
{[x1,y1],[x2,y2]...[x50,y50]}拟合出这个s
i
n
sin
sin函数 。先通过python代码生成一组训练数据 ,生成方法如下公式:
y
=
s
i
n
(
x
)
2
+
x
0.8
+
ϵ
y=sin(x)^2+x^{0.8}+\epsilon
y=sin(x)2+x0.8+ϵ import numpy as np import matplotlib.pyplot as plt from learn_attention_pool import CE, AttentionPoolWithParameter import torch import copy def f(x): """ 通过 y=sin(x)^2+x^0.8+\epsilon 获取所需的训练数据 。 :param x:训练数据x, 类型numpy(dim,) :return: 训练标签y, 类型numpy(dim,) """ return np.sin(x)*2 + x**0.8 def main(): # ---------------------------------------------------------------------- # 生成数据 # ---------------------------------------------------------------------- # 生成训练数据和标签 。 x_train = np.sort(np.random.rand(50)) * 6 y_train = f(x_train) + np.random.normal(0, 0.5, 50) # 生成测试数据和真实标签 。 x_test = np.arange(0, 6.28, 0.12566) y_true = f(x_test) # 绘制图像 plt.figure(1) l1 = plt.scatter(x_train, y_train, color="r") l2, = plt.plot(x_test, y_true, color="b") plt.legend(handles=[l1, l2], labels=["train_data", "sin_function"], loc="best") plt.savefig("data.png")训练数据和想要拟合的
s
i
n
sin
sin函数如下:
为了防止混淆 ,专门解释下:
训练数据:x_train, y_train是需要拟合的数据 ,
真实标签:y_true是我们想要的结果 。
测试数据:x_test是测试x_train, y_train拟合出来的函数f
f
f效果的数据。
上图中红点表示训练数据,蓝色的线表示我们想要拟合的函数 。1.3.1 平均汇聚
获得数据后 ,先使用平均汇聚方法来解决回归问题: 基于平均汇聚来计算所有训练样本输出值的平均值 ,公式如下:
f
(
x
)
=
1
n
∑
i
=
1
n
y
i
,
i
=
1
,
2
,
3...50
f(x)=\frac{1}{n}\sum^n_{i=1}y_i ,i=1,2,3...50
f(x)=n1∑i=1nyi ,i=1,2,3...50 def average_pool(y_train): """ 平均汇聚层 ,实现 f(x)=\frac{1}{n}\sum^n_{i=1}y_i :param y_train:输入数据y_train ,类型numpy(dim,) :return:numpy(公式的输出值 * dim) ,类型numpy(dim,) """ return np.array([np.sum(y_train)/len(y_train)]*len(y_train)) # ---------------------------------------------------------------------- # 平均汇聚方法 # ---------------------------------------------------------------------- average_function = average_pool(y_train) l3, = plt.plot(x_train, average_function, color="g") plt.legend(handles=[l1, l2, l3], labels=["train_data", "sin_function", "average_function"], loc="best") plt.savefig("average_function.png")结果如下:
绿线是平均汇聚的结果 ,从图中可以看出 ,平均汇聚的方法效果很差 。1.3.2 非参数的注意力汇聚(Nadaraya-Watson核回归)
很显然 ,平均汇聚方法只考虑
y
i
y_i
yi ,为了获取更好的效果还可以考虑x
i
x_i
xi。于是Nadaraya [Nadaraya, 1964]和 Watson [Watson, 1964]提出了一个方法 ,根据输入的位置对输出进行加权 。公式如下:f
(
x
)
=
∑
i
=
1
n
K
(
x
,
x
i
)
∑
j
=
1
n
K
(
x
,
x
j
)
y
i
f(x)=\sum^n_{i=1}\frac{K(x,x_i)}{\sum^n_{j=1}K(x, x_j)}y_i
f(x)=∑i=1n∑j=1nK(x,xj)K(x,xi)yi其中
K
(
x
,
x
i
)
K(x,x_i)
K(x,xi)是核函数 ,所以 ,该方法也成为Nadaraya-Watson核回归 。上述公式还可以写成一个更加通用形式来描述注意力机制,公式如下 。f
(
x
)
=
∑
i
=
1
n
a
(
x
,
x
i
)
y
i
f(x)=\sum^n_{i=1}a(x, x_i)y_i
f(x)=∑i=1na(x,xi)yi其中
a
(
x
,
x
i
)
a(x, x_i)
a(x,xi)就是下一章节所介绍的评分函数 ,其作用是对查询和键的关系进行建模 ,生成注意力权重,即attention值 ,用来描述查询和键的相似程度 。为了进一步的理解非参数的注意力汇聚 ,本示例选取高斯核作为核函数 。高斯核函数如下公式所示:
K
(
μ
)
=
1
2
π
e
x
p
(
−
μ
2
2
)
K(\mu) =\frac{1}{\sqrt{2\pi}}exp(-\frac{\mu^2}{2})
K(μ)=2π1exp(−2μ2)将该核函数带入注意力汇聚公式中 ,得到下式:
f
(
x
)
=
∑
i
=
1
n
e
x
p
(
−
1
2
(
x
−
x
i
)
2
)
∑
j
=
1
n
e
x
p
(
−
1
2
(
x
−
x
j
)
2
)
y
i
=
∑
i
=
1
n
s
o
f
t
m
a
x
(
e
x
p
(
−
1
2
(
x
−
x
i
)
2
)
)
y
i
f(x)=\sum^n_{i=1}\frac{exp(-\frac{1}{2}(x-x_i)^2)}{\sum^n_{j=1}exp(-\frac{1}{2}(x-x_j)^2)}y_i=\sum^n_{i=1}softmax(exp(-\frac{1}{2}(x-x_i)^2))y_i
f(x)=∑i=1n∑j=1nexp(−21(x−xj)2)exp(−21(x−xi)2)yi=∑i=1nsoftmax(exp(−21(x−xi)2))yi通过python代码实现该方法:
#使用到的方法 def softmax(x): """ 计算softmax值 :param x: 输入数据x, 类型numpy(dim,) :return: softmax值 , 类型numpy(dim,) """ return np.exp(x)/np.sum(np.exp(x)) def attention_pool(query_x, key, value): """ 非参数的注意力汇聚层的实现方法 。 $f(x)=\sum^n_{i=1}softmax(exp(-\frac{1}{2}(x-x_i)^2))y_i$ :param query_x:查询 , 类型numpy(dim,) :param key:键 ,类型numpy(dim,) :param value:值 ,类型numpy(dim,) :return: 注意力汇聚的加权和 ,类型numpy(dim) 。query_x中的元素 ,都是该元素通过该计算key的权重 ,和value的加权和 。 """ for i in range(len(value)): query_x[i] = np.sum(np.dot(softmax(-(query_x[i] - key)**2/2), value)) return query_x def show_heapmap(query_x, x_train): """ 计算注意力机制图 。 :param query_x: 查询, 类型numpy(dim,) :param x_train: 键 , 类型numpy(dim,) :return:注意力机制图 ,类型numpy(dim, dim) """ heapmap = [] for i in range(len(x_train)): heapmap.append(softmax(-(query_x[i] - x_train)**2/2)) heapmap = np.array(heapmap) return heapmap # ---------------------------------------------------------------------- # 非参数的注意力汇聚方法 # ---------------------------------------------------------------------- query_x = copy.deepcopy(x_test) sf_attebtiob_function = attention_pool(query_x, x_train, y_train) l4, = plt.plot(x_train, sf_attebtiob_function, color="black") plt.legend(handles=[l1, l2, l3, l4], labels=["train_data", "sin_function", "average_function", "sf_attention_function"], loc="best") plt.savefig("sf_average_function.png") # plt.show() # 生成注意力机制图 。 heap_map = show_heapmap(x_test, x_train) plt.figure(2) plt.imshow(heap_map) plt.xlabel("x_train") plt.ylabel("query_x") plt.savefig("heapmap_no_param.png")结果如下图所示:
黑色的线是非参数注意力汇聚方法的结果,从上图可以看出其结果明显优于平局汇聚方法 。下图是注意力权重图 。表示横轴Key和纵轴Query的权重关系 ,可以看出Query和Key值相似时 ,权重很大,其他情况权重很小。如下图所示:
1.3.3 带参数的注意力汇聚(Nadaraya-Watson核回归)
非参数的Nadaraya-Watson核回归具有一致性(consistency)的优点: 如果有足够的数据 ,此模型会收敛到最优结果 。 但是 ,我们也可以使用带参数的方法更好 ,更快的拟合目标函数 。公式如下所示 ,其中
w
w
w是可学习的参数。f
(
x
)
=
∑
i
=
1
n
e
x
p
(
−
1
2
(
(
x
−
x
i
)
w
)
2
)
∑
j
=
1
n
e
x
p
(
−
1
2
(
(
x
−
x
j
)
w
)
2
)
y
i
=
∑
i
=
1
n
s
o
f
t
m
a
x
(
e
x
p
(
−
1
2
(
(
x
−
x
i
)
w
)
2
)
)
y
i
f(x)=\sum^n_{i=1}\frac{exp(-\frac{1}{2}((x-x_i)w)^2)}{\sum^n_{j=1}exp(-\frac{1}{2}((x-x_j)w)^2)}y_i=\sum^n_{i=1}softmax(exp(-\frac{1}{2}((x-x_i)w)^2))y_i
f(x)=∑i=1n∑j=1nexp(−21((x−创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!