学习笔记八:transformer面试点

零、基础知识

0.1 线性变换

  • 变换:从数值意义上,变换即函数。一个变换,其实就是一个函数f(x),输入为x,在通过这个函数之后就变成了y对,那么这个从输入到输出的转变过程就是所谓的变换。
  • 线性变换:线性变换就是一阶导数为常数的函数,譬如y=kx。当k为常数时,易得满足同质性f(ka)=kf(a),当k为一个矩阵时,易得满足可加性f(a+b)=f(a)+f(b)。同质性和可加性又称为线性条件,满足该条件则为线性变换(在平面上画出来是一条直线),反之则为非线性变换。

0.2 点积、内积、外积、余弦相似度、投影(有空补)

参考帖子《带你一次搞懂点积(内积)、叉积(外积)》

  • 设两个向量 → a = ( x 1 , y 1 , z 1 ) \underset{a}{\rightarrow}=(x_{1},y_{1},z_{1}) a=(x1,y1,z1) → b = ( x 2 , y 2 , z 2 ) \underset{b}{\rightarrow}=(x_{2},y_{2},z_{2}) b=(x2,y2,z2)
  1. 内积(点积、数量积):numpy中 使用np.dot,对应元素相乘相加,结果是一个标量。几何意义是 → a \underset{a}{\rightarrow} a → b \underset{b}{\rightarrow} b上的投影:
    A ⊗ B = → a ⋅ → b = ∣ → a ∣ ∗ ∣ → b ∣ ∗ c o s θ = x 1 x 2 + y 1 y 2 + z 1 z 2 \mathbf{A\otimes B=\underset{a}{\rightarrow}\cdot \underset{b}{\rightarrow}=\left | \underset{a}{\rightarrow} \right |*\left | \underset{b}{\rightarrow} \right |*cos\theta} =x_{1}x_{2}+y_{1}y_{2}+z_{1}z_{2} AB=ab=abcosθ=x1x2+y1y2+z1z2
    在这里插入图片描述
  2. 外积(向量积、叉积Cross product):numpy中 使用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}} ABab=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])

一、transformer

《深度学习之注意力机制(Attention Mechanism)和Seq2Seq》

1.1 为啥FFNN第一层将向量扩维到4倍

个人理解,类似于“特征组合器”,增大神经元个数,增强Transformer对于distributed的文本特征的组合能力,从而获取更多、更复杂的语义信息。

1.2 注意力机制是为了解决什么问题而提出来的?

注意力机制(Attention Mechanism)是在计算能力有限的情况下,将计算资源分配给更重要的任务,同时解决信息超载问题的一种资源分配方案
在神经网络学习中,一般而言模型的参数越多则模型的表达能力越强,模型所存储的信息量也越大,但这会带来信息过载的问题。那么通过引入注意力机制,在众多的输入信息中聚焦于对当前任务更为关键的信息,降低对其他信息的关注度,甚至过滤掉无关信息,就可以解决信息过载问题,并提高任务处理的效率和准确性。

这就类似于人类的视觉注意力机制,通过扫描全局图像,获取需要重点关注的目标区域,而后对这一区域投入更多的注意力资源,获取更多与目标有关的细节信息,而忽视其他无关信息。通过这种机制可以利用有限的注意力资源从大量信息中快速筛选出高价值的信息。

1.3 为什么输入X要经过权重矩阵变换得到QKV向量?为啥不直接用X运算?

1.3不考虑多头的原因,self-attention中词向量不乘QKV参数矩阵,会有什么问题?

这两个问题是一个答案:

  1. 如果直接用输入X进行计算,则X同时承担了三种角色:査询( Query )键( Key )和值( Value ),导致其不容易学习。
    更好的做法是,对不同的角色使用不同的向量。即使用不同的参数矩阵对原始的输人向量做线性变换,从而让不同的变换结果承担不同的角色。

  2. 如果Q=K=V,点积后softmax后的加权平均中,该词本身所占的比重将会是最大的,使得其他词的比重很少,无法有效利用上下文信息来增强当前词的语义表示。

1.4 transformer中为啥要有那么多dropout?

dropout官方文档
dropout是随机对最后一维的元素,以一定比例替换为0,是一种正则手段,为了防止过拟合。

1.5 NLP 中的Mask全解

参考:《NLP 中的Mask全解》
《XLNet 中神奇的 Attention Mask》
《Transformer相关——(7)Mask机制》

1.6 Self-Attention 的时间复杂度是怎么计算的?

  1. Self-Attention时间复杂度: O ( n 2 , d ) O(n^2,d) O(n2,d) ,这里,n是序列的长度,d是embedding的维度。
    在这里插入图片描述
  2. 再分析一下Multi-Head Attention,它的作用类似于CNN中的多核。多头的实现不是循环的计算每个头,而是通过 transposes and reshapes,用矩阵乘法来完成的。

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(n2ma)=O(n2d)

1.7 Transformer在哪里做了权重共享,为什么可以做权重共享?

1.8 为什么FFNN有两层,先升维再降维?

  Self-Attention模型的作用是提取语义级别的信息(不存在长距离依赖),而FFNN是在各个时序上对特征进行非线性变换,提高网络表达能力。
  FFNN有两层,是将attention层输出先扩维4倍再降维。为什么这么做?神经网络中线性连接可以写成 d l = W l ⋅ x d^l=W^{l}\cdot x dl=Wlx。其中三者维度分别是m×1、m×n、n×1。

  • m>n:升维,将特征进行各种类型的特征组合,提高模型分辨能力
  • m<n:降维,去除区分度低的组合特征
    所以一般神经网络都是先做宽再做窄。
    Transformer在两个地方进行了权重共享:
  1. 词表共享:Encoder和Decoder间的Embedding层权重共享;

  2. 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),可以直接共享。

二、BERT

2.1 BERT的三个Embedding直接相加会对语义有影响吗?

原帖子在这

这是一个非常有意思的问题,苏剑林老师也给出了回答,真的很妙啊:

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 应该可以学到融合后特征的语义信息的。

2.2 为何选[CLS]做整个句子的表征?

  因为与文本中已有的其它词相比,这个无明显语义信息的符号会更“公平”地融合文本中各个词的语义信息,从而更好的表示整句话的语义。

  具体来说,self-attention是用文本中的其它词来增强目标词的语义表示,但是目标词本身的语义还是会占主要部分的,因此,经过BERT的12层,每次词的embedding融合了所有词的信息,可以去更好的表示自己的语义。

  而[CLS]位本身没有语义,经过12层,得到的是attention后所有词的加权平均,相比其他正常词,可以更好的表征句子语义。

当然,也可以通过对最后一层所有词的embedding做pooling去表征句子语义。

  • get_pooled_out(),就是上述[CLS]的表示,输出shape是[batch size,hidden size]。
  • get_sequence_out(),获取的是整个句子每一个token的向量表示,输出shape是[batch_size, seq_length, hidden_size],这里也包括[CLS],因此在做token级别的任务时要注意

2.3 3、使用BERT预训练模型为什么最多只能输入512个词,最多只能两个句子合成一句?

在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值去满足自己的需求。

2.3 Bert 如何解决长文本问题?

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
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

三、GPT

四、模型实现

4.1 weight decay 是什么?

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=wiwiObj=wiwiLossλwi=(1λ)wiwiLoss
在 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) (wiLoss+λwi)

4.2 模型初始化:

  • 深度学习模型本身上就是一个个全连接层的嵌套,所以为了使模型最后的输出不至于在初始化阶段就过于“膨胀”或者“退化”,一个想法就是让模型在初始化时能保持模长不变。

  • 正交矩阵是指满足 W ⊤ W = I W^⊤W=I WW=I的矩阵,也就是说它的逆等于转置。正交矩阵的重要意义在于它在变换过程中保持了向量的模长不变

  • 推论:当输出维度m≥输入维度n时,从任意的均值为0、方差为1/m的分布p(x)中独立重复采样出来的m×n矩阵,近似满足 W ⊤ W = I W^⊤W=I WW=I(只需要把采样分布的方差改为1/m就好)。
    在这里插入图片描述
    考虑激活函数有:

  • tanh(x) 在x比较小的时候有tanh(x)≈x,所以可以认为 Xavier初始化直接适用于tanh激活;

  • relu时可以认为relu(y)会有 大约一半的元素被置零,所以模长大约变为原来的 1 2 \frac{1}{\sqrt{2}} 2 1,而要保持模长不变,可以让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参数化能让我们更平等地处理每一个参数,并且比较形象地了解到训练的更新幅度,以便我们更好地调整参数

  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
Transformer模型的训练与评估通常涉及以下几个步骤: 1. 数据预处理:首先,需要准备用于训练和评估的数据集。对于Transformer模型,常用的数据集包括机器翻译、文本摘要和对话系统等。数据预处理的任务包括分词、构建词汇表、将文本转换为向量表示等。 2. 构建模型:Transformer模型由编码器(Encoder)和解码器(Decoder)组成。编码器将输入序列转换为一系列隐藏状态,解码器通过利用这些隐藏状态生成输出序列。在构建模型时,需要定义模型的架构、层数、注意力机制等。 3. 定义损失函数:通常使用交叉熵损失函数来度量模型生成的输出与真实标签之间的差异。对于机器翻译任务,可以使用序列到序列(Seq2Seq)模型来训练Transformer。 4. 训练模型:使用训练数据集对模型进行训练。训练过程中,需要定义优化器(如Adam)和学习率调度器。通过反向传播和参数更新,模型逐渐学习到输入序列和目标序列之间的映射。 5. 评估模型:使用测试数据集对训练好的模型进行评估。可以使用BLEU等指标来评估机器翻译任务的性能。此外,还可以通过可视化注意力权重来分析模型的行为。 需要注意的是,Transformer模型的训练过程相对复杂,需要一定的计算资源和时间。为了提高训练效果,还可以采用一些技巧,如数据增强、正则化和模型集成等。在实际应用中,还可以使用预训练的Transformer模型,并在特定任务上进行微调。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神洛华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值