TLDR;这种混淆来自于层的权重分别是input_hidden和hidden-hidden的串联。
您可以通过权重和偏差达到峰值来仔细查看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 ”和_hh
“ hidden-hidden ”。
为了更高效的计算,参数已经连接在一起,正如文档页面清楚地解释的那样(|
意味着连接)。在这个特定的例子中num_layers=1
和k=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_t
、z_t
、n_t
和h_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
t
x
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
中,张量包含批次中每个元素的上一步的隐藏状态,即 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)
。