Main Content

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

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

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

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

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

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

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

closedloop.png

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

データの読み込み

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

load WaveformData

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

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

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

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

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

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

    xlabel("Time Step")
end

Figure contains objects of type stackedplot.

データを学習セットとテスト セットに分割します。観測値の 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 ニューラル ネットワークに学習させます。予測子は、最後のタイム ステップを含まない学習シーケンスです。

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

良好な適合を実現し、学習の発散を防ぐには、チャネルがゼロ平均と単位分散をもつように予測子とターゲットを正規化します。予測を行うときは、学習データと同じ統計を使用してテスト データも正規化しなければなりません。

シーケンスについて、チャネルごとの平均値と標準偏差値を計算します。学習データの平均と標準偏差を簡単に計算するには、関数 cell2mat を使用して、連結されたシーケンスを含む数値配列を作成します。

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

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

計算された平均値と標準偏差値を使用してシーケンスを正規化します。

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

学習オプションの指定

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

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

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

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

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

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

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

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

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

関数trainnetを使用して LSTM ニューラル ネットワークに学習させます。回帰の場合は、平均二乗誤差損失を使用します。既定では、関数 trainnet は利用可能な GPU がある場合にそれを使用します。GPU を使用するには、Parallel Computing Toolbox™ ライセンスとサポートされている GPU デバイスが必要です。サポートされているデバイスについては、GPU 計算の要件 (Parallel Computing Toolbox)を参照してください。そうでない場合、関数は CPU を使用します。実行環境を指定するには、ExecutionEnvironment 学習オプションを使用します。

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

再帰型ニューラル ネットワークのテスト

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

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

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

関数minibatchpredictを使用して予測を行います。既定では、関数 minibatchpredict は利用可能な GPU がある場合にそれを使用します。学習の場合と同じパディング オプションを使用してシーケンスをパディングします。さまざまな長さのシーケンスを含む sequence-to-sequence タスクでは、UniformOutput オプションを false に設定して、予測を cell 配列として返します。

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

各テスト シーケンスについて、予測とターゲットの間の平方根平均二乗誤差 (RMSE) を計算します。ターゲット シーケンスの長さを参照に使用し、予測シーケンス内のパディング値をすべて無視します。

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

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

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.

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

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

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

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

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

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.

開ループ予測

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

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

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

さらに予測を続行するには、タイム ステップ全体にわたってループ処理し、関数 predict を使用して予測を行います。各予測の後、RNN の状態を更新します。入力データをタイム ステップ全体にわたってループ処理し、それらを RNN への入力として使用することにより、テスト観測の残りのタイム ステップの値を予測します。初期予測の最後のタイム ステップが最初の予測タイム ステップになります。

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

予測を入力値と比較します。

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.

閉ループ予測

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

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

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

さらに予測を続行するには、タイム ステップ全体にわたってループ処理し、関数 predict を使用して予測を行います。各予測の後、RNN の状態を更新します。RNN に前の予測値を繰り返し渡すことにより、次の 200 タイム ステップを予測します。RNN はさらに予測を行うための入力データを必要としないため、予測するタイム ステップには任意の数を指定できます。初期予測の最後のタイム ステップが最初の予測タイム ステップになります。

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

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

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.

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

参考

| | | |

関連するトピック