Keras LSTM avec série temporelle 1D

10

J'apprends à utiliser Keras et j'ai eu un succès raisonnable avec mon ensemble de données étiqueté en utilisant les exemples de Deep Learning pour Python de Chollet . L'ensemble de données est ~ 1000 séries temporelles de longueur 3125 avec 3 classes potentielles.

Je voudrais aller au-delà des couches denses de base qui me donnent un taux de prédiction d'environ 70% et le livre continue pour discuter des couches LSTM et RNN.

Tous les exemples semblent utiliser des ensembles de données avec plusieurs fonctionnalités pour chaque série temporelle et j'ai du mal à trouver comment implémenter mes données en conséquence.

Si, par exemple, j'ai des séries temporelles de 1 000 x 3 125, comment puis-je les alimenter dans quelque chose comme la couche SimpleRNN ou LSTM? Me manque-t-il une connaissance fondamentale de ce que font ces couches?

Code actuel:

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()
user1147964
la source

Réponses:

10

Les couches LSTM nécessitent des données de forme différente.

D'après votre description, je comprends que l'ensemble de données de départ comporte 3125 lignes et 1000 colonnes, où chaque ligne correspond à un pas de temps. La variable cible doit alors avoir 3125 lignes et 1 colonne, chaque valeur pouvant être l'une des trois valeurs possibles. Il semble donc que vous ayez un problème de classification. Pour vérifier cela dans le code, je ferais:

>>> X.shape
(3125, 1000)

>>> y.shape
(1000,)

La classe LSTM exige que chaque échantillon unique soit constitué d'un «bloc» de temps. Disons que vous voulez avoir un bloc de 100 pas de temps. Cela signifie qu'il X[0:100]s'agit d'un échantillon d'entrée unique, qui correspond à la variable cible à y[100]. cela signifie que la taille de votre fenêtre (c'est-à-dire le nombre de pas de temps ou le nombre de retards) est égale à 100. Comme indiqué ci-dessus, vous avez donc 3125 échantillons N = 3125. Pour former le premier bloc, nous devons malheureusement jeter les 100 premiers échantillons y, car nous ne pouvons pas former un bloc entier de 100 à partir des données disponibles (nous finirions par avoir besoin des points de données avant X[0]).

Compte tenu de tout cela, un LSTM vous oblige à fournir des lots de formes (N - window_size, window_size, num_features), ce qui se traduit par (3125 - 100, 100, 1000)== (3025, 100, 1000).

La création de ces blocs de temps est un peu compliquée, mais créez une bonne fonction une fois, puis enregistrez-la :)

Il y a plus de travail à faire, peut-être regardez des exemples plus approfondis de mon explication ci-dessus ici ... ou lisez la documentation LSTM , (ou mieux encore, le code source! ).

Le modèle final serait alors assez simple (basé sur votre code):

#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'])

Jetez un œil à la documentation concernant la forme d'entrée du Sequentialmodèle . Il dit essentiellement que nous n'avons pas besoin de spécifier le nombre de lots à l'intérieur input_shape. Cela peut être fait avec, par exemple batch_size=50, si vous souhaitez que ce soit un nombre fixe.

Je sais que l' input_shapeargument n'est pas dans la documentation de LSTM, mais la classe elle-même hérite de RNN, qui à son tour hérite de Layer- elle pourra donc utiliser les informations que vous fournissez.

Un dernier conseil: si vous prévoyez d'ajouter plusieurs couches LSTM (en les «empilant»), vous devrez ajouter un argument de plus à tous sauf le dernier LSTM , à savoir le return_sequences=True.

n1k31t4
la source
Merci pour la réponse complète Dexter (!). En ce qui concerne vos commentaires sur la taille du lot, la taille de lot spécifiée dans l'argument model.fit est-elle un hyper paramètre différent de la création de mon propre lot personnalisé? J'ai réussi à faire fonctionner mon code au moins en remodelant mes données d'une matrice 1000x3125 en une matrice 3D en utilisant data = np.reshape (data, (1000,1,3125)). Cela m'a permis d'exécuter le LSTM avec input_shape (1,3125) mais encore une fois, je ne suis pas vraiment sûr de ce que je fais. Encore une fois, merci beaucoup pour la réponse. J'examinerai les liens que vous avez fournis et j'étudierai davantage votre réponse.
user1147964
Vous êtes les bienvenus! Oui, vous l'avez compris, si vous omettez batch_sizelors de la définition du modèle, il sera tiré du même argument qu'il contient model.fit(). Vous devez remodeler pour obtenir (3025, 100, 1000), ce qui signifie 3025 lots, chacun de 100 (lignes) timesteps et 1000 (colonnes) variables. L'utilisation np.reshapene fonctionnera malheureusement pas pour cela (vous obtiendrez une erreur), car vous aurez des chevauchements de données ... la forme finale a plus de données que l'entrée. 3025x100x1000> 3125x1000 - np.reshapen'aime pas ça car c'est ambigu. Je suggère simplement de boucler sur l'ensemble de données, 1 boucle = 1 échantillon.
n1k31t4
Je pense que je suis un peu confus ici et cela pourrait être dû au fait que j'ai peut-être par inadvertance déjà fait le processus de traitement par lots. J'utiliserai des valeurs spécifiques ici. J'ai échantillonné 3 mesures différentes à 6,25 kHz pendant environ 3 minutes, résultant en 3 séries temporelles de longueur 1093750. Cela génère une matrice 3x1093750. J'ai ensuite segmenté chaque TS en incréments de 0,5 seconde, résultant en une matrice 1050x3125. Je pourrais techniquement restructurer cela en une matrice 3D avec des dimensions 3x350x3125. Cela me donne 350 "lots" longs de 0,5 secondes. Votre remodelage semble générer beaucoup plus de valeurs Merci pour la réponse à nouveau. Désolé
user1147964
Juste pour ajouter, la lecture du premier lien que vous avez publié me fait penser que je remodèle les choses correctement. Toutes mes excuses si je manque quelque chose d'évident mais ici, ils commencent avec une longueur TS 5000, et le transforme en une matrice 3D avec des dimensions [1 25 200].
user1147964
Par rapport à la méthode de votre lien, ma façon créera beaucoup plus d'échantillons. C'est parce que j'utilise une sorte de fenêtre «glissante». Jetez un oeil à cette représentation . Ils n'utilisent pas de fenêtre roulante . Faire 3 minutes en morceaux de 350x0.5s est correct (peut-être pas nécessaire - à quelle fréquence prévoyez-vous?), Chaque morceau doit être de 3x3125. "Je pourrais restructurer cela en une matrice 3D avec des dimensions 3x350x3125" - cela sonne mieux, mais après avoir fait les divisions, je m'attendrais à 350x3x3125 (350 morceaux de 3x3125). Chacun de ces morceaux pourrait ensuite être traité comme je l'ai décrit.
n1k31t4