Por que o LSTM apresenta desempenho pior em trava de informações do que a rede de neurônios recorrentes de baunilha

8

Gostaria de entender melhor por que o LSTM pode se lembrar de informações por um período mais longo do que a rede neural recorrente simples (baunilha / simples), refazendo um experimento do artigo Aprender Dependências de Longo Prazo com Descida Gradiente é Difícil por Bengio et al. 1994 .

Veja as figuras 1. e 2 nesse documento. A tarefa é simples, dada uma sequência, se começar com um valor alto (por exemplo, 1), o rótulo de saída será 1; se começar com um valor baixo (por exemplo, -1), a etiqueta de saída será 0. O meio é ruído. Essa tarefa é chamada de trava de informações, pois o modelo precisa lembrar o valor inicial ao passar pelo ruído do meio para gerar uma etiqueta correta. Ele usou um único neurônio RNN para construir um modelo que exibisse esse comportamento. A Figura 2 (b) mostra os resultados e a frequência de sucesso do treinamento desse modelo diminui drasticamente à medida que a duração da sequência aumenta. Não houve resultado para o LSTM, pois ainda não havia sido inventado em 1994.

Então, fico curioso e gostaria de ver se o LSTM realmente teria um desempenho melhor para essa tarefa. Da mesma forma, construí um RNN de neurônio único para células baunilha e LSTM para modelar a trava de informações. Surpreendentemente, achei o LSTM com desempenho pior e não sei por quê. Alguém poderia me ajudar a explicar ou se há algo errado com o meu código, por favor?

Aqui está o meu resultado:

insira a descrição da imagem aqui

Aqui está o meu código:

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()

Também tenho o resultado mostrado no caderno , o que seria conveniente se você gostaria de replicar os resultados. Demorou mais de um dia para executar o experimento na minha máquina usando apenas CPU. Poderia ser mais rápido em uma máquina habilitada para GPU.

Atualização 2018-04-18 :

Tentei reproduzir uma figura na paisagem da RNN inspirada na Figura 6 em Sobre a dificuldade de treinar redes neurais recorrentes . Acho interessante ver a formação de penhascos no cenário de perdas à medida que o número de recorrências / etapas de tempo / duração da sequência aumenta, o que pode estar relacionado à explicação da dificuldade de treinamento de longas sequências observadas aqui. Mais detalhes estão disponíveis aqui .

insira a descrição da imagem aqui

Atualização 2018-04-19

Estendendo o experimento de @ shimao. Parece que o LSTM e o GRU simplesmente não são tão bons em capturar informações. Mas alternando para uma tarefa diferente, que chamo de retransmissão de bits (tarefa 2 do shimao), o GRU tem um desempenho melhor, enquanto o SRNN e o LSTM são igualmente ruins.

Agora, acho que o desempenho de um tipo de célula pode ser específico de uma tarefa.

Tarefa 1: bloqueio de informações (1 unidade; 10 repetições; 10 épocas)

insira a descrição da imagem aqui

Tarefa 2: relé de bits (8 unidades; 10 repetições; 10 épocas)

insira a descrição da imagem aqui

Barras de erro são desvios-padrão.

Então, uma pergunta intrigante é por que o LSTM não funciona no bloqueio de informações. Dada a simplicidade da tarefa, ela deve poder funcionar, não deveria? Pode estar relacionado à paisagem (por exemplo, falésias) em relação aos seus gradientes.

zyxue
fonte
Parece que você está usando um RNN de unidade única nos dois casos. Você já tentou aumentar, digamos 10 ou 100?
Alex R.
11
Não, a idéia do artigo original é projetar o sistema mais simples para que possamos raciocinar com eficiência. Qual é a motivação do uso de várias células? A tarefa é muito simples e você pode ver que, quando a sequência é curta, o LSTM e o SRNN funcionam bem.
Zyxue 16/04/19
Não li este artigo atentamente (ou recentemente), mas você tem certeza de que está reproduzindo exatamente as condições experimentais? Parece plausível que haja várias diferenças pequenas, mas importantes, entre a sua configuração e o documento que possam explicar o erro. Ou, dito de outra forma, esse é basicamente um problema de depuração, onde há apenas um teste de unidade relativamente não informativo.
Sycorax diz Reinstate Monica
@ Sycorax, o artigo trata apenas de RNN de baunilha, já que o LSTM ainda não foi inventado em 1994. Meus resultados sobre o RNN de baunilha são consistentes com os deles, é por isso que o LSTM apresenta desempenho pior que me intriga.
zyxue
Este artigo papers.nips.cc/paper/… pode ser interessante - ele aborda as questões de dependência de longo prazo levantadas por Bengio no artigo que você cita, portanto, replicar o experimento usado por Schmidhuber pode ser um caminho mais produtivo
diz Sycorax Reintegrar Monica

Respostas:

3

Há um erro no seu código, uma vez que a primeira metade dos exemplos construídos é positiva e o restante é negativo, mas o keras não embaralha antes de dividir os dados em train e val, o que significa que todo o conjunto de val é negativo e o O conjunto de trens é tendencioso para positivo, e é por isso que você obteve resultados estranhos, como precisão 0 (pior que o acaso).

Além disso, ajustei alguns parâmetros (como taxa de aprendizado, número de épocas e tamanho do lote) para garantir que o treinamento sempre convergisse.

Por fim, executei apenas 5 e 100 etapas de tempo para economizar no cálculo.

insira a descrição da imagem aqui

Curiosamente, o LSTM não treina adequadamente, embora o GRU quase o faça tão bem quanto o RNN.

Tentei uma tarefa um pouco mais difícil: nas seqüências positivas, o sinal do primeiro elemento e um elemento no meio da sequência são os mesmos (ambos +1 ou -1); nas sequências negativas, os sinais são diferentes. Eu esperava que a célula de memória adicional no LSTM se beneficiasse aqui

insira a descrição da imagem aqui

Acabou funcionando melhor que a RNN, mas apenas marginalmente, e o GRU vence por algum motivo.

Não tenho uma resposta completa para o porquê do RNN se sair melhor que o LSTM na tarefa simples. Acho que não encontramos os hiperparâmetros certos para treinar adequadamente o LSTM, além do fato de que o problema é fácil para uma RNN simples. Possivelmente, um modelo com tão poucos parâmetros também é mais propenso a ficar preso no mínimo local.

O código modificado

shimao
fonte
Estendi seu experimento e obtive resultados consistentes. Eu acho que o desempenho provavelmente será específico da tarefa.
Zyxue 19/04/19
0

Não sei se fará diferença, mas usaria:

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

e

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

para um problema de classificação binária.

Wayne
fonte
11
Eu acredito matematicamente os dois são os mesmos. Em particular, sigmoide é equivalente ao softmax, onde existem apenas duas classes. Afinal, binary_crossentropy é crossentropy para duas classes. Esse problema está realmente me intrigando, e estou aberto a aumentar a recompensa se alguém puder fornecer uma boa explicação.
zyxue
Você está usando os mesmos parâmetros para cada versão? LSTM é mais complexa e pode exigir mais dados, mais épocas, uma taxa de aprendizagem diferente, etc. @ zyxue
Wayne