Transformer模型的编码器结构实现1(掩码张量+注意力机制)

说明:部分来自于网络教程

教程链接:2.3.1掩码张量-part1_哔哩哔哩_bilibili

1.掩码张量

掩码就是遮盖张量中的内容。“掩”就是遮掩,“码”就是张量中的数值。尺寸不定,他的内容一般只含有0和1。至于是0的位置被遮掩还是1的位置被遮掩可以自定义。

生成掩码张量的代码:

def subsequent_mask(size):
    '''
    生成掩码张量,参数size是掩码张量最后的两个维度大小,最后两维形成一个方阵
    '''
    attn_shape = (1,size,size)    # 定义掩码张量的形状
    # 利用np.triu形成上三角矩阵,为了节省空间转换为uint8
    # k=0时主对角线不移动,k=1表示主对角线向右上移动一次
    subsequent_mask = np.triu(np.ones(attn_shape),k=1).astype('uint8')
    # 进行反转并返回    

    return torch.form_numpy(1 - subsequent_mask)

2.注意力机制

注意力:我们在观察食物时,会自动关注到食物最关键的部分,由此快速进行判断。

注意力计算规则:需要3个输入Q(query),K(key),V(value),然后通过公式得到注意力的计算结果。计算规则有蛮多种,这里我们介绍其中一种:

Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V

加入我们要对一段文本进行概况描述。那么这段文本就是Q,此时会提前给你一些关键词作为提示信息,这些关键词就是K。而V则代表你看到这些关键词之后脑海里浮现的内容。

K=V\neq Q时,称为注意力机制;当K=V=Q时称为自注意力机制,此时关键词相当于就是文本本身。

代码实现:

注意力机制是注意力计算规则能够应用的深度学习网络的载体,其中除了注意力计算规则外还包括一些必要的全连接层以及相关张量处理。使用自注意力计算规则的注意力机制称为自注意力机制。

注意力机制代码实现:

def attention(query,key,value,mask=None,dropout=None):
    # query的最后一个维度:词嵌入的维度:(2,4,512)中的512
    # mask:掩码张量
    # dropout:实例化的dropout模块,用于随机置零
    
    d_k = query.size(-1)
    scores = torch.matmul(query,key.transpose(-2,-1)) / math.sqrt(d_k)
    
    
    if mask is not None:
        scores = scores.masked_fill(mask == 0,1e-9)
       
    p_attn = F.softmax(scores,dim=-1)
    
    if dropout is not None:
        p_attn = dropout(p_attn)
        
    # 最终返回值:query的注意力表示;注意力张量
    return torch.matmul(p_attn,value),p_attn

3.多头注意力机制

 如图所示为多头注意力机制的结构图。

多头注意力机制其实就是将词嵌入维度进行了拆分,例如经过词嵌入之后输入为(2,4,512),那么词嵌入维度为512,你可以将它分为2个头。每个头获得的输入(2,4,256)送到注意力机制当中去,这里的头数也就是图中的h。

多头注意力机制的作用:

这种结构设计可以让每个注意力机制去优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,让词义拥有来自更多元的表达。提升模型效果。

# 用于深度拷贝的工具包copy
import copy

# 首先需要定义克隆函数,因为在多头注意力机制实现中,用到多个结构相同的线性层
# 使用clone函数将他们一同初始化在一个网络层列表对象中
def clones(module,N):
    '''
        用于生成相同网络层的克隆函数,参数module表示克隆的目标网络层,N代表克隆的数量
    '''
    # 通过for循环对Module进行N次深度拷贝,然后放在列表中存放
    
    return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])

# 实现多头注意力机制
class MultiHeadedAttention(nn.Module):
    def __init__(self, head, embedding_dim, dropout=0.1):
        # head:头数
        # embedding_dim:词嵌入维度
        # dropout:dropout参数

        super(MultiHeadedAttention,self).__init__()
        # 确认词嵌入维度是否能整除头数
        assert embedding_dim % head == 0
        self.d_k = embedding_dim // head
        self.head = head
        self.embedding_dim = embedding_dim

        # 获得4个线性层
        self.linears =  clones(nn.Linear(embedding_dim,embedding_dim),4)

        # 初始化注意力张量
        self.attn = None
        self.dropout = nn.Dropout(p=dropout)
      
    def forward(self, query, key, value, mask=None):
        
        if mask is not None:
            mask = mask.unsqueeze(1)
        # 得到batchsize
        batch_size = query.size(0)
        # 使用zip将网络层和输入数据连接在一起,输出需要进行维度改变
        query, key, value = \
            [model(x).view(batch_size,-1,self.head,self.d_k).transpose(1,2)
             for model,x in zip(self.linears,(query, key, value))]
        # 将每个头的输出传入到注意力层
        x, self.attn = attention(query, key, value, mask=mask,dropout=self.dropout)
        x = x.transpose(1,2).contiguous().view(batch_size,-1,self.head*self.d_k)

        #最后将x输入线性层列表的最后一个线性层,得到最终输出:query的多头注意力机制表示
        return self.linears[-1](x)
        

猜你喜欢

转载自blog.csdn.net/APPLECHARLOTTE/article/details/127231042