Main Content

このページの翻訳は最新ではありません。ここをクリックして、英語の最新版を参照してください。

深層学習を使用した時系列予測

この例では、長短期記憶 (LSTM) ネットワークを使用して時系列データを予測する方法を説明します。

LSTM ネットワークは、タイム ステップでループ処理してネットワークの状態を更新することにより入力データを処理する再帰型ニューラル ネットワーク (RNN) です。ネットワークの状態には、前のすべてのタイム ステップで記憶された情報が含まれています。LSTM ネットワークを使用して、前のタイム ステップを入力として使用して、時系列またはシーケンスの後続の値を予測できます。時系列予測用に LSTM ネットワークに学習させるには、シーケンス出力で回帰用の LSTM ネットワークに学習させます。ここで、応答 (ターゲット) は、1 タイム ステップ分シフトした値を持つ学習シーケンスとします。つまり、入力シーケンスの各タイム ステップで、次のタイム ステップの値を予測するように LSTM ネットワークに学習させます。

予測には、開ループ予測と閉ループ予測の 2 つの方法があります。

  • 開ループ予測では、入力データのみを使用してシーケンスの次のタイム ステップを予測します。後続のタイム ステップの予測を行うときは、真の値をデータ ソースから収集し、それを入力として使用します。たとえば、1 ~ t-1 のタイム ステップで収集されたデータを使用して、そのシーケンスのタイム ステップ t の値を予測するとします。タイム ステップ t+1 の予測を行うには、タイム ステップ t の真の値が記録されるまで待ち、それを入力として使用して次の予測を行います。次の予測を行う前にネットワークに提供する真の値がある場合は、開ループ予測を使用します。

  • 閉ループ予測では、前の予測を入力として使用して、シーケンスの後続のタイム ステップを予測します。この場合、モデルは予測を行うために真の値を必要としません。たとえば、1 ~ t-1 のタイム ステップでのみ収集されたデータを使用して、シーケンスのタイム ステップ tt+k の値を予測するとします。タイム ステップ i の予測を行うには、タイム ステップ i-1 の予測値を入力として使用します。複数の後続のタイム ステップを予測する場合、または次の予測を行う前にネットワークに提供する真の値がない場合には、閉ループ予測を使用します。

以下の Figure は、閉ループ予測を使用した予測値のシーケンスの例を示しています。

この例では、波形データ セットを使用します。このデータ セットには、3 つのチャネルの異なる波長の合成生成波形が 2,000 個含まれます。この例では、閉ループ予測と開ループ予測の両方を使用して、前のタイム ステップからの値を与えて波形の将来の値を予測するよう LSTM ネットワークに学習させます。

データの読み込み

サンプル データを WaveformData.mat から読み込みます。データは、numObservations 行 1 列のシーケンスの cell 配列です。ここで、numObservations はシーケンスの数です。各シーケンスは numChannels-numTimeSteps 列の数値配列です。ここで、numChannels はシーケンスのチャネル数、numTimeSteps はシーケンスのタイム ステップ数です。

load WaveformData

最初のいくつかのシーケンスのサイズを表示します。

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

チャネル数を表示します。ネットワークに学習させるには、各シーケンスのチャネル数が同じでなければなりません。

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

最初のいくつかのシーケンスをプロットに可視化します。

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

    xlabel("Time Step")
end

データを学習セットとテスト セットに分割します。観測値の 90% を学習に使用し、残りをテストに使用します。

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

学習用データの準備

シーケンスの将来のタイム ステップの値を予測するには、1 タイム ステップ分シフトした値を持つ学習シーケンスとしてターゲットを指定します。つまり、入力シーケンスの各タイム ステップで、次のタイム ステップの値を予測するように LSTM ネットワークに学習させます。予測子は、最後のタイム ステップを含まない学習シーケンスです。

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

良好な適合を実現し、学習の発散を防ぐには、ゼロ平均と単位分散を持つように予測子とターゲットを正規化します。予測を行うときは、学習データと同じ統計を使用してテスト データも正規化しなければなりません。すべてのシーケンスについて平均値と標準偏差を簡単に計算するには、時間の次元でシーケンスを連結します。

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

LSTM ネットワーク アーキテクチャの定義

LSTM 回帰ネットワークを作成します。

  • 入力データのチャネル数と一致する入力サイズのシーケンス入力層を使用します。

  • 128 個の隠れユニットを持つ LSTM 層を使用します。隠れユニットの数によって、層に学習させる情報量が決まります。より多くの隠れユニットを使用するほど正確な結果が得られますが、学習データの過適合につながる可能性が高くなります。

  • 入力データと同じチャネル数でシーケンスを出力するには、入力データのチャネル数と一致する出力サイズの全結合層を含めます。

  • 最後に、回帰層を含めます。

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

学習オプションの指定

学習オプションを指定します。

  • Adam 最適化を使用して学習させます。

  • 学習を 200 エポック行います。より大きなデータ セットでは、良好な適合を実現させるために多くのエポックを学習させる必要がない場合があります。

  • 各ミニバッチでシーケンスを左パディングし、シーケンスが同じ長さになるようにします。左パディングにより、シーケンスの最後にパディングされた値をネットワークが予測することを防ぎます。

  • すべてのエポックでデータをシャッフルします。

  • 学習の進行状況をプロットに表示。

  • 詳細出力を無効にします。

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

ニューラル ネットワークの学習

関数 trainNetwork を使用し、指定した学習オプションで LSTM ネットワークに学習させます。

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

ネットワークのテスト

学習データの場合と同じ手順に従って、予測用のテスト データを準備します。

学習データから計算された統計を使用してテスト データを正規化します。1 タイム ステップ分シフトした値を持つテスト シーケンスとしてターゲットを指定し、最後のタイム ステップを含まないテスト シーケンスとして予測子を指定します。

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

テスト データを使用して、予測を実行します。学習の場合と同じパディング オプションを指定します。

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

精度を評価するには、各テスト シーケンスについて、予測とターゲットの間の平方根平均二乗誤差 (RMSE) を計算します。

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

ヒストグラムで誤差を可視化します。値が小さいほど精度が高いことを示しています。

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

すべてのテスト観測について平均 RMSE を計算します。

mean(rmse)
ans = single
    0.5080

将来のタイム ステップの予測

入力された時系列またはシーケンスについて、将来の複数のタイム ステップの値を予測するには、関数 predictAndUpdateState を使用して、タイム ステップを 1 つずつ予測し、予測ごとにネットワークの状態を更新します。予測ごとに、前の予測を関数への入力として使用します。

テスト シーケンスの 1 つをプロットに可視化します。

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

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

開ループ予測

開ループ予測では、入力データのみを使用してシーケンスの次のタイム ステップを予測します。後続のタイム ステップの予測を行うときは、データ ソースから真の値を収集し、それを入力として使用します。たとえば、1 ~ t-1 のタイム ステップで収集されたデータを使用して、そのシーケンスのタイム ステップ t の値を予測するとします。タイム ステップ t+1 の予測を行うには、タイム ステップ t の真の値が記録されるまで待ち、それを入力として使用して次の予測を行います。次の予測を行う前にネットワークに提供する真の値がある場合は、開ループ予測を使用します。

まず、関数 resetState を使用して状態をリセットし、ネットワークの状態を初期化します。次に、入力データの最初のいくつかのタイム ステップを使用して初期予測を行います。入力データの最初の 75 タイム ステップを使用して、ネットワークの状態を更新します。

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

さらに予測を続行するには、タイム ステップでループ処理し、関数 predictAndUpdateState を使用してネットワークの状態を更新します。入力データをタイム ステップでループ処理し、それらをネットワークへの入力として使用することにより、テスト観測の残りのタイム ステップの値を予測します。最初の予測は、タイム ステップ 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

予測とターゲット値を比較します。

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"])

閉ループ予測

閉ループ予測では、前の予測を入力として使用して、シーケンスの後続のタイム ステップを予測します。この場合、モデルは予測を行うために真の値を必要としません。たとえば、1 ~ t-1 のタイム ステップでのみ収集されたデータを使用して、そのシーケンスの tt+k のタイム ステップの値を予測するとします。タイム ステップ i の予測を行うには、タイム ステップ i-1 の予測値を入力として使用します。後続の複数のタイム ステップの予測を行う場合、または次の予測を行う前にネットワークに提供する真の値がない場合は、閉ループ予測を使用します。

まず、関数 resetState を使用して状態をリセットし、ネットワークの状態を初期化します。次に、入力データの最初のいくつかのタイム ステップを使用して初期予測 Z を求めます。入力データの最初の 75 タイム ステップを使用して、ネットワークの状態を更新します。

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

さらに予測を続行するには、タイム ステップでループ処理し、関数 predictAndUpdateState を使用してネットワークの状態を更新します。ネットワークに前の予測値を繰り返し渡すことにより、次の 200 タイム ステップを予測します。ネットワークはさらに予測を行うための入力データを必要としないため、予測するタイム ステップには任意の数を指定できます。

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

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

予測値をプロットに可視化します。

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"])

閉ループ予測では任意のタイム ステップ数を予測できますが、予測プロセス中にネットワークが真の値にアクセスできないため、開ループ予測と比較して精度が低くなる可能性があります。

参考

| | |

関連するトピック