一、引言Transformer的编码器Encoder是模型最核心的部分它由两个子层堆叠而成多头注意力层Multi-Head Attention让模型关注输入序列中不同位置的信息前馈全连接层Feed-Forward Network对注意力结果进行非线性变换增强特征表达每个子层都配备了残差连接Residual Connection和层归一化Layer Normalization这是保证深层网络可训练的关键设计。本文将从自注意力机制开始逐步实现编码器的每一个组件。二、自注意力机制回顾2.1 数学公式自注意力Self-Attention的核心计算公式QQuery查询向量表示我想关注什么KKey键向量表示我有什么可以被关注VValue值向量表示我的实际内容是什么√d_k缩放因子防止点积过大导致softmax梯度饱和2.2 代码实现def attention(query, key, value, maskNone, dropoutNone): 自注意力计算函数 :param query: [batch_size, seq_len, d_model] :param key: [batch_size, seq_len, d_model] :param value: [batch_size, seq_len, d_model] :param mask: 掩码张量用于遮挡未来位置 :param dropout: 随机失活层 :return: (注意力输出, 注意力权重) d_k query.size(-1) # 获取特征维度 # 1. 计算缩放点积注意力分数 scores torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k) # 2. 掩码处理关键 if mask is not None: # 将遮挡位置设为负无穷softmax后权重趋近于0 scores scores.masked_fill(mask 0, -1e9) # 3. 计算注意力权重在最后一个维度上归一化 p_attn F.softmax(scores, dim-1) # 4. 随机失活可选 if dropout is not None: p_attn dropout(p_attn) # 5. 加权求和得到最终输出 return torch.matmul(p_attn, value), p_attn 重点强调掩码机制掩码Mask是注意力机制中非常重要的概念它的作用是控制每个位置能看到哪些其他位置。# 掩码张量的可视化理解 def dm02_test_triu(size): # 生成上三角矩阵对角线以上为1以下为0 temp np.triu(mnp.ones((1, size, size)), k1).astype(uint8) # 取反得到下三角矩阵对角线以下为1以上为0 return torch.from_numpy(1 - temp)掩码矩阵示例size5# 下三角掩码用于Decoder防止看到未来位置 [[1, 0, 0, 0, 0], [1, 1, 0, 0, 0], [1, 1, 1, 0, 0], [1, 1, 1, 1, 0], [1, 1, 1, 1, 1]]横向列表示当前行位置能看到哪些列位置1不遮挡可以看到0遮挡看不到被设为负无穷三、多头注意力机制Multi-Head Attention3.1 核心思想多头注意力的本质是将词向量在特征维度上切分成多个头每个头独立计算注意力最后拼接起来。这样做的好处多视角每个头可以关注不同的特征子空间并行计算所有头同时计算效率高增强表达能力模型可以学习到更丰富的关系3.2 完整代码实现class MultiHeadAttention(nn.Module): def __init__(self, embed_dim, head, dropout_p0.1): super().__init__() assert embed_dim % head 0 # 必须能整除 self.d_k embed_dim // head # 每个头的维度512/864 self.head head # 4个线性层Q、K、V投影 最终输出 self.linears clones(nn.Linear(embed_dim, embed_dim), 4) self.dropout nn.Dropout(pdropout_p) self.attn None # 保存注意力权重用于可视化 def forward(self, query, key, value, maskNone): if mask is not None: mask mask.unsqueeze(0) # 增加头维度 batch query.size(0) # 1. 线性变换 分头处理 # [batch, seq_len, embed_dim] - [batch, seq_len, head, d_k] - [batch, head, seq_len, d_k] query, key, value [ model(x).view(batch, -1, self.head, self.d_k).transpose(1, 2) for model, x in zip(self.linears, (query, key, value)) ] # 2. 计算多头注意力 x, self.attn attention(query, key, value, mask, self.dropout) # 3. 合并多头结果 # [batch, head, seq_len, d_k] - [batch, seq_len, head, d_k] - [batch, seq_len, embed_dim] x x.transpose(1, 2).contiguous().view(batch, -1, self.head * self.d_k) # 4. 最终线性变换 return self.linears[-1](x)3.3 维度变化详解重点步骤操作形状变化输入query, key, value[2, 4, 512]线性投影分别通过4个Linear层[2, 4, 512]分头view transpose[2, 8, 4, 64]注意力计算attention()输出[2, 8, 4, 64]权重[2, 8, 4, 4]合并头transpose view[2, 4, 512]最终输出第4个Linear层[2, 4, 512] 重点关注transpose(1, 2)的作用# 原始形状[batch, seq_len, head, d_k] # 转置后[batch, head, seq_len, d_k] x.transpose(1, 2)这样做是为了让注意力计算在 seq_len 维度上进行而 head 维度可以并行计算。每个头独立计算注意力互不干扰。四、前馈全连接层Feed-Forward Network4.1 设计原理前馈全连接层是一个两层的全连接网络中间使用ReLU激活函数设计特点输入维度 → 中间维度4倍 → 输出维度例如512 → 2048 → 512中间维度扩大4倍增加模型的非线性表达能力4.2 代码实现class FeedForward(nn.Module): def __init__(self, d_model, d_ff, dropout_p0.1): super().__init__() self.linear1 nn.Linear(d_model, d_ff) # 512 - 2048 self.linear2 nn.Linear(d_ff, d_model) # 2048 - 512 self.dropout nn.Dropout(pdropout_p) def forward(self, x): # 第1层线性变换 x self.linear1(x) # 激活函数 随机失活 x self.dropout(F.relu(x)) # 第2层线性变换回原始维度 x self.linear2(x) return x五、克隆工具函数在Transformer中多个子层结构相同我们使用深拷贝来创建它们def clones(module, N): 克隆N个相同的模块 :param module: 要克隆的模块 :param N: 克隆次数 :return: ModuleList包含N个独立参数的模块 return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])为什么用深拷贝每个模块需要独立的参数不能共享copy.deepcopy()创建完全独立的副本用nn.ModuleList包装便于索引和迭代六、完整测试流程def use_multihead(): # 1. 创建多头注意力层 my_attention MultiHeadAttention(embed_dim512, head8) # 2. 获取输入数据词嵌入 位置编码 position_x use_position() # [2, 4, 512] # 3. 自注意力QKV query key value position_x # 4. 创建掩码8个头每个4x4 mask torch.zeros(8, 4, 4) # 5. 前向传播 result my_attention(query, key, value, mask) print(f多头注意力输出: {result.shape}) # [2, 4, 512] return result def use_ff(): # 1. 获取多头注意力输出 attn_x use_multihead() # [2, 4, 512] # 2. 创建前馈全连接层 my_ff FeedForward(d_model512, d_ff2048) # 3. 前向传播 result my_ff(attn_x) print(f前馈全连接输出: {result.shape}) # [2, 4, 512] return result七、关键知识点总结组件核心操作作用输入形状输出形状自注意力Softmax(QK^T/√d_k)V建立词间依赖关系[b, n, d][b, n, d]掩码masked_fill(mask0, -1e9)控制可见范围[b, n, n][b, n, n]多头注意力分头计算 拼接多视角建模[b, n, d][b, n, d]前馈网络Linear→ReLU→Linear非线性增强[b, n, d][b, n, d]八、面试高频追问Q1为什么Q和K的维度要相同因为要计算点积Q·K^T矩阵乘法要求Q的最后一维等于K的倒数第二维。Q2多头注意力中每个头的维度为什么是d_model / head为了保持总计算量不变。8个头每个64维总维度还是512维并行计算效率高。Q3掩码中的负无穷为什么用-1e9而不是-inf-inf在softmax中可能导致NaN-1e9足够小softmax后权重≈0数值稳定。Q4前馈网络为什么中间维度要扩大4倍这是Transformer论文中的经验设计扩大4倍能提供足够的非线性表达能力同时计算量可控。