【代码解读】Transformer(一)——Encoder

理论介绍

transformer有两个输入,编码端输入和解码端输入。编码端输入经过词向量层以及位置编码层得到一个最终输入,然后流经自注意力层,然后经过前馈神经网络层,得到一个编码端的输出;同样,解码端的输入经过词向量层和位置编码层,得到最终输入,经过掩码自注意力层(把单词全部mask掉),然后流经交互注意力层(编码端的输出和解码端的信息进行一个交互,q来自解码端,k,v来自编码端输出),然后经过前馈神经网络层,最后得到一个输出。
在这里插入图片描述
在这里插入图片描述
问题一:一条数据中三个句子分别代表什么?
编码端的输入;解码端的输入;解码端的真实标签
问题二:
P代表什么?PAD填充字符
S代表什么?开始标志
E代表什么?结束标志
在这里插入图片描述

代码部分

主函数

if __name__ == '__main__':
	
	#句子的输入部分
	sentences = ['ich mochte ein bier P', 'S i want a beer', 'i want a beer E']
	
	#transformer parameters
	#padding should be zero
	#构建词表
	#编码器词表
	src_vocab = {'P':0,'ich':1,'mochte':2,'ein':3,'bier':4}
	src_vocab_size = len(src_vocab)

	#解码器词表
	tgt_vocab = {'P':0,'i':1,'want':2,'a':3,'beer':4}
	tgt_vocab_size = len(tgt_vocab)

	src_vocab = 5 #length of source
	tgt_vocab = 5 #length of target

	#模型参数
	d_model = 512  #每个词转成embedding时的大小
	d_ff = 2048  #前馈神经网络中映射到多少维度
	d_k = d_v = 64 #
	n_layers = 6 # number of encoder of decoder layer
	n_heads = 8  #多头注意力机制的头的数量

	model = Transformer()

代码1:Transformer整体架构层代码

编码层
解码层
输出层

class Transformer(nn.Module):
	def __init__(self):
		super(Transformer, self).__init__()
		self.encoder = Encoder()
		self.decoder = Decoder()
		self.projection = nn.Linear(d_model, tgt_vocab_size, bias=False)
	
	#实现函数	
	def forward(self, enc_inputs, dec_inputs):
		#这里有两个数据进行输入,解码端输入形状为[batch_size, src_len],解码端输入形状为[batch_size, tgt_len]
		#enc_inputs作为输入,输出由自己的函数内部指定,想要什么指定输出什么,可以是全部tokens的输出,可以是特定每一层的输出;也可以是中间某些参数的输出;
		#enc_outputs是decoder主要的输出,enc_self_attns是QK转置相乘之后softmax之后的矩阵值(相似度),代表的是每个单词和其他单词的相关性。
		enc_outputs, enc_self_attns = self.encoder(enc_inputs)

		#dec_outputs是decoder主要输出,用于后续的Linear映射;dec_self_attns类比于enc_self_attns是查看每个单词对decoder中输入的其余单词的相关性;dec_enc_attns是decoder中每个单词对encoder中每个单词的相关性
		dec_outputs, dec_self_attns, dec_enc_attns = self.decoder(dec_inputs, enc_inputs, enc_outputs)

		#dec_outputs做映射到词表大小
		dec_logits = self.projection(dec_outputs)  #dec_logits : [batch_size × src_vocab_size × tgt_vocab_size]
		return dec_logits.view(-1, dec_logits.size(-1)),enc_self_attns, dec_self_attns, dec_enc_attns

代码2:Encoder部分

词向量embedding
位置编码部分
注意力层及后续的前馈神经网络

class Encoder(nn.Module):
	def __init__(self):
		super(Encoder, self).__init__()
		self.src_emb = nn.Embedding(src_vocab_size, d_model) # 定义生成一个矩阵,大小是src_vocab_size × d_model
		self.pos_emb = PositionalEncoding(d_model) # 位置编码情况,这里是固定的正余弦函数,也可以使用类似的nn.Embedding获得一个可以更新学习的位置编码
		self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)]) # 使用ModuleList对多个encoder进行堆叠,因为后续的encoder并没有使用词向量和位置编码,所以抽离出来
	
	#实现函数
	def forward(self, enc_inputs):
		# 这里的enc_inputs形状是:[batch_size × source_len](source_len是编码端输入句子的长度)

		# 下面这个代码是src_emb进行索引定位,enc_outputs输出形状是[batch_size,src_len,d_model]
		enc_outputs = self.src_emb(enc_inputs)

		# 这里就是位置编码,把两者相加放入到了这个函数里面,从这里可以去看一下位置编码函数的实现:3
		enc_outputs = self.pos_emb(enc_outputs.transpose(0,1)).transpose(0,1)

		# get_attn_pad_mask是为了得到句子中pad的位置信息,给到模型后面,在计算自注意力和交互注意力的时候去掉pad符号的影响,这个函数的实现:4
		enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)
		enc_self_attns = []
		for layer in self.layers:
			# 看EncoderLayer函数:5
			enc_outputs, enc_self_attn = layer(enc_outputs, enc_self_attn_mask)
			enc_self_attns.append(enc_self_attn)
		return enc_outputs, enc_self_attns

代码3:位置编码

在这里插入图片描述

class PositionEncoding(nn.Module):
	def __init__(self, d_model, dropout=0.1, max_len=5000):
		super(PositionEncoding, self).__init__()

		#位置编码的实现很简单,直接对照公式敲代码即可,下面的代码只是其中实现的一种方式;
		#需要注意的是偶数和奇数在公式上有一个共同的部分,我们使用log函数把次方拿下来,方便计算;
		#假设dmodel是512,那么公式里的0,1,...,511代表每一个位置,2i符号中i从0取到了255,那么2i对应取值是0,2,4,...,510
		self.dropout = nn.Dropout(p=dropout)

		pe = torch.zeros(max_len,d_model)
		position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
		div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
		pe[:,0::2] = torch.sin(position * div_term)  # pe[:,0::2],从0开始到最后,步长为2,代表偶数位置
		pe[:,1::2] = torch.cos(position * div_term)  # 代表奇数位置
		# 上面代码获取之后得到的pe:[max_len * d_model]

		#加一个维度的变换,pe形状变成[max_len * 1 * d_model]
		pe = pe.unsqueeze(0).transpose(0,1)

		self.register_buffer('pe', pe)  # 定义一个缓冲区,简单理解为这个参数不更新

	def forward(self, x)
		"""
		x: [seq_len, batch_size, d_model]
		"""
		x = x + self.pe[:x.size(0), :]
		return self.dropout(x)

代码4:get_attn_pad_mask

在这里插入图片描述
在这里插入图片描述

# 比如说,现在句子长度是5,在后面注意力机制的部分,在计算出来QK转置除以根号之后,softmax之前,我们得到的形状
# len_ipuut × len_input 代表每个单词对其余包含自己的单词的影响力
# 所以这里需要有一个同等大小形状的矩阵,告诉哪些位置是PAD,之后在计算softmax之前会把这里设置为无穷大;
# 注意这里得到的矩阵形状[batch_size × len_q × len_k], 对k中的符号进行标识,并没有对q中的做标识,因为没必要
# seq_q和seq_k不一定一致,在交互注意力,q来自解码端,k来自编码端,所以告诉模型编码这边pad符号信息就可以,解码端的pad信息在交互注意力层是没有用到的;

def get_attn_pad_mask(seq_q, seq_k):
	batch_size, len_q = seq_q.size()
	batch_size, len_k = seq_k.size()
	# eq(zero) is PAD token
	pad_attn_mask = sen_k.data.eq(0).unsqueeze(1)
	return pad_attn_mask.expand(batch_size, len_q, len_k)  # [batch_size × len_q × len_k]

代码5:EncoderLayer:多头注意力机制和前馈神经网络

class EncoderLayer(nn.Module):
	def __init__(self):
		super(EncoderLayer, self).__init__()
		self.enc_self_attn = MultiHeadAttention()
		self.pos_ffn = PoswiseFeedForwardNet()
	
	def forward(self, enc_inputs, enc_self_attn_mask):
		# 这个是做自注意力层,输入是enc_inouts,形状是[batch_size × seq_len_q × d_model] 注意最初始得QKV矩阵是等同于这个输入得,看一下enc_self_attn函数:6
		enc_outputs, attn = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs, enc_self_attn_mask) # enc_inputs to same Q,K,V
		enc_outputs = self.pos_ffn(enc_outputs)
		return enc_outputs, attn

代码6:MultiHeadAttention

在这里插入图片描述

class MultiHeadAttention(nn.Module):
	def __init__(self):
		super(MultiHeadAttention, self).__init__()
		#输入进来的QKV是相等的,会使用映射Linear做一个映射得到参数矩阵Wq,Wk,Wv
		self.W_Q = nn.Linear(d_model, d_k * n_heads)
		self.W_K = nn.Linear(d_model, d_k * n_heads)
		self.W_V = nn.Linear(d_model, d_v * n_heads)
		
		

总结:

写模型时应遵循的两个规则:

  1. 从整体到局部:先搭大的框架,再去搭细节的部分;
  2. 搞清楚数据的流动形状:经过这个小部分模型的输入是什么形状,输出是什么形状(下一部分的输入)
  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个示例的Transformer Encoder的代码: ```python import torch import torch.nn as nn class TransformerEncoder(nn.Module): def __init__(self, input_dim, hidden_dim, num_layers, num_heads): super(TransformerEncoder, self).__init__() self.embedding = nn.Embedding(input_dim, hidden_dim) self.positional_encoding = PositionalEncoding(hidden_dim) self.encoder_layers = nn.ModuleList([ TransformerEncoderLayer(hidden_dim, num_heads) for _ in range(num_layers) ]) def forward(self, input): embedded_input = self.embedding(input) encoded_input = self.positional_encoding(embedded_input) for encoder_layer in self.encoder_layers: encoded_input = encoder_layer(encoded_input) return encoded_input class PositionalEncoding(nn.Module): def __init__(self, hidden_dim, max_length=1000): super(PositionalEncoding, self).__init__() self.hidden_dim = hidden_dim self.max_length = max_length self.positional_encoding = self.generate_positional_encoding() def forward(self, input): batch_size, seq_length, _ = input.size() positional_encoding = self.positional_encoding[:seq_length, :].unsqueeze(0).expand(batch_size, -1, -1) return input + positional_encoding def generate_positional_encoding(self): positional_encoding = torch.zeros(self.max_length, self.hidden_dim) position = torch.arange(0, self.max_length, dtype=torch.float).unsqueeze(1) div_term = torch.exp(torch.arange(0, self.hidden_dim, 2).float() * (-math.log(10000.0) / self.hidden_dim)) positional_encoding[:, 0::2] = torch.sin(position * div_term) positional_encoding[:, 1::2] = torch.cos(position * div_term) return positional_encoding class TransformerEncoderLayer(nn.Module): def __init__(self, hidden_dim, num_heads): super(TransformerEncoderLayer, self).__init__() self.multihead_attention = MultiheadAttention(hidden_dim, num_heads) self.feed_forward = FeedForward(hidden_dim) self.layer_norm1 = nn.LayerNorm(hidden_dim) self.layer_norm2 = nn.LayerNorm(hidden_dim) def forward(self, input): attention_output = self.multihead_attention(input) attention_output = self.layer_norm1(input + attention_output) feed_forward_output = self.feed_forward(attention_output) output = self.layer_norm2(attention_output + feed_forward_output) return output class MultiheadAttention(nn.Module): def __init__(self, hidden_dim, num_heads): super(MultiheadAttention, self).__init__() self.hidden_dim = hidden_dim self.num_heads = num_heads self.head_dim = hidden_dim // num_heads self.query_projection = nn.Linear(hidden_dim, hidden_dim) self.key_projection = nn.Linear(hidden_dim, hidden_dim) self.value_projection = nn.Linear(hidden_dim, hidden_dim) self.output_projection = nn.Linear(hidden_dim, hidden_dim) def forward(self, input): batch_size, seq_length, _ = input.size() query = self.query_projection(input) key = self.key_projection(input) value = self.value_projection(input) query = self.split_heads(query) key = self.split_heads(key) value = self.split_heads(value) scaled_attention_scores = torch.matmul(query, key.transpose(-1, -2)) / math.sqrt(self.head_dim) attention_weights = nn.functional.softmax(scaled_attention_scores, dim=-1) attention_output = torch.matmul(attention_weights, value) attention_output = self.combine_heads(attention_output) output = self.output_projection(attention_output) return output def split_heads(self, input): batch_size, seq_length, hidden_dim = input.size() input = input.view(batch_size, seq_length, self.num_heads, self.head_dim) return input.transpose(1, 2) def combine_heads(self, input): batch_size, _, seq_length, hidden_dim = input.size() input = input.transpose(1, 2).contiguous() return input.view(batch_size, seq_length, self.num_heads * self.head_dim) class FeedForward(nn.Module): def __init__(self, hidden_dim): super(FeedForward, self).__init__() self.hidden_dim = hidden_dim self.feed_forward = nn.Sequential( nn.Linear(hidden_dim, 4 * hidden_dim), nn.ReLU(), nn.Linear(4 * hidden_dim, hidden_dim) ) def forward(self, input): return self.feed_forward(input) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值