您的位置:首页 > 科技 > IT业 > 企业网站源码生成_网站建设改版_百度搜索风云榜小说总榜_qq群怎么优化排名靠前

企业网站源码生成_网站建设改版_百度搜索风云榜小说总榜_qq群怎么优化排名靠前

2025/3/28 5:49:15 来源:https://blog.csdn.net/rzjslSe/article/details/146402622  浏览:    关键词:企业网站源码生成_网站建设改版_百度搜索风云榜小说总榜_qq群怎么优化排名靠前
企业网站源码生成_网站建设改版_百度搜索风云榜小说总榜_qq群怎么优化排名靠前

【Transformer】多头注意力机制的最后一个线性层的作用是什么?在Transformer的PyTorch实现中,既然已经有“d_model = num_heads * d_k”,为什么还需要self.W_o函数?(附代码)

  • 1. 为何 `d_model = num_heads * d_k` 并不等于不需要 `self.W_o`
  • 2. 具体原因:权重共享和参数学习
    • 进一步解释
  • 3. 输出的线性变换层对应原论文中的过程
  • 4. 为什么不是直接拼接?
  • 5. 总结
  • 6. 代码

最近在自己复现Transformer时,产生了如题的疑惑。我们都熟悉Transformer的注意力机制的架构如下图所示(取自论文原图):
(left) Scaled Dot-Product Attention. (right) Multi-Head Attention consists of several attention layers running in parallel.
这是多头注意力中的一个关键点,这一步可能看起来有些冗余,但实际上它的存在是有意义的。下面我们来从头到尾分析这个过程,看看为什么这一步是必要的,即便 d_model = num_heads * d_k

1. 为何 d_model = num_heads * d_k 并不等于不需要 self.W_o

对于这个问题,如果我们已经有了 d_model = num_heads * d_k,为什么还要用一个额外的线性变换层 self.W_o

  • 本质:虽然 num_heads * d_k 可能等于 d_model,但我们仍然需要这一步线性变换来完成一个重要的任务:对多头注意力的输出应用额外的参数化变换,而不仅仅是直接将拼接后的结果当做最终输出。

2. 具体原因:权重共享和参数学习

即使 num_heads * d_k = d_model,这也不意味着可以直接将拼接后的多头输出作为最终的输出。实际上,多头注意力层中的每个头是在不同的空间中学习特定的表示,而线性变换层 self.W_o 的作用是 将这些来自不同头的信息统一到一个共同的空间,并对其进行学习

进一步解释

  • 每个头的信息:在多头注意力中,多个头的输出是彼此独立的,虽然它们的维度总和是 d_model,但是每个头的特征表示可能会在不同的空间中捕捉到不同的特征(例如,某些头可能聚焦于不同的语法结构,而其他头可能聚焦于不同的语义信息)。

  • 统一信息表示self.W_o 的作用就是将这些信息 映射回统一的表示空间。尽管拼接后的维度是 d_model,但每个头的信息来自不同的空间,所以通过一个线性变换层 W_o,我们可以让模型通过训练学习到如何加权和整合不同头的信息。

3. 输出的线性变换层对应原论文中的过程

在原论文《Attention is All You Need》中,多头注意力机制的最后一步包含了一个线性层,这个层的作用就是将多头输出的信息融合在一起,并转换回模型的维度 d_model。尽管 num_heads * d_k 的维度已经等于 d_model,论文中的线性层不仅仅是为了确保维度匹配,而是为了 通过训练学习如何加权合并每个头的信息

这个过程的核心目的就是 学习一个统一的表示,而不是简单地拼接头的输出。通过加权和整合,模型能够学习到每个头的相对重要性,并决定如何将各个头的信息整合到最终的表示中。

4. 为什么不是直接拼接?

这也不必再多解释了,即使 num_heads * d_k 等于 d_model,如果我们直接拼接多个头的输出并视其为最终的表示,模型就会丧失对如何加权不同头输出的控制。没有 W_o 的线性变换层,模型不能学习到如何最优地融合各个头的信息。每个头的输出应该通过一个学习的权重加以组合,而线性层 self.W_o 提供了这一能力。

5. 总结

虽然 num_heads * d_k = d_model,我们仍然加上 self.W_o 这一线性变换层,因为:

  1. 多头注意力的每个头可能会学习到不同的表示空间,而我们需要将它们统一到一个共同的空间。
  2. self.W_o 为模型提供了一个 可学习的权重,它允许模型通过训练 决定如何加权整合不同头的信息。
  3. 这是原论文中多头注意力最后一层的重要部分,用来将不同头的信息整合并生成最终的输出。

6. 代码

最后附上我个人使用PyTorch实现的多头注意力部分代码:

class MultiHeadAttention(nn.Module):def __init__(self, d_model, num_heads, dropout=0.1):super().__init__()assert d_model % num_heads == 0self.d_model = d_model          # d_model:输入和输出的特征维度,也就是模型的维度。self.num_heads = num_heads      # num_heads:注意力头的数量,它用于并行计算多个注意力表示。self.d_k = d_model // num_heads # 每个注意力头的维度self.W_q = nn.Linear(d_model, d_model)  # 查询 权重矩阵self.W_k = nn.Linear(d_model, d_model)  # 键 权重矩阵self.W_v = nn.Linear(d_model, d_model)  # 值 权重矩阵self.W_o = nn.Linear(d_model, d_model)  # 输出 权重矩阵self.dropout = nn.Dropout(dropout)      # dropout:dropout 的比率,用于防止过拟合。def scaled_dot_product_attention(self, Q, K, V, mask=None):scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) # K.transpose(-2, -1)对矩阵K执行转置操作,交换K的倒数第二个维度和最后一个维度。if mask is not None:scores = scores.masked_fill(mask == 0, -1e9) # 将某些位置的得分(通常是填充位置)设置为非常小的值 -1e9。这是为了在计算注意力分布时,将被掩码的元素的注意力权重压缩到几乎为零,避免影响后续计算。attn = F.softmax(scores, dim=-1) # 归一化,得到注意力权重 attn,确保每一行的注意力权重和为1attn = self.dropout(attn)output = torch.matmul(attn, V) # 加权求和return output, attn# 实现了 多头注意力机制 中的一部分,即 分割头(split heads) 的操作def split_heads(self, x):batch_size, seq_len, _ = x.size()  # x 是输入张量:查询(Q)、键(K)或值(V),形状为 (batch_size, seq_len, d_model) 批次的大小、输入序列的长度、输入的特征维度return x.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) # .view()调整形状并分割为多个头# transpose(1, 2)交换第1和第2维度     (batch_size, seq_len, num_heads, d_k) -> (batch_size, num_heads, seq_len, d_k)def forward(self, Q, K, V, mask=None):# 对查询、键和值分别应用对应的权重矩阵,并通过 split_heads 方法进行分割。此时得到的形状为 (batch_size, num_heads, seq_len, d_k)。# 输入 Q、K 和 V 后通过这些线性层进行更新(为每个输入学习一个新的表示!!),这些变换层会在训练过程中通过梯度更新学习到更合适的表示。Q = self.split_heads(self.W_q(Q))K = self.split_heads(self.W_k(K))V = self.split_heads(self.W_v(V))attn_output, attn_weights = self.scaled_dot_product_attention(Q, K, V, mask)# .transpose(1, 2):在多头注意力计算中,为将多个头的输出拼接成一个大的向量(即 d_model),我们需要确保这些头的信息是按每个位置来排列的,而不是按头排列。# (batch_size, num_heads, seq_len, d_k) -> (batch_size, seq_len, num_heads, d_k):通过交换,可以让每个位置(seq_len)具有 num_heads 个头的输出信息。# .contiguous(): 在 PyTorch 中,当你对张量进行切片或转置后,该张量可能在内存中不再是连续存储的。调用 .contiguous() 会返回一个在内存中连续存储的新张量。这是为了确保后续调用 .view() 方法不会出错。# .view():attn_output.size(0) 是批次大小 (batch_size);-1 表示自动推断;这使得每个位置都有一个长度为 d_model 的特征向量(前面操作方便view这一步!!!)attn_output = attn_output.transpose(1, 2).contiguous().view(attn_output.size(0), -1, self.d_model)# .W_o(attn_output):attn_output是(batch_size, seq_len, num_heads * d_k),需将其转换回原始的模型维度 d_model(=num_heads * d_k)# 更重要的是:线性变换层 self.W_o 将这些来自不同头的信息统一到一个共同的空间,并对其进行学习,学到如何加权和整合不同头的信息。output = self.W_o(attn_output)return output

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com