参考帖子《带你一次搞懂点积(内积)、叉积(外积)》、
np.dot
,对应元素相乘相加,结果是一个标量。几何意义是 → a \underset{a}{\rightarrow} a→在 → b \underset{b}{\rightarrow} b→上的投影:np.cross
,是 → a \underset{a}{\rightarrow} a→和 → b \underset{b}{\rightarrow} b→的法向量,该向量垂直于 → a \underset{a}{\rightarrow} a→和 → b \underset{b}{\rightarrow} b→构成的平面。
3. 普通乘积:numpy中 使用np.multiply
或*
。对应元素相乘,结果还是向量。
A ⊙ B → a ∗ → b = x 1 x 2 , y 1 y 2 , z 1 z 2 A\odot B\underset{a}{\rightarrow}*\underset{b}{\rightarrow}={x_{1}x_{2},y_{1}y_{2},z_{1}z_{2}} A⊙Ba→∗b→=x1x2,y1y2,z1z2
>>> arr1 = np.array([1,2,3])
>>> arr2 = np.array([2,3,4])
# 外积
>>> outerx = np.outer(arr1,arr2)
>>> outerx
array([[ 2, 3, 4],
[ 4, 6, 8],
[ 6, 9, 12]])
# 内积
>>> dotx = np.dot(arr1,arr2)
>>> dotx
20
# 张量积
>>> kronx = np.kron(arr1,arr2)
>>> kronx
array([ 2, 3, 4, 4, 6, 8, 6, 9, 12])
# 对应元素乘积
>>> mul = a * b
>>> mul
array([1, 4, 9])
《深度学习之注意力机制(Attention Mechanism)和Seq2Seq》
个人理解,类似于“特征组合器”,增大神经元个数,增强Transformer对于distributed的文本特征的组合能力,从而获取更多、更复杂的语义信息。
注意力机制(Attention Mechanism)是在计算能力有限的情况下,将计算资源分配给更重要的任务,同时解决信息超载问题的一种资源分配方案
在神经网络学习中,一般而言模型的参数越多则模型的表达能力越强,模型所存储的信息量也越大,但这会带来信息过载的问题。那么通过引入注意力机制,在众多的输入信息中聚焦于对当前任务更为关键的信息,降低对其他信息的关注度,甚至过滤掉无关信息,就可以解决信息过载问题,并提高任务处理的效率和准确性。
这就类似于人类的视觉注意力机制,通过扫描全局图像,获取需要重点关注的目标区域,而后对这一区域投入更多的注意力资源,获取更多与目标有关的细节信息,而忽视其他无关信息。通过这种机制可以利用有限的注意力资源从大量信息中快速筛选出高价值的信息。
这两个问题是一个答案:
如果直接用输入X进行计算,则X同时承担了三种角色:査询( Query )键( Key )和值( Value ),导致其不容易学习。
更好的做法是,对不同的角色使用不同的向量。即使用不同的参数矩阵对原始的输人向量做线性变换,从而让不同的变换结果承担不同的角色。
如果Q=K=V,点积后softmax后的加权平均中,该词本身所占的比重将会是最大的,使得其他词的比重很少,无法有效利用上下文信息来增强当前词的语义表示。
dropout官方文档
dropout是随机对最后一维的元素,以一定比例替换为0,是一种正则手段,为了防止过拟合。
参考:《NLP 中的Mask全解》
《XLNet 中神奇的 Attention Mask》
《Transformer相关——(7)Mask机制》
Transformer/BERT中把 d ,也就是hidden_size/embedding_size这个维度做了reshape拆分,pytorch源码:hidden_size (d) = num_attention_heads (m) * attention_head_size (a),也即 d=m*a
。
并将 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\cdot m\cdot a)=O(n^2\cdot d) O(n2⋅m⋅a)=O(n2⋅d)
Self-Attention模型的作用是提取语义级别的信息(不存在长距离依赖),而FFNN是在各个时序上对特征进行非线性变换,提高网络表达能力。
FFNN有两层,是将attention层输出先扩维4倍再降维。为什么这么做?神经网络中线性连接可以写成 d l = W l ⋅ x d^l=W^{l}\cdot x dl=Wl⋅x。其中三者维度分别是m×1、m×n、n×1。
词表共享:Encoder和Decoder间的Embedding层权重共享;
Decoder中Embedding层和FC层权重共享。
对于1,《Attention is all you need》中Transformer做在机器翻译时,源语言和目标语言是不一样的,但它们可以共用一张大词表,对于两种语言中共同出现的词(比如:数字,标点等或者一些相同的subword等)可以得到更好的表示。而且对于Encoder和Decoder,嵌入时都只有对应语言的embedding会被激活,因此是可以共用一张词表做权重共享的。
Transformer词表用了bpe来处理,所以最小的单元是subword。英语和德语同属日耳曼语族,有很多相同的subword,可以共享类似的语义。而像中英这样相差较大的语系,语义共享作用可能不会很大。
但是,共用词表会使得词表数量增大,增加softmax的计算时间,因此实际使用中是否共享可能要根据情况权衡。
对于2,Embedding层可以说是通过onehot去取到对应的embedding向量,FC层可以说是相反的,通过embedding(定义为 x)去得到它可能是某个词的softmax概率,取概率最大(贪婪情况下)的作为预测值。(类似于embedding求最大的一个onehot)
在FC层的每一行量级相同的前提下,理论上和 x 相同的那一行对应的点积和softmax概率会是最大的(可类比本文问题1)。
因此,Embedding层和FC层权重共享,Embedding层中和向量 x 最接近的那一行对应的词,会获得更大的预测概率。实际上,Decoder中的Embedding层和FC层有点像互为逆过程。
通过这样的权重共享可以减少参数的数量,加快收敛。
Embedding层参数维度是:(v,d),FC层参数维度是:(d,v)。其中v是词表大小,d是embedding维度。
fc = nn.Linear(d, v, bias=False) # Decoder FC层定义
weight = Parameter(torch.Tensor(out_features, in_features)) # Linear层权重定义
Linear 层的权重定义中,是按照 (out_features, in_features) 顺序来的,实际计算会先将 weight 转置在乘以输入矩阵。所以 FC层 对应的 Linear 权重维度也是 (v,d),可以直接共享。
原帖子在这
这是一个非常有意思的问题,苏剑林老师也给出了回答,真的很妙啊:
Embedding的数学本质,就是以one hot为输入的单层全连接,也就是说,世界上本没什么Embedding,有的只是one hot。我们将token,position,segment三者都用one hot表示,然后concat起来,然后才去过一个单层全连接,等价的效果就是三个Embedding相加。原文链接:词向量与Embedding究竟是怎么回事?
在这里想用一个例子再尝试解释一下:
假设 token Embedding 矩阵维度是 [4,768];position Embedding 矩阵维度是 [3,768];segment Embedding 矩阵维度是 [2,768]。假设它的 token one-hot 是[1,0,0,0];它的 position one-hot 是[1,0,0];它的 segment one-hot 是[1,0]。
那这个字最后的 word Embedding,就是上面三种 Embedding 的加和。如此得到的 word Embedding,和concat后的特征:[1,0,0,0,1,0,0,1,0],再过维度为 [4+3+2,768] = [9, 768] 的全连接层,得到的向量其实就是一样的。
再换一个角度理解:
直接将三个one-hot 特征 concat 起来得到的 [1,0,0,0,1,0,0,1,0] 不再是one-hot了,但可以把它映射到三个one-hot 组成的特征空间,空间维度是 432=24 ,那在新的特征空间,这个字的one-hot就是[1,0,0,0,0…] (23个0)。
此时,Embedding 矩阵维度就是 [24,768],最后得到的 word Embedding 依然是和上面的等效,但是三个小Embedding 矩阵的大小会远小于新特征空间对应的Embedding 矩阵大小。
当然,在相同初始化方法前提下,两种方式得到的 word Embedding 可能方差会有差别,但是,BERT还有Layer Norm,会把 Embedding 结果统一到相同的分布。
BERT的三个Embedding相加,本质可以看作一个特征的融合,强大如 BERT 应该可以学到融合后特征的语义信息的。
因为与文本中已有的其它词相比,这个无明显语义信息的符号会更“公平”地融合文本中各个词的语义信息,从而更好的表示整句话的语义。
具体来说,self-attention是用文本中的其它词来增强目标词的语义表示,但是目标词本身的语义还是会占主要部分的,因此,经过BERT的12层,每次词的embedding融合了所有词的信息,可以去更好的表示自己的语义。
而[CLS]位本身没有语义,经过12层,得到的是attention后所有词的加权平均,相比其他正常词,可以更好的表征句子语义。
当然,也可以通过对最后一层所有词的embedding做pooling去表征句子语义。
在BERT中,Token,Position,Segment Embeddings 都是通过学习来得到的,pytorch代码中它们是这样的
self.word_embeddings = Embedding(config.vocab_size, config.hidden_size)
self.position_embeddings = Embedding(config.max_position_embeddings, config.hidden_size)
self.token_type_embeddings = Embedding(config.type_vocab_size, config.hidden_size)
而在BERT config中
"max_position_embeddings": 512
"type_vocab_size": 2
输入最多512个词(还要除掉[CLS]和[SEP]),最多两个句子合成一句。这之外的词和句子会没有对应的embedding。也可以更改 BERT config,设置更大max_position_embeddings 和 type_vocab_size值去满足自己的需求。
Amazon2019年EMNLP的这篇文章:Multi-passage BERT。主要思路是global norm + passage rank + sliding window
。不想切passages就上XLNet。
1.Sliding Window(划窗)
:主要见于诸阅读理解任务(如Stanford的SQuAD)。Sliding Window
即把文档分成有重叠的若干段,然后每一段都当作独立的文档送入BERT进行处理。最后再对于这些独立文档得到的结果进行整合。Sliding Window可以只用在Training中。因为Test之时不需要Back Propagation,亦不需要large batchsize,因而总有手段将长文本塞进显存中(如torch.nograd, batchsize=1)。
作者:猪猪侠和狗子
链接:https://www.zhihu.com/question/327450789/answer/1024153978
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
weight decay:权值衰减,即L2正则。当有L2正则时:
O b j = L o s s + λ 2 ∑ w i 2 \mathbf{Obj=Loss+\frac{\lambda }{2}\sum w_{i^{2}}} Obj=Loss+2λ∑wi2
w i + 1 = w i − ∂ O b j ∂ w i = w i − ∂ L o s s ∂ w i − λ w i = ( 1 − λ ) w i − ∂ L o s s ∂ w i \mathbf{w_{i+1}=w_{i}-\frac{\partial Obj}{\partial w_{i}}=w_{i}-\frac{\partial Loss}{\partial w_{i}}-\lambda w_{i}=(1-\lambda )w_{i}-\frac{\partial Loss}{\partial w_{i}}} wi+1=wi−∂wi∂Obj=wi−∂wi∂Loss−λwi=(1−λ)wi−∂wi∂Loss
在 PyTorch 中,L2 正则项是在优化器中实现的,在构造优化器时可以传入 weight decay 参数,对应的是公式中的 λ \lambda λ。
net_normal = MLP(neural_num=n_hidden)
net_weight_decay = MLP(neural_num=n_hidden)
optim_normal = torch.optim.SGD(net_normal.parameters(), lr=lr_init, momentum=0.9)
optim_wdecay = torch.optim.SGD(net_weight_decay.parameters(), lr=lr_init, momentum=0.9, weight_decay=1e-2)
使用了 weight decay 的模型虽然在训练集的 loss 更高,但是更加平滑,泛化能力更强。
例子来于《[PyTorch 学习笔记] 6.1 weight decay 和 dropout》
加上了 weight decay 后,随便训练次数的增加,权值的分布逐渐靠近 0 均值附近,这就是 L2 正则化的作用,约束权值尽量靠近 0。
for group in self.param_groups:
weight_decay = group['weight_decay']
momentum = group['momentum']
dampening = group['dampening']
nesterov = group['nesterov']
for p in group['params']:
if p.grad is None:
continue
d_p = p.grad.data
if weight_decay != 0:
d_p.add_(weight_decay, p.data)#函数后面加下划线是原地操作,改变被调用的张量的值
...
...
...
p.data.add_(-group['lr'], d_p)#根据梯度更新权重
dp 是计算得到的梯度,如果 weight decay 不为 0,那么更新:d_p=d_p+weight_decay×p.data。对应式子: ( ∂ L o s s ∂ w i + λ ∗ w i ) \left(\frac{\partial L o s s}{\partial w{i}}+\lambda * w_{i}\right) (∂wi∂Loss+λ∗wi)
深度学习模型本身上就是一个个全连接层的嵌套,所以为了使模型最后的输出不至于在初始化阶段就过于“膨胀”或者“退化”,一个想法就是让模型在初始化时能保持模长不变。
正交矩阵是指满足 W ⊤ W = I W^⊤W=I W⊤W=I的矩阵,也就是说它的逆等于转置。正交矩阵的重要意义在于它在变换过程中保持了向量的模长不变
推论:当输出维度m≥输入维度n时,从任意的均值为0、方差为1/m的分布p(x)中独立重复采样出来的m×n矩阵,近似满足 W ⊤ W = I W^⊤W=I W⊤W=I(只需要把采样分布的方差改为1/m就好)。
考虑激活函数有:
tanh(x) 在x比较小的时候有tanh(x)≈x,所以可以认为 Xavier初始化直接适用于tanh激活;
relu时可以认为relu(y)会有 大约一半的元素被置零,所以模长大约变为原来的 1 2 \frac{1}{\sqrt{2}} 21,而要保持模长不变,可以让W乘上 2 \sqrt{2} 2,也就是说初始化方差从1/m变成2/m
sigmoid函数:W服从 U [ − 96 n i + n i + 1 , 96 n i + n i + 1 ] U[-\sqrt{\frac{96}{n_{i}+n_{i+1}}},\sqrt{\frac{96}{n_{i}+n_{i+1}}}] U[−ni+ni+196,ni+ni+196]
Relu函数:W服从 U [ − 12 n i + n i + 1 , 12 n i + n i + 1 ] U[-\sqrt{\frac{12}{n_{i}+n_{i+1}}},\sqrt{\frac{12}{n_{i}+n_{i+1}}}] U[−ni+ni+112,ni+ni+112]
Xavier初始化是用“均值为0、方差为1/m的随机分布”初始化。
NTK参数化:用“均值为0、方差为1的随机分布”来初始化,但是将输出结果除以 m \sqrt{m} m。高斯过程中被称为“NTK参数化”
NTK参数化能让我们更平等地处理每一个参数,并且比较形象地了解到训练的更新幅度,以便我们更好地调整参数