我们今天学习一个长短期记忆网络,就是LSTM,它是循环神经网络的一种。在使用深度学习处理时序问题时,RNN是最常使用的模型之一。RNN之所以在时序数据上有着优异的表现是因为RNN在 t 时间片时会将 t−1 时间片的隐节点作为当前时间片的输入。这样有效的原因是之前时间片的信息也用于计算当前时间片的内容,而传统模型的隐节点的输出只取决于当前时间片的输入特征。LSTM的全称是Long Short Term Memory,顾名思义,它具有记忆长短期信息的能力的神经网络。LSTM首先在1997年由Hochreiter & Schmidhuber 提出,由于深度学习在2012年的兴起,LSTM又经过了若干代大牛的发展,由此便形成了比较系统且完整的LSTM框架,并且在很多领域得到了广泛的应用。本文着重介绍深度学习时代的LSTM。
目录
二、MATLAB深度学习之LSTM时间序列预测(单输入->单输出)
三、MATLAB深度学习之LSTM时间序列预测(多输入->单输出)
四、MATLAB深度学习之LSTM时间序列预测(多输入->多输出)
长短时记忆网络(Long Short Term Memory Network, LSTM),是一种改进之后的循环神经网络,可以解决RNN无法处理长距离的依赖的问题,目前比较流行。
长短期记忆网络的设计思路如下:
原始 RNN 的隐藏层只有一个状态,即h,它对于短期的输入非常敏感。再增加一个状态,即c,让它来保存长期的状态,称为单元状态(cell state)。
上面的是一个粗略的表示形式,下面我们剖析LSTM的结构图,看一下基本结构:
在 t 时刻,LSTM 的输入有三个:当前时刻网络的输入值 Xt、上一时刻 LSTM 的输出值 ht-1、以及上一时刻的单元状态 Ct-1;
LSTM 的输出有两个:当前时刻 LSTM 输出值 ht、和当前时刻的单元状态 Ct.
相比RNN,LSTM加入了一个长期单元C,那么怎样控制长期单元C呢,一般使用三个控制开关进行控制;具体如下:第一个控制开关控制继续保留C的状态,第二个控制把当前状态输入到长期状态C,第三个是控制把长期状态C作为当前LSTM的输出。
上面我们知道使用控制开关可以实现控制长期状态C,那么怎么在算法中实现上述三个开关呢,是通过门的方式实现的。定义:门(gate) 实际上就是一层全连接层,输入是一个向量,输出是一个 0到1 之间的实数向量。公式为:
方法:用门的输出向量按元素乘以我们需要控制的那个向量
原理:门的输出是 0到1 之间的实数向量
当门输出为 0 时,任何向量与之相乘都会得到 0 向量,这就相当于什么都不能通过;
输出为 1 时,任何向量与之相乘都不会有任何改变,这就相当于什么都可以通过。
1)向前计算公式
遗忘门(forget gate)
它决定了上一时刻的单元状态 Ct-1 有多少保留到当前时刻 Ct
输入门(input gate)
它决定了当前时刻网络的输入 Xt 有多少保存到单元状态 Ct
输出门(output gate)
控制单元状态 Ct 有多少输出到 LSTM 的当前输出值 ht
其中遗忘门的计算如下所示,遗忘门的计算公式中:
Wf 是遗忘门的权重矩阵,[ht-1, Xt] 表示把两个向量连接成一个更长的向量,bf是遗忘门的偏置项,σ 是 sigmoid 函数。
下面看输入门的计算,首先第一块的输入门是决定当前网络的输入xt有多少保存到记忆单元ct,可以避免无关紧要的内容进入记忆单元。
根据上一次的输出ht-1和本次的输入xt计算当前输入的状态,具体如下所示:
当前时刻的单元状态 Ct 的计算:由上一次的单元状态 Ct-1 按元素乘以遗忘门 ft,再用当前输入的单元状态 Ct 按元素乘以输入门 it,再将两个积加和:
这样,就可以把当前记忆 Ct 和长期记忆 Ct-1 组合在一起,形成了新的单元状态 Ct。
由于遗忘门的控制,它可以保存很久很久之前的信息,由于输入门的控制,它又可以避免当前无关紧要的内容进入记忆。
我们最后看一下输出门的计算,就是控制单元状态ct有多少记忆输出到LSTM的当前输出ht。
总结一下,向前计算公式一共包含如下,i、f、c、o分别为输入门、遗忘门、细胞状态,输出门,W 和 b 分别为对应的权重系数矩阵和偏置项;σ 和 tanh 分别为 sigmoid 和双曲正切激活函数。
2)反向传播训练算法
LSTM 模型训练过程采用的是与经典的反向传播(Back Propagation,BP) 算法原理类似的
BPTT 算法。大致可以分为 4 个步骤:①按照前向计算方法计算 LSTM 细胞的输出值;②反向计算每个 LSTM 细胞的误差项,包括按时间和网络层级 2 个反向传播方向;③根据相应的误差项,计算每个权重的梯度;④应用基于梯度的优化算法更新权重。
①按照前向计算方法计算 LSTM 细胞的输出值,就是上面的5个公式,即正向计算的过程,主要有5个部分需要计算。
②反向计算每个 LSTM 细胞的误差项,包括按时间和网络层级 2 个反向传播方向; 反向计算每个神经元的误差项值。与 RNN 一样,LSTM 误差项的反向传播也是包括两个方向: 一个是沿时间的反向传播,即从当前 t 时刻开始,计算每个时刻的误差项;一个是将误差项向上一层传播。
③根据相应的误差项,计算每个权重的梯度;gate 的激活函数定义为 sigmoid 函数,输出的激活函数为 tanh 函数。权重矩阵 W 都是由两个矩阵拼接而成,这两部分在反向传播中使用不同的公式,因此在后续的推导中,权重矩阵也要被写为分开的两个矩阵。
我们继续看一下具体的两个方向误差的计算,以及权重梯度的计算。
首先定义t时刻的误差项,目的是要计算出 t-1 时刻的误差项,如下所示:
利用 ht Ct 的定义和全导数公式,可以得到将误差项向前传递到任意k时刻的公式:
另外可以得到将误差项传递到上一层的公式,如下:
对于各个权重梯度的计算,具体的公式如下:
④应用基于梯度的优化算法更新权重。基于梯度的优化算法种类众多,用的比较多的就是随机梯
度下降 ( Stochastic Gradient Descent,SGD)。
此示例说明如何使用长短期记忆(LSTM)网络预测时间序列数据。为了预测序列的未来时间步长的值,可以训练一个序列到序列回归LSTM网络,其中的响应是值移动了一个时间步长的训练序列。也就是说,在输入序列的每个时间步长,LSTM网络学习预测下一个时间步长的值。要预测未来多个时间点的值,请使用forectAndUpdateState函数一次预测一个时间点,并在每次预测时更新网络状态。
使用的数据集是股票数据,数据是out.txt文件,一共436组数据,数据共享在阿里云盘:阿里云盘分享
1、加载数据
加载示例数据。某股票的收盘价,包含单个时间序列,时间步长为每日,值对应于每日收盘价。输出是一个单元数组,其中每个元素都是单个时间步长。将数据重塑为行矢量。
2、 数据拆分
划分训练和测试数据。在序列的前90%进行训练,在最后10%进行测试。
3、数据归一化
为了更好地匹配和防止训练发散,将训练数据标准化为零均值和单位方差。在预测时,您必须使用与训练数据相同的参数来标准化测试数据。
注意:要预测序列的未来时间步长的值,请将响应指定为值移位一个时间步长的训练序列。也就是说,在输入序列的每个时间步长,LSTM网络学习预测下一个时间步长的值。
4、 创建LSTM回归网络。将LSTM层指定为具有128个隐藏单元。训练参数可查看帮助,明白其中含义。
5、训练网络
使用TrainNetwork训练LSTM网络。
6、预测(使用预测值更新网络状态)
要预测未来多个时间点的值,请使用forectAndUpdateState函数一次预测一个时间点,并在每次预测时更新网络状态。对于每个预测,使用先前的预测作为函数的输入。使用与训练数据相同的参数对测试数据进行标准化。
为了初始化网络状态,首先对训练数据XTrain进行预测。接下来,使用训练响应YTrain的最后一个时间步长进行第一个预测(完)。循环其余的预测,并将先前的预测输入到forectAndUpdateState。
对于大型数据集合、长序列或大型网络,在GPU上计算预测通常比在CPU上计算预测快。否则,在CPU上进行预测的计算速度通常会更快。对于单时间步长预测,请使用CPU。要使用CPU进行预测,请将recrectAndUpdateState的‘ExecutionEnvironment’选项设置为‘CPU’
使用先前计算的参数来取消预测的标准化,即反归一化。 用预测值绘制训练时间序列。
7、预测(使用测试数据更新网络状态)
如果您可以访问预测之间时间步长的实际值,则可以使用观测值而不是预测值来更新网络状态。首先,初始化网络状态。要对新序列进行预测,请使用Reset State重置网络状态。重置网络状态可防止先前的预测影响对新数据的预测。重置网络状态,然后通过对训练数据进行预测来初始化网络状态。
实现上述单输入,单输出的时间序列预测的matlab代码如下:
- clear;
- clc
- %% 加载数据,某股票的收盘价,包含单个时间序列,时间步长为每日,值对应于每日收盘价。
- data = importdata('out.txt');
- data = data';
- figure()
- plot(data);
- %% 划分训练集和测试集,90%训练,10%测试
- numTimeStepsTrain = floor(0.9*numel(data));
- dataTrain = data(1:numTimeStepsTrain+1);
- dataTest = data(numTimeStepsTrain+1:end);
- %% 数据的归一化处理,数据标准化为0均值,单位方差
- mu = mean(dataTrain);
- sig = std(dataTrain);
- dataTrainStandardized = (dataTrain - mu) / sig;
- %% 定义预测序列的未来时间步长的值,请将响应指定为值移位一个时间步长的训练序列
- XTrain = dataTrainStandardized(1:end-1);
- YTrain = dataTrainStandardized(2:end);
- %% 定义LSTM网络结构,将LSTM层指定为具有128个隐藏单元
- % 定义网络结构
- layers = [
- sequenceInputLayer(1,'Name','input')
- lstmLayer(128,'Name','lstm')
- fullyConnectedLayer(1,'Name','fc')
- regressionLayer];
- % 定义训练参数
- options = trainingOptions('adam', ...
- 'MaxEpochs',250, ...
- 'GradientThreshold',1, ...
- 'InitialLearnRate',0.005, ...
- 'LearnRateSchedule','piecewise', ...
- 'LearnRateDropPeriod',125, ...
- 'LearnRateDropFactor',0.2, ...
- 'Verbose',0, ...
- 'Plots','training-progress');
- % 训练网络
- net = trainNetwork(XTrain,YTrain,layers,options);
- %% 预测,使用预测值更新网络状态
- % 测试集归一化
- dataTestStandardized = (dataTest - mu) / sig;
- XTest = dataTestStandardized(1:end-1);
- % 预测
- net = predictAndUpdateState(net,XTrain);
- [net,YPred] = predictAndUpdateState(net,YTrain(end));
-
- numTimeStepsTest = numel(XTest);
- for i = 2:numTimeStepsTest
- [net,YPred(:,i)] = predictAndUpdateState(net,YPred(:,i-1),'ExecutionEnvironment','cpu');
- end
- YPred = sig*YPred + mu; %反归一化
- %% 绘图
- figure
- plot(dataTrain(1:end-1))
- hold on
- idx = numTimeStepsTrain:(numTimeStepsTrain+numTimeStepsTest);
- plot(idx,[data(numTimeStepsTrain) YPred],'k.-'),hold on
- plot(idx,data(numTimeStepsTrain:end-1),'r'),hold on
- hold off
- xlabel('Day')
- ylabel('P')
- title('Forecast')
- legend({'Observed', 'Forecast'})
- %% 预测,使用测试数据更新网络状态
- net = resetState(net);
- net = predictAndUpdateState(net,XTrain);
- YPred = [];
- numTimeStepsTest = numel(XTest);
- for i = 1:numTimeStepsTest
- [net,YPred(:,i)] = predictAndUpdateState(net,XTest(:,i),'ExecutionEnvironment','cpu');
- end
-
- YPred = sig*YPred + mu;
-
- %% 绘图
- figure
- plot(dataTrain(1:end-1))
- hold on
- idx = numTimeStepsTrain:(numTimeStepsTrain+numTimeStepsTest);
- plot(idx,[data(numTimeStepsTrain) YPred],'k.-'),hold on
- plot(idx,data(numTimeStepsTrain:end-1),'r'),hold on
- hold off
- xlabel('Day')
- ylabel('P')
- title('Forecast')
- legend({'Observed', 'Forecast'})
原始数据的时间序列图如下:
训练的过程如下:
使用预测数据更新网络进行预测的效果如下:
使用测试数据更新网络的预测效果如下:
我们使用matlab模拟输入和输出数据,进行预测,输入三组数据,输出一组数据,即多输入和单输出的数据集。
- clear,clc
- t = (0.01:0.01:20)';
- a = (t.^2 - 10 * t);
- b = cos(5*pi*t).*exp(-5*pi*0.001*t);
- c = cos(7*pi*t).*exp(-7*pi*0.001*t);
- % y = a*0.001 + b + c; % 线性
- % y = a*0.001 + b.^2 + c; % 二次非线性
- % y = a.*b + c; % 非线性
- y = exp(a*0.01).*c.*b; % 高度非线性
- subplot(4,1,1)
- plot(t,a)
- ylabel('a')
- subplot(4,1,2)
- plot(t,b)
- ylabel('b')
- subplot(4,1,3)
- plot(t,c)
- ylabel('c')
- subplot(4,1,4)
- plot(t,y)
- ylabel('y')
如下a,b,c模拟三组输入信号,y模拟一组输出信号,我们根据三组输入数据完成相应的预测。
下面使用lstm模型进行划分数据集并进行预测,matlab代码如下:
- clear,clc
- %% 模拟数据
- t = (0.01:0.01:20)';
- a = (t.^2 - 10 * t);
- b = cos(5*pi*t).*exp(-5*pi*0.001*t);
- c = cos(7*pi*t).*exp(-7*pi*0.001*t);
- % y = a*0.001 + b + c; % 线性
- % y = a*0.001 + b.^2 + c; % 二次非线性
- % y = a.*b + c; % 非线性
- y = exp(a*0.01).*c.*b; % 高度非线性
- subplot(4,1,1)
- plot(t,a)
- ylabel('a')
- subplot(4,1,2)
- plot(t,b)
- ylabel('b')
- subplot(4,1,3)
- plot(t,c)
- ylabel('c')
- subplot(4,1,4)
- plot(t,y)
- ylabel('y')
-
- %% 数据集分割与标准化
- % 定义测试与训练长度
- data = [a,b,c,y];
- numTimeStepsTrain = floor(0.9*numel(y));
- dataTrain = data(1:numTimeStepsTrain+1,:);
- dataTest = data(numTimeStepsTrain+1:end,:);
- % 归一化
- mu = mean(dataTrain);
- sig = std(dataTrain);
- dataTrainStandardized = (dataTrain - ones(length(dataTrain(:,1)),1)*mu) ./ (ones(length(dataTrain(:,1)),1)*sig);
- % 生成训练集输入与输出
- XTrain = dataTrainStandardized(:,1:3)';
- YTrain = dataTrainStandardized(:,4)';
-
- %% 建立网络层,配置训练参数
- layers = [
- sequenceInputLayer(3,"Name","input")
- lstmLayer(200,"Name","lstm")
- dropoutLayer(0.1,"Name","drop")
- fullyConnectedLayer(1,"Name","fc")
- regressionLayer("Name","regressionoutput")];
- options = trainingOptions('adam', ...
- 'MaxEpochs',250, ...
- 'GradientThreshold',1, ...
- 'InitialLearnRate',0.005, ...
- 'LearnRateSchedule','piecewise', ...
- 'LearnRateDropPeriod',125, ...
- 'LearnRateDropFactor',0.2, ...
- 'Verbose',0, ...
- 'Plots','training-progress');
- %% 测试数据归一化
- % 测试集归一化
- dataTestStandardized = (dataTest - ones(length(dataTest(:,1)),1)*mu) ./ (ones(length(dataTest(:,1)),1)*sig);
- % XTest = dataTestStandardized(1:end-1);
- XTest = dataTestStandardized(:,1:3)';
-
- %% 多步预测
- net = predictAndUpdateState(net,XTrain);
- numTimeStepsTest = numel(XTest(1,:));
- YPred = [];
- for i = 1:numTimeStepsTest
- [net,YPred(i)] = predictAndUpdateState(net,XTest(:,i),'ExecutionEnvironment','cpu');
- end
- YPred = sig(4)*YPred + mu(4);
-
- %% 可视化
- % 绘图
- idx = (numTimeStepsTrain+1):(numTimeStepsTrain+numTimeStepsTest);
- figure
- set(gcf,'position',[1,1,1500,1000])
- set(gca,'position',[0.1,0.1,0.8,4])
- subplot(2,1,1)
- plot(data(1:end,4))
- hold on
- plot(idx, YPred,'-.k',LineWidth=1.5)
- hold off
- xlabel("t")
- ylabel("y")
- % xlim([1800,2000])
- subplot(2,1,2)
- plot(data(1:end,4))
- hold on
- plot(idx, YPred,'-.^k')
- hold off
- xlabel("t")
- ylabel("y")
- xlim([1800,2000])
- title("Forecast")
- legend(["Observed" "Forecast"])
预测结果的可视化如下:
使用matlab模拟出三组输入和三组输出作为预测的数据集。
- %% 模拟数据
- clear,clc,close all
- t = (0.01:0.01:20)';
- a = (t.^2 - 10 * t);
- b = cos(5*pi*t).*exp(-5*pi*0.001*t);
- c = cos(7*pi*t).*exp(-7*pi*0.001*t);
- x = a*0.001 + b + c; % 线性
- % x = a*0.001 + b.^2 + c; % 二次非线性
- % x = a.*b + c; % 非线性
- % x = exp(a*0.01).*c.*b; % 高度非线性
- y = b .* sin(x);
- z = (a + b + c) .* cos(x + y);
- figure
- subplot(3,1,1)
- plot(t,a)
- ylabel('a')
- subplot(3,1,2)
- plot(t,b)
- ylabel('b')
- subplot(3,1,3)
- plot(t,c)
- ylabel('c')
- figure
- subplot(3,1,1)
- plot(t,x)
- ylabel('x')
- subplot(3,1,2)
- plot(t,y)
- ylabel('y')
- subplot(3,1,3)
- plot(t,z)
- ylabel('z')
-
- %% 数据的预处理
- % 定义测试与训练长度
- data = [a,b,c,x,y,z];
- numTimeStepsTrain = floor(0.9*numel(y));
- dataTrain = data(1:numTimeStepsTrain+1,:);
- dataTest = data(numTimeStepsTrain+1:end,:);
- % 归一化
- mu = mean(dataTrain);
- sig = std(dataTrain);
- dataTrainStandardized = (dataTrain - ones(length(dataTrain(:,1)),1)*mu) ./ (ones(length(dataTrain(:,1)),1)*sig);
- % 生成训练集输入与输出
- XTrain = dataTrainStandardized(:,1:3)';
- YTrain = dataTrainStandardized(:,4:6)';
-
- %% 建立网络结构,训练网络
- layers = [
- sequenceInputLayer(3,"Name","input")
- lstmLayer(200,"Name","lstm")
- dropoutLayer(0.1,"Name","drop")
- lstmLayer(200,"Name","lstm")
- dropoutLayer(0.1,"Name","drop")
- fullyConnectedLayer(50,"Name","fc")
- fullyConnectedLayer(3,"Name","fc")
- regressionLayer("Name","regressionoutput")];
- options = trainingOptions('adam', ...
- 'MaxEpochs',250, ...
- 'GradientThreshold',1, ...
- 'InitialLearnRate',0.005, ...
- 'LearnRateSchedule','piecewise', ...
- 'LearnRateDropPeriod',125, ...
- 'LearnRateDropFactor',0.2, ...
- 'Verbose',0, ...
- 'MiniBatchSize',3, ...
- 'Plots','training-progress');
- net = trainNetwork(XTrain,YTrain,layers,options);
-
- %% 预测
- % 测试集归一化
- dataTestStandardized = (dataTest - ones(length(dataTest(:,1)),1)*mu) ./ (ones(length(dataTest(:,1)),1)*sig);
- % XTest = dataTestStandardized(1:end-1);
- XTest = dataTestStandardized(:,1:3)';
- % 数据存放内存
- adsXTrain = arrayDatastore(XTrain,'OutputType','cell','IterationDimension',3);
- adsYTrain = arrayDatastore(YTrain,'OutputType','cell','IterationDimension',3);
- cdsTrain = combine(adsXTrain,adsYTrain);
- net = trainedNetwork_1;
- % net = predictAndUpdateState(net,XTrain);
- numTimeStepsTest = numel(XTest(1,:));
- YPred = [];
- for i = 1:numTimeStepsTest
- [net,YPred(:,i)] = predictAndUpdateState(net,XTest(:,i),'ExecutionEnvironment','cpu');
- end
- YPred = diag(sig(4:6))*YPred + ones(length(YPred(:,1)),1).*mu(4:6)';
-
- %% 可视化
- idx = (numTimeStepsTrain+1):(numTimeStepsTrain+numTimeStepsTest);
- label = ['x','y','z'];
- for num=1:3
- figure
- set(gcf,'position',[1,1,1500,1000])
- set(gca,'position',[0.1,0.1,0.8,4])
- subplot(2,1,1)
- plot(data(1:end,num+3))
- hold on
- plot(idx, YPred(num,:),'-.k',LineWidth=1.5)
- hold off
- xlabel("t")
- ylabel(label(num))
- % xlim([1800,2000])
- subplot(2,1,2)
- plot(data(1:end,num+3))
- hold on
- plot(idx, YPred(num,:),'-.^k')
- hold off
- xlabel("t")
- ylabel(label(num))
- xlim([1800,2000])
- title("Forecast")
- legend(["Observed" "Forecast"])
- end
预测结果如下: