一、transform架构
1、自注意力同时具有并行计算和最短的最大路径长度这两个优势,Transformer模型完全基于注意力机制,没有任何卷积层或循环神经网络层。
2、Transformer是由编码器和解码器组成,与基于Bahdanau注意力实现的序列到序列的学习相比,Transformer的编码器和解码器是基于自注意力的模块叠加而成的,源(输入)序列和目标(输出)序列的嵌入(embedding)表示将加上位置编码(positional encoding),再分别输入到编码器和解码器中。
3、Transformer的编码器是由多个相同的层叠加而成的,每个层都有两个子层。第一个子层是多头自注意力(multi-head self-attention)汇聚;第二个子层是基于位置的前馈网络(positionwise feed-forward network);在计算编码器的自注意力时,查询、键和值都来自前一个编码器层的输出;每个子层都采用了残差连接。
4、Transformer解码器也是由多个相同的层叠加而成的,并且层中使用了残差连接和层规范化;除了编码器中描述的两个子层之外,解码器还在这两个子层之间插入了第三个子层,称为编码器-解码器注意力;
5、在编码器-解码器注意力中,查询来自前一个解码器层的输出,而键和值来自整个编码器的输出;在解码器自注意力中,查询、键和值都来自上一个解码器层的输出。但是,解码器中的每个位置只能考虑该位置之前的所有位置;掩蔽(masked)注意力确保预测仅依赖于已生成的输出词元。
二、多头注意力
1、用独立学习得到的h组不同的 线性投影(linear projections)来变换查询、键和值。 然后,这h组变换后的查询、键和值将并行地送到注意力汇聚中。 最后,将这h个注意力汇聚的输出拼接在一起, 并且通过另一个可以学习的线性投影进行变换, 以产生最终输出。
2、对同一个key、value、query,希望抽取不同的信息(例如短距离关系与长距离关系)
3、多头注意力使用h个独立的注意力池化,合并各个头(head)输出得到最终输出
4、模型
(1)给定
(2)头i可学习的参数
(3)头i的输出
(4)输出可学习的参数
(5)多头注意力的输出
5、有掩码的多头注意力
(1)解码器对序列中的一个元素输出时,不应该考虑元素之后的元素
(2)可以通过掩码来实现,也就是计算x_i输出时,假装当前序列长度为i
6、多头注意力融合了来自于多个注意力汇聚的不同知识,这些知识的不同来源于相同的查询、键和值的不同的子空间表示;基于适当的张量操作,可以实现多头注意力的并行计算。
三、基于位置的前馈网络,Feed-forward Network(前馈神经网络)
1、基于位置的前馈网络对序列中的所有位置的表示进行变换时使用的是同一个多层感知机(MLP),这就是称前馈网络是基于位置的(positionwise)的原因。
2、输入X
的形状(批量大小,时间步数或序列长度,隐单元数或特征维度)将被一个两层的感知机转换成形状为(批量大小,时间步数,ffn_num_outputs
)的输出张量。
四、AddNorm
类
1、使用残差连接和层规范化来实现AddNorm
类,暂退法也被作为正则化方法使用。
2、批量归一化对每个特征/通道里的元素进行归一化,这个不适合序列长度会变化的NLP应用
3、层归一化对每一个样本里的元素进行归一化
五、编码器与解码器
1、EncoderBlock
类包含两个子层:多头自注意力和基于位置的前馈网络,这两个子层都使用了残差连接和紧随的层规范化。
2、Transformer解码器也是由多个相同的层组成。在DecoderBlock
类中实现的每个层包含了三个子层:解码器自注意力、“编码器-解码器”注意力和基于位置的前馈网络。这些子层也都被残差连接和紧随的层规范化围绕。
六、信息传递
1、编码器中的输出y_1,......,y_n。
2、将其作为解码中的第i个Transformer块中多头注意力的key和value,它的query来自目标序列
3、编码器和解码器中的块的个数和输出维度都是一样的
七、预测
1、预测第t+1个输出时,解码器中输入前t个预测值;
2、在自注意力中,前t个预测值作为key和value,第t个预测值还作为query
八、总结
1、在Transformer中,多头自注意力用于表示输入序列和输出序列,不过解码器必须通过掩蔽机制来保留自回归属性。
2、Transformer中的残差连接和层规范化是训练非常深度模型的重要工具。
3、Transformer模型中基于位置的前馈网络使用同一个多层感知机,作用是对所有序列位置的表示进行转换。
4、Transformer是纯使用注意力的编码-解码器
5、编码器与解码器中都有n个Transformer块
6、每个块中使用多头(自)注意力,基于位置的前馈网络和层归一化
九、代码
1、基于位置的前馈网络
#@save class PositionWiseFFN(nn.Module): """基于位置的前馈网络""" def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs, **kwargs): super(PositionWiseFFN, self).__init__(**kwargs) #将输入维度从 ffn_num_input 转换为 ffn_num_hiddens。 self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens) self.relu = nn.ReLU() #将隐藏层的输出维度从 ffn_num_hiddens 转换为最终的输出维度 ffn_num_outputs self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs) def forward(self, X): return self.dense2(self.relu(self.dense1(X)))
2、使用残差连接和层规范化来实现AddNorm
类
#@save class AddNorm(nn.Module): """残差连接后进行层规范化""" def __init__(self, normalized_shape, dropout, **kwargs): super(AddNorm, self).__init__(**kwargs) self.dropout = nn.Dropout(dropout) self.ln = nn.LayerNorm(normalized_shape) def forward(self, X, Y): return self.ln(self.dropout(Y) + X)
3、编码器:多头注意力->加&规范化->前馈神经网络->加&规范化
#@save class EncoderBlock(nn.Module): """Transformer编码器块""" def __init__(self, key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, dropout, use_bias=False, **kwargs): super(EncoderBlock, self).__init__(**kwargs) self.attention = d2l.MultiHeadAttention( key_size, query_size, value_size, num_hiddens, num_heads, dropout, use_bias) self.addnorm1 = AddNorm(norm_shape, dropout) self.ffn = PositionWiseFFN( ffn_num_input, ffn_num_hiddens, num_hiddens) self.addnorm2 = AddNorm(norm_shape, dropout) def forward(self, X, valid_lens): Y = self.addnorm1(X, self.attention(X, X, X, valid_lens)) return self.addnorm2(Y, self.ffn(Y))
4、Transformer编码器
#@save class TransformerEncoder(d2l.Encoder): """Transformer编码器""" def __init__(self, vocab_size, key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout, use_bias=False, **kwargs): super(TransformerEncoder, self).__init__(**kwargs) self.num_hiddens = num_hiddens self.embedding = nn.Embedding(vocab_size, num_hiddens) self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout) self.blks = nn.Sequential() for i in range(num_layers): #在顺序容器中添加编码器块,每个编码器块由 EncoderBlock 类定义。 self.blks.add_module("block"+str(i), EncoderBlock(key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, dropout, use_bias)) def forward(self, X, valid_lens, *args): # 因为位置编码值在-1和1之间, # 因此嵌入值乘以嵌入维度的平方根进行缩放,缩放是通过乘以嵌入维度的平方根 sqrt(d_model) 来实现的。这样做的目的是使嵌入值的方差保持在一个合理的范围内,从而避免在训练初期出现梯度过大或过小的问题。 # 然后再与位置编码相加。 X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens)) #初始化一个列表,用于存放每个编码器块的注意力权重。 self.attention_weights = [None] * len(self.blks) for i, blk in enumerate(self.blks): X = blk(X, valid_lens) self.attention_weights[ i] = blk.attention.attention.attention_weights return X
5、解码器
class DecoderBlock(nn.Module): """解码器中第i个块""" def __init__(self, key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, dropout, i, **kwargs): super(DecoderBlock, self).__init__(**kwargs) self.i = i self.attention1 = d2l.MultiHeadAttention( key_size, query_size, value_size, num_hiddens, num_heads, dropout) self.addnorm1 = AddNorm(norm_shape, dropout) self.attention2 = d2l.MultiHeadAttention( key_size, query_size, value_size, num_hiddens, num_heads, dropout) self.addnorm2 = AddNorm(norm_shape, dropout) self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens) self.addnorm3 = AddNorm(norm_shape, dropout) def forward(self, X, state): #从 state 中提取编码器的输出 enc_outputs 和编码器的有效长度 enc_valid_lens。这些是在编码器阶段生成的,用于解码器中的注意力机制。 enc_outputs, enc_valid_lens = state[0], state[1] batch_size, num_steps, _ = X.shape # dec_valid_lens的开头:(batch_size,num_steps), # 其中每一行是[1,2,...,num_steps] #如果在训练阶段,构建 dec_valid_lens,每个时间步的有效长度为从 1 到当前步数的数组。这是因为在训练阶段,所有的词元在同一时间处理。 dec_valid_lens = torch.arange( 1, num_steps + 1, device=X.device).repeat(batch_size, 1) #如果在预测阶段,dec_valid_lens 为 None。 # 自注意力 X2 = self.attention1(X, key_values, key_values, dec_valid_lens) Y = self.addnorm1(X, X2) # 编码器-解码器注意力。 # enc_outputs的开头:(batch_size,num_steps,num_hiddens) Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens) Z = self.addnorm2(Y, Y2) #前馈网络和层归一化 return self.addnorm3(Z, self.ffn(Z)), state
6、Transformer解码器
class TransformerDecoder(d2l.AttentionDecoder): def __init__(self, vocab_size, key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout, **kwargs): super(TransformerDecoder, self).__init__(**kwargs) self.num_hiddens = num_hiddens self.num_layers = num_layers self.embedding = nn.Embedding(vocab_size, num_hiddens) self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout) self.blks = nn.Sequential() for i in range(num_layers): self.blks.add_module("block"+str(i), DecoderBlock(key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, dropout, i)) self.dense = nn.Linear(num_hiddens, vocab_size) def init_state(self, enc_outputs, enc_valid_lens, *args): self.seqX = None return [enc_outputs, enc_valid_lens] def forward(self, X, state): if not self.training: self,seqX = X if self.seqX is None else torch.cat((self.seqX, X),dim = 1) X = self.seqX X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens)) self._attention_weights = [[None] * len(self.blks) for _ in range (2)] for i, blk in enumerate(self.blks): X, state = blk(X, state) # 解码器自注意力权重 self._attention_weights[0][i] = blk.attention1.attention.attention_weights # “编码器-解码器”自注意力权重 self._attention_weights[1][i] = blk.attention2.attention.attention_weights if not self.training: return self.dense(X)[:, -1:, :],state return self.dense(X), state @property def attention_weights(self): return self._attention_weights
7、训练
num_hiddens, num_layers, dropout, batch_size, num_steps = 32, 2, 0.1, 64, 10 lr, num_epochs, device = 0.005, 200, d2l.try_gpu() ffn_num_input, ffn_num_hiddens, num_heads = 32, 64, 4 key_size, query_size, value_size = 32, 32, 32 norm_shape = [32] train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps) encoder = TransformerEncoder( len(src_vocab), key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout) decoder = TransformerDecoder( len(tgt_vocab), key_size, query_size, value_size, num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens, num_heads, num_layers, dropout) net = d2l.EncoderDecoder(encoder, decoder) d2l.train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)