LSTM: cómo lidiar con la no estacionariedad al predecir una serie temporal

8

Quiero hacer predicciones de un paso adelante para series temporales con LSTM. Para comprender el algoritmo, construí un ejemplo de juguete: un proceso simple 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

Luego construí un modelo LSTM en Keras, siguiendo este ejemplo . Simulé procesos con alta autocorrelación p=0.99de longitud n=10000, entrené la red neuronal en el primer 80% y le permití hacer predicciones de un paso adelante para el 20% restante.

Si configuro drift=0, displacement=0, todo funciona bien: ingrese la descripción de la imagen aquí

Luego configuré drift=0, displacement=10y las cosas se pusieron en forma de pera (observe la diferente escala en el eje y): ingrese la descripción de la imagen aquí

Esto no es terriblemente sorprendente: ¡los LSTM deben alimentarse con datos normalizados! Así que normalicé los datos volviéndolos a escalar al intervalo . Uf, las cosas están bien otra vez: [1,1]ingrese la descripción de la imagen aquí

Luego configuré drift=0.00001, displacement=10, normalicé los datos nuevamente y ejecuté el algoritmo sobre ellos. Esto no se ve bien: ingrese la descripción de la imagen aquí

Aparentemente, el LSTM no puede lidiar con una deriva. ¿Qué hacer? (Sí, en este ejemplo de juguete simplemente podría restar la deriva; pero para las series de tiempo real, esto es mucho más difícil). Tal vez podría ejecutar mi LSTM en la diferencia lugar de la serie de tiempo original . Esto eliminará cualquier deriva constante de la serie de tiempo. Pero ejecutar el LSTM en las series de tiempo diferenciadas no funciona en absoluto: XtXt1Xtingrese la descripción de la imagen aquí

Mi pregunta: ¿Por qué mi algoritmo se descompone cuando lo uso en las series de tiempo diferenciadas? ¿Cuál es una buena manera de lidiar con las desviaciones en series de tiempo?

Aquí está el código completo para mi 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
fuente

Respuestas:

1

Mirando nuevamente su proceso 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 las cosas se están desmoronando cuando el valor de displacementes alto. Esto tiene sentido, como usted dice, porque los LSTM necesitan datos normalizados.

El driftparámetro es un poco diferente. Cuando se incluye una pequeña cantidad de deriva, ya que pes grande, la cantidad de deriva es similar a la cantidad de ruido aleatorio que se agrega np.random.randn().

En las gráficas de drift=0.00001, displacement=10, parece que las predicciones estarían bien, excepto por el desplazamiento y. Debido a esto, creo que la raíz del problema todavía está en el displacementparámetro, no en el driftparámetro. La diferencia, como se ha hecho, no ayudará con el displacementparámetro; en cambio, corrige la deriva.

No puedo decir por su código, pero parece que tal vez displacementno se tuvo en cuenta model.predict. Esa es mi mejor suposición.

EstadísticasSorceress
fuente
¡Gracias por mirarlo! Sin displacementembargo, la diferenciación ayudará con el parámetro: . Además, el último ejemplo utiliza la normalización ( después de la diferenciación), por lo que no debería ser un problema ...(Xt+1+c)(Xt+c)=Xt+1Xt
Elias Strehle
1
Hola de nuevo, está bien, buen punto! Hmm Creo que lo que ustedes llaman 'deriva', llamaría un promedio móvil (espero). Puede intentar incluir algún tipo de covariable en su modelo para tener en cuenta el promedio móvil. Idealmente, un LSTM descubriría eso por sí solo, por supuesto, pero aquí parece estar atascado.
EstadísticasSorceress
Estoy un poco preocupado por esto porque quiero aplicar LSTM a los precios de las acciones. Tienen un promedio de deriva / movimiento, y el enfoque estándar (al menos en estadística) es aplicar la diferenciación, es decir, utilizar los rendimientos en lugar de los precios. Entonces, me gustaría entender por qué esto no parece funcionar (incluso para un modelo tan simple) con LSTM.
Elias Strehle
1
¿Has trazado los pasos hacia adelante y hacia atrás con tus valores originales frente a los valores diferenciados? Me pregunto si hay algún tipo de problema de gradiente de desaparición con los valores diferenciados. Los LSTM son, por supuesto, más robustos a esto, pero pueden encontrarse con este tipo de problemas, por lo que valdría la pena echarle un vistazo.
EstadísticasSorceress
1

Cuando eliges x_miny x_max, lo eliges 1:thresholdsolo. Dado que su serie está aumentando monotónicamente (bueno, casi ...), los valores de prueba son todos valores> 1. Esto, el modelo LSTM nunca ha visto durante el entrenamiento.

¿Es por eso que estás viendo lo que estás viendo?

¿Puede intentar lo mismo con x_miny x_maxviniendo de todo el conjunto de datos en su lugar?

Jigidi Sarnath
fuente
Esto podría funcionar en mi ejemplo de juguete; pero si uso LSTM para predecir algo, esto requeriría mirar hacia el futuro.
Elias Strehle