官方实现

PyTorch已经实现了一个RNN类,就在torch.nn工具包中,通过torch.nn.RNN调用。

使用步骤:

  1. 实例化类;
  2. 将输入层向量和隐藏层向量初始状态值传给实例化后的对象,获得RNN的输出。

在实例化该类时,需要传入如下属性:

  • input_size:输入层神经元个数;
  • hidden_size:每层隐藏层的神经元个数;
  • num_layers:隐藏层层数,默认设置为1层;
  • nonlinearity:激活函数的选择,可选是’tanh’或者’relu’,默认设置为’tanh’;
  • bias:偏置系数,可选是’True’或者’False’,默认设置为’True’;
  • batch_first:可选是’True’或者’False’,默认设置为’False’;
  • dropout:默认设置为0。若为非0,将在除最后一层的每层RNN输出上引入Dropout层,dropout概率就是该非零值;
  • bidirectional:默认设置为False。若为True,即为双向RNN。

RNN的输入有两个,一个是input,一个是h0。input就是输入层向量,h0就是隐藏层初始状态值。
若没有采用批量输入,则输入层向量的形状为(L, Hin);
若采用批量输入,且batch_first为False,则输入层向量的形状为(L, N, Hin);
若采用批量输入,且batch_first为True,则输入层向量的形状为(N, L, Hin);
对于(N, L, Hin),在文本输入时,可以按顺序理解为(每次输入几句话,每句话有几个字,每个字由多少维的向量表示)。

若没有采用批量输入,则隐藏层向量的形状为(D * num_layers, Hout);
若采用批量输入,则隐藏层向量的形状为(D * num_layers, N, Hout);
注意,batch_first的设置对隐藏层向量的形状不起作用。

RNN的输出有两个,一个是output,一个是hn。output包含了每个时间步最后一层的隐藏层状态,hn包含了最后一个时间步每层的隐藏层状态。
若没有采用批量输入,则输出层向量的形状为(L, D * Hout);
若采用批量输入,且batch_first为False,则输出层向量的形状为(L, N, D * Hout);
若采用批量输入,且batch_first为True,则输出层向量的形状为(N, L, D * Hout)。

参数解释:

  • N代表的是批量大小;
  • L代表的是输入的序列长度;
  • 若是双向RNN,则D的值为2;若是单向RNN,则D的值为1;
  • Hin在数值上是输入层神经元个数;
  • Hout在数值上是隐藏层神经元个数。
import torchimport torch.nn as nnrnn = nn.RNN(10, 20, 1, batch_first=True)  # 实例化一个单向单层RNNinput = torch.randn(5, 3, 10)h0 = torch.randn(1, 5, 20)output, hn = rnn(input, h0)

手写复现复现代码

import torchimport torch.nn as nnclass MyRNN(nn.Module):    def __init__(self, input_size, hidden_size):        super().__init__()        self.input_size = input_size        self.hidden_size = hidden_size        self.weight_ih = torch.randn(self.hidden_size, self.input_size) * 0.01        self.weight_hh = torch.randn(self.hidden_size, self.hidden_size) * 0.01        self.bias_ih = torch.randn(self.hidden_size)        self.bias_hh = torch.randn(self.hidden_size)            def forward(self, input, h0):        N, L, input_size = input.shape        output = torch.zeros(N, L, self.hidden_size)        for t in range(L):            x = input[:, t, :].unsqueeze(2)  # 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第n维上增加一维            w_ih_batch = self.weight_ih.unsqueeze(0).tile(N, 1, 1)  # [N, hidden_size, input_size]            w_hh_batch = self.weight_hh.unsqueeze(0).tile(N, 1, 1)  # [N, hidden_size, hidden_size]            w_times_x = torch.bmm(w_ih_batch, x).squeeze(-1)  # [N, hidden_size]。squeeze(n),在第n维上减小一维            w_times_h = torch.bmm(w_hh_batch, h0.unsqueeze(2)).squeeze(-1)  # [N, hidden_size]            h0 = torch.tanh(w_times_x + self.bias_ih  + w_times_h + self.bias_hh)            output[:, t, :] = h0        return output, h0.unsqueeze(0)

验证正确性

my_rnn = MyRNN(10, 20)input = torch.randn(5, 3, 10)h0 = torch.randn(5, 20)my_output, my_hn = my_rnn(input, h0)print(output.shape == my_output.shape, hn.shape == my_hn.shape)
True True

主要参考

官方说明文档