时间序列预测算法---LSTM
文章目录
- 一、前言
- 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]) #第8x=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, Inputmodel = 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_sizeself.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和c0h0 = 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和cnoutput, (_, _) = 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_sizeself.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同时较好地缓解了梯度消失/爆炸问题。
相关文章:
时间序列预测算法---LSTM
文章目录 一、前言1.1、深度学习时间序列一般是几维数据?每个维度的名字是什么?通常代表什么含义?1.2、为什么机器学习/深度学习算法无法处理时间序列数据?1.3、RNN(循环神经网络)处理时间序列数据的思路?1.4、RNN存在哪些问题?…...
【QT】:QT图形化界面概述
Qt背景介绍 1.1 什么是Qt Qt 是⼀个跨平台的C图形⽤⼾界⾯应⽤程序框架。它为应⽤程序开发者提供了建⽴艺术级图形 界⾯所需的所有功能。它是完全⾯向对象的,很容易扩展。Qt为开发者提供了⼀种基于组件的开发模 式,开发者可以通过简单的拖拽和组合来实现…...
[论文笔记]Representation Learning with Contrastive Predictive Coding
引言 今天带来论文 Representation Learning with Contrastive Predictive Coding的笔记。 提出了一种通用的无监督学习方法从高维数据中提取有用表示,称为对比预测编码(Contrastive Predictive Coding,CPC)。使用了一种概率对比损失, 通过使用负采样使…...
Redis相关
Redis相关 什么是redis?redis可以干什么? Redis是一个c语言编写的nosql数据库(不仅仅是sql,泛指非关系型数据库,一般把非关系型数据库称为nosql数据库),数据在内存中以键值对的形式存储,读写速度快,提供数据持久化方式. 常常被广泛应用到做缓存 Redis使用场景 1.缓存 2…...
【优选算法】Binary-Blade:二分查找的算法刃(上)
文章目录 1.概念解析2.二分查找的简单模版3.二分查找的进阶模版4.x的平方根5.搜索插入位置希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力! 本篇是优选算法之二分查找算法,该算法是一种高效的在有序数组中查找特定元素的搜索算法 1.概…...
Docker--Docker Network(网络)
Docker Network(网络)是Docker容器之间和容器与外部网络之间的通信和连接的一种机制。以下是对Docker Network的详细解释: 一、Docker网络的重要性 Docker容器网络是为应用程序所创造的虚拟环境的一部分,它能让应用从宿主机操作…...
转化率是衡量网页设计的一个重要指标,请问如何做?
AARRR是互联网产品运营中一个非常重要的模型,这些模型的每一个步骤都涉及到转化率问题,那么AARRR是是什么呢?转化漏斗是什么吗?转化率为什么重要?设计师在做网页设计的时候,如何提升转化率呢?本…...
运维工具之syncthing工具的安装和使用
一、syncthing工具简介 Syncthing是一款开源的文件同步工具,采用Go语言编写。它支持在多个操作系统上运行,包括Windows、macOS和Linux,以及BSD、Solaris和Android等。以下是对这款软件的详细介绍,主要功能: 实时文件同…...
国产数据库-崖山使用介绍
本文档基于崖山数据库23.3 个人版本,单机(主备)部署模式的情况下的使用介绍。 数据库实例状态: NOMOUNT:仅读取参数文件,不加载数据库 MOUNT:读取控制文件,加载数据库ÿ…...
primevue的<Menu>组件
1.使用场景 2.代码 1.给你的menu组件起个引用名 2.<Menu>组件需要一个MenuItem[] 3.你要知道MenuItem[ ]的特殊的数据格式,就像TreeNode[ ]一样,数据格式不对是不渲染的。。。。 常用的属性就这几种,js语言和java不一样,J…...
【玩转23种Java设计模式】行为型模式篇:备忘录模式
软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 汇总目录链接&…...
便捷饭店点餐小程序的设计与实现ssm+论文源码调试讲解
第4章 系统设计 一个成功设计的系统在内容上必定是丰富的,在系统外观或系统功能上必定是对用户友好的。所以为了提升系统的价值,吸引更多的访问者访问系统,以及让来访用户可以花费更多时间停留在系统上,则表明该系统设计得比较专…...
微信小程序Uniapp
使用命令行创建项目(vuets) npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project然后用HBX打开项目 再安装依赖 npm i 再运行开发版本,生成dist目录 pnpm dev:mp-weixin 注意要设置APPid 再用微信小程序打开...
Android GameActivity(NativeActivity)读写文件
最近研究native android相关内容,其中最棘手的就是文件读写问题,最主要的是相关的文档很少。这里写下我所知道的方法。 由于本人使用的是Android14[arm64-v8a]版本的设备,能访问的路径相当有限,如果想要访问更多的路径,就不得不申…...
《计算机网络A》单选题-复习题库解析-2
目录 51、下列关于以太网网卡地址特点的说法中,不正确的是( )。 52、当一个Web Browser向一个使用标准服务器端口的Web Server提出请求时,那么在服务返回的响应包中,所使用的源端口是( ࿰…...
GPU 进阶笔记(二):华为昇腾 910B GPU
大家读完觉得有意义记得关注和点赞!!! 1 术语 1.1 与 NVIDIA 术语对应关系1.2 缩写2 产品与机器 2.1 GPU 产品2.2 训练机器 底座 CPU功耗操作系统2.3 性能3 实探:鲲鹏底座 8*910B GPU 主机 3.1 CPU3.2 网卡和网络3.3 GPU 信息 3.3…...
如何利用 ClickHouse 实现高级分析:MySQL 到 ClickHouse 实时数据同步指南
在数据驱动的时代,企业必须依靠先进的数据分析能力来提升竞争力。随着数据量的激增和业务需求的复杂化,传统的关系型数据库已经无法满足高效处理和实时分析的需求。ClickHouse 作为一款高性能的列式数据库,凭借其卓越的查询性能和可扩展性&am…...
Python读取TIF文件
在Python中,逐帧读取TIFF文件(尤其是多页TIFF文件)可以使用tifffile库或Pillow库。以下是两种方法的示例: 方法 1:使用 tifffile 逐帧读取 tifffile 是一个专门用于处理TIFF文件的库,支持多页TIFF文件的逐…...
vue3+ts+element-plus 表单el-form取消回车默认提交
问题描述:在表单el-form中的el-input中按回车后,页面会刷新,url也会改变, 回车前: 回车后: 相关代码: 解决方法1:在 el-form 上阻止默认的 submit 事件,增加 submit.pre…...
面试经典150题——滑动窗口
文章目录 1、长度最小的子数组1.1 题目链接1.2 题目描述1.3 解题代码1.4 解题思路 2、无重复字符的最长子串2.1 题目链接2.2 题目描述2.3 解题代码2.4 解题思路 3、串联所有单词的子串3.1 题目链接3.2 题目描述3.3 解题代码3.4 解题思路 4、最小覆盖子串4.1 题目链接4.2 题目描…...
目标检测之DINO详解
相关链接 论文:[2203.03605] DINO: DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detectionhttps://arxiv.org/abs/2203.03605 代码:...
Linux指令
1. 将一个文件夹中的前5000张图片移动到另一个文件夹 可以使用 find 和 mv 命令来实现将一个文件夹 folder1 中的前 5000 张 jpg 图片移动到另一个文件夹 folder2。下面是具体的步骤: 首先,确保 folder2 存在。如果不存在,可以使用 mkdir 命…...
groovy:多线程 简单示例
在Groovy中,多线程编程与Java非常相似,因为Groovy运行在Java虚拟机(JVM)上,并且可以利用Java的所有并发工具。以下是一些在Groovy中实现多线程编程的方法: class MyThread extends Thread {Overridevoid…...
硬件产品:做产品,不仅仅是产品思维
目录 前言 1. 产品思维阶段 2. 流量思维阶段 3. 用户思维阶段 作者简介 前言 从思维层面来看, 做产品会经历三个阶段,分别是: 1. 产品思维阶段; 2. 流量思维阶段; 3. 用户思维阶段。 如果不理解这三个思维…...
【小程序开发】解决 HBuilder X 提示“本项目类型无法运行到小程序模拟器”
今天在hbuilder引入一个项目时,准备将该项目在微信开发者工具上运行时,发现提示“本项目类型”,如何解决这个问题? 问题如下: 第一:检查一下文件夹是否为一级文件夹(如图) 不要有多个…...
RuoYi-Vue从http升级为https(Jar+Nginx)
一、前提条件 1.已通过数字证书管理服务控制台签发证书。 2.SSL证书绑定的域名已完成DNS解析,即域名与主机IP地址相互映射。 附:阿里云网站运维检测平台 3.已在Web服务器开放443端口(HTTPS通信的标准端口)。 如果使用的是阿里云ECS服务器,请确保已经在安全组规则入方向…...
探索 Yocto-Meta-OpenEuler:嵌入式开发的强大基石
title: 探索 Yocto-Meta-OpenEuler:嵌入式开发的强大基石 date: ‘2024-11-19’ category: blog tags: Yocto-Meta-OpenEuler嵌入式系统开源项目定制化开发 sig: EmbeddedTech archives: ‘2024-12’ author:way_back summary: Yocto-Meta-OpenEuler 为嵌入式系统开…...
leetcode 3219. 切蛋糕的最小总开销 II
题目:3219. 切蛋糕的最小总开销 II - 力扣(LeetCode) 排序贪心。 开销越大的越早切。 注意m或n为1的情况。 class Solution { public:long long minimumCost(int m, int n, vector<int>& horizontalCut, vector<int>&…...
UniApp 打开文件工具,获取文件类型,判断文件类型
注意:以下代码使用 typeScript 开发,如果想在 js 中使用,可参考 npm 已经发布的包:https://www.npmjs.com/package/uni-easy-file NPM 使用 如果想直接在 npm 项目中使用可以直接执行以下命令 npm i uni-easy-file然后直接使用 …...
webpack打包node后端项目
webpack打包后端项目 后端项目写好了,感觉也可以打包一下,然后就想到了用webpack试试 先要下载webpack和webpack-cli npm install webpack webpack-cli然后创建webpack配置文件webpack.config.js 核心配置为entry、output、target 但是因为咱们是后…...
《学习之道》
《学习之道》主要讲述了以下内容: 学习的原理 大脑的两种认知模式:介绍了专注模式和发散模式。专注模式适合集中精力解决具体问题、进行深度理解和记忆推理,但长时间使用易疲惫和陷入思维定式;发散模式则让大脑在更广泛的认知网…...
随笔 | 写在2024的最后一天
. 前言 转眼又到了一年的末端。过去这一年,和前些年有些不同,变化巨大,感触良多。多到一时竟不知从何开始写。今天这篇随笔,因为时间有限,可能文法也会有些凌乱,就是想到哪里写到哪里,如果未来…...
对三层架构的梳理(Controller、Service、Dao)
项目结构如下: src├── main│ └── java│ └── com│ └── example│ └── demo│ ├── controller│ │ └── UserController.java│ ├…...
常用的公共 NTP(网络时间协议)服务器
公共 NTP 服务列表 以下是一些常用的公共 NTP(网络时间协议)服务器,供您参考: 中国地区公共 NTP 服务器 国家授时中心 NTP 服务器:ntp.ntsc.ac.cn中国 NTP 快速授时服务:cn.ntp.org.cn阿里云公共 NTP 服务…...
SimForge HSF 案例分享|复杂仿真应用定制——UAVSim无人机仿真APP(技术篇)
导读 「神工坊」核心技术——「SimForge HSF高性能数值模拟引擎」支持工程计算应用的快速开发、自动并行,以及多域耦合、AI求解加速,目前已实现航发整机数值模拟等多个系统级高保真数值模拟应用落地,支持10亿阶、100w核心量级的高效求解。其低…...
ROS2+OpenCV综合应用--10. AprilTag标签码追踪
1. 简介 apriltag标签码追踪是在apriltag标签码识别的基础上,增加了小车摄像头云台运动的功能,摄像头会保持标签码在视觉中间而运动,根据这一特性,从而实现标签码追踪功能。 2. 启动 2.1 程序启动前的准备 本次apriltag标签码使…...
Qanything 2.0源码解析系列6 PDF解析逻辑
Qanything 2.0源码解析系列6: PDF解析逻辑 type: Post status: Published date: 2024/12/04 summary: 深入剖析Qanything是如何拆解PDF的,核心是pdf转markdown category: 技术分享 原文:www.feifeixu.top 😀 前言: 在前面的文章中探究了图片是怎么进行解析的,这篇文章对…...
IDEA修改编译版本
目录 一、序言 二、修改maven配置 1.修改 2.代码 三、pom文件配置 1.修改 2.代码 3.问题 一、序言 有两种方法可以帮助大家解决IDEA每次刷新maven的pom配置时,会发生发行源版本不正常的报错。个人推荐第二种,原因:第二种你刷新maven后…...
Python 向量检索库Faiss使用
Faiss(Facebook AI Similarity Search)是一个由 Facebook AI Research 开发的库,它专门用于高效地搜索和聚类大量向量。Faiss 能够在几毫秒内搜索数亿个向量,这使得它非常适合于实现近似最近邻(ANN)搜索&am…...
LunarVim安装
LunarVim以其丰富的功能和灵活的定制性,迅速在Nvim用户中流行开来。它不仅提供了一套完善的默认配置,还允许用户根据自己的需求进行深度定制。无论是自动补全、内置终端、文件浏览器,还是模糊查找、LSP支持、代码检测、格式化和调试ÿ…...
机器学习随机森林回归时间序列预模型中时间滑动窗口作用以及参数设置
一、时间序列模型中时间滑动窗口作用 在时间序列模型中,时间滑动窗口(Sliding Window)起到了至关重要的作用。它是一种常见且有效的数据表示技术,通过将时间序列数据分割成多个固定大小的窗口,来捕捉和分析数据中的模式…...
【ArcGIS技巧】如何制作轨迹动画
轨迹是日常生活与工作经常要用到的,如跑步轨迹、自驾路线,考察轨迹等。地图根据路线生成轨迹也很好玩,今天小编就带大家用arcmap来实现这一功能,让你的制图动起来。 1、数据准备 在开始制作轨迹动画之前,准备一张影像…...
使用 Docker 搭建 Hadoop 集群
1.1. 启用 WSL 与虚拟机平台 1.1.1. 启用功能 启用 WSL并使用 Moba 连接-CSDN博客 1.2 安装 Docker Desktop 最新版本链接:Docker Desktop: The #1 Containerization Tool for Developers | Docker 指定版本链接:Docker Desktop release notes | Do…...
Bash 中的 2>1 | tee 命令详解
Bash 中的 2>&1 | tee 命令详解 在 Linux 和 Unix 系统中,命令行提供了强大的输出控制功能,能够灵活地处理标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。本文将详…...
hpcrunner
title: 探索 Hpcrunner:高性能计算的得力助手 date: ‘2024-12-31’ category: blog tags: Hpcrunner高性能计算任务调度资源优化 sig: HPC archives: ‘2024-12’ author:way_back summary: Hpcrunner 作为高性能计算领域的一款实用工具,专注于优化任务…...
关于 PPPOE技术的详细解释
PPPoE(以太网点对点协议)是一种网络协议,它通过光纤将点对点协议(PPP)封装以实现宽带接入点。PPPoE主要用于ADSL和光纤等宽带接入技术中,它允许多个用户共享同一个交换机连接,同时为每个用户提供…...
java下载文件流,不生成中间文件。
java下载文件流,不生成中间文件。 代码设计:代码实现 代码设计: 从前端获取的数据经过后端加工后,生成文件流,并返回前端,(不生成中间文件,注意内存,记得关闭流…...
Android:bug记录(简单)
1、theme 冲突 问题: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.demokotlinapplication/com.example.demokotlinapplication.MainActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (…...
【模块系列】STM321.69TFT屏幕
前言 在翻翻自己的器件盒的时候,发现这块好久之前买的TFT屏了,想起还没有用STM32点亮过,手头上正好有立创的梁山派STM32F4,就试着按照网上的文章教程顺便移植个LVGL看看,然后就有了就本文。 代码工程命名的是LvglDemo&…...
机器学习详解(11):分类任务的模型评估标准
模型评估是利用不同的评估指标来了解机器学习模型的性能,以及其优势和劣势的过程。评估对于确保机器学习模型的可靠性、泛化能力以及在新数据上的准确预测能力至关重要。 文章目录 1 介绍2 评估准则3 分类指标3.1 准确率 (Accuracy)3.2 精确率 (Precision)3.3 召回率…...