本篇博客对《Deep Learning》
第十章序列建模:循环与递归神经网络
的内容进行总结,以便加深理解和记忆。由于翻译问题和笔者学习程度尚浅,许多未完全理解的问题等以后再进行补充和纠正。
1)概念
循环神经网络RNN:是一类用于处理序列数据( x 1 , x 2 , . . . , x r x^1,x^2,...,x^r x1,x2,...,xr)的神经网络
2)动机/特点
[1]从MLP发展到RNN的动机:参数共享,参数共享使模型能够扩展到不同形式的样本并进行泛化,当信息的特定部分会在序列内多个位置出现时,这样的共享尤为重要(如若在每个时间点都有一个单独的参数,我们则无法泛化到训练时没有见到的序列长度,也不能在时间上共享不同序列长度和不同位置的统计强度)
[2]减少参数数目的代价是优化参数可能更加困难,因为参数共享依赖于相同参数可用于不同时间步的假设:即假设给定时刻t的变量后,时刻t+1变量条件分布是平稳的。原则上可以使用t作为每个时间步的额外输入,并让学习器在发现任何时间依赖性的同时,不同时间步间尽可能多地共享
3)时延神经网络与循环神经网络
[1]时延神经网络:使用一维时间序列的卷积,参数共享的方式是使用相同的卷积核
[2]循环神经网络:输出的每一项是输出前一项的函数(前一项的输出作为后一项的一个输入)参数共享的方式是:后一项的输出和前一项的输出使用相同的更新规则(函数)
这种循环导致参数通过很深的计算图进行共享,周期代表变量自身的值在未来某一时间步对自身的影响
4)计算图
在多层前馈神经网络中,对计算图进行了介绍,计算图实际是形式化一组计算结构的方式。
一般可用循环图和展开图对RNN隐藏层的计算进行描述
[1]循环计算图
对应的函数表达: h t = f ( h t − 1 , x t ; θ ) h^t = f(h^{t-1},x^t;θ) ht=f(ht−1,xt;θ)
对应的循环计算图:
[2]展开计算图
将循环结构线性展开,得到的重复结构称为事件链,展开循环图导致深度网络结构中的参数共享
对应的函数表达展开式: h t = f ( h t − 1 , x t ; θ ) = g t ( x t , x t − 1 , . . . , x 1 ) h^t = f(h^{t-1},x^t;θ) = g^t(x^t,x^{t-1},...,x^1) ht=f(ht−1,xt;θ)=gt(xt,xt−1,...,x1), g t g^t gt表示经t步展开的循环。
该状态包含了整个过去序列的信息,但有时我们根据不同的训练准则,只选择性地保留过去序列的部分影响(如 { y t − k , . . . , y t − 1 } → y t ) \{y^{t-k},...,y^{t-1}\}→y^t) {yt−k,...,yt−1}→yt)
对应的展开计算图:
[3]循环图与展开图的优点
①循环图:更加简洁
②展开图:能够描述计算流程,可以通过显式的信息流动帮助计算向前传播和向后传播。
同时也展现出RNN的两个特点:模型拥有相同的输入大小;在一个序列中的每个时间步使用相同参数的相同函数W,U,V
5)RNN的几种设计模式
U为输入样本X的权重矩阵,W为上一时间步单元隐藏层的输出或输出层的输出对应的权重矩阵,b为隐藏层的阈值,σ为隐藏层的激活函数,V为输出层的权重矩阵,c为输出层的阈值,输出层的激活函数是softmax函数(获取输出概率)
我们通常希望将RNN的输出解释为一个概率分布,且通常使用真实输出y与预测输出O之间的交叉熵作为损失函数
[1]每个时间步都有输出,且隐藏层之间有循环连接
前向传播:
h t = σ ( U x t + W h t − 1 + b ) o t = V h t + c y t = s o f t m a x ( o t ) L ( { x 1 , x 2 , . . . , x t } , { y 1 , y 2 , . . . , y t } ) = ∑ t L t = − ∑ t l o g p m o d e l ( y t ∣ x 1 , x 2 , . . . , x t ) h^t = σ(Ux^t + Wh^{t-1} + b) \\ o^t = Vh^t + c \\ y^t = softmax(o^t) \\ L(\{x^1,x^2,...,x^t\},\{y^1,y^2,...,y^t\}) = \sum_t L^t =- \sum_t log_{p_{model}}(y^t|{x^1,x^2,...,x^t}) ht=σ(Uxt+Wht−1+b)ot=Vht+cyt=softmax(ot)L({x1,x2,...,xt},{y1,y2,...,yt})=t∑Lt=−t∑logpmodel(yt∣x1,x2,...,xt)
[2]每个时间步都有输出,当前时刻的输出与下一时刻的隐藏单元之间有循环连接
前向传播:
h t = σ ( U x t + W o t − 1 + b ) o t = V h t + c y t = s o f t m a x ( o t ) L ( { x 1 , x 2 , . . . , x t } , { y 1 , y 2 , . . . , y t } ) = ∑ t L t = − ∑ t l o g p m o d e l ( y t ∣ x 1 , x 2 , . . . , x t ) h^t = σ(Ux^t + Wo^{t-1} + b) \\ o^t = Vh^t + c \\ y^t = softmax(o^t) \\ L(\{x^1,x^2,...,x^t\},\{y^1,y^2,...,y^t\}) = \sum_t L^t =- \sum_t log_{p_{model}}(y^t|{x^1,x^2,...,x^t}) ht=σ(Uxt+Wot−1+b)ot=Vht+cyt=softmax(ot)L({x1,x2,...,xt},{y1,y2,...,yt})=t∑Lt=−t∑logpmodel(yt∣x1,x2,...,xt)
该模式没有从h前向传播的直接连接,o通常缺乏过去的信息
[3]隐藏单元之间存在循环连接,待读取整个序列后产生单个输出的循环网络
前向传播:
h t = σ ( U x t + W h t − 1 + b ) o t = V h t + c y t = s o f t m a x ( o t ) L ( { x 1 , x 2 , . . . , x t } , y t ) = L t = − l o g p m o d e l ( y t ∣ x 1 , x 2 , . . . , x t ) h^t = σ(Ux^t + Wh^{t-1} + b) \\ o^t = Vh^t + c \\ y^t = softmax(o^t) \\ L(\{x^1,x^2,...,x^t\},y^t) = L^t =-log_{p_{model}}(y^t|{x^1,x^2,...,x^t}) ht=σ(Uxt+Wht−1+b)ot=Vht+cyt=softmax(ot)L({x1,x2,...,xt},yt)=Lt=−logpmodel(yt∣x1,x2,...,xt)
[4]Teacher Forcing
Teacher Forcing用以解决上述三种(前后时间步隐藏层/输出层之间循环连接)导致的计算效率低下的问题,由于后一个时间步的输入依赖于前一个时间步的输出,因而只能线性地进行前向和反向计算,无法进行并行计算。
Teacher Forcing则是用每个时间步的真实输出代替该时间步的预测输出作为下一个时间步的输入,这样则可以解决前后时间步计算时的依赖问题,就可以进行并行计算。
前向传播:
h t = σ ( U x t + W y t − 1 + b ) h^t = σ(Ux^t + Wy^{t-1} + b) \\ ht=σ(Uxt+Wyt−1+b)
问题①:该网络缺少隐藏层之间的循环连接,导致它无法通过之前的隐藏单元来获取过去的信息。而每个时间步的真实值又不太能携带关于过去历史的必要信息,除非用户知道每个时间步的真实输出如何描述系统的全貌,并将它作为训练目标的一部分
问题②:该网络在训练时,历史信息通过前一个时间步的真实输出获取,在测试和应用时,历史信息则通过前一个时间步的输出值O来获取。这将造成训练期间该网络看到的输入与测试期间该网络看到的输入有很大的不同。一种缓解方法是同时使用Teacher Forcing和自由运行的输入进行训练
[5]输出序列的每个元素 y t y^t yt同时作用于当前时间步的输入和训练期间的目标(前一个时间步)
可用于图注等多种任务
[6]可变长度的序列映射到相同长度的输出序列
前一个时间步的真实输出也作为当前时间步隐藏层的一个输入
6)通过时间反向传播BPTT
通过MLP的BP算法可以计算展开图的参数梯度,不需要特殊化的算法
7)如何确定序列长度
[1]设置序列终结符:如在词汇表输出中增加一个对应序列末尾的特殊符号
[2]在模型中每一个时间步引入一个额外的二项输出,表示在每个时间步决定继续或停止
[3]在模型中增加一个额外的输出r表示序列长度
8)作为有向图模型的循环网络(表示y的联合分布)、基于上下文的RNN序列建模(上下文即输入序列X,表示给定x,y的条件分布)
1)动机
单向RNN,在一个序列中的每个时间步上考虑了当前时间步以及过去历史对现今的影响。但在许多应用中预测输出可能依赖于整个输入序列,如语音识别:由于协同发音,当前声音作为音素的正确解释不仅取决于前一时间步的发音还取决于未来时间步的发音。
2)概念
双向RNN结合了一个序列从起点开始向着终点方向移动与从终点开始向着起点方向移动的两个方向,这能够使某一时间步的输出同时依赖于过去和未来的表示,而不必指定固定的大小窗口
3)扩展
该思想可以扩展到2维输入,如图像,由4个子RNN组成,沿着:上、下、左、右四个方向。如果RNN能够学习到承载长期依赖的信息,那么在2维网格的每个点的输出就能捕获到大多局部信息并依赖于长期输入的表示,对于这种RNN前向传播可以写成卷积的形式,自底向上的计算每一层的输入。相对于一般CNN,应用于图像的RNN通常代价更高。
1)动机
设输入序列长度为 n x n^x nx,输出序列长度为 n y n^y ny,先前给出的RNN网络总是有 n x = n y n^x=n^y nx=ny或 n x → n y = 1 n^x → n^y = 1 nx→ny=1的限制
基于编码-解码的序列到序列架构解决输入序列长度与输出序列长度不等的问题,如语音识别、机器翻译、问答等问题的输入输出序列通常不等
2)概念
[1]编码器/读取器(如向量到序列的RNN):输入RNN的输入序列X,输出上下文C(一般为最终隐藏状态的简单函数, n y = 1 n^y = 1 ny=1)
[2]解码器/写入器:输入上下文C,输出固定长度的输出序列Y
3)问题
由于上下文C是一个向量 n C = 1 n^C = 1 nC=1的维度太小,它难以适当地概括一个长序列。一种解决方法是让C成为一个可变长度的序列,而不是一个固定大小的向量,另外还引入了上下文C元素与输出序列元素相关联的注意机制(以后再总结)
介绍:递归神经网络是RNN的一个扩展,它被构造成深的树状结构而非链式结构,在“学习推论”上有很大的用途,在NLP、CV领域有很好的应用
特点/优势:是对于相同长度r的序列,通过树状结构的网络可以将计算复杂度从O®减小到O(log r),这有助于解决长期依赖问题
如何选择树结构:①选择不依赖于数据的树结构,如平衡二叉树;②外部方法为选择树结构提供借鉴:如使用句子语法分析树
1)长期依赖
RNN在每个时间步上使用同一矩阵W,因此会产生很深的计算图,长期依赖于同一W会导致梯度消失或梯度爆炸
如简单考虑计算图中存在一条反复与矩阵W相乘的路径,将W特征分解为: W = V d i a g ( λ ) V − 1 W=Vdiag(λ)V^{-1} W=Vdiag(λ)V−1,第k步后的矩阵为: W t = ( V d i a g ( λ ) V − 1 ) t = V d i a g ( λ ) t V − 1 W^t = (Vdiag(λ)V^{-1})^t=Vdiag(λ)^tV^{-1} Wt=(Vdiag(λ)V−1)t=Vdiag(λ)tV−1,当特征值λ不在1附近时,大于1会造成梯度爆炸,小于1会造成梯度消失。梯度爆炸会使学习变得不稳定,梯度消失会造成无法优化。从这个观点看, x T W t x^TW^t xTWt最终会丢失x中所有与W主特征向量正交的成分
2)优化长期依赖
机器学习中的一个主题是:设计一个易于优化的模型通常比设计出更强大的优化算法更容易
[1]梯度截断:梯度截断用于解决梯度爆炸的问题,设置梯度阈值,在梯度更新前丢掉饱和部分。具体有对于参数向量逐元素截断,还有对梯度范数进行截断两种方法
[2]引导信息流的正则化:用于解决梯度消失问题,在目标函数中引入正则化项 Ω = ∑ t ( ∣ ∣ ( ▽ h t L ) d h t d h t − 1 ∣ ∣ ∣ ∣ ▽ h t L ∣ ∣ ) 2 Ω=\sum_t(\frac {|| (▽_{h^t}L)\frac {dh^t} {dh^{t-1}} ||} {|| ▽_{h^t}L ||})^2 Ω=∑t(∣∣▽htL∣∣∣∣(▽htL)dht−1dht∣∣)2,要使 ( ▽ h t L ) d h t d h t − 1 (▽_{h^t}L)\frac {dh^t} {dh^{t-1}} (▽htL)dht−1dht与 ▽ h t L ▽_{h^t}L ▽htL一样大。计算该正则化项可能会出现困难,一种做法是在前向传播时将其设为近似的常值
1)动机
从输入层到隐藏层以及隐藏层之间的权重映射是RNN网络中最难学的参数,那么一个避免这种困难的方法是手动设定循环隐藏单元,使网络能够很好的捕捉输入历史,且只用学习输出层权重
2)方法
[1]回声状态网络ESN:使用连续的隐藏单元,如简单地固定权重使其具有常值(近1或远大于1如3)的谱半径(特征值谱: J t = d s t d s t − 1 J^t=\frac {d s^t}{ds^{t-1}} Jt=dst−1dst,谱半径:定义为特征值的最大绝对值)信息通过时间向前传播过程中由于非线性单元的饱和而不会产生爆炸
[2]流体状态机:与ESN类似,只是使用二值型的隐藏单元
[3]储层计算:ESN和流体状态机都被称为储层计算,因为隐藏单元形成了可能捕获输入历史不同方面的临时特征池。储层计算网络类似于核机器,它将任意长度的序列映射为一个长度固定的向量,之后可以施加一个线性预测算子(通常是一个线性回归)来解决问题
1)多时间尺度策略:是处理长期依赖的一种方法,它设计工作在多个时间尺度(即每次输入不是单个时间步,而是多个)使模型的某些部分在细粒度的时间尺度(单时间步)上操作并能处理细节,而其他部分在粗时间粒度(多个时间步一起作输入)操作并能把遥远过去的信息有效地传递过来
2)几种多时间尺度策略
[1]时间维度的跳跃连接
增加从遥远过去的变量到目前变量的连接(即每次输入不是当前时间步还包括多个过去时间步)以构造较长的延迟循环网络
引入了d延迟的循环网络导数指数减小的速度与 r d \frac r d dr而非r有关,可以减轻循环依赖的梯度爆炸问题
[2]渗漏单元
对某些参数更新时积累一个平滑的梯度: v → u t = α u t − 1 + ( 1 − α ) v t v → u^t = αu^{t-1} + (1-α)v^t v→ut=αut−1+(1−α)vt, u t u^t ut是滑动平均值,这是一个从 u t − 1 u^{t-1} ut−1到 u t u^t ut线性自连接的例子。当α接近1时,滑动平均值能记录很长一段时间的信息,当α接近0时,过去的信息会被丢弃。这种线性自连接的隐藏单元可以模拟滑动平均的行为,被称为渗漏单元
[3]删除连接
删除连接主动删除长度为1的连接并用更长的连接替换他们,这种修改方式迫使单元在长时间尺度上运作。一种具体方法是使循环单元变为渗漏单元,但不同的单元组关联不同的固定时间尺度;另一种是使显式且离散的更新发生在不同的时间,不同的单元组有不同的频率
门控RNN是解决长期依赖的一种RNN模型,是实际应用中非常有效的序列模型,渗漏单元通过手动设置权重矩阵,而门控RNN则是希望网络能够学会在不同时间步动态地遗忘(丢弃)历史信息和记忆(保留)历史信息以及保留信息的大小,门控则是起到遗忘和记忆的作用
1)长短期记忆模型LSTM
[1]相比于传统的RNN,对于隐藏层而言,对于每个时间步也是使用相同的隐藏层单元进行循环连接,但是每个隐藏单元的内部引入了门控控制,进行了更加复杂的计算。
[2]相对于保留记忆的历史时间步的信息 h t − 1 h^{t-1} ht−1被传递外,还增加了状态单元 s t − 1 s^{t-1} st−1进行传递。这时上一步的输出信息实际变成了原始的输入,称为短时记忆;而状态单元实际成为了模型处理后的保留历史信息的具体,称为长时记忆
[3]LSTM隐藏层的具体计算方法如下:
①遗忘门:控制状态单元保留的历史信息(长时记忆)的多少(也可看作遗忘了多少,当sigmoid函数输出0时,则表明完全丢弃历史信息)
②输入门:控制本时间步对整个序列上贡献度的多少,有多少值增加到以往的状态单元中(长时记忆)
③输出门:控制本时间步的具体输出(短期记忆)与状态单元的最终值(长期记忆)
对于一个训练好的LSTM而言,门的值绝大多数都非常接近0或1,其余的值很少
2)门控循环单元GRU
[1]GRU同LSTM一样用于解决长期依赖问题,它与LSTM在很多时候的表现相当。它相比于LSTM舍弃了状态单元 s t s^t st,仅使用某一时间步的输入X和上一时间步的输出 h t − 1 h^{t-1} ht−1进行计算。它的计算相比于LSTM的没有其分工明确,但更加得一气呵成,这使得它从形式上来说没有LSTM清晰,但好处就是减少了计算量,可以提高计算效率
[2]计算
①重置门: R t = σ ( W r X t + U r h t − 1 + b t ) R_t = σ(W_rX_t+U_rh_{t-1} + b_t) Rt=σ(WrXt+Urht−1+bt),重置门有助于捕获短期记忆
②更新门: Z t = σ ( W z X t + U z h t − 1 + b z ) Z_t = σ(W_zX_t+U_zh_{t-1} + b_z) Zt=σ(WzXt+Uzht−1+bz),更新门有助于捕获长期记忆
③使用重置门计算重置后的数据: h t ′ = t a n h ( W c X t + U ( R t ⊙ h t − 1 ) ) h'_t = tanh(W_cX_t + U(R_t ⊙h_{t-1})) ht′=tanh(WcXt+U(Rt⊙ht−1))
④使用更新门和重置数据计算当前时间步的输出: h t = Z t ⊙ h t − 1 + ( 1 − Z t ) ⊙ h t ′ h_t=Z_t⊙h_{t-1} + (1-Z_t)⊙h'_t ht=Zt⊙ht−1+(1−Zt)⊙ht′
[3]作用
①复位和更新门能独立地 ‘‘忽略’’ 状态向量的一部分
②更新门像条件渗漏累积器一样 可以线性门控任意维度,从而选择将它复制(在sigmoid的一个极端)或完全由新的 ‘‘目标状态’’ 值(朝向渗漏累积器的收敛方向)替换并完全忽略它(在另一个极端)
③复位门控制当前状态中哪些部分用于计算下一个目标状态,在过去状态和未来状态之间引入了附加的非线性效
最后给出一个应用LSTM模型基于时间序列的股票预测模型的示例程序
1)数据集下载
使用上海证券从1990年12月20日到2021年3月31日每天的股票数据
2)程序代码
"""
:description:This script used LSTM model to predict time sequence data:shanghai stock exchange
:dataset:1990.12.20-2021.03.31 shanghai stock exchange's data
:debugging-time:2022.11.09
"""
import keras
from pandas import read_csv
from keras import optimizers
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.callbacks import LearningRateScheduler
import keras.backend as K
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_score
import numpy
import matplotlib.pyplot as plt
import pickle
import math
def create_dataset(_dataset, _look_back, _predict_step):
"""
:description:创建训练数据:将时间序列划分成_look_back一组的输入数据,预测下一_predict_step的值
:param _dataset: 数据集
:param _look_back: 输入时间步
:param _predict_step: 预测时间步
:return:
"""
dataX, dataY = [], []
i = 0
while i < len(_dataset) - _look_back - _predict_step:
x = _dataset[i:(i + _look_back), :]
y = _dataset[i + _look_back:i + _look_back + _predict_step, :]
dataX.append(x)
dataY.append(y)
i = i + _predict_step
return numpy.array(dataX), numpy.array(dataY)
def scheduler(epoch):
"""
:description:设置自适应调整学习率规则:每隔10个epoch,学习率按训练轮数增长指数差值递减
:param epoch: 训练轮数
:return:
"""
# 改进点2:这里可以调训练轮数的参数,可以更换自适应学习率规则
if epoch % 100 == 0 and epoch != 0:
lr = K.get_value(model.optimizer.lr)
K.set_value(model.optimizer.lr, lr * 0.95 ** (epoch / 10))
print("lr changed to {}".format(lr * 0.95 ** (epoch / 10)))
return K.get_value(model.optimizer.lr)
if __name__ == '__main__':
# -----------CA-GrQc.txt.读入数据集-----------
fund_code = '000001'
dataframe = read_csv(fund_code + '.csv', encoding='gb2312')
# 数据预处理:dataframe切片,取全部样本第3列以后的列切片 -> 逆置(以时间升序) -> 取值 -> 转float32型
# https://blog.csdn.net/weixin_46649052/article/details/112462702
dataset = dataframe.iloc[:, 3:].iloc[::-1].values.astype('float32')
# --------2.划分原始的训练集和测试集---------
# 这里是80%做训练,20%做预测
train_size = int(len(dataset) * 0.8)
test_size = len(dataset) - train_size
# 分割原始的训练集和测试集
# 改进点1:划分训练集和测试集的方法是采用前80%的时间和后20%的时间。可以对时间按时间段抽样划分!
train, test = dataset[0:train_size, :], dataset[train_size:len(dataset), :]
# 归一化:将训练集和测试集数据进行归一化,并映射到[0,CA-GrQc.txt]区间
# https://blog.csdn.net/GentleCP/article/details/109333753
scaler = MinMaxScaler(feature_range=(0, 1))
train = scaler.fit_transform(train)
test = scaler.transform(test)
#
with open('scaler_for_' + fund_code + '.pickle', 'wb') as f:
pickle.dump(scaler, f)
# ------3.根据原始的数据构造模型输入的数据----------
look_back = time_step = 20 # 时间步长:可以调参
predict_step = 1 # 预测步数:可以调参
trainX, trainY = create_dataset(train, look_back, predict_step)
testX, testY = create_dataset(test, look_back, predict_step)
_, plt_data = create_dataset(dataset, look_back, predict_step)
# 重新整形模型输入数据的数据结构:[输入,时间步,真实输出]
trainX = numpy.reshape(trainX, (-1, time_step, trainX.shape[2]))
trainY = trainY.reshape(-1, predict_step * trainX.shape[2])
testX = numpy.reshape(testX, (-1, time_step, trainX.shape[2]))
testY = testY.reshape(-1, predict_step * trainX.shape[2])
# --------4.构建LSTM模型--------
# 4.1设置minibatch大小
batch_size = 32
# 4.2使用adam优化器
# https://blog.csdn.net/qq_32931827/article/details/122909624
optimizers.adam_v2.Adam(learning_rate=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-8)
# 4.3设置自适应学习率
reduce_lr = LearningRateScheduler(scheduler)
# 4.4设置模型结构
model = Sequential()
model.add(LSTM(32, input_shape=(time_step, trainX.shape[2]), return_sequences=True))
# model.add(Dropout(rate=0.2))
model.add(LSTM(18, return_sequences=False))
# model.add(Dropout(rate=0.3))
model.add(Dense(predict_step * trainX.shape[2]))
# 模型编译
model.compile(loss='mse', optimizer='adam')
# 模型数据输入并训练
model.fit(trainX, trainY, epochs=50, batch_size=batch_size, verbose=2, callbacks=[reduce_lr])
# 保存模型
model.save('model_' + fund_code + '.h5')
# ----------5.进行预测并调整数据结构-----------
model = keras.models.load_model('model_000001.h5')
trainPredict = model.predict(trainX)
testPredict = model.predict(testX)
trainPredict = trainPredict.reshape(-1, trainX.shape[2])
trainY = trainY.reshape(-1, trainX.shape[2])
testPredict = testPredict.reshape(-1, trainX.shape[2])
testY = testY.reshape(-1, trainX.shape[2])
plt_data = plt_data.reshape(-1, trainX.shape[2])
# -------------6.计算平均平方损失------------
trainScore = math.sqrt(mean_squared_error(trainY[:, :], trainPredict[:, :]))
print('Train Score: %.4f RMSE' % trainScore)
testScore = math.sqrt(mean_squared_error(testY[:], testPredict[:, :]))
print('Test Score: %.4f RMSE' % testScore)
testScore = mean_squared_error(testY[:], testPredict[:, :])
print('Test Score: %.4f MSE' % testScore)
R_2 = r2_score(testY, testPredict)
print('Model R^2 = %.3f' % R_2)
title = fund_code + ' time_step = ' + str(time_step) + ' R^2 = {:.2f}'.format(R_2)
# -------------7.反归一化:恢复原本的数据形式--------------
trainPredict = scaler.inverse_transform(trainPredict)
testPredict = scaler.inverse_transform(testPredict)
trainY = scaler.inverse_transform(trainY)
testY = scaler.inverse_transform(testY)
# --------------8.结果可视化----------------
# 蓝色为原数据,绿色为训练集的预测值,值红色为测试集的预测
# 8.1将训练集预测结果转为折线图
trainPredict = trainPredict.reshape(-1, trainX.shape[2])
testPredict = testPredict.reshape(-1, trainX.shape[2])
plt_data = plt_data.reshape(-1, trainX.shape[2])
trainPredictPlot = numpy.empty_like(plt_data)
trainPredictPlot[:] = numpy.nan
trainPredictPlot[0:len(trainPredict)] = trainPredict[:]
# 8.2将测试集预测结果转为折线图输出的数据结构
testPredictPlot = numpy.empty_like(plt_data)
testPredictPlot[:] = numpy.nan
testPredictPlot[len(trainPredict) + look_back + predict_step:] = testPredict[:]
# 8.3绘制训练集预测值和真实值的折线图
plt.figure(1)
plt.plot(plt_data[:, 0])
plt.plot(trainPredictPlot[:, 0])
plt.plot(testPredictPlot[:, 0])
plt.title(title)
plt.savefig('train_result.png')
# 8.4绘制测试集近100天的预测数据
plt.figure(2)
plt.plot(testY[-100:, 0])
plt.plot(testPredict[-100:, 0])
plt.title('test_set last 100 day')
plt.savefig('test_last_100.png')
plt.show()
3)运行结果
[1]真实输出、训练集上预测和测试集上预测输出