文章目录
- 一、前言
- 1.1、深度学习时间序列一般是几维数据?每个维度的名字是什么?通常代表什么含义?
- 1.2、为什么机器学习/深度学习算法无法处理时间序列数据?
- 1.3、RNN(循环神经网络)处理时间序列数据的思路?
- 1.4、RNN存在哪些问题?
- 二、LSTM的基本架构
- 2.1 记忆细胞
- 2.2 遗忘门
- 2.3 输入门
- 2.4 输出门
- 三、应用案例
- 3.1 TensorFlow版
- 3.2 PyTorch版
- 3.2.1 LSTM的输入参数
- 3.2.2 LSTM的三个输出
- 3.2.3 LSTM进行时间序列预测的基本流程
- 3.2.4 案例
- 四、拓展思考
- 4.1 为什么说 C t C_t Ct是主导长期记忆, h t h_t ht是主导短期记忆?
- 4.2 RNN为什么容易梯度消失和梯度爆炸?
- 4.3 LSTM解决梯度消失和梯度爆炸问题吗?
时间序列相关参考文章:
时间序列预测算法—ARIMA
时间序列预测算法—Prophet
时间序列预测算法—LSTM
时间序列分类任务—tsfresh
python时间序列处理
有季节效应的非平稳序列分析
时间序列异常值检测方法
时间序列异常值处理方法
一、前言
1.1、深度学习时间序列一般是几维数据?每个维度的名字是什么?通常代表什么含义?
答:时间序列可以是一维、二维、三维甚至更高维度的数据,在深度学习世界中常见的是三维时间序列,这三个维度分别是(batch_size,time_step,input_dimensions),其中,batch_size:在训练模型时一次输入到模型中的样本数量;time_step:每个样本的时间步数,即每个样本包含的时间序列长度;input_dimensions:每个时间步的特征数量。
1.2、为什么机器学习/深度学习算法无法处理时间序列数据?
答:时间序列数据属于序列化数据的范畴,其特点在于各样本之间存在内在的逻辑关联。这类数据按照一定的顺序组织,其顺序不可随意更改,因为这种顺序性反映了样本间的依赖关系。在分析序列数据时,我们不仅需要理解特征与目标标签之间的关系,还需掌握样本间的相互作用,因为序列中各样本的顺序对彼此及最终预测结果均产生影响。
多数传统机器学习和深度学习模型默认样本间是独立的,它们主要关注于特征与标签间的映射,而忽略了样本间的序列依赖性。这一局限性使得这些算法在处理时间序列数据时显得力不从心,无法充分捕捉数据中的时间动态特征,这也是它们在时间序列分析中适用性受限的主要原因。
1.3、RNN(循环神经网络)处理时间序列数据的思路?
答:在时间序列数据中,样本间的关系体现为连续时间点之间的依赖性。为了捕捉这种连续性,循环神经网络(RNN)及其变体采用了一种策略:逐个处理时间点,并将先前时间点的信息传递至后续时间点。这种方法允许每个时间点接收前一时间点的上下文信息,从而在连续的时间点之间建立联系。这种递归信息传递机制是早期深度学习算法处理时间序列数据时的标准做法。
1.4、RNN存在哪些问题?
答:RNN算法思路新颖,但是却存在众多实践中的问题,其中“记忆机制"相关的问题十分显著,包括但不限于:
1.RNN对所有信息照单全收,记忆机制中毫无重点。
从常识来判断,无论是在时间序列中还是在文字序列中,过去时间步上的信息对未来时间步上信息的影响可能是不同的。但RNN为了节约计算资源,设置了全样本权重共享机制,这意味着RNN处理所有时间步信息的方式是完全相同。因此,在RNN吸纳信息时,它无法辨别不同时间点上的信息对未来的不同影响,在使用这些信息进行预测的时候,RNN也无法辨别不同时间步上的标签最需要的信息有哪些,这会严重影响算法的整体预测效果。到今天,这依然是影响RNN预测效果的关键因素之一。
2.RNN中的新信息会强制覆盖旧信息,导致长期记忆被遗忘
虽然循环网络家族建立起了时间点之间的联系,但这种联系有时不总是有效的,当一个时间点上包含的历史时间点信息越多,那些久远的历史时间点的信息就不太容易对当下的预测构成影响。在RNN的计算流程中,虽然理论上最后一个时间步中包含了所有时间步上的信息,但是真正有影响力的只要非常接近最后一个时间步的几个时间步而已。大部分信息都被RNN遗忘,导致RNN很难处理较长的序列。
基于以上两点问题,研究者们在设计LSTM的时候存在一个根本诉求——要创造一个全新的架构、一套全新的数据流,为循环神经网络赋予选择性记忆和选择性传递信息的能力。这里的选择性包含多层含义,包括:
1.循环网络必须自行选择吸纳多少新信息,只留重点,拒绝照单全收
2.循环网络必须自行选择遗忘多少历史信息,主动遗忘无效内容保留有效内容
3.循环网络必须自行判断、对当前时间步的预测来说最重要的信息是哪些,并将该信息输出给当前时间步,这样既可以保证当前时间步上的预测是最高效的,也不会影响向下一个时间步传递的信息。
二、LSTM的基本架构
在上述提到的三个能力当中,前两个能力允许循环神经网络决定“哪些长期信息会被传递下去",最后一个能力允许循环神经网络决定“哪些短期信息对当前时间步的预测是最重要的"。这三种能力构成了LSTM的基本结构。
2.1 记忆细胞
首先,LSTM依然是一个循环神经网络,因此LSTM的网络架构与RNN高度相似,同时LSTM也是需要遍历所有时间步,不断完成循环和嵌套的。但不同的是,RNN由输入层、隐藏层和输出层构成,而LSTM由输入层、记忆细胞和输出层构成,其中输入、输出层与RNN的输入、输出层完全一致,而记忆细胞是LSTM独有的结构。
记忆细胞是LSTM的基本计算单元,在记忆细胞中,我们分割长期信息与短期信息,同时赋予循环网络对信息做选择的能力。在之前我们提到,循环网络必须自行决定哪些长期信息会被传递下去,哪些短期信息对当前的预测最为有效,因此在记忆细胞当中,LSTM设置了两个关键变量:主要负责记忆短期信息、尤其是当前时间步信息的隐藏状态h;以及主要负责长期记忆的细胞状态C。
这两个变量都随着时间步进行迭代。在迭代开始时,LSTM会同时初始化
h
0
h_0
h0和
C
0
C_0
C0;在任意时间步t上,记忆细胞会同时接受来自上一个时间步的长期记忆
C
t
−
1
C_{t-1}
Ct−1、短期信息
h
t
−
1
h_{t-1}
ht−1以及当前时间步上输入的新信息
X
t
X_t
Xt三个变量,结合三者进行运算后,记忆细胞会输出当前时间步上的长期记忆
C
t
C_t
Ct和短期信息
h
t
h_t
ht,并将它们传递到下一个时间步上。同时,在每个时间步上,
h
t
h_t
ht还会被传向当前时间步的输出层,用于计算当前时间步的预测值。
横向上,它可以被分割为C(长期记忆的传播)的传递和h(短期记忆的传播)的传递两条路径;在纵向上,它可以被分为三个不同的路径:
1.帮助循环网络选择吸纳多少新信息的输入门
2.帮助循环网络选择遗忘多少历史信息的遗忘门
3.帮助循环网络选择出对当前时间步的预测来说最重要的信息、并将该信息输出给当前时间步的输出门。
2.2 遗忘门
遗忘门是决定要留下多少长期信息C的关键计算单元,其数学本质是令上一个时间步传入的
C
t
−
1
C_{t-1}
Ct−1乘以一个[0,1]之间的比例,以此筛选掉部分旧信息。假设遗忘门令
C
t
−
1
C_{t-1}
Ct−1乘以0.7,就是说遗忘门决定了要保留70%的历史信息,遗忘30%的历史信息,这30%的信息空间就可以留给全新的信息来使用。核心就是计算
f
t
f_t
ft 求得比例。
遗忘门会参考当前时间步的信息
X
t
X_t
Xt与上一个时间步的短时信息
h
t
−
1
h_{t-1}
ht−1来计算该比例,其中σ是sigmoid函数,
W
f
W_f
Wf是动态影响最终权重大小的参数,
f
t
f_t
ft就是[0,1]之间的、用于影响
C
t
−
1
C_{t-1}
Ct−1的比例。
在LSTM的设计逻辑之中,考虑
X
t
X_t
Xt和
h
t
−
1
h_{t-1}
ht−1实际是在考虑离当前时间步最近的上下文信息,而权重
W
f
W_f
Wf会受到损失函数和算法整体表现的影响,不断调节遗忘门中计算出的比例 f 的大小,因此,遗忘门能够结合上下文信息、损失函数传来的梯度信息、以及历史信息共同计算出全新的、被留下的长期记忆
C
t
C_t
Ct。
2.3 输入门
输入门是决定要吸纳多少新信息来融入长期记忆C的计算单元,其数学本质是在当前时间步传入的所有信息上乘以一个[0,1]之间的比例,以筛选掉部分新信息,将剩余的新信息融入长期记忆C。
计算过程中,首先要计算出当前时间步总共吸收了多少全新的信息,这个计算全新信息的方式就与RNN中计算ht的方式高度相似,因此也会包含用于影响新信息传递的参数
W
c
W_c
Wc和RNN中常见的tanh函数。然后,要依据上下文信息(依然是
X
t
X_t
Xt和
h
t
−
1
h_{t-1}
ht−1)以及参数
W
t
W_t
Wt来筛选新信息的比例
i
t
i_t
it。最后将二者相乘,并加入到长期记忆C当中。
相比RNN的数据输入过程,LSTM的输入过程灵活了非常多,在输入门当中,不仅对输入数据加上了一个比例it,还分别使用了两个受损失函数影响的权重Wt和Wc来控制新信息聚合和比例计算的流程。在这一比例和两大参数的作用下,输入数据可以被高度灵活地调节,以便满足最佳的损失函数需求。
更新细胞状态:当遗忘门决定了哪些信息要被遗忘,输入门决定了哪些信息要被加入到长期记忆后,就可以更新用于控制长期记忆的细胞状态了。如下图所示,上一个时间步的长期记忆
C
t
−
1
C_{t-1}
Ct−1将乘以遗忘门给出的比例
f
t
f_t
ft,再加上新信息乘以新信息筛选的比例
i
t
i_t
it,同时考虑放弃过去的信息、容纳新信息,以此来构成传递下一个时间步的长期信息
C
t
C_t
Ct。
C
t
C_t
Ct是在
C
t
−
1
C_{t-1}
Ct−1基础上直接迭代更新得到的,所以
C
t
C_t
Ct整合了[1,t]所有时间步的历史信息,并负责将这些信息不断传递下去。因此,
C
t
C_t
Ct是长期记忆的象征。
2.4 输出门
输出门是从全新的长期信息
C
t
C_t
Ct中筛选出最适合当前时间步的短期信息ht的计算单元,本质是令已经计算好的长期信息
C
t
C_t
Ct乘以一个[0,1]之间的比例,以此筛选出对当前时间步最有效的信息用于当前时间步的预测。
这个流程分为三步:
1、首先要借助上下文信息和权重
W
o
W_o
Wo来求解出比例
O
t
O_t
Ot
2、对长期信息
C
t
C_t
Ct进行tanh标准化处理
3、将
O
t
O_t
Ot乘以标准化后的长期信息
C
t
C_t
Ct之上,用于筛选出
h
t
h_t
ht
为什么要对长期信息Ct做标准化处理呢?
答:Tanh标准化可以限制有效长期信息
C
t
C_t
Ct的数字范围,避免历史信息在传递过程中变得越来越大,同时还能为输出门赋予一定的非线性性质,这个过程被实践证明有助于保持训练稳定、还能够强化算法学习能力,因此在LSTM最终的设计中被保留下来。
h
t
h_t
ht和
h
t
−
1
h_{t-1}
ht−1之间没有直接的迭代关系,虽然二者有一定的联系,但
h
t
−
1
h_{t-1}
ht−1不是构成ht的核心。在记忆细胞中,
h
t
−
1
h_{t-1}
ht−1只是用来辅助C进行迭代的变量之一,而
h
t
h_t
ht是为了在当前时间步上生成
y
t
y_t
yt而计算出来的全新变量,影响ht具体取值的核心不是上个时间步的信息
h
t
−
1
h_{t-1}
ht−1,而是当前时间步上的输入信息和预测标签的需求,因此,
h
t
h_t
ht是一个主要承载短期信息、尤其是当前时间步上信息的变量,它是为了在当前时间步预测出最准确的标签而存在的。
LSTM是一种RNN特殊的类型,可以学习长期依赖信息。LSTM和基线RNN并没有特别大的结构不同,用了不同的函数来计算隐状态。LSTM的“记忆”我们叫做细胞,这些“细胞”会决定哪些之前的信息和状态需要保留/记住,而哪些要被抹去。实际的应用中发现,这种方式可以有效地保存很长时间之前的关联信息。
RNN 会受到短时记忆的影响。如果一条序列足够长,那它们将很难将信息从较早的时间步传送到后面的时间步。因此,如果你正在尝试处理一段文本进行预测,RNN 可能从一开始就会遗漏重要信息。RNN会忘记它在较长序列中以前看到的内容,因此RNN只具有短时记忆。
三、应用案例
通过网盘分享的文件:LSTM数据
链接: https://pan.baidu.com/s/1coExt1KeR6Ke4QqcJhCE1A 提取码: 2zxk
3.1 TensorFlow版
import pandas as pd
import numpy as np
data = pd.read_excel('zgpa_train.xlsx')
data.head()
price=data.loc[:,'收盘']
#归一化
price_norm=price/max(price)
price_norm
#可视化
%matplotlib inline
from matplotlib import pyplot as plt
# 设置中文显示和负号显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 黑体
plt.rcParams['axes.unicode_minus'] = False # 负号正常显示
fig1=plt.figure(figsize=(10,6))
plt.plot(data['日期'],price)
plt.title("收盘价")
plt.xlabel("time")
plt.ylabel("价格")
plt.show()
#定义x、y的值:将原始数据处理成可以供模型训练的格式。数据通过滑动窗口技术(time_step)进行处理,生成适合模型输入的 x和 y
def extract_data(data,time_step):
x=[] #数据
y=[] #标签
for i in range(len(data)-time_step):
x.append([a for a in data[i:i+time_step]]) #time_step=8,x=[0,1,2,3,4,5,6,7]
y.append(data[i+time_step]) #第8
x=np.array(x)
x=x.reshape(x.shape[0],x.shape[1],1) #将x数据的形状调整为 (样本数, 时间步数, 特征数)在这个例子中,特征数=1,因为每个时间步只包含一个数据点(例如一个股价)
y=np.array(y)
return x,y
time_step=8
x,y=extract_data(price_norm,time_step)
print(x[0,:,:])
print(y[0])
from keras.models import Sequential
from keras.layers import Dense, SimpleRNN, Input
model = Sequential()
# 使用 Input 层显式定义输入形状
model.add(Input(shape=(time_step, 1))) # 输入层
# 添加 RNN 层
model.add(SimpleRNN(units=5, activation='relu'))
# 添加输出层
model.add(Dense(units=1, activation="linear"))
# 模型配置
model.compile(optimizer='adam', loss="mean_squared_error")
model.summary()
#训练模型
model.fit(x,y,batch_size=30,epochs=200)
#预测
y_train_predict=model.predict(x)*max(price)
y_train=[i*max(price) for i in y]
fig2=plt.figure(figsize=(10,6))
r2 = r2_score(y_train,y_train_predict)
plt.title(f"R-squared: {r2:.2f}")
plt.plot(y_train,label="real price")
plt.plot(y_train_predict,label="train predict price")
plt.xlabel("time")
plt.ylabel("价格")
plt.legend()
plt.show()
#测试数据预测
data_test = pd.read_excel('zgpa_test.xlsx')
price_test=data_test.loc[:,'收盘']
price_test_norm=price_test/max(price_test)
x_test_norm,y_test_norm=extract_data(price_test_norm,time_step)
y_test_predict=model.predict(x_test_norm)*max(price_test)
y_test=[i*max(price_test) for i in y_test_norm]
fig3=plt.figure(figsize=(10,6))
r2 = r2_score(y_test,y_test_predict)
plt.title(f"R-squared: {r2:.2f}")
plt.plot(y_test,label="real price_test")
plt.plot(y_test_predict,label="test predict price_test")
plt.xlabel("time")
plt.ylabel("价格")
plt.legend()
plt.show()
3.2 PyTorch版
3.2.1 LSTM的输入参数
LSTM类与RNN类高度相似,这两个类的参数都很少、结构相当简单。虽然该类的名字叫做LSTM,但实际上nn.LSTM并不是完整的LSTM算法,而是LSTM中的输入层和隐藏层。nn.LSTM的输入层是在线性层的基础上改进后的层,隐藏层则是包含记忆细胞的层,这两个层除了最基本的匹配权重、神经元结果加和、激活函数、向前向后传播等功能外,还负责执行将上个时间步的中间变量传递给下个时间步的功能。对RNN来说,传递的是隐藏状态 h t h_t ht,对LSTM来说,同时传递细胞状态 c t c_t ct和隐藏状态 h t h_t ht。
nn.LSTM(self,input_size,hidden_size,num_layers=1,bias=True,batch_first=False,dropout=0.0,
bidirectional=False,proj_size=0,device=None,dtype=None)
input_size:输入特征的数量,也是输入层的神经元数量,对输入数据为三维数据的LSTM,input_size就是input_dimension。
hidden_size:隐藏层的神经元数量,对LSTM来说就是隐藏层上的记忆细胞的数量。
num_layers:隐藏层的数量。
batch_first:如果为True,输入和输出Tensor的形状为[**batch_size**,seq_len,input_dimensions],否则为[seq_len,**batch_size**,input_dimensions]
drop_out: 在神经网络中常见的抗过拟合机制,在Pytorch被内置在LSTM里帮助对抗过拟合。Dropout是在神经网络传播过程中,随机让部分参数为0的抗过拟合方法。令参数为0,就可以切断神经网络层与层之间的链接,从而切断信息的传播,以此来阻碍神经网络的学习,达到对抗过拟合的目的
drop_out这一参数中我们一般填写[0,1)之间的小数。例如填写0.5,就代表在每次正向传播中,随机令50%的参数为0。这一随机性是不可控制的,任何随机数种子都不能让Dropout的随机性变得固定,因此Dropout会让神经网络的训练过程变得无法复现。在LSTM中,Dropout被施加在隐藏层与隐藏层之间,而不是记忆细胞之间,也就是说Dropout是施加在
h
t
h_t
ht通向输出层的传播路径上,而不影响
C
t
C_t
Ct和
h
t
h_t
ht在时间步上的传播路径。
需要注意的是,和任意神经网络中的Dropout模式一致,dropout
参数默认会在训练过程中起作用,而在模型的预测、评估或推理阶段不起作用。因此在训练LSTM的时候,我们会需要使用 train()
和 eval()
两种模式来帮助模型分辨当前正在进行的是训练还是预测任务。
3.2.2 LSTM的三个输出
LSTM类有三个输出,一个是 output,一个是
h
n
h_n
hn,还有一个是
c
n
c_n
cn。LSTM类只有输入层和隐藏层。
output代表所有时间步上最后一个隐藏层上输出的隐藏状态的集合。如下图红色方框中的方向上的隐藏状态的集合。对单层的LSTM来说,output代表了唯一一个隐藏层上的隐藏状态。output的形状都为[seq_len,batch_size,hidden_size],不受隐藏层数量的影响。当batch_first=True时,output的形状为[batch_size,seq_len,hidden_size]
h
t
h_t
ht是最后一个时间步的、所有隐藏层上的隐藏状态。形状为[num_layers,batch_size,hidden_size]。无论batch_first参数取什么值,都不改变
h
t
h_t
ht的输出。
C
t
C_t
Ct是最后一个时间步的、所有隐藏层上的细胞状态。形状为[num_layers,batch_size,hidden_size]。无论batch_first参数取什么值,都不改变
C
n
C_n
Cn的输出。
虽然LSTM的每个时间步、每个隐藏层都会输出隐藏状态h和细胞状态C,但是LSTM类却只会“选择性”地帮助我们输出部分隐藏状态,例如output在深层神经网络中只会保留最后一个隐藏层的隐藏状态,而hn和Cn则是只显示最后一个时间步的隐藏状态。不难发现,其实output、hn、cn都不能代表nn.LSTM层在循环中所传输的全部信息,它们都只能代表部分信息。
如果我们需要执行对每一个时间步进行预测的任务(比如,预测每一分钟的股价,预测明天是否会下雨,预测每个单词的情感倾向,预测每个单词的词性),此时我们就会关注每个时间步在最后一个隐藏层上的输出。此时我们要关注的是整个output。
如果我们需要执行的是对每个时间序列表单、每个句子进行预测的任务时(比如对句子进行情感分类,预测某个时间段内用户的行为、看过所有的句子之后对后续的文字进行生成等任务),我们就只会关注最后一个时间步的输出。此时我们更可能使用hn或者cn来进行下一步的预测。同时,在进行生成式的任务的时候,我们大概率也只关心最后一个时间步上的输出。
需要注意的是,无论是output还是hn还是cn,都只是lstm层的输出,不是整个神经网络真正的输出,因为nn.LSTM层缺乏关键性结构:输出层。因此在构建网络的时候,我们需要在LSTM层之后再增加一个输出层(通常是线性层),用于补完整个网络架构。
3.2.3 LSTM进行时间序列预测的基本流程
数据预处理:时间序列的数据预处理是一个复杂的过程,可能涉及到使用动态的标准来进行数据的编码、缺失值处理、数据大小的处理、对数据进行滑窗、对数据进行分割等等。
数据导入:对深度学习数据来说,需要先将数据处理好,再导入为tensor。对于相对简单的数据,或许可以直接使用FloatTensor之类的工具进行转换,但对于结构复杂、或者尺寸过大的数据,则一般需要自定义继承自Datasets的数据导入函数。该流程的核心职责是将普通数据集整理出成深度学习算法所必须的输入形式,大部分时候数据导入的代码非常灵活,而且会根据数据的情况发生变化,十分复杂。
架构定义:自定义基于LSTM层的各类架构。最为简单的方式就是在PyTorch中自定义神经网络类,当然也可以使用Huggingface中的一系列高级LSTM相关融合架构。
定义训练流程所需的全部代码:在这个过程中,我们需要定义各类超参数、定义损失函数、定义优化算法、定义是否使用GPU、决定是否进行early stop、决定打印怎样的内容、决定是否进行持续的模型保存等等具体的流程。幸运的是,深度学习中的训练代码都是十分类似的。
进行初次训练,根据初次训练结果调整模型架构、模型参数、训练流程
不断进行训练、决定是否完成数据增强等工作
3.2.4 案例
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 黑体
plt.rcParams['axes.unicode_minus'] = False # 负号正常显示
data = pd.read_excel('zgpa_train.xlsx')
timeseries = data[["收盘"]].values.astype('float32')
#训练集与测试集分割:不能改变时间顺序,因此没有使用train_test_split等操作
train_size = int(len(timeseries) * 0.8)
test_size = len(timeseries) - train_size
train, test = timeseries[:train_size], timeseries[train_size:]
#数据格式转换为tensor
train_tensor = torch.FloatTensor(train).view(-1, train.shape[0], 1) #升为三维数据
test_tensor = torch.FloatTensor(test).view(-1, test.shape[0], 1)
print(train_tensor.shape,test_tensor.shape) #输出结果:torch.Size([1, 584, 1]) torch.Size([1, 147, 1])
#定义网络架构
import torch
import torch.nn as nn
class LSTMModel(nn.Module):
def __init__(self, input_size=1, hidden_size=50, num_layers=1, output_size=1):
super(LSTMModel, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
#LSTM层
self.lstm = nn.LSTM(input_size, hidden_size, num_layers,batch_first=True)
#输出层
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
#初始化h0和c0
h0 = torch.rand(self.num_layers, x.size(0), self.hidden_size).requires_grad_()
c0 = torch.rand(self.num_layers, x.size(0), self.hidden_size).requires_grad_()
#LSTM的向前传播,此时我们关注的是所有时间步上的输出,还是最后一个时间步上的输出?
#所有时间步上的输出,因此我们要输出的是output,而不是hn和cn
output, (_, _) = self.lstm(x, (h0.detach(), c0.detach()))
#output的结构为[batch_size, seq_len, hidden_size]
#因此我们要取出对我们有用的维度
out = self.fc(output[:, :, :])
return out
model = LSTMModel() #实例化
model(train_tensor).shape #尝试查看输出数据的结构
def MSE(Y_ture,Y_predict):
return ((Y_ture - Y_predict)**2).sum()/Y_ture.shape[0]
#对照:如果不使用神经网络预测,只将均值当做预测标签计算MSE的话。LSTM模型的mse不能比之高
MSE(train,train.mean()),MSE(test,test.mean()) #输出:(np.float32(233.31065), np.float32(9.202034))
- 超参数设置与架构设置
#超参数设置
input_size = 1
hidden_size = 50
num_layers = 1
output_size = 1
learning_rate = 0.01
num_epochs = 300
#实例化模型
model1 = LSTMModel(input_size, hidden_size, num_layers, output_size)
#定义损失函数与优化算法
criterion = nn.MSELoss(reduction="mean")
optimizer = torch.optim.Adam(model1.parameters(), lr=learning_rate)
#开始进行训练的循环
for epoch in range(num_epochs):
outputs = model1(train_tensor)
optimizer.zero_grad()
loss = criterion(outputs, train_tensor[:, :, :])
loss.backward()
optimizer.step()
if (epoch+1) % 50 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
model1.eval()
test_outputs = model1(test_tensor).detach().numpy()
MSE(test,test_outputs) #输出:67.62194
- 模型验证集MSE较验证集均值大,模型存在过拟合。优化模型超参数与架构设置。
#超参数设置
input_size = 1
hidden_size = 100
num_layers = 5
output_size = 1
learning_rate = 0.1
num_epochs = 1500
#实例化模型
model = LSTMModel(input_size, hidden_size, num_layers, output_size)
#定义损失函数与优化算法
criterion = nn.MSELoss(reduction="mean")
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
#开始进行训练的循环
for epoch in range(num_epochs):
outputs = model(train_tensor)
optimizer.zero_grad()
loss = criterion(outputs, train_tensor[:, -1, :])
loss.backward()
optimizer.step()
if (epoch+1) % 150 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
model.eval()
test_outputs = model(test_tensor).detach().numpy()
MSE(test,test_outputs) #输出:10.368058
模型依然存在过拟合风险。
增加滑动窗口进行处理数据。
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import matplotlib.pyplot as plt
# 1. 数据加载
# 替换为您的数据文件名
file_path = 'zgpa_train.xlsx'
data = pd.read_excel(file_path)
# 提取收盘价并归一化
price = data.loc[:, '收盘']
price_norm = price / max(price)
# 2. 数据集构建
# 数据提取函数
def extract_data(data, time_step):
x, y = [], []
for i in range(len(data) - time_step):
x.append([a for a in data[i:i + time_step]])
y.append(data[i + time_step])
x = np.array(x)
y = np.array(y)
x = x.reshape(x.shape[0], x.shape[1], 1)
return x, y
# 定义时间步
time_step = 8
x, y = extract_data(price_norm, time_step)
# 转换为Tensor
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
x = torch.tensor(x, dtype=torch.float32).to(device)
y = torch.tensor(y, dtype=torch.float32).to(device)
# 数据划分
train_size = int(len(x) * 0.8)
train_x, test_x = x[:train_size], x[train_size:]
train_y, test_y = y[:train_size], y[train_size:]
# 数据加载器
train_loader = DataLoader(TensorDataset(train_x, train_y), batch_size=64, shuffle=True)
# 3. 定义LSTM模型
class LSTM(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, output_size):
super(LSTM, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
# 定义LSTM层
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
# 定义全连接层
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# 初始化隐藏状态和细胞状态
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
# 前向传播
out, _ = self.lstm(x, (h0, c0))
out = self.fc(out[:, -1, :]) # 取最后一个时间步的输出
return out
# 超参数设置
input_size = 1
hidden_size = 32
num_layers = 2
output_size = 1
learning_rate = 0.05
epochs = 100
# 实例化模型
model = LSTM(input_size, hidden_size, num_layers, output_size).to(device)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 4. 模型训练
losses = []
for epoch in range(epochs):
model.train()
for batch_x, batch_y in train_loader:
outputs = model(batch_x)
loss = criterion(outputs.squeeze(), batch_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
losses.append(loss.item())
if (epoch+1) % 10 == 0:
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
# 5. 模型评估
model.eval()
with torch.no_grad():
pred_y = model(test_x).cpu().numpy()
true_y = test_y.cpu().numpy()
# 反归一化
max_price = max(price)
pred_y = pred_y * max_price
# 6. 可视化结果
plt.figure(figsize=(12, 6))
plt.plot(range(len(price)), price, label='Original Data')
plt.plot(range(train_size + time_step, train_size + time_step + len(pred_y)), pred_y, label='Predicted Data', color='red') #
plt.xlabel('Time')
plt.ylabel('Normalized Price')
plt.title('LSTM Stock Price Prediction')
plt.legend()
plt.show()
# 保存模型
# torch.save(model.state_dict(), 'lstm_stock_model.pth')
四、拓展思考
4.1 为什么说 C t C_t Ct是主导长期记忆, h t h_t ht是主导短期记忆?
C
t
C_t
Ct是在历史的
C
t
−
1
C_{t-1}
Ct−1基础上加上新信息迭代更新得到的(这是说,我们可以写出一个形似
C
t
C_t
Ct= w
C
t
−
1
C_{t-1}
Ct−1+b的公式),所以
C
t
C_t
Ct整合了[1,t]所有时间步的历史信息,并负责将这些信息不断传递下去。
h
t
h_t
ht和
h
t
−
1
h_{t-1}
ht−1之间没有直接的选代关系,虽然二者有一定的联系,但
h
t
−
1
h_{t-1}
ht−1不是构成ht的核心。在记忆细胞中,
h
t
−
1
h_{t-1}
ht−1只是用来辅助C(长期记忆)进行迭代的变量之一,影响
h
t
h_t
ht具体取值的核心不是上个时间步的信息
h
t
−
1
h_{t-1}
ht−1,而是当前时间步上的输入信息和预测标签的需求,因此
h
t
h_t
ht是一个主要承载短期信息、尤其是当前时间步上信息的变量,它是为了在当前时间步预测出最准确的标签而存在的。
4.2 RNN为什么容易梯度消失和梯度爆炸?
梯度消失和梯度爆炸是RNN在反向传播过程中常见的问题,RNN的反向传播是通过时间的反向传播(Backpropagation Through Time,BPTT),其运行流程与一般的反向传播大有不同。在不同类型NLP任务会有不同的输出层结构、会有不同的标签输出方式。例如,在对词语/样本进行预测的任务中(情感分类、词性标注、时间序列等任务),RNN会在每个时间步都输出词语对应的相应预测标签;但是,在对句子进行预测的任务中(例如,生成式任务、seq2seq的任务、或以句子为单位进行标注、分类的任务),RNN很可能只会在最后一个时间步输出句子相对应的预测标签。输出标签的方式不同,反向传播的流程自然会有所区别。
假设现在我们有一个最为简单的RNN,需要完成针对每个词语的情感分类任务。该RNN由输入层、一个隐藏层和一个输出层构成,全部层都没有截距项,总共循环
t
t
t个时间步。该网络的输入数据为
X
X
X,输出的预测标签为
y
^
\hat{y}
y^,真实标签为
y
y
y,激活函数为
σ
\sigma
σ,输入层与隐藏层之间的权重矩阵为
W
x
h
W_{xh}
Wxh,隐藏层与输出层之间的权重矩阵为
W
h
y
W_{hy}
Why,隐藏层与隐藏层之间的权重为
W
h
h
W_{hh}
Whh,损失函数为
L
(
y
^
,
y
)
L(\hat{y},y)
L(y^,y),t时刻的损失函数我们简写为
L
t
L_t
Lt。此时,在时间步t上,这个RNN的正向传播过程可以展示如下:
RNN中存在至少三个权重矩阵需要迭代:输入层与隐藏层之间的权重矩阵
W
x
h
W_{xh}
Wxh,隐藏层与输出层之间的权重矩阵
W
h
y
W_{hy}
Why,隐藏层与隐藏层之间的权重
W
h
h
W_{hh}
Whh。当完成正向传播后,我们需要在反向传播过程中对以上三个权重求解梯度、并迭代权重,以
W
h
h
W_{hh}
Whh为例:
h
t
=
σ
(
W
x
h
X
t
+
W
h
h
h
t
−
1
)
=
σ
(
W
x
h
X
t
+
W
h
h
σ
(
W
x
h
X
t
−
1
+
W
h
h
h
t
−
2
)
)
y
^
t
=
W
h
y
h
t
L
t
=
L
(
y
^
t
,
y
t
)
\begin{align*} \mathbf{h}_{t} &= \sigma(\mathbf{W}_{xh} \mathbf{X}_{t} + \mathbf{W}_{hh} \color{red}{\mathbf{h}_{t-1}}) \\ & = \sigma(\mathbf{W}_{xh} \mathbf{X}_t + \mathbf{W}_{hh} \color{red}{\sigma(\mathbf{W}_{xh} \mathbf{X}_{t-1} + \mathbf{W}_{hh} \mathbf{h}_{t-2})}) \\ \\ \mathbf{\hat{y}}_{t} &= \mathbf{W}_{hy} \mathbf{h}_{t} \\ \\ L_{t} &= L(\mathbf{\hat{y}}_{t},\mathbf{y}_{t}) \end{align*}
hty^tLt=σ(WxhXt+Whhht−1)=σ(WxhXt+Whhσ(WxhXt−1+Whhht−2))=Whyht=L(y^t,yt)
L
t
L_{t}
Lt可以展开展示为:
L
t
=
L
(
y
^
t
,
y
t
)
=
L
(
W
h
y
h
t
,
y
t
)
=
L
(
W
h
y
σ
(
W
x
h
X
t
+
W
h
h
h
t
−
1
)
,
y
t
)
\begin{align*} L_{t} &= L(\mathbf{\hat{y}}_{t}, \mathbf{y}_{t}) \\ \\ &=L(\mathbf{W}_{hy} \mathbf{h}_{t}, \mathbf{y}_{t}) \\ \\ &=L(\mathbf{W}_{hy} \sigma(\mathbf{W}_{xh} \mathbf{X}_{t} + \mathbf{W}_{hh} \mathbf{h}_{t-1}), \mathbf{y}_{t}) \end{align*}
Lt=L(y^t,yt)=L(Whyht,yt)=L(Whyσ(WxhXt+Whhht−1),yt)
因此根据链式法则,有:
∂
L
t
∂
W
h
y
=
∂
L
t
∂
y
^
t
∗
∂
y
^
t
∂
W
h
y
∂
L
t
∂
W
x
h
=
∂
L
t
∂
y
^
t
∗
∂
y
^
t
∂
h
t
∗
∂
h
t
∂
W
x
h
∂
L
t
∂
W
h
h
=
∂
L
t
∂
y
^
t
∗
∂
y
^
t
∂
h
t
∗
∂
h
t
∂
W
h
h
\begin{align*} \ \frac{\partial L_{t}}{\partial W_{hy}} &= \frac{\partial L_{t}}{\partial \hat{y}_{t}} * \frac{\partial \hat{y}_{t}}{\partial W_{hy}} \\ \\ \ \frac{\partial L_{t}}{\partial W_{xh}} &= \frac{\partial L_{t}}{\partial \hat{y}_{t}} * \frac{\partial \hat{y}_{t}}{\partial h_{t}} * \frac{\partial h_{t}}{\partial W_{xh}} \\ \\ \ \frac{\partial L_{t}}{\partial W_{hh}} &= \frac{\partial L_{t}}{\partial \hat{y}_{t}} * \frac{\partial \hat{y}_{t}}{\partial h_{t}} * \frac{\partial h_{t}}{\partial W_{hh}} \end{align*}
∂Why∂Lt ∂Wxh∂Lt ∂Whh∂Lt=∂y^t∂Lt∗∂Why∂y^t=∂y^t∂Lt∗∂ht∂y^t∗∂Wxh∂ht=∂y^t∂Lt∗∂ht∂y^t∗∂Whh∂ht
h
t
h_t
ht作为一个复合函数,不止能以
W
x
h
W_{xh}
Wxh和
W
h
h
W_{hh}
Whh为自变量,还能以上层的隐藏状态
h
t
−
1
h_{t-1}
ht−1作为自变量,而
h
t
−
1
h_{t-1}
ht−1本身又是以
W
x
h
W_{xh}
Wxh和
W
h
h
W_{hh}
Whh为自变量的函数:
L
t
=
L
(
y
^
t
,
y
t
)
=
L
(
W
h
y
h
t
,
y
t
)
=
L
(
W
h
y
σ
(
W
x
h
X
t
+
W
h
h
h
t
−
1
)
,
y
t
)
=
L
(
W
h
y
σ
(
W
x
h
X
t
+
W
h
h
σ
(
W
x
h
X
t
+
W
h
h
h
t
−
2
)
,
y
t
)
\begin{align*} L_{t} &= L(\mathbf{\hat{y}}_{t}, \mathbf{y}_{t}) \\ \\ &=L(\mathbf{W}_{hy} \mathbf{h}_{t}, \mathbf{y}_{t}) \\ \\ &= L(\mathbf{W}_{hy} \sigma(\mathbf{W}_{xh} \mathbf{X}_{t} + \mathbf{W}_{hh} \color{red}{\mathbf{h}_{t-1}}), \mathbf{y}_{t}) \\ \\ &= L(\mathbf{W}_{hy} \sigma(\mathbf{W}_{xh} \mathbf{X}_{t} + \mathbf{W}_{hh} \color{red}{\sigma(\mathbf{W}_{xh} \mathbf{X}_{t} + \mathbf{W}_{hh} \mathbf{h}_{t-2})},\mathbf{y}_{t}) \end{align*}
Lt=L(y^t,yt)=L(Whyht,yt)=L(Whyσ(WxhXt+Whhht−1),yt)=L(Whyσ(WxhXt+Whhσ(WxhXt+Whhht−2),yt)
甚至,我们还可以将
h
t
−
1
h_{t-1}
ht−1继续拆解为
σ
(
W
x
h
X
t
−
1
+
W
h
h
h
t
−
2
)
\sigma(\mathbf{W}_{xh} \mathbf{X}_{t-1} + \mathbf{W}_{hh} \mathbf{h}_{t-2})
σ(WxhXt−1+Whhht−2),还可以将
h
t
−
2
h_{t-2}
ht−2继续拆解为
σ
(
W
x
h
X
t
−
2
+
W
h
h
h
t
−
3
)
\sigma(\mathbf{W}_{xh} \mathbf{X}_{t-2} + \mathbf{W}_{hh} \mathbf{h}_{t-3})
σ(WxhXt−2+Whhht−3),我们可以将嵌套函数无止尽地拆解下去,直到拆到
h
1
=
σ
(
W
x
h
X
1
+
W
h
h
h
0
)
\mathbf{h}_1 = \sigma(\mathbf{W}_{xh} \mathbf{X}_1 + \mathbf{W}_{hh} \mathbf{h}_0)
h1=σ(WxhX1+Whhh0)为止。在这个过程中,只要拆解足够多,我们可以从
L
t
L_{t}
Lt求解出t个针对和
W
h
h
W_{hh}
Whh的导数。因此惊人的事实是,在时间步t上,我们可以计算t个用于迭代
W
x
h
W_{xh}
Wxh和
W
h
h
W_{hh}
Whh的梯度!
∂
L
t
∂
W
h
h
=
∂
L
t
∂
y
^
t
∗
∂
y
^
t
∂
h
t
∗
∂
h
t
∂
h
t
−
1
∗
∂
h
t
−
1
∂
h
t
−
2
∗
.
.
.
∗
∂
h
2
∂
h
1
∗
∂
h
1
∂
W
h
h
\begin{align*} \ \frac{\partial L_{t}}{\partial W_{hh}} &= \frac{\partial L_{t}}{\partial \hat{y}_{t}} * \frac{\partial \hat{y}_{t}}{\partial h_{t}} * \frac{\partial h_{t}}{\partial h_{t-1}} * \frac{\partial h_{t-1}}{\partial h_{t-2}} * \ \ ... \ \ * \frac{\partial h_2}{\partial h_1} * \frac{\partial h_1}{\partial W_{hh}} \\ \\ \end{align*}
∂Whh∂Lt=∂y^t∂Lt∗∂ht∂y^t∗∂ht−1∂ht∗∂ht−2∂ht−1∗ ... ∗∂h1∂h2∗∂Whh∂h1
此时在这个公式中,许多偏导数的求解就变得非常简单,例如:
∵
y
^
t
=
W
h
y
h
t
,
∴
∂
y
^
t
∂
h
t
=
W
h
y
∵
h
t
=
W
x
h
X
t
+
W
h
h
h
t
−
1
,
∴
∂
h
t
∂
h
t
−
1
=
W
h
h
∵
h
t
−
1
=
W
x
h
X
t
−
1
+
W
h
h
h
t
−
2
,
∴
∂
h
t
−
1
∂
h
t
−
2
=
W
h
h
⋮
∵
h
2
=
W
x
h
X
2
+
W
h
h
h
1
,
∴
∂
h
2
∂
h
1
=
W
h
h
∵
h
1
=
W
x
h
X
1
+
W
h
h
h
0
,
∴
∂
h
1
∂
W
h
h
=
h
0
\begin{align*} &\because {\hat{y}}_{t} = W_{hy} h_{t},\ \ \therefore \frac{\partial \hat{y}_{t}}{\partial h_{t}} = {W}_{hy} \\ \\ &\because {h}_{t} = {W}_{xh} {X}_{t} + {W}_{hh} {h}_{t-1}, \ \ \therefore \frac{\partial h_{t}}{\partial h_{t-1}} = {W}_{hh} \\ \\ &\because {h}_{t-1} = {W}_{xh} {X}_{t-1} + {W}_{hh} {h}_{t-2}, \ \ \therefore \frac{\partial h_{t-1}}{\partial h_{t-2}} = {W}_{hh} \\ \\ & \vdots \\ \\ &\because {h}_2 = {W}_{xh} {X}_{2} + {W}_{hh} {h}_{1}, \ \ \therefore \frac{\partial h_2}{\partial h_1} = {W}_{hh} \\ \\ &\because {h}_1 = {W}_{xh} {X}_{1} + {W}_{hh} {h}_{0}, \ \ \therefore \frac{\partial h_1}{\partial {W}_{hh}} = {h}_{0} \end{align*}
∵y^t=Whyht, ∴∂ht∂y^t=Why∵ht=WxhXt+Whhht−1, ∴∂ht−1∂ht=Whh∵ht−1=WxhXt−1+Whhht−2, ∴∂ht−2∂ht−1=Whh⋮∵h2=WxhX2+Whhh1, ∴∂h1∂h2=Whh∵h1=WxhX1+Whhh0, ∴∂Whh∂h1=h0
所以最终的梯度表达式为:
∂
L
t
∂
W
h
h
=
∂
L
t
∂
y
^
t
∗
∂
y
^
t
∂
h
t
∗
∂
h
t
∂
h
t
−
1
∗
∂
h
t
−
1
∂
h
t
−
2
∗
.
.
.
∗
∂
h
2
∂
h
1
∗
∂
h
1
∂
W
h
h
=
∂
L
t
∂
y
^
t
∗
W
h
y
∗
W
h
h
∗
W
h
h
∗
.
.
.
∗
W
h
h
∗
h
0
=
∂
L
t
∂
y
^
t
∗
W
h
y
∗
(
W
h
h
)
t
−
1
∗
h
0
\begin{align*} \ \frac{\partial L_{t}}{\partial W_{hh}} &= \frac{\partial L_{t}}{\partial \hat{y}_{t}} * \frac{\partial \hat{y}_{t}}{\partial h_{t}} * \frac{\partial h_{t}}{\partial h_{t-1}} * \frac{\partial h_{t-1}}{\partial h_{t-2}} * \ \ ... \ \ * \frac{\partial h_2}{\partial h_1} * \frac{\partial h_1}{\partial W_{hh}} \\ \\ &= \frac{\partial L_{t}}{\partial \hat{y}_{t}} * W_{hy} * W_{hh} * W_{hh} * \ \ ... \ \ * W_{hh} * h_0 \\ \\ &= \frac{\partial L_{t}}{\partial \hat{y}_{t}} * W_{hy} * (W_{hh})^{t-1}* h_0 \\ \\ \end{align*}
∂Whh∂Lt=∂y^t∂Lt∗∂ht∂y^t∗∂ht−1∂ht∗∂ht−2∂ht−1∗ ... ∗∂h1∂h2∗∂Whh∂h1=∂y^t∂Lt∗Why∗Whh∗Whh∗ ... ∗Whh∗h0=∂y^t∂Lt∗Why∗(Whh)t−1∗h0
不难发现,在这个梯度表达式中出现了
(
W
h
h
)
t
−
1
(W_{hh})^{t-1}
(Whh)t−1这样的高次项,这就是循环神经网络非常容易梯度爆炸和梯度消失的根源所在——假设
W
h
h
W_{hh}
Whh是一个小于1的值,那
(
W
h
h
)
t
−
1
(W_{hh})^{t-1}
(Whh)t−1将会非常接近于0,从而导致梯度消失;假设
W
h
h
W_{hh}
Whh大于1,那
(
W
h
h
)
t
−
1
(W_{hh})^{t-1}
(Whh)t−1将会接近无穷大,从而引发梯度爆炸,其中梯度消失发生的可能性又远远高于梯度爆炸。
在深度神经网络中,在应用链式法则后,我们也会面临复合函数梯度连乘的问题,但由于普通神经网络中并不存在“权值共享”的现象,因此每个偏导数的表达式求解出的值大多是不一致的,在连乘的时候有的偏导数值较大、有的偏导数值较小,相比之下就不那么容易发生梯度爆炸或梯度消失问题的问题。
4.3 LSTM解决梯度消失和梯度爆炸问题吗?
或许很早就听说过,LSTM能够很好地解决RNN的梯度消失/梯度爆炸问题,甚至可能认为LSTM是为了解决梯度消失和梯度爆炸而诞生的,但我们在进行深度原理研究的过程中却发现并非如此。
∂
L
t
∂
W
h
f
=
∂
L
t
∂
y
^
t
∗
∂
y
^
t
∂
h
t
∗
∂
h
t
∂
C
t
∗
∂
C
t
∂
f
t
∗
∂
f
t
∂
h
t
−
1
∗
∂
h
t
−
1
∂
C
t
−
1
.
.
.
∗
∂
f
1
∂
W
h
f
=
∂
L
t
∂
y
^
t
∂
y
^
t
∂
h
t
∗
∂
h
t
∂
C
t
∗
[
∂
C
t
∂
C
t
−
1
∗
∂
C
t
−
1
∂
C
t
−
2
.
.
.
∗
∂
C
2
∂
C
1
]
∗
∂
C
1
∂
f
1
∗
∂
f
1
∂
w
h
f
=
∂
L
t
∂
y
^
t
∂
y
^
t
∂
h
t
∗
∂
h
t
∂
C
t
∗
[
f
t
∗
f
t
−
1
∗
f
t
−
2
.
.
.
∗
f
1
]
∗
∂
C
1
∂
f
1
∗
∂
f
1
∂
w
h
f
\begin{align*} \ \frac{\partial L_{t}}{\partial W_{hf}} &= \frac{\partial L_{t}}{\partial \hat{y}_{t}} * \frac{\partial \hat{y}_{t}}{\partial h_{t}} * \frac{\partial h_{t}}{\partial C_{t}} * \frac{\partial C_{t}}{\partial f_{t}} * \frac{\partial f_{t}}{\partial h_{t-1}}* \frac{\partial h_{t-1}}{\partial C_{t-1}}\ \ ... \ \ * \frac{\partial f_1}{\partial W_{hf}} \\ \\ &= \frac{\partial L_{t}}{\partial \hat{y}_{t}} \frac{\partial \hat{y}_{t}}{\partial h_{t}} * \frac{\partial h_{t}}{\partial C_{t}} * [ \frac{\partial C_t}{\partial C_{t-1}} * \frac{\partial C_{t-1}}{\partial C_{t-2}} \ \ ... \ \ * \frac{\partial C_2}{\partial C_{1}}] * \frac{\partial C_1}{\partial f_1} * \frac{\partial f_1}{\partial w_{hf}} \\ \\ &= \frac{\partial L_{t}}{\partial \hat{y}_{t}} \frac{\partial \hat{y}_{t}}{\partial h_{t}} * \frac{\partial h_{t}}{\partial C_{t}} * [f_t * f_{t-1} * f_{t-2} ... * f_1] * \frac{\partial C_1}{\partial f_1} * \frac{\partial f_1}{\partial w_{hf}} \\ \\ \end{align*}
∂Whf∂Lt=∂y^t∂Lt∗∂ht∂y^t∗∂Ct∂ht∗∂ft∂Ct∗∂ht−1∂ft∗∂Ct−1∂ht−1 ... ∗∂Whf∂f1=∂y^t∂Lt∂ht∂y^t∗∂Ct∂ht∗[∂Ct−1∂Ct∗∂Ct−2∂Ct−1 ... ∗∂C1∂C2]∗∂f1∂C1∗∂whf∂f1=∂y^t∂Lt∂ht∂y^t∗∂Ct∂ht∗[ft∗ft−1∗ft−2...∗f1]∗∂f1∂C1∗∂whf∂f1
通过避开共享权重的相乘,LSTM将循环网络梯度爆炸和梯度消失的危险性降低到了一般神经网络的水平。由于
f
t
f_{t}
ft在0~1之间,因此就意味着梯度爆炸的风险将会很小,至于会不会梯度消失,取决于是否接近于1。如果当下时刻的长期记忆比较依赖于历史信息,那么就会接近于1,这时候历史的梯度信息也正好不容易消失;如果很接近于0,那么就说明当下的长期记忆不依赖于历史信息,这时候就算梯度消失也无妨了。
f
t
f_{t}
ft在0~1之间这个特性决定了它梯度爆炸的风险很小,同时
f
t
f_{t}
ft表明了模型对历史信息的依赖性,也正好是历史梯度的保留程度,两者相互自洽,所以LSTM也能较好地缓解梯度消失问题。因此,LSTM同时较好地缓解了梯度消失/爆炸问题。