1.简介
seq2seq 就是一个任务,实现的是从序列到序列的映射,比如将我爱你翻译成i love you,实现这个任务的方式有很多种,不同的架构,不同的算法等等
encoder-decoder就是一种网络架构,实现编码和解码的功能
1.1通过rnn的编码解码机制引出attention机制
上图缺失target——batch
上图缺失initial-state=
2.序列到序列的注意力机制(seq2seq with attention)
2.1 seq2seq with attention网络架构
1.相当于循环神经网络虽然后一个节点保留了前一个结点的部分信息,但是最后只是输出了一个特征c,用于解码器进行解码
2.可以让每一个结点都进行输出一个特征c,然后将这n个c进行累加,但是存在的问题是解码器可能不需要这么多的特征
3.为了解决上面的问题,将每个特征添加一个权重,就是上图中的alpha t1,即提取对应数据的特征,并计算对应特征的权重,最后进行累加,即注意力机制
2.2 seq2seq with Attention编码器Encoder原理
上图遮挡部分为激活函数alpha
2.3 seq2seq with Attention编码器Decoder原理
所以现在的问题就转换成了如何计算权重的问题了,如上面的ci的计算。eij为打分函数
2.4 seq2seq with Attention原理实现
transformer
注意力机制有很多种,软/硬/自/多头/全局/局部注意力机制
注意力机制的计算分为两步
1、在输入信息上计算注意力分布
2、根据注意力分布计算输入信息的加权平均
这里需要注意的是一个概念叫做查询序列,在预测n的时候,会查询1~n-1,即前面的所有序列,即预测序列的前序序列,即查询序列query,前序序列可以是动态生成的,也可以是可学习的参数
点奇函数是矩阵想成,导致方差太大,提出缩放点积解决这个问题
软注意力机制
加权平均,即不断的求数学期望
在软注意力机制中,x参与权值计算,然后经过softmax得到注意力的权值分布后,再与x进行累加,相当于一个人(x)干两个活。如上图所示。
键值对的注意力机制
就是不再是一个人干两个活了,使用key键来计算注意力分布,使用value值计算聚合信息
通过上图可以看出这个计算过程通过key先与向量计算注意力分布(分布通过softmax计算,即权值分布),然后再2与值value相乘,进行特征的提取,最后进行累加
注意这里如果key=value时,就相当于是软注意力机制了
自注意力机制
上面可以看到前序序列即查询序列Q和键K和值V都对输入通过W权重进行相乘,这样的话每次的更新Wq Wk Wv都能够保留x的相关信息。这里x相当于一个人干了三个人的活,其中q,k,v和上面的键值对注意力机制的计算方式相同
输出序列计算方式和键值对注意力机制一样,就是权值分布再乘上value,即乘上WvX
3.Transformer通用的特征提取器
inputs 源序列
outputs (probabilites)目标序列
outputs (shift right)当前预测的目标单词的前序序列
比如在目标序列[t1, t2,…tq-1, tq, tq+1, tq+2 …] 中要预测tq,那么在前序序列那里就是[t1,t2,…tq-1]
上面的图处理流程:
1.第一步进行词嵌入
先对词进行编码,然后将编码后的映射成向量便于计算,映射方式(即嵌入方式)有onehot编码,tfidf
词序列 | 编码 |
---|---|
我 | 1 |
爱 | 2 |
你 | 3 |
。 | 4 |
此时input就是[1,2,3,4],对输入进行enbedding,使用onehot可以得到编码所对应的向量
编码 | 向量 | . | ||
---|---|---|---|---|
1 | 1 | 0 | 0 | 0 |
2 | 0 | 1 | 0 | 0 |
3 | 0 | 0 | 1 | 0 |
4 | 0 | 0 | 0 | 1 |
可以知道这个纬度是什么样的,一般输入中有重复的字,但是onehot编码没有重复的,所以假设句子长度为N,而词嵌入维度为d_model,就可以得到输入序列的位置编码R(Nxd_model)
一般取长句子的N作为N,然后长度不够的句子进行padding补零,如果还有比N大的,那么只能截断了
输入序列,目标序列与输出序列
词嵌入与位置编码
输入序列词嵌入与位置编码
位置信息很重要,进行词嵌入就丢失了前后的依赖信息,即使把句子打乱了还是有相同的词嵌入,所以要加入位置信息,通过下面的pos两个公式可以将位置信息计算出来,公式来源于经验
将位置嵌入信息和词嵌入信息进行累加,就得到了所有的信息内容,即包含语义信息又包含位置信息
上图可以看出是位置嵌入和词嵌入相加得到的输入信息
输出序列词嵌入与位置编码
同上
第二步encoder网络结构
上图可以看出流程为:
+多头注意力机制
+残差网络的累积加归一化
+前馈神经网络即全连接网络,加残差网络加归一化
Nx表示不断的堆叠,经验值是堆叠6层
如上图,e0作为词嵌入和位置嵌入输入,然后通过e0通过编码器得到e1然后不断进行堆叠(n=6),并且可以注意到词嵌入和位置嵌入的向量维度是一致的可以直接进行相加
对于编码器部分看下图,上面的公式和下图是一一对应的
分出三个叉、由于对头注意力机制的核心是自注意力机制,就是有三个权重Wq Wk Wv
多头注意力机制与缩放点积
首先声明,上面的左右两幅图中的QKA不是等同的,左边的图是直接输入的QKV,右边的图是QKV进行线性变换后,才输入到模型里面,就像是一片一片的面包片,沾了不同的酱,然后最后通过contact进行拼接,然后再通过一个线性变化压回原始的Nxd_model维里面去
按照上面的公式进行解读,可以看到是直接将QKV输入到里面进行head的计算,省略了线性变化的部分
解码器pad掩码、解码器sequence掩码和编码器pad掩码
上图即mask部分部分,是掩膜版作用是在使用前序序列Q时,只能看到qt位置以前的序列,后面的序列都被mask了,比如在使用我爱你时,如果比如在预测我时,Q什么也看不到,当在预测爱时,Q可以看到我,当在预测你时,Q可以看到我爱。
上图可以看到,前序序列是Query序列,然后kv是成对出现的
编码器pad掩码
长度不够,用0填充
transformer结构代码部分
编码定义
解码器
掩码
attention代码
class BertSelfAttention(nn.Module):
def __init__(self, config):
super().__init__()
if config.hidden_size % config.num_attention_heads != 0 and not hasattr(config, "embedding_size"):
raise ValueError(
f"The hidden size ({config.hidden_size}) is not a multiple of the number of attention "
f"heads ({config.num_attention_heads})"
)
self.num_attention_heads = config.num_attention_heads
self.attention_head_size = int(config.hidden_size / config.num_attention_heads)
self.all_head_size = self.num_attention_heads * self.attention_head_size
self.query = nn.Linear(config.hidden_size, self.all_head_size) # W_q
self.key = nn.Linear(config.hidden_size, self.all_head_size) # W_k
self.value = nn.Linear(config.hidden_size, self.all_head_size) # W_v
self.dropout = nn.Dropout(config.attention_probs_dropout_prob)
self.position_embedding_type = getattr(config, "position_embedding_type", "absolute")
if self.position_embedding_type == "relative_key" or self.position_embedding_type == "relative_key_query":
self.max_position_embeddings = config.max_position_embeddings
self.distance_embedding = nn.Embedding(2 * config.max_position_embeddings - 1, self.attention_head_size)
self.is_decoder = config.is_decoder
def transpose_for_scores(self, x):
new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size)
x = x.view(*new_x_shape) # [batch_size, seq_length, num_heads, all_hidden_size]
return x.permute(0, 2, 1, 3) # [batch_size, num_heads, seq_length, all_hidden_size] 第一维度和第二维度换个顺序
def forward(
self,
hidden_states,
attention_mask=None,
head_mask=None,
encoder_hidden_states=None,
encoder_attention_mask=None,
past_key_value=None,
output_attentions=False,
):
mixed_query_layer = self.query(hidden_states) # [batch_size, seq_length, all_hidden_size]
# If this is instantiated as a cross-attention module, the keys
# and values come from an encoder; the attention mask needs to be
# such that the encoder's padding tokens are not attended to.
is_cross_attention = encoder_hidden_states is not None
if is_cross_attention and past_key_value is not None:
# reuse k,v, cross_attentions
key_layer = past_key_value[0]
value_layer = past_key_value[1]
attention_mask = encoder_attention_mask
elif is_cross_attention:
key_layer = self.transpose_for_scores(self.key(encoder_hidden_states))
value_layer = self.transpose_for_scores(self.value(encoder_hidden_states))
attention_mask = encoder_attention_mask
elif past_key_value is not None:
key_layer = self.transpose_for_scores(self.key(hidden_states))
value_layer = self.transpose_for_scores(self.value(hidden_states))
key_layer = torch.cat([past_key_value[0], key_layer], dim=2)
value_layer = torch.cat([past_key_value[1], value_layer], dim=2)
else:
key_layer = self.transpose_for_scores(self.key(hidden_states)) #[batch_size, num_heads, seq_length, all_hidden_size]
value_layer = self.transpose_for_scores(self.value(hidden_states)) #[batch_size, num_heads, seq_length, all_hidden_size]
query_layer = self.transpose_for_scores(mixed_query_layer) #[batch_size, num_heads, seq_length, all_hidden_size]
if self.is_decoder:
# if cross_attention save Tuple(torch.Tensor, torch.Tensor) of all cross attention key/value_states.
# Further calls to cross_attention layer can then reuse all cross-attention
# key/value_states (first "if" case)
# if uni-directional self-attention (decoder) save Tuple(torch.Tensor, torch.Tensor) of
# all previous decoder key/value_states. Further calls to uni-directional self-attention
# can concat previous decoder key/value_states to current projected key/value_states (third "elif" case)
# if encoder bi-directional self-attention `past_key_value` is always `None`
past_key_value = (key_layer, value_layer)
# Take the dot product between "query" and "key" to get the raw attention scores.
attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2)) #[batch_size, num_heads, seq_length, all_hidden_size]
if self.position_embedding_type == "relative_key" or self.position_embedding_type == "relative_key_query":
seq_length = hidden_states.size()[1]
position_ids_l = torch.arange(seq_length, dtype=torch.long, device=hidden_states.device).view(-1, 1)
position_ids_r = torch.arange(seq_length, dtype=torch.long, device=hidden_states.device).view(1, -1)
distance = position_ids_l - position_ids_r
positional_embedding = self.distance_embedding(distance + self.max_position_embeddings - 1)
positional_embedding = positional_embedding.to(dtype=query_layer.dtype) # fp16 compatibility
if self.position_embedding_type == "relative_key":
relative_position_scores = torch.einsum("bhld,lrd->bhlr", query_layer, positional_embedding)
attention_scores = attention_scores + relative_position_scores
elif self.position_embedding_type == "relative_key_query":
relative_position_scores_query = torch.einsum("bhld,lrd->bhlr", query_layer, positional_embedding)
relative_position_scores_key = torch.einsum("bhrd,lrd->bhlr", key_layer, positional_embedding)
attention_scores = attention_scores + relative_position_scores_query + relative_position_scores_key
attention_scores = attention_scores / math.sqrt(self.attention_head_size) #[batch_size, num_heads, seq_length, all_hidden_size]
if attention_mask is not None:
# Apply the attention mask is (precomputed for all layers in BertModel forward() function)
attention_scores = attention_scores + attention_mask
# Normalize the attention scores to probabilities.
attention_probs = nn.Softmax(dim=-1)(attention_scores) #[batch_size, num_heads, seq_length, all_hidden_size]
# This is actually dropping out entire tokens to attend to, which might
# seem a bit unusual, but is taken from the original Transformer paper.
attention_probs = self.dropout(attention_probs) #[batch_size, num_heads, seq_length, all_hidden_size]
# Mask heads if we want to
if head_mask is not None:
attention_probs = attention_probs * head_mask
context_layer = torch.matmul(attention_probs, value_layer) #[batch_size, num_heads, seq_length, all_hidden_size]
context_layer = context_layer.permute(0, 2, 1, 3).contiguous()
new_context_layer_shape = context_layer.size()[:-2] + (self.all_head_size,) #[batch_size, seq_length, num_heads, all_hidden_size]维度再进心调转会来
context_layer = context_layer.view(*new_context_layer_shape) #[batch_size, seq_length, all_hidden_size]进行维度的拼接,输入是这个维度,输出还是这个维度,这就是transformer结构
outputs = (context_layer, attention_probs) if output_attentions else (context_layer,)
if self.is_decoder:
outputs = outputs + (past_key_value,)
return outputs
分词的代码
class WordpieceTokenizer(object): #word to token 拆分的方式和下面的例子是一样的,input = "unaffable"` wil return as output :obj:`["un", "##aff", "##able"]`
"""Runs WordPiece tokenization."""
def __init__(self, vocab, unk_token, max_input_chars_per_word=100):
self.vocab = vocab
self.unk_token = unk_token
self.max_input_chars_per_word = max_input_chars_per_word
def tokenize(self, text):
"""
Tokenizes a piece of text into its word pieces. This uses a greedy longest-match-first algorithm to perform
tokenization using the given vocabulary.
For example, :obj:`input = "unaffable"` wil return as output :obj:`["un", "##aff", "##able"]`.
Args:
text: A single token or whitespace separated tokens. This should have
already been passed through `BasicTokenizer`.
Returns:
A list of wordpiece tokens.
"""
output_tokens = []
for token in whitespace_tokenize(text):
chars = list(token)
if len(chars) > self.max_input_chars_per_word:
output_tokens.append(self.unk_token)
continue
is_bad = False
start = 0
sub_tokens = []
while start < len(chars):
end = len(chars)
cur_substr = None
while start < end:
substr = "".join(chars[start:end])
if start > 0:
substr = "##" + substr
if substr in self.vocab:
cur_substr = substr
break
end -= 1
if cur_substr is None:
is_bad = True
break
sub_tokens.append(cur_substr)
start = end
if is_bad:
output_tokens.append(self.unk_token)
else:
output_tokens.extend(sub_tokens)
return output_tokens
decoder就是将上三角形,将t1 --> t2
t1,t2—>t3
t1,t2,t3—>t4
以此类推进行预测