Main Content

使用深度网络设计器编译时间序列预测网络

此示例说明如何使用深度网络设计器创建一个简单的长短期记忆 (LSTM) 网络来预测时间序列数据。

LSTM 网络是一种循环神经网络 (RNN),它通过遍历时间步并更新 RNN 状态来处理输入数据。RNN 状态包含在所有先前时间步中记住的信息。您可以使用 LSTM 神经网络,通过将先前的时间步作为输入来预测时间序列或序列的后续值。要为时间序列预测创建一个 LSTM 网络,请使用深度网络设计器。

此图显示使用闭环预测的预测值的序列示例。

加载序列数据

WaveformData 加载示例数据。要访问此数据,请以实时脚本形式打开此示例。Waveform 数据集包含以合成方式生成的具有三个通道且具有不同长度的波形。此示例训练一个 LSTM 神经网络,根据先前时间步提供的值来预测波形的将来值。

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

可视化一些序列。

idx = 1;
numChannels = size(data{idx},2);

figure
stackedplot(data{idx},DisplayLabels="Channel " + (1:numChannels))

准备要训练的数据

要预测序列在将来时间步的值,请将目标指定为将值移位了一个时间步的训练序列。不要在训练序列中包含最终时间步。也就是说,在输入序列的每个时间步,LSTM 神经网络都学习预测下一个时间步的值。预测变量是没有最终时间步的训练序列。

numObservations = numel(data);
XData = cell(numObservations,1);
TData = cell(numObservations,1);
for n = 1:numObservations
    X = data{n};
    XData{n} = X(1:end-1,:);
    TData{n} = X(2:end,:);
end

将数据划分为训练集、验证集和测试集。将 80% 的数据用于训练,将 10% 的数据用于验证,而将 10% 的数据用于测试。要划分数据,请使用 trainingPartitions 函数,此函数作为支持文件包含在此示例中。要访问此文件,请以实时脚本形式打开此示例。

[idxTrain,idxValidation,idxTest] = trainingPartitions(numObservations,[0.8 0.1 0.1]);

XTrain = XData(idxTrain);
TTrain = TData(idxTrain);

XValidation = XData(idxValidation);
TValidation = TData(idxValidation);

XTest = XData(idxTest);
TTest = TData(idxTest);

为了更好地拟合并防止训练发散,请将预测变量和目标值归一化,以使通道的均值和单位方差为零。在进行预测时,还必须使用与训练数据相同的统计量对验证数据和测试数据进行归一化。

计算序列的每通道均值和标准差值。要轻松计算训练数据的均值和标准差,请使用 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

使用从训练数据计算出的统计量来归一化验证数据和测试数据。

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

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

定义网络架构

要构建网络,请打开深度网络设计器。

deepNetworkDesigner

要创建序列网络,请在序列网络部分中的序列到序列上暂停,然后点击打开

这会打开一个适合序列分类问题的预置网络。网络包含下列层:

  • sequenceInputLayer

  • lstmLayer

  • dropoutLayer

  • fullyConnectedLayer

  • softmaxLayer

您可以通过编辑最终层将分类网络转换为适合时间序列预测的网络。首先,删除 softmax 层。

接下来,调整层的属性,使其适用于 Waveform 数据集。由于目标是预测时间序列中将来的数据点,因此输出大小必须与输入大小相同。在此示例中,输入数据有三个输入通道,因此网络输出也必须有三个输出通道。

选择序列输入层 input,并将 InputSize 设置为 3。

选择全连接层 fc,并将 OutputSize 设置为 3。

LSTM 层有 128 个隐藏单元。隐藏单元的数量确定该层学习了多少信息。使用更多隐藏单元可以产生更准确的结果,但也更有可能导致训练数据过拟合。丢弃层通过将该层的输入随机设置为零并在训练迭代之间有效地更改网络架构来帮助避免过拟合。较高的丢弃概率有助于提高模型的泛化能力,但代价是丢失信息和减慢学习过程。

要检查网络是否准备好进行训练,请点击分析。深度学习网络分析器报告零错误或警告,因此,网络已准备就绪,可以开始进行训练。要导出网络,请点击导出。该 App 将网络保存在变量 net_1 中。

指定训练选项

指定训练选项。在选项中进行选择需要经验分析。要通过运行试验探索不同训练选项配置,您可以使用Experiment Manager

  • 使用 Adam 优化进行训练。

  • 进行 200 轮训练。对于较大的数据集,您可能不需要像良好拟合那样进行这么多轮训练。

  • 在每个小批量中,对序列进行左填充,使它们具有相同的长度。左填充可以防止 RNN 预测序列末尾的填充值。

  • 每轮训练都会打乱数据。

  • 使用验证数据监控过拟合。

  • 监控均方根误差。

  • 在绘图中显示训练进度。

  • 禁用详尽输出。

options = trainingOptions("adam", ...
    MaxEpochs=200, ...
    SequencePaddingDirection="left", ...
    Shuffle="every-epoch", ...
    ValidationData={XValidation,TValidation}, ...
    Metrics="rmse", ...
    Plots="training-progress", ...
    Verbose=false);

训练神经网络

使用 trainnet 函数训练 LSTM 神经网络。对于回归,请使用均方误差损失。默认情况下,trainnet 函数使用 GPU(如果有)。使用 GPU 需要 Parallel Computing Toolbox™ 许可证和受支持的 GPU 设备。有关受支持设备的信息,请参阅GPU Computing Requirements (Parallel Computing Toolbox)。否则,该函数使用 CPU。要指定执行环境,请使用 ExecutionEnvironment 训练选项。

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

测试循环神经网络

使用 minibatchpredict 函数进行预测。默认情况下,minibatchpredict 函数使用 GPU(如果有)。使用与训练相同的填充选项填充序列。对于具有不同长度序列的“序列到序列”任务,通过将 UniformOutput 选项设置为 false,以元胞数组形式返回预测。

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

对于每个测试序列,请计算预测值和目标值之间的均方根误差 (RMSE)。在计算 RMSE 之前,请通过删除填充值来忽略任何填充值。

numObservationsTest = numel(XTest);

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

计算所有测试观测值的 RMSE 均值。

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

预测将来时间步

给定输入时间序列或序列,要预测多个将来时间步的值,请使用 predict 函数一次预测一个时间步,并在每次预测时更新 RNN 状态。对于每次预测,使用前一次预测作为函数的输入。

在绘图中可视化其中一个测试序列。

idx = 3;
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 的真实值,并将其用作输入进行下一次预测。在进行下一次预测之前,如果有可以提供给 RNN 的真实值,则请使用开环预测。

  • 闭环预测 - 通过使用先前的预测值作为输入来预测序列中的后续时间步。在这种情况下,模型不需要真实值便可进行预测。例如,假设您要仅使用在时间步 1 至 t-1 中收集的数据来预测序列的时间步 tt+k 的值。要对时间步 i 进行预测,请使用时间步 i-1 的预测值作为输入。使用闭环预测来预测多个后续时间步,或在进行下一次预测之前没有真实值可提供给 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,:);

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

闭环预测

执行闭环预测。

首先使用 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"])

闭环预测允许您预测任意数量的时间步,但与开环预测相比,其准确度可能会降低,因为 RNN 在预测过程中不会访问真实值。

另请参阅

相关主题