【datawhale组队学习】HappyLLM-2.1注意力机制

发布于:2025-06-19 ⋅ 阅读:(10) ⋅ 点赞:(0)

计算机视觉 computer vision
(Graphics Processing Unit,GPU)

注意力机制

NLP 从统计机器学习向深度学习迈进,作为 NLP 核心问题的文本表示方法也逐渐从统计学习向深度学习迈进。
文本表示从最初的通过统计学习模型进行计算的向量空间模型、语言模型,通过 Word2Vec 的单层神经网络进入到通过神经网络学习文本表示的时代。
从计算机视觉为起源发展起来的神经网络,其核心架构有三种:

  1. 前馈神经网络(FNN feedforward Neural Network),每一层的神经元都与上下两层的每一个神经元完全连接
    在这里插入图片描述
  2. 卷积神经网络(Convolutional Neural Network CNN),即训练参数量远小于前馈神经网络的卷积层来进行特征提取和学习
    在这里插入图片描述
  3. 循环神经网络(Recurrent Neural Network,RNN),能够使用历史信息作为输入,包含环和自重复的网络
    在这里插入图片描述
    由于 NLP 任务所需要处理的文本往往是序列,因此专用于处理序列、时序数据的 RNN 往往能够在 NLP 任务上取得最优的效果。
    在注意力机制横空出世之前,RNN 以及 RNN 的衍生架构 LSTM(上一章讲的ELMo就是使用双向LSTM作为网络架构) 是 NLP 领域当之无愧的霸主。

RNN 及 LSTM 虽然具有捕捉时序信息、适合序列生成的优点,却有两个难以弥补的缺陷:

  1. 序列依序计算的模式能够很好地模拟时序信息,但限制了计算机并行计算的能力。
    由于序列需要依次输入依序计算,图形处理器并行计算的能力受到了极大限制,导致 RNN 为基础架构的模型虽然参数量不算特别大,但计算时间成本却很高;

  2. RNN 难以捕捉长序列的相关关系。
    在 RNN 架构中,距离越远的输入之间的关系就越难被捕捉,同时 RNN 需要将整个序列读入内存依次计算,也限制了序列的长度。虽然 LSTM 中通过门机制对此进行了一定优化,但对于较远距离相关关系的捕捉,RNN 依旧是不如人意的。

注意力机制的引入
潜在的答案:基于循环神经网络(RNN)一类的seq2seq模型,在处理长文本时遇到了挑战,而对长文本中不同位置的信息进行attention有助于提升RNN的模型效果。针对这样的问题,Vaswani 等学者参考了在 CV 领域被提出、被经常融入到 RNN 中使用的注意力机制(Attention)

什么是注意力机制:注意力机制最先源于计算机视觉领域,其核心思想为当我们关注一张图片,我们往往无需看清楚全部内容而仅将注意力集中在重点部分即可。而在自然语言处理领域,我们往往也可以通过将重点注意力集中在一个或几个 token,从而取得更高效高质的计算效果。
注意力机制有三个核心变量: Q u e r y Query Query(查询值)、 K e y Key Key(键值)和 V a l u e Value Value(真值)
例如,当我们有一篇新闻报道,我们想要找到这个报道的时间,
那么,我们的 Query 可以是类似于“时间”、“日期”一类的向量(为了便于理解,此处使用文本来表示,但其实际是稠密的向量),Key 和 Value 会是整个文本。通过对 Query 和 Key 进行运算我们可以得到一个权重,这个权重其实反映了从 Query 出发,对文本每一个 token 应该分布的注意力相对大小。通过把权重和 Value 进行运算,得到的最后结果就是从 Query 出发计算整个文本注意力得到的结果。

稠密的向量: 向量中的大部分元素都不为零,每个元素都携带具体的数值信息,且数值分布在连续的实数空间中。
与 “稀疏向量” 相对 —— 稀疏向量中大部分元素为零,仅少数位置有非零值(如 one-hot 编码)。Query、Key、Value 本质上都是稠密向量,它们通过模型的线性层(权重矩阵)生成,每个向量的维度通常为 64、128 或更高(如 BERT 中常用 768 维或 1024 维)(稠密的向量1)

​具体而言,注意力机制的特点是通过计算 Query 与Key的相关性为真值加权求和,从而拟合序列中每个词同其他词的相关关系。

深入理解注意力机制

首先,我们有这样一个字典 字典的键就是注意力机制中的键值 Key,而字典的值就是真值 Value。那么我们可以直接通过将 Query 与 Key 做匹配来得到对应的 Value。

{
    "apple":10,
    "banana":5,
    "chair":2
}

要匹配的 Query 是一个包含多个 Key 的概念呢?例如,我们想要查找“fruit”,此时,我们应该将 apple 和 banana 都匹配到,但不能匹配到 chair。
当我们的 Query 为“fruit”,我们可以分别给三个 Key 赋予如下的权重:

{
    "apple":0.6,
    "banana":0.4,
    "chair":0
}

我们最终查询到的值应该是:
value=0.6∗10+0.4∗5+0∗2=8

给不同 Key 所赋予的不同权重,就是我们所说的注意力分数,也就是为了查询到 Query,我们应该赋予给每一个 Key 多少注意力。但是,如何针对每一个 Query,计算出对应的注意力分数呢?从直观上讲,我们可以认为 Key 与 Query 相关性越高,则其所应该赋予的注意力权重就越大。

通过合理的训练拟合,词向量能够表征语义信息,语义相近的词在向量空间中距离更近,语义较远的词在向量空间中距离更远。

  1. 我们往往用欧式距离来衡量词向量的相似性,但我们同样也可以用点积来进行度量:
    v ⋅ w = ∑ i v i w i v·w=\sum_i v_i w_i vw=iviwi
    根据词向量的定义,语义相似的两个词对应的词向量的点积应该大于0,而语义不相似的词向量点积应该小于0。

我们可以用点积来计算词之间的相似度。
在这里插入图片描述
此处的 K 即为将所有 Key 对应的词向量堆叠形成的矩阵。基于矩阵乘法的定义,x 即为 q 与每一个 k 值的点积。现在我们得到的 x 即反映了 Query 和每一个 Key 的相似程度,我们再通过一个 Softmax 层将其转化为和为 1 的权重:
softmax ( x ) i = e x i ∑ j e x j \text{softmax}(x)i = \frac{e^{xi}}{\sum{j}e^{x_j}} softmax(x)i=jexjexi
得到的向量就能够反映 Query 和每一个 Key 的相似程度,同时又相加权重为 1,也就是我们的注意力分数了。最后,我们再将得到的注意力分数和值向量做对应乘积即可。

注意力机制计算的基本公式:
单 Q u e r y : attention ( Q , K , V ) = softmax ( q K T ) v 单Query:\text{attention}(Q,K,V) = \text{softmax}(qK^T)v Query:attention(Q,K,V)=softmax(qKT)v

不过,此时的值还是一个标量,同时,我们此次只查询了一个 Query。我们可以将值转化为维度为 dv(Value 向量的维度) 的向量,同时一次性查询多个 Query,同样将多个 Query 对应的词向量堆叠在一起形成矩阵 Q,得到公式:

多 Q u e r y : attention ( Q , K , V ) = softmax ( Q K T ) V 多Query:\text{attention}(Q,K,V) = \text{softmax}(QK^T)V Query:attention(Q,K,V)=softmax(QKT)V

目前,我们离标准的注意力机制公式还差最后一步。在上一个公式中,如果 ( Q ) 和 ( K ) 对应的维度 ( d_k ) 比较大,softmax 放缩时就非常容易受影响,使不同值之间的差异较大,从而影响梯度的稳定性。因此,我们要将 ( Q ) 和 ( K ) 乘积的结果做一个放缩:

attention ( Q , K , V ) = softmax ( Q K T d k ) V \text{attention}(Q,K,V) = \text{softmax}(\frac{QK^T}{\sqrt{d_k}})V attention(Q,K,V)=softmax(dk QKT)V

注意力机制的实现

'''注意力计算函数'''
def attention(query, key, value, dropout=None):
    '''
    args:
    query: 查询值矩阵
    key: 键值矩阵
    value: 真值矩阵
    '''
    # 获取键向量的维度,键向量的维度和值向量的维度相同
    d_k = query.size(-1) #print(x.size(-1))    # 输出: (最后一维的大小)
    # 计算Q与K的内积并除以根号dk
    # transpose——相当于转置
    scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
    # Softmax
    p_attn = scores.softmax(dim=-1)
    if dropout is not None:
        p_attn = dropout(p_attn)
        #这行代码对注意力权重矩阵 p_attn 应用了 Dropout 正则化,其核心作用是防止过拟合并提高模型泛化能力。
        # 采样
     # 根据计算结果对value进行加权求和
    return torch.matmul(p_attn, value), p_attn

自注意力

注意力机制的本质是对两段序列的元素依次进行相似度计算,寻找出一个序列的每个元素对另一个序列的每个元素的相关度,然后基于相关度进行加权,即分配注意力。而这两段序列即是我们计算过程中 Q、K、V 的来源。
但是,在我们的实际应用中,我们往往只需要计算 Query 和 Key 之间的注意力结果,很少存在额外的真值 Value。我们其实只需要拟合两个文本序列
​在经典的注意力机制中,Q 往往来自于一个序列,K 与 V 来自于另一个序列,都通过参数矩阵计算得到,从而可以拟合这两个序列之间的关系。例如在 Transformer 的 Decoder 结构中,Q 来自于 Decoder 的输入,K 与 V 来自于 Encoder 的输出,从而拟合了编码信息与历史信息之间的关系,便于综合这两种信息实现未来的预测。

普通注意力(如机器翻译中的编码器 - 解码器注意力)

  • Q(查询):来自解码器的输出(需要翻译的目标词)。
  • K(键)、V(值):来自编码器的输出(源语言句子的表示)。
  • 目的:让解码器关注源语言中与当前目标词相关的信息(如翻译 “apple” 时关注源语言中的 “苹果”)。
    自注意力(Encoder 中的核心机制)
  • Q、K、V 均来自同一输入序列(如源语言句子本身)。
  • 目的:让序列中的每个元素(token)关注自身与其他所有元素的关系,从而捕捉句子内部的语义依赖(如 “他买了苹果,因为喜欢它的味道” 中,“它” 与 “苹果” 的关联)。

自注意力:但在 Transformer 的 Encoder 结构中,使用的是 注意力机制的变种 —— 自注意力(self-attention,自注意力)机制。所谓自注意力,即是计算本身序列中每个元素对其他元素的注意力分布。Encoder 需要将整个输入序列编码为包含内部依赖关系的向量,而自注意力通过 “内部互动” 实现这一点。
即在计算过程中,Q、K、V 都由同一个输入通过不同的参数矩阵计算得到。在 Encoder 中,Q、K、V 分别是输入对参数矩阵 Wq、Wk、Wv做积得到,从而拟合输入语句中每一个 token 对其他所有 token 的关系。通过自注意力机制,我们可以找到一段文本中每一个 token 与其他所有 token 的相关关系大小,从而建模文本之间的依赖关系。​在代码中的实现,self-attention 机制其实是通过给 Q、K、V 的输入传入同一个参数实现的

# attention 为上文定义的注意力计算函数
attention(x, x, x)

Q、K、V 的生成过程:从同一输入到三种角色
线性变换生成三种矩阵

  • Query(Q):通过参数矩阵 W_q 投影得到,用于 “查询” 序列中的相关信息。
    Q = X · W_q(W_q 形状为 [d_model, d_k],d_k 是 Query 的维度)。
  • Key(K):通过参数矩阵 W_k 投影得到,用于 “标记” 每个位置的特征。
    K = X · W_k(W_k 形状为 [d_model, d_k],通常 d_k = d_model / num_heads)。
  • Value(V):通过参数矩阵 W_v 投影得到,用于 “提供” 需要聚合的信息。
    V = X · W_v(W_v 形状为 [d_model, d_v],通常 d_v = d_k)。

掩码自注意力

掩码自注意力,即 Mask Self-Attention,是指使用注意力掩码的自注意力机制。掩码的作用是遮蔽一些特定位置的 token,模型在学习的过程中,会忽略掉被遮蔽的 token。
使用注意力掩码的核心动机是让模型只能使用历史信息进行预测而不能看到未来信息。使用注意力机制的 Transformer 模型也是通过类似于 n-gram 的语言模型任务来学习的,也就是对一个文本序列,不断根据之前的 token 来预测下一个 token,直到将整个文本序列补全。(马尔科夫链)【串行,计算效率低】

Step 1:输入 【BOS】,输出 I
Step 2:输入 【BOS】I,输出 like
Step 3:输入 【BOS】I like,输出 you
Step 4:输入 【BOS】I like you,输出 【EOS】

理论上来说,只要学习的语料足够多,通过上述的过程,模型可以学会任意一种文本序列的建模方式,也就是可以对任意的文本进行补全。上述过程是一个串行的过程,也就是需要先完成 Step 1,才能做 Step 2,接下来逐步完成整个序列的补全。Transformer 相对于 RNN 的核心优势之一即在于其可以并行计算,具有更高的计算效率。如果对于每一个训练语料,模型都需要串行完成上述过程才能完成学习,那么很明显没有做到并行计算,计算效率很低。

针对这个【问题】,Transformer 就提出了掩码自注意力的方法。掩码自注意力会生成一串掩码,来遮蔽未来信息。例如,我们待学习的文本序列仍然是 【BOS】I like you【EOS】,我们使用的注意力掩码是【MASK】,那么模型的输入为:

<BOS> 【MASK】【MASK】【MASK】【MASK】
<BOS>    I   【MASK】 【MASK】【MASK】
<BOS>    I     like  【MASK】【MASK】
<BOS>    I     like    you  【MASK】
<BoS>    I     like    you   </EOS>

模型仍然是只看到前面的 token,预测下一个 token。但是注意,上述输入不再是串行的过程,而可以一起并行地输入到模型中,模型只需要每一个样本根据未被遮蔽的 token 来预测下一个 token 即可,从而实现了并行的语言模型。观察上述的掩码,我们可以发现其实则是一个和文本序列等长的上三角矩阵。我们可以简单地通过创建一个和输入同等长度的上三角矩阵作为注意力掩码,再使用掩码来遮蔽掉输入即可。

当输入维度为 (batch_size, seq_len, hidden_size)时,我们的 Mask 矩阵维度一般为 (1, seq_len, seq_len)【因果掩码 通过一个 上三角矩阵(对角线及下方元素为 0,上方为负无穷) 实现】(通过广播实现同一个 batch 中不同样本的计算)。
在自注意力中,注意力分数矩阵的维度为 (batch_size, seq_len, seq_len):
行维度:查询(Query)的位置(当前正在处理的 token);
列维度:键(Key)的位置(被查询的 token)。
掩码逻辑:在注意力分数计算后、Softmax 前应用该
掩码,使得未来位置的分数变为负无穷,Softmax 后权重趋近于 0。

# 创建一个上三角矩阵,用于遮蔽未来信息。
# 先通过 full 函数创建一个 1 * seq_len * seq_len 的矩阵
mask = torch.full((1, args.max_seq_len, args.max_seq_len), float("-inf"))
# triu 函数的功能是创建一个上三角矩阵
mask = torch.triu(mask, diagonal=1)
#生成的 Mask 矩阵会是一个上三角矩阵,上三角位置的元素均为 -inf,其他位置的元素置为0。(不包括对角线)

在注意力计算时,我们会将计算得到的注意力分数与这个掩码做和,再进行 Softmax 操作:

# 此处的 scores 为计算得到的注意力分数,mask 为上文生成的掩码矩阵
scores = scores + mask[:, :seqlen, :seqlen]
scores = F.softmax(scores.float(), dim=-1).type_as(xq)

注意力遮蔽的原理
通过做求和,上三角区域(也就是应该被遮蔽的 token 对应的位置)的注意力分数结果都变成了 -inf,而下三角区域的分数不变。再做 Softmax 操作,-inf 的值在经过 Softmax 之后会被置为 0,从而忽略了上三角区域计算的注意力分数,从而实现了注意力遮蔽。

多头注意力

注意力机制可以实现并行化与长期依赖关系拟合,但一次注意力计算只能拟合一种相关关系,单一的注意力机制很难全面拟合语句序列里的相关关系。因此 Transformer 使用了多头注意力机制(Multi-Head Attention),即同时对一个语料进行多次注意力计算,每次注意力计算都能拟合不同的关系,将最后的多次结果拼接起来作为最后的输出,即可更全面深入地拟合语言信息。

在这里插入图片描述
​上层与下层分别是两个注意力头对同一段语句序列进行自注意力计算的结果,可以看到,对于不同的注意力头,能够拟合不同层次的相关信息。通过多个注意力头同时计算,能够更全面地拟合语句关系。
事实上,所谓的多头注意力机制其实就是将原始的输入序列进行多组的自注意力处理;然后再将每一组得到的自注意力结果拼接起来,再通过一个线性层进行处理,得到最终的输出。我们用公式可以表示为:
MultiHead ( Q , K , V ) = Concat ( head 1 , … , head h ) W O where head i = Attention ( Q W i Q , K W i K , V W i V ) \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \ldots, \text{head}_h) W^O \quad \text{where} \quad \text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V) MultiHead(Q,K,V)=Concat(head1,,headh)WOwhereheadi=Attention(QWiQ,KWiK,VWiV)
其最直观的代码实现并不复杂,即 n 个头就有 n 组3个参数矩阵,每一组进行同样的注意力计算,但由于是不同的参数矩阵从而通过反向传播实现了不同的注意力结果,然后将 n 个结果拼接起来输出即可。

但上述实现时空复杂度均较高,我们可以通过矩阵运算巧妙地实现并行的多头计算,其核心逻辑在于使用三个组合矩阵来代替了n个参数矩阵的组合,也就是矩阵内积再拼接其实等同于拼接矩阵再内积。具体实现可以参考下列代码:

import torch.nn as nn
import torch

'''多头自注意力计算模块'''
class MultiHeadAttention(nn.Module):

    def __init__(self, args: ModelArgs, is_causal=False):
        # 构造函数
        # args: 配置对象
        super().__init__()
        # 隐藏层维度必须是头数的整数倍,因为后面我们会将输入拆成头数个矩阵
        assert args.n_embd % args.n_heads == 0
        # 模型并行处理大小,默认为1。
        model_parallel_size = 1
        # 本地计算头数,等于总头数除以模型并行处理大小。
        self.n_local_heads = args.n_heads // model_parallel_size
        # 每个头的维度,等于模型维度除以头的总数。
        self.head_dim = args.dim // args.n_heads

        # Wq, Wk, Wv 参数矩阵,每个参数矩阵为 n_embd x n_embd
        # 这里通过三个组合矩阵来代替了n个参数矩阵的组合,其逻辑在于矩阵内积再拼接其实等同于拼接矩阵再内积,
        # 不理解的读者可以自行模拟一下,每一个线性层其实相当于n个参数矩阵的拼接
        self.wq = nn.Linear(args.dim, args.n_heads * self.head_dim, bias=False)
        self.wk = nn.Linear(args.dim, args.n_heads * self.head_dim, bias=False)
        self.wv = nn.Linear(args.dim, args.n_heads * self.head_dim, bias=False)
        # 输出权重矩阵,维度为 n_embd x n_embd(head_dim = n_embeds / n_heads)
        self.wo = nn.Linear(args.n_heads * self.head_dim, args.dim, bias=False)
        # 注意力的 dropout
        self.attn_dropout = nn.Dropout(args.dropout)
        # 残差连接的 dropout
        self.resid_dropout = nn.Dropout(args.dropout)
         
        # 创建一个上三角矩阵,用于遮蔽未来信息
        # 注意,因为是多头注意力,Mask 矩阵比之前我们定义的多一个维度
        ## 因果掩码(解码器自注意力需要,防止看到未来token)
        if is_causal:
           mask = torch.full((1, 1, args.max_seq_len, args.max_seq_len), float("-inf"))
           mask = torch.triu(mask, diagonal=1)
           # 注册为模型的缓冲区
           self.register_buffer("mask", mask)

    def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor):

        # 获取批次大小和序列长度,[batch_size, seq_len, dim]
        bsz, seqlen, _ = q.shape

        # 计算查询(Q)、键(K)、值(V),输入通过参数矩阵层,维度为 (B, T, n_embed) x (n_embed, n_embed) -> (B, T, n_embed)
        xq, xk, xv = self.wq(q), self.wk(k), self.wv(v)

        # 将 Q、K、V 拆分成多头,维度为 (B, T, n_head, C // n_head),然后交换维度,变成 (B, n_head, T, C // n_head)
        # 因为在注意力计算中我们是取了后两个维度参与计算
        # 为什么要先按B*T*n_head*C//n_head展开再互换1、2维度而不是直接按注意力输入展开,是因为view的展开方式是直接把输入全部排开,
        # 然后按要求构造,可以发现只有上述操作能够实现我们将每个头对应部分取出来的目标
        xq = xq.view(bsz, seqlen, self.n_local_heads, self.head_dim)
        xk = xk.view(bsz, seqlen, self.n_local_heads, self.head_dim)
        xv = xv.view(bsz, seqlen, self.n_local_heads, self.head_dim)
        xq = xq.transpose(1, 2)
        xk = xk.transpose(1, 2)
        xv = xv.transpose(1, 2)


        # 注意力计算
        # 计算 QK^T / sqrt(d_k),维度为 (B, nh, T, hs) x (B, nh, hs, T) -> (B, nh, T, T)
        scores = torch.matmul(xq, xk.transpose(2, 3)) / math.sqrt(self.head_dim)
        # 掩码自注意力必须有注意力掩码
        if self.is_causal:
            assert hasattr(self, 'mask')
            # 这里截取到序列长度,因为有些序列可能比 max_seq_len 短
            scores = scores + self.mask[:, :, :seqlen, :seqlen]
        # 计算 softmax,维度为 (B, nh, T, T)
        scores = F.softmax(scores.float(), dim=-1).type_as(xq)
        # 做 Dropout
        scores = self.attn_dropout(scores)
        # V * Score,维度为(B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)
        ## 加权聚合Value
        output = torch.matmul(scores, xv)

        # 恢复时间维度并合并头。 重塑并合并多头结果
        # 将多头的结果拼接起来, 先交换维度为 (B, T, n_head, C // n_head),再拼接成 (B, T, n_head * C // n_head)
        # contiguous 函数用于重新开辟一块新内存存储,因为Pytorch设置先transpose再view会报错,
        # 因为view直接基于底层存储得到,然而transpose并不会改变底层存储,因此需要额外存储
        output = output.transpose(1, 2).contiguous().view(bsz, seqlen, -1)

        # 最终投影回残差流。
        output = self.wo(output)
        output = self.resid_dropout(output)
        return output


Token 可以理解为对文本进行分割后的 “语义片段”,分割方式取决于具体任务和模型

注意力机制中softmax的核心作用:
Softmax 的核心作用

  1. 将相似度转化为可解释的权重
    在注意力计算中,Query 与 Key 的点积(或其他相似度函数)结果只是原始数值,无法直接作为权重使用。
    通过 Softmax 转换后,每个 Key 对应的输出值表示 “在所有 Key 中,该 Key 与 Query 的相对重要性”,总和为 1 的特性确保了权重的合理性。
  2. 实现 “聚焦” 关键信息的能力
    例如:若 Query 是 “时间”,与 Key “日期” 的点积为 3,与 Key “天气” 的点积为 1,Softmax 后前者权重约 0.88,后者约 0.12。
    这意味着模型会将 88% 的 “注意力” 分配给 “日期” 相关的内容,12% 分配给 “天气”,从而实现对关键信息的聚焦。
    为什么注意力机制必须用 Softmax?其他函数不行吗?
  3. 对比其他归一化方法
    ReLU:将负数置 0,但输出和不一定为 1,无法直接作为权重。
    Sigmoid:将值映射到 (0,1),但多个输出的和可能大于 1,且无法反映元素间的相对关系(如输入 [3,1],Sigmoid 输出约 [0.95, 0.73],和为 1.68)。
    L2 归一化:将向量长度归一化,但不保留数值差异的相对比例(如输入 [3,1] 归一化后为 [0.95, 0.31],而 Softmax 为 [0.88, 0.12],后者更强调最大值的主导性)。
  4. Softmax 的不可替代性
    注意力机制需要的是 “相对重要性” 的概率分布,Softmax 通过指数运算放大差异,同时保证和为 1,完美匹配这一需求。
    若替换为其他函数,可能导致权重分配不合理,例如 L2 归一化会让次要信息获得过高权重,影响模型聚焦关键内容的能力。

Softmax层的应用场景对比

场景 输入类型 Softmax的作用 输出含义
注意力机制 Query与Key的点积结果 将相似度转化为概率权重 每个Key的相对重要性
分类任务(如CNN) 全连接层的输出 将logits转化为类别概率 样本属于每个类别的概率
多标签学习(变种) 原始分数 (变种Softmax)允许非互斥概率 每个标签的相关性分数
import torch

# 创建一个形状为 [2, 3, 4] 的张量
x = torch.randn(2, 3, 4)

print(x.size())      # 输出: torch.Size([2, 3, 4])
print(x.size(0))     # 输出: 2(第0维的大小)
print(x.size(1))     # 输出: 3(第1维的大小)
print(x.size(-1))    # 输出: 4(最后一维的大小)

Seq2seq

seq2seq模型的输入可以是一个(单词、字母或者图像特征)序列,输出是另外一个(单词、字母或者图像特征)序列。
以NLP中的机器翻译任务为例,序列指的是一连串的单词,输出也是一连串单词。
seq2seq模型由编码器(Encoder)和解码器(Decoder)组成。

这些信息会被转换成为一个黄色的向量(称为context向量)。当我们处理完整个输入序列后,编码器把 context向量 发送给紫色的解码器,解码器通过context向量中的信息,逐个元素输出新的序列。
由于seq2seq模型可以用来解决机器翻译任务,因此机器翻译被任务seq2seq模型解决过程如下图所示,当作seq2seq模型的一个具体例子来学习。seq2seq模型中的编码器和解码器一般采用的是循环神经网络RNN。编码器将输入的法语单词序列编码成context向量。

#假设输入序列为 [A, B, C](seq_len=3),注意力分数矩阵为:
scores = [[a_aa, a_ab, a_ac],  # A 对 A、B、C 的分数
          [a_ba, a_bb, a_bc],  # B 对 A、B、C 的分数
          [a_ca, a_cb, a_cc]]  # C 对 A、B、C 的分数

#应用掩码 mask 后:
masked_scores = [[a_aa, -inf, -inf],
                 [a_ba, a_bb, -inf],
                 [a_ca, a_cb, a_cc]]


#经过 Softmax 后:
attention_weights = [[1.0, 0.0, 0.0],  # A 只能关注自己
                     [w_ba, w_bb, 0.0],  # B 只能关注 A 和 B
                     [w_ca, w_cb, w_cc]]  # C 可以关注所有位置

torch.full():创建全值矩阵
torch.full(size, fill_value, dtype=None, device=None) → Tensor
参数解释
size:元组,表示输出矩阵的形状(如 (3, 4) 表示 3×4 的矩阵)。
fill_value:标量值,用于填充矩阵的每个元素。
dtype(可选):数据类型(如 torch.float32)。
device(可选):存储设备(如 ‘cuda’ 或 ‘cpu’)。

torch.triu():提取 / 创建上三角矩阵

torch.triu(input, diagonal=0) → Tensor
参数解释
input:输入张量。
diagonal(可选):对角线偏移量:
diagonal=0(默认):主对角线及以上元素保留,其余置零。
diagonal=1:主对角线上方的元素保留(不包括主对角线),其余置零。
diagonal=-1:主对角线及下方的元素保留,其余置零。


  1. 维度设置依据模型设计而定:
    64/128维:常见于基础研究和小规模模型
    768维:BERT-base的标准配置
    1024维:常用于BERT-large等更大模型
    更高维度(如2048)出现在某些特定场景的模型设计中 ↩︎


网站公告

今日签到

点亮在社区的每一天
去签到