参考博客及文献:4 Strategies for Multi-Step Time Series Forecasting

Multivariate Time Series Forecasting with LSTMs in Keras (machinelearningmastery.com)

LSTM进阶:使用LSTM进行多维多步的时间序列预测_lstm多维多部预测_一只小EZ的博客-CSDN博客

(更新于2023年4月,有问题或者有其他预测需求可以直接私聊我一起交流,我有空的时候会看的。本文将记录一下深度学习中LSTM人工神经网络的多种运用规则,以及在不同环境中的应用,将对LSTM里面提到的单维、多维单输入、单输出、多输入、多输出单步预测、多步预测等等概念进行一个整合。所用源代码+数据(含有四种结果的预测代码和数据文件)将会开源(以文件的方式供大家下载),有需要的自己去下载使用,希望能给大家启发,对文中有问题的地方,请多多交流,提出您的宝贵意见。)


目录

一、时间序列数据的构造方法及代码

Step1:加载数据,并进行数据预处理

Step2:交替一个时间步

Step3:定义LSTM网络架构并训练

Step4:初始化网络状态

Step5:利用LSTM网络进行单步预测

Step6:将预测结果与实际数据对比

Step7:重置网络状态

二、根据观测值去更新网络状态

Step1:加载数据,并进行数据预处理

Step2:构造交替5秒时间间隔的时间序列作为神经网络的输入和输出。

Step3:神经网络训练

Step4:神经网络预测+精度反映

Step5:神经网络实际应用

三、报错解决方法(神经网络训练时出错)


一、概念介绍

第一种情况:拥有某个地区的降雨量数据,预测该地区之后的降雨量,此时称之为单维

第二种情况:拥有某个地区的降雨量数据,同时拥有该地区的植被量,此时称之为多维

一、时间序列数据的构造方法及代码

方法一:

 首先以下两个表格用来解释一下时间序列神经网络预测的数据结构。


27fe2965c83143fb9c3f03a22878145a.png

472a868fb04c41f781e59664e2b7ae5e.png

二、根据预测值去更新网络状态

本文将根据LSTM有两种预测方式,一种是根据预测值去更新网络状态,另外一种是根据观测值去更新网络状态。重新说明一下,前者适用于单维的时间序列预测,将会累积误差;后者适用于有其他变量去将外界的干扰值反馈给输出预测值的情况,由于有外界变量的介入更新,因此预测将会更准确。这两种方式需要根据实际情况去选择。举个例子,例如需要预测某个变量对股票价格的影响,那么就应该选择后者。

一、根据预测值去更新网络状态

数据集、源码地址:(直接运行就能用)

放在评论区了,放在文字里面没法通过审核。

实现的是:已知前面几年的数据,往后预测2023年的100天的数据。

Step1:加载数据,并进行数据预处理

clc
clear
load dataset.mat %加载数据(double型,剔除了2023年的的第一个数据,总共为2191个。时序预测没有实际时间,只有事情发生的顺序)
data=data(:,2)';  %不转置的话,无法训练lstm网络,显示维度不对。
%% 序列的前2000个用于训练,后191个用于验证神经网络,然后往后预测200个数据。
dataTrain = data(1:2000);    %定义训练集
dataTest = data(2001:2191);    %该数据是用来在最后与预测值进行对比的
%% 数据预处理
mu = mean(dataTrain);    %求均值 
sig = std(dataTrain);      %求均差 
dataTrainStandardized = (dataTrain - mu) / sig;    

 LSTM对于数据标准化要求很高。且这里只对训练集进行标准化的原因是经过神经网络中的值只有训练集,因此无须对测试集进行标准化。

Step2:交替一个时间步

%% 输入的每个时间步,LSTM网络学习预测下一个时间步,这里交错一个时间步效果最好。
XTrain = dataTrainStandardized(1:end-1);  
YTrain = dataTrainStandardized(2:end);  

大多数LSTM相关的资料还是使用默认的a=1来将时间序列拆分为具有输入和输出分量的样本。我在自己的其他试验中使用不同的a来观察(a=1,2,5,10)。这个a的意思是规定了一次性往后预测多少步。设置不同的预测步数a的目的是观察是否能够改善预测模型性能。但据某些验证表明,增加滞后并不能改善预测模型的性能。其实也很好理解,单次预测精度肯定是比预测两步的精度要高的。

a=1即输入[[1],[2],[3],...,[10]]对应着输出[[2],[3],[4],...[11]],a为2的时候即[[1],[2],[3],...,[10]]对应着[[2,3],[3,4],[4,5],...,[10,11]。

这里的交替时间步的作用是什么呢?

  1. 转化序列数据使其呈静态。具体来说,就是使用 a=1差分移除数据中的增长趋势。
  2. 将时间序列问题转化为监督学习问题。具体来说,就是将数据组为输入和输出模式,上一时间步长的观察值可作为输入用于预测当前时间步长的观察值。

Step3:定义LSTM网络架构并训练

%% 一维特征lstm网络训练
numFeatures = 1;   %特征为一维
numResponses = 1;  %输出也是一维
numHiddenUnits = 200;   %创建LSTM回归网络,指定LSTM层的隐含单元个数200。可调
 
layers = [ ...
    sequenceInputLayer(numFeatures)    %输入层
    lstmLayer(numHiddenUnits)  % lstm层,如果是构建多层的LSTM模型,可以修改。
    fullyConnectedLayer(numResponses)    %为全连接层,是输出的维数。
    regressionLayer];      %其计算回归问题的半均方误差模块 。即说明这不是在进行分类问题。
 
%指定训练选项,求解器设置为adam, 1000轮训练。
%梯度阈值设置为 1。指定初始学习率 0.01,在 125 轮训练后通过乘以因子 0.2 来降低学习率。
options = trainingOptions('adam', ...
    'MaxEpochs',1000, ...
    'GradientThreshold',1, ...
    'InitialLearnRate',0.01, ...      
    'LearnRateSchedule','piecewise', ...%每当经过一定数量的时期时,学习率就会乘以一个系数。
    'LearnRateDropPeriod',400, ...      %乘法之间的纪元数由“ LearnRateDropPeriod”控制。可调
    'LearnRateDropFactor',0.15, ...      %乘法因子由参“ LearnRateDropFactor”控制,可调
    'Verbose',0,  ...  %如果将其设置为true,则有关训练进度的信息将被打印到命令窗口中。默认值为true。
    'Plots','training-progress');    %构建曲线图 将'training-progress'替换为none
net = trainNetwork(XTrain,YTrain,layers,options); 

需要提醒的是:在MATLAB中,其已经将LSTM网络进行封装,类似工具箱的形式,因此不再涉及底层的“门”概念。如果利用Python去编程,将会涉及到。

这里有三个参数很重要,第一个是numHiddenUnits,第二是LearnRateDropPeriod,第三个是LearnRateDropFactor,这三个需要根据你的数据集去改。如果RMSE曲线下降太慢,那么就可能是以下原因:(在本代码中,需要将LearnRateDropPeriod改大。)

e6be95adad2b8f51b6f1290b8da18670.png#pic_center

Step4:初始化网络状态

net = predictAndUpdateState(net,XTrain);  %将新的XTrain数据用在网络上进行初始化网络状态
[net,YPred] = predictAndUpdateState(net,YTrain(end));  %用训练的最后一步来进行预测第一个预测值,给定一个初始值。这是用预测值更新网络状态特有的。

训练的最后一个,即预测的第一个。

Step5:利用LSTM网络进行单步预测

%% 进行用于验证神经网络的数据预测(用预测值更新网络状态)
for i = 2:291  %从第二步开始,这里进行191次单步预测(191为用于验证的预测值,100为往后预测的值。一共291个)
    [net,YPred(:,i)] = predictAndUpdateState(net,YPred(:,i-1),'ExecutionEnvironment','cpu');  %predictAndUpdateState函数是一次预测一个值并更新网络状态
end

在其他博客提到的NAR和NARx中,递归预测会一直累积误差,以至于使性能迅速下降,但LSTM对比其他网络独有的是其有这个特殊的函数predictAndUpdateState能够在每次预测时更新网络状态,因此其能够预测更长的序列。

Step6:将预测结果与实际数据对比

%% 验证神经网络
YPred = sig*YPred + mu;      %使用先前计算的参数对预测去标准化。
rmse = sqrt(mean((YPred(1:191)-dataTest).^2)) ;     %计算均方根误差 (RMSE)。
subplot(2,1,1)
plot(dataTrain(1:end))   %先画出前面2000个数据,是训练数据。
hold on
idx = 2001:(2000+191);   %为横坐标
plot(idx,YPred(1:191),'.-')  %显示预测值
hold off
xlabel("Time")
ylabel("Case")
title("Forecast")
legend(["Observed" "Forecast"])
subplot(2,1,2)
plot(data)
xlabel("Time")
ylabel("Case")
title("Dataset")
%% 继续往后预测2023年的数据
figure(2)
idx = 2001:(2000+291);   %为横坐标
plot(idx,YPred(1:291),'.-')  %显示预测值
hold off

Step7:重置网络状态

net = resetState(net);  

如果需要进行其他的预测,如进行下一轮300个的预测。需要重置一下网络状态。

训练结果:49b51505785649bfa2dcbf1a7f01fea0.png

 0eed16b2a34e4237bbf5fad7dab4a9af.jpeg

左图表示的是,从2191个开始,往后预测了100个。右图表示的是利用2001:2191个数据来验证神经网络的训练结果,这里还是比较好的。

但在预测的时候,不能单从训练集和预测集的数量以及比例去评价这个数据集的好坏,从理论上来说,训练集应包含多个周期的变化,不然肯定无法通过此去进行预测。而且100个数据去预测30个数据这并不意味着用1000个数据去预测后面300个是一样的表现,两者是不相同的。

使预测模型更加精确的方法有:增加预测输入的维度,即加入不同的特征(如下文所提到的多个波高仪)。同时需要注意的是,该模型仅适用于单序列预测(即仅有波浪力这个数据,去预测后面的波浪力,因此其实它的预测精度也是不高的。)

二、根据观测值去更新网络状态

在实验中的需求是:利用5个波高仪的波高数据去预测去一段时间间隔(5秒)后的波浪力。因此神经网络训练所需要的是足够数量的波高、波浪力数据样本,每一步将5个波高仪的数据去跟5秒后的波浪力数据进行对应即可。由于实验数据不能泄露,因此大家可参透代码根据自己的实际情况进行使用。

Step1:加载数据,并进行数据预处理

%% 将所有测得的数据切分为7500个数据和对数据进行标准化,消除变量之间的量纲关系,使数据具有可比性。
load testdata.mat %其中第一列为时间,第二列至第六列为波高数据,第七列为波浪力数据。
dt=0.01         %这里的每一步的间隔为0.01秒,可根据实际实验采样频率情况来定。
time_lag=500;   %这里指的是根据波高力数据预测5秒后的波浪力,这个间距可以自己根据实际来调整。
N_total=7500;  %这里指的是7500个时间步
t=testdata(:,1);      %时间,共有7500个。
std_scale=std(testdata(:,2:7));  %对波高数据和波浪力数据求标准差
testdata_norm(:,2:7) =normalize(testdata(:,2:7));  %将数据归一化
lag=500;


Step2:构造交替5秒时间间隔的时间序列作为神经网络的输入和输出。

%% 首先先将时间步分割好
t_input=t(1:end-lag);    %7000=7500-500。1:7000个
t_output=t(lag+1:end);   %501:7500个

%% 分割波高数据和波浪力数据
height_input(1:5,:)=testdata_norm(1:end-lag,2:6)';   %因此这里height_input中的每一行是一个波高仪的数据,一共有5个波高仪,每个波高仪收集了1-7500个时间步的波高信息。然后这里波高作为输入,是只截取第1-7000个时间步的数据作为输入。
height_output(1,:)=testdata_norm(lag+1:end,7)'; %这里指的是第501~7500个时间步的波浪力作为输出。

%% 决定网络的输入、输出、总数
net_input=height_input;    %1:7000,指代的是波高信息
net_output=height_output;    %501:7500,指代的是波浪力信息
sample_size=length(height_output);    %样本总数7000

Step3:神经网络训练

%% 训练神经网络参数设定
numHiddenUnits =5;    %指定LSTM层的隐含单元个数为5
train_ratio= 0.8;     %划分用于神经网络训练的数据比例为总数的80%
LearnRateDropPeriod=50;  %乘法之间的纪元数由“ LearnRateDropPeriod”控制
LearnRateDropFactor=0.5;  %乘法因子由参“ LearnRateDropFactor”控制,


%% 定义训练时的时间步。
numTimeStepsTrain = floor(train_ratio*numel(net_input(1,:)));   %一共为7000个*0.8=5600个

%% 交替一个时间步,可以交替多个时间步,但这里交替一个时间步的效果其实是最好的,详见那篇开头第二篇建立在python之上的文章。(真正想要改变往后预测时间的长短为lag这个变量。)
XTrain = net_input(:, 1: numTimeStepsTrain+1);  %1~5601,XTrain---input,一共为5601个
YTrain = net_output(:, 2: numTimeStepsTrain+2);   %2~5602,YTrain---expected output,为5601个

%% 输入有5个特征,输出有1个特征。
numFeatures =  numel(net_input(:,1));    %5  
numResponses =  numel(net_output(:,1));   %1  

layers = [ ...
    sequenceInputLayer(numFeatures)   %输入层为5
    lstmLayer(numHiddenUnits)         %lstm层,构建5层的LSTM模型,
    fullyConnectedLayer(numResponses) %为全连接层,是输出的维数。
    regressionLayer];         %其计算回归问题的半均方误差模块 。即说明这不是在进行分类问题。

    options = trainingOptions('adam', ... %指定训练选项,求解器设置为adam, 1000轮训练。
        'MaxEpochs',150, ...    %最大训练周期为150
        'GradientThreshold',1, ...   %梯度阈值设置为 1
        'InitialLearnRate',0.01, ...  %指定初始学习率 0.01
        'LearnRateSchedule','piecewise', ...  %每当经过一定数量的时期时,学习率就会乘以一个系数。
        'LearnRateDropPeriod', LearnRateDropPeriod, ...  
        'LearnRateDropFactor',LearnRateDropFactor, ...  %在50轮训练后通过乘以因子 0.5 来降低学习率。
        'Verbose',0, ...   %如果将其设置为true,则有关训练进度的信息将被打印到命令窗口中,0即是不打印 。
        'Plots','training-progress');   %构建曲线图 ,不想构造就将'training-progress'替换为none


net = trainNetwork(XTrain,YTrain,layers,options);    %训练神经网络
save('LSTM_net', 'net');            %将net保存为LSTM_net

end

Step4:神经网络预测+精度反映

%% 定义Xtest和Ytest,其目的是仿照神经网络训练中XTrain和YTrain的样式去构造一个测试集
%% input_Test指的是实验中得到的波高数据。
%% output_Test指的就是参与跟预测值进行对比的测量值。即expected output
numTimeStepsTrain2 = floor(0.1*sample_size);    %一共为7000个*0.1=700个。可以自己再随意取一个比例,去尝试一下。当然也可以跟上面的numTimeStepsTrain保持一致。
input_Test = net_input(:, numTimeStepsTrain2+1: end-1);   %7000个中取701个~6999,一共是6299个
output_Test = net_output(:, numTimeStepsTrain2+2: end);   %7000个取702:7000,为6299个,因为 numTimeStepsTrain是700.


%% 这里首先用input_Train来初始化神经网络。经过测试,不是将整个input_Train放进去最好,放其中一点比例即可。
input_Train = net_input(:, floor(numTimeStepsTrain*0.9): numTimeStepsTrain);   %630~700
net = predictAndUpdateState(net, input_Train); 


%% 预测量预定义
rec_step=numel(output_Test);    %滚动预测6299个。跟后面的j循环有关。
YPred=zeros(rec_step,1);  %6299个预测,1个特征。这个预测是和Test来比对,看是否正确的。


%% 神经网络预测(这个也是之后实际预测需要用到的)
    for j=1: rec_step
        [net,YPred0] = predictAndUpdateState(net, input_Test(:, j));    %用input_Test代入刚刚用input_Train来更新的网络得到第一个输出并得到对应的预测值。
        YPred(j, :) = YPred0;    %记录输出的预测值。
    end


%% 神经网络精度反映。这里的Y都是波浪力
YTest = output_Test(:,1:rec_step)';     %这一步可以简化。其实只是一个转置。
NRMSE=sqrt(sum((YPred-YTest).^2)/rec_step/(max(YTest)-min(YTest)));

这里需要重点说明的是用input_Train来初始化神经网络的问题。 因为在LSTM中,state cell是实时在更新的,但对于整个网络需要给它一个初始化的值,但不一定是利用整个input_Train的效果最好,因此在实际使用中input_Train可以考虑给予一个比例,来让其初始化。

Step5:神经网络实际应用

rec_step=2;    %这里只预测两步
input_Test=[1;2;3;4;5,6;4;8;9;7];   %假设第一个时间步波高数据分别为1,2,3,4,5,第二个时间步波高数据分别为6;4;8;9;7。

%% 神经网络预测
    for j=1: rec_step
        [net,YPred0] = predictAndUpdateState(net, input_Test(:, j));    %用input_Test代入刚刚用input_Train来更新的网络得到第一个输出并得到对应的预测值。
        YPred(j, :) = YPred0;    %记录输出2个的预测值。
    end

假设已经同时得到了5个波高仪数据,需要预测5秒(在之前的神经网络训练中已经预设好了)后的波浪力。每次得到的波高数据应该是放在for循环里面,去不断的进行单步预测。

06d3dcdf6f884c45b273f54eafde8dbf.png

 左边是数据集,右边是预测,我在预测中的输入是2秒前的波高,预测了2秒后的波浪力。这个相当于多次的单步预测,还是相当的准确的。

三、报错解决方法(神经网络训练时出错)

报错内容:

错误使用 trainNetwork (第 184 行)
Layer 2: Invalid initializer. BLAS 加载错误:

出错 untitled5 (第 71 行)
net = trainNetwork(p_train, t_train, layers, options);

原因:
    错误使用 nnet.internal.cnn.assembler.InitializeMixin/initializeLearnableParameters (第 21 行)
    Layer 2: Invalid initializer. BLAS 加载错误:

解决方法:

这类问题主要是由于之前安装了别的版本(特制MATLAB7),然后环境变量没有更改造成的。可以先在命令行窗口输入getenv('BLAS_VERSION'),查看BLAS存储的位置如果跟当前MATLAB不同,那么调用的时候就会出错,这里需要右击我的电脑,高级设置,环境变量,将BLAS相关的这一个环境变量直接删除,重新打开MATLAB即可。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐