LLM--Transformer复现

张开发
2026/4/8 17:33:45 15 分钟阅读

分享文章

LLM--Transformer复现
论文精度【论文精度】Transformer—大模型基石-CSDN博客文章目录词嵌入与位置编码词嵌入位置编码多头注意力缩放点击注意力多头注意力机制前馈神经网络(FFN)编码层解码层搭建Transformer架构测试参考资料输入数据: [batch_size, seq_len]输出数据: [batch_size, seq_len]词嵌入与位置编码词嵌入数据维度变化(x)[batch_size, seq_len]--[batch_size, seq_len, d_model]词嵌入将词表中每个词映射到一个统一的空间中词嵌入矩阵如下行每一行代表一个词列词嵌入维度**注意**词嵌入矩阵是训练得到的权重矩阵是可学习的。代码实现classEmbeddings(nn.Module):def__init__(self,vocal_size,d_model):super(Embeddings,self).__init__()# 词嵌入self.embeddednn.Embedding(vocal_size,d_model)self.d_modeld_modeldefforward(self,x):# 这里要注意乘以math.sqrt(d_model)returnself.embedded(x)*math.sqrt(self.d_model)**难点self.embedded(x)*math.sqrt(self.d_model)****原因**放大词嵌入的数值使其方差与位置编码的方差大致相当从而在模型训练初期保持两者重要性的平衡。位置编码编码公式数据维度变化(x)[batch_size, seq_len, d_model] - [batch_size, seq_len, d_model]代码实现classPositionalEncoding(nn.Module):def__init__(self,d_model,dropout,max_len5000): d_model: 词嵌入维度 dropout: dropout层概率 max_len: 表示支持输入最大的序列长度 # 词嵌入self.dropoutnn.Dropout(pdropout)# 初始化pe矩阵用来存储位置编码petorch.zeros(max_len,d_model)# 生成位置序列positiontorch.arange(0.,max_len).unsqueeze(1)# [0..max_len] -- [max_len, 1]# 运用幂运算div_termtorch.exp(torch.arange(0.,d_model,2)*-(math.log(10000.0)/d_model))# 计算位置编码pe[:,0::2]torch.sin(position*div_term)pe[:,1::2]torch.cos(position*div_term)# 增加维度-- 1 x max_len x embeddingpepe.unsqueeze(0)# 将pe设置为持久状态 -- 不会做为参数训练self.register_buffer(pe,pe)defforward(self,x):# x[batch_size, seq_len, d_model]xxself.pe[:,:x.size(1),:]returnself.dropout(x)代码实现有些复杂参数解释max_len代表的表示支持输入最大的序列长度这里默认设置为5000self.dropout nn.Dropout(pdropout)、self.dropout(x)作用在论文中提到有利于模型的泛化pe torch.zeros(max_len, d_model)这一步就是初始化位置编码矩阵维度[max_len, d_model]max_len输入序列最大长度d_model嵌入维度。position torch.arange(0., max_len).unsqueeze(1)作用位置标注信息对应公式中pos参数维度[0…max_len]一维 -- [max_len, 1] 二维为了广播计算具体可看6div_term torch.exp(torch.arange(0., d_model, 2) * -(math.log(10000.0) / d_model))这里有一定理解有一定难度推到如图position * div_term这里包含广播机制pe pe.unsqueeze(0), x x self.pe[:, :x.size(1), :]这里也涉及到广播机制即多头注意力缩放点击注意力$ Attention(Q, K, V)softmax(\frac{Q K^{T}}{\sqrt{d_{k}}}) V $数据维度(x)[batch_size, n_heads, seq_len, d_k]--[batch_size, n_heads, seq_len, d_k]注意缩放点击注意力也称为“自注意力”在transformer中是多头注意力的基石故在数据维度中数据是先传入多头注意力中多头注意力将数据** ****font stylecolor:#DF2A3F;[batch_size, seq_len, d_model] -- [batch_size, seq_len, n_heads, d_k] -- [batch_size, n_heads, seq_len, d_k]/font**后传入自注意力中。代码classScaleDotProductAttention(nn.Module):def__init__(self):super(ScaleDotProductAttention,self).__init__()self.softmaxnn.Softmax(dim-1)# 归一化权重defforward(self,q,k,v,maskNone): q k v shape: [batch_size, n_heads, seq_len, d_k] n_heads: 头数 d_k: 头维度 d_kq.size(-1)k_tk.transpose(-2,-1)scores(q k_t)/math.sqrt(d_k)ifmaskisnotNone:scoresscores.masked_fill(mask0,-1e9)# softmax归一化scoresself.softmax(scores)# 计算注意力vscores vreturnv,scores# 返回注意力注意力得分难点解释k_t k.transpose(-2, -1)scores (q k_t) / math.sqrt(d_k)q、k维度font stylecolor:#DF2A3F;[batch_size, n_heads, seq_len, d_k]/font,q x k^T故需要将k的最后两个维度转置即k -- [batch_size, n_heads, d_k, seq_len]此时[batch_size, n_heads, seq_len, d_k] * [batch_size, n_heads, d_k, seq_len] [batch_size, n_heads, seq_len, seq_len]scores scores.masked_fill(mask 0, -1e9)掩码的操作对象注意力分数掩码用处在encoder输入的时候由于在nlp数据中对句子进行数字化的时候由于句子长短不一往往要对句子进行填充使得句子长度一样一般是填充为0但是在encoder的注意力中这是填充的数据不应该赋予权重故需要赋予一个很小的数字这样在softmax中对应权重几乎为0在decoder中被称为“因果掩码”为了保证在decoder输入的时候t时刻的数据只能看到t时刻以前的数据不能看到t时刻之后的数据。多头注意力机制$ MultiHead(Q, K, V) Concat(head_{1}, \ldots, head_{h}) W^{O} $数据维度(x)x分别乘以一个权重矩阵WQ、WK、WV得到q(query)、k(key)、v(value)矩阵WQ、WK、WV是可学习的此时qkv[batch_size, seq_len, d_model]WQ、WK、WV[d_model, d_model]然后在d_model的维度上进行才分d_model n_heads * n_kn_heads为拆分头数此时q、k、v[batch_size, seq_len, n_heads, d_k]对每个拆分的头分别进行自注意力机制计算最后结果在d_model进行合并[batch_size, seq_len, n_heads, d_k] --[batchs_size, seq_len, d_models]代码classMultiHeadAttention(nn.Module):def__init__(self,d_model,n_head):super(MultiHeadAttention,self).__init__()self.n_headn_head self.attentionScaleDotProductAttention()# 生成WQ WK WVself.w_qnn.Linear(d_model,d_model)self.w_knn.Linear(d_model,d_model)self.w_vnn.Linear(d_model,d_model)# 合并self.w_concatnn.Linear(d_model,d_model)defforward(self,q,k,v,maskNone):# q、k、w 维度 [batch_size, seq_len, d_model]q,k,vself.w_q(q),self.w_k(k),self.w_v(v)# 拆分头q,k,vself.split(q),self.split(k),self.split(v)# 得到注意力分数out,attentionself.attention(q,k,v,maskmask)# 合并outself.concat(out)returnout# 拆多头defsplit(self,x):# x: [batch_size, seq_len, d_model]batch_size,seq_len,d_modelx.size()# 拆维度d_kd_model//self.n_head# 拆分view-- [batch_size, seq_len, n_head, d_k] -- [batch_size, n_head, seq_len, d_k]tensorx.view(batch_size,seq_len,self.n_head,d_k).transpose(1,2)returntensor# 多头进行拼接defconcat(self,x): x: [batch_size, n_head, seq_len, d_k] return [batch, seq_len, d_model] batch_size,n_head,seq_len,d_kx.size()# 还原d_modeld_modeln_head*d_k# .contiguous() 强制将张量内存变为连续存储xx.transpose(1,2).contiguous().view(batch_size,seq_len,d_model)returnx前馈神经网络(FFN)展开图**数据维度(**x)[batch_size, seq_len, d_model] --[batch_size, seq_len, d_model]代码classFeedForward(nn.Module):def__init__(self,d_model,hidden,dropout0.1):super(FeedForward,self).__init__() d_model -- hidden -- d_model # 线性层self.linear1nn.Linear(d_model,hidden)self.linear2nn.Linear(hidden,d_model)self.relunn.ReLU()self.dropoutnn.Dropout(pdropout)defforward(self,x):xself.linear1(x)xself.relu(x)xself.dropout(x)xself.linear2(x)returnx分析两层线性层第一层x - linear(d_model **升维到 **hidden) - ReLU激活函数第二层ReLU - linear(hidden降维到d_model)****在transformer论文上d_model是512hidden是2048编码层编码层就是将注意力机制和前馈神经网络连接起来同时采用“残差连接”和归一化数据维度(x)[batch_size, seq_len, d_model] -- [batch_size, seq_len, d_model]代码classEncoderLayer(nn.Module):def__init__(self,d_model,n_heads,ff_hidden,dropout0.1):super(EncoderLayer,self).__init__()# 多头注意力self.attentionMultiHeadAttention(d_model,n_heads)# 归一化self.norm1nn.LayerNorm(d_model)# 前馈self.feedforwardFeedForward(d_model,ff_hidden,dropout)# 归一化self.norm2nn.LayerNorm(d_model)self.dropoutnn.Dropout(dropout)defforward(self,x,src_mask):# 多头注意力out1self.attention(x,x,x,src_mask)xxself.dropout(out1)# 残差连接xself.norm1(x)# 前馈神经网络out2self.feedforward(x)xxself.dropout(out2)# 残差连接xself.norm2(x)returnx难点分析self.attention(x, x, x, src_mask)q、k、v矩阵生成方法x分别乘以WQ、WK、WVx x self.dropout(out1)残差连接transformer在注意力和前馈神经网络中大量采用了残差连接。解码层依然是多头注意力、前馈神经网络和他们的残差连接但是要注意Q、K、V来源这里有两个多头注意力数据维度(x)[batch_size, seq_len, d_model] -- [batch_size, seq_len, d_model]代码classDecoderLayer(nn.Module):def__init__(self,d_model,n_heads,ff_hidden,dropout0.1):super(DecoderLayer,self).__init__()# 两个注意力机制self.dec_attMultiHeadAttention(d_model,n_heads)# 编码-编码self.enc_decMultiHeadAttention(d_model,n_heads)# 编码-解码self.feedforwardFeedForward(d_model,ff_hidden,dropout)# 前馈神经网络# 归一化self.norm1nn.LayerNorm(d_model)self.norm2nn.LayerNorm(d_model)self.norm3nn.LayerNorm(d_model)self.dropoutnn.Dropout(pdropout)defforward(self,x,enc_out,reason_mask,dec_mask):out1self.dec_att(x,x,x,reason_mask)xxself.dropout(out1)xself.norm1(x)out2self.enc_dec(x,enc_out,enc_out,dec_mask)xxself.norm2(out2)xself.norm2(x)out3self.feedforward(x)xxself.dropout(out3)xself.norm3(x)returnx难点分析font stylecolor:#DF2A3F;out1 self.dec_att(x, x, x, reason_mask)/fontfont stylecolor:#DF2A3F;out2 self.enc_dec(x, enc_out, enc_out, dec_mask)/font这两个注意力机制的输入一个叫做“因果注意力机制”一个叫做“交叉注意力机制”输入来源Q来源于decoder输出K、V来源于encoder输出搭建Transformer架构代码classTransformer(nn.Module):def__init__(self,vocab_size,d_model,n_heads,n_encoder_layers,n_decoder_layers,ff_hidden,dropout0.1):super(Transformer,self).__init__()# 嵌入self.embeddingEmbeddings(vocab_size,d_model)# 位置编码self.position_codingPositionalEncoding(d_model,dropout)# 编码器self.encodernn.ModuleList([EncoderLayer(d_model,n_heads,ff_hidden,dropout)for_inrange(n_encoder_layers)])# 解码器self.decodernn.ModuleList([DecoderLayer(d_model,n_heads,ff_hidden,dropout)for_inrange(n_decoder_layers)])# 映射 -- vocab_sizeself.fc_outnn.Linear(d_model,vocab_size)# dropoutself.dropoutnn.Dropout(pdropout)defforward(self,src,trg,src_mask,trg_mask):# 编码器层输入srcself.embedding(src)srcself.position_coding(src)# 解码器输入trgself.embedding(trg)trgself.position_coding(trg)# 编码器forlayerinself.encoder:srclayer(src,src_mask)# 解码器forlayerinself.decoder:trglayer(trg,src,trg_mask,src_mask)# 最后输出层outself.fc_out(trg)returnout难点**self.fc_out nn.Linear(d_model, vocab_size); out self.fc_out(trg); **最后输出的时候需要将d_model -- vocab_size用来预测词测试vocab_size10000# 词表大小d_model512# 嵌入维度n_heads8# 多头注意力机制中拆分头数n_encoder_layers6# 编码层数n_decoder_layers6# 解码层数ff_hidden2048# 隐藏层dropout0.1transformer_modelTransformer(vocab_size,d_model,n_heads,n_encoder_layers,n_decoder_layers,ff_hidden,dropout)# 模拟数据srctorch.randint(0,vocab_size,(32,10))# [batch_size, seq_len]trgtorch.randint(0,vocab_size,(32,20))# [batch_size, seq_len]src_mask(src!0).unsqueeze(1).unsqueeze(2)# [batch_size, 1, 1, seq_len], 可以方便广播trg_mask(trg!0).unsqueeze(1).unsqueeze(2)outtransformer_model(src,trg,src_mask,trg_mask)print(out.shape)输出参考资料https://zhuanlan.zhihu.com/p/18910815723058464954 【Transformer 模型复现 - K同学啊 | 小红书 - 你的生活兴趣社区】 PZsgnugn1v10lHI https://www.xiaohongshu.com/discovery/item/66d110a4000000001d038650?sourcewebsharexhssharepc_webxsec_tokenABezmXTI4LZU5nWeCDdSTw-DXbAegOrYc16uH6nayHcegxsec_sourcepc_share30 【面试官写一下 Transformer Encoder - 小森学AI | 小红书 - 你的生活兴趣社区】 EjOsocJ5WNJSzDD https://www.xiaohongshu.com/discovery/item/67ff3910000000000b01e8bf?sourcewebsharexhssharepc_webxsec_tokenAB7qgPs1HgxBH_KsuTEZ0_iALG2haVqln1BuqjFRZu7XQxsec_sourcepc_share

更多文章