标准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(wih∗xt+bih+whh∗ht−1+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 0∼1数值,会在网络中除了最后一层之外的其他输出层加上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)
(layers∗direction,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,hidden∗direction),分别表示序列长度、批量和输出维度乘上方向,
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)
(layers∗direction,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)
(layer∗direction,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三个单步函数,也就是说它们的输入不再是一个序列,而是一个序列中的一步,也可以说是循环神经网络的一个循环,在序列的应用上更加灵活,因为序列中的每一步都是手动实现的,能够在基础上添加更多自定义的操作。