Transformer是《Attention Is All You Need》提出来的,结构如下所示:
讲解Transformer的文章很多,这里不再重复,可以参考文献1和文献2
本文只想讲一些在看Transformer这篇文章时遇到的一些问题,并写下我对这些问题的理解。
问题一:为什么要除以 d k \sqrt{d_k} dk ?
当
d
k
d_k
dk增大时,意味着q和k之间的点乘操作会增加,只要
q
i
k
i
q_ik_i
qiki稍微比其它值大一点,经过softmax之后绝大部分值就会变得很小,接近于0,使得其梯度很小。
我们假设q和k的分布均服从标准正太分布,即其均值为0,方差为1,当进行点乘操作后:
分布变成均值为0,方差为
d
k
d_k
dk【利用均值和方差的性质即可推得】,当
d
k
d_k
dk很大时,意味着q*k的方差就很大,分布会趋于陡峭(分布的方差大,分布就会集中在绝对值大的区域),就会使得softmax()之后使得值出现两极分化的状态。
做个简单的实验,
d
k
d_k
dk分别取5和100时,随机生成
d
k
d_k
dk个服从标准正太分布的q和k,点乘后经过softmax函数,图像如下所示:
当
d
k
d_k
dk=5时:
当
d
k
d_k
dk=100时:
当我把
d
k
d_k
dk=100,q和k点乘之后除以
d
k
\sqrt{d_k}
dk后,图像变成:
问题二:multi-head attention的作用
Attention是将query和key映射到同一高维空间中去计算相似度,而对应的multi-head attention把query和key分成h个小序列分别映射到高维空间的不同子空间中去计算相似度,这样的好处是在两种方法的参数总量保持不变的情况下,Attention在不同子空间有不同的分布,进行concat后使得Attention层信息多样化,增加了Attention的表达能力。
问题三:positional encoding
因为Transformer是处理序列问题,没有捕获数据位置的能力,所以需要加上额外的位置信息,这里位置信息可以是绝对位置也可以是相对位置,可以是可训练的位置信息也可以是不可训练的位置信息,在文中作者提出使用sin和cos来表示序列的相对位置信息:
其中pos是位置,i是维度。
sin和cos是周期性的函数,且对任意的x值,函数值都是唯一确定的,当位置 p o s pos pos偏移了k个单位(记为 p o s + k pos+k pos+k), P E p o s + k PE_{pos+k} PEpos+k可以用 P E p o s PE_{pos} PEpos的线性倍数表示。
问题四:Layer Normal和batch Normal
假定输入为[batch_size,channels,H,W]
batch Normal如第一张图所示,是针对小批次(batch_size)中所有样本每一个通道分别做计算均值和方差,然后再进行归一化。batch Normal和batch_size有关系,当batch_size较小时,对每个小批次进行归一化不足以代表整个数据的分布情况,而且计算的均值方差等信息需要额外的存储空间,对于有着固定的深度的DNN和CNN来说比较适合。
Layer Normal如第二张图所示,是对每一个样本计算均值和方差做归一化,不依赖于batch_size的大小和输入sequence的深度,比较适合RNN。【参考】
2023/5/18 再一次学习Transformer时一些代码记录
这部分代码和文字基本参考 https://b23.tv/Vw6IYII
掩码张量
掩码张量一般由0和1组成,代表当前位置被遮掩/不被遮掩。在Transformer中掩码张量主要用在attention上,有些生成的attention张量中的计算有可能是已知了未来的信息而得到的,未来的信息被看到是因为训练时把整个输出结果一次性进行embedding,但是一般decoder不是一次就能产生最终结果,而是需要一次次通过上一次结果综合得到。故未来的信息可能会被提前使用。所以需要掩码张量将未来信息进行遮掩。
# 构建掩码张量函数
def subsequent_mask(size):
atten_shape=(1,size,size)
subsequent_mask=np.triu(np.ones(atten_shape),k=1).astype('uint8')
return torch.from_numpy(1-subsequent_mask)
plt.figure(figsize=(5,5))
plt.imshow(subsequent_mask(20)[0])
plt.show()
上图为掩码张量的一个图例,黄色表示遮挡部分,紫色为可见部分,横坐标为目标词汇的位置,纵坐标为词汇可查看的位置。例如x=5时,y=4,表示在第五个词汇位置上,可以查看的只有前4个词汇。
注意力代码
# 注意力机制
def attention(query,key,value,mask=None,dropout=None):
'''
args:
query
key
value
mask:是否使用掩码
dropout:dropout函数
'''
d_k=query.size(-1) # 词向量维度,一般设为512
scores=torch.matmul(query,key.transpose(-2,-1))/math.sqrt(d_k)
if mask is not None:
# mask维度和scores一样,用于遮掩满足一定条件的scores位置的值,用于掩码注意力机制中
# mask一般为上三角矩阵,这里的意思是将mask中为0的位置对应的scores位置的值用1e-9进行填充
scores=scores.masked_fill(mask==0,1e-9)
p_attn=F.softmax(scores)
if dropout is not None:
p_attn=dropout(p_attn)
return torch.matmul(p_attn,value),p_attn
Q、K和V的形象解释:
假设给出一段文本,需要使用一些关键词对它进行描述:
key表示提前给的提示信息,value是第一次看到后脑中猜测的信息,qurey表示给出的这段文本。
一般key和value值相同,这种为一般的注意力机制。当key=value=query时,成为自注意力机制。
多头注意力机制
def clone(module,nums):
return nn.ModuleList([module for _ in range(nums)])
class MultiHeadedAttention(nn.Module):
def __init__(self,head,embedding_dim,dropout=0.1):
super(MultiHeadedAttention,self).__init__()
'''
head:多头数目
embedding_dim:词嵌入维度
'''
assert embedding_dim%head == 0 #可以整除
self.d_k=embedding_dim/head # 每个头分配的词嵌入维度
self.head=head
self.linears=clone(nn.Linear(embedding_dim,embedding_dim),4)
self.attn=None
self.dropout=nn.Dropout(p=dropout)
def forward(self,query,key,value,mask=None):
if mask is not None:
mask=mask.unsqueeze(1) # 使用unsqueeze扩展维度,表示多头中的第头
bach_size=query.size(0) #表示有多少样本
# 多头注意力机制
query,key,value=[model(x).view(bach_size,-1,self.head,self.d_k).transpose(1,2)
for model,x in zip(self.linears,(query,key,value))]
# 传入attention函数
x,self.attn=attention(query,key,value,mask,self.dropout)
x=x.transpose(1,2).contiguous().view(bach_size,-1,self.head*self.d_k)
return self.linears[-1](x)
以上就是我在看Transformer论文时遇到的问题和自己的一些想法,如果有写的不正确或者表达不清楚的请各位多指教。