【问题标题】:LSTM cell implementation in Pytorch design choicesPytorch 设计选择中的 LSTM 单元实现
【发布时间】:2020-09-18 03:02:49
【问题描述】:

我正在寻找可以扩展的 Pytorch 中的 LSTM 单元的实现,我在接受的答案 here 中找到了它的实现。我会把它贴在这里,因为我想参考它。有很多我不明白的实现细节,我想知道是否有人可以澄清一下。

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- 为什么我们在 forward 方法中对 h、c 和 x 使用view

4- 我也对 forward 方法的 activations 部分中的列边界感到困惑。例如,为什么gates 的上限是 3 * self.hidden_​​size?

5- LSTM 的所有参数在哪里?我在这里谈论 Us 和 Ws:

【问题讨论】:

    标签: python pytorch lstm


    【解决方案1】:

    1- 为什么要将 self.i2h 和 self.h2h 的隐藏大小乘以 4(在 init 方法中)

    在您包含的方程式中,输入 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- 我也对 forward 方法的 activations 部分中的列边界感到困惑。例如,为什么gates 的上限是 3 * self.hidden_​​size?

    这就是将输出分开以对应于四个单独计算的输出的地方。输出是 [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_tf_to_t,并将torch.tanh 应用于g_t

    5- LSTM 的所有参数在哪里?我在这里说的是 Us 和 Ws:

    参数W是线性层self.i2h中的权重,U是线性层self.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- 为什么我们在 forward 方法中对 h、c 和 x 使用view

    基于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- 我不明白参数的重置方法。特别是我们为什么要这样重置参数?

    参数初始化会对模型的学习能力产生很大影响。初始化的一般规则是使值接近零而不会太小。一个常见的初始化是从均值为 0 且方差为 1 / n 的正态分布中绘制,其中 n 是神经元的数量,这反过来意味着标准偏差为1 / sqrt(n).

    在这种情况下,它使用均匀分布而不是正态分布,但总体思路是相似的。根据神经元的数量确定最小/最大值,但避免使它们太小。如果最小值/最大值为 1 / n,则值会变得非常小,因此使用 1 / sqrt(n) 更合适,例如256 个神经元:1 / 256 = 0.00391 / sqrt(256) = 0.0625

    Initializing neural networks 通过交互式可视化提供了一些不同初始化的解释。

    【讨论】:

    • 非常感谢,这很有帮助。我会接受你的回答,但只是再澄清一点。你说隐藏状态有大小(1,batch_size,hidden_​​size)。为什么一开始第一个维度有一个 1?
    • 我只能推测,但我想说那是因为它遵循nn.LSTM,其中隐藏状态的大小为 (num_layers * num_directions, batch_size, hidden_​​size) 和单元格只有 1 个方向和 1 层,因此它将是一个。但相比之下,nn.LSTMCell 完全省略了该尺寸,尺寸为 (batch_size, hidden_​​size),这对我来说更有意义。
    • 谢谢,这是有道理的。其实还有两个问题。据我所知,LSTM 单元将隐藏状态和单元状态都作为输入。在 forward 方法中,唯一的输入是“隐藏”。然后他们将 h 和 c 设置为“隐藏”。这似乎很奇怪,他们没有区分隐藏状态和单元状态......
    • 我的第二个问题是关于退货声明的。他们为什么返回 h_t, (h_t, c_t)。为什么要返回 h_t 两次?
    • 隐藏状态和单元状态是分开的,但是保存在一个元组中,据说是因为它们是相互补充的,并且保持API类似于常规RNN(没有单元状态),所以除非你解包LSTM 的隐藏状态,各种 RNN 可以无缝互换。 h_t 是输出和隐藏状态,nn.LSTMCell 完全省略了它。我建议阅读 Understanding LSTM Networks 以更好地了解 LSTM。
    猜你喜欢
    • 2019-04-30
    • 2020-09-18
    • 2019-11-18
    • 1970-01-01
    • 2022-07-17
    • 2021-11-28
    • 2019-08-19
    • 2018-08-10
    • 2015-06-30
    相关资源
    最近更新 更多