x# Transformer


transformer是一个拥有self-attention的Sequence2Sequence模型,

Sequence

transformer中有sigmoid_矩阵乘法

RNN是常用的Sequence模型,在双向的sequence模型中,b1 ,b2 的输入为a1到a4 ,适用于解决序列化的问题常用语音和NLP领域。但是序列化执行难以并行处理。希望采用CNN来替代RNN,实现并行化处理,然而CNN只有高层的filter的感受野比较大,能考虑长的序列。

self-attention

transformer中有sigmoid_pytorch_02

用self-attention来代替RNN,既能考虑长的序列,有能便于并行化。

如何做self-attention?

transformer中有sigmoid_矩阵乘法_03

  1. 输入的变量为xi ,首先对xi做编码 得到编码后的向量 ai
class Embeddings(nn.Module):
    def __init__(self, d_model, vocab):
        super(Embeddings, self).__init__()
        self.lut = nn.Embedding(vocab, d_model)

    def forward(self, x):
        return self.lut(x)

nn.Embedding是一个l固定字典lookup的表格,初始化的输入为单词数vocab和编码向量维度d_model。

  1. 对向量ai 分别得到 用于attention的三元组(q,k,v)
  2. 拿每个query去对每个key做attention,用点乘计算:transformer中有sigmoid_神经网络_04 <img
  3. transformer中有sigmoid_矩阵乘法_05

  4. attention之后用softmax归一化:transformer中有sigmoid_神经网络_06

transformer中有sigmoid_矩阵乘法_07

之后通过加权平均得到最后的输出:transformer中有sigmoid_并行化_08

transformer中有sigmoid_机器学习_09

def attention(query, key, value, mask=None, dropout=None):
	d_k = query.size(-1)
	scores = torch.matmul(query, key.transpose(-2, -1)) \
		/ math.sqrt(d_k)
	if mask is not None:
		scores = scores.masked_fill(mask == 0, -1e9)
	p_attn = F.softmax(scores, dim = -1)
	if dropout is not None:
		p_attn = dropout(p_attn)
	return torch.matmul(p_attn, value), p_attn

在实际实验中query,key,value维度为(batch, head个数,序列长度,特征数量)。通过矩阵乘法后scores的维度是(batch,head个数,序列长度,序列长度)。matmul会把query和key的最后两维进行矩阵乘法,这样效率更高。

为什么易于并行?

transformer中有sigmoid_矩阵乘法_10

可以将每个时刻的输入做concat操作,这样直接通过一个矩阵乘法能够得到所有的值

transformer中有sigmoid_pytorch_11

transformer中有sigmoid_矩阵乘法_12


transformer中有sigmoid_矩阵乘法_13


transformer中有sigmoid_机器学习_14

所有的计算都是矩阵乘法,所以易于并行化。

Multi-head self-attention

每个head关注的东西可能不同

transformer中有sigmoid_神经网络_15

class MultiHeadedAttention(nn.Module):
	def __init__(self, h, d_model, dropout=0.1):
		super(MultiHeadedAttention, self).__init__()
		assert d_model % h == 0
		# We assume d_v always equals d_k
		self.d_k = d_model // h
		self.h = h
		self.linears = clones(nn.Linear(d_model, d_model), 4)
		self.attn = None
		self.dropout = nn.Dropout(p=dropout)
	
	def forward(self, query, key, value, mask=None): 
		if mask is not None:
			# 所有h个head的mask都是相同的 
			mask = mask.unsqueeze(1)
		nbatches = query.size(0)
		
		# 1) 首先使用线性变换,然后把d_model分配给h个Head,每个head为d_k=d_model/h 
		query, key, value = \
			[l(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)	
				for l, x in zip(self.linears, (query, key, value))]
		
		# 2) 使用attention函数计算
		x, self.attn = attention(query, key, value, mask=mask, 
			dropout=self.dropout)
		
		# 3) 把8个head的64维向量拼接成一个512的向量。然后再使用一个线性变换(512,521),shape不变。 
		x = x.transpose(1, 2).contiguous() \
			.view(nbatches, -1, self.h * self.d_k)
		return self.linears[-1](x)

先看构造函数,这里d_model(512)是Multi-Head的输出大小,因为有h(8)个head,因此每个head的d_k=512/8=64。接着我们构造4个(d_model x d_model)的矩阵,后面我们会看到它的用处。最后是构造一个Dropout层。

然后我们来看forward方法。输入的mask是(batch, 1, time)的,因为每个head的mask都是一样的,所以先用unsqueeze(1)变成(batch, 1, 1, time),mask我们前面已经详细分析过了。

接下来是根据输入query,key和value计算变换后的Multi-Head的query,key和value。这是通过下面的语句来实现的:

query, key, value = \
		[l(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)	
			for l, x in zip(self.linears, (query, key, value))]

zip(self.linears, (query, key, value))是把(self.linears[0],self.linears[1],self.linears[2])和(query, key, value)放到一起然后遍历。我们只看一个self.linears[0] (query)。根据构造函数的定义,self.linears[0]是一个(512, 512)的矩阵,而query是(batch, time, 512),相乘之后得到的新query还是512(d_model)维的向量,然后用view把它变成(batch, time, 8, 64)。然后transponse成(batch, 8,time,64),这是attention函数要求的shape。分别对应8个Head,每个Head的Query都是64维。

Key和Value的运算完全相同,因此我们也分别得到8个Head的64维的Key和64维的Value。接下来调用attention函数,得到x和self.attn。其中x的shape是(batch, 8, time, 64),而attn是(batch, 8, time, time)。

x.transpose(1, 2)把x变成(batch, time, 8, 64),然后把它view成(batch, time, 512),其实就是把最后8个64维的向量拼接成512的向量。最后使用self.linears[-1]对x进行线性变换,self.linears[-1]是(512, 512)的,因此最终的输出还是(batch, time, 512)。我们最初构造了4个(512, 512)的矩阵,前3个用于对query,key和value进行变换,而最后一个对8个head拼接后的向量再做一次变换。
Positional Encoding

transformer中有sigmoid_矩阵乘法_16


transformer中有sigmoid_pytorch_17