内容包括:
- attention机制
- 为什么需要attention
- 简介
- Seq2Seq
attention
为什么需要attention
由于之前的输入文本无论长度,最后都变成了一个隐藏状态输入到decoder解码,因为encoder最后的隐藏状态一定程度上包含了所有文本的信息,所以理论上是可以有效果的,但是实际上一旦输入文本很长,那么指望这么一个隐藏状态变量清楚的记录文本的所有信息并且能够解码出正确的输出是不现实的,也就是导致了输出的隐藏状态(contenxt vector)"遗忘"了一部分信息,因此我们想到是否可以给解码器提供更多的信息,也就是编码器每个时间步的信息,attention为解码器提供编码器每个隐藏状态的信息,通过这些信息,模型可以有重点的关注编码器中需要关注的部分
简介
总的来说,是为编码器每个时间步分配一个权重(注意力),利用softmax对编码器隐藏状态加权求和,得到context vector
步骤:
- 获取每个编码器隐藏状态的分数
-分数(标量)是通过评分函数(alignment)获得的,其中评分函数并不是唯一的,假设为点积函数
*decoder_hidden *= [10, 5, 10]##其中一个解码器的隐藏状态
*encoder_hidden score*
---------------------
[0, 1, 1] 15 (= 10×0 + 5×1 + 10×1, the dot product)
[5, 0, 1] 60
[1, 1, 0] 15
[0, 5, 1] 35
以上就获得了4个编码器隐藏状态关于一个解码器隐藏状态的分数,其中[5, 0, 1]的注意力分数为60是最高的,说明接下来翻译出的这个词将受到这个编码器隐藏状态的影响
- 通过softmax层,归一化
*encoder_hidden score score^*
-----------------------------
[0, 1, 1] 15 0
[5, 0, 1] 60 1
[1, 1, 0] 15 0
[0, 5, 1] 35 0
- 通过softmax获得的权重与每个编码器隐藏状态相乘,也就是加权,获得alignment向量
*encoder_hidden score score^ alignment*
----------------------------------------
[0, 1, 1] 15 0 [0, 0, 0]
[5, 0, 1] 60 1 [5, 0, 1]
[1, 1, 0] 15 0 [0, 0, 0]
[0, 5, 1] 35 0 [0, 0, 0]
- 对alignment向量求和
*encoder_hidden score score^ alignment*
----------------------------------------
[0, 1, 1] 15 0 [0, 0, 0]
[5, 0, 1] 60 1 [5, 0, 1]
[1, 1, 0] 15 0 [0, 0, 0]
[0, 5, 1] 35 0 [0, 0, 0]
*context *= [0+5+0+0, 0+0+0+0, 0+1+0+0] = [5, 0, 1]
- 将上下文向量输入解码器
输入方式取决于架构,一般是和解码器的输入一起喂进模型
'''
1. 计算评分函数
2. 求出对应的softmax归一化后的值
3. 用上面得到的值加权value(在这里k和v都是编码器的隐藏状态)
'''
def SequenceMask(X, X_len,value=-1e6):
maxlen = X.size(1)
#print(X.size(),torch.arange((maxlen),dtype=torch.float)[None, :],'\n',X_len[:, None] )
mask = torch.arange((maxlen),dtype=torch.float)[None, :] >= X_len[:, None] ##这是一种reshape操作,将X_reshape为()
#print(mask)
X[mask]=value
return X
def masked_softmax(X, valid_length):
'''
1. valid_length可能是(batch_size_1, ),也可能是(batch_size, x)
2. X(batch_size_1, 1,num_hidden_size)
'''
# X: 3-D tensor, valid_length: 1-D or 2-D tensor
softmax = nn.Softmax(dim=-1)
if valid_length is None:
return softmax(X)
else:
shape = X.shape
if valid_length.dim() == 1:
try:
valid_length = torch.FloatTensor(valid_length.numpy().repeat(shape[1], axis=0))#[2,2,3,3]
except:
valid_length = torch.FloatTensor(valid_length.cpu().numpy().repeat(shape[1], axis=0))#[2,2,3,3]
else:
valid_length = valid_length.reshape((-1,))
# fill masked elements with a large negative, whose exp is 0
X = SequenceMask(X.reshape((-1, shape[-1])), valid_length)
return softmax(X).reshape(shape)
class DotProductAttention(nn.Module):
'''
1. 计算评分
2. 计算归一化
3. 计算加权
'''
def __init__(self, dropout, **kwargs):
super(DotProductAttention, self).__init__(**kwargs)
self.dropout = nn.Dropout(dropout)
# query: (batch_size, #queries, d)
# key: (batch_size, #kv_pairs, d)
# value: (batch_size, #kv_pairs, dim_v)
# valid_length: either (batch_size, ) or (batch_size, xx)
def forward(self, query, key, value, valid_length=None):
d = query.shape[-1]
# set transpose_b=True to swap the last two dimensions of key
scores = torch.bmm(query, key.transpose(1,2)) / math.sqrt(d)
attention_weights = self.dropout(masked_softmax(scores, valid_length))
print("attention_weight\n",attention_weights)
return torch.bmm(attention_weights, value)