今天要实现的代码是RNN、LSTM、GRU和注意力机制的框架部分。
RNN、LSTM和GRU的框架图如上所示。
import torch
import torch.nn as nn
import torch.nn.functional as F
rnn=nn.RNN(64,4,3) #input_size,hidden_size(隐藏层神经元数),num_layers(隐藏层层数)
input=torch.randn(150,3,64) #sequence_length,batch_size,input_size
h0=torch.randn(3,3,4) #num_layers*num_directions,batch_size,hidden_size
output,hn=rnn(input,h0)
print(output.shape) #sequence_length,batch_size,hidden_size -> (150,3,4)
print(hn.shape) #the same as h0 -> (3,3,4)
lstm=nn.LSTM(64,4,3) #input_size,hidden_size,num_layers
input=torch.randn(150,3,64) #sequence_length,batch_size,input_size
h0=torch.randn(3,3,4) #num_layers*num_directions,batch_size,hidden_size
c0=torch.randn(3,3,4) #num_layers*num_directions,batch_size,hidden_size
output,(hn,cn)=lstm(input,(h0,c0))
print(output.shape) #sequence_length,batch_size,hidden_size -> (150,3,4)
print(hn.shape) #the same as h0 -> (3,3,4)
print(cn.shape) #the same as c0 -> (3,3,4)
gru=nn.GRU(64,4,3) #input_size,hidden_size,num_layers
input=torch.randn(150,3,64) #sequence_length,batch_size,input_size
h0=torch.randn(3,3,4) #num_layers*num_directions,batch_size,hidden_size
output,hn=gru(input,h0)
print(output.shape) #sequence_length,batch_size,hidden_size -> (150,3,4)
print(hn.shape) #the same as h0 -> (3,3,4)
#bmm运算
mat1=torch.randn(10,3,4) #(b,m,n)
mat2=torch.randn(10,4,5) #(b,n,p)
res=torch.bmm(mat1,mat2)
print(res.size()) #(b,m,p) -> (10,3,5)
#torch.bmm运算针对的是两个第一个维度相同的三维张量如(b,m,n)和(b,n,p),运算过程第一个维度不变,后两个维度进行矩阵乘法,则运算结果为(b,m,p)
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])
print(output[0].size()) #对应output的形状 -> (1,1,64)
print(output[1])
print(output[1].size()) #对应attn_weights的形状 -> (1,32)
对于RNN、LSTM和GRU来说,弄清楚什么是input_size,什么是sequence_length很重要。
一种新的torch的运算方式bmm,对两个第一个维度相等的三维矩阵的后两维进行矩阵乘法运算。
对于注意力机制,弄清楚Q、K、V各自的size和维度的意义很重要。
当Q=K=V的时候为自注意力。
需要用案例进行进一步的理解。