GRU 层中的计算是如何进行的

Posted

技术标签:

【中文标题】GRU 层中的计算是如何进行的【英文标题】:How does calculation in a GRU layer take place 【发布时间】:2021-10-22 16:24:55 【问题描述】:

所以我想确切了解 GRU 单元的输出和隐藏状态是如何计算的。

我从here获得了预训练模型,GRU层已经定义为nn.GRU(96, 96, bias=True)

我查看了PyTorch Documentation 并确认权重和偏差的尺寸为:

weight_ih_l0: (288, 96) weight_hh_l0: (288, 96) bias_ih_l0: (288) bias_hh_l0: (288)

我的输入大小和输出大小是(1000, 8, 96)。我知道有1000 张量,每个张量的大小为(8, 96)。隐藏状态是(1, 8, 96),它是一个大小为(8, 96)的张量。

我还打印了变量batch_first,发现它是False。这意味着:

序列长度:L=1000 批量大小:B=8 输入大小:Hin=96

现在根据文档中的方程式,对于重置门,我需要将权重乘以输入 x。但是我的权重是二维的,而我的输入是三个维度的。

这是我尝试过的,我从输入中取出第一个 (8, 96) 矩阵并将其与权重矩阵的转置相乘:

Input (8, 96) x Weight (96, 288) = (8, 288)

然后我通过复制(288) 八次来添加偏差以给出(8, 288)。这将使r(t) 的大小为(8, 288)。同样,z(t) 也将是 (8, 288)

r(t) 用在 n(t) 中,因为使用了 Hadamard 乘积,所以要相乘的两个矩阵的大小必须与 (8, 288) 的大小相同。这意味着n(t) 也是(8, 288)

最后,h(t) 是 Hadamard 产生和矩阵加法,这将使 h(t) 的大小为 (8, 288),这是错误

在这个过程中我哪里出错了?

【问题讨论】:

【参考方案1】:

TLDR;这种混淆源于层的权重分别是 input_hiddenhidden-hidden 的串联。


- nn.GRU 层权重/偏置布局

您可以通过权重和偏差达到峰值来仔细查看inside the GRU layer 的实现torch.nn.GRU

>>> gru = nn.GRU(input_size=96, hidden_size=96, num_layers=1)

首先是GRU层的参数:

>>> gru._all_weights
[['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0']]

您可以查看gru.state_dict() 以获取层权重字典。

我们有两个权重和两个偏差,_ih 代表'input-hidden',_hh 代表'hidden-hidden'。

为了更高效的计算,参数已经连接在一起,正如文档页面清楚地解释的那样(| 表示连接)。在此特定示例中,num_layers=1k=0

~GRU.weight_ih_l[k] – 层(W_ir | W_iz | W_in) 的可学习输入隐藏权重,形状为(3*hidden_size, input_size)

~GRU.weight_hh_l[k] – 层(W_hr | W_hz | W_hn) 的可学习隐藏权重,形状为(3*hidden_size, hidden_size)

~GRU.bias_ih_l[k] – 层 (b_ir | b_iz | b_in) 的可学习输入隐藏偏差,形状为 (3*hidden_size)

~GRU.bias_hh_l[k](b_hr | b_hz | b_hn) 的可学习隐藏偏差。

为了进一步检查,我们可以使用以下代码将它们分开:

>>> W_ih, W_hh, b_ih, b_hh = gru._flat_weights
>>> W_ir, W_iz, W_in = W_ih.split(H_in)
>>> W_hr, W_hz, W_hn = W_hh.split(H_in)
>>> b_ir, b_iz, b_in = b_ih.split(H_in)
>>> b_hr, b_hz, b_hn = b_hh.split(H_in)

现在我们整理出了12张量参数。


- 表达式

GRU 层的四个表达式:r_tz_tn_th_t,在每个时间步计算

第一个操作是r_t = σ(W_ir@x_t + b_ir + W_hr@h + b_hr)。我使用@ 符号来指定矩阵乘法运算符(__matmul__)。请记住W_ir 的形状为(H_in=input_size, hidden_size),而x_t 包含来自x 序列的步骤t 处的元素。张量x_t = x[t] 的形状为(N=batch_size, H_in=input_size)。此时,它只是输入x[t] 和权重矩阵之间的矩阵乘法。生成的张量 r 的形状为 (N, hidden_size=H_in)

>>> (x[t]@W_ir.T).shape
(8, 96)

执行的所有其他权重乘法操作也是如此。因此,您最终会得到一个形状为 (N, H_out=hidden_size) 的输出张量。

在以下表达式中,h 是包含批次中每个元素的上一步隐藏状态的张量,即形状为(N, hidden_size=H_out),因为num_layers=1只有一个隐藏层。

>>> r_t = torch.sigmoid(x[t]@W_ir.T + b_ir + h@W_hr.T + b_hr)
>>> r_t.shape
(8, 96)

>>> z_t = torch.sigmoid(x[t]@W_iz.T + b_iz + h@W_hz.T + b_hz)
>>> z_t.shape
(8, 96)

层的输出是计算的h张量的串联 连续时间步长t(在0L-1 之间)。


- 演示

这是手动计算的nn.GRU 推理的最小示例:

Parameters Description Values
H_in feature size 3
H_out hidden size 2
L sequence length 3
N batch size 1
k number of layers 1

设置:

gru = nn.GRU(input_size=H_in, hidden_size=H_out, num_layers=k)
W_ih, W_hh, b_ih, b_hh = gru._flat_weights
W_ir, W_iz, W_in = W_ih.split(H_out)
W_hr, W_hz, W_hn = W_hh.split(H_out)
b_ir, b_iz, b_in = b_ih.split(H_out)
b_hr, b_hz, b_hn = b_hh.split(H_out)

随机输入:

x = torch.rand(L, N, H_in)

推理循环:

output = []
h = torch.zeros(1, N, H_out)
for t in range(L):
   r = torch.sigmoid(x[t]@W_ir.T + b_ir + h@W_hr.T + b_hr)
   z = torch.sigmoid(x[t]@W_iz.T + b_iz + h@W_hz.T + b_hz)
   n = torch.tanh(x[t]@W_in.T + b_in + r*(h@W_hn.T + b_hn))
   h = (1-z)*n + z*h
   output.append(h)

最终输出由张量h在连续时间步长的堆叠给出:

>>> torch.vstack(output)
tensor([[[0.1086, 0.0362]],

        [[0.2150, 0.0108]],

        [[0.3020, 0.0352]]], grad_fn=<CatBackward>)

在这种情况下,输出形状是(L, N, H_out)(3, 1, 2)

你可以和output, _ = gru(x)比较。

【讨论】:

非常感谢您的详细解释!我把连接运算符 | 误认为是“或”,我真的很困惑。我在拆分权重后进行了计算,得到了(1000, 8, 96)。在计算h_t = (1 - z_t) * n_t + z_t * h 时,由于z_tn_t(1000, 8, 96),那么h_t 怎么会是(1, 8, 96)。与时间步长有关吗? 抱歉造成混淆,张量的形状确实是 (1, 8, 96) 或 (8, 96)`,因为这四个表达式中的每一个都是在每个时间步 t ∈ [0, L[ 计算的。这意味着我们使用x[t](不是x)来计算r_tz_tn_th_t。我已经编辑了我的答案并添加了应该清除形状问题的完整实现。感谢您指出这一点! 我使用你提到的循环对我的数据进行了计算,并将其与output, _ = gru(x) 进行了比较,两者都匹配。我只想提一下,小数点后 8 位的某些值存在差异,但这对我的用例来说完全没问题。再次感谢!

以上是关于GRU 层中的计算是如何进行的的主要内容,如果未能解决你的问题,请参考以下文章

计算卷积层中的输出大小

数据访问层中的计算

LSTM 和 GRU 门如何决定将哪个单词保留在内存中

如何在 OBIEE 12c 中查看业务模型和映射层中的数据?

pytorch 中的 torch.nn.gru 函数的输入是啥?

arcmap中如何把一个图层中的要素复制到另一个图层上