PyTorch深度学习实战 |多头注意力机制

简介: 摘要:本文详细介绍了Transformer中的多头自注意力机制,从整体结构到实现细节,包括四部分内容:(1)多头自注意力的基本架构;(2)内部计算流程解析;(3)注意力计算公式详解;(4)代码实现。重点阐述了多头并行的计算方式、缩放点积注意力的计算步骤(QK转置、缩放、softmax和加权求和),以及残差连接和层归一化的作用。通过"Are you OK?"示例展示了输入张量如何经过8个64维注意力头处理后拼接成512维输出。文章最后提供了完整的PyTorch实现代码,并附测试用例验证模型

多头自注意力

多头自注意力的本质,

     是“由多个线性层构成的复杂组合体”:通过多头对应的多个线性层对输入x进行特征变换,同时

借助注意力机制精准捕捉序列内部元素间的关联信息,最终输出与输入x尺寸完全一致的特征张量

x'。

image.gif

下面我们将从四个方面,系统介绍Transformer的多头自注意力机制:

(1)多头自注意力的整体结构;

(2)多头自注意力的内部细节;

(3) Attention计算公式的深入解析;

(4) 核心逻辑的代码实现。

多头自注意力的整体结构

观察Transformer的整体架构可发现,其包含3个以橙色标记的多头注意力模块:左侧编码器中设有

1个,右侧解码器中设有2个。从核心实现逻辑来看,这3个多头注意力模块的底层机制完全一致,

差异主要体现在输入数据的来源与掩码(Mask)的应用方式上。为更清晰地解析多头注意力的具

体运作,接下来我们将以解码器中的多头注意力模块为核心展开说明。

image.gif

         看多头注意力模块的话,能看到它有3个输入和1个输出。这3个输入咱们叫查询(Q)、键

(K)、值(V),其实就是三组数据(张量)。这三组数据可以一样,也可以不一样,不用不用

太纠结它们名字的具体意思,就当是三组输入数据就行。 再说说Transformer编码器里的情况:这

Q、K、V三组数据都是从同一个输入来的。具体来说,输入数据先经过词嵌入层和位置编码处理,

得到一组综合后的特征;然后这组特征会分成3路,分别作为Q、K、V输入到多头注意力里去。

image.gif

     在编码器里,Q、K、V 其实指的都是同一个张量,咱们就叫它 x 吧。把 x 放进多头注意力

里,能算出一个结果,就叫 multihead (x)。另外,x 还会直接从左边走,送到 Add&Norm 那一

步。这里的 Add 就是残差连接,简单说就是把 x 和刚才算出来的 multihead (x) 加起来;Norm 就

是层归一化,会把加完的结果处理一下,让数值更稳定。所以最后从 Add&Norm 出来的,就是

“x 加上 multihead (x) 之后,再经过层归一化的结果”。

image.gif

多头自注意力的内部细节

      多头的意思,就是同时用多个自注意力机制来提取特征。下面拿个例子说说它是怎么算的。

首先,我们的输入数据是加了位置编码的“Are you OK?”,就叫它x吧。这个x会分别经过3个线性

层(简单说就是做些数学变换),得到三组结果,咱们管它们叫qx、kx、vx(也就是Q、K、

V)。 接下来,把这三组结果放进注意力的计算公式里一算,得到的输出y,就把这句话里各个词

之间的全局关系(比如谁和谁关联紧密)都融合进去了。

image.gif

Attention 计算公式的深入理解

image.gif 编辑

咱们假设输入的句子是 “Are you OK?”,对应的序列就叫 X。把 X 分别放进 qnet、knet、vnet 这

三个结构里计算,最后会得到 Q、K、V 三组数据(也就是张量)。这三组数据的尺寸都是 4×3:

其中 4 代表句子里的 4 个单词,3 代表每个单词都用 3 个维度来表示。

image.gif

首先,我们要把Q和K的转置乘起来。你看,Q是个4×3的矩阵,K转置之后是3×4的矩阵,这俩乘

完之后,会得到一个4×4的矩阵。 这个4×4的矩阵其实就代表了输入句子里所有单词之间的关系。

比如说,矩阵里的每一行对应一个单词,每一列也对应一个单词,那行和列交叉的那个数值,就表

示这两个单词之间的关联程度。

image.gif

下面我们需要对计算后的结果进行缩放,缩放操作可以调节点积计算的尺度,缩放后的结果上会继

续 应用softmax的函数,将词语之间的相互关系转换成概率分布,假设这矩阵是P,矩阵的每一行

代码了该单词和其它单词的关系,这个关系是一个概率分布,假设这个矩阵的第一行是下图这样,

就说明are和ok之间的相互作用和语义联系就是最强的。

image.gif

最后一步是把矩阵 P 和矩阵 V 相乘,这个操作相当于给 V 里的每个单词 “加上上下文的重要性”。

概率矩阵 P 是 4×4 的,它的每一行对应一个单词,行里的每个数代表这个单词和其他单词的相关

程度;而矩阵 V 是 4×3 的,对应 4 个单词、每个单词 3 个维度。这俩相乘后得到的结果还是 4×3

的:每一行对应原来的一个单词,每一列对应一个维度。但这时候,V 里的每个单词都已经融合了

上下文的重要性。

image.gif

多头注意力的整体内容

   假设我们的输入还是Are you OK? 每个单词都是512维度的向量,那么我的输入X就是一个4*512

的张量,原始论文中有8个注意力层,所以单个注意力的输出的维度就是512/8=64维。所以单个注

意力的输出就是4*64大小的张量。最后我们把这8个4*64大小的张量按照列进行拼接,最后会得到

4*512的输出张量。

image.gif


四种注意力机制

咱们看右边的解码器,里面有个多头注意力和别的不一样 —— 它多了个 “Mask”(掩码),这就是

掩码注意力。还有个特殊情况:在编码器和解码器连接的地方,K 矩阵和 V 矩阵都是从编码器来

的,只有 Q 矩阵是解码器自己的,这就是交叉注意力。

image.gif

咱们先说说掩码注意力,它的计算其实就是在原来的基础上,多了一步“加矩阵”的操作——加的这

个矩阵就是M(掩码矩阵),它的样子很特别:主对角线下面的元素全是0,主对角线上面的全是

负无穷大。 具体做法就是,把这个带负无穷大的掩码矩阵,和之前算出来的相似度矩阵,对应位

置的元素一个个加起来。 效果也很直接:如果掩码矩阵里某个位置是负无穷大,那相似度矩阵对

应位置的数值就会被改掉;要是掩码矩阵里是0,那相似度矩阵原来的数值就保持不变。

image.gif

代码实现

import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadSelfAttention(nn.Module):
    def __init__(self, d_model=64, num_heads=8):
        """
        初始化多头自注意力
        :param d_model: 输入/输出的特征维度(必须能被 num_heads 整除)
        :param num_heads: 头的数量
        """
        super().__init__()
        assert d_model % num_heads == 0, "d_model 必须能被 num_heads 整除"
        self.d_model = d_model  # 整体特征维度
        self.num_heads = num_heads  # 头数
        self.d_k = d_model // num_heads  # 每个头的特征维度(d_model / num_heads)
        # 1. 定义3个线性层,分别生成 Q、K、V(输入输出维度都是 d_model)
        self.w_q = nn.Linear(d_model, d_model)  # Q的线性变换
        self.w_k = nn.Linear(d_model, d_model)  # K的线性变换
        self.w_v = nn.Linear(d_model, d_model)  # V的线性变换
        # 2. 定义输出的线性层(拼接多头后做最终变换)
        self.w_o = nn.Linear(d_model, d_model)
        # 层归一化(用于残差连接后)
        self.layer_norm = nn.LayerNorm(d_model)
    def scaled_dot_product_attention(self, q, k, v, mask=None):
        """
        缩放点积注意力(单个头的注意力计算)
        :param q: 单个头的Q (batch_size × seq_len × d_k)
        :param k: 单个头的K (batch_size × seq_len × d_k)
        :param v: 单个头的V (batch_size × seq_len × d_k)
        :param mask: 掩码(可选,batch_size × seq_len × seq_len),0/False 表示遮挡
        :return: 注意力输出 + 注意力权重
        """
        # 第一步:Q·K^T,计算相似度(batch_size × seq_len × seq_len)
        scores = torch.matmul(q, k.transpose(-2, -1))  # k.transpose(-2,-1) 交换最后两个维度(seq_len 和 d_k)
        # 第二步:缩放(除以 sqrt(d_k),避免数值过大)
        scores = scores / torch.sqrt(torch.tensor(self.d_k, dtype=torch.float32))
        # 第三步:应用掩码(如果有)—— 遮挡位置设为负无穷,softmax后为0
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)  # mask==0 的位置填 -1e9
        
        # 第四步:softmax 归一化,得到注意力权重(batch_size × seq_len × seq_len)
        attn_weights = F.softmax(scores, dim=-1)
        # 第五步:权重 × V,得到注意力输出(batch_size × seq_len × d_k)
        output = torch.matmul(attn_weights, v)
        return output, attn_weights
    def split_heads(self, x):
        """
        将 Q/K/V 拆分成多个头(batch_size × seq_len × d_model)→(batch_size × num_heads × seq_len × d_k)
        """
        batch_size = x.size(0)
        # 先拆分最后一维:d_model → num_heads × d_k
        x = x.view(batch_size, -1, self.num_heads, self.d_k)
        # 交换维度:把 num_heads 提到 seq_len 前面(方便后续并行计算)
        return x.transpose(1, 2)  # 输出:batch_size × num_heads × seq_len × d_k
    def combine_heads(self, x):
        """
        拼接多个头的输出(batch_size × num_heads × seq_len × d_k)→(batch_size × seq_len × d_model)
        """
        batch_size = x.size(0)
        # 先交换维度:num_heads 放回最后一维前面
        x = x.transpose(1, 2)  # 输出:batch_size × seq_len × num_heads × d_k
        # 拼接最后两维:num_heads × d_k → d_model
        x = x.contiguous().view(batch_size, -1, self.d_model)  # contiguous() 确保内存连续
        return x
    def forward(self, x, mask=None):
        """
        前向传播(核心流程)
        :param x: 输入(batch_size × seq_len × d_model)
        :param mask: 掩码(可选,batch_size × seq_len × seq_len)
        :return: 多头自注意力输出(batch_size × seq_len × d_model)、注意力权重
        """
        batch_size = x.size(0)
        
        # 1. 生成 Q、K、V(线性变换)
        q = self.w_q(x)  # batch_size × seq_len × d_model
        k = self.w_k(x)
        v = self.w_v(x)
        
        # 2. 拆分多头(并行计算每个头的注意力)
        q = self.split_heads(q)  # batch_size × num_heads × seq_len × d_k
        k = self.split_heads(k)
        v = self.split_heads(v)
        
        # 3. 计算缩放点积注意力(每个头独立计算)
        attn_output, attn_weights = self.scaled_dot_product_attention(q, k, v, mask)
        
        # 4. 拼接所有头的输出
        attn_output = self.combine_heads(attn_output)  # batch_size × seq_len × d_model
        
        # 5. 最终线性变换
        output = self.w_o(attn_output)  # batch_size × seq_len × d_model
        
        # 6. 残差连接 + 层归一化(Transformer 标准操作,稳定训练)
        output = self.layer_norm(x + output)  # x 是原始输入(残差),加完后归一化
        
        return output, attn_weights
# -------------------------- 测试代码(运行看效果)--------------------------
if __name__ == "__main__":
    # 1. 设置超参数
    batch_size = 2  # 2个样本
    seq_len = 4     # 每个样本4个词(比如 "Are you OK ?")
    d_model = 64    # 每个词的特征维度
    num_heads = 8   # 8个多头(64 / 8 = 8,每个头维度是8)
    
    # 2. 生成随机输入(模拟经过词嵌入+位置编码后的特征)
    x = torch.randn(batch_size, seq_len, d_model)  # 形状:(2, 4, 64)
    print("输入形状:", x.shape)  # 输出:torch.Size([2, 4, 64])
    
    # 3. 初始化多头自注意力
    multi_head_attn = MultiHeadSelfAttention(d_model=d_model, num_heads=num_heads)
    
    # 4. 前向传播(无掩码,默认计算所有词的关联)
    output, attn_weights = multi_head_attn(x)
    
    # 5. 输出结果查看
    print("输出形状:", output.shape)  # 输出:torch.Size([2, 4, 64])(和输入形状一致)
    print("注意力权重形状:", attn_weights.shape)  # 输出:torch.Size([2, 8, 4, 4])
    # 注意力权重解释:(batch_size, num_heads, seq_len, seq_len) → 每个头对每个词的关联权重

image.gif

输入形状: torch.Size([2, 4, 64])

输出形状: torch.Size([2, 4, 64])

注意力权重形状: torch.Size([2, 8, 4, 4])

目录
相关文章
|
5月前
|
机器学习/深度学习 数据可视化 PyTorch
PyTorch深度学习实战 | 基于LSTM的时间序列预测任务
本文介绍了使用LSTM模型预测印度德里市平均温度的两个项目。项目1对温度数据进行归一化处理,采用滑动窗口法构建监督学习样本,设计5层LSTM网络结构,并详细说明了模型训练过程及评估方法。项目2在数据处理上增加了标准化和周期性特征,改进了网络架构,引入了学习率调整和早停机制优化训练过程。两个项目均通过可视化对比预测值和真实值,验证了LSTM模型在时间序列预测中的有效性。文章从数据处理、模型构建到训练优化,完整呈现了温度预测的实现流程,为时序预测任务提供了实用参考。
362 0
|
5月前
|
机器学习/深度学习 自然语言处理 PyTorch
PyTorch深度学习实战 |手动计算 Transformer和完整的代码实现
本文介绍了基于PyTorch实现Transformer模型的完整过程。主要内容包括:1)Transformer架构的核心组件实现,如多头注意力机制、位置前馈网络、位置编码等;2)模型构建步骤,包括词嵌入层、编码器/解码器块和输出层的实现;3)完整的训练流程,包含数据处理、损失计算和参数优化;4)评估方法验证模型性能。文章通过代码示例详细展示了如何从零开始构建Transformer,并应用于机器翻译任务,同时对模型各层的输入输出维度进行了说明。该实现可作为深度学习实践者学习Transformer架构的实用指南
433 0
|
5月前
|
机器学习/深度学习 存储 人工智能
图解人工智能的数学基础(线性代数)
本文系统讲解线性代数核心概念,涵盖向量(定义、几何/坐标表示、内积)、矩阵(含义、运算、秩、逆、相似、分解)、行列式(几何意义与变换关系)、线性方程组、特征值与特征向量、二次型、向量空间及范数等,强调其在AI与神经网络中的实际应用。
672 7
|
5月前
|
机器学习/深度学习 数据挖掘 PyTorch
PyTorch深度学习实战 |手算​​FCN全卷积神经网络
本文介绍了FCN-8s语义分割网络的实现细节。首先解释了语义分割的概念及其与图像分类的区别,重点分析了FCN网络结构中的全卷积化、上采样和跳跃连接三个关键技术。全卷积化将传统CNN的全连接层改为卷积层,实现像素级分类;上采样通过双线性插值恢复特征图尺寸;跳跃连接则融合高低层特征以提升细节表现。文章详细推导了损失函数的计算过程,并提供了完整的PyTorch实现代码,包括双线性插值权重初始化、VGG16骨干网络和FCN-8s主体结构。最后通过测试验证了模型能正确输出与输入尺寸匹配的预测结果。
475 3
|
5月前
|
机器学习/深度学习 编解码 算法
PyTorch深度学习实战 |手算​​U-net
本文详细解析了U-Net网络架构及其在医学图像分割中的应用。重点对比了U-Net与FCN的核心区别:U-Net采用特征拼接(Concat)保留所有层级信息,而FCN使用特征相加(Add)进行融合。文章深入剖析了U-Net的编码器-瓶颈-解码器结构,解释了其独特的裁剪拼接机制和Overlap-tile策略,并提供了完整的PyTorch实现代码。现代U-Net通过SamePadding实现了输入输出尺寸一致,显著提升了分割精度。文章还探讨了弹性形变数据增强和带空间权重的损失函数设计,为医学图像分析提供了实用解决
469 2
|
5月前
|
机器学习/深度学习 人工智能 PyTorch
PyTorch深度学习实战 | 人工智能项目从训练到部署
本项目基于LSTM模型对污水处理厂总曝气量(旧区+新区)进行时序预测。通过数据清洗、Min-Max归一化、滑动窗口构造(12小时输入→预测未来1小时),构建并训练轻量级LSTM模型,支持API部署与实时调用,已实现端到端预测流程及模型保存。
400 6
|
5月前
|
机器学习/深度学习 算法 机器人
图解强化学习 |手算SAC算法
SAC(Soft Actor-Critic)是最稳定、强大的连续动作强化学习算法,广泛应用于机器人控制与决策任务。其核心是最大熵强化学习:通过双Q网络抑制过估计,柔性策略网络增强探索,自适应温度系数α动态平衡利用与探索,兼顾最优性与鲁棒性。(239字)
834 1
|
5月前
|
机器学习/深度学习 PyTorch 算法框架/工具
PyTorch深度学习实战 |层归一化层和FeedForward
本文介绍了PyTorch深度学习中Add&Norm层和FeedForward层的实现原理。Add&Norm层由残差连接(Add)和层归一化(Norm)组成,能加速模型收敛并稳定训练。层归一化会对神经网络每层的输出进行归一化处理,文中详细展示了其计算方法和PyTorch实现代码。FeedForward层是一个两层的全连接网络,通过线性变换提取更深层次特征。文章还分析了Transformer模型中使用层归一化的原因,并提供了完整的代码实现,包括参数初始化和前向传播过程。
269 0
|
5月前
|
机器学习/深度学习 数据可视化 PyTorch
PyTorch深度学习实战 | 基于神经网络的水质分类
代码实现# 定义模型x = x.view(x.size(0), -1) # 展平成 (batch_size, 3072)x = self.fc3(x) # 输出return xprint("--- network.py 测试结果 ---")print(f"模型输出尺寸 (Batch, Classes): {output.shape}")语言描述步骤操作 / 组件输入数据形状输出数据形状核心作用1输入数据 (x)原始图像数据(B 为batch_size2展平 (Flatten)
176 0
|
5月前
|
机器学习/深度学习 自然语言处理 算法
PyTorch深度学习实战 | Transformer模型初识
本文介绍了Transformer模型在机器翻译任务中的工作原理。主要内容包括:1)模型分为编码器(处理英文输入)和解码器(生成中文输出)两部分;2)推理时采用自回归模式,逐步生成翻译结果;3)训练时使用教师强制模式,始终以真实标签指导模型学习。文章详细阐述了编码器-解码器结构的工作流程,包括词嵌入、多头注意力机制等核心组件,以及如何通过残差连接和层标准化优化模型性能。最后解释了编码器和解码器三个关键计算步骤的张量维度变化过程。
345 0

热门文章

最新文章