循环神经网络2025 年 3 月 17 日 | 阅读 46 分钟 为什么不用前馈网络?前馈网络用于图像分类。让我们通过下面的例子来理解前馈网络的概念,其中我们训练了我们的网络来分类各种动物图像。如果我们输入一张猫的图像,它将识别该图像并为该特定图像提供一个相关标签。同样,如果您输入一张狗的图像,它也会为该图像提供一个相关标签。 考虑下图 ![]() 如果你注意到我们得到的新输出是分类狗,它与之前的输出(猫)没有关系,或者你可以说,时间“t”的输出与时间“t-1”的输出是独立的。可以清楚地看到,新输出与前一个输出之间没有关系。因此,我们可以说在前馈网络中,输出彼此独立。 在某些情况下,我们实际上需要先前的输出来获取新的输出。让我们讨论一个这样的场景,我们需要使用之前获得的输出。 ![]() 现在,当你读书时会发生什么?你只有理解了之前的单词才能理解那本书。因此,如果我们使用前馈网络并尝试预测句子中的下一个单词,那么在这种情况下,我们将无法做到,因为我们的输出实际上将取决于之前的输出。但在前馈网络中,新输出独立于之前的输出,即时间“t+1”的输出与时间“t-2”、“t-1”和“t”的输出没有关系。因此,可以得出结论,我们不能使用前馈网络来预测句子中的下一个单词。同样,还可以举出许多其他例子,我们需要先前的输出或来自先前输出的一些信息,以便推断出新的输出。 如何克服这个挑战?考虑下图 ![]() 我们在“t-1”时刻有输入,我们将其输入网络,然后我们将得到“t-1”时刻的输出。然后在下一个时间戳,即“t”时刻,我们有一个“t”时刻的输入,它将再次与来自前一个时间戳(即“t-1”)的信息一起提供给网络,这将进一步帮助我们得到“t”时刻的输出。同样,对于“t+1”时刻的输出,我们有两个输入;一个是给出的新输入,另一个是来自前一个时间戳(即“t”)的信息,以便得到“t+1”时刻的输出。以同样的方式,它将继续下去。这里我们以更通用的方式来表示它。有一个循环,来自前一个时间戳的信息正在流动,这就是我们如何解决一个特定的挑战。 什么是循环神经网络?“循环网络是一种人工神经网络,主要用于识别数据序列中的模式,例如文本、基因组、手写、口语、来自传感器、股票市场和政府机构的数值时间序列数据”。 为了理解循环神经网络的概念,让我们考虑以下类比。 ![]() 假设你的健身教练为你制定了一个时间表。每三天重复一次练习。上图包括你的练习顺序;第一天你会做肩膀,第二天你会做二头肌,第三天你会做有氧运动,所有这些练习都按正确的顺序重复。 让我们看看如果我们使用前馈网络来预测今天的练习会发生什么。 ![]() 我们提供了输入,例如星期几、一年中的月份和健康状况。此外,我们需要根据我们过去所做的练习来训练我们的模型或网络。之后,将涉及一个复杂的投票程序,该程序将为我们预测练习,并且该程序不会那么准确。在这种情况下,我们得到的任何输出都不会像我们希望的那样准确。现在,如果输入发生变化,我们将输入设为我们前一天所做的练习会怎样? ![]() 因此,如果昨天做了肩膀,那么今天肯定是二头肌日。同样,如果昨天做了二头肌,那么今天将是有氧运动日,如果昨天是有氧运动日,那么今天,我们需要做肩膀。 现在可能存在这样一种情况,由于某些个人原因你有一天不能去健身房,那么在这种情况下,我们将回溯一个时间戳,并输入前天发生的练习,如下所示。 ![]() 所以,如果前天进行的练习是肩膀,那么昨天就是二头肌练习。同样,如果前天是二头肌练习,那么昨天就是有氧运动练习,如果前天是有氧运动练习,那么昨天就是肩膀练习。而对昨天发生的练习的预测将反馈到我们的网络,以便这些预测可以用作输入,以预测今天会发生什么练习。同样,如果你错过了健身房,比如两天、三天甚至一周,你实际上需要回溯,这意味着你需要回到你上次去健身房的那一天,你需要找出你在那一天做了什么练习,然后你才能得到今天会发生什么练习的相关输出。 接下来,我们将把所有这些转换为一个向量,它只是一串数字。 ![]() 所以,有新的信息以及我们从前一个时间戳的预测中得到的信息,因为我们需要所有这些才能在“t”时刻得到预测。想象一下你昨天做了肩膀练习,那么在这种情况下,预测将是二头肌练习,因为如果昨天做了肩膀,那么今天肯定会是二头肌,输出将是 0, 1 和 0,这实际上是我们的向量的工作。 让我们通过简单地查看下图来理解循环神经网络背后的数学原理。 ![]() 假设 'w' 是权重矩阵,'b' 是偏差。考虑在时间 t=0,我们的输入是 'xo',我们需要弄清楚 'ho' 到底是什么。我们将在等式中代入 t=0,如图像所示,以获取函数 ht 的值。 之后,我们将通过使用之前计算出的值(当我们将其应用于新公式时)来找出“yo”的值。 在模型中,相同的过程在所有时间戳中一遍又一遍地重复,以便对其进行训练。所以,循环神经网络就是这样工作的。 训练循环神经网络循环神经网络使用反向传播算法进行训练,但反向传播发生在每个时间戳,这就是为什么它通常被称为“通过时间反向传播”。反向传播存在一些问题,即梯度消失和梯度爆炸,我们将逐一看到。 梯度消失 考虑下图 ![]() 在梯度消失中,当我们使用反向传播时,我们倾向于计算误差,误差无非是实际输出(我们已经知道的)减去通过模型得到的模型输出,然后平方,这样我们就可以计算出误差,并借助该误差,我们倾向于找出误差相对于权重或任何变量(这里称为权重)变化的变化。 因此,误差相对于权重变化的变化乘以学习率将给我们带来变化率。然后我们将这个权重变化添加到旧权重中以获得新权重。基本上,这里我们正在努力减少误差,为此,我们需要弄清楚如果变量发生变化,误差将如何变化,通过它我们可以得到变量的变化并将其添加到我们的旧变量中以获得新变量。 现在这里可能发生的情况是,如果值 de⁄dw,即梯度或简单地说,误差相对于权重变量的变化率变得远小于 1,并且如果我们将其乘以学习率(这肯定小于 1),那么在这种情况下,我们将得到可忽略不计的权重变化。 考虑一个场景,你需要预测句子中的下一个单词,你的句子是“我曾去过美国”。之后有很多人说话,然后你需要预测“说话”之后是什么。现在,如果你需要这样做,那么你必须回去理解它在说什么的上下文,这正是我们的长期依赖。在长期依赖期间,de⁄dw 变得非常小,然后当你将其乘以 n(再次小于 1)时,你将得到 Δw,它将非常小或简直可以忽略不计。因此,你将在这里得到的新权重将几乎等于你的旧权重,这样权重将不会进一步更新。此外,这里不会有任何学习,这正是梯度消失问题。 同样,如果我们谈论梯度爆炸,它实际上与梯度消失相反。考虑下面的图表以更好地理解它。 ![]() 如果 de⁄dw 变得非常大或大于 1,并且我们有一些长期依赖,那么在这种情况下,de⁄dw 将持续增加,并且 Δw 将变得非常大,这将使新权重与旧权重不同。所以,这是反向传播的两个问题,现在我们将看到如何解决这些问题。 如何克服这些挑战?
长短期记忆网络 (LSTM)长短期记忆网络,通常称为“LSTM”,是一种特殊的循环神经网络,它足以学习长期依赖。 什么是长期依赖?很多时候,我们只需要最近的数据就能在模型中执行问题。但同时,我们也可能需要之前获取的数据。 请看下面的例子,以便更好地理解它。 假设有一个语言模型,它试图根据前面的词预测下一个词。假设我们试图预测句子中的最后一个词,例如,“车在路上跑”。 这里的上下文相当简单,因为最后一个词总是以“路”结尾。通过结合循环神经网络,可以很容易地关联起先前信息和现有需求之间的差距。 这就是为什么不存在梯度消失和梯度爆炸问题的原因,因此使得 LSTM 网络能够轻松处理长期依赖。 LSTM 包含一个链式神经网络层。在标准循环神经网络中,重复模块包含一个单一函数,如下图所示 ![]() 从上图可以看出,层中有一个 tanh 函数,称为压缩函数。那么什么是压缩函数呢? 压缩函数主要用于 -1 到 +1 的范围内,以便可以根据输入来操作值。 现在,让我们考虑 LSTM 网络的结构 ![]() 在 LSTM 网络中,所有层中存在的函数都有其自身的结构。细胞状态由水平线表示,充当传送带,线性地跨数据通道传输数据。 让我们一步一步地理解 LSTM 网络。 步骤 1 LSTM 的第一步是识别那些不需要的信息,并将其从细胞状态中丢弃。这个决定由一个称为遗忘门层的 sigmoid 层做出。 ![]() 上面突出显示的层是前面提到的 sigmoid 层。 通过考虑新的输入和前一个时间戳,完成计算,最终得到该单元格状态中每个数字的介于 0 和 1 之间的数字输出。 作为典型的二进制数,1 表示保留细胞状态,而 0 表示丢弃它。 ft = σ(wf [ht-1, xt] + bf) 其中,wf = 权重 ht-1 = 前一个时间戳的输出 xt = 新输入 bf = 偏差 考虑到性别分类问题,当我们使用网络时,需要观察正确的性别。 步骤 2 接下来,我们将决定在细胞状态中存储哪些信息。它进一步包括以下步骤
![]() 然后,新输入以及前一个时间戳的输入将通过一个 sigmoid 函数,该函数将产生值 i(t),然后将其乘以 c(t),再将其添加到细胞状态。 在下一步中,我们将把它们两者结合起来以更新状态。 步骤 3 在第 3 步中,之前的细胞状态 Ct-1 将更新为新的细胞状态 Ct。 为此,我们需要将旧状态 (Ct-1) 乘以 f(t),将我们之前决定放弃的东西放在一边。 ![]() 接下来,我们将添加 i_t* c˜_t,这是新的候选值。它实际上已经根据我们希望更新每个状态值的程度进行了缩放。 在第二步中,我们决定使用当时所需的数据。然而,在第三步中,我们已经执行了它。 步骤 4 在第 4 步中,我们将运行 sigmoid 层,它将决定细胞状态中哪些部分将产生输出。 接下来,我们将把细胞状态通过 tanh,这意味着我们将把值推入 -1 和 1 的范围内。 然后,我们将进一步将其与 sigmoid 门的输出相乘,以便只有决定的部分产生输出。 ![]() 在此步骤中,我们将进行一些计算,这些计算将导致输出。 但是,输出只包含在前面步骤中决定要转发的输出,而不是一次性所有输出。 快速回顾
构建 RNN在深度学习的第三部分,即循环神经网络中,我们将解决一个非常具有挑战性的问题;我们将预测谷歌的股价。布朗运动确实表明股价的未来变化与过去无关。因此,我们将尝试预测谷歌股价中存在的上涨和下跌趋势。为此,我们将实现 LSTM 模型。 我们将构建一个 LSTM,它将尝试捕捉谷歌股价的上涨和下跌趋势,因为 LSTM 是唯一能够做到这一点的强大模型,它的表现优于传统模型。除此之外,我们不会执行一个简单的 LSTM 模型。它将是一个具有高维度的超级鲁棒模型,包含多个层,它将是一个堆叠 LSTM,然后我们将添加一些 dropout 正则化以避免过拟合。此外,我们将使用 Keras 库中最强大的优化器。 为了解决这个问题,我们将在谷歌股价五年的数据上训练我们的 LSTM 模型,即从 2012 年初到 2016 年底,然后根据这个训练以及 LSTM 捕获的谷歌股价的相关性,我们将尝试预测 2017 年的第一个月。我们将尝试预测 2017 年 1 月,我们再次不会精确预测股价,而是尝试预测谷歌股价的上涨和下跌趋势。 在这里,我们使用 Spyder IDE 来实现我们的 RNN 模型。所以,我们将从导入第一部分的基本库开始,即数据预处理,然后导入训练集,然后我们将构建 RNN。最后,我们将进行预测并可视化结果。 第一部分:数据预处理我们将像往常一样,首先导入我们用于实现 RNN 的基本库,就像我们在之前的模型中所做的那样。因此,我们有 NumPy,它允许我们创建一些数组,这些数组是神经网络唯一允许的输入,而不是数据帧。然后我们有 matplotlib.pyplot,我们将在最后用它来可视化结果。最后,pandas 用于轻松导入数据集和管理它们。 输出 ![]() 从上图可以看出,我们已经成功导入了这些库。 接下来,我们将导入训练集,而不是整个集合,这与第一部分和第二部分不同,因为我们希望强调我们只在训练集上训练我们的 RNN。 RNN 将对测试集上发生的事情一无所知。在训练期间,它不会与测试集有任何关联。就像测试集对 RNN 不存在一样。但是一旦训练完成,我们就会将测试集引入 RNN,以便它可以预测 2017 年 1 月的未来股价。这就是为什么我们现在只导入训练集,并且在训练完成后,我们将导入测试集。 因此,要导入训练集,我们首先需要将数据作为 DataFrame 导入,我们将使用 pandas 的 read_csv 函数导入。但是请记住,我们不仅要选择我们需要的正确列,即谷歌股票的开盘价,而且我们还需要将其转换为 NumPy 数组,因为只有 NumPy 数组才能作为 Keras 中神经网络的输入。 输出 ![]() 从上图可以看出,dataset_train 是 DataFrame,training_set 是一个包含 1258 行(对应 2012 年至 2016 年间的 1258 个股价)和一列(即谷歌股票开盘价)的 NumPy 数组。我们可以通过点击 dataset_train 和 training_set 逐一打开它们。 ![]() 现在我们可以从上图精确地检查,训练集中的谷歌股票开盘价与行数相同,即股票价格的数量相同。所以我们有一个一列的 NumPy 数组,而不是向量。 在此之后,我们将对数据应用特征缩放以优化训练过程,特征缩放可以通过两种不同的方式完成,即标准化和归一化。在标准化中,我们用同一列中所有观测值的均值减去观测值,然后除以标准差。然而,在归一化中,我们用所有观测值的最小值(即最小股价)减去观测值,然后除以所有股价的最大值减去所有股价的最小值。 因此,这次更适合使用归一化,因为每当我们构建 RNN,特别是当循环神经网络的输出层中使用 sigmoid 函数作为激活函数时,建议应用归一化。因此,我们将应用归一化,为此,我们将从 scikit learn 库的预处理模块中导入 Min-Max K-Load 类,然后从该预处理模块中导入 MinMaxScaler 类。 现在,我们将从这个类中创建一个同类对象,我们称之为 sc,表示缩放。sc 将是 MinMaxScaler 类的对象,在其中我们将传入默认参数 feature_range。这里我们将 feature_range 设置为 (0, 1),因为如果我们查看归一化的情况,我们会发现所有新的缩放后的股票价格都将在 0 和 1 之间,这正是我们想要的。 接下来,我们将对数据应用 sc 对象,以有效地进行归一化。为此,我们将引入一个新变量,它将是缩放后的训练集,所以我们将其命名为 training_set_scaled。为了获得归一化后的训练集,我们将简单地获取 sc 对象,然后应用 fit_transform 方法,这是 MinMaxScaler 类的方法,以便将 sc 对象拟合到我们将作为参数输入的 training_set,然后对其进行缩放。基本上,fit 意味着它将获取数据的最小值(即最小股价)和最大股价,以便能够应用归一化公式。然后,通过 transform 方法,它将根据公式计算训练集中每个股价的缩放后股价。 输出 ![]() 执行上述代码行后,我们将得到 training_set_scaled,如上图所示。如果我们查看它,我们可以看到所有股价确实都在 0 和 1 之间归一化。 在下一步中,我们将创建一个特定的数据结构,这是循环神经网络数据预处理中最重要的一步。基本上,我们将创建一个数据结构,指定 RNN 在预测下一个股价时需要记住什么,这实际上称为时间步数,拥有正确的时间步数非常重要,因为错误的时间步数可能导致过拟合或毫无根据的预测。 所以,我们将创建 60 个时间步和 1 个输出,这样 60 个时间步意味着在每个时间 T,RNN 将回顾时间 T 之前的 60 个股价,即时间 T 之前 60 天到时间 T 之间的股价,并根据它在这 60 个先前时间步中捕捉到的趋势,它将尝试预测下一个输出。所以,60 个时间步是过去的这些信息,我们的 RNN 将从中学习并理解一些相关性或趋势,并根据其理解,它将尝试预测下一个输出,即时间 t+1 的股价。此外,60 个时间步指的是 60 个过去的金融日,由于一个月有 20 个金融日,所以 60 个时间步对应于三个月,这意味着每天我们将查看前三个月的数据来尝试预测下一天的股价。 所以,我们首先需要创建两个独立的实体;我们将创建的第一个实体是 X_train,它将是神经网络的输入,第二个实体是 y_train,它将包含输出。基本上,对于每个观测值,或者我们可以说对于每个金融日,X_train 将包含该金融日之前的 60 个历史股价,而 y_train 将包含下一个金融日的股价。我们将开始将这两个独立的实体,即 X_train 和 y_train,初始化为空列表。 下一步是循环,因为我们将用 X_train 中的 60 个先前股价和 y_train 中的下一个股价填充这些实体。所以,我们将从 60 开始循环,因为对于每个 i(即股价观测值的索引),我们将得到从 i-60 到 i 的范围,这正好包含时间 t 之前 60 个先前股价。因此,我们将范围从 60 开始,因为这样上限更容易找到,当然是观测值的最后一个索引,即 1258。在 for 循环内部,我们将从 X_train 开始,它目前是一个空列表,所以我们将使用 append 函数向 X_train 中添加一些元素。我们将添加在索引 i 处的股价(即第 i 个金融日的股价)之前的 60 个先前股价。因此,为了获取它们,我们将获取 training_set_scaled,并在此其中,我们将获取第 i 个金融日之前的 60 个先前股价,即索引范围从 i-60 到 i。由于我们已经为 X_train 选择了正确的行,但我们仍然需要指定列,并且由于缩放后的训练集中有一列,即索引为 0 的列,这正是我们需要在这里添加的。 现在,我们以同样的方式处理 y_train,这将更容易,因为我们只需要输入时间 t+1 的股价,因此我们只需在这里做同样的事情。时间 t+1 的股价当然将从 training_set_scaled 中获取,在其中我们将为列选择相同的索引,即 0,但对于观测行,我们将选择第 i 个索引,因为如果我们考虑相同的例子,当 i 等于 60 时,X_train 将包含从 0 到 59 的所有股价,因为上限被排除,但我们想要预测的实际上是基于 60 个先前股价的时间 t+1 的股价,即 60,这就是为什么我们在这里输入 i 而不是 i+1。 所以,现在我们有 X_train 中的 60 个先前股价和 y_train 中时间 t+1 的股价。由于 X_trian 和 y_train 都是列表,所以我们再次需要将它们转换为 NumPy 数组,以便我们的未来循环神经网络能够接受它们。 输出 执行上述代码后,我们可以通过在变量浏览器窗格中单独点击 X_train 和 y_train 来查看它们。 X_train ![]() 正如我们从上图中看到的,X_train 是一个特殊的数据结构。这里的第一行观测值对应于时间 t 等于 60,这意味着它对应于我们训练数据集第 60 个金融日的股价。所有这些值都是该第 60 个金融日股价之前的 60 个历史股价,这意味着这里有 59 个值,这样如果我们看一下第一行,即第 1 个索引的观测值,它对应于训练集第 61 个金融日的股价。所有这些股价都是该第 61 个股价之前的历史股价。 y_train ![]() 现在,如果我们看一下 y_train,我们会发现它非常简单,因为它包含时间 t+1 的股价。如果我们将 X_train 和 y_train 进行比较,我们会发现 X_train 包含 t = 60 的所有 60 个先前股价,并且基于每行单独的股价,我们将训练我们的循环神经网络来预测时间 t+1 的股价。 在此之后,我们将执行数据预处理的最后一步,即重塑数据,或者简单地说,我们将为之前的数据结构添加更多维度。我们将添加的维度是“单元”,即我们可以用来预测时间 t+1 的谷歌股价的预测因子数量。 因此,在此次金融工程问题中,我们试图预测谷歌股价的趋势,这些预测器是指标。目前我们有一个指标,即谷歌股价,因此我们使用 60 个历史谷歌股价来预测下一个股价。但是借助我们将添加到数据结构中的新维度,我们将能够添加更多指标,这将有助于更好地预测谷歌股价的上涨和下跌趋势。 我们将使用 reshape 函数在 NumPy 数组中添加一个维度。我们只需要对 X_train 执行此操作,因为它实际上包含神经网络的输入。因此,我们创建了一个新数据结构的新维度,因为这正是我们将在第二部分中构建的未来循环神经网络所期望的。 因此,我们将通过使用 NumPy 库中的 reshape 函数更新 X_train,因为我们正在重塑 NumPy 数组。在 reshape 函数内部,我们将输入以下参数
输出 ![]() 执行上述代码行后,我们将看到新的 X_train,如果查看上图,我们将看到它具有我们刚才提到的三个维度。为了进一步查看 X_train,我们需要再次从变量浏览器窗格中点击它,它将类似于下面给出的内容。 ![]() 从上图我们可以清楚地看到,虽然它不是三维的,但我们可以通过简单地改变轴来观察它。正如我们在图中看到的,它是一维的,轴是 0。同样,我们将看到其余的轴,它们对应于结构的三维。 现在我们已经完成了数据预处理,我们将进入第二部分,即构建循环神经网络,在该部分中,我们将构建我们堆叠 LSTM 的整个架构,其中包含多个 LSTM 层。 第二部分 - 构建 RNN在第二部分中,我们将构建神经网络的整个架构,一个健壮的架构,因为我们不仅要构建一个简单的 LSTM,还要构建一个带有 dropout 正则化以防止过拟合的堆叠 LSTM。 因此,我们不仅要导入 Sequential 类,它将帮助我们创建表示一系列层的神经网络对象,还要导入 Dense 类以添加输出层。我们还将导入 LSTM 类以添加 LSTM 层,然后导入 Dropout 类以添加一些 dropout 正则化。这就是我们构建强大 RNN 所需的一切。 输出 ![]() 使用 TensorFlow 后端,所有类都已导入,如上所示。 接下来,我们将把循环神经网络初始化为一系列层,而不是计算图。我们将使用 Keras 中的 Sequential 类将回归器作为一系列层引入。回归器只是 Sequential 类的一个对象,它表示层的精确序列。 我们将其称为回归器,而不是 ANN 和 CNN 模型中的分类器,因为这次我们正在预测一个连续输出,或者我们可以说一个连续值,即时间 t+1 的谷歌股价。所以,我们可以说我们正在进行回归,回归是关于预测连续值,而分类是预测一个类别或一个类,由于我们正在预测一个连续值,这就是为什么我们将循环神经网络称为回归器的原因。 初始化回归器后,我们将添加不同的层,使其成为一个强大的堆叠 LSTM。因此,我们将从添加循环神经网络的第一个 LSTM 层开始,它作为一系列层引入,并添加一些 dropout 正则化以避免过拟合,因为我们不希望在预测股价时出现过拟合。我们将分两步完成:我们将添加第一个 LSTM 层,然后我们将添加 dropout 正则化。 让我们开始添加第一个 LSTM 层,为此,我们将使用我们的回归器,它是 sequential 类的一个对象。sequential 类包含 add 方法,允许添加神经网络的一些层,在 add 方法内部,我们将输入我们想要添加的层类型,即 LSTM 层,这就是我们使用 LSTM 类的地方,因为我们在此 add 方法中添加的实际上将是 LSTM 类的一个对象。因此,我们通过创建 LSTM 类的一个对象来创建 LSTM 层,该对象将接受几个参数,如下所示
完成第一步后,现在我们将处理构建神经网络架构的第一步的第二小步,即添加一些 Dropout 正则化。为此,我们将再次使用我们的回归器,然后使用 sequential 类的 add 方法,因为它将与 LSTM 的工作方式相同。我们将通过创建一个我们已经导入的 Dropout 类的对象来开始,以包含此 dropout 正则化。 因此,与 LSTM 完全相同,我们需要在这里指定此类的名称为 Dropout,它只接受一个参数,即 Dropout 率,这无非是我们想要在层中丢弃或简单地说忽略的神经元数量,以进行此正则化。而使用它们的相关数字是在层中丢弃 20% 的神经元,这正是我们需要在此处输入的。这就是我们添加 0.2 的原因,因为它对应于 20%。 因此,我们将进行 20% 的 dropout,即 LSTM 层中 20% 的神经元将在训练期间被忽略,即在每次训练迭代中发生的前向和反向传播期间。因此,由于 50 的 20% 是 10 个神经元,这简单意味着在每次训练迭代中将忽略和丢弃 10 个神经元。因此,我们已经完成了第一个 LSTM 层,并为其添加了一些 dropout 正则化。 现在我们将添加一些额外的 LSTM 层,然后为每个层添加一些 dropout 正则化。所以,我们将像上一步一样开始添加第二个 LSTM 层,因为我们将再次使用 sequential 类的 add 方法来为我们的回归器添加新的 LSTM 层和一些 dropout 正则化,但是我们将对 input_shape 参数进行一些更改。因为在上一步中,我们必须指定 input_shape 参数,因为那是我们的第一个 LSTM 层,我们被要求指定输入形状,其中最后两个维度对应于时间步和预测器,但是现在情况略有不同,我们只是添加我们的第二个 LSTM 层,这就是为什么我们不再需要指定它了。由于它会自动识别,所以当我们在第一个 LSTM 层之后添加下一个 LSTM 层时,我们将跳过将其添加到代码中。 因此,我们将在第二个 LSTM 层中保持相同的神经元数量,即 50 个神经元,以及相同的 20% dropout 进行正则化,因为这是一个相关的选择。 同样,为了添加我们的第三个 LSTM 层,我们将完全复制上面添加第二个 LSTM 层的两行代码,因为添加第三个 LSTM 层与添加第二个 LSTM 层类似。我们只需要指定 LSTM 层中神经元的数量,我们将其保持为 50 个神经元,以达到相同的目标,即拥有高维度。我们仍然需要保持 return_sequences 等于 True,因为我们在第二个 LSTM 层之后添加另一个 LSTM 层,并且我们再次保持 20% 的 dropout 正则化。 接下来,我们将添加我们的第四个 LSTM 层,但这次情况将略有不同。我们将在第四个 LSTM 层中保持 50 个神经元,因为这不是循环神经网络的最终层。但在第四层之后,我们将有输出层,其输出维度将为 1,当然,因为我们只预测一个值,即时间 t+1 的股价。由于我们正在添加第四个 LSTM 层,这是我们正在添加的最后一个 LSTM 层,所以我们需要将 return_sequences 设置为 False,因为我们不再返回任何序列。但是我们知道,return_sequences 参数的默认值是 False,所以我们只需删除那部分,因为这就是我们为第四个 LSTM 层必须做的。 我们只是添加了带有 50 个单元的 LSTM 类,并且我们将保持 20% 的 dropout 正则化。 输出 ![]() 从上图可以看出,我们已经成功完成了 LSTM 部分。 现在我们只需要添加我们的最后一层,即输出层。我们将简单地使用我们的回归器,它与 ANN 和 CNN 完全相同,然后再次使用 sequential 类的 add 方法来添加神经网络的最终输出层。由于我们不是添加 LSTM 层,而是实际上是一个经典的完全连接层,因为输出层与前一个 LSTM 层完全连接,所以在这种情况下,为了使其成为一个完全连接层,我们将需要像 ANN 和 CNN 一样使用 Dense 类。 所以,我们将在 add 方法中指定 Dense 类,然后我们将添加一个参数,该参数对应于输出层中所需的神经元数量。由于我们正在预测与股价对应的实值,因此输出只有一个维度,这正是我们需要输入的,其参数是 units,因为它对应于输出层中的神经元数量或输出层的维度,即 1。 现在我们已经完成了我们超鲁棒的 LSTM 循环神经网络的架构,构建 RNN 还剩下两个步骤;第一个是用强大的优化器和正确的损失(这将是均方误差,因为我们正在进行回归)编译 RNN,第二个是将这个循环神经网络拟合到训练集。 由于我们的训练集由 X_train 组成,它是神经网络期望的正确数据结构,所以我们将使用 X_train 而不是训练集或 training_set_scaled,当然,在将回归器拟合到我们的训练集时,我们需要指定输出,因为输出包含基本事实,即时间 t+1 的股价。由于我们正在根据真实情况训练 RNN,即在 60 个生成的股价在 60 个生成的金融日之后,时间 t+1 发生的真实股价,所以这就是为什么我们还需要包含基本事实,即 y_train。 让我们使用正确的优化器和正确的损失函数编译 RNN。所以,我们将从获取我们的回归器开始,因为我们正在预测一个连续值,然后使用 compile 方法,这是 sequential 类的另一个方法,在 compile 方法中,我们将输入两个参数,即优化器和损失函数。 通常,对于循环神经网络,建议使用 RMSprop 优化器,但在我们的问题案例中,我们将使用 adam 优化器,因为它始终是一个安全的选择,因为它非常强大并且总是执行一些相关的权重更新。我们将输入的第二个参数是损失函数。由于我们不是在处理分类问题,而是回归问题,因为我们必须预测一个连续值,所以这次的损失函数是 mean_squared_error,因为误差可以通过预测和目标(即真实值)之间平方差的均值来衡量。 编译 RNN 后,我们将把 RNN 拟合到由 X_train 和 y_train 组成的训练集。所以,我们将再次从获取回归器开始,而不是分类器,然后使用 fit 方法,它不仅会将神经网络连接到训练集,而且还将在一部分我们将在同一个 fit 方法中选择的时期数上执行训练。在 fit 方法内部,我们将传入四个参数,即 X_train、y_train、epochs 和 batch_size。所以,我们的网络将不会在单个观测值进入神经网络时进行训练,而是在批量观测值,即批量股价进入神经网络时进行训练。 我们不会在每个股价被前向传播到神经网络并产生误差(然后反向传播到神经网络)时更新权重,而是每 32 个股价更新一次,因为我们选择了 batch_size 为 32。所以,我们在这里完成了构建一个超鲁棒的循环神经网络,并且我们已准备好在谷歌股价 5 年的数据上对其进行训练。 输出 ![]() 从上图可以看出,我们已经充分防止了过拟合,以至于损失不再进一步降低,因为如果最终损失过小,我们可能会出现过拟合,而且我们的预测会非常接近真实的谷歌股价。在训练数据中,即过去的数据,而不是我们感兴趣进行预测的数据中,我们将获得一些很大的损失,而在测试数据中将获得一些非常糟糕的损失。所以,这就是过拟合的全部内容。 这就是为什么我们在训练集时,必须小心不要出现过拟合,因此不要试图尽可能地降低损失,这也是为什么我们似乎得到了很好的结果。 在此之后,我们将进入第三部分,在该部分中,我们将可视化我们的预测与 2017 年第一个金融月的真实谷歌股价的比较。 # 第三部分 - 进行预测并可视化结果首先,我们将获取 2017 年的真实股价,然后在第二步中,我们将获取 2017 年的预测股价,最后,我们将可视化结果。因此,为了获取 2017 年的真实股价,我们将从 CSV 文件中的测试集中获取,因此我们将完全按照我们对训练集所做的操作进行。 我们将简单地从创建一个数据帧开始,通过 pandas 的 read_csv 函数导入 Google_Stock_Price_Test.csv 文件,然后我们将选择正确的列,即谷歌股票的开盘价,然后将其转换为 NumPy 数组,我们将通过用测试集替换训练集来完成。由于测试集将是 2017 年 1 月第一个月的谷歌股价的真实值,所以我们将简单地用 real_stock_price 替换 training_set。 执行上述代码后,我们将获得 2017 年 1 月的 real_stock_price,我们可以在变量浏览器窗格中查看它。 输出 ![]() 从上图我们可以看到,real_stock_price 包含 20 个观测值,即 20 个股价,因为这些是金融日,一个月有 20 个金融日(不包括周六和周日)。我们可以通过点击 real_stock_price 查看它,它将类似于下面给出的内容。 ![]() 接下来,我们将进入第二步,在该步骤中,我们将预测 2017 年 1 月的股价。因此,在这里,我们将借助我们的回归器来预测 2017 年 1 月的谷歌股价。基本上,第一个关键点是,我们训练模型以基于 60 个历史股价预测时间 t+1 的股价,因此为了预测 2017 年 1 月每个金融日的每个股价,我们将需要该实际日期之前 60 个金融日的 60 个历史股价。 第二个关键点是,为了获得 2017 年 1 月每天前 60 天的 60 个历史股价,我们将需要训练集和测试集,因为这 60 天中的一些将来自训练集(因为它们将来自 2016 年 12 月),而一些股价将来自测试集(因为它们将来自 2017 年 1 月)。 因此,我们首先需要做的就是将训练集和测试集进行连接,以便能够获得 2017 年 1 月每天的这 60 个先前的输入,这又引出了对第三个关键点的理解。我们将通过连接训练集和测试集来进行连接,即通过连接包含 2012 年至 2016 年底真实谷歌股价的训练集,这样将训练集与测试集连接实际上会导致一个问题,因为届时我们将不得不对训练集和测试集的连接进行缩放。为此,我们将不得不从我们在特征缩放部分创建的 sc 对象中应用 fit_transform 方法来缩放训练集和测试集的连接,以获得缩放后的 real_stock_price。但这会改变实际的测试值,我们永远不应该这样做,所以我们将保持实际的测试值不变。 因此,我们将进行另一次串联,这将是串联我们仍然拥有的原始 DataFrame,即 dataset_train 和 dataset_test,从这次串联中,我们将获得每个预测的输入,即在每个时间 t 产生的股价,这就是我们将要缩放的。这些是我们将应用于我们的 sc 对象并进行缩放以获得预测的输入。通过这种方式,我们只缩放输入,而不是改变实际的测试值,这将使我们获得最相关的结果。 因此,我们将首先引入一个名为 dataset_total 的新变量,因为它将包含整个数据集,然后我们将进行连接,为此我们将使用 pandas 库中的 concat 函数。在 pandas 函数内部,我们需要输入两个参数,例如第一个是我们要连接的两个 DataFrame 对,即我们将 dataset_train 连接到 dataset_test,另一个参数是我们要进行此连接的轴。由于我们想沿着行进行此连接,因为我们想将测试集的股价添加到训练集的股价中,所以我们将沿着垂直轴进行连接,为了指定这一点,我们将添加第二个参数,即 axis=0,因为垂直轴的标签是 0。 现在,在下一步中,我们将获取输入,即在2017年1月每个时间t或每个金融日,我们需要获取前60个金融日的前60个股票价格。因此,为了获取这些输入,我们将首先引入一个新变量inputs。然后我们将获取dataset_total,因为我们是从我们目前的DataFrame数据集获取这些股票价格的,因此,由于我们需要从2017年第一个金融日减去60天直到我们整个数据集的最后一个股票价格。 为此,我们获取所需输入范围的第一个下限。下限是1月3日的股票价格减去60,为了获取它,我们需要找到1月3日的索引,这可以通过获取len(dataset_total)(总数据集的长度)然后减去len(dataset_test)(数据集的长度)来简单实现,由于我们想获取这一天的股票价格,因此我们将再次减去60,因为它是我们所需输入的下限。为了获取上限,我们只需添加一个冒号(即:)。基本上,上限是整个数据集的最后一个索引,因为要预测最后一个金融日的最后一个股票价格,我们需要前60个股票价格,因此我们需要的最后一个股票价格是最后一个金融日之前的股票价格。所以,这是将导致DataFrame的输入范围,但当然,我们需要转换为NumPy数组,为此,我们将添加.values使其成为NumPy数组。所有这些都将包含我们预测2017年1月股票价格所需的所有输入。 在下一步中,我们将进行简单的重塑以获得正确的NumPy形状,因此我们将更新输入,为此,我们将再次使用上一步中使用的相同的旧inputs,并在此基础上添加reshape函数。在reshape函数内部,我们将传递(-1, 1),因为它将帮助我们获得从1月3日减去3个月到最终股票价格的、以行显示、以1列显示的输入。 现在我们将重复我们之前所做的相同过程,以获得神经网络期望的正确3D格式,这不仅用于训练,也用于预测。因此,无论我们是应用拟合方法来训练回归器,还是应用预测方法使回归器进行预测,我们都需要有正确格式的输入,即我们之前创建的3D格式。在开始创建这种3D特殊结构之前,我们必须对输入进行缩放,因为它们直接来自dataset_total中包含的原始DataFrame,所以我们有股票价格的原始值,并且由于我们的循环神经网络是在缩放值上训练的,当然,我们需要缩放输入,这符合我们前面讨论的第三个关键点,即只缩放输入,而不缩放实际测试值,因为我们需要保持测试值不变。 所以,我们将再次开始更新输入,为此我们将使用缩放对象sc,但在这里我们不会使用fit_transform方法,因为sc对象已经拟合到训练集,因此我们将直接使用transform方法,因为我们需要应用于输入的缩放必须与我们应用于训练集的缩放相同。因此,我们不能再次拟合我们的缩放对象sc,而必须直接应用transform方法来获得我们回归器所训练的先前缩放。 接下来,我们将为测试集创建一个特殊的<数据>集结构,因此我们将引入一个新变量并将其命名为X_test,因为它将是我们需要预测测试集值的输入。由于我们没有进行任何训练,所以我们需要y_test。我们实际上正在进行一些预测,所以我们不再需要真实值,这就是为什么这里也不包括y_train,并且在循环内部,我们不会改变下限以获取前60个时间步,由于这里是i-60,所以我们必须从60开始。但是对于上限,情况就大不相同了,因为我们所做的只是获取测试集的输入,因为它只包含20个金融日,所以我们需要达到60+20=80,这样我们就可以为2017年1月的每个股票价格(包含20个金融日)获取前60个输入。 在此之后,我们将在X_test中添加之前的股票价格,这些价格确实是从输入中获取的,并保持其索引范围从i-60到i,我们还将0保留,因为它对应于谷歌开盘股票价格,并且无论如何输入中只有一列。 由于X_test也是一个列表,所以我们再次需要将其转换为NumPy数组,以便它能被我们未来的循环神经网络接受,通过这样做,我们得到一个结构,其中在每行观测中,即对于2017年1月的每个股票价格,我们在60列中有我们预测下一个股票价格所需的60个先前的股票价格。 现在,我们将进一步转换为3D格式,为此我们将再次使用reshape函数来在NumPy数组中添加一个维度。我们将以与数据预处理部分的重塑部分完全相同的方式进行,只是需要将X_train替换为X_test,其余代码及其解释与上述类似。 所以,我们已经准备好进行预测,因为我们的输入X_test中包含了正确的3D结构,这正是我们的循环神经网络回归器所期望的,因此我们已经准备好应用该回归器的predict方法来获取2017年1月的预测股票价格。 我们将使用regressor,并从该回归器中应用predict方法,我们需要向其输入X_test,它以正确的格式包含输入以预测2017年1月的股票价格。由于它返回预测结果,所以我们将这些预测结果存储在一个名为predicted_stock_price的新变量中,该变量将与real_stock_price保持一致,然后使其等于由回归器的predict方法返回并应用于X_test中包含的正确输入的值。 完成此操作后,我们将对预测进行反向缩放,因为我们的回归器经过训练可以预测股票价格的缩放值,因此为了获得这些缩放预测值的原始比例,我们只需从我们的缩放sc对象应用inverse_transform方法。由于我们将使用我们谷歌股票价格值的正确比例更新predicted_stock_price,所以我们将获取我们的predicted_stock_price,然后获取我们的缩放对象,即sc,我们将在那里应用inverse_transform方法,我们将对predicted_stock_price应用该方法。 输出 ![]() 因此,在执行完整个“获取2017年预测股票价格”部分后,我们将得到上述输出,其中包含预测值,这些预测值确实在2017年1月谷歌股票价格的范围内。但我们还无法判断它是否近似遵循了2017年1月真实谷歌股票价格的趋势。 接下来,我们将进行结果可视化,这将实际证明模型的稳健性,因为我们将看到我们的预测如何跟随谷歌股票价格的趋势。因此,我们将首先使用matplotlib.pyplot库中的plt.plot函数,在此plt.plot函数内部,我们首先需要输入包含要绘制的股票价格的变量名称,这些变量包含在real_stock_price变量中。因此,我们首先需要输入real_stock_price变量,然后添加我们的下一个参数,即我们为真实股票价格选择的颜色,即红色,然后最后一个参数是标签,我们将为此在图表上绘制一些图例。因此,我们将使用plt.legend函数来显示图例。这里我们选择了'Real Google Stock Price',以便记住我们绘制的不是2012年至2017年第一个月之间完整的真实谷歌股票价格,而是绘制2017年第一个月(1月)的真实谷歌股票价格,因为我们只有2017年1月的预测,所以我们只想比较这两个股票价格在这个第一个月内的表现。 同样,我们将再次使用plt.plot函数来绘制包含2017年1月股票价格预测的predicted_stock_price变量。这将以与上述相同的方式进行,但会选择不同的颜色,即蓝色,以及标签'Predicted Google Stock Price'。 由于我们希望图表美观,因此我们将为图表添加标题,为此我们将使用plt.title函数,并在其中注明我们希望给图表起的标题,即'Google Stock Price Prediction'。 接下来,我们将为x轴和y轴添加标签,为此,我们将分别使用plt.xlabel和plt.ylabel函数。在plt.xlabel函数内部,我们将输入对应x轴的标签,即'Time',因为我们正在绘制1月3日至1月31日的数据,同样在plt.ylabel内部,我们将输入对应y轴的标签,即'Google Stock Price'。 在此之后,我们将添加不带任何输入的plt.legend函数,以便在图表中包含图例,最后使用plt.show函数显示图表。 输出 ![]() 从上面的输入中,我们可以看到我们用红色表示真实的谷歌股票价格,用蓝色表示我们预测的谷歌股票价格。我们还得到了2017年1月整月的真实和预测谷歌股票价格的比较。我们从网络上经过验证的金融来源获得了真实的谷歌股票价格。然而,这些预测来自我们刚刚实现的RNN模型。 我们可以看到,在某些部分,我们的预测落后于实际值。我们可以清楚地看到一个很大的尖峰,就像一个股票时间奇点,而预测没有跟随这个尖峰,这是完全正常的。我们的模型只是滞后,因为它无法对快速、非线性的变化做出反应。 图像中的尖峰是股票时间不规律性,确实是一种快速的非线性变化,我们的模型无法正确应对,但这完全没问题,因为根据金融工程中的布朗运动数学概念,股票价格的未来变化与过去无关。因此,我们在尖峰周围看到的未来变化,它确实与之前的股票价格完全独立。 但另一方面,也有好消息,我们的模型对真实谷歌股票价格中发生的平滑变化反应良好,除了模型无法应对的尖峰,除此之外,我们的循环神经网络对这些平滑变化反应非常好。 因此,可以得出结论,在包含一些尖峰的预测部分,我们的预测落后于实际值,因为我们的模型无法对快速、非线性变化做出反应,而另一方面,对于包含平滑变化的预测部分,我们的模型预测得非常好,并且能够跟随上升和下降趋势。它成功地跟随了预测谷歌股票价格的上升趋势、稳定趋势和再次上升趋势。然后在1月最后几个金融日出现下降趋势,并且模型开始捕捉到它。所以,我们可以说它在有尖峰的情况下也取得了非常好的结果,这些结果实际上很有意义。 下一个主题Kohonen自组织映射 |
我们请求您订阅我们的新闻通讯以获取最新更新。