为什么 LSTM 在信息锁存方面的表现比普通循环神经元网络差

机器算法验证 神经网络 lstm 张量流 循环神经网络 喀拉斯
2022-03-22 23:13:36

我想通过从Bengio 等人的《 Learning Long-Term Dependencies with Gradient Descent is Difficult》一文中重做一个实验,更好地理解为什么 LSTM 可以比普通/简单循环神经网络 (SRNN) 记住更长的时间。1994 年

参见那张纸上的图 1 和图 2。任务很简单,给定一个序列,如果它以高值(例如1)开始,那么输出标签为1;如果它以低值(例如 -1)开始,则输出标签为 0。中间是噪声。这个任务被称为信息锁存,因为模型需要在通过中间噪声时记住起始值才能输出正确的标签。它使用单个神经元 RNN 来构建表现出这种行为的模型。图 2(b) 显示了结果,训练这种模型的成功频率随着序列长度的增加而急剧下降。LSTM 没有结果,因为它在 1994 年还没有发明。

所以,我变得好奇,想看看 LSTM 是否真的能更好地完成这样的任务。同样,我为 vanilla 和 LSTM 单元构建了单个神经元 RNN,以对信息锁存进行建模。令人惊讶的是,我发现 LSTM 的表现更差,我不知道为什么。谁能帮我解释一下,或者我的代码有什么问题吗?

这是我的结果:

在此处输入图像描述

这是我的代码:

import matplotlib.pyplot as plt
import numpy as np    
from keras.models import Model
from keras.layers import Input, LSTM, Dense, SimpleRNN


N = 10000
num_repeats = 30
num_epochs = 5
# sequence length options
lens = [2, 5, 8, 10, 15, 20, 25, 30] + np.arange(30, 210, 10).tolist()

res = {}
for (RNN_CELL, key) in zip([SimpleRNN, LSTM], ['srnn', 'lstm']):
    res[key] = {}
    print(key, end=': ')
    for seq_len in lens:
        print(seq_len, end=',')
        xs = np.zeros((N, seq_len))
        ys = np.zeros(N)

        # construct input data
        positive_indexes = np.arange(N // 2)
        negative_indexes = np.arange(N // 2, N)

        xs[positive_indexes, 0] = 1
        ys[positive_indexes] = 1

        xs[negative_indexes, 0] = -1
        ys[negative_indexes] = 0

        noise = np.random.normal(loc=0, scale=0.1, size=(N, seq_len))

        train_xs = (xs + noise).reshape(N, seq_len, 1)
        train_ys = ys

        # repeat each experiments multiple times
        hists = []
        for i in range(num_repeats):
            inputs = Input(shape=(None, 1), name='input')

            rnn = RNN_CELL(1, input_shape=(None, 1), name='rnn')(inputs)
            out = Dense(2, activation='softmax', name='output')(rnn)
            model = Model(inputs, out)
            model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
            hist = model.fit(train_xs, train_ys, epochs=num_epochs, shuffle=True, validation_split=0.2, batch_size=16, verbose=0)
            hists.append(hist.history['val_acc'][-1])
        res[key][seq_len] = hists
    print()


fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(pd.DataFrame.from_dict(res['lstm']).mean(), label='lstm')
ax.plot(pd.DataFrame.from_dict(res['srnn']).mean(), label='srnn')
ax.legend()

我的结果也显示在notebook中,如果您想复制结果会很方便。仅使用 CPU 在我的机器上运行实验花了一天多的时间。在支持 GPU 的机器上它可能会更快。

2018-04-18 更新

我试图在 RNN 的景观上重现一个图,灵感来自于训练递归神经网络的难度中的图 6 。我发现随着重复/时间步长/序列长度的增加,在损失情况中看到悬崖的形成很有趣,这可能与解释此处观察到的长序列训练困难有关。更多详细信息可在此处获得。

在此处输入图像描述

2018-04-19 更新

扩展@shimao 的实验。似乎 LSTM 和 GRU 不太擅长锁定信息。但是切换到不同的任务,我称之为位中继(@shimao 的任务 2),GRU 表现更好,而 SRNN 和 LSTM 同样糟糕。

现在,我倾向于认为细胞类型的性能可能是特定于任务的。

任务 1:信息锁存(1 个单元;10 次重复;10 个 epoch)

在此处输入图像描述

任务 2:位中继(8 个单元;10 次重复;10 个 epoch)

在此处输入图像描述

误差线是标准偏差。

然后,一个有趣的问题是为什么 LSTM 不能用于信息锁存。鉴于任务的简单性,它应该能够工作,不是吗?可能与景观(例如悬崖)的梯度有关。

2个回答

您的代码中有一个错误,因为您构造的示例的前半部分是正的,其余的都是负的,但是 keras 在将数据拆分为 train 和 val之前不会洗牌,这意味着所有 val 集都是负的,并且训练集偏向正数,这就是为什么你会得到奇怪的结果,例如 0 准确度(比机会更糟糕)。

此外,我调整了一些参数(例如学习率、时期数和批量大小)以确保训练始终收敛。

最后,我只运行了 5 和 100 个时间步以节省计算量。

在此处输入图像描述

奇怪的是,LSTM 没有正确训练,尽管 GRU 几乎和 RNN 一样好。

我尝试了一个稍微困难的任务:在正序列中,第一个元素的符号和序列中途的元素的符号是相同的(都是 +1 或都是 -1),在负序列中,符号是不同的。我希望 LSTM 中的额外记忆单元能在这里受益

在此处输入图像描述

它最终比 RNN 工作得更好,但只有一点点,而且 GRU 出于某种原因胜出。

对于为什么 RNN 在简单任务上比 LSTM 做得更好,我没有完整的答案。我认为这一定是我们没有找到正确的超参数来正确训练 LSTM,除了这个问题对于一个简单的 RNN 来说很容易。可能,参数如此少的模型也更容易陷入局部最小值。

修改后的代码

我不知道这是否会有所不同,但我会使用:

out = Dense (1, activation='sigmoid', ...

model.compile(loss='binary_crossentropy', ...

对于二分类问题。