首页 > 解决方案 > Pytorch 设计选择中的 LSTM 单元实现

问题描述

我一直在寻找可以扩展的 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- 为什么我们view在 forward 方法中使用 h、c 和 x?

4-我也activations对前向方法部分的列边界感到困惑。举个例子,为什么我们用 3 * self.hidden_​​size 为上限gates

5- LSTM 的所有参数在哪里?我在这里谈论 Us 和 Ws: 在此处输入图像描述

标签: pythonpytorchlstm

解决方案


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-我也activations对前向方法部分的列边界感到困惑。举个例子,为什么我们用 3 * self.hidden_​​size 为上限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_to_t,torch.tanhg_t

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

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

基于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

初始化神经网络通过交互式可视化提供了对不同初始化的一些解释。


推荐阅读