文章目录

  • 一、注意力机制
  • 1.基本框架
  • 2.实际例子
  • 二、torch的squeeze和unsqueeze操作
  • 1.squeeze
  • 2.unsqueeze
  • 总结



一、注意力机制

1.基本框架

import torch
import torch.nn as nn
import torch.nn.function as F

class Attn(nn.Module):
    def __init__(self,query_size,key_size,value_size1,value_size2,output_size):
        #query_size代表的是Q的最后一个维度,key_size代表K的最后一个维度
        #V的尺寸表示(1,value_size1,value_size2)
        #output_size代表输出的最后一个维度的大小
        super(Attn,self).__init__()
        self.query_size=query_size
        self.key_size=key_size
        self.value_size1=value_size1
        self.value_size2=value_size2
        self.output_size=output_size

        #初始化注意力机制实现中第一步的线性层
        self.attn=nn.Linear(self.query_size+self.key_size,self.value_size1)

        #初始化注意力机制实现中第三步的线性层
        self.attn_combine=nn.Linear(self.query_size+self.value_size2,self.output_size)

    def forward(self,Q,K,V):
        #注意我们假定Q,K,V都是三维张量
        #第一步,将Q,K进行纵轴的拼接,然后做一次线性变换,最后使用softmax进行处理得到注意力向量
        attn_weights=F.softmax(self.attn(torch.cat((Q[0],K[0]),1)),dim=1)
            
        #将注意力矩阵和V进行一次bmm运算
        attn_applied=torch.bmm(attn_weights.unsqueeze(0),V)

        #再次去Q[0]进行降维,再次和上面的运算结果进行一次拼接
        output=torch.cat((Q[0],attn_applied[0]),1)

        #第三步就是将上面的输出进行一次线性变换,然后再扩展维度成3维张量
        output=self.attn_combine(output).unsqueeze(0)
        return output,attn_weights

query_size=32
key_size=32
value_size1=32
value_size2=64
output_size=64

attn=Attn(query_size,key_size,value_size1,value_size2,output_size)
Q=torch.randn(1,1,32)
K=torch.randn(1,1,32)
V=torch.randn(1,32,64)
output=attn(Q,K,V)
print(output[0])
#结果为tensor([[[ 0.2311,  0.3643, -0.3394,  0.2316, -0.1764, -0.1787, -0.5936,
          -0.1706,  0.2430,  0.1561,  0.1066,  0.0730, -0.0439,  0.2775,
          -0.1694, -0.0690, -0.6398,  0.2985, -0.5118, -0.3733, -0.2123,
          -0.8069,  0.0567,  0.2429,  0.2462,  0.5775,  0.2361,  0.0145,
          -0.1076,  0.1315, -0.8674,  0.7022,  0.6434,  0.3773,  0.6877,
           0.0146,  0.0937,  0.2458, -0.2988,  0.1753,  0.1406, -0.0409,
           0.4365,  0.4243, -0.0839,  0.0506, -0.1254, -0.2220,  0.1707,
           0.1370,  0.1591, -0.0595,  0.5210,  0.1402,  0.0017,  0.5091,
          -0.2914, -0.2598, -0.0708,  0.6379, -0.2135,  0.2534, -0.3521,
           0.4372]]], grad_fn=<UnsqueezeBackward0>)
print(output[0].size())
#结果为torch.Size([1, 1, 64])
#这里的结果与output_size相对应

print(output[1])
#结果为tensor([[0.0355, 0.0190, 0.0383, 0.0643, 0.0146, 0.0308, 0.0236, 0.0254, 0.0266,
         0.0407, 0.0212, 0.0102, 0.0253, 0.0257, 0.0239, 0.0269, 0.0411, 0.0245,
         0.0383, 0.0299, 0.0180, 0.0689, 0.0170, 0.0293, 0.0383, 0.0249, 0.0576,
         0.0325, 0.0287, 0.0672, 0.0100, 0.0220]], grad_fn=<SoftmaxBackward>)
print(output[1].size())
#结果为torch.Size([1, 32])
#这里的结果是attn_weights,作为output[1]的返回值

2.实际例子

MAX_LENGTH=10
#构建基于GRU和Attention的解码器类
class AttnDecoderRNN(nn.Module):
    def __init__(self,hidden_size,output_size,dropout_p=0.1,max_length=MAX_LENGTH):
        #hidden_size:代表解码器的GRU输出尺寸,就是隐藏层的神经元个数
        #output_size:指定的网络输出尺寸,代表目标语言的词汇总数(法文)
        #dropout_p:使用Dropout层的置零比例
        #max_lenth:代表句子的最大长度
        super(AttnDecoderRNN,self).__init__()
        #将参数传入类中
        self.hidden_size=hidden_size
        self.output_size=output_size
        self.dropout_p=dropout_p
        self.max_length=max_length

        #实例化一个Embedding对象,参数是目标语言的词汇总数和词嵌入的维度
        self.embedding=nn.Embedding(output_size,hidden_size)

        #实例化第一个注意力层,注意输入是两个张良的合并
        self.attn=nn.Linear(self.hidden_size*2,self.max_length)

        #实例化第二个注意力层,注意输入也是两个张量的合并,同时输出要进入GRU中
        self.attn_combine=nn.Linear(self.hidden_size*2,self.hidden_size)

        #实例化nn.Dropout层
        self.dropout=nn.Dropout(self.dropout_p)

        #实例化GRU单元
        self.gru=nn.GRU(self.hidden_size,self.hidden_size)

        #实例化GRU之后的线性层,作为整个解码器的输出
        self.out=nn.Linear(self.hidden_size,self.output_size)

    def forward(self,input1,hidden,encoder_output):
        #input1:源数据的输入张量
        #hidden:初始化的隐藏层张量
        #encoder_output:代表编码器的输出张量
        #对输入input1进行词嵌入处理,并扩展维度
        embedded=self.embedding(input1).view(1,1,-1)
        #紧接着将其输入dropout层,防止过拟合
        embedded=self.dropout(embedded)

        #在进行第一个注意力层处理前,要将Q,K进行纵轴拼接
        attn_weights=F.softmax(self.attn(torch.cat((embedded[0],hidden[0]),1)),dim=1)

        #进行bmm操作,注意要将二维张量扩展成三维张量
        attn_applied=torch.bmm(attn_weights.unsqueeze(0),encoder_output.unsqueeze(0))

        #再次进行拼接,顺便要进行一次降维
        output=torch.cat((embedded[0],attn_applied[0]),1)

        #将output输入第二个注意力层
        output=self.attn_combine(output).unsqueeze(0)

        #使用relu进行激活层处理
        output=F.relu(output)

        #将激活后的张量,连同隐藏层张量,一起出入GRU中
        output,hidden=self.gru(output,hidden)

        #最后将结果先降维,然后线性层处理成指定的输出维度,最后经过softmax处理
        output=F.log_softmax(self.out(output[0]),dim=1)

        #返回解码器的最终输出结果,最后的隐藏层张量,注意力权重张量
        return output,hidden,attn_weights

    def initHidden(self):
        #初始化一个全零的隐藏层张量,形状为1*1*self.hidden_size
        return torch.zeros(1,1,self.hidden_size)

hidden_size=25
output_size=10
input1=torch.tensor([2])
hidden=torch.zeros(1,1,hidden_size)
encoder_output=torch.randn(10,25)

decoder_attn=AttnDecoderRNN(hidden_size,output_size)
output,hidden,attn_weights=decoder_attn(input1,hidden,encoder_output)
print(output)
#这里的结果为tensor([[-2.4426, -2.4968, -2.1501, -2.2232, -2.3987, -2.1430, -2.5029, -2.1484, -2.4032, -2.2172]], grad_fn=<LogSoftmaxBackward>)
print(output.shape)
#这里的将结果为torch.Size([1, 10])
#和output_size相对应
print(hidden.shape)
#这里的结果为torch.Size([1, 1, 25])
#和hidden_size相对应
print(attn_weights)
#这里的结果为tensor([[0.1824, 0.0739, 0.0755, 0.1670, 0.1002, 0.1325, 0.1195, 0.0666, 0.0489, 0.0334]], grad_fn=<SoftmaxBackward>)
print(attn_weights.shape)
#这里的结果为torch.Size([1, 10])
#这里为什么是10呢???和output_size相对应嘛???

二、torch的squeeze和unsqueeze操作

1.squeeze

import torch
a=torch.randn(1,3,1)
print(a)
#结果为tensor([[[-2.1732],
         [ 1.3840],
         [-3.3431]]])
print(a.size())
#结果为torch.Size([1, 3, 1])

b=a.squeeze(-1)
print(b)
#结果为tensor([[-2.1732,  1.3840, -3.3431]])
print(b.size())
#结果为torch.Size([1, 3])
#实现了降维操作,降了最后一位

c=a.squeeze(0)
print(c)
#结果为tensor([[-2.1732],
        [ 1.3840],
        [-3.3431]])
print(c.size())
#结果为torch.Size([3, 1])
#实现了降维操作,降了第一维

d=a.squeeze(1)
print(d)
#结果为tensor([[[-2.1732],
         [ 1.3840],
         [-3.3431]]])
print(d.size())
#结果为torch.Size([1, 3, 1])
#没有变化

2.unsqueeze

e=a.unsqueeze(-1)
print(e)
#结果为tensor([[[[-2.1732]],

         [[ 1.3840]],

         [[-3.3431]]]])
print(e.size())
#结果为torch.Size([1, 3, 1, 1])
#实现了维度的扩充,最后一个维度升了一维

f=a.unsqueeze(0)
print(f)
#结果为tensor([[[[-2.1732],
          [ 1.3840],
          [-3.3431]]]])
print(f.size())
#结果为torch.Size([1, 1, 3, 1])
#实现了维度的扩充,第一个维度升了一维

g=a.unsqueeze(1)
print(g)
#结果为tensor([[[[-2.1732],
          [ 1.3840],
          [-3.3431]]]])
print(g.size())
#结果为torch.Size([1, 1, 3, 1])
#和0的结果一样

squeeze(有积压的意思因此)是降维,unsqueeze是升维度,其中的参数0和-1比较有用,0是对第一个维度进行操作,-1则是对最后一个维度进行操作。


总结

之前只是通过代码实现了一个简单的注意力机制的框架和一个英译法的案例中的decoder用到了注意力机制,这里想更加具体地认识注意力机制的原理,进而将自己的数据更好地输入注意力机制中,对之有更好的应用,就可以进行接下来的transformer的学习啦。另一个方面,补充一下之前没有完全弄清楚的squeeze操作和unsqueeze操作,算是对torch运算的补充,和更充分的掌握,之后可能会再填一下torch.cat操作的坑。