Transformer/BERT/Attention面试问题与答案
from: https://blog.csdn.net/weixin_40633696/article/details/121810403
文章目录
1. Self-Attention 的核心是什么?
2. 不考虑多头的原因,self-attention中词向量不乘QKV参数矩阵(W Q , W K , W V W_Q,W_K,W_VW
Q
,W
K
,W
V
),会有什么问题?
3. 在常规attention中,一般有k=v,那self-attention 可以嘛?
4. self-attention 在计算的过程中,如何对padding位做mask?
5. self-attention 的时间复杂度是怎么计算的?
6. transformer中multi-head attention中每个head为什么要进行降维?
7. 为什么BERT选择mask掉15%这个比例的词,可以是其他的比例吗?
8. 为什么BERT在第一句前会加一个 [CLS] 标志?
9. 使用BERT预训练模型为什么最多只能输入512个词,最多只能两个句子合成一句?
10. Transformer在哪里做了权重共享,为什么可以做权重共享?
11. BERT非线性的来源在哪里?
12. Transformer的点积模型做缩放的原因是什么?
13. BERT的三个Embedding直接相加会对语义有影响吗?
14. BERT训练时使用的学习率 warm-up 策略是怎样的?为什么要这么做?
15. 深度学习中Attention与全连接层的区别何在?
16. 在BERT应用中,如何解决长文本问题?
本文主要参考知乎作者:海晨威 的文章
1. Self-Attention 的核心是什么?
Self-Attention的核心是用文本中的其它词来增强目标词的语义表示,从而更好的利用上下文的信息。
2. 不考虑多头的原因,self-attention中词向量不乘QKV参数矩阵(W Q , W K , W V W_Q,W_K,W_VW
Q
,W
K
,W
V
),会有什么问题?
self-attention的核心是用文本中的其它词来增强目标词的语义表示,从而更好的利用上下文的信息。
self-attention中,sequence中的每个词都会和sequence中的每个词做点积计算相似度,也包括这个词本身。
对于self-attention,一般会说它的q = k = v q=k=vq=k=v,这里的相等实际上是指它们来自同一个基础向量。但是在实际计算时,它们是不一样的。因为这三者都是乘了W Q , W K , W V W_Q,W_K,W_VW
Q
,W
K
,W
V
矩阵的。如果不乘,每个词对应的q , k , v q,k,vq,k,v就是完全一样的。
在相同量级的情况下,q i q_iq
i
与k i k_ik
i
点积的值会是最大的(可以从“两数和相同的情况下,两数相等对应的积最大”类比过来)。
那在softmax后的加权平均中,该词本身所占的比重将会是最大的,使得其他词的比重很少,无法有效利用上下文信息来增强当前词的语义表示。
而乘以W Q , W K , W V W_Q,W_K,W_VW
Q
,W
K
,W
V
矩阵,会使得每个词的q , k , v q,k,vq,k,v都不一样,能很大程度上减轻上述的影响。
当然,W Q , W K , W V W_Q,W_K,W_VW
Q
,W
K
,W
V
矩阵也使得多头(类似于CNN中的多核)去捕捉更丰富的特征/信息成为可能。
输入:
Q = np.array([[2,4,4],
[2,3,5],
[3,3,4],
[4,4,2]])
K = Q.T
print('Q:\n{}'.format(Q))
print('K:\n{}'.format(K))
print('Q*K:\n{}'.format(np.dot(Q,K))
1
2
3
4
5
6
7
8
输出:
Q:
[[2 4 4]
[2 3 5]
[3 3 4]
[4 4 2]]
K:
[[2 2 3 4]
[4 3 3 4]
[4 5 4 2]]
Q*K:
[[36 36 34 32]
[36 38 35 30]
[34 35 34 32]
[32 30 32 36]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
可以发现相同向量相乘,积往往最大
输入:
q1 = np.array([2,4,4])
k1 = np.array([3,4,3])
k2 = np.array([2,4,4])
print('q1和k1点积:{}\nq1和k2点积:{}'.format(np.dot(q1,k1.T),np.dot(q1,k2.T)))
1
2
3
4
输出:
q1和k1点积:34
q1和k2点积:36
1
2
3. 在常规attention中,一般有k=v,那self-attention 可以嘛?
self-attention实际只是attention中的一种特殊情况,因此k=v是没有问题的,也即K,V参数矩阵相同。
扩展到Multi-Head Attention中,乘以QK参数矩阵之后,其实就已经保证了多头之间的差异性了,在q和k点积+softmax得到相似度之后,从常规attention的角度,觉得再去乘以和k相等的v会更合理一些。
在Transformer/BERT中,完全独立的QKV参数矩阵,可以扩大模型的容量和表达能力。
但采用Q,K=V这样的参数模式,我认为也是没有问题的,也能减少模型的参数,又不影响多头的实现。
当然,上述想法并没有做过实验,为个人观点,仅供参考。
self-attention源码解读: 目前主流的attention方法都有哪些?
4. self-attention 在计算的过程中,如何对padding位做mask?
在 Attention 机制中,同样需要忽略 padding 部分的影响,这里以transformer encoder中的self-attention为例:
self-attention中,Q和K在点积之后,需要先经过mask再进行softmax,因此,对于要屏蔽的部分,mask之后的输出需要为负无穷,这样softmax之后输出才为0。
5. self-attention 的时间复杂度是怎么计算的?
Self-Attention时间复杂度:O ( n 2 ⋅ d ) O(n^2·d)O(n
2
⋅d),这里n是序列的长度,d是embedding的维度。
Self-Attention包括三个步骤:(1)相似度计算(2)softmax(3)加权平均,它们分别的时间复杂度是:
(1)相似度计算可以看作大小为(n,d)和(d,n)的两个矩阵相乘:( n , d ) × ( d , n ) = O ( n 2 ⋅ d ) (n,d)\times(d,n)=O(n^2·d)(n,d)×(d,n)=O(n
2
⋅d) ,得到一个(n,n)的矩阵
(2)softmax就是直接计算了,时间复杂度为O ( 𝑛 2 ) O(𝑛^2)O(n
2
)
(3)加权平均可以看作大小为(n,n)和(n,d)的两个矩阵相乘:( n , n ) × ( d , n ) = O ( n 2 ⋅ d ) (n,n)\times(d,n)=O(n^2·d)(n,n)×(d,n)=O(n
2
⋅d) ,得到一个(n,d)的矩阵
因此,Self-Attention的时间复杂度是( n , d ) × ( d , n ) = O ( n 2 ⋅ d ) (n,d)\times(d,n)=O(n^2·d)(n,d)×(d,n)=O(n
2
⋅d) 。
6. transformer中multi-head attention中每个head为什么要进行降维?
一言蔽之的话,大概是:在不增加时间复杂度的情况下,同时,借鉴CNN多核的思想,在更低的维度,在多个独立的特征空间,更容易学习到更丰富的特征信息。
前面多位答主也都提到了,在经过维度的“分割”之后,在多个低维空间,相比原有的高维空间,能降低特征学习的难度。
Self-Attention时间复杂度:O ( n 2 ⋅ d ) O(n^2·d)O(n
2
⋅d)(5.中给出了解释)
对于 Multi-Head Attention,简单来说就是多个 Self-Attention 的组合,但多头的实现不是循环的计算每个头,而是通过 transposes and reshapes,用矩阵乘法来完成的。
In practice, the multi-headed attention are done with transposes and reshapes rather than actual separate tensors.——来自google BERT源代码注释
1
Transformer中把 d ,也就是hidden_size/embedding_size这个维度做了reshape拆分,具体可以看对应的 pytorch 代码:
hidden_size (d) = num_attention_heads (m) * attention_head_size (a),也即 d=m*a
1
并将 num_attention_heads 维度transpose到前面,使得Q和K的维度都是(m,n,a),这里不考虑batch维度。
这样点积可以看作大小为(m,n,a)和(m,a,n)的两个张量相乘,得到一个(m,n,n)的矩阵,其实就相当于(n,a)和(a,n)的两个矩阵相乘,做了m次,时间复杂度是:
O ( n 2 ⋅ m ⋅ a ) = O ( n 2 ⋅ d ) O(n^2·m·a) = O(n^2·d)O(n
2
⋅m⋅a)=O(n
2
⋅d)
张量乘法时间复杂度分析参见:矩阵、张量乘法(numpy.tensordot)的时间复杂度分析
因此Multi-Head Attention时间复杂度也是O ( n 2 ⋅ d ) O(n^2·d)O(n
2
⋅d),复杂度相较单头并没有变化,主要还是transposes and reshapes 的操作,相当于把一个大矩阵相乘变成了多个小矩阵的相乘。
正是经过这样维度的“分割”,才有了Multi-Head Attention,不增加复杂度,又提升了效果,但其实并没有什么必然性,是实验之后的结果,验证了它的有效性。
7. 为什么BERT选择mask掉15%这个比例的词,可以是其他的比例吗?
BERT采用的Masked LM,会选取语料中所有词的15%进行随机mask,论文中表示是受到完形填空任务的启发,但其实与CBOW也有异曲同工之妙。
从CBOW的角度,这里p = 15 % p=15\%p=15%有一个比较好的解释是:在一个大小为1 p = 100 15 ≈ 7 \frac{1}{p}=\frac{100}{15}\approx7
p
1
=
15
100
≈7的窗口中随机选一个词,类似CBOW中滑动窗口的中心词,区别是这里的滑动窗口是非重叠的。
那从CBOW的滑动窗口角度,10%~20%都是还ok的比例。
上述非官方解释,是来自我的一位朋友提供的一个理解切入的角度,供参考。
8. 为什么BERT在第一句前会加一个 [CLS] 标志?
BERT在第一句前会加一个 [CLS] 标志,最后一层该位对应向量可以作为整句话的语义表示,从而用于下游的分类任务等。
为什么选它?因为与文本中已有的其它词相比,这个无明显语义信息的符号会更“公平”地融合文本中各个词的语义信息,从而更好的表示整句话的语义。
具体来说,self-attention是用文本中的其它词来增强目标词的语义表示,但是目标词本身的语义还是会占主要部分的,因此,经过BERT的12层,每次词的embedding融合了所有词的信息,可以去更好的表示自己的语义。
而 [CLS] 位本身没有语义,经过12层,得到的是attention后所有词的加权平均,相比其他正常词,可以更好的表征句子语义。
当然,也可以通过对最后一层所有词的embedding做pooling去表征句子语义。
这里补充一下BERT的输出,有两种,在BERT TF源码中对应:
一种是get_pooled_out(),就是上述 [CLS] 的表示,输出shape是[batch size,hidden size]。
一种是get_sequence_out(),获取的是整个句子每一个token的向量表示,输出shape是[batch_size, seq_length, hidden_size],这里也包括 [CLS],因此在做token级别的任务时要注意它。
9. 使用BERT预训练模型为什么最多只能输入512个词,最多只能两个句子合成一句?
这是Google BERT预训练模型初始设置的原因,前者对应Position Embeddings,后者对应Segment Embeddings
在BERT config中:
"max_position_embeddings": 512
"type_vocab_size": 2
1
2
因此,在直接使用Google 的BERT预训练模型时,输入最多512个词(还要除掉[CLS]和[SEP]),最多两个句子合成一句。这之外的词和句子会没有对应的embedding。
当然,如果有足够的硬件资源自己重新训练BERT,可以更改 BERT config,设置更大max_position_embeddings 和 type_vocab_size值去满足自己的需求。
10. Transformer在哪里做了权重共享,为什么可以做权重共享?
Transformer在两个地方进行了权重共享:
(1)Encoder和Decoder间的Embedding层权重共享;
(2)Decoder中Embedding层和FC层权重共享。
对于(1),《Attention is all you need》中Transformer被应用在机器翻译任务中,源语言和目标语言是不一样的,但它们可以共用一张大词表,对于两种语言中共同出现的词(比如:数字,标点等等)可以得到更好的表示,而且对于Encoder和Decoder,嵌入时都只有对应语言的embedding会被激活,因此是可以共用一张词表做权重共享的。
论文中,Transformer词表用了bpe来处理,所以最小的单元是subword。英语和德语同属日耳曼语族,有很多相同的subword,可以共享类似的语义。而像中英这样相差较大的语系,语义共享作用可能不会很大。
但是,共用词表会使得词表数量增大,增加softmax的计算时间,因此实际使用中是否共享可能要根据情况权衡。
该点参考:为什么transformer可以在embedding层之间共享参数?
对于(2),Embedding层可以说是通过onehot去取到对应的embedding向量,FC层可以说是相反的,通过向量(定义为 x)去得到它可能是某个词的softmax概率,取概率最大(贪婪情况下)的作为预测值。
那哪一个会是概率最大的呢?在FC层的每一行量级相同的前提下,理论上和 x 相同的那一行对应的点积和softmax概率会是最大的(可类比本文问题1)。
因此,Embedding层和FC层权重共享,Embedding层中和向量 x 最接近的那一行对应的词,会获得更大的预测概率。实际上,Decoder中的Embedding层和FC层有点像互为逆过程。
通过这样的权重共享可以减少参数的数量,加快收敛。
但开始我有一个困惑是:Embedding层参数维度是:(v,d),FC层参数维度是:(d,v),可以直接共享嘛,还是要转置?其中v是词表大小,d是embedding维度。
查看 pytorch 源码发现真的可以直接共享:
fc = nn.Linear(d, v, bias=False) # Decoder FC层定义
weight = Parameter(torch.Tensor(out_features, in_features)) # Linear层权重定义
1
2
3
Linear 层的权重定义中,是按照 (out_features, in_features) 顺序来的,实际计算会先将 weight 转置在乘以输入矩阵。所以 FC层 对应的 Linear 权重维度也是 (v,d),可以直接共享。
11. BERT非线性的来源在哪里?
前馈层的gelu激活函数和self-attention
12. Transformer的点积模型做缩放的原因是什么?
针对为什么维度会影响点积的大小,在论文的脚注中其实给出了一点解释:
假设向量q qq和k kk的各个分量是互相独立的随机变量,均值是0,方差是1,那么点积q ⋅ k q·kq⋅k的均值是0,方差是d k d_kd
k
。
那么一个自然的做法就是把方差稳定到1,做法是将点积除以d k \sqrt{d_k}
d
k
。
更详细的推导:transformer中的attention为什么scaled?
13. BERT的三个Embedding直接相加会对语义有影响吗?
苏剑林给出的回答:
Embedding的数学本质,就是以one hot为输入的单层全连接。
也就是说,世界上本没什么Embedding,有的只是one hot。
1
2
用一个例子再尝试解释一下:
假设 token Embedding 矩阵维度是 [ 4 , 768 ] [4,768][4,768];position Embedding 矩阵维度是 [ 3 , 768 ] [3,768][3,768];segment Embedding 矩阵维度是 [ 2 , 768 ] [2,768][2,768]。
对于一个字,假设它的 token one-hot 是 [ 1 , 0 , 0 , 0 ] [1,0,0,0][1,0,0,0];它的 position one-hot 是 [ 1 , 0 , 0 ] [1,0,0][1,0,0];它的 segment one-hot 是 [ 1 , 0 ] [1,0][1,0]。
那这个字最后的 word Embedding,就是上面三种 Embedding 的加和。
如此得到的 word Embedding,和concat后的特征:[ 1 , 0 , 0 , 0 , 1 , 0 , 0 , 1 , 0 ] [1,0,0,0,1,0,0,1,0][1,0,0,0,1,0,0,1,0],再过维度为 [ 4 + 3 + 2 , 768 ] [4+3+2,768][4+3+2,768] = [ 9 , 768 ] [9, 768][9,768] 的全连接层,得到的向量其实就是一样的。
再换一个角度理解:
直接将三个one-hot 特征 concat 起来得到的 [ 1 , 0 , 0 , 0 , 1 , 0 , 0 , 1 , 0 ] [1,0,0,0,1,0,0,1,0][1,0,0,0,1,0,0,1,0] 不再是one-hot了,但可以把它映射到三个one-hot 组成的特征空间,空间维度是 4 × 3 × 2 = 24 4\times3\times2=244×3×2=24,那在新的特征空间,这个字的one-hot就是 [ 1 , 0 , 0 , 0 , 0... ] [1,0,0,0,0...][1,0,0,0,0...] (23个0)。
此时,Embedding 矩阵维度就是 [ 24 , 768 ] [24,768][24,768],最后得到的 word Embedding 依然是和上面的等效,但是三个小Embedding 矩阵的大小会远小于新特征空间对应的Embedding 矩阵大小。
当然,在相同初始化方法前提下,两种方式得到的 word Embedding 可能方差会有差别,但是,BERT还有Layer Norm,会把 Embedding 结果统一到相同的分布。
BERT的三个Embedding相加,本质可以看作一个特征的融合,强大如 BERT 应该可以学到融合后特征的语义信息的。
参考:为什么 Bert 的三个 Embedding 可以进行相加?
14. BERT训练时使用的学习率 warm-up 策略是怎样的?为什么要这么做?
看到回答都是通过理论和公式推导,来辩证warmup有助于收敛,那么这里来补充一点。
之前遇到一个这样的问题:
在做一个数据量很大的训练(千万至亿级别),在一个epoch内,看到训练误差先减小后上升,然后通过观察测试集变化,排除了模型和数据出错的可能。
最后把问题目标锁定在学习率过大,导致模型提前过拟合,结果对于新的训练数据loss变大。
那么有人可能会问,这跟warmup有什么关系?
先看warmup的实现:
if warmup:
warmup_steps = int(batches_per_epoch * 5)
warmup_lr = (initial_learning_rate * tf.cast(global_step, tf.float32) / tf.cast(warmup_steps, tf.float32))
return tf.cond(global_step < warmup_steps, lambda: warmup_lr, lambda: lr)
1
2
3
4
(ps:粘贴一段网上代码 tf 不知道哪个版本的代码)
可以看到 warmup_lr 的初始值是跟训练预料的大小成反比的,也就是说训练预料越大,那么warmup_lr 初值越小,随后增长到预设的超参 initial_learning_rate相同的量级,再接下来又通过 decay_rates 逐步下降。
这样做有什么好处?
1)这样可以使得学习率可以适应不同的训练集合size
实验的时候经常需要先使用小的数据集训练验证模型,然后换大的数据集做生成环境模型训练。
2)即使不幸学习率设置得很大,那么也能通过warmup机制看到合适的学习率区间(即训练误差先降后升的关键位置附近),以便后续验证。
15. 深度学习中Attention与全连接层的区别何在?
这是个非常有意思的问题,要回答这个问题,我们必须重新定义一下Attention。
Transformer Paper里重新用QKV定义了Attention。所谓的QKV就是Query,Key,Value。如果我们用这个机制来研究传统的RNN attention,就会发现这个过程其实是这样的:
RNN最后一步的output是Q,这个Q query了每一个中间步骤的K。Q和K共同产生了Attention Score,最后Attention Score乘以V加权求和得到context。
那如果我们不用Attention,单纯用全连接层呢?很简单,全链接层可没有什么Query和Key的概念,只有一个Value,也就是说给每个V加一个权重再加到一起(如果是Self Attention,加权这个过程都免了,因为V就直接是从raw input加权得到的。)
可见Attention和全连接最大的区别就是Query和Key,而这两者也恰好产生了Attention Score这个Attention中最核心的机制。而在Query和Key中,我认为Query又相对更重要,因为Query是一个锚点,Attention Score便是从过计算与这个锚点的距离算出来的。任何Attention based algorithm里都会有Query这个概念,但全连接显然没有。
最后来一个比较形象的比喻吧。如果一个神经网络的任务是从一堆白色小球中找到一个略微发灰的,那么全连接就是在里面随便乱抓然后凭记忆和感觉找,而attention则是左手拿一个白色小球,右手从袋子里一个一个抓出来,两两对比颜色,你左手抓的那个白色小球就是Query。
16. 在BERT应用中,如何解决长文本问题?
参考:【BERT】QA、阅读理解、信息检索
————————————————
版权声明:本文为CSDN博主「尊新必威」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_40633696/article/details/121810403