LSTM: Como lidar com a não-estacionariedade ao prever uma série temporal

8

Quero fazer previsões um passo à frente para séries temporais com LSTM. Para entender o algoritmo, criei um exemplo de brinquedo: um processo autocorrelacionado simples.

def my_process(n, p, drift=0, displacement=0):
    x = np.zeros(n)

    for i in range(1, n):
        x[i] = drift * i + p * x[i-1] + (1-p) * np.random.randn()
    return x + displacement

Então, construí um modelo LSTM no Keras, seguindo este exemplo . Simulei processos com alta autocorrelação p=0.99de comprimento n=10000, treinei a rede neural nos primeiros 80% e deixei fazer previsões um passo à frente para os 20% restantes.

Se eu definir drift=0, displacement=0, tudo funcionará bem: insira a descrição da imagem aqui

Então eu ajustei drift=0, displacement=10e as coisas ficaram em forma de pêra (observe a escala diferente no eixo y): insira a descrição da imagem aqui

Isso não é muito surpreendente: os LSTMs devem ser alimentados com dados normalizados! Então, normalizei os dados, redimensionando-os para o intervalo[-1,1]. Ufa, tudo está bem novamente: insira a descrição da imagem aqui

Então eu configurei drift=0.00001, displacement=10, normalizei os dados novamente e executei o algoritmo nele. Isso não parece bom: insira a descrição da imagem aqui

Aparentemente, o LSTM não pode lidar com uma deriva. O que fazer? (Sim, neste exemplo de brinquedo eu poderia simplesmente subtrair a deriva; mas para as séries temporais do mundo real, isso é muito mais difícil). Talvez eu possa executar meu LSTM na diferença vez da série temporal original . Isso removerá qualquer desvio constante da série temporal. Mas executar o LSTM em séries cronológicas diferenciadas não funciona: Xt-Xt-1Xtinsira a descrição da imagem aqui

Minha pergunta: Por que meu algoritmo falha quando eu o uso em séries temporais diferenciadas? Qual é uma boa maneira de lidar com desvios em séries temporais?

Aqui está o código completo do meu modelo:

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)

from keras.layers.core import Dense, Activation, Dropout
from keras.layers.recurrent import LSTM
from keras.models import Sequential


# The LSTM model
my_model = Sequential()

my_model.add(LSTM(input_shape=(1, 1), units=50, return_sequences=True))
my_model.add(Dropout(0.2))

my_model.add(LSTM(units=100, return_sequences=False))
my_model.add(Dropout(0.2))

my_model.add(Dense(units=1))
my_model.add(Activation('linear'))

my_model.compile(loss='mse', optimizer='rmsprop')


def my_prediction(x, model, normalize=False, difference=False):
    # Plot the process x
    plt.figure(figsize=(15, 7))
    plt.subplot(121)
    plt.plot(x)
    plt.title('Original data')

    n = len(x)
    thrs = int(0.8 * n)    # Train-test split
    # Save starting values for test set to reverse differencing
    x_test_0 = x[thrs + 1]
    # Save minimum and maximum on test set to reverse normalization
    x_min = min(x[:thrs])  
    x_max = max(x[:thrs])

    if difference:
        x = np.diff(x)   # Take difference to remove drift
    if normalize:
        x = (2*x - x_min - x_max) / (x_max - x_min)   # Normalize to [-1, 1]

    # Split into train and test set. The model will be trained on one-step-ahead predictions.
    x_train, y_train, x_test, y_test = x[0:(thrs-1)], x[1:thrs], x[thrs:(n-1)], x[(thrs+1):n]

    x_train, x_test = x_train.reshape(-1, 1, 1), x_test.reshape(-1, 1, 1)
    y_train, y_test = y_train.reshape(-1, 1), y_test.reshape(-1, 1)

    # Fit the model
    model.fit(x_train, y_train, batch_size=200, epochs=10, validation_split=0.05, verbose=0)

    # Predict the test set
    y_pred = model.predict(x_test)

    # Reverse differencing and normalization
    if normalize:
        y_pred = ((x_max - x_min) * y_pred + x_max + x_min) / 2
        y_test = ((x_max - x_min) * y_test + x_max + x_min) / 2  
    if difference:
        y_pred = x_test_0 + np.cumsum(y_pred)
        y_test = x_test_0 + np.cumsum(y_test)

    # Plot estimation
    plt.subplot(122)
    plt.plot(y_pred[-100:], label='One-step-ahead-predictions')
    plt.plot(y_test[-100:], label='Actual data')
    plt.title('Prediction on test set')
    plt.legend()
    plt.show()

# Make plots
x = my_process(10000, 0.99, drift=0, displacement=0)
my_prediction(x, my_model, normalize=False, difference=False)

x = my_process(10000, 0.99, drift=0, displacement=10)
my_prediction(x, my_model, normalize=False, difference=False)

x = my_process(10000, 0.99, drift=0, displacement=10)
my_prediction(x, my_model, normalize=True, difference=False)

x = my_process(10000, 0.99, drift=0.00001, displacement=10)
my_prediction(x, my_model, normalize=True, difference=False)

x = my_process(10000, 0.99, drift=0.00001, displacement=10)
my_prediction(x, my_model, normalize=True, difference=True)
Elias Strehle
fonte

Respostas:

1

Analisando novamente seu processo autocorrelacionado:

    def my_process(n, p, drift=0, displacement=0):
        x = np.zeros(n)

        for i in range(1, n):
            x[i] = drift * i + p * x[i-1] + (1-p) * np.random.randn()
    return x + displacement

Parece que as coisas estão quebrando quando o valor de displacementé alto. Isso faz sentido, como você diz, porque os LSTMs precisam de dados normalizados.

O driftparâmetro é um pouco diferente. Quando uma pequena quantidade de desvio é incluída, uma vez que pé grande, a quantidade de desvio é semelhante à quantidade de ruído aleatório sendo adicionado via np.random.randn().

Nos gráficos para drift=0.00001, displacement=10, parece que as previsões seriam boas, exceto para o turno y. Por isso, acho que a raiz do problema ainda está no displacementparâmetro, não no driftparâmetro. A diferença, como foi feito, não ajudará no displacementparâmetro; em vez disso, ele corrige a deriva.

Não sei do seu código, mas parece que talvez displacementnão tenha sido considerado model.predict. Esse é o meu melhor palpite.

Estatísticas
fonte
Obrigado por olhar! A diferença ajudará no displacementparâmetro:(Xt+1+c)-(Xt+c)=Xt+1-Xt. Além disso, o último exemplo usa a normalização ( depois de diferenciação), de modo que não deve ser um problema ...
Elias Strehle
1
Olá novamente, tudo bem, bom ponto! Hmm. Eu acho que o que você chama de 'deriva', eu chamaria de média móvel (espero). Você pode tentar incluir algum tipo de covariável em seu modelo para explicar a média móvel. Idealmente, um LSTM descobriria isso por si só, é claro, mas aqui parece estar preso.
StatsSorceress
Estou um pouco preocupado com isso, porque quero aplicar LSTMs aos preços das ações. Eles têm uma média móvel / desvio, e a abordagem padrão (pelo menos em estatística) é aplicar diferenciação, ou seja, usar retornos em vez de preços. Então, eu gostaria de entender por que isso não parece funcionar (mesmo para um modelo tão simples) com LSTMs.
Elias Strehle 5/0318
1
Você rastreou as passagens para frente e para trás com seus valores originais versus valores diferenciados? Gostaria de saber se há algum tipo de problema de gradiente de fuga acontecendo com os valores diferenciados. Obviamente, os LSTMs são mais robustos para isso, mas podem enfrentar esses tipos de problemas, portanto vale a pena dar uma olhada.
22818 StatSorceress
1

Quando você escolhe x_mine x_maxvocê está escolhendo 1:thresholdsozinho. Como sua série está aumentando monotonicamente (quase ...), os valores dos testes são todos> 1. Isso, o modelo LSTM nunca foi visto durante o treinamento.

É por isso que você está vendo o que está vendo?

Você pode tentar o mesmo com x_mine x_maxvindo de todo o conjunto de dados?

Jigidi Sarnath
fonte
Isso pode funcionar no meu exemplo de brinquedo; mas se eu usar LSTMs para realmente prever algo, isso exigiria uma reflexão sobre o futuro.
Elias Strehle