前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >预测金融时间序列——Keras 中的 MLP 模型

预测金融时间序列——Keras 中的 MLP 模型

作者头像
磐创AI
发布2021-11-11 10:06:30
4.9K1
发布2021-11-11 10:06:30
举报

作者 | shivani46

编译 | Flin

介绍

本文的目的是展示使用时间序列从数据处理到构建神经网络和验证结果的过程。作为一个例子,金融系列被选择为完全随机的,一般来说,如果传统的神经网络架构能够捕获必要的模式来预测金融工具的行为,那就很有趣了。

本文中描述的管道可以轻松应用于任何其他数据和其他分类算法。

金融时间序列预测的数据准备

例如,以像苹果这样的普通公司2005年至今的股价为例。可以从Yahoo Finance以.csv格式下载(https://finance.yahoo.com/quote/AAPL/history?period1=1104534000&period2=1491775200&interval=1d&filter=history&frequency=1d)。

让我们加载这些数据,看看是什么样子。

首先,让我们导入我们需要下载的库:

代码语言:javascript
复制
import matplotlib.pylab as plt
import numpy as np
import pandas as pd

现在,只需读取数据即可绘制图形,不要忘记使用 [:: – 1] 翻转数据,因为 CSV 的数据是倒序的,即从 2017 年到 2005 年:

代码语言:javascript
复制
data = pd.read_csv('./data/AAPL.csv')[::-1]
close_price = data.ix[:, 'Adj Close'].tolist()
plt.plot(close_price)
plt.show()

它看起来几乎像一个典型的随机过程,但我们将尝试解决提前一天或更长时间预测的问题。“预测”的问题必须首先更接近机器学习的问题来描述。

我们可以简单地预测市场中股票价格的变动——或多或少——这将是一个二元分类问题。

另一方面,我们可以仅预测第二天(或几天后)的价格值或与前一天相比第二天的价格变化,或这种差异的对数——即,我们要预测一个数字,这是一个问题回归。但是在解决回归问题时,你将不得不面对数据归一化的问题,我们现在将考虑这个问题。

无论是在分类的情况下,还是在回归的情况下,我们都会以某种时间序列窗口(例如,30 天)作为入口,尝试预测第二天的价格走势(分类),或者变化(回归)的价值。

金融时间序列的主要问题是它们根本不是平稳的。

期望值、方差、平均最大值和最小值在窗口中随着时间的推移而变化。

图 1

并且以友好的方式,我们不能根据我们的窗口使用这些值的极大极小或z分数归一化,因为如果在 30 天内有一些特征,它们也可以在第二天改变或在我们窗口的中间改变。

但是如果你仔细观察分类问题:

代码语言:javascript
复制
Dat = [(np.array(x) - np.mean(x)) / np.std(x) for x in Dat]

对于回归问题,这是行不通的,因为如果我们还减去平均值并除以偏差,我们将不得不为第二天的价格值恢复这个值,而这些参数可能完全不同。

因此,我们将尝试两种选择:

对原始数据进行训练,并尝试通过带走不那么感兴趣的第二天的预期或方差来欺骗系统。我们只对向上或向下移动感兴趣。因此,我们将冒险并使用 z 分数标准化我们的 30 天窗口,但仅限于它们,不会影响“未来”的任何内容:

第二天价格的百分比变化——pandas将帮助我们解决这个问题:

代码语言:javascript
复制
close_price_diffs = close.price.pct_change()

看起来像这样,正如我们所看到的,这些数据在没有任何具有统计特征的操作的情况下获得,已经处于 -0.5 到 0.5 的限制范围内:

将训练样本进行划分,我们取前85%的时间窗口用于训练,后15%用于检查神经网络的运行情况。

因此,为了训练我们的神经网络,我们将收到以下 X、Y对:

30 天收盘价和 [1, 0] 或 [0, 1],具体取决于二进制文件的价格值分类增加或减少;30 天的价格百分比变化和回归的第二天变化。

神经网络架构

我们将使用多层感知器作为基本模型。让我们把Keras作为一个实现框架——它非常简单、直观,你可以用它来实现相当复杂的计算图,但到目前为止我们还不需要它。

一个基本的网格由输入层的 30 个神经元、64 个神经元(第一个隐藏层)实现,然后是批量归一化——建议将它用于几乎所有多层网络,然后是激活函数(ReLU) 已经被认为是不正常的,所以让我们采取一些像 LeakyReLU 这样的时髦的函数。

在输出端,我们放置一个神经元(或两个用于分类),根据任务(分类或回归),它要么在输出端有一个 softmax,要么让它没有非线性,以便能够预测任何值。

分类代码如下所示:

代码语言:javascript
复制
model = Sequential()
model.add(Dense(64, input_dim=30))
model.add(BatchNormalization())
model.add(LeakyReLU())
model.add(Dense(2))
model.add(Activation('softmax'))

对于回归问题,激活参数最后应该是“线性”。

接下来,我们需要定义误差函数和优化算法。在不深入讨论梯度下降变化的细节的情况下,让我们以步长为 0.001 的 Adam 为例;分类的损失参数需要设置为交叉熵 ——'categorical_crossentropy',回归的损失参数需要设置为均方误差——“mse”。

Keras 还允许我们非常灵活地控制训练过程,例如,如果我们的结果没有改善,最好减少梯度下降步骤的值——这正是 Reduce LR On Plateau 所做的,我们将其添加为回调到模型训练。

代码语言:javascript
复制
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.9, patience=5, min_lr=0.000001, verbose=1)
model.compile(optimizer=opt, 
              loss='categorical_crossentropy',
              metrics=['accuracy'])
Neural network training
history = model.fit(X_train, Y_train, 
          nb_epoch = 50, 
          batch_size = 128, 
          verbose=1, 
          validation_data=(X_test, Y_test),
          shuffle=True,
          callbacks=[reduce_lr])

学习过程完成后,最好在屏幕上显示误差和准确度值的动态图表:

代码语言:javascript
复制
plt.figure()
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='best')
plt.show()
plt.figure()
plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.title('model accuracy')
plt.ylabel(‘acc’)
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='best')
plt.show()

在开始训练之前,我想提请你注意一个重要的点:有必要在此类数据上学习更长时间的算法,至少 50-100 个 epoch。

这是因为,如果你训练 5-10 个 epochs 并看到 55% 的准确率,这很可能并不意味着你已经学会了在分析训练数据时找到模式,你将看到只有 55 % 窗口用于一种模式(例如增加),其余 45% 用于另一种模式(减少)。

在我们的例子中,53% 的窗口属于“减少”类,47% 属于“增加”类,因此我们将尝试获得高于 53% 的准确度,这表明我们已经学会了寻找符号。

在准备训练样本时,原始数据(例如收盘价和简单算法)的准确性太高很可能表明模型过度拟合了。

预测金融时间序列 - 分类问题

让我们训练我们的第一个模型并查看图表:

可以看到,测试样本的准确率一直保持在±1值的误差,训练样本的误差下降,准确率增加,说明过拟合了。

让我们看看更深层次的两层模型:

代码语言:javascript
复制
model = Sequential()

model.add(Dense(64, input_dim=30))

model.add(BatchNormalization())

model.add(LeakyReLU())

model.add(Dense(16))

model.add(BatchNormalization())

model.add(LeakyReLU())

model.add(Dense(2))

model.add(Activation('softmax'))

以下是它的工作成果:

大致相同的图片。当我们面临过拟合时,我们需要为我们的模型添加正则化。

在正则化的过程中,我们对神经网络的权重施加了一定的限制,使得值不会出现大的散布,尽管有大量的参数(即网络权重),但其中一些被翻转,为简单起见,设置为零。

我们将从最常见的方式开始——在权重总和的L2 范数中向误差函数添加一个附加项,在Keras 中 这是使用 keras.regularizers.activity_regularizer 完成的。

代码语言:javascript
复制
model = Sequential()
model.add(Dense(64, input_dim=30,
                activity_regularizer=regularizers.l2(0.01)))
model.add(BatchNormalization())
model.add(LeakyReLU())
model.add(Dense(16,
                activity_regularizer=regularizers.l2(0.01)))
model.add(BatchNormalization())
model.add(LeakyReLU())
model.add(Dense(2))
model.add(Activation('softmax'))

这个神经网络在误差函数方面学习得更好一点,但准确性仍然受到影响:

在处理具有大量噪声或随机性质的数据时,经常会遇到诸如误差减少而不是准确度降低这样的奇怪效果——这是因为误差是基于交叉熵值计算的,这可能会降低,而准确度是具有正确答案的神经元的指标,即使错误发生变化,也可能保持不正确。

因此,值得使用近年来流行的 Dropout 技术为我们的模型添加更多的正则化——粗略地说,这是在学习过程中随机“忽略”一些权重,以避免神经元的共同适应(以便他们不学习相同的功能)。代码如下所示:

代码语言:javascript
复制
model = Sequential()
model.add(Dense(64, input_dim=30,
                activity_regularizer=regularizers.l2(0.01)))
model.add(BatchNormalization())
model.add(LeakyReLU())
model.add(Dropout(0.5))
model.add(Dense(16,
                activity_regularizer=regularizers.l2(0.01)))
model.add(BatchNormalization())
model.add(LeakyReLU())
model.add(Dense(2))
model.add(Activation('softmax'))

如你所见,我们将在训练期间以 50% 的概率为每个权重“丢弃”两个隐藏层之间的连接。通常不会在输入层和第一个隐藏层之间添加 dropout,因为在这种情况下,我们将从简单的噪声数据中学习,并且它也不会在输出之前添加。当然,在网络测试期间,不会发生掉线。这样的网格如何学习:

如果你稍早停止训练网络,我们可以在预测价格变动方面获得 58% 的准确率,这肯定比随机猜测要好。

预测金融时间序列的另一个有趣且直观的时刻是,第二天的波动具有随机性,但是当我们查看图表、蜡烛图时,我们仍然可以注意到接下来 5-10 天的趋势。

让我们检查一下我们的神经元是否可以处理这样的任务——我们将使用最后一个成功的架构预测 5 天后的价格走势,我们将训练更多的 epoch:

如你所见,如果我们足够早地停止训练(超时,仍然会出现过拟合),那么我们可以获得 60% 的准确率,这是非常好的。

预测金融时间序列——回归问题

对于回归问题,让我们采用我们最后一个成功的分类架构(它已经表明它可以学习必要的特征),移除 Dropout,并进行更多迭代训练。

此外,在这种情况下,我们不仅可以查看误差值,还可以使用以下代码直观地评估预测质量:

代码语言:javascript
复制
pred = model.predict(np.array(X_test))
original = Y_test
predicted = pred
plt.plot(original, color='black', label = 'Original data')
plt.plot(predicted, color='blue', label = 'Predicted data')
plt.legend(loc='best')
plt.title('Actual and predicted')
plt.show()
The network architecture will look like this:
model = Sequential()
model.add(Dense(64, input_dim=30,
                activity_regularizer=regularizers.l2(0.01)))
model.add(BatchNormalization())
model.add(LeakyReLU())
model.add(Dense(16,
                activity_regularizer=regularizers.l2(0.01)))
model.add(BatchNormalization())
model.add(LeakyReLU())
model.add(Dense(1))
model.add(Activation('linear'))

让我们看看如果我们在“原始”调整收盘价上训练会发生什么:

从远处看起来不错,但是如果我们仔细观察,我们会发现我们的神经网络只是在其预测方面落后,这可以被视为失败。

对于价格变化,结果是:

有些值预测得很好,有些地方的趋势是正确猜测的,但总的来说 - 马马虎虎。

讨论

原则上,结果通常乍一看并不令人印象深刻。确实如此,但我们在没有太多预处理的情况下,在一维数据上训练了最简单的神经网络。有许多步骤可以让你将准确度提高到 60-70% 的水平:

  • 不仅要使用收盘价,还要使用我们 .csv 中的所有数据(最高价、最低价、开盘价、收盘价、成交量)——也就是说,注意任何给定时间的所有可用信息
  • 优化超参数——窗口大小、隐藏层中的神经元数量、训练步骤——所有这些参数都是随机取的,使用随机搜索,你可以发现,也许,我们需要查看 45 天前和以较小的步长学习更深的网格。
  • 使用更适合我们任务的损失函数(例如,为了预测价格变化,我们可以找到不正确符号的神经函数,通常的 MSE 对数字的符号是不变的)

结论

在本文中,我们应用了最简单的神经网络架构来预测市场的价格走势。这个管道可以用于任何时间序列,主要是选择正确的数据预处理,确定网络架构,并评估算法的质量。

在我们的例子中,我们设法使用前 30 天的价格窗口以 60% 的准确率预测了 5 天的趋势,这可以被认为是一个很好的结果。价格变化的定量预测结果证明是失败的,对于这项任务,建议使用更严肃的工具和时间序列的统计分析。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-11-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 磐创AI 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档