attention机制

本质思想就是【从大量信息中】【有选择的筛选出】【少量重要信息】并【聚焦到这些重要信息上】,【忽略/较少关注大多不重要的信息】。聚焦的过程体现在【权重系数】的计算上,权重越大越聚焦于其对应的value值上。即权重代表了信息的重要性,而value是其对应的信息。

引入Attention机制原因

类似RNN无法捕捉长序列的道理,没有引入Attention机制在输入句子较短时影响不大,但是如果输入句子比较长,此时所有语义通过一个中间语义向量表示,单词自身的信息避免不了会消失,也就是会丢失很多细节信息,这也是为何引入Attention机制的原因。

固定的中间语义表示C换成了根据当前输出单词来调整成加入注意力模型的变化的Ci

Attention思想

一个典型的Attention思想包括三部分:Q-query 查询、K-key 索引、V-value 内容。

  • Q是query,是输入的信息;key和value成组出现,通常是原始文本等已有的信息;

  • 通过计算Q与K之间的相关性a,得出不同的K对输出的重要程度;

  • 再与对应的v进行相乘求和,就得到了Q的输出;

Self-Attention

只关注输入序列元素之间的关系,即每个输入元素都有它自己的Q、K、V,比如在一般任务的Encoder-Decoder框架中,输入Source和输出Target内容是不一样的,比如对于英-中机器翻译来说,Source是英文句子,Target是对应的翻译出的中文句子,Attention机制发生在Target的元素QuerySource中的所有元素之间。而Self Attention指的不是Target和Source之间的Attention机制,而是Source内部元素之间或者Target内部元素之间发生的Attention机制,也可以理解为Target=Source这种特殊情况下的注意力计算机制。】

公式

时间复杂度

self-attention注意力机制中关于时间复杂度的计算_自注意力机制的时间复杂度-CSDN博客

O(T^2d)

代码(无pose)

import torch
import torch.nn as nn

class SelfAttention(nn.Module):
    def __init__(self, embed_dim):
        super(SelfAttention, self).__init__()
        self.embed_dim = embed_dim
        
        # 线性层,用于计算query、key和value
        self.query = nn.Linear(embed_dim, embed_dim)
        self.key = nn.Linear(embed_dim, embed_dim)
        self.value = nn.Linear(embed_dim, embed_dim)
        
        # 用于计算attention的权重
        self.attn = nn.Linear(embed_dim, 1)
        
    def forward(self, x):
        # 假设x的形状为[batch_size, seq_len, embed_dim]
        
        # 计算query、key和value
        query = self.query(x)
        key = self.key(x)
        value = self.value(x)
        
        # 计算attention权重
        score = torch.matmul(query, key.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.embed_dim, dtype=torch.float32))
        attn = torch.softmax(score, dim=-1)
        
        # 计算attended value
        context = torch.matmul(attn, value)
        
        return context

# 示例
batch_size = 10
seq_len = 5
embed_dim = 10

# 创建一个自注意力模块
attention = SelfAttention(embed_dim)

# 创建一个示例张量
x = torch.randn(batch_size, seq_len, embed_dim)

# 调用自注意力模块
output = attention(x)

print(output.shape)  # 输出: torch.Size([10, 5, 10])

Multi-Head Attention

多种变换生成的Q、K、V进行计算,再将它们对相关性的结论综合起来,进一步增强自注意力的效果。

代码

import torch
import torch.nn as nn

# 位置编码
class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout=0.1, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)
        
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-torch.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0).transpose(0, 1)
        self.register_buffer('pe', pe)
        
    def forward(self, x):
        x = x + self.pe[:x.size(0), :]
        return self.dropout(x)

# 多头注意力
class MultiHeadAttention(nn.Module):
    def __init__(self, embed_dim, num_heads, dropout=0.1):
        super(MultiHeadAttention, self).__init__()
        self.embed_dim = embed_dim
        self.num_heads = num_heads
        self.dropout = dropout
        
        self.q_linear = nn.Linear(embed_dim, embed_dim)
        self.k_linear = nn.Linear(embed_dim, embed_dim)
        self.v_linear = nn.Linear(embed_dim, embed_dim)
        self.out_linear = nn.Linear(embed_dim, embed_dim)
        
    def forward(self, q, k, v, mask):
        batch_size = q.size(0)
        
        # 线性变换
        q = self.q_linear(q)
        k = self.k_linear(k)
        v = self.v_linear(v)
        
        # 分割多头
        q = q.view(batch_size, -1, self.num_heads, self.embed_dim //self.num_heads).transpose(1, 2)
        k = k.view(batch_size, -1, self.num_heads, self.embed_dim // self.num_heads).transpose(1, 2)
        v = v.view(batch_size, -1, self.num_heads, self.embed_dim // self.num_heads).transpose(1, 2)
        
        # 计算注意力
        attn = torch.matmul(q, k.transpose(-2, -1)) / (self.embed_dim // self.num_heads)
        if mask is not None:
            attn = attn.masked_fill(mask == 0, -float('inf'))
        attn = torch.softmax(attn, dim=-1)
        attn = self.dropout(attn)
        
        # 计算输出
        out = torch.matmul(attn, v).transpose(1, 2).contiguous().view(batch_size, -1, self.embed_dim)
        out = self.out_linear(out)
        
        return out

# 示例
input_dim = 100
output_dim = 100
embed_dim = 512
num_heads = 8
dropout = 0.1

# 创建位置编码
positional_encoding = PositionalEncoding(embed_dim, dropout)

# 创建多头注意力模块
multi_head_attn = MultiHeadAttention(embed_dim, num_heads, dropout)

常见问题

  1. 缩放点积中为什么要除以根号dk

    • 在两个向量维度非常大的时候,点乘结果的方差也会很大,即结果中的元素差距很大,在点乘的值非常大的时候,softmax的梯度会趋近于0,也就是梯度消失。在原文中有提到,假设q和k的元素是相互独立维度为dk的随机变量,它们的均值是0,方差为1,那么q和k的点乘的平均值为0,方差为dk,如果将点乘的结果进行缩放操作,也就是除以dk,就可以有效控制方差从dk回到1,也就是有效控制梯度消失问题。

  2. 为什么比较大的输入会使得softmax的梯度变得很小?

    • 非常接近于one-hot,梯度消失为0

  3. 维度与点积大小的关系是怎么样的,为什么使用维度的根号来放缩?

    • DX=DY=1,EX=EY=0,E(XY)=0,D(XY)=DXDY-[EXEXY]^2=1,独立分量,求和变成0和dk了

    • 方差大表示各个分量的差距较大

  • 36
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值