Main Content

Prévision de séries temporelles avec le Deep Learning

Cet exemple montre comment prévoir des données de séries temporelles avec un réseau LSTM (long short-term memory).

Un réseau LSTM est un réseau de neurones récurrent (RNN, Recurrent Neural Network) qui traite les données d’entrée en effectuant une boucle à chaque pas de temps tout en mettant à jour l’état du RNN. L’état du RNN contient des informations mémorisées sur tous les pas de temps précédents. Vous pouvez utiliser un réseau de neurones LSTM pour prévoir les valeurs suivantes d’une série temporelle ou d’une séquence en utilisant les pas de temps précédents comme entrée. Pour entraîner un réseau de neurones LSTM pour la prévision de séries temporelles, entraînez un réseau de neurones LSTM de régression ayant une sortie de type séquence où les réponses (cibles) sont les séquences d’apprentissage avec des valeurs décalées d’un pas de temps. En d’autres termes, à chaque pas de temps de la séquence en entrée, le réseau de neurones LSTM apprend à prédire la valeur du pas de temps suivant.

Il existe deux méthodes de prévision : en boucle ouverte ou fermée.

  • Prévision en boucle ouverte — Prédit le pas de temps suivant dans une séquence en utilisant uniquement les données en entrée. Lorsque vous faites des prédictions pour les pas de temps suivants, vous collectez les vraies valeurs de votre source de données et les utilisez comme entrée. Par exemple, supposons que vous souhaitiez prédire la valeur pour le pas de temps t d’une séquence avec des données collectées entre les pas de temps 1 et t-1. Pour faire des prédictions pour le pas de temps t+1, attendez jusqu’à ce que vous enregistriez la vraie valeur pour le pas de temps t et utilisez-la comme entrée pour faire la prédiction suivante. Utilisez une prévision en boucle ouverte lorsque vous avez de vraies valeurs à fournir au RNN avant de faire la prédiction suivante.

  • Prévision en boucle fermée — Prédit les pas de temps suivants dans une séquence en utilisant les prédictions précédentes comme entrée. Dans ce cas, le modèle n’a pas besoin de vraies valeurs pour faire la prédiction. Par exemple, supposons que vous souhaitiez prédire les valeurs pour les pas de temps t à t+k de la séquence avec des données collectées entre les pas de temps 1 et t-1 uniquement. Pour faire des prédictions pour le pas de temps i, utilisez la valeur prédite pour le pas de temps i-1 comme entrée. Utilisez une prévision en boucle fermée pour prévoir plusieurs pas de temps suivants ou lorsque vous n’avez pas de vraies valeurs à fournir au RNN avant de faire la prédiction suivante.

Cette figure montre un exemple de séquence avec des valeurs prévues à l’aide d’une prédiction en boucle fermée.

closedloop.png

Cet exemple utilise le jeu de données Waveform qui contient 2 000 formes d’onde générées synthétiquement de différentes longueurs avec trois canaux. Cet exemple entraîne un réseau de neurones LSTM pour prévoir les futures valeurs des formes d’onde selon les valeurs des pas de temps précédents, en utilisant une prévision en boucle fermée et une prévision en boucle ouverte.

Charger les données

Chargez les données d'exemple depuis WaveformData.mat. Les données sont un cell array de séquences de dimension numObservations x 1, où numObservations est le nombre de séquences. Chaque séquence est un tableau numérique numTimeSteps x -numChannels, où numTimeSteps est le nombre de pas de temps de la séquence et numChannels est le nombre de canaux de la séquence.

load WaveformData

Affichez les tailles des premières séquences.

data(1:4)
ans=4×1 cell array
    {103×3 double}
    {136×3 double}
    {140×3 double}
    {124×3 double}

Affichez le nombre de canaux. Pour entraîner le réseau de neurones LSTM, chaque séquence doit avoir le même nombre de canaux.

numChannels = size(data{1},2)
numChannels = 3

Visualisez les premières séquences dans un tracé.

figure
tiledlayout(2,2)
for i = 1:4
    nexttile
    stackedplot(data{i})

    xlabel("Time Step")
end

Figure contains objects of type stackedplot.

Partitionnez les données entre les jeux d’apprentissage et de test. Utilisez 90 % des observations pour l’apprentissage et le reste pour les tests.

numObservations = numel(data);
idxTrain = 1:floor(0.9*numObservations);
idxTest = floor(0.9*numObservations)+1:numObservations;
dataTrain = data(idxTrain);
dataTest = data(idxTest);

Préparer les données pour l’apprentissage

Pour prédire les valeurs des futurs pas de temps d’une séquence, spécifiez les cibles comme étant les séquences d’apprentissage dont les valeurs sont décalées d’un pas de temps. N’incluez pas le pas de temps final dans les séquences d’apprentissage. En d’autres termes, à chaque pas de temps de la séquence en entrée, le réseau de neurones LSTM apprend à prédire la valeur du pas de temps suivant. Les prédicteurs sont les séquences d’apprentissage sans le pas de temps final.

numObservationsTrain = numel(dataTrain);
XTrain = cell(numObservationsTrain,1);
TTrain = cell(numObservationsTrain,1);
for n = 1:numObservationsTrain
    X = dataTrain{n};
    XTrain{n} = X(1:end-1,:);
    TTrain{n} = X(2:end,:);
end

Pour un meilleur ajustement et pour éviter que l’apprentissage diverge, normalisez les prédicteurs et les cibles afin que les canaux aient une moyenne nulle et une variance unitaire. Lorsque vous faites des prédictions, vous devez également normaliser les données de test avec les mêmes statistiques que les données d’apprentissage.

Calculez la moyenne et l’écart-type par canal pour les séquences. Pour calculer facilement la moyenne et l’écart-type pour les données d’apprentissage, créez des tableaux numériques contenant les séquences concaténées avec la fonction cell2mat.

muX = mean(cell2mat(XTrain));
sigmaX = std(cell2mat(XTrain),0);

muT = mean(cell2mat(TTrain));
sigmaT = std(cell2mat(TTrain),0);

Normalisez les séquences avec la moyenne et l’écart-type calculés.

for n = 1:numel(XTrain)
    XTrain{n} = (XTrain{n} - muX) ./ sigmaX;
    TTrain{n} = (TTrain{n} - muT) ./ sigmaT;
end

Définir l’architecture du réseau de neurones LSTM

Créez un réseau de neurones LSTM de régression.

  • Utilisez une couche d’entrée de séquence avec une taille d’entrée correspondant au nombre de canaux des données en entrée.

  • Utilisez une couche LSTM avec 128 unités cachées. Le nombre d’unités cachées détermine la quantité d’informations apprises par la couche. L’utilisation d’un plus grand nombre d’unités cachées peut produire des résultats plus précis, mais est davantage susceptible d’entraîner un surajustement aux données d’apprentissage.

  • Pour obtenir en sortie des séquences avec le même nombre de canaux que les données en entrée, utilisez une couche entièrement connectée avec une taille de sortie correspondant au nombre de canaux des données en entrée.

layers = [
    sequenceInputLayer(numChannels)
    lstmLayer(128)
    fullyConnectedLayer(numChannels)];

Spécifier les options d’apprentissage

Spécifiez les options d’apprentissage.

  • Effectuez un apprentissage avec l’optimisation Adam.

  • Effectuez un apprentissage sur 200 epochs. Pour des jeux de données plus grands, il n’est pas forcément nécessaire d’effectuer un apprentissage sur autant d’epochs pour un bon ajustement.

  • Dans chaque mini-batch, remplissez les séquences à gauche afin qu’elles aient la même longueur. Le remplissage à gauche évite que le RNN prédise des valeurs de remplissage à la fin des séquences.

  • Mélangez les données à chaque epoch.

  • Affichez la progression de l’apprentissage dans un tracé.

  • Désactivez la sortie en clair.

options = trainingOptions("adam", ...
    MaxEpochs=200, ...
    SequencePaddingDirection="left", ...
    Shuffle="every-epoch", ...
    Plots="training-progress", ...
    Verbose=false);

Entraîner un réseau de neurones récurrent

Entraînez le réseau de neurones LSTM avec la fonction trainnet. Pour la régression, utilisez la perte d’erreur quadratique moyenne. Par défaut et selon disponibilité, la fonction trainnet utilise un GPU. L’utilisation d’un GPU nécessite une licence Parallel Computing Toolbox™ et un dispositif GPU supporté. Pour plus d'information sur les dispositifs supportés, veuillez consulter GPU Computing Requirements (Parallel Computing Toolbox). Sinon, la fonction utilise le CPU. Pour spécifier l’environnement d’exécution, utilisez l’option d’apprentissage ExecutionEnvironment.

net = trainnet(XTrain,TTrain,layers,"mse",options);

Tester un réseau de neurones récurrent

Préparez les données de test pour la prédiction avec les mêmes pas que les données d’apprentissage.

Normalisez les données de test avec les statistiques calculées à partir des données d’apprentissage. Spécifiez les cibles comme séquences de test avec les valeurs décalées d’un pas de temps et les prédicteurs comme séquences de test sans le pas de temps final.

numObservationsTest = numel(dataTest);
XTest = cell(numObservationsTest,1);
TTest = cell(numObservationsTest,1);
for n = 1:numObservationsTest
    X = dataTest{n};
    XTest{n} = (X(1:end-1,:) - muX) ./ sigmaX;
    TTest{n} = (X(2:end,:) - muT) ./ sigmaT;
end

Réalisez des prédictions avec la fonction minibatchpredict. Par défaut et selon disponibilité, la fonction minibatchpredict utilise un GPU. Remplissez les séquences en utilisant les mêmes options de remplissage que pour l’apprentissage. Pour les tâches sequence-to-sequence avec des séquences de différentes longueurs, renvoyez les prédictions comme un cell array en définissant l’option UniformOutput à false.

YTest = minibatchpredict(net,XTest, ...
    SequencePaddingDirection="left", ...
    UniformOutput=false);

Pour chaque séquence de test, calculez la racine de l’erreur quadratique moyenne (RMSE) entre les prédictions et les cibles. Ignorez les valeurs de remplissage dans les séquences prédites en vous référant à la longueur des séquences cibles.

for n = 1:numObservationsTest
    T = TTest{n};

    sequenceLength = size(T,1);    

    Y = YTest{n}(end-sequenceLength+1:end,:);

    err(n) = rmse(Y,T,"all");
end

Visualisez les erreurs dans un histogramme. Plus les valeurs sont faibles, plus la précision est grande.

figure
histogram(err)
xlabel("RMSE")
ylabel("Frequency")

Figure contains an axes object. The axes object with xlabel RMSE, ylabel Frequency contains an object of type histogram.

Calculez la RMSE moyenne sur toutes les observations de test.

mean(err,"all")
ans = single
    0.5099

Prévoir les futurs pas de temps

Étant donné une série temporelle ou une séquence en entrée, pour prédire les valeurs de plusieurs pas de temps futurs, utilisez la fonction predict pour prédire les pas de temps un par un et mettre à jour l’état du RNN à chaque prédiction. Pour chaque prédiction, utilisez la prédiction précédente comme entrée de la fonction.

Visualisez une des séquences de test dans un tracé.

idx = 2;
X = XTest{idx};
T = TTest{idx};

figure
stackedplot(X,DisplayLabels="Channel " + (1:numChannels))
xlabel("Time Step")
title("Test Observation " + idx)

Figure contains an object of type stackedplot. The chart of type stackedplot has title Test Observation 2.

Prévision en boucle ouverte

La prévision en boucle ouverte prédit le pas de temps suivant dans une séquence avec uniquement les données en entrée. Lorsque vous faites des prédictions pour les pas de temps suivants, vous collectez les vraies valeurs de votre source de données et les utilisez comme entrée. Par exemple, supposons que vous souhaitiez prédire la valeur pour le pas de temps t d’une séquence avec des données collectées entre les pas de temps 1 et t-1. Pour faire des prédictions pour le pas de temps t+1, attendez jusqu’à ce que vous enregistriez la vraie valeur pour le pas de temps t et utilisez-la comme entrée pour faire la prédiction suivante. Utilisez une prévision en boucle ouverte lorsque vous avez de vraies valeurs à fournir au RNN avant de faire la prédiction suivante.

Initialisez l’état du RNN en réinitialisant d’abord l’état avec la fonction resetState puis faites une prédiction initiale avec les premiers pas de temps des données en entrée. Mettez à jour l’état du RNN avec les 75 premiers pas de temps des données en entrée.

net = resetState(net);
offset = 75;
[Z,state] = predict(net,X(1:offset,:));
net.State = state;

Pour prévoir d’autres prédictions, effectuez une boucle sur les pas de temps et faites des prédictions avec la fonction predict. Après chaque prédiction, mettez à jour l’état du RNN. Prévoyez des valeurs pour les pas de temps restants de l’observation de test en effectuant une boucle sur les pas de temps des données en entrée et en les utilisant comme entrée du RNN. Le dernier pas de temps de la prédiction initiale est le premier pas de temps prévu.

numTimeSteps = size(X,1);
numPredictionTimeSteps = numTimeSteps - offset;
Y = zeros(numPredictionTimeSteps,numChannels);
Y(1,:) = Z(end,1);

for t = 1:numPredictionTimeSteps-1
    Xt = X(offset+t,:);
    [Y(t+1,:),state] = predict(net,Xt);
    net.State = state;
end

Comparez les prédictions avec les valeurs d’entrée.

figure
t = tiledlayout(numChannels,1);
title(t,"Open Loop Forecasting")

for i = 1:numChannels
    nexttile
    plot(X(:,i))
    hold on
    plot(offset:numTimeSteps,[X(offset,i) Y(:,i)'],"--")
    ylabel("Channel " + i)
end

xlabel("Time Step")
nexttile(1)
legend(["Input" "Forecasted"])

Figure contains 3 axes objects. Axes object 1 with ylabel Channel 1 contains 2 objects of type line. These objects represent Input, Forecasted. Axes object 2 with ylabel Channel 2 contains 2 objects of type line. Axes object 3 with xlabel Time Step, ylabel Channel 3 contains 2 objects of type line.

Prévision en boucle fermée

La prévision en boucle fermée prédit les pas de temps suivants dans une séquence en utilisant les prédictions précédentes comme entrée. Dans ce cas, le modèle n’a pas besoin de vraies valeurs pour faire la prédiction. Par exemple, supposons que vous souhaitiez prédire la valeur pour les pas de temps t à t+k de la séquence avec des données collectées entre les pas de temps 1 et t-1 uniquement. Pour faire des prédictions pour le pas de temps i, utilisez la valeur prédite pour le pas de temps i-1 comme entrée. Utilisez une prévision en boucle fermée pour prévoir plusieurs pas de temps suivants ou lorsque vous n’avez pas de vraies valeurs à fournir au RNN avant de faire la prédiction suivante.

Initialisez l’état du RNN en réinitialisant d’abord l’état avec la fonction resetState puis faites une prédiction initiale Z avec les premiers pas de temps des données en entrée. Mettez à jour l’état du RNN avec tous les pas de temps des données en entrée.

net = resetState(net);
offset = size(X,1);
[Z,state] = predict(net,X(1:offset,:));
net.State = state;

Pour prévoir d’autres prédictions, effectuez une boucle sur les pas de temps et faites des prédictions avec la fonction predict. Après chaque prédiction, mettez à jour l’état du RNN. Prévoyez les 200 pas de temps suivants en passant itérativement la valeur prédite précédente au RNN. Le RNN n’ayant pas besoin des données en entrée pour faire d’autres prédictions, vous pouvez spécifier n’importe quel nombre de pas de temps pour la prévision. Le dernier pas de temps de la prédiction initiale est le premier pas de temps prévu.

numPredictionTimeSteps = 200;
Y = zeros(numPredictionTimeSteps,numChannels);
Y(1,:) = Z(end,:);

for t = 2:numPredictionTimeSteps
    [Y(t,:),state] = predict(net,Y(t-1,:));
    net.State = state;
end

Visualisez les valeurs prévues dans un tracé.

numTimeSteps = offset + numPredictionTimeSteps;

figure
t = tiledlayout(numChannels,1);
title(t,"Closed Loop Forecasting")

for i = 1:numChannels
    nexttile
    plot(X(1:offset,i))
    hold on
    plot(offset:numTimeSteps,[X(offset,i) Y(:,i)'],"--")
    ylabel("Channel " + i)
end

xlabel("Time Step")
nexttile(1)
legend(["Input" "Forecasted"])

Figure contains 3 axes objects. Axes object 1 with ylabel Channel 1 contains 2 objects of type line. These objects represent Input, Forecasted. Axes object 2 with ylabel Channel 2 contains 2 objects of type line. Axes object 3 with xlabel Time Step, ylabel Channel 3 contains 2 objects of type line.

La prévision en boucle fermée vous permet de prévoir un nombre arbitraire de pas de temps mais peut être moins précise que la prévision en boucle ouverte car le RNN n’a pas accès aux vraies valeurs pendant le processus de prévision.

Voir aussi

| | | |

Sujets associés