时间序列预测算法---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同时较好地缓解了梯度消失/爆炸问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/64928.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode题目(3)

目录 1.加一 2.二进制求和 3.x的平方根 4.爬楼梯 5.颜色分类 6.二叉树的中序遍历 1.加一 https://leetcode.cn/problems/plus-one/ class Solution { public:vector<int> plusOne(vector<int>& digits) {int n digits.size();for(int i n -1;i>0;-…

快速上手LangChain(三)构建检索增强生成(RAG)应用

文章目录 快速上手LangChain(三)构建检索增强生成(RAG)应用概述索引阿里嵌入模型 Embedding检索和生成RAG应用(demo:根据我的博客主页,分析一下我的技术栈)快速上手LangChain(三)构建检索增强生成(RAG)应用 langchain官方文档:https://python.langchain.ac.cn/do…

[cg] android studio 无法调试cpp问题

折腾了好久&#xff0c;native cpp库无法调试问题&#xff0c;原因 下面的Deploy 需要选Apk from app bundle!! 另外就是指定Debug type为Dual&#xff0c;并在Symbol Directories 指定native cpp的so路径 UE项目调试&#xff1a; 使用Android Studio调试虚幻引擎Android项目…

【Windows】powershell 设置执行策略(Execution Policy)禁止了脚本的运行

报错信息&#xff1a; 无法加载文件 C:\Users\11726\Documents\WindowsPowerShell\profile.ps1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参 阅 https:/go.microsoft.com/fwlink/?LinkID135170 中的 about_Execution_Policies。 所在位置 行:1 字符…

可编辑37页PPT |“数据湖”构建汽车集团数据中台

荐言分享&#xff1a;随着汽车行业智能化、网联化的快速发展&#xff0c;数据已成为车企经营决策、优化生产、整合供应链的核心资源。为了在激烈的市场竞争中占据先机&#xff0c;汽车集团亟需构建一个高效、可扩展的数据管理平台&#xff0c;以实现对海量数据的收集、存储、处…

【快速实践】类激活图(CAM,class activation map)可视化

类激活图可视化&#xff1a;有助于了解一张图像的哪一部分让卷积神经网络做出了最终的分类决策 对输入图像生成类激活热力图类激活热力图是与特定输出类别相关的二维分数网格&#xff1a;对任何输入图像的每个位置都要进行计算&#xff0c;它表示每个位置对该类别的重要程度 我…

ros2 py文件间函数调用

文章目录 写在前面的话生成python工程包命令运行python函数命令python工程包的目录结构目录结构&#xff08;细节&#xff09; 报错 1&#xff08; no module name ***&#xff09;错误示意 截图终端输出解决方法 报错 2&#xff08; AttributeError: *** object has no attrib…

Milvus×合邦电力:向量数据库如何提升15%电价预测精度

01. 全球能源市场化改革下的合邦电力 在全球能源转型和市场化改革的大背景下&#xff0c;电力交易市场正逐渐成为优化资源配置、提升系统效率的关键平台。电力交易通过市场化手段&#xff0c;促进了电力资源的有效分配&#xff0c;为电力行业的可持续发展提供了动力。 合邦电力…

OLED的显示

一、I2C I2C时序&#xff1a;时钟线SCL高电平下&#xff1a;SDA由高变低代表启动信号&#xff0c;开始发送数据&#xff1b;SCL高电平时&#xff0c;数据稳定&#xff0c;数据可以被读走&#xff0c;开始进行读操作&#xff0c;SCL低电平时&#xff0c;数据发生改变&#xff1…

VMware运维效率提升50%,RVTools管理更简单

RVTools 是一款专为 VMware 虚拟化环境量身打造的高效管理工具&#xff0c;基于 .NET 4.7.2 框架开发&#xff0c;并与 VMware vSphere Management SDK 8.0 和 CIS REST API 深度集成&#xff0c;能够全面呈现虚拟化平台的各项关键数据。该工具不仅能够详细列出虚拟机、CPU、内…

python +t kinter绘制彩虹和云朵

python t kinter绘制彩虹和云朵 彩虹&#xff0c;简称虹&#xff0c;是气象中的一种光学现象&#xff0c;当太阳光照射到半空中的水滴&#xff0c;光线被折射及反射&#xff0c;在天空上形成拱形的七彩光谱&#xff0c;由外圈至内圈呈红、橙、黄、绿、蓝、靛、紫七种颜色。事实…

Zabbix5.0版本(监控Nginx+PHP服务状态信息)

目录 1.监控Nginx服务状态信息 &#xff08;1&#xff09;通过Nginx监控模块&#xff0c;监控Nginx的7种状态 &#xff08;2&#xff09;开启Nginx状态模块 &#xff08;3&#xff09;配置监控项 &#xff08;4&#xff09;创建模板 &#xff08;5&#xff09;用默认键值…

Python入门教程 —— 字符串

字符串介绍 字符串可以理解为一段普通的文本内容,在python里,使用引号来表示一个字符串,不同的引号表示的效果会有区别。 字符串表示方式 a = "Im Tom" # 一对双引号 b = Tom said:"I am Tom" # 一对单引号c = Tom said:"I\m Tom" # 转义…

AcWing练习题:差

读取四个整数 A,B,C,D&#xff0c;并计算 (AB−CD)的值。 输入格式 输入共四行&#xff0c;第一行包含整数 A&#xff0c;第二行包含整数 B&#xff0c;第三行包含整数 C&#xff0c;第四行包含整数 D。 输出格式 输出格式为 DIFERENCA X&#xff0c;其中 X 为 (AB−CD) 的…

小程序添加购物车业务逻辑

数据库设计 DTO设计 实现步骤 1 判断当前加入购物车中的的商品是否已经存在了 2 如果已经存在 只需要将数量加一 3 如果不存在 插入一条购物车数据 4 判断加到本次购物车的是菜品还是套餐 Impl代码实现 Service public class ShoppingCartServiceImpl implements Shoppin…

如何在谷歌浏览器中使用自定义搜索快捷方式

在数字时代&#xff0c;浏览器已经成为我们日常生活中不可或缺的一部分。作为最常用的浏览器之一&#xff0c;谷歌浏览器凭借其简洁的界面和强大的功能深受用户喜爱。本文将详细介绍如何自定义谷歌浏览器的快捷工具栏&#xff0c;帮助你更高效地使用这一工具。 一、如何找到谷歌…

Python 3 与 Python 2 的主要区别

文章目录 1. 语法与关键字print 函数整数除法 2. 字符串处理默认字符串类型字符串格式化 3. 输入函数4. 迭代器和生成器range 函数map, filter, zip 5. 标准库变化urllib 模块configparser 模块 6. 异常处理7. 移除的功能8. 其他重要改进数据库操作多线程与并发类型注解 9. 总结…

关于IDE的相关知识之二【插件推荐】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于ide插件推荐的相关内容&#xff01…

基于微信小程序的校园点餐平台的设计与实现(源码+SQL+LW+部署讲解)

文章目录 摘 要1. 第1章 选题背景及研究意义1.1 选题背景1.2 研究意义1.3 论文结构安排 2. 第2章 相关开发技术2.1 前端技术2.2 后端技术2.3 数据库技术 3. 第3章 可行性及需求分析3.1 可行性分析3.2 系统需求分析 4. 第4章 系统概要设计4.1 系统功能模块设计4.2 数据库设计 5.…

spring中使用@Validated,什么是JSR 303数据校验,spring boot中怎么使用数据校验

文章目录 一、JSR 303后台数据校验1.1 什么是 JSR303&#xff1f;1.2 为什么使用 JSR 303&#xff1f; 二、Spring Boot 中使用数据校验2.1 基本注解校验2.1.1 使用步骤2.1.2 举例Valid注解全局统一异常处理 2.2 分组校验2.2.1 使用步骤2.2.2 举例Validated注解Validated和Vali…