LK 博客
Transformer编码器与解码器主干部分
大数据
约 1 分钟阅读 1 赞 0 条评论 鸿蒙黑体

Transformer编码器与解码器主干部分

zihan
王子翰 @zihan
累计点赞 1 登录后每个账号只能点一次
内容长度 0 正文词元数
正文
目录会跟随阅读位置移动。
阅读进度

1. Encoderlayer

class Encoderlayer(nn.Module):
  def __init__(self,d_model,n_heads,d_ff,dropout=0.1):
      super().__init__()
      #多头注意力机制
      self.attn = MultiHeadAttention(d_model,n_heads,dropout)

      #前馈神经网络
      self.ffn = FeedForward(d_model,d_ff,dropout)

  def forward(self,src,src_mask,mask=None):
      # src 输入序列向量,形状 batch ,seq_len,d_model
      # src_mask 屏蔽padding的位置,避免模型关注无效的token,在decoder中 masker用来防止看到未来的词,以提高模型思考能力
      # Q,K,V +src 对输入序列本身进行自注意力计算
      out,attn = self.attn(src,src,src,mask)
      # 经过 MoE 前馈层,让门控网络选择专家参与计算
      out = self.ffn(out)

      return out

作用:

  • 表示编码器中的一层。

  • 由“自注意力 + 前馈网络”组成。

它做了什么:

  1. 输入 src 先经过自注意力:

out, attn = self.attn(src, src, src, mask)

  1. 再经过前馈网络:

out = self.ffn(out)

这个模块的核心意义:

  • 让输入序列中的每个 token 融合整句上下文信息。

2. DecoderLayer

class DecoderLayer(nn.Module):
  def __init__(self,d_model,n_heads,d_ff,dropout=0.1):
      super().__init__()
      # 先加上带有掩码的掩码自注意力机制,让模型逐渐知道信息的关系
      # mask多头自注意力机制,输入tgt
      self.attn = MultiHeadAttention(d_model, n_heads, dropout)
      # 交叉注意力,和encoder做交互
      # 输入 Q=当前注意力的输出,K和V来自编码器的memory(原序列上下文信息)
      self.ctoss_attn = MultiHeadAttention(d_model, n_heads, dropout)
      # 前馈神经网络,提升模型的表达能力
      self.ffn = FeedForward(d_model, d_ff, dropout)

  def forward(self,tgt,memory,tgt_mask = None,memory_mask = None):
      # tgt:目标序列 memory:编码器输出
      # tgt——mask:屏蔽未来的token,memory_mask:PAD做掩码
      out, attn = self.attn(tgt, tgt, tgt, tgt_mask)
      out, _ = self.ctoss_attn(out, memory, memory, memory_mask)
      out = self.ffn(out)
      return out

作用:

  • 表示解码器中的一层。

  • 由“带 mask 的自注意力 + 交叉注意力 + 前馈网络”组成。

它做了什么:

  1. 先对目标序列做带 mask 的自注意力,防止偷看未来:

out, attn = self.attn(tgt, tgt, tgt, tgt_mask)

  1. 再和编码器输出 memory 做交叉注意力:

out, _ = self.ctoss_attn(out, memory, memory, memory_mask)

  1. 最后经过前馈网络:

out = self.ffn(out)

这个模块的核心意义:

  • 一边看已经生成的内容,一边看输入句子的上下文,逐步预测下一个 token。

3. PositionalEncoding

#创建位置编码
class PositionalEncoding(nn.Module):
  def __init__(self,d_model,max_len):
      super().__init__()
      # d_model 每个词向量的维度 :max_len:句子的最大长度
      # 初始化位置编码矩阵 形状为max_len,d_model
      pe = torch.zeros(max_len,d_model)

      # 定义记录每一个token项链改动位置索引 从0到max_len-1
      # [max_1,1] 方便和缩放因子进行相处
      position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
      # div_term 每个维度得到缩放因子,torch.arange(0, d_model, 2):生成偶数索引 0,2,4,对应公式就是2i
      # torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model) = (2i/d_model)*-ln(10000.0)
      div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))

      # 对偶数进行sin(pos * 缩放因子)得到位置编码值
      pe[:, 0::2] = torch.sin(position * div_term)
      # 对奇数进行cos(pos * 缩放因子)得到位置编码值
      pe[:, 1::2] = torch.cos(position * div_term)
      #增加batch维度.1,max_len,d_model,方便后续与输入embedding进行相加
      pe = pe.unsqueeze(0)
      # 注册为buffer,把位置编码pe加载到模型里面,但是不参与训练
      self.register_buffer('pe', pe)

  def forward(self, x):

      # X: 输入的embedding 形状batch,seq_len, d_model
      seq_len = x.size(1)
      # 每个token的embedding加上对应的位置编码
      # self.pe[;,;seq_len,;]:取出前seq_len个位置, 形状会变成1,seq_len,d_model可以和输入x对齐
      return self.pe[:, :seq_len, :] + x

作用:

  • 给 embedding 加上位置信息。

  • 因为注意力本身不带顺序感,所以需要额外注入“第几个 token”。

它做了什么:

  • sincos 生成不同位置的编码向量。

  • 再把位置编码加到 embedding 上。

这个模块的核心意义:

  • 让模型知道词序。

4. Encoder

class Encoder(nn.Module):
  def __init__(self,vocab_size,d_model,n_heads,num_layer,d_ff,dropout=0.1,max_len=5000):
      super().__init__()
      # 词嵌入层,vocab_size:词表大小,包含不同的token总数
      # 将输入的token ID(对原始文本分词得到词表, 不同词对应不同ID)准换成连续向量, 维度为d_model
      self.embedding = nn.Embedding(vocab_size,d_model)
      # 位置编码加入序列中的token的位置信息
      self.pos_encoding = PositionalEncoding(d_model,max_len)

      # 构建编码器的堆叠结构
      self.layers = nn.ModuleList([
          Encoderlayer(d_model,n_heads,d_ff,dropout)for _ in range(num_layer)
      ])

  def forward(self,src,src_mask=None):

      # 将输入 token ID 转换成 embedding 向量
      # 输出结构 shape batch,seq_len,d_model
      # 加上 sqrt(d_model),进行缩放,让后续注意力计算更稳定
      out = self.embedding(src) * math.sqrt(self.embedding.embedding_dim)
      # 经过位置编码
      out = self.pos_encoding(out)
      # 逐层经过encoderlayer
      for layer in self.layers:
          out = layer(out,src_mask,src_mask)

      return out

作用:

  • 把输入序列编码成上下文表示。

它做了什么:

  1. token id 先经过 embedding

  2. 乘以 sqrt(d_model) 做缩放。

  3. 加上位置编码。

  4. 逐层通过多个 Encoderlayer

输出:

  • 一个上下文化后的表示 memory,供解码器使用。

5. Decoder

class Decoder(nn.Module):
  def __init__(self,vocab_size,d_model,n_heads,num_layer,d_ff,dropout=0.1,max_len=5000):
      super().__init__()
      # 将目标序列的 token id 转换为向量 维度为 d_model
      self.embedding = nn.Embedding(vocab_size,d_model)
      # 经过位置编码
      self.pos_encoding = PositionalEncoding(d_model,max_len)
      # 定义解码器列表
      self.layers = nn.ModuleList([
          DecoderLayer(d_model,n_heads,d_ff,dropout)for _ in range(num_layer)
      ])
      # 最后一个全连接输出映射回原词汇表的大小, 从而得到每个token推测分布(最后的softmax自动有了所以不用加)
      self.fc = nn.Linear(d_model,vocab_size)


  def forward(self,tgt,memory,tgt_mask = None,memory_mask = None):
      # tgt 目标序列 解码器的输入 memory编码器的输出 也叫上下文信息
      # tgt_mask 目标序列的mask 用来屏蔽未来的位置 memory_mask:用来屏蔽pad
      out = self.embedding(tgt) * math.sqrt(self.embedding.embedding_dim)
      #添加位置编码
      out = self.pos_encoding(out)
      #逐层经过 decoderlayer
      for layer in self.layers:
          out = layer(out,memory,tgt_mask,memory_mask)

      return self.fc(out)

作用:

  • 根据目标序列前文和编码器输出,生成每个位置对词表的预测分数。

它做了什么:

  1. tgt 先经过 embedding

  2. 加上位置编码。

  3. 通过多个 DecoderLayer

  4. 最后通过线性层 fc 投影到词表大小。

输出形状通常是:


(batch_size, seq_len, vocab_size)

6. Transformer

class Transformer(nn.Module):
  def __init__(self,
               src_size,  # 原语言词表大小
               tgt_size,  #目标语言词表大小
               d_model=512,  #embedding向量维度
               n_heads=8, #多头注意力头数
               num_encoder_layers=6, #编码器层数(循环次数)
               num_decoder_layers=6, #解码器层数(循环次数)
               d_ff=2048,  #前馈神经网络隐藏维度
               dropout=0.1,
               max_len=5000):
      super().__init__()

      self.encoder = Encoder(
          src_size,d_model,n_heads,num_encoder_layers,d_ff,dropout,max_len
      )
      self.decoder = Decoder(
          tgt_size,d_model,n_heads,num_decoder_layers,d_ff,dropout,max_len
      )
  def forward(self,src,tgt,src_mask=None,tgt_mask=None,memory_mask=None):

      memory = self.encoder(src,src_mask)

      out = self.decoder(tgt,memory,tgt_mask,memory_mask)

      return out

作用:

  • 把整个 Encoder 和 Decoder 组装成完整模型。

它做了什么:

  1. 先用编码器处理 src

memory = self.encoder(src, src_mask)

  1. 再用解码器处理 tgt

out = self.decoder(tgt, memory, tgt_mask, memory_mask)

这个模块的核心意义:

  • 是整个模型的统一入口。

7.  FeedForward

作用:

  • 对每个 token 的表示单独做非线性变换。

  • 不负责 token 和 token 之间的信息交互,而是增强单个 token 的特征表达能力。

结构:


Linear(d_model -> d_ff)

ReLU

Linear(d_ff -> d_model)

Dropout

Residual + LayerNorm

这个模块的核心意义:

  • 注意力负责“看别人”,前馈层负责“更新自己”。

作者名片

zihan
王子翰
@zihan

这个作者暂时还没有填写个人简介。

评论区
文章作者和管理员都可以管理这里的评论。
0 条评论
登录后即可参与评论。 去登录
还没有评论,欢迎留下第一条交流内容。