Main Content

La traduction de cette page n'est pas à jour. Cliquez ici pour voir la dernière version en anglais.

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 réseau. L’état du réseau contient des informations mémorisées sur tous les pas de temps précédents. Vous pouvez utiliser un réseau 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 LSTM pour la prévision de séries temporelles, entraînez un réseau 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 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.

  • 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 réseau avant de faire la prédiction suivante.

  • 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 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 réseau 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 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 de dimension numChannels x -numTimeSteps, où numChannels est le nombre de canaux de la séquence et numTimeSteps est le nombre de pas de temps de la séquence.

load WaveformData

Affichez les tailles des premières séquences.

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

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

numChannels = size(data{1},1)
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

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. En d’autres termes, à chaque pas de temps de la séquence en entrée, le réseau 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.

for n = 1:numel(dataTrain)
    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 la moyenne soit nulle et la 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. Pour calculer facilement la moyenne et l’écart-type sur toutes les séquences, concaténez les séquences dans la dimension temporelle.

muX = mean(cat(2,XTrain{:}),2);
sigmaX = std(cat(2,XTrain{:}),0,2);

muT = mean(cat(2,TTrain{:}),2);
sigmaT = std(cat(2,TTrain{:}),0,2);

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 LSTM

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

  • 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.

  • Enfin, introduisez une couche de régression.

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

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 réseau 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=0);

Entraîner le réseau de neurones

Entraînez le réseau LSTM avec les options d’apprentissage spécifiées avec la fonction trainNetwork.

net = trainNetwork(XTrain,TTrain,layers,options);

Tester le réseau

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.

for n = 1:size(dataTest,1)
    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 les données de test. Spécifiez les mêmes options de remplissage que pour l’apprentissage.

YTest = predict(net,XTest,SequencePaddingDirection="left");

Pour évaluer la précision, pour chaque séquence de test, calculez la racine de l’erreur quadratique moyenne (RMSE) entre les prédictions et la cible.

for i = 1:size(YTest,1)
    rmse(i) = sqrt(mean((YTest{i} - TTest{i}).^2,"all"));
end

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

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

Calculez la RMSE moyenne sur toutes les observations de test.

mean(rmse)
ans = single
    0.5080

Prévoir les futurs pas de temps

Étant donné une série temporelle ou une séquence d’entrée, pour prédire les valeurs de plusieurs pas de temps futurs, utilisez la fonction predictAndUpdateState pour prédire les pas de temps un par un et mettre à jour l’état du réseau à 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)

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 réseau avant de faire la prédiction suivante.

Initialisez l’état du réseau 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 réseau avec les 75 premiers pas de temps des données en entrée.

net = resetState(net);
offset = 75;
[net,~] = predictAndUpdateState(net,X(:,1:offset));

Pour prévoir d’autres prédictions, effectuez une boucle sur les pas de temps et mettez à jour l’état du réseau avec la fonction predictAndUpdateState. 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 réseau. La première prédiction est la valeur correspondant au pas de temps offset + 1.

numTimeSteps = size(X,2);
numPredictionTimeSteps = numTimeSteps - offset;
Y = zeros(numChannels,numPredictionTimeSteps);

for t = 1:numPredictionTimeSteps
    Xt = X(:,offset+t);
    [net,Y(:,t)] = predictAndUpdateState(net,Xt);
end

Comparez les prédictions avec les valeurs cibles.

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

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

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

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 réseau avant de faire la prédiction suivante.

Initialisez l’état du réseau 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 réseau avec les 75 premiers pas de temps des données en entrée.

net = resetState(net);
offset = size(X,2);
[net,Z] = predictAndUpdateState(net,X);

Pour prévoir d’autres prédictions, effectuez une boucle sur les pas de temps et mettez à jour l’état du réseau avec la fonction predictAndUpdateState. Prévoyez les 200 pas de temps suivants en passant itérativement la valeur prédite précédente dans le réseau. Le réseau 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.

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

for t = 1:numPredictionTimeSteps
    [net,Y(:,t)] = predictAndUpdateState(net,Xt);
    Xt = Y(:,t);
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(T(i,1:offset))
    hold on
    plot(offset:numTimeSteps,[T(i,offset) Y(i,:)],'--')
    ylabel("Channel " + i)
end

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

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 réseau n’a pas accès aux vraies valeurs pendant le processus de prévision.

Voir aussi

| | |

Sujets associés