GRU 层中的计算是如何进行的
How does calculation in a GRU layer take place
所以我想确切地了解 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)
,因为使用了阿达玛乘积,两个矩阵相乘的大小必须相同,即(8, 288)
。这意味着 n(t)
也是 (8, 288)
.
最后,h(t)
是 Hadamard 产生和矩阵加法,这将使 h(t)
的大小为 (8, 288)
,这是 错误的 .
这个过程我哪里出错了?
TLDR;这种混淆来自于层的权重分别是 input_hidden 和 hidden-hidden 的串联。
- nn.GRU
层weight/bias布局
您可以通过权重和偏差的峰值来更仔细地了解什么是 inside the GRU layer implementation 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 -隐藏'.
为了更有效的计算,参数已连接在一起,正如文档页面清楚解释的那样(|
表示连接)。在此特定示例中 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
包含 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
(在 0
和 L-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)
进行比较。
所以我想确切地了解 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)
,因为使用了阿达玛乘积,两个矩阵相乘的大小必须相同,即(8, 288)
。这意味着 n(t)
也是 (8, 288)
.
最后,h(t)
是 Hadamard 产生和矩阵加法,这将使 h(t)
的大小为 (8, 288)
,这是 错误的 .
这个过程我哪里出错了?
TLDR;这种混淆来自于层的权重分别是 input_hidden 和 hidden-hidden 的串联。
- nn.GRU
层weight/bias布局
您可以通过权重和偏差的峰值来更仔细地了解什么是 inside the GRU layer implementation 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 -隐藏'.
为了更有效的计算,参数已连接在一起,正如文档页面清楚解释的那样(|
表示连接)。在此特定示例中 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
包含 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
(在 0
和 L-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)
进行比较。