0


PyTorch nn.RNN 参数全解析

目录

一、简介

  1. torch.nn.RNN

用于构建循环层,其中的计算规则如下:

  1. h
  2. t
  3. =
  4. tanh
  5. (
  6. W
  7. i
  8. h
  9. x
  10. t
  11. +
  12. b
  13. i
  14. h
  15. +
  16. W
  17. h
  18. h
  19. h
  20. t
  21. 1
  22. +
  23. b
  24. h
  25. h
  26. )
  27. (1)
  28. \boldsymbol{h}_{t}=\tanh({\bf W}_{ih}\boldsymbol{x}_t+\boldsymbol{b}_{ih}+{\bf W}_{hh}\boldsymbol{h}_{t-1}+\boldsymbol{b}_{hh}) \tag{1}
  29. ht​=tanh(Wihxt​+bih​+Whhht1​+bhh​)(1)

其中

  1. h
  2. t
  3. \boldsymbol{h}_{t}
  4. ht
  5. t
  6. t
  7. t 时刻的隐层状态,
  8. x
  9. t
  10. \boldsymbol{x}_{t}
  11. xt
  12. t
  13. t
  14. t 时刻的输入。下标
  15. i
  16. i
  17. i
  18. i
  19. n
  20. p
  21. u
  22. t
  23. input
  24. input 的简写,下标
  25. h
  26. h
  27. h
  28. h
  29. i
  30. d
  31. d
  32. e
  33. n
  34. hidden
  35. hidden 的简写。
  36. W
  37. ,
  38. b
  39. {\bf W},\boldsymbol{b}
  40. W,b 分别是权重和偏置。

二、前置知识

先回顾一下普通的神经网络,我们在训练它的过程中通常会投喂一小批量的数据。不妨设

  1. batch_size
  2. =
  3. N
  4. \text{batch\_size}=N
  5. batch_size=N,则投喂的数据的形式为:
  6. X
  7. =
  8. [
  9. x
  10. 1
  11. T
  12. x
  13. N
  14. T
  15. ]
  16. N
  17. ×
  18. d
  19. {\bf X}= \begin{bmatrix} \boldsymbol{x}_1^{\text T} \\ \vdots \\ \boldsymbol{x}_N^{\text T} \end{bmatrix}_{N\times d}
  20. X=⎣⎢⎡​x1T​⋮xNT​​⎦⎥⎤​N×d

其中

  1. x
  2. i
  3. =
  4. (
  5. x
  6. i
  7. 1
  8. ,
  9. x
  10. i
  11. 2
  12. ,
  13. ,
  14. x
  15. i
  16. d
  17. )
  18. T
  19. \boldsymbol{x}_i=(x_{i1},x_{i2},\cdots,x_{id})^{\text T}
  20. xi​=(xi1​,xi2​,⋯,xid​)T 为特征向量,维数为
  21. d
  22. d
  23. d

在处理序列问题中,我们会将词元转化成对应的特征向量。例如在处理一个英文句子时,我们通常会通过某种手段将每个单词转化为合适的特征向量。设序列(句子)长度为

  1. L
  2. L
  3. L,于是在此情景下,一个句子可以表示为:
  4. seq
  5. i
  6. =
  7. [
  8. x
  9. i
  10. 1
  11. T
  12. x
  13. i
  14. L
  15. T
  16. ]
  17. L
  18. ×
  19. d
  20. \text{seq}_i= \begin{bmatrix} \boldsymbol{x}_{i1}^{\text T} \\ \vdots \\ \boldsymbol{x}_{iL}^{\text T} \end{bmatrix}_{L\times d}
  21. seqi​=⎣⎢⎡​xi1T​⋮xiLT​​⎦⎥⎤​L×d

其中的每个

  1. x
  2. i
  3. j
  4. ,
  5.   
  6. j
  7. =
  8. 1
  9. ,
  10. ,
  11. L
  12. \boldsymbol{x}_{ij},\;j=1,\cdots, L
  13. xij​,j=1,⋯,L 都对应了句子
  14. seq
  15. i
  16. \text{seq}_i
  17. seqi 中的一个单词。在上述约定下,我们**在
  18. t
  19. t
  20. t 时刻**投喂给RNN的数据为:
  21. X
  22. t
  23. =
  24. [
  25. x
  26. 1
  27. t
  28. T
  29. x
  30. N
  31. t
  32. T
  33. ]
  34. N
  35. ×
  36. d
  37. (2)
  38. {\bf X}_t= \begin{bmatrix} \boldsymbol{x}_{1t}^{\text T} \\ \vdots \\ \boldsymbol{x}_{Nt}^{\text T} \end{bmatrix}_{N\times d}\tag{2}
  39. Xt​=⎣⎢⎡​x1tT​⋮xNtT​​⎦⎥⎤​N×d​(2)

从而

  1. (
  2. 1
  3. )
  4. (1)
  5. (1) 式改写为
  6. H
  7. t
  8. =
  9. tanh
  10. (
  11. X
  12. t
  13. W
  14. i
  15. h
  16. +
  17. b
  18. i
  19. h
  20. +
  21. H
  22. t
  23. 1
  24. W
  25. h
  26. h
  27. +
  28. b
  29. h
  30. h
  31. )
  32. (3)
  33. {\bf H}_t=\tanh({\bf X}_t{\bf W}_{ih}+\boldsymbol{b}_{ih}+{\bf H}_{t-1}{\bf W}_{hh}+\boldsymbol{b}_{hh})\tag{3}
  34. Ht​=tanh(XtWih​+bih​+Ht1Whh​+bhh​)(3)

其中

  1. H
  2. t
  3. ,
  4. H
  5. t
  6. 1
  7. {\bf H}_t,{\bf H}_{t-1}
  8. Ht​,Ht1 的形状为
  9. N
  10. ×
  11. h
  12. N\times h
  13. N×h
  14. W
  15. i
  16. h
  17. {\bf W}_{ih}
  18. Wih 的形状为
  19. d
  20. ×
  21. h
  22. d\times h
  23. d×h
  24. W
  25. h
  26. h
  27. {\bf W}_{hh}
  28. Whh 的形状为
  29. h
  30. ×
  31. h
  32. h\times h
  33. h×h
  34. b
  35. i
  36. h
  37. ,
  38. b
  39. h
  40. h
  41. \boldsymbol{b}_{ih},\boldsymbol{b}_{hh}
  42. bih​,bhh 的形状为
  43. 1
  44. ×
  45. h
  46. 1\times h
  47. 1×h,求和时利用广播机制。

  1. nn.RNN

中,我们是一次性将所有时刻的数据全部投喂进去,数据形式为:

  1. X
  2. =
  3. [
  4. seq
  5. 1
  6. ,
  7. seq
  8. 2
  9. ,
  10. ,
  11. seq
  12. N
  13. ]
  14. N
  15. ×
  16. L
  17. ×
  18. d
  19. or
  20. X
  21. =
  22. [
  23. X
  24. 1
  25. ,
  26. X
  27. 2
  28. ,
  29. ,
  30. X
  31. L
  32. ]
  33. L
  34. ×
  35. N
  36. ×
  37. d
  38. {\bf X}=[\text{seq}_1,\text{seq}_2,\cdots,\text{seq}_N]_{N\times L\times d}\quad\text{or}\quad {\bf X}=[{\bf X}_1,{\bf X}_2,\cdots,{\bf X}_L]_{L\times N\times d}
  39. X=[seq1​,seq2​,⋯,seqN​]N×L×dorX=[X1​,X2​,⋯,XL​]L×N×d

其中左边代表

  1. batch_first=True

的情形,右边代表

  1. batch_first=False

的情形。

注意: 在一个 batch 中,所有 sequence 的长度要保持相同,即

  1. L
  2. L
  3. L 需一致。

三、解析

3.1 所有参数

在这里插入图片描述

有了前置知识后,我们就能很方便的解释这些参数了。

  • input_size:即 d d d;
  • hidden_size:即 h h h;
  • num_layers:即RNN的层数。默认是 1 1 1 层。该参数大于 1 1 1 时,会形成 Stacked RNN,又称多层RNN或深度RNN;
  • nonlinearity:即非线性激活函数。可以选择 tanhrelu,默认是 tanh
  • bias:即偏置。默认启用,可以选择关闭;
  • batch_first:即是否选择让 batch_size 作为输入的形状中的第一个参数。当 batch_first=True 时,输入应具有 N × L × d N\times L\times d N×L×d 这样的形状,否则应具有 L × N × d L\times N\times d L×N×d 这样的形状。默认是 False
  • dropout:即是否启用 dropout。如要启用,则应设置 dropout 的概率,此时除最后一层外,RNN的每一层后面都会加上一个dropout层。默认是 0 0 0,即不启用;
  • bidirectional:即是否启用双向RNN,默认关闭。

3.2 输入参数

在这里插入图片描述
这里我们只考虑有 batch 的情况。

  1. batch_first=True

时,输入

  1. input

应具有形状

  1. N
  2. ×
  3. L
  4. ×
  5. d
  6. N\times L\times d
  7. N×L×d,否则应具有形状
  8. L
  9. ×
  10. N
  11. ×
  12. d
  13. L\times N\times d
  14. L×N×d
  1. h_0

为初始时刻的隐状态。当RNN为单向RNN时,

  1. h_0

的形状应为

  1. num_layers
  2. ×
  3. N
  4. ×
  5. h
  6. \text{num\_layers}\times N\times h
  7. num_layers×N×h;当RNN为双向RNN时,
  1. h_0

的形状应为

  1. (
  2. 2
  3. num_layers
  4. )
  5. ×
  6. N
  7. ×
  8. h
  9. (2\cdot \text{num\_layers})\times N\times h
  10. (2num_layersN×h。如不提供该参数的值,则默认为全0张量。

3.3 输出参数

在这里插入图片描述

这里我们只考虑有 batch 的情况。

当RNN为单向RNN时:若

  1. batch_first=True

,输出

  1. output

具有形状

  1. N
  2. ×
  3. L
  4. ×
  5. h
  6. N\times L\times h
  7. N×L×h,否则具有形状
  8. L
  9. ×
  10. N
  11. ×
  12. h
  13. L\times N\times h
  14. L×N×h。当
  1. batch_first=False

时,

  1. output[t, :, :]

代表时刻

  1. t
  2. t
  3. t 时,RNN最后一层(之所以用最后一层这个术语是因为有可能出现Stacked RNN情形)的输出
  4. h
  5. t
  6. \boldsymbol{h}_t
  7. ht​。
  1. h_n

代表最终的隐状态,形状为

  1. num_layers
  2. ×
  3. N
  4. ×
  5. h
  6. \text{num\_layers}\times N\times h
  7. num_layers×N×h

当RNN为双向RNN时:若

  1. batch_first=True

,输出

  1. output

具有形状

  1. N
  2. ×
  3. L
  4. ×
  5. 2
  6. h
  7. N\times L\times 2h
  8. N×L×2h,否则具有形状
  9. L
  10. ×
  11. N
  12. ×
  13. 2
  14. h
  15. L\times N\times 2h
  16. L×N×2h
  1. h_n

的形状为

  1. (
  2. 2
  3. num_layers
  4. )
  5. ×
  6. N
  7. ×
  8. h
  9. (2\cdot \text{num\_layers})\times N\times h
  10. (2num_layersN×h

事实上,对于单向RNN,有

  1. output
  2. =
  3. [
  4. H
  5. 1
  6. ,
  7. H
  8. 2
  9. ,
  10. ,
  11. H
  12. L
  13. ]
  14. L
  15. ×
  16. N
  17. ×
  18. h
  19. ,
  20. h_n
  21. =
  22. [
  23. H
  24. L
  25. ]
  26. 1
  27. ×
  28. N
  29. ×
  30. h
  31. \text{output}=[{\bf H}_1,{\bf H}_2,\cdots,{\bf H}_L]_{L\times N\times h},\quad \text{h\_n}=[{\bf H}_L]_{1\times N\times h}
  32. output=[H1​,H2​,⋯,HL​]L×N×h​,h_n=[HL​]1×N×h

四、通过例子来进一步理解 nn.RNN

以单隐层单向RNN为例(接下来的例子都默认

  1. batch_first=False

)。

假设有一个英文句子:

  1. He ate an apple.

,忽略

  1. .

并设置词元为单词(word)时,该序列的长度为

  1. 4
  2. 4
  3. 4。简便起见,我们假设每个词元都对应了一个
  4. 6
  5. 6
  6. 6 维的特征向量,则上述的序列可写成:
  1. import torch
  2. import torch.nn as nn
  3. torch.manual_seed(42)
  4. seq = torch.randn(4,6)# 只是为了举例print(seq)# tensor([[ 1.9269, 1.4873, 0.9007, -2.1055, 0.6784, -1.2345],# [-0.0431, -1.6047, 0.3559, -0.6866, -0.4934, 0.2415],# [-1.1109, 0.0915, -2.3169, -0.2168, -0.3097, -0.3957],# [ 0.8034, -0.6216, -0.5920, -0.0631, -0.8286, 0.3309]])

将这个句子视为一个 batch,即(注意形状为

  1. L
  2. ×
  3. N
  4. ×
  5. d
  6. L\times N\times d
  7. L×N×d):
  1. inputs = seq.unsqueeze(1)print(inputs)# tensor([[[ 1.9269, 1.4873, 0.9007, -2.1055, 0.6784, -1.2345]],# [[-0.0431, -1.6047, 0.3559, -0.6866, -0.4934, 0.2415]],# [[-1.1109, 0.0915, -2.3169, -0.2168, -0.3097, -0.3957]],# [[ 0.8034, -0.6216, -0.5920, -0.0631, -0.8286, 0.3309]]])print(inputs.shape)# torch.Size([4, 1, 6])

有了

  1. inputs

,我们还需要初始化隐状态

  1. h_0

,不妨设

  1. h
  2. =
  3. 3
  4. h=3
  5. h=3
  1. h_0 = torch.randn(1,1,3)print(h_0)# tensor([[[ 1.3525, 0.6863, -0.3278]]])

接下来创建RNN层,事实上只需要输入

  1. input_size

  1. hidden_size

即可:

  1. rnn = nn.RNN(6,3)

观察输出:

  1. outputs, h_n = rnn(inputs, h_0)print(outputs)# tensor([[[-0.5428, 0.9207, 0.7060]],# [[-0.2245, 0.2461, -0.4578]],# [[ 0.5950, -0.3390, -0.4598]],# [[ 0.9281, -0.7660, 0.5954]]], grad_fn=<StackBackward0>)print(h_n)# tensor([[[ 0.9281, -0.7660, 0.5954]]], grad_fn=<StackBackward0>)

五、从零开始手写一个单隐层单向RNN

首先写好框架:

  1. classRNN(nn.Module):def__init__(self, input_size, hidden_size):super().__init__()passdefforward(self, inputs, h_0):pass

我们的计算遵循

  1. (
  2. 3
  3. )
  4. (3)
  5. (3) 式,即:
  6. H
  7. t
  8. =
  9. tanh
  10. (
  11. X
  12. t
  13. W
  14. i
  15. h
  16. +
  17. b
  18. i
  19. h
  20. +
  21. H
  22. t
  23. 1
  24. W
  25. h
  26. h
  27. +
  28. b
  29. h
  30. h
  31. )
  32. {\bf H}_t=\tanh({\bf X}_t{\bf W}_{ih}+\boldsymbol{b}_{ih}+{\bf H}_{t-1}{\bf W}_{hh}+\boldsymbol{b}_{hh})
  33. Ht​=tanh(XtWih​+bih​+Ht1Whh​+bhh​)
  1. classRNN(nn.Module):def__init__(self, input_size, hidden_size):super().__init__()
  2. self.W_ih = torch.randn(input_size, hidden_size)
  3. self.W_hh = torch.randn(hidden_size, hidden_size)
  4. self.b_ih = torch.randn(1, hidden_size)
  5. self.b_hh = torch.randn(1, hidden_size)defforward(self, inputs, h_0):
  6. L, N, d = inputs.shape # 分别对应序列长度、批量大小和特征维度
  7. H = h_0[0]# 因为h_0的形状为(1,N,h),我们需要使用(N,h)去计算
  8. outputs =[]# 用来存储h_1,h_2,...,h_Lfor t inrange(L):
  9. X_t = inputs[t]
  10. H = torch.tanh(X_t @ self.W_ih + self.b_ih + H @ self.W_hh + self.b_hh)
  11. outputs.append(H)
  12. h_n = outputs[-1].unsqueeze(0)# h_n实际上就是h_L,但此时的形状为(N,h)
  13. outputs = torch.cat(outputs,0).unsqueeze(1)return outputs, h_n

为了检验我们的RNN是正确的,我们需要使用相同的输入来验证我们的输出是否与之前的一致。

  1. torch.manual_seed(42)
  2. seq = torch.randn(4,6)
  3. inputs = seq.unsqueeze(1)
  4. h_0 = torch.randn(1,1,3)# 保持RNN内部参数:权重和偏置一致
  5. rnn = nn.RNN(6,3)
  6. params =[param.data.T for param in rnn.parameters()]
  7. my_rnn = RNN(6,3)
  8. my_rnn.W_ih = params[0]
  9. my_rnn.W_hh = params[1]
  10. my_rnn.b_ih[0]= params[2]
  11. my_rnn.b_hh[0]= params[3]
  12. outputs, h_n = my_rnn(inputs, h_0)print(outputs)# tensor([[[-0.5428, 0.9207, 0.7060]],# [[-0.2245, 0.2461, -0.4578]],# [[ 0.5950, -0.3390, -0.4598]],# [[ 0.9281, -0.7660, 0.5954]]])print(h_n)# tensor([[[ 0.9281, -0.7660, 0.5954]]])

可以看出结果与之前的一致,这说明我们构造的RNN是正确的。

最后

博主才疏学浅,如有错误请在评论区指出,感谢!


本文转载自: https://blog.csdn.net/raelum/article/details/125311936
版权归原作者 raelum 所有, 如有侵权,请联系我们删除。

“PyTorch nn.RNN 参数全解析”的评论:

还没有评论