news 2026/7/8 9:43:01

Transformer编码器核心组件详解:从自注意力到前馈网络(附PyTorch代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Transformer编码器核心组件详解:从自注意力到前馈网络(附PyTorch代码)

一、引言

Transformer的编码器(Encoder)是模型最核心的部分,它由两个子层堆叠而成:

  1. 多头注意力层(Multi-Head Attention):让模型关注输入序列中不同位置的信息

  2. 前馈全连接层(Feed-Forward Network):对注意力结果进行非线性变换,增强特征表达

每个子层都配备了残差连接(Residual Connection)层归一化(Layer Normalization),这是保证深层网络可训练的关键设计。

本文将从自注意力机制开始,逐步实现编码器的每一个组件。

二、自注意力机制回顾

2.1 数学公式

自注意力(Self-Attention)的核心计算公式:

  • Q(Query):查询向量,表示"我想关注什么"

  • K(Key):键向量,表示"我有什么可以被关注"

  • V(Value):值向量,表示"我的实际内容是什么"

  • √d_k:缩放因子,防止点积过大导致softmax梯度饱和

2.2 代码实现

def attention(query, key, value, mask=None, dropout=None): """ 自注意力计算函数 :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(m=np.ones((1, size, size)), k=1).astype('uint8') # 取反得到下三角矩阵(对角线以下为1,以上为0) return torch.from_numpy(1 - temp)

掩码矩阵示例(size=5):

# 下三角掩码(用于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 Attention)

3.1 核心思想

多头注意力的本质是:将词向量在特征维度上切分成多个"头",每个头独立计算注意力,最后拼接起来

这样做的好处:

  1. 多视角:每个头可以关注不同的特征子空间

  2. 并行计算:所有头同时计算,效率高

  3. 增强表达能力:模型可以学习到更丰富的关系

3.2 完整代码实现

class MultiHeadAttention(nn.Module): def __init__(self, embed_dim, head, dropout_p=0.1): super().__init__() assert embed_dim % head == 0 # 必须能整除 self.d_k = embed_dim // head # 每个头的维度:512/8=64 self.head = head # 4个线性层:Q、K、V投影 + 最终输出 self.linears = clones(nn.Linear(embed_dim, embed_dim), 4) self.dropout = nn.Dropout(p=dropout_p) self.attn = None # 保存注意力权重用于可视化 def forward(self, query, key, value, mask=None): 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 Network)

4.1 设计原理

前馈全连接层是一个两层的全连接网络,中间使用ReLU激活函数:

设计特点

  • 输入维度 → 中间维度(4倍) → 输出维度

  • 例如:512 → 2048 → 512

  • 中间维度扩大4倍,增加模型的非线性表达能力

4.2 代码实现

class FeedForward(nn.Module): def __init__(self, d_model, d_ff, dropout_p=0.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(p=dropout_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_dim=512, head=8) # 2. 获取输入数据(词嵌入 + 位置编码) position_x = use_position() # [2, 4, 512] # 3. 自注意力:Q=K=V 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_model=512, d_ff=2048) # 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(mask==0, -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倍能提供足够的非线性表达能力,同时计算量可控。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/8 9:41:56

chezmoi:跨多台机器安全管理你的 dotfiles

文章目录chezmoi:跨多台机器安全管理你的 dotfileschezmoi:跨多台机器安全管理你的 dotfiles 管理多台开发机器的 dotfiles 一直是件麻烦事。手动复制配置,时间一长就会混乱。chezmoi 是解决这个问题的开源工具,目前在 GitHub 上…

作者头像 李华
网站建设 2026/7/8 9:41:35

精读笔记02_ViT

【3D点云暑期学习】精读笔记(2):ViT — An Image is Worth 16x16 Words 研0暑期进组学习 3D点云多模态方向 论文精读系列第 2 篇 论文信息 标题:An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale作者:Alexey …

作者头像 李华
网站建设 2026/7/8 9:40:08

小白渗透入门实战:90分钟完成SQL注入验证链

1. 这份文档到底是什么,谁该看、怎么看、能解决什么问题“小白渗透入门最全实战文档”——光看标题,很多人第一反应是:又一个教人“黑别人网站”的速成班?其实完全不是。我带过二十多期网络安全方向的新人训练营,也给高…

作者头像 李华