单向/双向,单层/多层RNN输入输出维度问题

单向/双向,单层/多层RNN输入输出维度问题


RNN

单层单向RNN

Rnn Cell

循环神经网络最原始的Simple RNN实现如下图所示:

下面写出单个时间步对应的Rnn Cell计算公式:

如果用矩阵运算视角来看待的话,单个时间步对应的Rnn Cell计算过程如下图所示:

代码示例如下:

python 复制代码
import torch

batch_size = 2   # 批量大小
seq_len = 3      # 序列长度(单个RNN层包含的Time Step数量)
input_size = 4   # 输入数据的维度
hidden_size = 2  # 隐藏层的维度

cell = torch.nn.RNNCell(input_size=input_size, hidden_size=hidden_size)
dataset = torch.rand(seq_len, batch_size, input_size)
hidden = torch.zeros(batch_size, hidden_size)

# 使用Rnn Cell加循环实现一个Rnn层处理一批输入序列的过程
for idx, input in enumerate(dataset):
    print('='*10,idx,'='*10)
    print('input size:', input.shape)
    hidden = cell(input, hidden)
    print('outputs size:', hidden.shape)
    print(hidden)

输出:

js 复制代码
========== 0 ==========
input size: torch.Size([2, 4])
outputs size: torch.Size([2, 2])
tensor([[ 0.1077, -0.7972],
        [-0.1070, -0.9517]], grad_fn=<TanhBackward0>)
========== 1 ==========
input size: torch.Size([2, 4])
outputs size: torch.Size([2, 2])
tensor([[-0.4524, -0.8874],
        [ 0.0443, -0.9716]], grad_fn=<TanhBackward0>)
========== 2 ==========
input size: torch.Size([2, 4])
outputs size: torch.Size([2, 2])
tensor([[-0.2941, -0.9751],
        [-0.3554, -0.9399]], grad_fn=<TanhBackward0>)

图解运算过程:

  1. 输入数据采用三维来表示 (序列长度,批量大小,输入向量维度) , 将序列长度作为第0维是因为这样可以很方便的将同属于一个Time Step处理的批次序列数据第i个词一次性送入Rnn Cell中处理。
  1. 隐藏层输出的向量为Rnn Cell对当前Time Step输入的 x t x_{t} xt和 h t − 1 h_{t-1} ht−1做完信息融合和空间变换后得到的结果,其维度可以保持与输入 x t x_{t} xt一致或者降低维度。
  1. 由Rnn Cell内的权重矩阵 W i h W_{ih} Wih负责完成对输入 x t x_{t} xt的线性变换,使其由输入维度input_size变为hidden_size; 然后通过加法运算与同样处理的 h t − 1 h_{t-1} ht−1做信息融合,再通过tanh激活做一次非线性变换,增强模型拟合数据的能力。

大家重点关注Rnn Cell如何在一个Time Step内同时处理一批数据的过程。


关于Rnn Cell内部具体的运算过程还可以简化为一次矩阵乘法运算完成,如下图所示:


Rnn

我们将上文用Rnn Cell加循环实现的例子通过Rnn层来实现一遍:

python 复制代码
import torch

batch_size = 2   # 批量大小
seq_len = 3      # 序列长度(单个RNN层包含的Time Step数量)
input_size = 4   # 输入数据的维度
hidden_size = 2  # 隐藏层的维度

rnn = torch.nn.RNN(input_size=input_size, hidden_size=hidden_size,num_layers=1)
dataset = torch.rand(seq_len, batch_size, input_size)
hidden = torch.zeros(1,batch_size, hidden_size) # 此处1为RNN层数,下文会讲

out, hidden = rnn(dataset, hidden)
print('output size:', out.shape)
print('Hidden size:', hidden.shape)

输出:

python 复制代码
output size: torch.Size([3, 2, 2])
Hidden size: torch.Size([1, 2, 2])

Rnn可以一次性处理完输入的批量序列数据,并返回处理后的结果和最后一个Time Step隐藏层的输出结果,返回结果的维度解释如下:

  • out:(序列长度,批量大小,隐藏层维度)
  • hidden: (隐藏层层数,批量大小,隐藏层维度)

上图中按照Time Step维度展开的Rnn Cell为同一个实例对象,也就是上文中最开始给出的Rnn Cell加循环的图解过程,所以不同Time Step处理时涉及到的权重矩阵是共享的。


双层单向RNN

双层单向RNN处理的流程如下图所示:

我们下面通过代码来验证一下:

python 复制代码
import torch

batch_size = 2   # 批量大小
seq_len = 3      # 序列长度(单个RNN层包含的Time Step数量)
input_size = 4   # 输入数据的维度
hidden_size = 2  # 隐藏层的维度

rnn = torch.nn.RNN(input_size=input_size, hidden_size=hidden_size,num_layers=2)
dataset = torch.rand(seq_len, batch_size, input_size)
hidden = torch.zeros(2,batch_size, hidden_size) # 此处1为RNN层数,下文会讲

out, hidden = rnn(dataset, hidden)
print('output size:', out.shape)
print('Hidden size:', hidden.shape)

输出:

python 复制代码
output size: torch.Size([3, 2, 2])
Hidden size: torch.Size([2, 2, 2])

改为双层RNN后,我们初始化输入的隐藏层向量要同时为层1的RNN和层2的RNN都提供,因此hidden的第一维num_layers要变为2; 同理,我们最终处理完毕后,会得到层1和层2的最后一个Time Step的输出,因此返回的hidden维度第一维也是2。

  • out:(序列长度,批量大小,隐藏层维度)
  • hidden: (隐藏层层数,批量大小,隐藏层维度)

单层双向RNN

单层双向RNN处理的流程如下图所示:

我们下面通过代码来验证一下:

python 复制代码
import torch

batch_size = 2   # 批量大小
seq_len = 3      # 序列长度(单个RNN层包含的Time Step数量)
input_size = 4   # 输入数据的维度
hidden_size = 2  # 隐藏层的维度

rnn = torch.nn.RNN(input_size=input_size, hidden_size=hidden_size,num_layers=1,bidirectional=True)
dataset = torch.rand(seq_len, batch_size, input_size)
hidden = torch.zeros(2,batch_size, hidden_size) # 此处1为RNN层数,下文会讲

out, hidden = rnn(dataset, hidden)
print('output size:', out.shape)
print('Hidden size:', hidden.shape)

输出:

python 复制代码
output size: torch.Size([3, 2, 4])
Hidden size: torch.Size([2, 2, 2])

当我们考虑双向时,最终输出得到的out和hidden维度都会发生改变:

  • out:(序列长度,批量大小,隐藏层维度*2)
  • hidden: (隐藏层层数*2,批量大小,隐藏层维度)

当设置RNN为双向后,在每一个Time Step我们会得到正向和反向计算的两份输出,因此每个Time Step隐藏层的输出为正向和反向输出的concat,这里的concat运算过程如下所示:

python 复制代码
# 维度: (序列长度,批量大小,隐藏层维度) --> [3,2,4]
# 假设正向RNN输出如下
forward_outputs = torch.tensor([
    [[0.1, 0.2, 0.3, 0.4], [0.5, 0.6, 0.7, 0.8]],  # t=0
    [[0.9, 1.0, 1.1, 1.2], [1.3, 1.4, 1.5, 1.6]],  # t=1
    [[1.7, 1.8, 1.9, 2.0], [2.1, 2.2, 2.3, 2.4]]  # t=2
])
# 假设反向RNN输出如下
backward_outputs = torch.tensor([
    [[2.5, 2.6, 2.7, 2.8], [2.9, 3.0, 3.1, 3.2]],  # t=0
    [[3.3, 3.4, 3.5, 3.6], [3.7, 3.8, 3.9, 4.0]],  # t=1
    [[4.1, 4.2, 4.3, 4.4], [4.5, 4.6, 4.7, 4.8]]  # t=2
])
# 拼接(concatenation)后的结果如下
concatenated_outputs = torch.tensor([
    [[0.1, 0.2, 0.3, 0.4, 2.5, 2.6, 2.7, 2.8], [0.5, 0.6, 0.7, 0.8, 2.9, 3.0, 3.1, 3.2]],  # t=0
    [[0.9, 1.0, 1.1, 1.2, 3.3, 3.4, 3.5, 3.6], [1.3, 1.4, 1.5, 1.6, 3.7, 3.8, 3.9, 4.0]],  # t=1
    [[1.7, 1.8, 1.9, 2.0, 4.1, 4.2, 4.3, 4.4], [2.1, 2.2, 2.3, 2.4, 4.5, 4.6, 4.7, 4.8]]  # t=2
])

同理,采用双向RNN后,正向和反向的最后一个Time Step计算完后都会得到一个hidden输出,因此最终计算完返回的hidden第一维是2;我们初始化时也要同时给出正向和反向RNN的hidden,因此初始化输入的hidden第一维也是2。


双层双向RNN

双层双向RNN处理流程如下:

  • 第一层双向 RNN:
    • 正向 RNN 从序列的开始到结束处理数据。
    • 反向 RNN 从序列的结束到开始处理数据。
    • 两个方向的输出在每个时间步上进行拼接,形成第一层的输出。
  • 第二层双向 RNN:
    • 第二层的输入是第一层的输出。
    • 第二层的正向 RNN 从第一层的输出序列的开始到结束处理数据。
    • 第二层的反向 RNN 从第一层的输出序列的结束到开始处理数据。
    • 两个方向的输出在每个时间步上进行拼接,形成第二层的输出。

我们下面通过代码来验证一下:

python 复制代码
import torch

batch_size = 2   # 批量大小
seq_len = 3      # 序列长度(单个RNN层包含的Time Step数量)
input_size = 4   # 输入数据的维度
hidden_size = 2  # 隐藏层的维度

rnn = torch.nn.RNN(input_size=input_size, hidden_size=hidden_size,num_layers=2,bidirectional=True)
dataset = torch.rand(seq_len, batch_size, input_size)
hidden = torch.zeros(2*2,batch_size, hidden_size) # 此处1为RNN层数,下文会讲

out, hidden = rnn(dataset, hidden)
print('output size:', out.shape)
print('Hidden size:', hidden.shape)

通过断点debug可以看到rnn对象实例内部各层权重矩阵的维度:

重点关注weight_ih_l1 和 weight_ih_l1_reverse ,这两个权重矩阵分别为第二层RNN的正向和反向层对应的输入数据的权重矩阵,可以看到其维度为4,2 (X*W.T这里给出W转置后的维度) , 说明此处单个Time Step输入第二层Rnn Cell的输入数据维度为2,4,由此可推断输入的为第一层双向RNN拼接输出后结果。

输出:

python 复制代码
output size: torch.Size([3, 2, 4])
Hidden size: torch.Size([4, 2, 2])

这里output输出的维度和单层双向RNN的结果一致,这是因为第一层RNN的输出结果维度和初始input数据维度相同。

hidden第一维输出变成了4,这是因为每一个双向RNN层都会在最后一个Time Step提供正向和反向两个hidden输出,而因为我们这里采用了两层双向RNN,因此最终会得到四个hidden输出。

相关推荐
IT_陈寒11 小时前
SpringBoot自动配置差点让我加班到凌晨
前端·人工智能·后端
yumgpkpm11 小时前
Acceldata ODP(Open Data Platform)3.3.6.4(RHEL9)保姆级完整安装手册
大数据·人工智能·hive·hadoop·kafka·hbase·cloudera
程序员cxuan11 小时前
腾讯又来一王炸,开源版 WorkBuddy 太夯了!
人工智能·后端·程序员
邓工说电11 小时前
智慧断路器安全吗?数据加密、离线保护与合规认证全解读
大数据·数据库·人工智能·智能断路器·炜晔科技
阿里云大数据AI技术11 小时前
Lance 数据检索怎么选,当然阿里云 Milvus 向量湖
人工智能
出海客11 小时前
跨境电商多语言客服知识库怎么建:资料结构、检索边界与人工升级
大数据·人工智能
xsd2024111812 小时前
从自主导航到视觉读表:一台工业巡检机器人的全栈技术链路拆解
人工智能
袁哥大话安全12 小时前
巡隐WEBSHELL扫描软件
人工智能·安全·web
论文复现现场12 小时前
8卡4090能跑70B吗?Llama-2显存预算、QLoRA与通信瓶颈
人工智能·深度学习·分布式训练·llama·显存·qlora·算家云
酷虎软件12 小时前
如何用AI创作AI歌曲AI音乐
人工智能