Pytorch实现标准RNN、LSTM和GRU

标准RNN

先给出标准RNN的示意图,如下图所示:
从图中可以看到在标准RNN的内部网络中,计算公式为: h t = t a n h ( w i h ∗ x t + b i h + w h h ∗ h t − 1 + b h h ) h_t=tanh(w_{ih}*x_t+b_{ih}+w_{hh}*h_{t-1}+b_{hh}) ht=tanh(wihxt+bih+whhht1+bhh)
在Pytorch中的调用也非常简单,只需 n n . R N N ( ) nn.RNN() nn.RNN()即可。
下面介绍其中的参数。

  • input_size表示输入 x t x_t xt的特征维度;
  • hidden_size表示输出 h t h_t ht的特征维度;
  • num_layers表示网络层数,默认是1层;
  • nonlinearity表示非线性激活函数的选择,默认是tanh,可以选择relu;
  • bias表示是否使用偏置,默认是True;
  • batch_first表示决定网络输入的维度顺序,默认网络输入是按照(seq,batch,feature)输入的,也就是序列长度放在最前面,然后是批量,最后是特征维度,如果这个参数设置为True,那么顺序就变为(batch,seq,feature);
  • dropout表示接受一个 0 ∼ 1 0\sim1 01数值,会在网络中除了最后一层之外的其他输出层加上dropout层;
  • bidirectional默认是False,如果设置为True,就是双向循环神经网络的结构。

接着介绍网络接收的输入和输出。网络会接收一个序列输入 x t x_t xt和记忆输入 h 0 h_0 h0 x t x_t xt的维度是(seq,batch,feature),分别表示序列长度、批量和输入的特征维度, h 0 h_0 h0也叫隐藏状态,它的维度是 ( l a y e r s ∗ d i r e c t i o n , b a t c h , h i d d e n ) (layers*direction,batch,hidden) (layersdirection,batch,hidden),分别表示层数乘上方向(如果是单向就是1,如果是双向就是2)、批量和输出的维度。网络会输出output和 h n h_n hn,output表示网络实际的输出,维度是 ( s e q , b a t c h , h i d d e n ∗ d i r e c t i o n ) (seq,batch,hidden*direction) (seq,batch,hiddendirection),分别表示序列长度、批量和输出维度乘上方向, h n h_n hn表示记忆单元,维度是 ( l a y e r s ∗ d i r e c t i o n , b a t c h , h i d d e n ) (layers*direction,batch,hidden) (layersdirection,batch,hidden),分别表示层数乘上方向、批量和输出维度。
首先建立一个简单的循环神经网络:输入维度是20、输出维度是50、两层的单向网络。

basic_rnn = nn.RNN(input_size=20,hidden_size=50,num_layers=2)

对于定义好的RNN,可以通过weight_ih_l0来访问第一层中的 w i h w_{ih} wih,因为输入 x t x_t xt是20维,输出是50维,所以 w i h w_{ih} wih是一个 50 × 20 的 向 量 50\times20的向量 50×20,另外要访问第二层网络可以使用weight_ih_l1。对于 w h h w_{hh} whh,可以用weight_hh_l0来访问,而 b i h b_{ih} bih则可以用bias_ih_l0来访问。
接着将输入传入网络,验证得到的输出是否如前面介绍的一样。首先随机初始化输入和隐藏状态如下,输入是一个长为100、批量为32、维度为20的张量,隐藏状态的维度按照网络的需求定义如下:

toy_input = torch.randn(100, 32, 20)
h_0 = torch.randn(2, 32, 50)

然后将输入和隐藏状态传入网络,得到输出和更新之后的隐藏状态,输出的长度是100、批量是32、维度是50,和前面介绍的一致,而更新之后的隐藏状态和输入的隐藏状态也是大小相同的。

toy_output,h_n=basic_rnn(toy_input,h_0)
print(toy_output.size())
print(h_n.size())
torch.Size([100, 32, 50])
torch.Size([2, 32, 50])

如果在传入网络的时候不特别注明隐藏状态 h 0 h_0 h0,那么初始的隐藏状态参数全是0,当然也可以用上面的方式来自定义隐藏状态的初始化。

LSTM

LSTM本质上和标准RNN是一样的,只不过LSTM内部的计算更加复杂、参数更多、输入和输出的数目也更多。在Pytorch中,使用 n n . L S T M ( ) nn.LSTM() nn.LSTM()即可。里面的参数和标准RNN中的参数相同,就不再赘述,下面介绍与标准RNN不同的地方。
首先LSTM的参数比标准RNN多,但是访问的方式仍然是相同的,使用weight_ih_l0即可,只是里面的维度和标准RNN不再相同,它是标准RNN维度的4倍,因为LSTM中间比标准RNN多了三个线性变换,多的三个线性变换的权重拼在一起,所以一共是4倍,同理偏置也将是4倍。下面定义一个 简单的LSTM网络如下:

basic_rnn = nn.RNN(input_size=20, hidden_size=50, num_layers=2)
print(basic_rnn.weight_ih_l0.size())
lstm=nn.LSTM(input_size=20, hidden_size=50, num_layers=2)
print(lstm.weight_ih_l0.size())
torch.Size([50, 20])
torch.Size([200, 20])

其次,LSTM的输入也不再只有序列输入和隐藏状态,隐藏状态除了 h 0 h_0 h0以外,还多了一个 C 0 C_0 C0,它们合在一起成为网络的隐藏状态,而且它们的大小完全一样,就是 ( l a y e r ∗ d i r e c t i o n , b a t c h , h i d d e n ) (layer*direction,batch,hidden) (layerdirection,batch,hidden),当然输出也会有 h 0 h_0 h0 C 0 C_0 C0
传入输入,这次没有传入隐藏状态,那么默认就会传入参数全是0的隐藏状态 h 0 h_0 h0 C 0 C_0 C0

lstm_out,(h_n,c_n)=lstm(toy_input)
print(lstm_out.size())
print(h_n.size())
print(c_n.size())
torch.Size([100, 32, 50])
torch.Size([2, 32, 50])
torch.Size([2, 32, 50])

GRU

GRU本质上和LSTM是一样的,下面只简单介绍与LSTM不同的地方。首先它的隐藏状态不再是标准RNN的4倍,而是3倍,这是由于它内部计算结构确定的。同时网络的隐藏状态也不再是 h 0 h_0 h0 C 0 C_0 C0,而是只有 h 0 h_0 h0,其余部分和LSTM完全一样,就不在赘述。
除此之外,Pytorch中还提供了RNNCell、LSTMCell和GRUCell三个单步函数,也就是说它们的输入不再是一个序列,而是一个序列中的一步,也可以说是循环神经网络的一个循环,在序列的应用上更加灵活,因为序列中的每一步都是手动实现的,能够在基础上添加更多自定义的操作。

你可能感兴趣的:(Pytorch)