RNN的PyTorch实现
官方实现
PyTorch已经实现了一个RNN类,就在torch.nn工具包中,通过torch.nn.RNN调用。
使用步骤:
- 实例化类;
- 将输入层向量和隐藏层向量初始状态值传给实例化后的对象,获得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 torch
import torch.nn as nn
rnn = nn.RNN(10, 20, 1, batch_first=True) # 实例化一个单向单层RNN
input = torch.randn(5, 3, 10)
h0 = torch.randn(1, 5, 20)
output, hn = rnn(input, h0)
手写复现
复现代码
import torch
import torch.nn as nn
class 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