MENU

RNN Layer

January 27, 2020 • Read: 8936 • Deep Learning阅读设置

一个 RNN Layer 如下图所示

假设 x 的 shape 是 [10, 3, 100],翻译一下就是,10 个单词,每次训练 3 句话,每个单词用一个 100 维的 tensor 来表达

那么对于输入 $x_t$ 来说,$x_t$ 的 shape 就是 [3 100]

接着再看上面的运算过程,其中 hidden len 就是 memory 的维度,假设是 20。因此:

$$ \begin{align*} h_{t+1} &= x_t @ w_{xh} + h_t @ w_{hh}\\ &= [3, 100] @ [20, 100]^T + [3, 20] @ [20, 20]^T \\ &= [3, 20] \end{align*} $$

nn.RNN

用代码定义一个 RNN Layer,然后查看其参数信息

  • import torch
  • import torch.nn as nn
  • rnn = nn.RNN(100, 20)
  • print(rnn._parameters.keys())
  • print(rnn.weight_ih_l0.shape) # w_{xh} [20, 100]
  • print(rnn.weight_hh_l0.shape) # w_{hh} [20, 20]
  • print(rnn.bias_ih_l0.shape) # b_{xh} [20]
  • print(rnn.bias_hh_l0.shape) # b_{hh} [20]

解释上面的代码前先看一下 PyTorch 中 RNN 类的参数(参考于 PyTorch 官网 RNN API

  • 必选参数 input_size,指定输入序列中单个样本的尺寸大小,例如可能用一个 1000 长度的向量表示一个单词,则 input_size=1000
  • 必选参数 hidden_size,指的是隐藏层中输出特征的大小
  • 必选参数 num_layers,指的是纵向的隐藏层个数,一般设置为 1~10,default=1

现在上面的代码就很好理解了,nn.RNN(100, 20) 中 100 指的是用一个长度为 100 的向量表示一个单词,20 指的是 hidden_size

RNN 的 forward 函数与 CNN 定义的方式有点不太一样,具体见下图

参数中的 $x$ 不是 $x_t$,就是直接把 $x=[seq\_len, batch, feature\_len]$ 带进去

$h_0$ 如果不写默认就是 0,如果写的话,$h_0$ 的维度是 $[layers, batch, hidden\_len]$

看下代码

  • import torch
  • import torch.nn as nn
  • rnn = nn.RNN(input_size=100, hidden_size=20, num_layers=1)
  • x = torch.randn(10, 3, 100)
  • out, h_t = rnn(x, torch.zeros(1, 3, 20))
  • print(out.shape) # [10, 3, 20]
  • print(h_t.shape) # [1, 3, 20]

每个地方参数的 shape 都是有关联的,必须要把上面我写的内容看懂了才能理解

$h_t$ 和 $out$ 很容易搞混,我们先看一个 2 层的 RNN 模型

在解释 $h_t$ 和 $out$ 之前要先理解一个概念 —— 时间戳,时间戳是针左右而不是上下,什么意思呢,就是上图是一个两层的 RNN,假设这两层的 RNN 右边分别又各接一层,那这样的左右结构就是时间戳,基于此,给出 $h_t$ 和 $out$ 的定义:

  • $h_t$:最后一个时间戳上面所有的 memory 状态
  • $out$:所有时间戳上的最后一个 memory 状态

而第几个 memory 是针对层来说的,比方说第一层的 memory 就是第一个 memory,最后一层的 memory 就是最后一个 memory

看下代码

  • import torch
  • import torch.nn as nn
  • rnn = nn.RNN(input_size=100, hidden_size=20, num_layers=4)
  • x = torch.randn(10, 3, 100)
  • out, h_t = rnn(x)
  • print(out.shape) # [10, 3, 20]
  • print(h_t.shape) # [4, 3, 20]

如果理解了上面 $out$ 和 $h_t$ 的 shape,这里的输出也就不难想到了

上面 nn.RNN() 的定义方式是直接把整个 $x$ 输入,自动完成循环。下面再介绍一种定义 RNN 的方式,需要手动完成循环

nn.RNNCell

先看一下 PyTorch 的官方 API

参数和 nn.RNN 大体相似,但是注意 input_size 的 shape 是 (batch, input_size),而且 hidden_size 的 shape 也是 (batch, hidden_size),这就导致 forward 也不一样

看下代码

  • import torch
  • import torch.nn as nn
  • cell1 = nn.RNNCell(100, 20)
  • x = torch.randn(10, 3, 100)
  • h1 = torch.zeros(3, 20)
  • for xt in x:
  • h1 = cell1(xt, h1)
  • print(h1.shape) # [3, 20]

上面就就是一层的 RNN,用 RNNCell 的方式,手动循环进行训练

下面在看一个两层的 RNN,用 RNNCell 的方式怎么做

  • import torch
  • import torch.nn as nn
  • cell1 = nn.RNNCell(100, 30) # 100 -> 30
  • cell2 = nn.RNNCell(30, 20)
  • x = torch.randn(10, 3, 100)
  • h1 = torch.zeros(3, 30)
  • h2 = torch.zeros(3, 20)
  • for xt in x:
  • h1 = cell1(xt, h1)
  • h2 = cell2(h1, h2)
  • print(h2.shape) # [3, 20]

第一层的作用是将一个 100 维的输入变为 30 维的 memory 输出,然后将输出带入到第二层,第二层的输出是一个 20 维的 memory。最重要的代码是 for 中的两句话,第一层的输入是 xt 和 memory h1,第二层的输入是第一层的 memory h1,以及第二层的 memory h2

Last Modified: December 31, 2020
Archives Tip
QR Code for this page
Tipping QR Code
Leave a Comment

4 Comments
  1. Jimmy Jimmy

    大佬,第二张图下面的 @Whh 是 [20,20] 的转置,不是 [20,100] 吧?

    1. mathor mathor

      @Jimmy 已修改,感谢提醒

  2. gcman gcman

    大佬太强了,本来迷迷糊糊的,看了这篇一下就懂了

  3. amber amber

    真是好登西啊好登西,谢谢大佬