深度学习在进行训练时,可能会用到多个数据,但是各个数据的重要程度是不同的,例如在 RNN 的介绍中,对 c1, c2, c3 的计算,实际上就是一种注意力机制,aij 表示重要程度

具体来说,注意力机制是通过如下的结构实现的:

  • Query:需要计算的目标元素

  • key:source中元素的键值

  • value:source中元素的数值

  • similarity:相关性计算

  • 计算可以分为以下3个阶段

  • 本质上 Attention 机制就是对 Source 中 key 元素的 Value 值进行加权求和,而对应 Value 的加权求和系数则表现了每个 value 重要程度,是通过 Query 和 Key 计算的(similarity,key 与 Query越相关,加权系数越大)

    • 对于每个 RNN、LSTM 时间步,计算解码器当前输入状态和编码器输出的所有状态的相关性(transformer不是这样,其属于自注意力机制

    • RNN 的例子中,每个 h (编码器的输出)的 Value,key 都是其本身,Query(解码器当前输入状态)分别是(h0 ,h1 ,h2 假设预测 3 个时间步),最终Attention Value 分别是(c1,c2,c3),拿c3的计算举例:

  • 如果是在时序分类任务中,只需要一个输出,下面是一个使用加性注意力实现该注意力机制的代码,如下所示

class Attention(nn.Module):
    def __init__(self, hidden_size):
        super(Attention, self).__init__()
        self.hidden_size = hidden_size
        self.attn = nn.Linear(hidden_size, hidden_size)
        self.v = nn.Parameter(torch.rand(hidden_size))  # 定义一个可学习向量 v ,包装成 nn.Parameter,使其成为模型的参数

        # 对注意力机制中的权重参数进行初始化,使用Xavier均匀分布初始化方法
        # 自动根据张量的形状计算合适的范围,不需要手动指定
        nn.init.xavier_uniform_(self.attn.weight)
        # 对注意力机制中的偏置参数进行初始化,使用零初始化方法
        nn.init.constant_(self.attn.bias, 0)
        # 使用均匀分布初始化 query 参数,范围为 -0.1 到 0.1
        nn.init.uniform_(self.v, -0.1, 0.1)

    def forward(self, lstm_outputs):
        """
        lstm_outputs: Tensor of shape (batch_size, seq_length, hidden_size)
        """
        # 将h0,h1...... 的值映射到 (-1,1),实际上在加性注意力中
        # energy = torch.tanh(self.attn(lstm_outputs) + self.attn(query)) ,lstm_outputs 是 keys

        energy = torch.tanh(self.attn(lstm_outputs))  # (batch_size, seq_length, hidden_size)

        # 将 v 向量处理格式,v: (hidden_size) -> (1, 1, hidden_size) 
        v = self.v.unsqueeze(0).unsqueeze(0)  # (1, 1, hidden_size)

        # 相关性计算,energy * v: (batch_size, seq_length, hidden_size)
        energy = energy * v

        # 求和计算得分: (batch_size, seq_length)
        scores = energy.sum(dim=2)

        # 计算注意力权重
        # 对 scores 进行 Softmax 操作,确保权重在序列长度维度上归一化(即所有权重的和为 1)
        attn_weights = torch.softmax(scores, dim=1)  # (batch_size, seq_length)

        # unsqueeze(1) 将 attn_weights 的形状从 (batch_size, seq_length) 转换为 (batch_size, 1, seq_length)
        # 使用 torch.bmm(批量矩阵乘法)将注意力权重与 LSTM 输出(value)相乘
        context = torch.bmm(attn_weights.unsqueeze(1), lstm_outputs)  # (batch_size, 1, hidden_size)
        context = context.squeeze(1)  # (batch_size, hidden_size)

        return context, attn_weights
  • 在常见的其他注意力机制应用中,Query、Key、Value 都是向量与 Q、K、V 三个矩阵相乘得到的,下面是自注意力机制的例子,Key 与 Query都是自己:

  • 自注意力机制(Self-Attention)是深度学习中一种非常重要的机制,它允许序列中的每个元素在处理时与其他所有元素进行交互,并通过加权的方式聚合信息

  • 对输入编码器的每个词向量,都创建 3 个向量,分别是:Query 向量,Key 向量,Value 向量。这 3 个向量是词向量分别和 3 个矩阵相乘得到的,而这个矩阵是我们要学习的参数

  • 比如 q1 = x1 * Wq ,其他类似

  • 注:这 3 个新得到的向量一般比原来的词向量的长度更小

  • 权重计算过程中,score 用的最简单的点积方式表示,算出每个 key 对应的score后再除以 (dk)^(1/2),dk 为k的维度大小,然后再通过 Softmax 计算出权重,也可以除以其他数,除以一个数是为了在反向传播时,求取梯度更加稳定

  • Transformer 用的即自注意力机制,具体在Transformer 部分中介绍