趋近智
大师班
编码器和解码器堆叠是Transformer架构的主要组成部分,它们通过组合注意力机制和位置编码构建而成。原始的Transformer模型为编码器和解码器都提出了N=6个相同层的堆叠,尽管现代的大型语言模型通常采用更多的层。
编码器的作用是处理输入序列并生成一系列包含丰富上下文信息的表示。编码器堆叠中的每个层接收一系列嵌入向量(或来自上一层的输出)并对其进行变换。一个单独的编码器层由两个主要子层构成:
重要的是,在两个子层周围都使用了残差连接,随后是层归一化。如果 x 是子层(例如,多头注意力或FFN)的输入,并且 子层(x) 是子层自身实现的函数,则子层块的输出是 层归一化(x+子层(x))。这种结构通过促进梯度流动和稳定激活,有助于训练更深的模型。
单个Transformer编码器层内部的流程。
一个编码器层的输出作为下一个相同编码器层的输入。这种堆叠方式使得模型能够逐步构建输入序列的更复杂表示,在不同层面捕获依赖关系。
以下是编码器层结构的一个简化PyTorch表示:
import torch
import torch.nn as nn
class EncoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.self_attn = nn.MultiheadAttention(
d_model,
num_heads,
dropout=dropout,
batch_first=True
)
self.feed_forward = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.ReLU(), # 或 GeLU, SwiGLU 等
nn.Dropout(dropout),
nn.Linear(d_ff, d_model)
)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, src, src_mask=None):
# --- 自注意力块 ---
# Q, K, V 都来自输入 'src'
attn_output, _ = self.self_attn(
src,
src,
src,
key_padding_mask=src_mask,
need_weights=False
)
# 残差连接和层归一化
src = self.norm1(src + self.dropout(attn_output))
# --- 前馈块 ---
ff_output = self.feed_forward(src)
# 残差连接和层归一化
src = self.norm2(src + self.dropout(ff_output))
return src
class Encoder(nn.Module):
def __init__(self, num_layers, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.layers = nn.ModuleList([
EncoderLayer(d_model, num_heads, d_ff, dropout)
for _ in range(num_layers)
])
# 假设输入嵌入(包括位置编码)
# 在外部处理
def forward(self, src, src_mask=None):
for layer in self.layers:
src = layer(src, src_mask)
return src # 最终输出表示
整个编码器堆叠的最终输出是一系列向量,每个输入令牌对应一个,旨在捕获该令牌在序列中的上下文含义。此输出通常由解码器使用。
解码器的作用通常是基于编码后的输入序列和迄今已生成的令牌,一次生成一个令牌的输出序列。与编码器类似,解码器也由N个相同的层堆叠构成。然而,每个解码器层有三个主要子层:
与编码器类似,在这三个子层之后都应用了残差连接和层归一化: 层归一化(x+子层(x))。
单个Transformer解码器层内部的流程,突出了三个子层和输入。
解码器层的一个简化PyTorch结构如下:
import torch
import torch.nn as nn
class DecoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.self_attn = nn.MultiheadAttention(
d_model,
num_heads,
dropout=dropout,
batch_first=True
)
self.encoder_attn = nn.MultiheadAttention(
d_model,
num_heads,
dropout=dropout,
batch_first=True
)
self.feed_forward = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.ReLU(), # 或 GeLU, SwiGLU 等
nn.Dropout(dropout),
nn.Linear(d_ff, d_model)
)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.norm3 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, tgt, memory, tgt_mask=None, memory_mask=None):
# --- 带掩码的自注意力块 ---
# Q, K, V 都来自目标输入 'tgt'
# 'tgt_mask' 防止关注未来的位置
attn_output, _ = self.self_attn(
tgt,
tgt,
tgt,
attn_mask=tgt_mask,
need_weights=False
)
# 残差连接和层归一化
tgt = self.norm1(tgt + self.dropout(attn_output))
# --- 编码器-解码器注意力块 ---
# Q 来自前一个解码器层 ('tgt'),K & V 来自编码器输出 ('memory')
# 'memory_mask' (可选) 遮蔽源序列中的填充
attn_output, _ = self.encoder_attn(
tgt,
memory,
memory,
key_padding_mask=memory_mask,
need_weights=False
)
# 残差连接和层归一化
tgt = self.norm2(tgt + self.dropout(attn_output))
# --- 前馈块 ---
ff_output = self.feed_forward(tgt)
# 残差连接和层归一化
tgt = self.norm3(tgt + self.dropout(ff_output))
return tgt
class Decoder(nn.Module):
def __init__(self, num_layers, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.layers = nn.ModuleList([
DecoderLayer(d_model, num_heads, d_ff, dropout)
for _ in range(num_layers)
])
# 假设目标嵌入(包括位置编码)在外部处理
def forward(self, tgt, memory, tgt_mask=None, memory_mask=None):
for layer in self.layers:
tgt = layer(tgt, memory, tgt_mask, memory_mask)
return tgt # 在线性层/softmax之前的最终输出表示
输入通过整个解码器堆叠后,产生的向量序列表示预测的输出令牌。为了将这些向量转换为目标词汇表上的概率,通常应用一个最终线性层,然后是softmax函数。线性层将解码器输出向量(维度为 dmodel)投射到词汇表大小 (V) 的空间。softmax函数将这些得分(logits)转换为概率,表示词汇表中每个词成为序列中下一个令牌的可能性。
P(yi∣y<i,x)=softmax(线性(解码器输出i))编码器堆叠(处理输入)与解码器堆叠(根据输入和之前输出生成输出)的组合构成了完整的Transformer架构,能够处理各种序列到序列的任务。理解这些堆叠如何由注意力层、前馈层以及归一化和残差连接构成,对于构建和修改这些高效模型非常重要。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造