Keras LSTM con series de tiempo 1D

10

Estoy aprendiendo a usar Keras y he tenido un éxito razonable con mi conjunto de datos etiquetado utilizando los ejemplos de Deep Learning for Python de Chollet . El conjunto de datos es ~ 1000 Series temporales con longitud 3125 con 3 clases potenciales.

Me gustaría ir más allá de las capas densas básicas que me dan una tasa de predicción del 70% y el libro continúa discutiendo las capas LSTM y RNN.

Todos los ejemplos parecen usar conjuntos de datos con múltiples características para cada serie de tiempo y, como resultado, estoy luchando para encontrar la manera de implementar mis datos.

Si, por ejemplo, tengo una serie temporal de 1000x3125, ¿cómo introduzco eso en algo como la capa SimpleRNN o LSTM? ¿Me falta algún conocimiento fundamental de lo que hacen estas capas?

Código actual:

import pandas as pd
import numpy as np
import os
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM, Dropout, SimpleRNN, Embedding, Reshape
from keras.utils import to_categorical
from keras import regularizers
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

def readData():
    # Get labels from the labels.txt file
    labels = pd.read_csv('labels.txt', header = None)
    labels = labels.values
    labels = labels-1
    print('One Hot Encoding Data...')
    labels = to_categorical(labels)

    data = pd.read_csv('ts.txt', header = None)

    return data, labels

print('Reading data...')
data, labels = readData()

print('Splitting Data')
data_train, data_test, labels_train, labels_test = train_test_split(data, labels)

print('Building Model...')
#Create model
model = Sequential()
## LSTM / RNN goes here ##
model.add(Dense(3, activation='softmax'))

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

print('Training NN...')
history = model.fit(data_train, labels_train, epochs=1000, batch_size=50,
    validation_split=0.25,verbose=2)

results = model.evaluate(data_test, labels_test)

predictions = model.predict(data_test)

print(predictions[0].shape)
print(np.sum(predictions[0]))
print(np.argmax(predictions[0]))

print(results)

acc = history.history['acc']
val_acc = history.history['val_acc']
epochs = range(1, len(acc) + 1)

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
usuario1147964
fuente

Respuestas:

10

Las capas LSTM requieren datos de una forma diferente.

Según su descripción, entiendo que el conjunto de datos inicial tiene 3125 filas y 1000 columnas, donde cada fila es un paso de tiempo. La variable de destino debe tener 3125 filas y 1 columna, donde cada valor puede ser uno de los tres valores posibles. Entonces parece que estás haciendo un problema de clasificación. Para verificar esto en el código, haría:

>>> X.shape
(3125, 1000)

>>> y.shape
(1000,)

La clase LSTM requiere que cada muestra consista en un 'bloque' de tiempo. Digamos que quieres tener un bloque de 100 pasos de tiempo. Esto significa que X[0:100]es una muestra de entrada única, que corresponde a la variable objetivo en y[100]. esto significa que el tamaño de su ventana (también conocido como número de pasos de tiempo o retraso) es igual a 100. Como se indicó anteriormente, tiene 3125 muestras, por lo tanto N = 3125. Para formar el primer bloque, desafortunadamente tenemos que descartar las primeras 100 muestras de y, ya que no podemos formar un bloque completo de 100 a partir de los datos disponibles (terminaríamos necesitando los puntos de datos antes X[0]).

Dado todo esto, un LSTM requiere que entregue lotes de forma (N - window_size, window_size, num_features), lo que se traduce en (3125 - 100, 100, 1000)== (3025, 100, 1000).

Crear estos bloques de tiempo es un poco complicado, pero cree una buena función una vez y guárdelo :)

Hay más trabajo por hacer, tal vez mire ejemplos más detallados de mi explicación aquí arriba ... o lea la documentación de LSTM (o mejor aún, ¡ el código fuente! ).

El modelo final sería lo suficientemente simple (según su código):

#Create model
model = Sequential()
model.add(LSTM(units=32, activation='relu',
               input_shape=(100, 1000))    # the batch size is neglected!
model.add(Dense(3, activation='softmax'))

model.compile(loss='categorical_crossentropy', optimizer='adam',
              metrics=['accuracy'])

Eche un vistazo a la documentación sobre la forma de entrada para el Sequentialmodelo . Básicamente dice que no necesitamos especificar el número de lotes dentro input_shape. Se puede hacer, por ejemplo batch_size=50, si requiere que sea un número fijo.

Sé que el input_shapeargumento no está en la documentación LSTM, pero la clase misma hereda de RNN, que a su vez hereda de Layer, por lo que podrá usar la información que proporcione.

Un último consejo: si planea agregar varias capas LSTM ('apilarlas'), entonces deberá agregar un argumento más a todos menos al último LSTM , a saber, el return_sequences=True.

n1k31t4
fuente
Gracias por la respuesta integral Dexter (!). Con respecto a sus comentarios sobre el tamaño del lote, ¿el tamaño del lote especificado en el argumento model.fit es un hiperparámetro diferente en comparación con la creación de mi propio lote personalizado? Logré que mi código se ejecute al menos rediseñando mis datos de una matriz 1000x3125 en una matriz 3D usando data = np.reshape (data, (1000,1,3125)). Esto me permitió ejecutar el LSTM con input_shape (1,3125) pero, una vez más, no estoy realmente seguro de lo que estoy haciendo. Nuevamente, muchas gracias por la respuesta. Echaré un vistazo a los enlaces que proporcionó y estudiaré su respuesta un poco más.
user1147964
¡De nada! Sí, lo entendiste, si dejas de lado batch_sizeal definir el modelo, se tomará del mismo argumento dentro model.fit(). Debería remodelar para obtener (3025, 100, 1000), lo que significa 3025 lotes, cada uno de 100 (filas) pasos de tiempo y 1000 (columnas) variables. El uso np.reshapelamentablemente no funcionará para esto (obtendrá un error), debido al hecho de que tendrá superposiciones de datos ... la forma final tiene más datos que la entrada. 3025x100x1000> 3125x1000 : np.reshapeno le gusta porque es ambiguo. Sugiero simplemente recorrer el conjunto de datos, 1 ciclo = 1 muestra.
n1k31t4
Creo que estoy un poco confundido aquí y podría ser porque inadvertidamente ya hice el proceso por lotes. Usaré valores específicos aquí. Tomé muestras de 3 mediciones diferentes a 6.25 kHz durante aproximadamente 3 minutos, lo que resultó en 3 series temporales de longitud 1093750. Esto genera una matriz de 3x1093750. Luego segmenté cada TS en incrementos de 0,5 segundos, lo que resultó en una matriz de 1050x3125. Técnicamente, podría reestructurar esto en una matriz 3D con dimensiones 3x350x3125. Esto me da 350 "0.5s" lotes "largos. Su remodelación parece generar muchos más valores Gracias por la respuesta nuevamente. Lo sentimos
usuario1147964
Solo para agregar, leer el primer enlace que publicaste me hace pensar que estoy cambiando las cosas correctamente. Pido disculpas si me falta algo obvio, pero aquí comienzan con una longitud TS 5000 y la convierten en una matriz 3D con dimensiones [1 25 200].
user1147964
En comparación con el método en su enlace, mi manera creará muchas más muestras. Esto se debe a que estoy usando una especie de ventana 'rodante'. Echa un vistazo a esta representación . No usan una ventana enrollable . Hacer 3 minutos en fragmentos de 350x0.5s está bien (tal vez no sea necesario, ¿con qué frecuencia predices?), Cada fragmento debe ser 3x3125. "Podría reestructurar esto en una matriz 3D con dimensiones de 3x350x3125" . Esto suena mejor, pero después de hacer las divisiones esperaría 350x3x3125 (350 fragmentos de 3x3125). Cada uno de estos fragmentos podría procesarse como lo describí.
n1k31t4