2

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

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

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

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

我的输入大小和输出大小是(1000, 8, 96). 我知道有1000张量,每个张量 size (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)as的大小(8, 288)。同样,z(t)也会是(8, 288)

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

最后,h(t)是 Hadamard 产生和矩阵加法,这将给出h(t)as (8, 288)which is wrong的大小。

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

4

1 回答 1

1

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


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

您可以通过权重和偏差达到峰值来仔细查看GRU 层实现内部的内容。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 ”和_hhhidden-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_ttxx_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中,张量包含批次中每个元素的上一步的隐藏状态,即 shape (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(在0和之间L-1)的连接。


- 示范

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

参数 描述 价值观
H_in 特征尺寸 3
H_out 隐藏尺寸 2
L 序列长度 3
N 批量大小 1
k 层数 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)

于 2021-08-21T15:00:46.497 回答