当前位置: 首页 > news >正文

时间序列预测算法---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 案例
  • 四、拓展思考

时间序列相关参考文章
时间序列预测算法—ARIMA
时间序列预测算法—Prophet
时间序列预测算法—LSTM
长时间序列预测算法—Informer
时间序列分类任务—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} Ct1短期信息 h t − 1 h_{t-1} ht1以及当前时间步上输入的新信息 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} Ct1乘以一个[0,1]之间的比例,以此筛选掉部分旧信息。假设遗忘门令 C t − 1 C_{t-1} Ct1乘以0.7,就是说遗忘门决定了要保留70%的历史信息,遗忘30%的历史信息,这30%的信息空间就可以留给全新的信息来使用。核心就是计算 f t f_t ft 求得比例。
在这里插入图片描述
  遗忘门会参考当前时间步的信息 X t X_t Xt与上一个时间步的短时信息 h t − 1 h_{t-1} ht1来计算该比例,其中σ是sigmoid函数, W f W_f Wf是动态影响最终权重大小的参数, f t f_t ft就是[0,1]之间的、用于影响 C t − 1 C_{t-1} Ct1的比例
  在LSTM的设计逻辑之中,考虑 X t X_t Xt h t − 1 h_{t-1} ht1实际是在考虑离当前时间步最近的上下文信息,而权重 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} ht1)以及参数 W t W_t Wt来筛选新信息的比例 i t i_t it。最后将二者相乘,并加入到长期记忆C当中。
在这里插入图片描述
  相比RNN的数据输入过程,LSTM的输入过程灵活了非常多,在输入门当中,不仅对输入数据加上了一个比例it,还分别使用了两个受损失函数影响的权重Wt和Wc来控制新信息聚合和比例计算的流程。在这一比例和两大参数的作用下,输入数据可以被高度灵活地调节,以便满足最佳的损失函数需求。
  更新细胞状态:当遗忘门决定了哪些信息要被遗忘,输入门决定了哪些信息要被加入到长期记忆后,就可以更新用于控制长期记忆的细胞状态了。如下图所示,上一个时间步的长期记忆 C t − 1 C_{t-1} Ct1将乘以遗忘门给出的比例 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} Ct1基础上直接迭代更新得到的,所以 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} ht1之间没有直接的迭代关系,虽然二者有一定的联系,但 h t − 1 h_{t-1} ht1不是构成ht的核心。在记忆细胞中, h t − 1 h_{t-1} ht1只是用来辅助C进行迭代的变量之一,而 h t h_t ht是为了在当前时间步上生成 y t y_t yt而计算出来的全新变量,影响ht具体取值的核心不是上个时间步的信息 h t − 1 h_{t-1} ht1,而是当前时间步上的输入信息和预测标签的需求,因此, 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} Ct1基础上加上新信息迭代更新得到的(这是说,我们可以写出一个形似 C t C_t Ct= w C t − 1 C_{t-1} Ct1+b的公式),所以 C t C_t Ct整合了[1,t]所有时间步的历史信息,并负责将这些信息不断传递下去。
在这里插入图片描述
   h t h_t ht h t − 1 h_{t-1} ht1之间没有直接的选代关系,虽然二者有一定的联系,但 h t − 1 h_{t-1} ht1不是构成ht的核心。在记忆细胞中, h t − 1 h_{t-1} ht1只是用来辅助C(长期记忆)进行迭代的变量之一,影响 h t h_t ht具体取值的核心不是上个时间步的信息 h t − 1 h_{t-1} ht1,而是当前时间步上的输入信息预测标签的需求,因此 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+Whhht1)=σ(WxhXt+Whhσ(WxhXt1+Whhht2))=Whyht=L(y^tyt)
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+Whhht1),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*}  WhyLt WxhLt WhhLt=y^tLtWhyy^t=y^tLthty^tWxhht=y^tLthty^tWhhht
h t h_t ht作为一个复合函数,不止能以 W x h W_{xh} Wxh W h h W_{hh} Whh为自变量,还能以上层的隐藏状态 h t − 1 h_{t-1} ht1作为自变量,而 h t − 1 h_{t-1} ht1本身又是以 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+Whhht1),yt)=L(Whyσ(WxhXt+Whhσ(WxhXt+Whhht2),yt)
甚至,我们还可以将 h t − 1 h_{t-1} ht1继续拆解为 σ ( 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}) σ(WxhXt1+Whhht2),还可以将 h t − 2 h_{t-2} ht2继续拆解为 σ ( 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}) σ(WxhXt2+Whhht3),我们可以将嵌套函数无止尽地拆解下去,直到拆到 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*}  WhhLt=y^tLthty^tht1htht2ht1  ...  h1h2Whhh1
此时在这个公式中,许多偏导数的求解就变得非常简单,例如:
∵ 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,  hty^t=Whyht=WxhXt+Whhht1,  ht1ht=Whhht1=WxhXt1+Whhht2,  ht2ht1=Whhh2=WxhX2+Whhh1,  h1h2=Whhh1=WxhX1+Whhh0,  Whhh1=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*}  WhhLt=y^tLthty^tht1htht2ht1  ...  h1h2Whhh1=y^tLtWhyWhhWhh  ...  Whhh0=y^tLtWhy(Whh)t1h0
  不难发现,在这个梯度表达式中出现了 ( W h h ) t − 1 (W_{hh})^{t-1} (Whh)t1这样的高次项,这就是循环神经网络非常容易梯度爆炸和梯度消失的根源所在——假设 W h h W_{hh} Whh是一个小于1的值,那 ( W h h ) t − 1 (W_{hh})^{t-1} (Whh)t1将会非常接近于0,从而导致梯度消失;假设 W h h W_{hh} Whh大于1,那 ( W h h ) t − 1 (W_{hh})^{t-1} (Whh)t1将会接近无穷大,从而引发梯度爆炸,其中梯度消失发生的可能性又远远高于梯度爆炸。
  在深度神经网络中,在应用链式法则后,我们也会面临复合函数梯度连乘的问题,但由于普通神经网络中并不存在“权值共享”的现象,因此每个偏导数的表达式求解出的值大多是不一致的,在连乘的时候有的偏导数值较大、有的偏导数值较小,相比之下就不那么容易发生梯度爆炸或梯度消失问题的问题。

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*}  WhfLt=y^tLthty^tCthtftCtht1ftCt1ht1  ...  Whff1=y^tLthty^tCtht[Ct1CtCt2Ct1  ...  C1C2]f1C1whff1=y^tLthty^tCtht[ftft1ft2...f1]f1C1whff1
   通过避开共享权重的相乘,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存在哪些问题? 二、…...

二十三种设计模式-建造者模式

建造者模式(Builder Pattern)是一种创建型设计模式,它提供了一种分步骤构建复杂对象的方法。这种模式允许你通过相同的创建过程构建不同的表示。建造者模式将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的对象…...

MarkDown 的 mermaid gantt(甘特图)、mermaid sequenceDiagram (流程图) 语法解析和应用

简简单单 Online zuozuo: 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo : 文章目录 MarkDown 的 mermaid gantt、mermaid sequenceDiagram 语法解析和应用前言mermaid gan…...

git submodule的使用:将别人的git仓库作为自己的子仓库

git的基本操作在该篇中展示:git的基本操作在日常开发中,我们经常会碰到需要将别人的仓库作为自己的子仓库来进行开发。下面将介绍具体将如何操作。 1、添加Submodule至自己的git仓库 1.1、创建自己的Git仓库 (1)在github中创建自…...

Springboot 下载附件

GetMapping("/download") public void download(RequestParam String fileId, HttpServletResponse response) throws IOException {// 查询文件信息SysFileEntity sysFileEntity fileService.queryFileById(fileId);response.setContentType("application/oct…...

MySQL 延迟复制:确保数据安全与系统稳定的秘诀

MySQL 延迟复制:确保数据安全与系统稳定的秘诀 在 MySQL 主从复制架构中,数据的同步通常是实时的。然而,在一些特定场景下,我们可能不希望从库立刻同步主库的所有更新。特别是在高风险操作或者主库出现故障时,实时复制…...

ELK 使用教程采集系统日志 Elasticsearch、Logstash、Kibana

前言 你知道对于一个系统的上线考察,必备的几样东西是什么吗?其实这也是面试中考察求职者,是否真的做过系统开发和上线的必备问题。包括:服务治理(熔断/限流) (opens new window)、监控 (opens new window)和日志,如果…...

python实现自动登录12306抢票 -- selenium

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 python实现自动登录12306抢票 -- selenium 前言其实网上也出现了很多12306的代码,但是都不是最新的,我也是从网上找别人的帖子,看B站视频&…...

使用Diffusion Models进行图像超分辩重建

Diffusion Models专栏文章汇总:入门与实战 前言:图像超分辨率重建是一个经典CV任务,其实LR(低分辨率)和 HR(高分辨率)图像仅在高频细节上存在差异。通过添加适当的噪声,LR 图像将变得与其 HR 对应图像无法区分。这篇博客介绍一种方式巧妙利用这个规律使用Diffusion Mod…...

吾杯网络安全技能大赛——Misc方向WP

吾杯网络安全技能大赛——Misc方向WP Sign 题目介绍: 浅浅签个到吧 解题过程: 57754375707B64663335376434372D333163622D343261382D616130632D3634333036333464646634617D 直接使用赛博橱子秒了 flag为 WuCup{df357d47-31cb-42a8-aa0c-6430634ddf4a} 原神启动…...

Web安全 - “Referrer Policy“ Security 头值不安全

文章目录 概述原因分析风险说明Referrer-Policy 头配置选项1. 不安全的策略no-referrer-when-downgradeunsafe-url 2. 安全的策略no-referreroriginorigin-when-cross-originsame-originstrict-originstrict-origin-when-cross-origin 推荐配置Nginx 配置示例 在 Nginx 中配置 …...

C#OPC(上)

OPC(OLE for Process Control),用于过程控制的OLE,是一个工业标准,管理这个标准的国际组织是OPC基金会,OPC基金会现有会员以超过220家。遍布全球,包括世界上所有主要的自动化控制系统、仪器仪表及过程控制系统的公司。基于微软的O…...

Imgui + Cmake + OpenGL + GLFW 隐藏控制台窗口

网上一般是VS&#xff0c;如何在属性里面把控制台改为窗口 本文&#xff0c;使用Clion Cmake&#xff0c;实现如何隐藏控制台窗口 环境&#xff1a;OpenGL3 GLFW 添加头文件#include <windows.h>把main函数入口改为int WinMain(HINSTANCE hInstance, HINSTANCE hPrevI…...

Spring Boot(七):Swagger 接口文档

1. Swagger 简介 1.1 Swagger 是什么&#xff1f; Swagger 是一款 RESTful 风格的接口文档在线自动生成 功能测试功能软件。Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。目标是使客户端和文件系统作为服务器以同样的…...

RabbitMQ - 4 ( 22000 字 RabbitMQ 入门级教程 )

一&#xff1a; RabbitMQ 高级特性 前面主要讲解了 RabbitMQ 的概念和应用。RabbitMQ 实现了 AMQP 0-9-1 规范&#xff0c;并在此基础上进行了多项扩展。在 RabbitMQ 官方网站中详细介绍了其特性&#xff0c;我们将其中一些重要且常用的特性挑选出来进行讲解。 1.1 消息确认 …...

印象笔记07——试一试PDF标注

印象笔记07——试一试PDF标注 [!CAUTION] 根据第六期&#xff0c;我再次查询了资料&#xff0c;印象笔记还是有一些可圈可点的功能的&#xff08;当然部分有平替&#xff09;&#xff0c;针对会员作用&#xff0c;开发使用场景虽然是逆向的&#xff0c;但我坚信这是一部分人的现…...

Vue3实战教程》24:Vue3自定义指令

如果您有疑问&#xff0c;请观看视频教程《Vue3实战教程》 自定义指令​ 介绍​ 除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外&#xff0c;Vue 还允许你注册自定义的指令 (Custom Directives)。 我们已经介绍了两种在 Vue 中重用代码的方式&#xff1a;组件和组…...

【2025优质学术推荐】征稿控制科学、仪器、智能系统、通信、计算机、电子信息、人工智能、大数据、机器学习、软件工程、网络安全方向

【2025优质学术推荐】征稿控制科学、仪器、智能系统、通信、计算机、电子信息、人工智能、大数据、机器学习、软件工程、网络安全方向 【2025优质学术推荐】征稿控制科学、仪器、智能系统、通信、计算机、电子信息、人工智能、大数据、机器学习、软件工程、网络安全方向 文章目…...

【ArcGIS Pro/GeoScene Pro】可视化时态数据

可视化过去二十年新西兰国际旅游业的发展变化 工程数据下载 ArcGIS Pro 快速入门指南—ArcGIS Pro | 文档 添加数据 数据为中国旅客数据 转置表字段 列数据转行数据...

Linux buildroot和ubuntu的异同点

Buildroot 和 Ubuntu 都是 Linux 系统的操作环境,但它们的设计理念和使用场景有很大的不同。 一、定义与目标 Buildroot Buildroot 是一个用于生成嵌入式 Linux 系统的工具集,专注于交叉编译和构建嵌入式设备的最小 Linux 环境。它的目标是为嵌入式系统提供定制化和优化的…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

使用SSE解决获取状态不一致问题

使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件&#xff0c;这个上传文件是整体功能的一部分&#xff0c;文件在上传的过程中…...

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…...

基于单片机的宠物屋智能系统设计与实现(论文+源码)

本设计基于单片机的宠物屋智能系统核心是实现对宠物生活环境及状态的智能管理。系统以单片机为中枢&#xff0c;连接红外测温传感器&#xff0c;可实时精准捕捉宠物体温变化&#xff0c;以便及时发现健康异常&#xff1b;水位检测传感器时刻监测饮用水余量&#xff0c;防止宠物…...