Pytorch 设计选择中的 LSTM 单元实现
LSTM cell implementation in Pytorch design choices
我在 Pytorch 中寻找可以扩展的 LSTM 单元的实现,我在接受的答案中找到了它的实现 。我将 post 放在这里是因为我想参考它。有很多实现细节我不明白,我想知道是否有人可以澄清。
import math
import torch as th
import torch.nn as nn
class LSTM(nn.Module):
def __init__(self, input_size, hidden_size, bias=True):
super(LSTM, self).__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.bias = bias
self.i2h = nn.Linear(input_size, 4 * hidden_size, bias=bias)
self.h2h = nn.Linear(hidden_size, 4 * hidden_size, bias=bias)
self.reset_parameters()
def reset_parameters(self):
std = 1.0 / math.sqrt(self.hidden_size)
for w in self.parameters():
w.data.uniform_(-std, std)
def forward(self, x, hidden):
h, c = hidden
h = h.view(h.size(1), -1)
c = c.view(c.size(1), -1)
x = x.view(x.size(1), -1)
# Linear mappings
preact = self.i2h(x) + self.h2h(h)
# activations
gates = preact[:, :3 * self.hidden_size].sigmoid()
g_t = preact[:, 3 * self.hidden_size:].tanh()
i_t = gates[:, :self.hidden_size]
f_t = gates[:, self.hidden_size:2 * self.hidden_size]
o_t = gates[:, -self.hidden_size:]
c_t = th.mul(c, f_t) + th.mul(i_t, g_t)
h_t = th.mul(o_t, c_t.tanh())
h_t = h_t.view(1, h_t.size(0), -1)
c_t = c_t.view(1, c_t.size(0), -1)
return h_t, (h_t, c_t)
1- 为什么将 self.i2h 和 self.h2h 的隐藏大小乘以 4(在 init
方法中)
2-我不明白参数的重置方法。特别是,为什么我们要这样重新设置参数?
3- 为什么我们在前向方法中对 h、c 和 x 使用 view
?
4- 我也对 forward 方法的 activations
部分中的列边界感到困惑。例如,为什么我们用 3 * self.hidden_size 作为上限 gates
?
5- LSTM 的所有参数在哪里?我在这里谈论的是 Us 和 Ws:
1- Why multiply the hidden size by 4 for both self.i2h and self.h2h (in the init
method)
在您包含的方程中,输入 x 和隐藏状态 h 用于四个计算,其中每个是与权重的矩阵乘法。无论您进行四次矩阵乘法还是连接权重并进行一次更大的矩阵乘法然后将结果分开,结果都是一样的。
input_size = 5
hidden_size = 10
input = torch.randn((2, input_size))
# Two different weights
w_c = torch.randn((hidden_size, input_size))
w_i = torch.randn((hidden_size, input_size))
# Concatenated weights into one tensor
# with size:[2 * hidden_size, input_size]
w_combined = torch.cat((w_c, w_i), dim=0)
# Output calculated by using separate matrix multiplications
out_c = torch.matmul(w_c, input.transpose(0, 1))
out_i = torch.matmul(w_i, input.transpose(0, 1))
# One bigger matrix multiplication with the combined weights
out_combined = torch.matmul(w_combined, input.transpose(0, 1))
# The first hidden_size number of rows belong to w_c
out_combined_c = out_combined[:hidden_size]
# The second hidden_size number of rows belong to w_i
out_combined_i = out_combined[hidden_size:]
# Using torch.allclose because they are equal besides floating point errors.
torch.allclose(out_c, out_combined_c) # => True
torch.allclose(out_i, out_combined_i) # => True
通过将线性层的输出大小设置为4 * hidden_size有四个权重大小为hidden_size,所以只需要一层而不是四层。这样做并没有真正的优势,除了可能会有轻微的性能改进,主要是针对较小的输入,如果单独完成,这些输入不会完全耗尽并行化能力。
4- I'm also confused about the column bounds in the activations
part of the forward method. As an example, why do we upper bound with 3 * self.hidden_size for gates
?
这就是输出被分开以对应于四个单独计算的输出的地方。输出是 [i_t; f_t; o_t; g_t]
的串联(分别不包括 tanh 和 sigmoid)。
您可以通过使用 torch.chunk
:
将输出分成四个块来获得相同的分离
i_t, f_t, o_t, g_t = torch.chunk(preact, 4, dim=1)
但在分离之后,您必须将 torch.sigmoid
应用到 i_t
、f_t
和 o_t
,并将 torch.tanh
应用到 g_t
.
5- Where are all the parameters of the LSTM? I'm talking about the Us and Ws here:
参数W是线性层中的权重self.i2h
和线性层中的Uself.h2h
,但串联起来。
W_i, W_f, W_o, W_c = torch.chunk(self.i2h.weight, 4, dim=0)
U_i, U_f, U_o, U_c = torch.chunk(self.h2h.weight, 4, dim=0)
3- Why do we use view
for h, c, and x in the forward method?
基于 h_t = h_t.view(1, h_t.size(0), -1)
最后,隐藏状态的大小为 [1, batch_size, hidden_size]。使用 h = h.view(h.size(1), -1)
摆脱第一个奇异维度以获得大小 [batch_size、hidden_size]。 h.squeeze(0)
.
也可以实现同样的效果
2- I don't understand the reset method for the parameters. In particular, why do we reset parameters in this way?
参数初始化会对模型的学习能力产生很大的影响。初始化的一般规则是使值接近零但又不会太小。一个常见的初始化是从均值为 0 且方差为 1 / n 的正态分布中提取,其中 n 是神经元的数量,其中turn 表示 1 / sqrt(n).
的标准偏差
在这种情况下,它使用均匀分布而不是正态分布,但总体思路是相似的。根据神经元数量确定 minimum/maximum 值,但避免使它们太小。如果 minimum/maximum 值是 1 / n 值会变得非常小,所以使用 1 / sqrt(n) 更合适合适的,例如256 个神经元:1 / 256 = 0.0039 而 1 / sqrt(256) = 0.0625.
Initializing neural networks 通过交互式可视化提供了一些不同初始化的解释。
我在 Pytorch 中寻找可以扩展的 LSTM 单元的实现,我在接受的答案中找到了它的实现
import math
import torch as th
import torch.nn as nn
class LSTM(nn.Module):
def __init__(self, input_size, hidden_size, bias=True):
super(LSTM, self).__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.bias = bias
self.i2h = nn.Linear(input_size, 4 * hidden_size, bias=bias)
self.h2h = nn.Linear(hidden_size, 4 * hidden_size, bias=bias)
self.reset_parameters()
def reset_parameters(self):
std = 1.0 / math.sqrt(self.hidden_size)
for w in self.parameters():
w.data.uniform_(-std, std)
def forward(self, x, hidden):
h, c = hidden
h = h.view(h.size(1), -1)
c = c.view(c.size(1), -1)
x = x.view(x.size(1), -1)
# Linear mappings
preact = self.i2h(x) + self.h2h(h)
# activations
gates = preact[:, :3 * self.hidden_size].sigmoid()
g_t = preact[:, 3 * self.hidden_size:].tanh()
i_t = gates[:, :self.hidden_size]
f_t = gates[:, self.hidden_size:2 * self.hidden_size]
o_t = gates[:, -self.hidden_size:]
c_t = th.mul(c, f_t) + th.mul(i_t, g_t)
h_t = th.mul(o_t, c_t.tanh())
h_t = h_t.view(1, h_t.size(0), -1)
c_t = c_t.view(1, c_t.size(0), -1)
return h_t, (h_t, c_t)
1- 为什么将 self.i2h 和 self.h2h 的隐藏大小乘以 4(在 init
方法中)
2-我不明白参数的重置方法。特别是,为什么我们要这样重新设置参数?
3- 为什么我们在前向方法中对 h、c 和 x 使用 view
?
4- 我也对 forward 方法的 activations
部分中的列边界感到困惑。例如,为什么我们用 3 * self.hidden_size 作为上限 gates
?
5- LSTM 的所有参数在哪里?我在这里谈论的是 Us 和 Ws:
1- Why multiply the hidden size by 4 for both self.i2h and self.h2h (in the
init
method)
在您包含的方程中,输入 x 和隐藏状态 h 用于四个计算,其中每个是与权重的矩阵乘法。无论您进行四次矩阵乘法还是连接权重并进行一次更大的矩阵乘法然后将结果分开,结果都是一样的。
input_size = 5
hidden_size = 10
input = torch.randn((2, input_size))
# Two different weights
w_c = torch.randn((hidden_size, input_size))
w_i = torch.randn((hidden_size, input_size))
# Concatenated weights into one tensor
# with size:[2 * hidden_size, input_size]
w_combined = torch.cat((w_c, w_i), dim=0)
# Output calculated by using separate matrix multiplications
out_c = torch.matmul(w_c, input.transpose(0, 1))
out_i = torch.matmul(w_i, input.transpose(0, 1))
# One bigger matrix multiplication with the combined weights
out_combined = torch.matmul(w_combined, input.transpose(0, 1))
# The first hidden_size number of rows belong to w_c
out_combined_c = out_combined[:hidden_size]
# The second hidden_size number of rows belong to w_i
out_combined_i = out_combined[hidden_size:]
# Using torch.allclose because they are equal besides floating point errors.
torch.allclose(out_c, out_combined_c) # => True
torch.allclose(out_i, out_combined_i) # => True
通过将线性层的输出大小设置为4 * hidden_size有四个权重大小为hidden_size,所以只需要一层而不是四层。这样做并没有真正的优势,除了可能会有轻微的性能改进,主要是针对较小的输入,如果单独完成,这些输入不会完全耗尽并行化能力。
4- I'm also confused about the column bounds in the
activations
part of the forward method. As an example, why do we upper bound with 3 * self.hidden_size forgates
?
这就是输出被分开以对应于四个单独计算的输出的地方。输出是 [i_t; f_t; o_t; g_t]
的串联(分别不包括 tanh 和 sigmoid)。
您可以通过使用 torch.chunk
:
i_t, f_t, o_t, g_t = torch.chunk(preact, 4, dim=1)
但在分离之后,您必须将 torch.sigmoid
应用到 i_t
、f_t
和 o_t
,并将 torch.tanh
应用到 g_t
.
5- Where are all the parameters of the LSTM? I'm talking about the Us and Ws here:
参数W是线性层中的权重self.i2h
和线性层中的Uself.h2h
,但串联起来。
W_i, W_f, W_o, W_c = torch.chunk(self.i2h.weight, 4, dim=0)
U_i, U_f, U_o, U_c = torch.chunk(self.h2h.weight, 4, dim=0)
3- Why do we use
view
for h, c, and x in the forward method?
基于 h_t = h_t.view(1, h_t.size(0), -1)
最后,隐藏状态的大小为 [1, batch_size, hidden_size]。使用 h = h.view(h.size(1), -1)
摆脱第一个奇异维度以获得大小 [batch_size、hidden_size]。 h.squeeze(0)
.
2- I don't understand the reset method for the parameters. In particular, why do we reset parameters in this way?
参数初始化会对模型的学习能力产生很大的影响。初始化的一般规则是使值接近零但又不会太小。一个常见的初始化是从均值为 0 且方差为 1 / n 的正态分布中提取,其中 n 是神经元的数量,其中turn 表示 1 / sqrt(n).
的标准偏差在这种情况下,它使用均匀分布而不是正态分布,但总体思路是相似的。根据神经元数量确定 minimum/maximum 值,但避免使它们太小。如果 minimum/maximum 值是 1 / n 值会变得非常小,所以使用 1 / sqrt(n) 更合适合适的,例如256 个神经元:1 / 256 = 0.0039 而 1 / sqrt(256) = 0.0625.
Initializing neural networks 通过交互式可视化提供了一些不同初始化的解释。