以前初学的时候是使用的RNNCell,是用来迭代的,接收的是两个参数,一个是当前位置输入,一个是之前隐状态,然后就更新得到新的状态。
简要概括为:
输入:当前位置输入[bs,hdim] 之前隐状态[bs,hdim]
输出:现在隐状态[bs,hdim]
bs为批大小,hdim为向量维度。
后来直接使用RNN了,可以输入多个位置的输入,直接得到最后一个位置的隐状态。可以看到,它不需要RNNCell那样手动每一个位置迭代了,屏蔽了内部细节,直接得到最后一个位置的隐状态。
简要概括为:
输入:所有位置输入[bs,seqlen,hdim] 初始隐状态[x,bs,hdim]#这个x和层数以及是否双向有关,不用管,我们默认为1
输出:所有位置隐状态[bs,seqlen,hdim2]以及最后一个位置隐状态[x,bs,hdim]#这个hdim2默认是hdim,除非x不为1。
bs为批大小,seqlen为序列长度,hdim为向量维度。
可以看到,RNN更加方便了,直接得到所有信息,不需要我们手动去循环递归,现在问题是某些场景下需要去递归怎么办呢?一种方法当然是用回RNNCell,但是之前训练模型的时候用的是RNN,不方便换回去重新训练RNNCell,现在想在RNN基础上加一个递归的功能。这个功能可以用来干嘛?当然就是用来测试test或者推断inference喽,例如训练好RNN之后我们要自回归地生成一个句子,AI自动写诗写文章等,这个时候单词是一个一个生成,递归生成的,需要用到这个功能。
import torch
import torch.nn as nn
这里以GRU为例,其实都是差不多的。
rnn=nn.GRU(2,2,batch_first=True)
构造两个位置的输入
x=torch.tensor([1.,2,3,4]).view(1,2,2)#[bs=1,seqlen=2,hdim=2]
可以直接将上述多个位置的输入放到RNN中,
rnn(x)#输出的是所有位置的隐向量以及最后一个位置的隐向量。所以没有毛病。
(tensor([[[-0.5278, 0.0937],
[-0.9068, 0.7536]]], grad_fn=<TransposeBackward1>),
tensor([[[-0.9068, 0.7536]]], grad_fn=<StackBackward>))
得到的结果的含义,前面已经说了,我们现在的任务是,能不能利用RNN递归地得到上述结果呢?
seqlen=x.shape[1]
hiddens=[torch.tensor([0.,0]).view(1,1,-1)]#[1,bs,hdim],其中这个1指代的是方向,可以不管。
for i in range(seqlen):#逐个位置进行传播。
all_hidden,hidden=rnn(x[:,[i]],hiddens[-1])#当前输入以及上一个时刻之后的隐状态。
#输出是所有位置的隐状态以及最后一个时刻的隐状态,我们这里逐位置传播,所以其实只有一个位置,两者相等。
hiddens.append(hidden)
print(hiddens[1:])
注:初始化的隐状态是0。
结果如下,可以看到,和之前的结果一模一样。
[tensor([[[-0.5278, 0.0937]]], grad_fn=), tensor([[[-0.9068, 0.7536]]], grad_fn=)]