深圳有名的网站设计公司,做网站及小程序需要会哪些技能,discuz 科技网站模板,网站没有做301的后果是什么前言
虽然Transformer是2017年由Google推出#xff0c;如果按照读论文只读近两年的思路看#xff0c;那它无疑是过时的#xff0c;但可惜的是#xff0c;目前很多论文的核心依然是Transformer#xff0c;或者由其进行改进的#xff0c;故本文使用pytorch来搭建一下Trans…前言
虽然Transformer是2017年由Google推出如果按照读论文只读近两年的思路看那它无疑是过时的但可惜的是目前很多论文的核心依然是Transformer或者由其进行改进的故本文使用pytorch来搭建一下Transformer这个模型
全局分析
首先我们要从整个模型架构入手从大的层面看这块内容然后再开始编写代码。欧克这里默认大家掌握了一些基础知识Transformer是由Google于2017年的Attention Is All You Need论文上所提出来的。如下图则是论文中所提出的整个框架可以很清晰的看出具体所使用的组件 总的来看两个模块编码器、解码器特别有自编码器的思想(换句话说Transformer借鉴了seq2sqe而seq2sqe天然就是自编码器的思想)下面引用了论文当中的话. Encoder: 编码器由 N 6 N 6 N6 个相同层的堆叠组成。每一层有两个子层。第一个是多头自注意机制第二个是一个简单的位置全连接前馈网络。我们在两个子层中的每一个周围使用残差连接随后进行层归一化。也就是说每个子层的输出是 L a y e r N o r m ( x S u b l a y e r ( x ) ) LayerNorm(x Sublayer(x)) LayerNorm(xSublayer(x))其中 S u b l a y e r ( x ) Sublayer(x) Sublayer(x) 是由子层本身实现的函数。为了促进这些剩余连接模型中的所有子层以及嵌入层产生维度 d m o d e l 512 d_{model} 512 dmodel512 的输出。 Decoder: 解码器也由 N 6 N 6 N6 个相同层的堆栈组成。除了每个编码器层中的两个子层之外解码器还插入第三子层该第三子层对编码器堆栈的输出执行多头注意。与编码器类似我们在每个子层周围使用残差连接然后进行层归一化。我们还修改了解码器堆栈中的自注意子层以防止位置注意到后续位置。该掩蔽与输出嵌入偏移一个位置的事实相结合确保了位置i的预测可以仅依赖于小于 i i i 的位置处的已知输出。
好了我们了解了整个大的框架开始接触小的组件由上图我们可以进行拆分其整个框架是由嵌入层、位置编码层、掩码多头注意力层、前馈神经网络层、残差连接归一化层接下来我们分别实现这些神经网络块层并将其堆叠在一起其实就是Transformer了。
好了接下来我们就开始编写代码吧
位置编码层
下面的就是位置编码。在这里打个比方就比如机器翻译问题上我们需要将一句话翻译成另一句话即A -- B那么A和B必然是长度随机的编码这里输入的就是想要翻译几句话几个单词每个单词的特征编码 P E ( p o s , 2 i ) s i n ( p o s / 1000 0 2 i / d m o d e l ) PE_{(pos,2i)} sin(pos/10000^{2i/d_{model}}) PE(pos,2i)sin(pos/100002i/dmodel) P E ( p o s , 2 i 1 ) c o s ( p o s / 1000 0 2 i / d m o d e l ) PE_{(pos,2i1)} cos(pos/10000^{2i/d_{model}}) PE(pos,2i1)cos(pos/100002i/dmodel)
# 位置编码
class PositionalEncoding(nn.Module):num_hiddens: 神经元数量嵌入维度数量dropout: 神经元的丢弃概率max_len: 序列最大长度def __init__(self, num_hiddens, dropout, max_len1000):super(PositionalEncoding, self).__init__()self.dropout nn.Dropout(dropout)self.P torch.zeros((1, max_len, num_hiddens))# 等差数列X (torch.arange(max_len, dtypetorch.float32).reshape(-1, 1)/ torch.pow(10000, torch.arange(0, num_hiddens, 2, dtypetorch.float32) / num_hiddens))self.P[:, :, 0::2] torch.sin(X)self.P[:, :, 1::2] torch.cos(X)def forward(self, X): # X (批次序列长度特征)X X self.P[:, :X.shape[1], :].to(X.device)return self.dropout(X)残差连接归一化层
欧克接下来我们先来看一下简单理解的层。残差连接可以理解为防止梯度消失问题以及加快收敛的一个有效的方法。而归一化就是防止梯度爆炸的问题
# 残差连接 归一化层
class AddNorm(nn.Module):normalized_shape: 形状大小dropout: 神经元的丢弃概率def __init__(self, normalized_shape, dropout):super(AddNorm, self).__init__()self.drop nn.Dropout(dropout) # 丢弃层self.layer nn.LayerNorm(normalized_shape) # 对输入的小批量应用层规范化def forward(self, X, Y):return self.layer(self.drop(Y) X)前馈神经网络层
这块也比较简单就是两层的感知机而已。 F F N ( x ) m a x ( 0 , x W 1 b 1 ) W 2 b 2 FFN(x) max(0, xW_1 b_1)W_2 b_2 FFN(x)max(0,xW1b1)W2b2
# 前馈神经网络
class PostionWiseFFN(nn.Module):num_input: 输入形状num_hiddens: 隐藏层形状num_ouput: 输出层形状def __init__(self, num_input, num_hiddens, num_ouput):super(PostionWiseFFN, self).__init__()self.liner1 nn.Linear(num_input, num_hiddens) # 线性层1self.relu nn.ReLU() # 激活函数self.liner2 nn.Linear(num_hiddens, num_ouput) # 线性层2def forward(self, X):return self.liner2(self.relu(self.liner1(X)))点积注意力机制 这里给出点积注意力机制的公式 A t t e n t i o n ( Q , K , V ) s o f t m a x ( Q K T d ) V Attention(Q,K,V) softmax(\frac{QK^T}{\sqrt{d}})V Attention(Q,K,V)softmax(d QKT)V
其中Q为查询矩阵K键矩阵V值矩阵它们的维度是 ( n , d k ) (n,d_k) (n,dk) ( m , d k ) (m,d_k) (m,dk) ( m , d v ) (m,d_v) (m,dv)m是键值对的数量 d k d_k dk是键矩阵和查询矩阵的特征维度 d v d_v dv是值矩阵的特征维度。而除于 d \sqrt{d} d 是为了避免梯度消失问题
class DotProductAttention(nn.Module):dropout: 神经元的丢弃概率def __init__(self, dropout):super(DotProductAttention, self).__init__()self.dropout nn.Dropout(dropout)def sequence_mask(self, X, valid_lens, value0): # 根据valid_lens生成掩码X: (批量, 最大序列长度, 特征)valid_lens: 有效长度value: 填充数据maxlen X.size(1)mask torch.arange((maxlen), dtypetorch.float32,deviceX.device)[None, :] valid_lens[:, None]X[~mask] valuereturn Xdef masked_softmax(self, X, valid_lens):if valid_lens is None:return nn.functional.softmax(X, dim-1)else:shape X.shapeif valid_lens.dim 1:valid_lens valid_lens.repeat_interleave(valid_lens, shape[1])else:valid_lens valid_lens.reshape(-1)X self.sequence_mask(X, valid_lens)return nn.functional.softmax(X.reshape(shape), dim-1)def forward(self, Q, K, V, valid_lensNone):d Q.shape[-1]scores torch.bmm(Q, K.transpose(1, 2)) / math.sqrt(d) # 点积获取注意力分数self.attention_weights self.masked_softmax(scores, valid_lens) # 获取注意力权重return torch.bmm(self.dropout(self.attention_weights), V) # 注意力权重 * 值V到这里我们由此便可以提出自注意力机制而自注意力机制对上面做出了一个很简单的改变就是 Q K V Q K V QKV换句话说Q、K、V同源
多头注意力
所谓多头注意力机制就是有多个自注意力机制并行然后将输出进行拼接送到线性层进一步整合 M u l t i H e a d ( Q , K , V ) C o n c a t ( h e a d 1 , . . . , h e a d h ) W O w h e r e h e a d i A t t e n t i o n ( Q W i Q , K W i K , V W i V ) MultiHead(Q,K,V) Concat(head_1,...,head_h)W^O\\ where \ head_i Attention(QW_i^Q,KW_i^K,VW_i^V) MultiHead(Q,K,V)Concat(head1,...,headh)WOwhere headiAttention(QWiQ,KWiK,VWiV)
# 多头注意力
class MultiHeadAttention(nn.Module):key_size: K值形状大小query_size: Q值形状大小value_size: V值形状大小num_hiddens: 隐藏层神经元数量num_heads: 头数dropout: 神经元的丢弃概率bias: 是否学习加性偏差默认不学习def __init__(self, key_size, query_size, value_size, num_hiddens,num_heads, dropout, biasFalse):super(MultiHeadAttention, self).__init__()assert num_hiddens % num_heads 0self.num_heads num_headsself.attention DotProductAttention(dropout) # 这里使用到了点积注意力机制self.W_q nn.Linear(query_size, num_hiddens, biasbias) # Q矩阵权重self.W_k nn.Linear(key_size, num_hiddens, biasbias) # K矩阵权重self.W_v nn.Linear(value_size, num_hiddens, biasbias) # V矩阵权重self.W_o nn.Linear(num_heads, num_hiddens, biasbias) # 输出线性层def forward(self, Q, K, V, valid_lens): # valid_lens 有效长度Q transpose_qkv(self.W_q(Q), self.num_heads)K transpose_qkv(self.W_k(K), self.num_heads)V transpose_qkv(self.W_v(V), self.num_heads)if valid_lens is not None:valid_lens torch.repeat_interleave(valid_lens, repeatsself.num_heads, dim0)output self.attention(Q, K, V, valid_lens)output_concat transpose_output(output, self.num_heads)return self.W_o(output_concat)这里我们通过transpose_qkv和transpose_output函数来将数据进行改造以此来进行并行操作
def transpose_qkv(X, num_heads):为了多注意力头的并行计算而变换形状# 输入X的形状:(batch_size查询或者“键值”对的个数num_hiddens)# 输出X的形状:(batch_size查询或者“键值”对的个数num_headsnum_hiddens/num_heads)X X.reshape(X.shape[0], X.shape[1], num_heads, -1)X X.permute(0, 2, 1, 3)return X.reshape(-1, X.shape[2], X.shape[3])
def transpose_output(X, num_heads):逆转transpose_qkv函数的操作X X.reshape(-1, num_heads, X.shape[1], X.shape[2])X X.permute(0, 2, 1, 3)return X.reshape(X.shape[0], X.shape[1], -1)编码器
如下则是编码器的代码
class EncoderBlock(nn.Module):K_size, Q_size, V_size: 键值对和查询的大小num_hiddens: 中间隐藏层神经元num_heads: 头数norm_shape: 残差连接归一化层的输入形状num_input: FFN的输入形状ffn_num_hiddens: FFN隐藏层神经元dropout: 神经元的丢弃概率def __init__(self, K_size, Q_size, V_size, num_hiddens, num_heads,norm_shape, num_input, ffn_num_hiddens, dropout, biasFalse):super(EncoderBlock, self).__init__()self.attention MultiHeadAttention(K_size, Q_size, V_size, num_hiddens, num_heads, dropout, bias) # 多头注意力机制 self.addnorm1 AddNorm(norm_shape, dropout) # 归一化残差连接self.ffn PostionWiseFFN(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))接下来我们需要将位置编码层、嵌入层、残差连接归一化层、前馈神经网络、编码器等封装起来
class TransformerEncoder(nn.Module):vocab_size: 词典大小K_size, Q_size, V_size: 键值对和查询的大小num_hiddens: 中间隐藏层神经元num_heads: 头数norm_shape: 残差连接归一化层的输入形状num_input: FFN的输入形状ffn_num_hiddens: FFN隐藏层神经元num_layers: 编码器的层数dropout: 神经元的丢弃概率def __init__(self, vocab_size, K_size, Q_size, V_size, num_hiddens, num_heads,norm_shape, num_input, ffn_num_hiddens, num_layers, dropout, biasFalse):super(TransformerEncoder, self).__init__()self.num_hiddens num_hiddens# num_embeddings: 嵌入词典的大小 embedding_dim: 每个嵌入向量的尺寸self.embedding nn.Embedding(num_embeddingsvocab_size, embedding_dimnum_hiddens) # 嵌入层self.pos_encoding PositionalEncoding(num_hiddens, dropout) # 位置编码层self.blks nn.Sequential()for i in range(num_layers):self.blks.add_module(block str(i),EncoderBlock(K_size, Q_size, V_size, num_hiddens, num_heads, norm_shape, num_input, ffn_num_hiddens, dropout, bias))def forward(self, X, valid_lens):# 输入 X (句子个数单词个数)# 输出 ret (句子个数单词个数单词特征)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_weightsreturn X解码器
class DecodeBlock(nn.Module):K_size, Q_size, V_size: 键值对和查询的大小num_hiddens: 中间隐藏层神经元num_heads: 头数norm_shape: 残差连接归一化层的输入形状num_input: FFN的输入形状ffn_num_hiddens: FFN隐藏层神经元dropout: 神经元的丢弃概率i:def __init__(self, K_size, Q_size, V_size, num_hiddens, num_heads,norm_shape, num_input, ffn_num_hiddens, dropout, i):super(DecodeBlock, self).__init__()self.i iself.attention1 MultiHeadAttention(K_size, Q_size, V_size, num_hiddens, num_heads, dropout) # 多头注意力机制self.addnorm1 AddNorm(norm_shape, dropout)self.attention2 MultiHeadAttention(K_size, Q_size, V_size, num_hiddens, num_heads, dropout) # 多头注意力机制self.addnorm2 AddNorm(norm_shape, dropout)self.ffn PostionWiseFFN(num_input, ffn_num_hiddens, num_hiddens)self.addnorm3 AddNorm(norm_shape, dropout)def forward(self, X, state):# state [编码器输入编码器有效长度中间状态用于记录]enc_outputs, enc_valid_lens state[0], state[1]if state[2][self.i] is None:K Xelse:K torch.cat((state[2][self.i], X), axis1)state[2][self.i] Kif self.training:batch_size, num_steps, _ X.shapedec_valid_lens torch.arange(1, batch_size 1, deviceX.device)else:dec_valid_lens NoneX2 self.attention1(X, K, K, dec_valid_lens)Y self.addnorm1(X, X2)Y2 self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)Z self.addnorm2(Y, Y2)return self.addnorm3(Z, self.ffn(Z)), state封装
class TransformerDecoder(nn.Module):vocab_size: 词典大小K_size, Q_size, V_size: 键值对和查询的大小num_hiddens: 中间隐藏层神经元num_heads: 头数norm_shape: 残差连接归一化层的输入形状num_input: FFN的输入形状ffn_num_hiddens: FFN隐藏层神经元num_layers: 编码器的层数dropout: 神经元的丢弃概率def __init__(self, vocab_size, K_size, Q_size, V_size, num_hiddens, num_heads,norm_shape, num_input, ffn_num_hiddens, num_layers, dropout):super(TransformerDecoder, self).__init__()self.num_hiddens num_hiddensself.num_layers num_layersself.embedding nn.Embedding(num_embeddingsvocab_size, embedding_dimnum_hiddens)self.pos_encoding PositionalEncoding(num_hiddens, dropout)self.blks nn.Sequential()for i in range(num_layers):self.blks.add_module(block str(i),DecodeBlock(K_size, Q_size, V_size, num_hiddens, num_heads, norm_shape, num_input, ffn_num_hiddens, dropout, i))self.linear nn.Linear(num_hiddens, vocab_size)def init_state(self, enc_outputs, enc_valid_lens):return [enc_outputs, enc_valid_lens, [None] * self.num_layers]def forward(self, X, state):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_weightsself._attention_weights[1][i] blk.attention2.attention.attention_weightsreturn self.linear(X), self._attention_weightspropertydef attention_weights(self):return self._attention_weightsTransformer
最后整合一下
class EncoderDecoder(nn.Module):编码器-解码器架构的基类def __init__(self, encoder, decoder, **kwargs):super(EncoderDecoder, self).__init__(**kwargs)self.encoder encoderself.decoder decoderdef forward(self, enc_X, dec_X, *args):enc_outputs self.encoder(enc_X, *args)dec_state self.decoder.init_state(enc_outputs, *args)return self.decoder(dec_X, dec_state)Transformer以三种不同的方式使用多头注意力(引自论文)
在 “encoder-decoder attention” 层中查询 Q Q Q来自先前的解码器层并且存储器键 K K K和值 V V V来自编码器的输出。这使得解码器中的每个位置都能处理输入序列中的所有位置。这模拟了序列到序列模型中的典型编码器-解码器注意机制。编码器包含自我注意层。在自关注层中所有的键、值和查询都来自同一个地方在这种情况下是编码器中前一层的输出。编码器中的每个位置都可以处理编码器的前一层中的所有位置。类似地解码器中的自关注层允许解码器中的每个位置关注解码器中的直到并且包括该位置的所有位置。我们需要防止解码器中的冗余信息流以保持自回归特性。我们通过屏蔽设置为 − ∞ -\infty −∞ s o f t m a x softmax softmax 输入中对应于非法连接的所有值来实现这一点。
最后大家再来回顾一下Transformer模型整体架构