金融中的深度学习-三-

283 阅读1小时+

金融中的深度学习(三)

原文:annas-archive.org/md5/1d10c05a97f5e0702eda83b77eec256b

译者:飞龙

协议:CC BY-NC-SA 4.0

第七章:时间序列预测的机器学习模型

机器学习是人工智能的一个子领域,专注于开发能够使计算机在没有明确编程的情况下学习和进行预测或决策的算法和模型,因此被称为学习。机器学习涉及设计和构建能够从经验中自动学习和改进的系统,通常通过分析和从大量数据中提取模式来实现。

本章介绍了使用机器学习模型进行时间序列预测的框架,并讨论了一些已知的算法选择。

框架

框架非常重要,因为它组织了整个研究过程的方式(从数据收集到性能评估)。拥有适当的框架确保在回测中保持协调,从而允许在不同的机器学习模型之间进行适当的比较。框架可能遵循以下时间顺序步骤:

  1. 导入和预处理历史数据,这些数据必须包含足够数量的值,以确保良好的回测和评估。

  2. 进行训练-测试拆分,将数据分为两部分,其中数据的第一部分(例如从 2000 年到 2020 年)保留用于训练算法,以便理解预测未来值的数学公式,而数据的第二部分(例如从 2020 年到 2023 年)保留用于测试算法在其从未见过的数据上的表现。

  3. 使用算法对数据进行拟合(训练)和预测(测试)。

  4. 运行性能评估算法以了解模型在过去的表现。

注意

训练集也称为样本内数据,而测试集也称为样本外数据

框架的第一步已在第六章中讨论过。现在您应该能够使用 Python 轻松导入历史数据。训练-测试拆分将历史数据分为训练(样本内)集,其中模型被拟合(训练),以找到一个隐含的预测函数,以及测试(样本外)集,在测试集上应用和评估在训练集上计算的预测函数。理论上,如果模型在测试集上表现良好,那么您可能有一个潜在的交易策略候选,但这仅仅是第一步,现实远比这复杂得多。

为了一切顺利,从 GitHub 仓库 下载 master_function.py,然后设置 Python 解释器(例如 Spyder)的目录与下载的文件位于同一位置,以便您可以将其作为库导入并使用其函数。例如,如果您将文件下载到桌面,您可能希望将目录设置为 第 6-5 图 所示的 第 6 章 的位置。通常可以在 Spyder 的右上角找到目录选项(变量资源管理器上方)。

注意

如果您不想导入 master_function.py,您可以像正常文件一样在解释器中打开它并执行它,以便 Python 定义内部的函数。但是,每次重新启动内核时都必须执行此操作。

现在,对时间序列进行预处理(转换)并将其分割成四个不同的数组(或数据框),每个数组都具有以下实用性:

数组 x_train

解释您希望预测变量变化的样本内特征集(即独立变量)。它们是预测器。

数组 y_train

样本内的因变量集(即正确答案),您希望模型在其上校准其预测函数。

数组 x_test

作为模型的测试的样本外特征集,以查看它在此前未见数据上的表现。

数组 y_test

包含模型必须接近的实际值。换句话说,这些是将与模型预测进行比较的正确答案。

在分割之前,了解正在预测的内容以及用于预测的内容非常重要。在本章中,滞后价格差(收益率)将用于预测。通常,在执行此操作之前必须进行一些测试,但为了简单起见,让我们将它们略过,并假设最近的 500 个日常 EURUSD 收益率对当前收益率具有预测能力,这意味着您可以找到一个预测公式,该公式使用最近的 500 个观测值来观察下一个观测值:

因变量(预测)

EURUSD 日间时间框架中的 t+1 回报。这也称为 y 变量。

自变量(输入)

EURUSD 的最近 500 个日回报。这些也称为 x 变量。

图 7-1 显示了某一时间段内 EURUSD 的日收益率。注意其稳态外观。根据 ADF 测试(见 第 3 章),收益率数据集似乎是稳态的,适合进行回归分析。

图 7-1. EURUSD 的日收益率。
注意

在本章中,特征(x 值)将是 EURUSD 滞后日价格差。¹ 在后续章节中,将使用滞后收益率或技术指标值作为特征。请注意,您可以使用任何您认为值得被视为预测的特征。

时间框架(每日)的选择非常适合希望获得一日市场视图并在当天结束前关闭仓位的交易者。

让我们首先使用虚拟回归模型作为第一个基本示例。虚拟回归是一种比较机器学习算法,仅用作基准,因为它使用非常简单的规则进行预测,不太可能增加任何真正的预测价值。虚拟回归的真正实用性在于看看你的真实模型是否胜过它。作为提醒,机器学习算法遵循以下步骤:

  1. 导入数据。

  2. 预处理并分割数据。

  3. 训练算法。

  4. 使用训练参数在测试数据上进行预测。此外,为了比较,还要在训练数据上进行预测。

  5. 绘制和评估结果。

首先导入本章所需的库:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from master_function import data_preprocessing, mass_import
from master_function import plot_train_test_values, 
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

现在导入您将使用的算法的特定库:

from sklearn.dummy import DummyRegressor

下一步是导入和转换收盘价数据。请记住,您正在尝试预测每日收益,这意味着您必须仅选择收盘列,然后对其应用差分函数,以使价格差异化:

# Importing the differenced close price of EURUSD daily time frame
data = np.diff(mass_import(0, 'H1')[:, 3])
注意

在金融中,术语收益通常指的是投资或某种资产产生的收益或损失,它可以通过当前资产价值与先前某个时间点的价值之间的差异来计算。这本质上是一种差分形式,因为您正在计算资产价值的变化或差异。

在时间序列分析中,差分是一种常用技术,用于使时间序列数据平稳化,这对于各种分析都很有帮助。差分涉及从彼此减去连续观测值,以消除趋势或季节性,从而关注数据的变化。

接下来,设置算法的超参数。对于这些基本算法而言,这将是滞后数(预测器数)和数据的百分比分割:

# Setting the hyperparameters
num_lags = 500
train_test_split = 0.80

0.80 的train_test_split表示 80%的数据将用于训练,而剩余的 20%将用于测试。

用于拆分和定义回测所需的四个数组的函数可以定义如下:

def data_preprocessing(data, num_lags, train_test_split):
    # Prepare the data for training
    x = []
    y = []
    for i in range(len(data) – num_lags):
        x.append(data[i:i + num_lags])
        y.append(data[i+ num_lags])
    # Convert the data to numpy arrays
    x = np.array(x)
    y = np.array(y)
    # Split the data into training and testing sets
    split_index = int(train_test_split * len(x))
    x_train = x[:split_index]
    y_train = y[:split_index]
    x_test = x[split_index:]
    y_test = y[split_index:]
    return x_train, y_train, x_test, y_test

调用函数创建四个数组:

# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, 
                                                      num_lags, 
                                                      train_test_split)

您现在应该看到变量资源管理器中出现了四个新数组。下一步是使用所选算法对数据进行训练:

# Fitting the model
model = DummyRegressor(strategy = 'mean')
model.fit(x_train, y_train)

请注意,虚拟回归可以采用以下任何策略作为参数:

mean

总是预测训练集的均值

median

总是预测训练集的中位数

quantile

总是预测训练集的指定分位数,由分位数参数提供

constant

总是预测由用户提供的常量值

如你从前面的代码中看到的,所选参数是mean。这自然意味着所有的预测都将简单地是训练集y_train的均值。这就是为什么虚拟回归只被用作基准,而不是作为严肃的机器学习模型。

下一步是对测试数据进行预测,以及对训练数据进行比较。请注意,对训练数据的预测没有价值,因为算法在训练期间已经见过数据,但了解算法在从未见过的数据上表现得更好或更差是很有意思的:

# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

为了确保你对使用虚拟回归算法的推理是正确的,手动计算y_train的平均值,并将其与每个y_predicted的值进行比较。你会发现它们是相同的:

# Comparing the mean of y_train to an arbitrary value in y_predicted
y_train.mean() == y_predicted[123]

输出应该如下所示:

True

最后,使用以下函数绘制最后的训练数据,然后是第一个测试数据和相应的预测数据:

# Plotting
plot_train_test_values(100, 50, y_train, y_test, y_predicted)
注意

你可以在本书的GitHub 仓库中找到plot_train_test_values()函数的定义。

图 7-2 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。显然,虚拟回归算法预测一个常数值,这就是为什么在测试值旁边的预测线是一条直线。

图 7-2. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线代表测试期的开始。所使用的模型是虚拟回归算法。
注意

如果你想要在单独的窗口中绘制图形,请在控制台中输入**%matplotlib qt**。如果你想要图形显示在绘图资源管理器中,请在控制台中输入**%matplotlib inline**

你如何判断一个模型表现好还是不好?性能评估是交易和算法开发中的关键概念,因为它确保你选择了正确的模型并将其实施。然而,由于一个讽刺的简单问题,任务并不简单:如果过去的表现很好,能保证未来也会表现良好吗?

这个问题很痛苦,但它指向了正确的方向。对这个问题的答案是主观的。现在,让我们谈谈衡量模型性能的不同方法。为了简化任务,我将性能和评估指标分为两部分:模型评估和交易评估。模型评估关注算法在预测中的表现,而交易评估关注使用算法进行交易的系统的财务表现(交易评估指标的一个示例是净利润)。

让我们从模型评估开始。准确率 是比较预测与实际值时首先考虑的指标,尤其是在金融市场中。理论上,如果您预测方向(上涨或下跌)并且预测正确,您应该赚钱(不包括交易成本)。在金融术语中,准确率也称为命中率,计算方法如下:

Accuracy = Correctpredictions Totalpredictions × 100

例如,如果您去年进行了 100 次预测,其中 73 次正确,那么您的准确率为 73%。

预测还可以通过预测值(y_predicted)与实际值(y_test)的接近程度来评估。这是通过损失函数完成的。损失函数 是衡量预测值与实际(测试)值之间差异的数学计算。最基本的损失函数是平均绝对误差(MAE)。它衡量预测值和实际值之间绝对差异的平均值。MAE 的数学表示如下:

M A E = ∑ i=1 n |y ^-y i | n

y ^ is the predicted value y is the real value

因此,MAE 计算预测值和实际值之间的平均距离(或正差异)。MAE 越低,模型越准确。

均方误差(MSE)是回归常用的损失函数之一。它衡量预测值和实际值之间平方差的平均值。您可以将 MSE 视为第三章中所见方差指标的等价物。MSE 的数学表示如下:

M S E = ∑ i=1 n (y ^-y i ) 2 n

因此,MSE 计算预测值和实际值之间的平方距离的平均值。与 MAE 类似,MSE 值越低,模型越准确。有鉴于此,比较同类产品(例如方差和标准差,如第三章所示)非常有帮助。因此,均方根误差(RMSE)已被开发用于解决此问题(因此,将误差指标缩放回与目标变量相同的单位)。RMSE 的数学表示如下:

R M S E = ∑ i=1 n (y ^-y i ) 2 n

RMSE 相当于描述性统计中的标准偏差。

注意

MAE 对异常值的敏感性相对较低,比 MSE 更少,通常在数据包含极端值或错误的绝对值比其平方值更重要时使用。另一方面,由于 MSE 更重视较大的错误,因此在试图提高模型性能时是首选的损失函数。

在使用 MAE、MSE 或 RMSE 评估模型时,重要的是有一个比较基准:

  • 如果您已建立多个回归模型,则可以比较它们的度量标准,以确定哪个模型表现更好。通常情况下,具有较低度量标准的模型被认为在预测中更准确。

  • 根 根据具体问题,您可能会对被视为可接受的预测误差水平设定一个阈值。例如,在某些情况下,低于某个阈值的 RMSE 可能被认为是令人满意的,而高于该阈值的值可能被认为是不可接受的。

  • 您可以将训练数据的损失函数与测试数据的损失函数进行比较。

算法有时可能会出现多种原因的方向性偏见(无论是结构上的还是外部的)。有偏模型 在一个方向上进行的交易明显多于另一个方向(例如,一个算法有 200 个多头和 30 个空头)。模型偏差 将这种情况表示为一个比值,通过将多头头寸数除以空头头寸数。理想的模型偏差约为 1.00,这意味着一个平衡的交易系统。模型偏差的数学表示如下:

Model bias = Numberofbullishsignals Numberofbearishsignals

如果一个模型今年有 934 个多头和 899 个空头,那么该模型的偏差度量为 1.038,这是可以接受的。这意味着该模型实际上没有偏见。值得注意的是,偏差度量为 0.0 表示没有任何看涨信号,而具有未定义值的偏差度量表示没有任何看跌信号(因为被零除)。

现在我们将把注意力转向交易评估。金融先驱们一直在开发度量标准,用于衡量策略和投资组合的表现。让我们讨论一下最常见和最有用的度量标准。最基本的度量标准是净收益,它实质上是在至少有一个已平仓交易的交易期之后的投资资本回报。净收益的数学表示如下:

Net return = ( Finalvalue Initialvalue - 1 ) × 100

净值 一词意味着扣除费用后的结果;否则,它被称为毛收益。例如,如果您年初有 52,000 美元,年底为 67,150 美元,您将获得 29.13% 的收益率(净利润为 15,150 美元)。

另一个盈利能力度量标准是利润因子,它是总毛利润与总毛亏损的比率。直观地,利润因子大于 1.00 意味着一个盈利策略,而小于 1.00 意味着一个亏损策略。利润因子的数学表示如下:

Profit factor = Grossprofits Grosslosses

利润因子是评估交易策略盈利能力的一个有用度量标准,因为它同时考虑了策略产生的利润和损失,而不仅仅是看一方面。一个交易策略的利润因子,其利润为 54,012 美元,损失为 29,988 美元,为 1.80。

下一个有趣的指标与个别交易有关。每笔交易的平均盈利计算基于历史数据的每笔交易的平均利润(或正收益),而每笔交易的平均亏损计算基于历史数据的每笔交易的亏损(或负收益)。这两个指标根据以下公式计算:

Average gain = Totalprofit Numberofwinningtrades Average loss = Totallosses Numberoflosingtrades

下一个指标与风险相关,是评估的最重要指标之一。最大回撤是一种衡量投资或投资组合价值从历史最高峰值到最低点的最大百分比下降的指标。它通常用于评估投资或投资组合的下行风险。例如,如果一个投资的峰值为 10 万美元,其价值随后下跌至 5 万美元后恢复,那么最大回撤将为 50%,即从峰值到谷底的百分比下降。最大回撤的计算方法如下:

Maximum drawdown = ( Troughvalue-Peakvalue Peakvalue ) × 100

最后,让我们讨论一个众所周知的盈利能力比率,称为夏普比率。它衡量了单位超额风险所产生的回报。该比率的公式如下:

S h a r p e = μ-r σ

  • μ 是净收益

  • r 是无风险利率

  • σ 是收益波动率

因此,如果净收益率为 5%,无风险利率为 2%,而收益波动率为 2.5%,则夏普比率为 1.20。任何高于 1.00 的值都是理想的,因为它意味着策略产生了正的超额风险调整回报。

本书的重点是开发机器和深度学习算法,因此性能评估步骤将仅专注于准确性、RMSE 和模型偏差(附带预测变量之间的相关性作为额外指标)。性能函数可以在 GitHub 存储库中找到,以及完整的脚本。

应用性能指标后,模型在 EURUSD 上的结果如下:

Accuracy Train =  49.28 %
Accuracy Test =  49.33 %
RMSE Train =  0.0076467838
RMSE Test =  0.0053250347
Model Bias =  0.0

偏差为 0.0 时,很容易看出这是一个虚拟的回归模型。偏差意味着根据公式,所有的预测都是看跌的。仔细查看预测的细节,你会发现它们都是恒定值。

注意

本节的主要要点如下:

  • 自动数据导入和创建节省时间,让您可以专注于算法的主要问题。

  • 为了进行适当的回测,数据必须分成训练集和测试集。

  • 训练集包含x_trainy_train,前者包含了被认为对后者具有预测能力的值。

  • 测试集包含x_testy_test,前者包含了被认为对后者具有预测能力的值(即使模型在训练中没有遇到过它们)。

  • 将数据拟合是指算法在训练集上运行;预测数据是指算法在测试集上运行。

  • 预测存储在名为y_predicted的变量中,用于性能评估目的与y_test进行比较。

  • 算法的主要目标是具有良好的准确性和稳定的低波动率回报。

机器学习模型

本节介绍了一些使用迄今为止开发的框架的机器学习模型的选择。了解每个模型的优缺点非常重要,这样您就知道根据预测任务选择哪种模型。

线性回归

线性回归算法通过找到最佳拟合线来最小化预测值和实际目标值之间的平方差。在此算法中最常用的优化技术是普通最小二乘(OLS)方法。²

该模型使用 OLS 方法在训练集上进行训练,该方法估计最小化预测值和实际目标值之间平方差的系数,以找到独立变量的最优系数(系数分别代表最佳拟合线的y-截距和斜率)。输出是一个线性函数,根据系数加权解释变量给出期望的回报,并对噪声和截距进行调整。

要从sklearn导入线性回归库,请使用以下代码:

from sklearn.linear_model import LinearRegression

现在让我们看看算法的实现:

# Fitting the model
model = LinearRegression()
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

该模型假设过去保持的线性关系在未来仍将保持不变。这是不现实的,并忽视了市场动态和驱动因素不断变化的事实,无论是短期还是长期。它们也是非线性的。

图 7-3 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图 7-3. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。使用的模型是线性回归算法。

在应用性能指标后,EURUSD 的模型结果如下:

Accuracy Train =  58.52 %
Accuracy Test =  49.54 %
RMSE Train =  0.007096094
RMSE Test =  0.0055932632
Correlation In-Sample Predicted/Train =  0.373
Correlation Out-of-Sample Predicted/Test =  0.014
Model Bias =  0.93

结果表明,线性回归算法的表现不佳,准确率低于 50.00%。正如您所看到的,切换到测试集后,准确率通常会下降。样本内预测与实际样本值之间的相关性也从 0.373 下降到 0.014。模型偏差接近均衡,这意味着长信号数量接近短信号数量。

模型结果有几个要注意的地方:

  • 交易成本未被纳入考虑,因此这些是毛收益结果(非净结果)。

  • 由于这是一个纯粹的时间序列机器学习模型,而不是包含止损和目标的完整交易算法,因此没有风险管理系统。因此,由于这是一个纯粹的定向模型,任务是尽量提高正确预测的数量。在每日时间段内,您正在寻找准确性。

  • 不同的外汇数据提供商可能在历史数据上有细微差异,这可能导致回测之间的一些差异。

模型被设计为优化和调整。优化过程可能包括以下任一技术:

选择正确的预测因子对模型的成功至关重要。

在本章中,使用的预测因子是滞后收益率。这是任意选择的,并不一定是正确的选择。必须基于经济和统计直觉选择预测因子。例如,选择黄金的回报来解释(预测)标准普尔 500 指数的波动可能是合理的,因为它们在经济上有关联。避险资产如黄金在经济不确定时期上涨,而股市则倾向于下跌。这种负相关可能隐藏了这两种工具之间的潜在模式。选择预测因子的另一种方法是使用技术指标,如相对强度指数(RSI)和移动平均线。

适当的分割对正确评估模型至关重要。

训练测试分离很重要,因为它们决定评估的窗口。通常使用 20/80 和 30/70,这意味着数据的 20%(30%)用于测试样本,80%(70%)用于训练样本。

正则化技术可以帮助防止偏差。

岭回归和 Lasso 回归是线性回归中常见的两种正则化方法。岭回归在 OLS 函数中增加惩罚项,以减少大系数的影响,而Lasso 回归可以将一些系数驱动为零,有效进行特征选择。

本节中看到的模型被称为自回归模型,因为因变量取决于其过去的值,而不是外生数据。此外,由于在每个时间步骤中使用了 500 个不同的变量(及其系数)来预测下一个变量,因此该模型被称为多元线性回归模型。相比之下,当模型仅使用一个因变量来预测依赖变量时,它被称为简单线性回归模型。

线性回归的优点是:

  • 实施和训练都很容易。它也不会消耗大量内存。

  • 在数据具有线性依赖性时表现优越。

线性回归的缺点是:

  • 对异常值敏感。

  • 它很容易出现偏差(更多关于这种类型偏差的信息请参见“过拟合与欠拟合”)。

  • 它具有不切实际的假设,例如数据的独立性。

在进入下一节之前,重要的是注意一些线性回归模型不会对数据进行转换。你可能看到非常高的准确度和非常接近真实数据的预测,但实际上预测是滞后一个时间步长的。这意味着在每个时间步长,预测值只是上一个真实值。让我们用之前的例子来证明这一点。使用与之前相同的代码,但省略价格差分代码。你应该看到图 7-4。

图 7-4. 非平稳训练数据接着测试数据(虚线)和预测数据(细线);垂直虚线代表测试期的开始。使用的模型是线性回归算法。

注意它仅仅是滞后于真实值,并没有添加任何预测信息。处理此类模型时,始终要对非平稳数据进行转换。非平稳数据不能使用此类算法进行预测(当然也有例外,稍后会看到)。

在非平稳数据(如市场价格)上使用线性回归,并观察到预测结果与上一个值相同可能表明存在一种称为天真预测的问题。这种情况发生在最近的观察值(在本例中是上一个值)仅被用作下一个时间段的预测值。虽然这种方法有时对某些类型的数据有效,但通常不是一种复杂的预测方法,可能无法捕捉数据中的潜在模式或趋势。导致这种情况发生的原因有几个:

缺乏预测能力

线性回归假设自变量与因变量之间存在线性关系。如果数据高度非平稳且缺乏明确的线性关系,那么线性回归模型可能无法捕捉到有意义的模式,并会默认为类似天真预测的简单预测。

滞后指标

市场价格通常表现出强烈的自相关性,这意味着当前价格与先前价格高度相关。在这种情况下,如果模型仅考虑滞后值作为预测因子,它可能只是将最后一个值复制为预测值。

缺乏特征工程

线性回归模型依赖于您提供的特征(预测因子)来进行预测。如果您只使用滞后值作为预测因子,而没有整合其他相关特征,模型可能会难以生成有意义的预测。

模型复杂性

线性回归是一种相对简单的建模技术。如果数据中的基本关系比线性方程能够捕捉到的更复杂,那么该模型可能无法进行准确的预测。

支持向量回归

支持向量回归(SVR)是一种机器学习算法,属于 支持向量机(SVM)家族。SVR 专门设计用于回归问题,其目标是预测连续的数值(例如返回值)。

SVR 通过在高维特征空间中找到一个最佳超平面来执行回归,该超平面最佳地逼近输入特征和目标变量之间的关系。与传统回归技术不同,传统回归技术旨在最小化预测值与实际值之间的误差,SVR 则专注于找到一个能够捕捉数据中大部分数据的超平面,即所谓的 epsilon tube(损失函数)。

SVR 的关键思想是使用核函数将原始输入空间转换为更高维度的空间。这种转换允许 SVR 隐式地将数据映射到更高维的特征空间,在这个空间中更容易找到特征与目标变量之间的线性关系。核函数计算两个数据点之间的相似性,使得 SVR 算法能够有效地处理非线性回归问题。SVR 过程中执行的步骤如下:

  1. 算法使用核函数将输入特征转换为更高维度的空间。常见的核函数包括线性核函数、多项式核函数、径向基函数(RBF)核函数和 sigmoid 核函数。核的选择取决于数据和潜在问题。

  2. 算法然后旨在找到最佳拟合数据点的超平面,这些数据点在 epsilon 管道内。训练过程涉及解决优化问题,以最小化误差(使用例如 MSE 的损失函数)同时控制间隔。

注意

RBF 核函数是 SVR 的流行选择,因为它能有效捕捉非线性关系。当对关系的具体形式没有先验知识时,它是合适的选择。RBF 核函数根据输入空间中特征向量之间的距离计算它们的相似性。它使用一个称为 gamma 的参数,决定每个训练样本对模型的影响。较高的 gamma 值使模型更加关注单个数据点,可能导致错误。

通过在 epsilon 管道内找到一个最优超平面,SVR 可以有效地捕捉数据中的潜在模式和关系,即使在存在噪声或异常值的情况下也是如此。对于回归任务,特别是处理特征与目标变量之间的非线性关系时,它是一种强大的技术。

由于 SVR 对特征的尺度敏感,将所有特征带到相似的尺度上是很重要的。常见的缩放方法包括 标准化(减去平均值并除以标准差)和 归一化(将特征缩放到范围,例如 [0, 1])。

让我们看看 SVR 的运作方式。再次,目标是根据先前的收益预测下一个 EURUSD 的收益。要导入 SVR 库和缩放库,请使用以下代码:

from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

对于 SVR 算法,进行了一些微调以获得可接受的预测结果。调整是将滞后值的数量从 500 减少到 50:

num_lags = 50

这使得 SVR 算法能够改善其预测能力。在本书中您将看到,执行这些类型的回测的一部分是调整和校准模型。

接下来,要实现算法,请使用以下代码:

# Fitting the model
model = make_pipeline(StandardScaler(), 
                      SVR(kernel = 'rbf', C = 1, gamma = 0.04, 
                      epsilon = 0.01))
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

图 7-5 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变过程。

图 7-5。训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。使用的模型是 SVR 算法。

模型的结果如下:

Accuracy Train =  57.94 %
Accuracy Test =  50.14 %
RMSE Train =  0.0060447699
RMSE Test =  0.0054036167
Correlation In-Sample Predicted/Train =  0.686
Correlation Out-of-Sample Predicted/Test =  0.024
Model Bias =  0.98

SVR 的优点包括:

  • 它在高维特征空间中表现良好,即特征数量远远大于样本数量时。在处理复杂数据集时特别有用。

  • 它可以通过使用核函数捕捉输入特征与目标变量之间的非线性关系。

  • 由于ε-tube 公式的存在,它对训练数据中的异常值具有鲁棒性。该模型专注于将大多数数据拟合在指定边界内,从而减少异常值的影响。

SVR 的缺点包括:

  • 它有几个需要调整以达到最佳性能的超参数。选择适当的超参数可能是一项具有挑战性的任务,并且可能需要进行广泛的实验。

  • 它可能计算成本较高,特别是对于大型数据集或使用复杂核函数时。

  • 它可能对超参数的选择敏感。选择不当的超参数可能导致拟合问题。

随机梯度下降回归

梯度下降(GD)是一种常用的优化算法,用于最小化模型的成本或损失函数,它也是各种优化算法的基础。

注意

梯度简单地指的是表面的斜率或倾斜度。要到达表面的最低点,必须沿着斜坡下降。

随机梯度下降(SGD)是一种常用的迭代优化算法,用于训练机器学习模型,包括回归模型。它特别适用于大型数据集和在线学习场景。当应用于时间序列预测时,SGD 可用于训练能够捕捉时间模式并基于历史数据进行预测的回归模型。因此,SGD 是一种使用随机梯度下降优化的线性回归类型。

与普通的最小二乘法不同,SGD 会迭代地更新模型的参数,使其更适用于大型数据集(以小批量方式处理)。SGD 不是使用整个数据集进行每次更新步骤,而是随机选择训练数据集中的一小批样本或单个样本。这种随机选择有助于引入随机性并避免陷入局部最优解(您可以参考第四章了解更多关于优化的信息)。GD 和 SGD 之间的主要区别在于它们在优化期间如何更新模型的参数。

注意

SGD 不属于任何特定的机器学习模型家族;它本质上是一种优化技术。

GD 计算整个训练数据集上的梯度,每个时代更新一次模型的参数,而 SGD 基于单个训练示例或小批量计算梯度,更频繁地更新参数。SGD 更快但表现更不稳定,而 GD 更慢但具有更平滑的收敛轨迹。SGD 对局部最小值也更具鲁棒性。选择 GD 还是 SGD 取决于问题的具体要求、数据集的大小以及计算效率和收敛行为之间的权衡。

与往常一样,第一步是导入必要的库:

from sklearn.linear_model import SGDRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

接下来,要实现该算法,请使用以下代码:

# Fitting the model
model = make_pipeline(StandardScaler(), SGDRegressor(max_iter = 50, 
                                                     tol = 1e–3))
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

图 7-6 显示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图片

图 7-6。训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 SGD 算法。

模型的结果如下:

Accuracy Train =  55.59 %
Accuracy Test =  46.45 %
RMSE Train =  0.007834505
RMSE Test =  0.0059334014
Correlation In-Sample Predicted/Train =  0.235
Correlation Out-of-Sample Predicted/Test =  –0.001
Model Bias =  0.95

SGD 的优点包括:

  • 它在大型数据集上表现良好,因为它根据单个或小型训练示例逐步更新模型参数。

  • 它可以逃脱局部最小值并找到更好的全局最优解(由于其随机性质)。

  • 它可以通过在每次迭代中将模型暴露于不同的训练样本来提高泛化能力,从而减少过拟合。

SGD 的缺点包括:

  • 收敛路径可能会有噪声并且比确定性优化算法表现出更多的波动。这可能导致较慢的收敛或在最优解周围的振荡。

  • 它受特征缩放的影响,这意味着它对这种技术敏感。

最近邻回归

最近邻回归算法,也被称为k最近邻(KNN)回归,是一种非参数³算法,用于回归任务。它基于特征空间中最近邻的值来预测目标变量的值。该算法首先确定k,即在进行预测时要考虑的最近邻的数量。这是一个需要根据具体问题选择的超参数。

注意

较大的k值提供了更平滑的预测,而较小的k值捕捉更多局部变化,但可能更容易受到噪音的影响。

然后,模型计算新的未见数据点与训练集中所有数据点之间的距离。距离度量的选择取决于输入特征的性质。常见的距离度量包括欧氏距离、曼哈顿距离和闵可夫斯基距离。接下来,算法选择距离查询点最近的k个数据点。这些数据点是最近邻,将用于进行预测。

要导入 KNN 回归器,请使用以下代码:

from sklearn.neighbors import KNeighborsRegressor

现在让我们来看看算法的实现。使用k = 10 来拟合模型:

# Fitting the model
model = KNeighborsRegressor(n_neighbors = 10)
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

图 7-7 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图 7-7. 训练数据,随后是测试数据(虚线),以及预测数据(细线);垂直虚线表示测试期的开始。使用的模型是 KNN 回归算法。

使用 KNN 回归器进行时间序列预测时,选择邻居数量取决于多个因素,包括数据集的特性和所需的精度水平。没有一个确定的答案来确定选择多少个邻居,通常通过实验和验证来确定。通常选择适当的邻居数量涉及偏差和方差之间的权衡:

  • 较小的k值与能够捕捉数据中局部模式的模型相关联,但也可能对噪声或异常值敏感。

  • 较大的k值与能够更加抗噪声或异常值的模型相关联,但可能忽略数据中的局部模式。

注意

如果将k趋向于数据集大小的极限,将得到一个仅预测数据集中频率最高类别的模型。这被称为贝叶斯误差

模型的结果如下:

Accuracy Train =  67.69 %
Accuracy Test =  50.77 %
RMSE Train =  0.0069584171
RMSE Test =  0.0054027335
Correlation In-Sample Predicted/Train =  0.599
Correlation Out-of-Sample Predicted/Test =  0.002
Model Bias =  0.76

注意

在考虑您的时间序列数据时,考虑其时间特性是至关重要的。如果存在跨多个数据点的明显趋势或模式,较大的k值可能更适合捕捉这些依赖关系。然而,如果时间序列展示出快速变化或短期波动,较小的k值可能更合适。

你的数据集的大小也可能影响k的选择。如果数据集很小,选择较小的k值可能更好,以避免过拟合。相反,较大的数据集可以容忍更高的k值。

KNN 的优点包括:

  • 它的非线性允许捕捉金融数据中的复杂模式,这对于预测可能表现出非线性行为的回报序列有优势。

  • 它可以适应变化的市场条件或模式。由于算法是基于实例的,当新数据可用时,不需要重新训练模型。这种适应性在金融回报的情境中可能是有益的,因为市场动态可以随时间变化。

  • 它为预测提供直观的解释。由于该算法选择k个最近邻来进行预测,相对于更复杂的算法,理解和解释可能更容易。

KNN 的缺点包括:

  • 在处理高维数据时,其性能可能会下降。金融回报序列通常涉及多个预测因子(如技术指标和其他相关回报),KNN 可能难以在高维空间中找到有意义的邻居。

  • 随着数据集的增大,KNN 的计算要求可能变得显著。

  • 它对噪声或异常数据点敏感,因为该算法平等地考虑所有邻居。

决策树回归

决策树是多功能且直观的机器学习模型。它们是基于特征值的一系列决策或选择的图形表示,这些选择导致不同的结果。决策树结构化为分层流程图,其中每个内部节点代表基于特征的决策,每个分支表示该决策的结果,每个叶节点表示最终的预测或类标签。

在决策树的根部,考虑所有的输入特征,并选择基于特定标准(例如在第二章中讨论的信息增益度量)最佳分离数据的特征。创建与所选特征相关联的决策节点。根据所选特征的可能值来分割数据。递归地重复前面的步骤,考虑每个节点处剩余的特征,直到达到停止条件为止,例如达到最大深度、节点中的最小样本数,或者在纯度或增益上不再有进一步的改善。

要导入决策树回归器,请使用以下代码:

from sklearn.tree import DecisionTreeRegressor

现在让我们看一下算法的实现:

# Fitting the model
model = DecisionTreeRegressor(random_state = 123)
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

注意

参数random_state通常用于初始化包含随机性的算法,比如初始化权重。这确保了如果你多次使用相同的random_state来训练模型,你将得到相同的结果,这对于比较不同的算法或超参数是很重要的。

图 7-8 显示了预测任务从y_train的最后值到y_testy_predicted的第一个值的演变。

图 7-8. 训练数据接着是测试数据(虚线)和预测数据(细线);垂直虚线代表测试期的开始。使用的模型是决策树回归算法。

模型的结果如下:

Accuracy Train =  100.0 %
Accuracy Test =  47.37 %
RMSE Train =  0.0
RMSE Test =  0.007640736
Correlation In-Sample Predicted/Train =  1.0
Correlation Out-of-Sample Predicted/Test =  –0.079
Model Bias =  0.94

注意训练集的准确度非常高。这明显是过度拟合的证据(受训练数据的 RMSE 支持)。

决策树的优点是:

  • 它们需要最少的数据预处理并且可以处理缺失值。

  • 它们可以捕捉非线性关系、交互作用和变量重要性。

决策树的缺点是:

  • 如果没有适当正则化,它们可能对数据中的细微变化敏感,容易过度拟合。

  • 它们可能难以捕捉需要更深树的复杂关系。

下一节介绍了另一类机器学习算法。这些被称为集成算法。可以使用集成方法将决策树组合起来以创建更稳健和准确的模型。随机森林是下一节中看到的算法,它结合了多个决策树以增强预测能力,尤其是减少过度拟合的风险。

随机森林回归

随机森林是一种利用多个决策树的力量形成单一输出(预测)的机器学习算法。它灵活且不需要太多调整。由于其集成学习技术,它也不太容易过度拟合。集成学习指的是将多个学习器(模型)组合起来以改善最终预测。

使用随机森林,多个学习器是不同的决策树,它们会汇聚到一个单一的预测。

因此,在随机森林算法中可以调整的超参数之一是决策树的数量。该算法使用装袋法。在随机森林的背景下,装袋指的是自助聚合技术,旨在通过减少偏差来改善机器学习模型(如决策树)的性能和鲁棒性。以下是随机森林算法中装袋的工作原理:

  1. 自助采样:随机森林采用自助采样,这意味着通过替换抽样创建原始训练数据的多个子集。每个子集的大小与原始数据集相同,但可能包含重复实例并排除其中一些。这个过程对随机森林中的每棵树都是独立进行的。

  2. 树构建和特征选择:对于每个自助采样,使用一种称为递归分区的过程构建决策树,其中数据基于特征进行分割,以创建优化目标变量分离的分支。在决策树的每个节点,考虑一个随机子集的特征进行分割。这有助于在森林中引入多样性,并防止它们过于依赖单一主导特征。

  3. 集成预测:一旦所有树都构建完成,通过聚合各个树的输出进行预测。对于回归任务,预测值取平均值。

要导入随机森林回归器,请使用以下代码:

from sklearn.ensemble import RandomForestRegressor

现在让我们来看看算法的实现:

# Fitting the model
model = RandomForestRegressor(max_depth = 20, random_state = 123)
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

注意

max_depth超参数控制随机森林中每棵决策树的深度。深度较大的决策树可以捕捉数据中更复杂的模式,但也更容易过拟合,这意味着在训练数据上表现很好但在未见数据上表现不佳。另一方面,较浅的树可能无法捕捉数据的所有细节,但在新的未见数据上可能更具一般化能力。

图 7-9 显示了从y_train的最后值到y_testy_predicted的第一个值的预测任务演变过程。

图 7-9. 训练数据后跟测试数据(虚线),以及预测数据(细线);垂直虚线表示测试期的开始。所用模型是随机森林回归算法。

模型的结果如下:

Accuracy Train =  82.72 %
Accuracy Test =  50.15 %
RMSE Train =  0.0058106512
RMSE Test =  0.0053452064
Correlation In-Sample Predicted/Train =  0.809
Correlation Out-of-Sample Predicted/Test =  –0.049
Model Bias =  0.63

随机森林回归的优点如下:

  • 由于其集成的特性,通常对数据有准确的预测。由于金融时间序列具有高噪声和边际随机性,尽管如此,其结果仍需优化。

  • 由于其平均化的特性,它对噪声和异常值表现出鲁棒性。

随机森林回归的缺点如下:

  • 有时可能难以解释。由于使用聚合方法,当使用大量树时可能会丢失真实和最终决策。

  • 随着树的数量增加,算法的计算时间变长,训练过程缓慢。

AdaBoost 回归

在理解 AdaBoost 算法之前,让我们先讨论梯度提升,这样就更容易理解其背后的算法。梯度提升是一种基于改进弱学习器(指表现略好于随机的模型)思想构建模型的技术。

提升这些弱学习器的方法是针对它们的弱点创建其他能处理这些弱点的弱学习器。这就诞生了所谓的自适应增强,简称AdaBoost。因此,通俗来说,增强就是将弱学习器结合起来形成更好的模型。

AdaBoost 中的学习器(如前所述,是弱的)是单分裂的决策树(称为stumps)。它们被赋予权重,对于更难分类的实例放置更多的权重,对其余实例放置较少的权重。同时,新的学习器被纳入以在困难部分进行训练,从而创建一个更强大的模型。因此,困难的实例获得更大的权重,直到它们被新的弱学习器解决。

预测基于弱学习器的投票。采用多数原则以最大化准确性。因此,梯度提升可以概括为三个步骤:

  1. 它按顺序构建了一组弱预测模型,通常是决策树。

  2. 每个后续模型都是为了纠正先前模型的错误或残差而构建的,使用梯度下降来调整预测以最小化总体错误。

  3. 所有模型的预测通过加权平均或总和来组合,加权由学习率确定,以产生最终预测。

要导入 AdaBoost 回归器,请使用以下代码:

from sklearn.ensemble import AdaBoostRegressor

现在让我们看一下算法的实现:

# Fitting the model
model = AdaBoostRegressor(random_state = 123)
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

图 7-10 显示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图 7-10. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 AdaBoost 回归算法。

模型的结果如下:

Accuracy Train =  53.27 %
Accuracy Test =  51.7 %
RMSE Train =  0.0070124217
RMSE Test =  0.0053582343
Correlation In-Sample Predicted/Train =  0.461
Correlation Out-of-Sample Predicted/Test =  0.017
Model Bias =  0.72

AdaBoost 的优点是:

  • 它通常具有很高的准确性。

  • 它很容易理解。

AdaBoost 的缺点是:

  • 它受异常值的影响,对噪声敏感。

  • 它速度较慢,且未经优化。

XGBoost 回归

XGBoost是一种快速且性能优异的梯度提升决策树算法。这个名字可能很复杂,但如果你理解了 AdaBoost 上一节中的梯度提升,就不难理解。XGBoost 代表极端梯度提升,是由陈天奇创建的。它的工作原理如下:

  1. XGBoost 从一个简单的基础模型开始,通常是一个决策树。

  2. 它定义了一个衡量模型性能的目标函数。

  3. 使用梯度下降优化,通过根据目标函数的梯度调整模型来迭代地改进模型的预测。

  4. 新的决策树被添加到集合中以纠正先前模型的错误。

  5. 采用正则化技术,如学习率和列子采样,以增强性能并防止拟合问题。

  6. 最终预测是通过组合集合中所有模型的预测得到的。

Python 中 XGBoost 的实现比前面的算法需要更多步骤。第一步是pip install所需的模块。在提示符中输入以下命令:⁴

pip install xgboost

要导入 XGBoost 库,请使用以下代码:

from xgboost import XGBRegressor

算法的实现如下所示:

# Fitting the model
model = XGBRegressor(random_state = 123, n_estimators = 16, 
                     max_depth = 12)
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

注意

参数n_estimators是一个超参数,用于确定要在集成中构建的提升回合或树的数量。由于算法结合多个弱学习器(单个决策树)的预测来创建强预测模型,每个提升回合(迭代)将一个新的决策树添加到集成中,并且算法从之前树的错误中学习。n_estimators超参数控制在训练过程中将添加到集成中的树的最大数量。

AdaBoost 和 XGBoost 都是用于增强弱学习器(通常为决策树)预测能力的提升算法。AdaBoost 专注于使用指数损失迭代强调误分类样本,缺乏内置正则化,并且并行化能力有限。相比之下,XGBoost 利用梯度提升,支持各种损失函数,提供正则化,处理缺失值,通过并行化更好地扩展,提供全面的特征重要性,并允许更广泛的超参数调整。

因此,XGBoost 提供了更高级的功能。它通常因为整体性能更好和处理复杂任务的能力而受到青睐。然而,两者之间的选择取决于具体的问题、数据集和可用的计算资源。

图 7-11 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图 7-11。训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。使用的模型是 XGBoost 回归算法。

模型的结果如下所示:

Accuracy Train =  75.77 %
Accuracy Test =  53.04 %
RMSE Train =  0.0042354698
RMSE Test =  0.0056622704
Correlation In-Sample Predicted/Train =  0.923
Correlation Out-of-Sample Predicted/Test =  0.05
Model Bias =  6.8

过拟合和欠拟合

机器学习预测分析中会出现问题,这是完全正常的,因为在数据科学(和金融)世界中,“完美”是一个不可能的词。本节涵盖了在预测数据时最重要的问题,即“拟合问题”。过度拟合和欠拟合是两个术语,您必须彻底了解它们,以避免在运行模型时遇到它们的后果。

过拟合发生在模型在训练数据上表现极好但在测试数据上结果糟糕的情况下。这表明模型不仅学习了样本内数据的细节,还学习了发生的噪声。过拟合通常与高方差和低偏差模型相关,但这两个术语的含义是什么?

偏差是指模型预测的期望值与目标变量的实际值之间的差异。低偏差模型是足够复杂,可以捕捉数据中的潜在模式。

方差指的是模型在不同训练集上预测结果的变化程度。高方差模型过于复杂,可能捕捉到训练数据中的随机噪声和波动,导致过拟合。这会使模型适应数据中的噪声。

为了避免过拟合,重要的是在偏差和方差之间找到平衡,选择一个足够复杂以捕捉数据中潜在模式的模型,但不要过于复杂以至于捕捉到数据中的随机噪声和波动。正则化技术也可以用来减少方差,防止过拟合。

过拟合出现的原因有多种,尤其包括:

数据不足

如果训练数据不够多样化,或者数据量不足,模型可能会对训练数据过拟合。

过度复杂的模型

如果模型过于复杂,可能会学习到数据中的噪声而非潜在模式。

特征过载

如果模型训练时使用了过多的特征,可能会学习到不具有泛化能力的无关或噪声特征。

缺乏正则化

如果模型的正则化不合适,可能会导致对训练数据的过拟合。

泄露

泄露发生在测试集的信息被无意中包含在训练集中时。这会导致过拟合,因为模型在学习后期测试时将再次见到这些数据。

高偏差模型过于简化,无法捕捉数据中真实的潜在模式。这可能导致欠拟合。类似地,低方差模型对训练数据的小变化不敏感,并且能够很好地泛化到未见过的新数据。

欠拟合出现的原因有多种,尤其包括:

模型复杂性不足

如果所用模型过于简单,无法捕捉数据中的潜在模式,可能导致欠拟合。例如,线性回归模型可能无法捕捉特征与目标变量之间的非线性关系。

训练不足

如果模型训练时间不够长,或者数据量不足,可能无法捕捉数据中的潜在模式。

过度正则化

正则化是一种防止过拟合的技术,但如果使用过度,可能导致欠拟合。

特征选择不当

如果为模型选择的特征信息量不足或不相关,可能会导致欠拟合。

图 7-12 显示了模型与数据之间不同拟合的比较。欠拟合模型未能从一开始就捕获真实关系,因此在预测过去值和未来值方面表现不佳。良好拟合的模型捕获了数据的一般趋势。它不是精确或完美的模型,但通常在整个时间段内具有令人满意的预测。过拟合模型捕捉了过去的每一个细节,即使是噪音或随机位移。过拟合模型的危险在于它给未来带来了虚假的承诺。

图 7-12。不同的拟合情况。

因此,在构建用于时间序列预测的机器学习模型时,必须确保不调整参数以完美地拟合过去的值。为了减少拟合偏差,请确保在您的回测中包含以下最佳实践:

增加训练数据

收集更多的训练数据有助于捕获数据中更广泛的模式和变化,减少过拟合的机会。

特征选择

仔细选择与您的模型相关且信息丰富的特征。去除无关或冗余的特征可以减少数据中的噪音和复杂性,使模型更容易对未见示例进行良好泛化。

正则化技术

正则化方法明确控制模型的复杂性,以防止过拟合。

超参数调优

优化模型的超参数以找到最佳配置。超参数控制模型的行为和复杂性。

集成方法

使用集成方法,如随机森林,将多个模型的预测组合起来。集成方法可以通过聚合多个模型的预测结果,平滑个别模型的偏差,并提高泛化能力,从而减少过拟合。

定期模型评估

定期评估您的模型在未见数据或专门的验证集上的表现。这有助于监控模型的泛化能力,并检测过拟合或性能下降的任何迹象。

摘要

通过正确理解机器学习算法的来源,更容易解释它们并了解它们的局限性。本章提供了构建时间序列模型所需的知识(理论和实践),使用几种已知的机器学习算法来预测值,希望使用过去的值。

你必须明确知道的是,过去的价值不一定能反映未来的结果。回测总是存在某种程度的偏见,因为需要大量调整来调整结果,这可能会导致过度拟合。模式确实存在,但它们的结果不一定相同。金融时间序列预测的机器学习不断发展,大多数算法(以原始形式及其基本输入)都不太具有预测性,但通过适当的组合和添加风险管理工具和过滤器,您可能会得到一个可持续的算法,为整个框架增加价值。

¹ 为简单起见,价格差异将被称为收益。一般来说,收益也可以表示时间序列的百分比收益。

² 普通最小二乘法使用数学公式来估计系数。它涉及矩阵代数和微积分来求解最小化残差平方和的系数。

³ 一类不依赖于关于基础概率分布的特定假设的统计方法。

⁴ 提示符是一个命令行界面,通常可以在“开始”菜单中访问。它不同于您输入后将被执行的 Python 代码的区域。

第八章:时间序列预测的深度学习 I

深度学习比机器学习稍微复杂和更详细一些。机器学习和深度学习都属于数据科学的范畴。正如你将看到的那样,深度学习主要是关于神经网络,这是一种高度复杂和强大的算法,因为它非常强大并且能够捕捉不同变量之间高度复杂的非线性关系。

本章的目的是在使用 Python 预测金融时间序列之前,解释神经网络的功能,就像您在第七章中看到的那样。

逐步理解神经网络

人工神经网络(ANNs)起源于神经科学的研究,研究人员试图理解人脑及其复杂的互联神经网络如何运作。人工神经网络旨在产生生物神经网络行为的计算表示。

自 1940 年代以来,人工神经网络(ANNs)就存在了,当时学者们首次开始探索基于人脑构建计算模型的方法。逻辑学家沃尔特·皮茨和神经生理学家沃伦·麦卡洛克是这一学科的早期先驱者之一。他们在一篇文章中发布了基于简化人工神经元的计算模型的概念。¹

人工神经网络的发展在 20 世纪 50 年代和 60 年代进一步加速,当时像弗兰克·罗森布拉特这样的研究人员致力于感知器的研究,这是一种可以从其输入中学习的人工神经元类型。罗森布拉特的工作为单层神经网络的发展铺平了道路,这种网络能够进行模式识别任务。

随着多层神经网络(也称为深度神经网络)的创建以及更强大算法的引入,人工神经网络在 1980 年代和 1990 年代取得了显著进展。这一创新使神经网络能够学习层次化的数据表示,从而提高了它们在挑战性任务上的性能。尽管有多位研究者为人工神经网络的发展和进步做出了贡献,但一个有影响力的人物是杰弗里·辛顿。辛顿与他的合作者通过开发新的学习算法和神经网络结构,在该领域做出了显著贡献。他在深度学习方面的工作对人工神经网络的最近复兴和成功起到了重要作用。

人工神经网络由相互连接的节点组成,称为人工神经元,组织成不同层。这些层通常分为三种类型:

输入层

输入层接收输入数据,可以是数值、分类或者原始感官数据。输入层是解释变量,它们被认为具有预测性质。

隐藏层

隐藏层(一个或多个)通过其相互连接的神经元处理输入数据。每个层中的神经元接收输入,执行计算(稍后讨论),并将输出传递到下一层。

输出层

输出层根据来自隐藏层的处理信息产生最终结果或预测。输出层中的神经元数量取决于网络设计的问题类型。

图 8-1 展示了人工神经网络的示意图,信息从左到右流动。它始于两个输入连接到四个隐藏层,在输出层输出加权预测之前进行计算。

图 8-1. 人工神经网络的简单示意图。

人工神经网络中的每个神经元执行两个主要操作:

  1. 神经元从上一层或直接从输入数据接收输入。每个输入乘以权重值,表示该连接的强度或重要性。加权输入然后求和。

  2. 加权和之后,应用激活函数(下一节讨论)引入神经元输出的非线性。激活函数根据加权输入确定神经元的输出值。

在训练过程中,人工神经网络调整其连接的权重以改善其性能。通常通过迭代优化算法(如梯度下降)完成这一过程,其中网络的性能使用定义的损失函数进行评估。算法计算损失函数相对于网络权重的梯度,允许以最小化误差的方式更新权重。

**人工神经网络(ANNs)**具有从数据中学习和泛化的能力,使它们适用于诸如模式识别和回归的任务。随着深度学习的进展,具有多个隐藏层的人工神经网络在复杂任务上表现出色,利用其学习分层表示和捕捉数据中复杂模式的能力。

注意

值得注意的是,从输入到输出的过程称为前向传播

激活函数

在神经网络中,激活函数引入非线性到神经元的输出中,使神经网络能够建模复杂关系并从非线性数据中学习。它们根据输入的加权和确定神经元的输出。让我们详细讨论这些激活函数。

Sigmoid 激活函数将输入映射到 0 到 1 的范围内,适用于二元分类问题或作为阶跃函数的平滑近似。函数的数学表示如下:

S ( x ) = 1 1+e -x

图 8-2 展示了 Sigmoid 函数。

图 8-2. Sigmoid 函数的图形。

sigmoid 激活函数的优点包括以下几点:

  • 它是一个既平滑又可微的函数,有助于基于梯度的优化算法。

  • 它将输入压缩到有界范围内,可以解释为概率或置信水平。

但是,它也有其局限性:

  • 它存在梯度消失问题,即梯度在极端输入值下变得非常小。这可能会阻碍学习过程。

  • 输出不是零中心化的,这使得它在某些情况下不太适用,比如使用对称更新规则(如梯度下降)优化权重。

接下来的激活函数是双曲正切函数(tanh),你在第四章中见过。该函数的数学表示如下:

t a n h ( x ) = e x -e -x e x +e -x

双曲正切函数的优点包括以下几点:

  • 它类似于 sigmoid 函数,但是是零中心化的,这有助于减轻权重优化中非对称更新的问题。

  • 其非线性可以捕捉比 sigmoid 函数更广泛的数据变化范围。

以下是它的一些局限性:

  • 它遭受梯度消失问题,特别是在深度网络中。

  • 输出仍然容易在极端情况下饱和,导致梯度接近零。

图 8-3 展示了双曲正切函数。

图 8-3. 双曲正切函数的图像。

接下来的函数称为ReLU 激活函数。ReLU 代表修正线性单元。该函数将负值设为零,保持正值不变。它高效且有助于避免梯度消失问题。该函数的数学表示如下:

f ( x ) = m a x ( 0 , x )

ReLU 函数的优点包括以下几点:

  • 它实现简单,只需取 0 和输入值的最大值。ReLU 的简单性导致比更复杂的激活函数更快的计算和训练。

  • 它有助于缓解在深度神经网络训练中可能出现的梯度消失问题。ReLU 的导数要么是 0 要么是 1,这意味着梯度可以更自由地流动,避免随着网络深度增加而指数级变小。

该函数的局限性包括以下几点:

  • 对于负输入值,它输出 0,这可能导致信息丢失。在某些情况下,具有能够产生负输出的激活函数可能是有益的。

  • 它不是一个平滑的函数,因为它在 0 处的导数是不连续的。这可能会在某些场景下导致优化困难。

图 8-4 展示了 ReLU 函数。

图 8-4. ReLU 函数的图像。

最后要讨论的激活函数是泄漏整流线性单元激活函数。该激活函数是 ReLU 函数的扩展,为负输入引入了一个小的斜率。该函数的数学表示如下:

f ( x ) = m a x ( 0 . 01 x , x )

泄漏整流线性单元解决了 ReLU 中的死神经元问题,并允许负输入的某些激活,这有助于训练期间梯度的流动。

泄漏整流线性单元函数的优点包括以下几点:

  • 它克服了 ReLU 可能出现的死神经元问题。通过为负输入引入一个小的斜率,泄漏整流线性单元确保即使神经元未被激活,它仍然可以在训练期间对梯度流做出贡献。

  • 它是一个连续函数,即使在负输入值时也是如此。负输入的非零斜率允许激活函数在其输入范围内具有定义的导数。

函数的限制如下:

  • 泄漏部分的斜率是一个需要手动设置的超参数。它需要仔细调整,以在避免死神经元的同时防止过多的泄漏,可能会阻碍激活函数的非线性。

  • 尽管泄漏整流线性单元为负输入提供了非零响应,但在负激活水平方面,它与某些其他激活函数(如双曲正切(tanh)和 S 形函数)不同。在需要强烈的负激活响应的场景中,其他激活函数可能更合适。

图 8-5 展示了泄漏整流线性单元(leaky ReLU)函数。

你选择的激活函数取决于问题的性质、网络的结构以及网络中神经元的期望行为。

激活函数通常获取神经元输入的加权和,并对其应用非线性变换。转换后的值随后作为神经元的输出传递到网络的下一层。激活函数的具体形式和行为可能有所不同,但它们的整体目的是引入非线性,使网络能够学习数据中的复杂模式和关系。

图 8-5. 泄漏整流线性单元函数的图示。

总之,激活函数通过引入非线性,对人工神经网络的计算起到关键作用。它们应用于单个神经元或中间层的输出,并根据其接收到的输入决定神经元是否应激活。如果没有激活函数,网络只能学习输入和输出之间的线性关系。然而,大多数现实世界的问题(特别是金融时间序列)涉及复杂的非线性关系,因此激活函数对于使神经网络有效地学习和表示这些关系至关重要。

反向传播

反向传播 是用于训练神经网络的基本算法。它使网络能够以最小化预测输出与期望输出之间差异的方式更新其权重。

注意

反向传播是 backward propagation of errors 的简称。

训练神经网络包括以下步骤:

  1. 随机初始化神经网络的权重和偏置。这使得在没有初始信息时能够迈出第一步。

  2. 执行 forward propagation,一种计算网络给定输入的预测输出的技术。作为提醒,这一步包括计算每个神经元输入的加权和,应用激活函数到加权和,将值传递到下一层(如果不是最后一层),并继续这个过程直到达到输出层(预测)。

  3. 将预测输出与实际输出(测试数据)进行比较并计算损失,表示它们之间的差异。选择损失函数(如 MAE 或 MSE)取决于正在解决的具体问题。

  4. 执行反向传播计算损失相对于权重和偏置的梯度。在这一步中,算法将从输出层(最后一层)开始向后传播。它将计算当前层每个神经元输出相对于损失的梯度。然后,通过应用链式法则,它将计算当前层每个神经元输入加权和相对于损失的梯度。之后,它将使用前面步骤的梯度计算当前层每个神经元的权重和偏置相对于损失的梯度。这些步骤重复直到所有层的梯度都计算完成。

  5. 使用计算得到的梯度和选择的优化算法在特定数量的数据批次上更新网络的权重和偏置,由超参数(称为批量大小)控制。更新权重是通过减去学习率和权重梯度的乘积。调整偏置是通过减去学习率和偏置梯度的乘积。重复上述步骤直到所有层的权重和偏置都更新完成。

  6. 算法然后重复步骤 2–5 特定数量的 epochs 或者直到达到收敛标准。一个 epoch 代表一次完整通过整个训练数据集(理想情况下整个过程通过训练数据集多次进行)。

  7. 训练完成后,评估训练好的神经网络在单独的验证或测试数据集上的表现。

注意

学习率是一个超参数,确定在训练过程中更新神经网络权重的步长。它控制模型从正在训练的数据中学习的速度。

批量大小是一个超参数,确定在训练过程的每次迭代中更新模型权重时处理的样本数量。换句话说,它指定了每次用于计算梯度并更新权重的训练样例数量。

选择适当的批量大小对有效训练至关重要,可以影响收敛速度和内存需求。没有适合所有情况的理想批量大小,因为它取决于诸如数据集大小、可用的计算资源和模型复杂性等各种因素。

用于训练 MLP 的常用批量大小范围从小值(如 16、32 或 64)到较大值(如 128、256 或更大)。较小的批量大小可以提供更频繁的权重更新,并可能有助于模型更快地收敛,特别是当数据集很大或具有很多变化时。然而,较小的批量大小可能会引入更多噪声,并由于使用不太准确的梯度进行频繁更新而导致收敛速度较慢。另一方面,较大的批量大小可以提供更稳定的梯度和更好的并行处理能力利用,从而在现代硬件上实现更快的训练。然而,它们可能需要更多的内存,并且更新较少频繁,这可能会减慢收敛速度或使训练过程不够稳健。

作为一个经验法则,您可以从适度的批量大小,如 32 开始,并尝试不同的值,以找到您特定的 MLP 模型和数据集的收敛速度和计算效率之间的最佳平衡。

反向传播算法利用链式法则(参见第四章了解微积分的更多信息),通过网络向后传播错误来计算梯度。

通过根据通过网络向后传播的错误迭代调整权重,反向传播使网络能够随着时间的推移学习并改善其预测能力。反向传播是训练神经网络的关键算法,并在各个领域的显著进展中发挥了作用。

优化算法

在神经网络中,优化算法,也称为优化器,用于在训练过程中更新网络的参数(权重和偏置)。这些算法旨在最小化损失函数,并找到使网络性能最佳的参数的最优值。有几种类型的优化器:

梯度下降(GD)

梯度下降是最基础的优化算法。它根据损失函数关于参数的梯度的方向更新网络的权重和偏置。它通过步长与梯度的负值成比例来调整参数。

随机梯度下降(SGD)

随机梯度下降(SGD)是梯度下降的一种变体,它随机选择单个训练样本或小批量样本来计算梯度并更新参数。它提供了一种计算效率高的方法,并在训练过程中引入噪声,有助于逃离局部最优解。

自适应矩估计(Adam)

Adam 是一种自适应优化算法,它根据梯度的一阶和二阶矩估计计算每个参数的自适应学习率。由于其在各种应用中的高效性和有效性,Adam 得到了广泛应用。

均方根传播(RMSprop)

RMSprop 的目的是解决标准梯度下降算法的一些限制,例如收敛速度慢和在不同方向上的振荡。RMSprop 根据最近平方梯度的平均值调整每个参数的学习率。它会计算随时间指数加权移动平均的平方梯度。

每种优化器都有其独特的特性、优势和限制,它们的性能可以根据数据集和网络架构而异。通常需要进行实验和调优,以确定特定任务的最佳优化器。

正则化技术

神经网络中的正则化技术 是用于防止过拟合的方法,过拟合可能导致模型性能不佳,并减少模型在新样本上进行准确预测的能力。正则化技术有助于控制神经网络的复杂性,并提高其泛化到未见数据的能力。

Dropout 是神经网络中常用的正则化技术,用于防止过拟合(详见第七章关于过拟合的详细信息)。在训练过程中,它通过随机地省略(丢弃)部分神经元的输出(将其设置为零)来实现。这暂时从网络中删除神经元及其相应的连接,迫使剩余神经元学习更加健壮和独立的表示。

Dropout 的关键思想是它充当模型平均或集成学习的形式。通过随机丢弃神经元,网络减少对特定神经元或连接的依赖,从而学习更加健壮的特征。Dropout 还有助于防止神经元的共适应,即某些神经元过度依赖其他神经元,降低它们的个体学习能力。因此,Dropout 可以提高网络的泛化能力并减少过拟合。

早停止 是一种技术,它通过在训练过程中监控模型在验证集上的表现来防止过拟合。当模型在验证集上的表现开始恶化时,它会停止训练过程。早停止的理念是随着模型继续训练,它可能开始对训练数据过拟合,从而导致在未见数据上的性能下降。

训练过程通常分为若干个周期,每个周期代表对训练数据的完整遍历。在训练过程中,每个周期后评估模型在验证集上的表现。如果验证损失或选择的度量开始连续恶化达到一定数量的周期,训练将停止,并使用性能最佳时期的模型参数作为最终模型。

早停止通过找到模型学习最有用模式的最佳点来帮助防止过拟合,而不是记忆训练数据中的噪声或无关紧要的细节。辍学和早停止都是防止过拟合和帮助稳定模型的关键正则化技术。

多层感知器

多层感知器(MLP)是一种包含多层人工神经元或节点、按顺序排列的 ANN 类型。它是前馈神经网络,意味着信息在网络中单向流动,从输入层到输出层,没有任何循环或反馈连接(您将在后面的“递归神经网络”中学到更多)。

MLP 的基本构建块是感知器,它是一种人工神经元,接受多个输入,对这些输入应用权重,执行加权和,并通过激活函数产生输出(基本上是您已经见过的神经元)。MLP 包含多个按层组织的感知器。它通常包括一个输入层,一个或多个隐藏层(层数越多,学习过程越深入,直到某个点),以及一个输出层。

注意

术语感知器有时更广泛地用于指称基于感知器类似架构的单层神经网络。在此背景下,术语感知器可以与神经网络单层感知器交替使用。

作为提醒,输入层接收原始输入数据,例如数据集中的特征(例如移动平均的稳定值)。隐藏层位于输入层和输出层之间,对输入数据执行复杂的变换。隐藏层中的每个神经元从前一层的所有神经元接收输入,应用权重,执行加权和,并通过激活函数传递结果。输出层生成网络的最终输出。

MLP 使用反向传播进行训练,它调整网络中神经元的权重,以最小化预测输出与期望输出之间的差异。它们以能够学习数据中复杂的非线性关系而闻名,使它们适用于各种任务,包括模式识别。图 8-6 显示了深度 MLP 架构的示例。

图 8-6. 一个具有两个隐藏层的 MLP 的简单示例。

在这个阶段,您应该理解深度学习基本上就是具有许多隐藏层的神经网络,这增加了学习过程的复杂性。

从本书的 GitHub 仓库 下载 master_function.py 是很重要的,以便访问本书中看到的函数。下载后,您必须将 Python 解释器的目录设置为存储 master_function.py 的路径。

本节旨在创建一个多层感知器(MLP)来预测每日标准普尔 500 指数的收益。导入所需的库:

from keras.models import Sequential
from keras.layers import Dense
import keras
import numpy as np
import matplotlib.pyplot as plt
import pandas_datareader as pdr
from master_function import data_preprocessing, plot_train_test_values
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

现在导入历史数据并进行转换:

# Set the start and end dates for the data
start_date = '1990-01-01'
end_date   = '2023-06-01'
# Fetch S&P 500 price data
data = np.array((pdr.get_data_fred('SP500', start = start_date, 
                                   end = end_date)).dropna())
# Difference the data and make it stationary
data = np.diff(data[:, 0])

为模型设置超参数:

num_lags = 100
train_test_split = 0.80
num_neurons_in_hidden_layers = 20
num_epochs = 500
batch_size = 16

使用数据预处理函数创建四个所需的数组:

# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, num_lags, 
                                                      train_test_split)

下面的代码块显示了如何在 keras 中构建 MLP 架构。确保您理解代码中的注释:

# Designing the architecture of the model
model = Sequential()
# First hidden layer with ReLU as activation function
model.add(Dense(num_neurons_in_hidden_layers, input_dim = num_lags, 
                activation = 'relu'))  
# Second hidden layer with ReLU as activation function
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Output layer
model.add(Dense(1))
# Compiling
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Fitting the model
model.fit(x_train, np.reshape(y_train, (–1, 1)), epochs = num_epochs, 
          batch_size = batch_size)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

在创建 Dense 层时,需要在神经网络的第一层中指定 input_dim 参数。对于后续的 Dense 层,input_dim 将自动从上一层的输出中推断出来。

让我们绘制结果并分析性能:

Accuracy Train =  92.4 %
Accuracy Test =  54.85 %
RMSE Train =  4.3602984254
RMSE Test =  75.7542774467
Correlation In-Sample Predicted/Train =  0.989
Correlation Out-of-Sample Predicted/Test =  0.044
Model Bias =  1.03

图 8-7 显示了从 y_train 的最后值到 y_testy_predicted 的第一个值的预测任务的演变。

图 8-7. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 MLP 回归算法。

当更改超参数时,结果非常波动。这就是为什么在复杂数据上使用复杂模型需要大量的调整和优化。考虑以下改进来增强模型结果:

  • 选择相关特征(输入),捕获金融时间序列的潜在模式和特征。这可以涉及计算技术指标(例如,移动平均线和相对强弱指数)或从数据中推导出其他有意义的变量。

  • 审查模型的架构。考虑增加层数或神经元数量,以为模型提供更多学习复杂模式的能力。尝试不同的激活函数和正则化技术,如丢失和提前停止(见 第九章 中正则化技术的应用)。

  • 调整你的 MLP 模型的超参数。像批量大小和 epoch 数这样的参数可以显著影响模型的收敛能力和泛化能力。

  • 将多个 MLP 模型组合成一个集成模型。这可以涉及使用不同的初始化训练多个模型或使用数据的不同子集。聚合它们的预测结果可能比使用单个模型获得更好的结果。

随着模型训练,由于学习过程,损失函数应该会减少。可以通过以下代码来查看(在编译模型后运行):

import tensorflow as tf
losses = []
epochs = []
class LossCallback(tf.keras.callbacks.Callback):
    def on_epoch_end(self, epoch, logs = None):
        losses.append(logs['loss'])
        epochs.append(epoch + 1)
        plt.clf()
        plt.plot(epochs, losses, marker = 'o')
        plt.title('Loss Curve')
        plt.xlabel('Epoch')
        plt.ylabel('Loss Value')
        plt.grid(True)
        plt.pause(0.01)
model.fit(x_train, np.reshape(y_train, (–1, 1)), epochs = 100, 
          verbose = 0, callbacks = [LossCallback()])
plt.show()

前面的代码块绘制了每个 epoch 结束时的损失,从而创建了一个实时可视化的动态损失曲线。注意看它如何下降,直到达到一个难以进一步降低的平台期。图 8-8 展示了跨 epoch 的损失函数下降情况。

图 8-8. 跨 epoch 的损失值。

循环神经网络

循环神经网络(RNN)是一种设计用来处理序列数据或具有时间依赖性数据的人工神经网络类型。与前馈神经网络不同,后者只需单次通过从输入到输出处理数据,RNN 维护内部记忆或隐藏状态,以捕获先前输入的信息,并在处理后续输入时利用它。

RNN 的关键特征是递归连接的存在,这在网络中创建了一个循环。这个循环允许网络跨时间步保持信息,使其非常适合处理涉及顺序或时间相关数据的任务。

在每个时间步,RNN 接收一个输入向量并将其与前一个隐藏状态组合。然后应用激活函数计算新的隐藏状态并产生一个输出。此过程针对每个时间步重复进行,更新隐藏状态并将其作为信息传递到网络中。

递归连接使得 RNN 能够捕捉序列数据中的依赖关系和模式。它们可以建模数据的上下文和时间动态,因此在时间序列预测中非常有用。

然而,传统的 RNN 存在梯度消失问题,即通过递归连接反向传播的梯度可能变得非常小或非常大,导致训练网络困难。下一节将使用一种增强型神经网络来解决梯度消失问题。现在,让我们专注于 RNN 及其特性。

图 8-9 展示了一个循环神经网络(RNN)架构的示例。

图 8-9. 一个包含两个隐藏层的简单循环神经网络示例。

让我们部署一个 RNN 算法来预测标准普尔 500 指数的日回报率。像往常一样,导入所需的库:

from keras.models import Sequential
from keras.layers import Dense, SimpleRNN
import keras
import numpy as np
import matplotlib.pyplot as plt
import pandas_datareader as pdr
from master_function import data_preprocessing, plot_train_test_values
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

现在设置模型的超参数:

num_lags = 100
train_test_split = 0.80
num_neurons_in_hidden_layers = 20
num_epochs = 500
batch_size = 16

下面的代码块展示了如何在keras中构建 RNN 架构。

# Designing the architecture of the model
model = Sequential()
# First hidden layer
model.add(Dense(num_neurons_in_hidden_layers, input_dim = num_lags, 
                activation = 'relu'))  
# Second hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Output layer
model.add(Dense(1))
# Compiling
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Fitting the model
model.fit(x_train, np.reshape(y_train, (–1, 1)), epochs = num_epochs, 
          batch_size = batch_size)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

让我们绘制结果并分析性能:

Accuracy Train =  67.16 %
Accuracy Test =  52.11 %
RMSE Train =  22.7704952044
RMSE Test =  60.3443059267
Correlation In-Sample Predicted/Train =  0.642
Correlation Out-of-Sample Predicted/Test =  –0.022
Model Bias =  2.18

图 8-10 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图 8-10。训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 RNN 回归算法。
提示

你可以做的一个好任务是创建一个优化函数,循环尝试不同的超参数并选择最佳的或对最佳的进行平均。这样,你可能能够基于集成技术获得一个健壮的模型。你还可以对不同的市场和不同的时间范围进行回测。请注意,这些技术不仅适用于金融时间序列,而且适用于所有类型的时间序列。

总之,RNN 是能够通过维护内部记忆并捕捉时间依赖关系处理序列数据的神经网络。它们对涉及时间序列或顺序数据的任务是强大的模型。作为提醒,稳定性是成功进行时间序列预测的重要属性。稳定的时间序列在时间上展现出恒定的均值、方差和自协方差。RNN(以及其他深度学习模型)假设底层时间序列是稳定的,这意味着数据的统计特性随时间不变。如果时间序列是非稳定的,它可能包含趋势、季节性或其他可能影响 RNN 性能的模式。MLP 的优化和增强建议在 RNN 上也是有效的。

长短期记忆网络(LSTM)

长短期记忆(LSTM)是一种能够解决梯度消失问题并允许网络捕捉序列数据中长期依赖的 RNN 类型。LSTM 由 Hochreiter 和 Schmidhuber 于 1997 年提出。

LSTM 旨在克服传统 RNN 在处理长序列数据时的局限性。它通过引入特殊的记忆细胞来实现在扩展时间段内保持信息的能力。LSTM 背后的关键思想是利用门控机制来控制信息在记忆细胞中的流动。

LSTM 结构由记忆细胞、输入门、遗忘门和输出门组成。记忆细胞在每个时间步存储和更新信息,而门控制信息的流动。以下是 LSTM 的工作原理:

输入门

输入门决定当前时间步的哪些信息应存储在记忆细胞中。它将当前输入和上一个隐藏状态作为输入,然后应用 Sigmoid 激活函数为记忆细胞的每个组件生成一个介于 0 和 1 之间的值。

忘记门

忘记门确定应从上一个记忆单元中遗忘哪些信息。它接受当前输入和上一个隐藏状态作为输入,然后应用 Sigmoid 激活函数以生成遗忘向量。然后,该向量与先前的记忆单元值逐元素相乘,允许 LSTM 遗忘无关信息。

更新

更新步骤结合了输入门和遗忘门的信息。它接受当前输入和上一个隐藏状态作为输入,然后应用 tanh 激活函数。然后,将得到的向量与输入门输出逐元素相乘,并将乘积添加到遗忘门和先前记忆单元值的乘积中。此更新操作确定要存储在记忆单元中的新信息。

输出门

输出门确定当前时间步的 LSTM 输出。它接受当前输入和上一个隐藏状态作为输入,然后应用 Sigmoid 激活函数。更新后的记忆单元值通过双曲正切(tanh)激活函数传递,并与输出门逐元素相乘。得到的向量成为当前隐藏状态,也是该时间步的 LSTM 输出。

LSTM 中的门控机制使其能够有选择地记住或遗忘长序列中的信息,使其非常适合涉及长期依赖关系的任务。通过解决梯度消失问题并捕捉长期依赖关系,LSTM 已成为顺序数据处理的流行选择,并在推动深度学习领域方面发挥了重要作用。

注意

理论上,RNN 能够学习长期依赖关系,但在实践中并不会,因此需要 LSTM。

通常情况下,让我们将 LSTM 应用于相同的时间序列问题。然而,请注意,由于解释变量是任意的且超参数未调整,因此结果并无意义。进行这样的练习的目的是理解代码和算法背后的逻辑。之后,由您来选择值得测试的输入和变量。

如下导入所需的库:

from keras.models import Sequential
from keras.layers import Dense, LSTM
import keras
import numpy as np
import matplotlib.pyplot as plt
import pandas_datareader as pdr
from master_function import data_preprocessing, plot_train_test_values
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

现在设置模型的超参数:

num_lags = 100
train_test_split = 0.80
num_neurons_in_hidden_layers = 20
num_epochs = 100
batch_size = 32

LSTM 模型需要特征的三维数组。可以使用以下代码完成:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

下面的代码块显示了如何在keras中构建 LSTM 架构:

# Create the LSTM model
model = Sequential()
# First LSTM layer
model.add(LSTM(units = num_neurons_in_hidden_layers, 
               input_shape = (num_lags, 1)))
# Second hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Output layer
model.add(Dense(units = 1))
# Compile the model
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Train the model
model.fit(x_train, y_train, epochs = num_epochs, batch_size = batch_size)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

让我们绘制结果并分析性能:

Accuracy Train =  65.63 %
Accuracy Test =  50.42 %
RMSE Train =  25.5619843783
RMSE Test =  55.1133475721
Correlation In-Sample Predicted/Train =  0.515
Correlation Out-of-Sample Predicted/Test =  0.057
Model Bias =  2.56

图 8-11 显示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。请注意,超参数与 RNN 模型中使用的相同。

图 8-11. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 LSTM 回归算法。

值得一看的是算法如何与训练数据拟合良好。图 8-12 展示了来自 y_predicted_trainy_train 的值。

图 8-12. 使用 LSTM 回归算法的样本内预测。

在 LSTM 的上下文中,一个三维数组表示输入数据的形状,这些数据被馈送到模型中。它通常用于容纳顺序或时间序列数据的输入序列。三维数组的维度具有特定的含义:

维度 1(样本数)

这个维度代表数据集中的样本或示例数量。每个样本对应于一个特定的序列或时间序列实例。例如,如果你的数据集中有 1,000 个时间序列,那么维度 1 就是 1,000。

维度 2(时间步)

这个维度代表每个序列中的时间步或数据点的数量。它定义了 LSTM 或 RNN 模型在每个时间步处理的输入序列的长度。例如,如果你的输入序列长度为 10 个时间步,那么维度 2 就是 10。

维度 3(特征数)

这个维度代表与每个时间步的序列相关联的特征或变量的数量。它定义了每个时间步数据的维度。对于单变量时间序列数据,其中每个时间步只考虑一个单一值,维度 3 通常为 1。对于多变量时间序列,其中每个时间步观察到多个变量,维度 3 将大于 1。

让我们稍作休息,讨论一个有趣的话题。使用简单的线性算法来建模复杂的非线性关系很可能会产生糟糕的结果。与此同时,在简单且可预测的数据上使用像 LSTM 这样极其复杂的方法可能并非必要,尽管它可能会带来积极的结果。图 8-13 展示了一个看起来像是在正常间隔内振荡的升序时间序列。

图 8-13. 一个生成的带有振荡特性的升序时间序列。

信不信由你,线性回归实际上可以很好地对这种原始时间序列进行建模。通过假设一个具有 100 个特征的自回归模型(这意味着为了预测下一个值,模型查看最后 100 个值),线性回归算法可以在样本内数据上训练,并输出在 图 8-14 中展示的样本外结果。

图 8-14. 使用线性回归对升序时间序列进行预测。

但让我们首先对其进行一阶差分,使其平稳化。 看看图 8-15,显示了从图 8-13 中差分时间序列创建的平稳时间序列。

图 8-15. 生成的具有振荡特性的上升时间序列(差分)。

线性回归算法可以在样本内数据上进行训练,并输出在图 8-16 中显示的样本外结果,精度极高。

图 8-16. 使用线性回归进行差分时间序列预测。

评估线性回归模型拟合优度的另一种方法是使用 R²。也称为决定系数是一个统计量,指示因变量方差中可以通过回归模型中的自变量解释的比例。

R²的取值范围从 0 到 1,并经常以百分比形式表示。 值为 0 表示自变量不能解释因变量的任何变异性,而值为 1 表示自变量可以完全解释因变量的变异性。

简单来说,R²表示可以归因于模型中包含的独立变量的因变量变异性的比例。它提供了回归模型拟合观察数据的程度的度量。然而,它并不表示变量之间的因果关系或模型的整体质量。 值得注意的是,R²是两个变量之间的平方相关性。 差异化时间序列的 R²指标为 0.935,表明非常良好的拟合度。

同时,使用一些优化的 MLP 也能产生良好的结果。 图 8-17 展示了在使用简单 MLP 模型(每个包含 24 个神经元的两个隐藏层和批大小为 128 经过 50 个 epochs 运行)时,使用差分值的结果。

图 8-17. 使用 MLP 进行差分时间序列预测。

然而,使用深度学习方法预测这样一个简单时间序列可能并不值得增加复杂性。

时间卷积神经网络

卷积神经网络 (CNNs) 是一类专为处理结构化网格数据设计的深度学习模型,特别强调处理图像和其他网格数据,如时间序列(使用较少)和音频频谱图。 CNNs 擅长从输入数据中学习和提取层次化模式和特征,使它们成为图像识别、物体检测、图像分割等任务的强大工具。

CNN 的核心构建模块是卷积层。这些层通过将一组可学习的滤波器应用于输入数据来执行卷积运算,生成捕获相关空间模式和局部依赖性的特征图。CNN 的另一个重要概念是池化层,这些层对卷积层生成的特征图进行降采样。常见的池化操作包括最大池化(选择邻域内的最大值)和平均池化(计算平均值)。池化有助于减少空间维度,提取主要特征,并提高计算效率。

注意

专门用于时间序列预测的 CNN 通常称为 1D-CNN 或时序卷积网络

术语1D-CNN表示卷积操作应用于输入数据的时间维度,这是时间序列数据的特征。这使其与传统的在空间维度上进行任务(如图像识别)的 CNN 有所不同。

典型的 CNN 架构包括三个主要组件:输入层,若干交替的卷积和池化层,以及最后的全连接层。卷积层负责特征提取,而池化层则降采样数据。全连接层提供最终的预测。

CNN 的架构可以根据具体任务的不同而大不相同。这些架构通常采用额外的技术,如 dropout 正则化来提高性能并解决过拟合等挑战。

CNN 可以通过利用其捕捉局部模式和从输入数据中提取相关特征的能力用于时间序列预测。该过程的框架如下:

  1. CNN 使用卷积层进行局部特征提取。卷积层由一组可学习的滤波器组成,这些滤波器与输入数据进行卷积。每个滤波器通过滑动窗口方式进行元素级乘法和求和,从输入数据的不同位置提取不同的特征。结果是一个在输入数据中不同位置突出显示重要模式或特征的特征图。

  2. 池化层通常在卷积层之后使用,以减少特征图的空间维度。最大池化是一种常见的技术,其中选择局部邻域内的最大值,有效地对特征图进行降采样。池化有助于捕获最显著的特征,同时减少计算复杂性并增强网络的泛化能力。

  3. 在卷积和池化层之后,生成的特征图通常被展平为一维向量。这种展平操作将空间分布的特征转换为线性序列,然后可以传递到全连接层。

  4. 全连接层接收扁平化特征向量作为输入,并学习将其映射到所需的输出。这些层使网络能够学习输入特征和目标预测之间的复杂组合关系。最后一个全连接层通常代表输出层,用于预测时间序列的目标值。

在进行算法创建步骤之前,让我们回顾一些在 CNN 中使用的关键概念。在使用 CNN 进行时间序列预测时,滤波器沿着输入数据的时间维度应用。与考虑图像数据中的空间特征不同,这些滤波器旨在捕获时间序列内的时间模式或依赖关系。每个滤波器在时间序列上滑动,一次处理一组连续的时间步长子集。该滤波器学习在输入数据中检测特定的时间模式或特征。例如,它可能捕获短期趋势、季节性或对预测任务有关的重复模式。每个卷积层可以使用多个滤波器,使网络能够学习多样化的时间特征。每个滤波器捕获时间序列的不同方面,使模型能够捕获复杂的时间关系。

另一个概念是卷积核大小,它指的是在卷积操作期间滤波器考虑的连续时间步长的长度或数量。它定义了滤波器的接受域,并影响所提取的时间模式的大小。选择卷积核大小取决于时间序列数据的特性和要捕获的模式。较小的卷积核大小,如 3 或 5,专注于捕获短期模式,而较大的卷积核大小,如 7 或 10,则适合捕获长期依赖关系。通过尝试不同的卷积核大小,可以帮助确定捕获准确预测所需的相关时间模式的最佳接受域。通常使用具有不同卷积核大小的多个卷积层来捕获各种时间尺度上的模式。

现在让我们看看如何创建一个时间 CNN 来预测 S&P 500 的回报,使用其滞后值。如下导入所需的库:

from keras.models import Sequential
from keras.layers import Conv1D, MaxPooling1D, Flatten, Dense
import keras
import numpy as np
import matplotlib.pyplot as plt
import pandas_datareader as pdr
from master_function import data_preprocessing, plot_train_test_values
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

接下来,设置模型的超参数:

num_lags = 100 
train_test_split = 0.80 
filters = 64 
kernel_size = 4
pool_size = 2
num_epochs = 100 
batch_size = 8

将特征数组重塑为三维数据结构:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

现在创建时间卷积网络(TCN)的架构并运行算法:

# Create the temporal convolutional network model
model = Sequential()
model.add(Conv1D(filters = filters, kernel_size = kernel_size, 
                 activation = 'relu', input_shape = (num_lags, 1)))
model.add(MaxPooling1D(pool_size = pool_size))
model.add(Flatten())
model.add(Dense(units = 1))
# Compile the model
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Train the model
model.fit(x_train, y_train, epochs = num_epochs , batch_size = batch_size)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

让我们绘制结果并分析性能:

Accuracy Train =  68.9 %
Accuracy Test =  49.16 %
RMSE Train =  18.3047790152
RMSE Test =  63.4069105299
Correlation In-Sample Predicted/Train =  0.786
Correlation Out-of-Sample Predicted/Test =  0.041
Model Bias =  0.98

图 8-18 显示了从y_train的最后值到y_testy_predicted的第一个值的预测任务演变。

图 8-18. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 CNN 回归算法。

使用反映您选择的性能指标并寻找更好算法是很重要的。准确率可能是一个基本指标,能够快速了解模型的预测能力,但仅靠它是不够的。本章中的结果仅反映了使用选定超参数进行的训练。优化将使您能够在某些模型上获得非常好的结果。

注意

没有严格的规定定义神经网络需要多少隐藏层才能被视为深度神经网络。然而,一个常见的约定是,具有两个或更多隐藏层的神经网络通常被认为是深度神经网络。

摘要

将深度学习算法应用于时间序列数据可以带来几个好处和挑战。深度学习算法在时间序列分析中展示了巨大的效用,通过有效捕捉复杂模式、提取有意义的特征和进行准确预测。然而,它们的成功很大程度上取决于数据的质量和所选择的特征。

将深度学习算法应用于时间序列数据的效用源于它们能够自动学习层次化表示和模拟复杂的时间依赖关系。它们能够处理非线性关系并捕捉局部和全局模式,使其适用于诸如预测、异常检测、分类和信号处理等广泛的时间序列任务。

然而,将深度学习算法应用于时间序列可能面临挑战:

数据质量

深度学习模型在训练时严重依赖大量高质量的标记数据。数据不足或噪声过多可能会影响模型的性能,导致预测不准确或洞察不可靠。数据预处理、清洗和处理缺失值成为确保数据质量的关键步骤。

特征工程

深度学习模型可以自动从数据中学习相关特征。然而,选择和提取信息性特征对模型的性能有重要影响。领域知识、数据探索和特征工程技术在选择或转换能够增强模型捕捉相关模式的特征方面至关重要。

模型复杂性

深度学习模型通常非常复杂,具有大量参数。训练这样的模型需要大量的计算资源、较长的训练时间和精细的超参数调整。过拟合是一个常见的挑战,即模型只记住了训练数据,而无法很好地推广到未见过的数据。

可解释性

深度学习模型通常被视为神秘盒子,这使得解释学习到的表示和理解预测背后的推理过程具有挑战性。在金融等领域,解释性和可解释性至关重要,这可能是一个问题。

要克服这些挑战并利用深度学习算法进行时间序列分析,需要仔细考虑数据质量、适当的特征工程、模型架构选择、正则化技术和解释性方法。理解时间序列数据的特定特征和任务要求是选择和调整深度学习方法的关键。

¹ W. S. McCulloch 和 W. Pitts,《神经活动中的思想的逻辑演算》,Bulletin of Mathematical Biophysics 5 (1943): 115–33.

第九章:时间序列预测的深度学习 II

本章介绍了一些技术和方法,用于补充机器学习和深度学习算法的预测任务。它由不同的主题组成,每个主题讨论一种改进和优化流程的方法。此时,您应该对机器学习和深度学习模型的基础知识有了扎实的理解,并且知道如何编写一个基本的算法来预测金融时间序列(或任何平稳时间序列)。本章弥合了基础知识和提升算法到功能水平所需的高级知识之间的差距。

分数阶微分

在他的书籍金融机器学习进展中,马科斯·洛佩斯·德普拉多描述了一种将非平稳数据转换为平稳数据的技术。这称为分数阶微分。

分数阶微分是一种数学技术,用于将时间序列转化为平稳序列,同时保留部分其记忆。它扩展了微分(或称为收益率),常用于去除趋势并使时间序列变得平稳的概念。

在传统的微分中,数据序列通过一个通常为 1 的整数进行微分,这涉及从当前值中减去前一个值。这有助于消除趋势并使系列平稳。然而,在某些情况下,系列可能表现出传统微分无法有效捕捉的长期依赖或记忆效应。这些依赖关系可能有助于预测时间序列,如果完全消除,可能会影响算法的性能。这些依赖关系被称为记忆

分数阶微分通过允许微分参数为分数值来解决这一限制。分数阶微分算子有效地对系列中的每个观测值应用滞后值的加权和,其中权重由分数阶微分参数确定。这允许捕捉系列中的长期依赖或记忆效应。分数阶微分在金融时间序列分析中特别有用,其中数据经常表现出长期记忆或持续行为。这可以在 Python 中实现。首先,在命令提示符中使用pip install安装所需的库:

pip install fracdiff

接下来,导入所需的库:

from fracdiff.sklearn import Fracdiff
import pandas_datareader as pdr
import numpy as np
import matplotlib.pyplot as plt

让我们使用德普拉多在他的书中经典的例子,标普 500 指数,来证明分数阶微分将非平稳时间序列转化为具有可见保留记忆的平稳序列。

以下代码应用分数阶微分并与传统微分进行比较:

# Set the start and end dates for the data
start_date = '1990-01-01'
end_date   = '2023-06-01'
# Fetch S&P 500 price data
data = np.array((pdr.get_data_fred('SP500', start = start_date, 
                                   end = end_date)).dropna())
# Calculate the fractional differentiation
window = 100
f = Fracdiff(0.48, mode = 'valid', window = window)
frac_data = f.fit_transform(data)
# Calculate a simple differencing function for comparison
diff_data = np.reshape(np.diff(data[:, 0]), (–1, 1))
# Harmonizing time indices
data = data[window – 1:, ]
diff_data = diff_data[window – 2:, ]

图 9-1 展示了三种类型的变换。您可以在顶部面板中注意到非变换的 S&P 500 数据的趋势性质。您还可以注意到在中间面板中,这种趋势性质减弱了但仍然存在。这就是分数阶差分的目标。通过保持市场记忆的线索同时使其稳定,这种技术可以帮助改进一些预测算法。底部面板显示了价格数据的正常差分。

图 9-1. S&P 500 的分数阶差分(order = 0.48)

图 9-1 是使用此代码生成的:

fig, axes = plt.subplots(nrows = 3, ncols = 1)
axes[0].plot(data[5:,], label = 'S&P 500', color = 'blue', linewidth = 1)
axes[1].plot(frac_data[5:,], label = 
             'Fractionally Differentiated S&P 500 (0.48)', 
             color = 'orange', linewidth = 1)
axes[2].plot(diff_data[5:,], label = 
             'Differenced S&P 500', color = 'green', linewidth = 1)
axes[0].legend()
axes[1].legend()
axes[2].legend()
axes[0].grid()
axes[1].grid()
axes[2].grid()   
axes[1].axhline(y = 0, color = 'black', linestyle = 'dashed') 
axes[2].axhline(y = 0, color = 'black', linestyle = 'dashed')  

让我们通过应用增广迪基—富勒(ADF)检验确保分数差分数据确实是稳定的(您在第三章 中使用了此检验):

from statsmodels.tsa.stattools import adfuller
print('p-value: %f' % adfuller(data)[1])
print('p-value: %f' % adfuller(frac_data)[1])
print('p-value: %f' % adfuller(diff_data)[1])

前一代码块的输出如下(假设显著性水平为 5%):

# The original S&P 500 dataset is nonstationary
p-value: 0.842099 
# The fractionally differentiated S&P 500 dataset is stationary
p-value: 0.038829
# The normally differenced S&P 500 dataset is stationary
p-value: 0.000000

根据结果显示,数据确实是稳定的。让我们看另一个例子。以下代码导入了 EURUSD 的日常数值:

data = np.array((pdr.get_data_fred('DEXUSEU', start = start_date, 
                                   end = end_date)).dropna())

图 9-2 将 EURUSD 与应用了分数阶差分(0.20)进行了比较,底部面板显示了常规差分。

图 9-2. EURUSD 的分数阶差分(order = 0.20)

ADF 测试的结果如下:

# The original EURUSD dataset is nonstationary
p-value: 0.397494
# The fractionally differentiated EURUSD  dataset is stationary
p-value: 0.043214
# The normally differenced EURUSD  dataset is stationary
p-value: 0.000000 

作为比较,图 9-3 将同一数据集与应用了分数阶差分(0.30)进行了比较,底部面板显示了常规差分。

图 9-3. EURUSD 的分数阶差分(order = 0.30)
注意

当接近 1.00 的阶数时,分数阶差分方法直观上变成了正常的整数差分。同样地,当接近 0.00 的阶数时,分数阶差分方法则变成了未经变换的数据序列。

图 9-3 在中间面板上显示的 EURUSD 系列比图 9-2 更加稳定,这是因为分数阶差分的阶数增加了。这也是为什么当阶数为 0.30 时,分数阶差分的 ADF 测试结果为 0.002,远低于当阶数为 0.20 时的 ADF 测试结果(为 0.043)。

总结一下,分数阶差分是时间序列预测的有价值工具,因为它捕捉长期依赖关系,处理非平稳性,适应各种动态,并保留积分属性。其捕捉复杂模式和提高预测精度的能力使其非常适合对各种实际时间序列数据进行建模和预测。

预测阈值

预测阈值 是验证信号所需的最低百分比预测。这意味着预测阈值技术是一种过滤器,用于删除低信度预测。

客观地说,低信度预测低于一定百分比。在表 9-1 中展示了一个假设性例子。阈值为±1%。

表 9-1. 预测表

时间预测状态
10.09%已取消
2–0.60%已取消
3–1.50%已接受
41.00%已接受
52.33%已接受

在时间 1,交易信号是看涨的,预计虚拟金融工具将上涨 0.09%。由于此预测低于 1.00% 的阈值,因此不进行交易。在时间 2,应用相同的直觉,因为看跌信号低于阈值。

其余信号都已被接受,因为它们等于或大于阈值(在大小方面)。本节的目的是开发多层感知器(MLP)模型并仅保留符合一定阈值的预测。

像往常一样,首先导入所需的库:

import numpy as np
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Dense
from master_function import data_preprocessing, mass_import
from master_function import plot_train_test_values, forecasting_threshold

接下来,设置超参数并使用 mass_import() 导入数据:

num_lags = 500
train_test_split = 0.80 
num_neurons_in_hidden_layers = 256 
num_epochs = 100 
batch_size = 10
threshold = 0.0015

导入并预处理数据,然后设计 MLP 架构:

# Fetching the historical price data
data = np.diff(mass_import(0, 'D1')[:, 3])
# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, num_lags, 
                                                      train_test_split)
# Designing the architecture of the model
model = Sequential()
# First hidden layer
model.add(Dense(num_neurons_in_hidden_layers, input_dim = num_lags, 
                activation = 'relu'))  
# Second hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))
# Output layer
model.add(Dense(1))
# Compiling
model.compile(loss = 'mean_squared_error', optimizer = 'adam')

下一步是拟合和预测数据,并保留满足您在超参数中定义的阈值的预测。这是使用函数 forecasting_threshold() 完成的:

# Fitting
model.fit(x_train, y_train, epochs = num_epochs, batch_size = batch_size)
# Predicting
y_predicted = model.predict(x_test)
# Threshold function
y_predicted = forecasting_threshold(y_predicted, threshold)
# Plotting
plot_train_test_values(100, 50, y_train, y_test, y_predicted)

图 9-4 显示了真实值和预测值之间的比较图表。预测中的平坦观察表示没有低于所需阈值(在本例中为 0.0015)的信号。

图 9-4. 使用预测阈值进行预测

阈值可以通过多种方式找到,特别是:

固定数值技术

正如您在前面的示例中看到的,该技术假设一个固定的任意数字用作阈值。

基于波动率的技术

使用这种技术,您可以使用波动率指标(例如价格滚动标准差)在每个时间步长上设置可变阈值。这种技术的好处是使用最新的波动率信息。

统计技术

使用此技术,您查看来自训练集(而不是测试集)的实际值,并选择某个分位数(例如,75% 分位数)作为验证信号的最低阈值。

总之,使用预测阈值可能有助于选择具有最高信心的交易,并且还可以帮助最小化交易成本,因为算法假定始终进行交易,这是不推荐的。这假设向算法添加一个新状态,总共有三个状态:

看涨信号

该算法预测一个较高的值。

看跌信号

该算法预测一个较低的值。

中性信号

该算法没有任何方向性信念。

连续重新训练

重新训练 指的是每次有新数据进来时对算法进行训练。这意味着在处理每日时间序列时,重新训练会每天进行一次,同时合并最新的每日输入。

连续重新训练技术值得测试,这就是本节的目的。算法的架构将遵循这个框架:

  1. 在训练测试数据上训练数据。

  2. 对于每次预测,重新运行算法并将新的实际输入包含在训练集中。

注意

持续重新训练技术的一个重大限制是算法的速度,因为它必须在每个时间步重新训练。如果您有 1,000 个测试数据实例,每次训练都需要几分钟,那么回测过程将变得极其缓慢。这尤其是深度学习算法,例如 LSTM,可能需要很长时间来训练。

持续重新训练的主要原因是概念漂移,这是数据内部动态和结构的变化,可能会使训练阶段找到的函数失效。基本上,金融时间序列不表现出静态关系;相反,它们随着时间的推移而变化。因此,持续重新训练旨在通过始终使用最新数据进行训练来更新模型。

注意

持续重新训练不需要在每个时间步骤都进行。您可以设置n周期进行重新训练。例如,如果您选择了 10,则模型在每组 10 个新值之后重新训练。

为了简化事情,本节展示了每天使用线性回归模型对每个时间步长的周 EURUSD 值进行连续重新训练的代码。您可以使用其他模型执行相同的操作;您只需更改导入和设计模型的代码行即可。首先,导入所需的库:

import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import LinearRegression
from master_function import data_preprocessing, mass_import
from master_function import plot_train_test_values, 
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

导入数据并设置算法的超参数:

# Importing the time series
data = np.diff(mass_import(0, 'D1')[:, 3])
# Setting the hyperparameters
num_lags = 15
train_test_split = 0.80 
# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, num_lags, 
                                                      train_test_split)
# Fitting the model
model = LinearRegression()
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))

创建持续重新训练循环如下:

# Store the new forecasts
y_predicted = []
# Reshape x_test to forecast one period
latest_values = np.transpose(np.reshape(x_test[0], (–1, 1)))
# Isolate the real values for comparison
y_test_store = y_test
y_train_store = y_train
for i in range(len(y_test)):
    try: 
        # Predict over the first x_test data
        predicted_value = model.predict(latest_values)
        # Store the prediction in an array
        y_predicted = np.append(y_predicted, predicted_value)
        # Add the first test values to the last training values
        x_train = np.concatenate((x_train, latest_values), axis = 0)
        y_train = np.append(y_train, y_test[0])
        # Remove the first test values from the test arrays
        y_test = y_test[1:]
        x_test = x_test[1:, ]
        # Retrain
        model.fit(x_train, y_train)
        # Select the first values of the test set
        latest_values = np.transpose(np.reshape(x_test[0], (–1, 1)))
    except IndexError:
        pass

绘制预测值:

plot_train_test_values(100, 50, y_train, y_test_store, y_predicted)

图 9-5 显示了结果。

作为简单的比较,对没有重新训练的模型进行了相同的回测。与重新训练的同一模型相比,后者在 48.55%的测试集准确率上获得了 48.92%的测试集准确率。

持续重新训练并不能保证获得更好的结果,但由于市场动态的变化,定期更新模型是有意义的。您应该更新模型的频率可能是主观的。

图 9-5. 使用持续重新训练技术进行预测

时间序列交叉验证

交叉验证是机器学习中用于评估模型性能的一种技术。它涉及将可用数据分成用于训练和评估的子集。在时间序列数据的情况下,观察值的顺序很重要(由于数据的顺序性质),传统的k-折叠交叉验证方法可能不适用。而是使用时间序列交叉验证技术,例如滚动窗口扩展窗口方法。

注意

在传统的k折交叉验证中,数据被随机分成k个大小相等的折叠。每个折叠被用作验证集,而其余k-1个折叠被组合用于训练模型。该过程重复k次,每个折叠一次作为验证集。最后,跨k次迭代对性能指标进行平均以评估模型的性能。

与传统的k折交叉验证不同,时间序列交叉验证方法尊重数据点的时间顺序。用于时间序列交叉验证的两种常用技术是滚动窗口和扩展窗口方法。

滚动窗口交叉验证中,固定大小的训练窗口在时间序列数据上进行迭代移动。在每个步骤中,模型在窗口内的观察结果上进行训练,并在随后的窗口上进行评估。该过程重复,直到达到数据的末尾。窗口大小可以根据特定的时间持续时间或固定数量的观察来定义。图 9-6 显示了滚动窗口交叉验证的示意图。

图 9-6

图 9-6 滚动窗口交叉验证

扩展窗口交叉验证中,训练集从一个小的初始窗口开始,并随着时间的推移不断扩展,每个步骤都会纳入更多的数据点。模型根据可用数据训练到特定点,并在随后的时间段进行评估。与滚动窗口方法类似,该过程重复,直到达到数据的末尾。图 9-7 显示了扩展窗口交叉验证的示意图。

图 9-7

图 9-7 扩展窗口交叉验证

在时间序列交叉验证的每次迭代中,使用适当的评估指标来衡量模型的性能。从每次迭代获得的性能结果可以进行汇总和总结,以评估模型在时间序列数据上的整体性能。

多期预测

多期预测(MPF)是一种旨在预测不止下一期的技术。它旨在生成一个由用户定义的n期路径。有两种方法可以处理 MPF:

递归模型

递归模型将预测作为下一个预测的输入。正如你可能已经猜到的那样,递归模型可能会由于在预测时使用预测作为输入而快速偏离轨道,导致指数级增长的误差项。

直接模型

直接模型从头开始训练模型,以在其各自的时间期间输出多个预测。该模型可能比递归模型更加健壮。

让我们从递归模型开始。从数学上讲,它的最基本形式可以表示如下:

P r e d i c t i o n i = f x ( P r e d i c t i o n i-1 , . . . , P r e d i c t i o n i-n )

本节将使用天气数据和经济指标应用深度学习算法。

预测分析的第一步是了解数据,所以让我们看看算法将要预测的内容。第一个时间序列是自 2005 年以来瑞士巴塞尔的平均日温度。Figure 9-8 显示了时间序列。

第二个时间序列是美国供应管理协会采购经理人指数(ISM PMI),这是一个广为认可的经济指标,提供有关制造业健康和整体经济状况的见解。该指数基于对来自各行业(包括制造业)的采购经理的月度调查,并评估新订单、生产、就业、供应商交付和库存等关键因素。

图 9-8. 数据集样本,显示温度的季节性特征

该指数报告为百分比,值超过 50 表示制造业扩张,值低于 50 表示收缩。更高的 PMI 通常表明积极的经济增长,而较低的 PMI 可能表示经济放缓或衰退条件。由于 ISM PMI 可以提供有关经济趋势和商业周期潜在转变的宝贵见解,因此它受到政策制定者、投资者和企业的密切关注。Figure 9-9 显示了 ISM PMI 的历史观测。

预测的目标是测试算法穿透噪音并对 ISM PMI 的原始均值回归特性建模的能力。让我们从递归模型开始。

递归模型的框架如下:

  1. 使用通常的 80/20 分割在训练集上训练数据。

  2. 使用来自测试集的所需输入预测第一个观察结果。

  3. 使用步骤 2 中的最后一个预测和测试集中所需的数据预测第二个观察结果,同时放弃第一个观察结果。

  4. 重复步骤 3,直到达到所需的预测数量。在某一点上,预测是通过仅查看先前的预测来进行的。

图 9-9. 导入数据集样本,显示 ISM PMI 的均值回归特性
注意

到目前为止,您一直在使用ca⁠lc⁠ul⁠ate_​ac⁠cur⁠acy()评估准确性,当您预测正负值(如 EURUSD 价格变动)时,这是有效的。但是,在预测不围绕零点波动的多周期值时,最好计算方向准确性,这基本上是相同的计算,但不围绕零点。为此,使用函数calculate_directional_accuracy()。请记住,这些函数可以在书籍的master_function.py中的GitHub 仓库中找到。

让我们从巴塞尔的平均温度开始。使用以下代码导入数据集(确保您从GitHub 仓库下载了历史观测数据):

from keras.models import Sequential
from keras.layers import Dense
import keras
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from master_function import data_preprocessing, plot_train_test_values, 
from master_function import recursive_mpf
from master_function import calculate_directional_accuracy
from sklearn.metrics import mean_squared_error

接下来,预处理数据:

# Importing the data
data = np.reshape(np.array(pd.read_excel('Temperature_Basel.xlsx').dropna()
                  ), (–1))
# Setting the hyperparameters
num_lags = 500
train_test_split = 0.8
num_neurons_in_hidden_layers = 100
num_epochs = 200
batch_size = 12
# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, num_lags, 
                                                      train_test_split)

设计具有多个隐藏层的 MLP 架构。然后,基于递归方式进行拟合和预测:

# Designing the architecture of the model
model = Sequential()
# First hidden layer
model.add(Dense(num_neurons_in_hidden_layers, input_dim = num_lags, 
          activation = 'relu'))  
# Second hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Third hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Fourth hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu')) 
# Output layer
model.add(Dense(1))
# Compiling
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Fitting the model
model.fit(x_train, np.reshape(y_train, (–1, 1)), epochs = num_epochs, 
          batch_size = batch_size)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting in the test set on a recursive basis
x_test, y_predicted = recursive_mpf(x_test, y_test, num_lags, 
                                    model, architecture = 'MLP')

函数recursive_mpf()接受以下参数:

  • 将持续更新的测试集特征。它们由变量x_test表示。

  • 将持续更新的测试集依赖变量。它们由变量y_test表示。

  • 滞后数。这个变量由num_lags表示。

  • 变量model定义的拟合模型。

  • 架构类型由参数architecture表示。它可以是MLP,适用于二维数组,或者LSTM,适用于三维数组。

图 9-10 显示了预测与实际值之间的对比(虚线时间序列在截断线之后)。注意深度神经网络如何重新创建时间序列的季节特性(虽然存在一些缺陷),并且很好地将其投影到未来,而无需沿途获取任何必要的知识。

图 9-10. 多期预测与实际值
注意

许多机器学习和深度学习算法能够很好地建模这种关系。本例中使用了 MLP,但这并不贬低其他模型,甚至简单的模型如线性回归也同样适用。你可以尝试用你选择的模型(如 LSTM)应用相同的例子并比较结果。如果你使用 LSTM 模型,请确保设置architecture = 'LSTM'

现在在第二个时间序列上应用相同的过程。你只需要更改导入文件的名称和超参数(按照你的需求):

data = np.reshape(np.array(pd.read_excel('ISM_PMI.xlsx').dropna()), (–1))

图 9-11 显示了预测(虚线)与实际值之间的对比。

图 9-11. 提前多个周期预测;预测数据用细实线表示,测试数据用虚线表示

训练模型并不复杂,以避免过拟合。然而,在最初的预测期间,它确实能够很好地预测转折点。自然地,这种能力随着时间的推移逐渐减弱。调整超参数是实现良好方向准确性的关键。从以下超参数开始:

num_lags = 200
train_test_split = 0.8
num_neurons_in_hidden_layers = 500
num_epochs = 400
batch_size = 100

第二种 MPF 技术从头开始训练模型,输出多个在各自时间段的预测。数学上可以表示如下:

P r e d i c t i o n i = f x ( r e a l i n p u t i-1 , . . . , i n p u t i-n ) P r e d i c t i o n i+1 = f x ( r e a l i n p u t i-1 , . . . , i n p u t i-n ) P r e d i c t i o n i+2 = f x ( r e a l i n p u t i-1 , . . . , i n p u t i-n )

递归模型的框架如下:

  1. 创建一个函数,将所需的输入数量与所需的输出数量相关联。这意味着神经网络的最后一层将包含等于你想要向未来投影的预测期数的输出数量。

  2. 训练模型以预测每个时间步的多个输出,基于同一时间步的输入。

让我们继续使用 ISM PMI。像往常一样,导入所需的库:

from keras.models import Sequential
from keras.layers import Dense
import keras
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from master_function import direct_mpf
from master_function import calculate_directional_accuracy
from sklearn.metrics import mean_squared_error

导入和预处理数据时设置超参数:

# Importing the data
data = np.reshape(np.array(pd.read_excel('ISM_PMI.xlsx').dropna()), (–1))
# Setting the hyperparameters
num_lags = 10
train_test_split = 0.80
num_neurons_in_hidden_layers = 200
num_epochs = 200
batch_size = 10
forecast_horizon = 18 # This means eighteen months
x_train, y_train, x_test, y_test = direct_mpf(data, num_lags, 
                                              train_test_split, 
                                              forecast_horizon)

函数direct_mpf()接受以下参数:

  • 变量data表示的数据集

  • 变量num_lags表示的滞后数目。

  • 变量train_test_split表示的拆分。

  • 变量forecast_horizon表示的要预测的观察次数。

准备数组,设计架构,并预测数据,预测时间跨度为 18 个月(因为 ISM PMI 是一个月度指标):

# Designing the architecture of the model
model = Sequential()
# First hidden layer
model.add(Dense(num_neurons_in_hidden_layers, input_dim = num_lags, 
                activation = 'relu'))  
# Second hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Output layer
model.add(Dense(forecast_horizon))
# Compiling
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Fitting (training) the model
model.fit(x_train, y_train, epochs = num_epochs, batch_size = batch_size)
# Make predictions
y_predicted = model.predict(x_test)
# Plotting
plt.plot(y_predicted[–1], label = 'Predicted data', color = 'red', 
         linewidth = 1)
plt.plot(y_test[–1], label = 'Test data', color = 'black', 
         linestyle = 'dashed', linewidth = 2)
plt.grid()
plt.legend()

图 9-12 在此时显示了预测数据和测试数据。

图 9-12。模型的多周期预测与实际值比较,带有一些优化

在预测时,模型解释了连续 18 个月 ISM PMI 的下降。模型似乎在预测这个趋势方面表现良好。请注意,由于算法的随机初始化可能会影响其收敛到最小损失函数,因此可能会得到不同的结果。您可以使用random_state以确保每次获得相同的结果(您在第七章中看到了这一点)。

注意

ISM PMI 与美国国内生产总值(GDP)呈正相关,与标准普尔 500 指数略有正相关。更确切地说,ISM PMI 的底部与股票市场的底部相吻合。

出于好奇,让我们尝试使用非常简单和基本的超参数来运行模型:

num_lags = 1
train_test_split = 0.80
num_neurons_in_hidden_layers = 2
num_epochs = 10
batch_size = 1
forecast_horizon = 18

显然,对于一个滞后的情况,模型只会考虑先前的值来学习如何预测未来。隐藏层每个仅包含两个神经元,仅运行 10 个时期,使用批量大小为 1。自然地,使用这些超参数不会得到令人满意的结果。图 9-13 比较了预测值和实际值之间的巨大差异,因为模型未能捕捉到幅度或方向。

图 9-13。使用基本超参数的模型的多周期预测与实际值比较

这就是为什么超参数优化很重要,需要一定程度的复杂性。毕竟,这些时间序列并不简单,其中带有相当大的噪音。

最后,让我们来看看在巴塞尔温度数据上运行以下超参数的结果,就像您在本节开头看到的那样:

num_lags = 500
train_test_split = 0.80
num_neurons_in_hidden_layers = 128
num_epochs = 50
batch_size = 12
forecast_horizon = 500

图 9-14 比较了使用温度时间序列的预测值与实际值,预测的观察次数为 500。

图 9-14。使用温度时间序列的模型的多周期预测与实际值比较

使用哪种预测技术取决于您的偏好和需求。值得一提的是另一种 MPF 技术被称为多输出模型,它是预测一系列值的一次性预测。这意味着模型在训练集上训练,旨在产生预先定义数量的输出(预测)。显然,这种模型可能计算成本高昂,并且需要大量的数据。

将正则化应用于 MLPs

第八章讨论了深度学习中涉及的两种正则化概念:

  • 丢弃作为一种正则化技术,在训练期间随机关闭神经元以防止过拟合

  • 提前停止作为一种方法,通过监控模型性能并在性能开始下降时停止训练来防止过拟合

另一种值得讨论的正则化技术是批归一化,这是深度学习中用于改善神经网络训练和泛化的技术。它在训练期间规范化每个层的输入,有助于稳定和加速学习过程。

批归一化背后的主要思想是确保层的输入均值为零,方差为单位。该归一化独立应用于每个层内的每个特征(或神经元)。该过程可以总结为以下步骤:

  1. 对于每个特征在一个小批次中,计算批次中所有样本的均值和方差。

  2. 对于每个特征,减去均值并除以标准差(方差的平方根)。

  3. 归一化后,特征通过可学习参数进行缩放和移位。这些参数允许模型为每个归一化特征学习最佳的缩放和移位。

本节介绍了使用 LSTM 进行简单预测任务的示例,同时添加了三种正则化技术。时间序列是标准普尔 500 的 20 天滚动自相关数据。导入所需的库:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import LSTM, Dense, Dropout, BatchNormalization
from tensorflow.keras.callbacks import EarlyStopping
import pandas_datareader as pdr
from master_function import data_preprocessing, plot_train_test_values
from master_function import calculate_directional_accuracy
from sklearn.metrics import mean_squared_error

导入并预处理数据:

# Set the start and end dates for the data
start_date = '1990-01-01'
end_date   = '2023-06-01'
# Fetch S&P 500 price data
data = np.array((pdr.get_data_fred('SP500', start = start_date, 
                                   end = end_date)).dropna())

计算收盘价格的 20 天自相关性:

rolling_autocorr = pd.DataFrame(data).rolling(window = 
                                              20).apply(lambda x: 
                                              x.autocorr(lag=1)).dropna()
rolling_autocorr = np.reshape(np.array(rolling_autocorr), (–1))
注意

在 Python 中,lambda函数,也称为匿名函数,是一个小型的未命名函数,可以有任意数量的参数,但只能有一个表达式。这些函数通常用于创建简单的内联函数,而无需使用def关键字定义完整的函数。以下是一个简单的示例,说明了lambda的工作原理:

# Create an anonymous function to divide two variables
divide = lambda x, y: x / y
# Call the function
result = divide(10, 2)

输出将是存储在result中的浮点数 5.0。

apply()函数是pandas中可用的方法。它主要用于沿着数据帧的轴应用给定函数。

继续之前,请尝试绘制您刚刚计算的标准普尔 500 价格数据与其 20 天自相关性的图。使用以下代码生成图 9-15:

fig, axes = plt.subplots(nrows = 2, ncols = 1)
axes[0].plot(data[-350:,], label = 'S&P 500', linewidth = 1.5)
axes[1].plot(rolling_autocorr[-350:,], label = '20-Day Autocorrelation', 
             color = 'orange', linewidth = 1.5)
axes[0].legend()
axes[1].legend()
axes[0].grid()
axes[1].grid()
axes[1].axhline(y = 0.95, color = 'black', linestyle = 'dashed')

图 9-15. 标准普尔 500 与其 20 天价格自相关性(滞后 = 1)

从图表和自相关性的直觉中应该记住的是,每当自相关性接近 1.00 时,当前趋势可能会中断,从而导致市场纠正。这不是一个完美的假设,但您可以遵循这些基本规则来解释滚动自相关性的观察结果:

  • 一个趋势市场(看涨或看跌)其自相关性迟早会接近 1.00。当这种情况发生时,可能表明潜在趋势暂停,或者在更少的情况下,完全反转。

  • 一个横向(波动)市场的自相关性会很低。如果自相关接近历史最低点,则可能意味着市场准备好趋势。

现在让我们继续构建算法。下一步是设置超参数并准备数组:

num_lags = 500 
train_test_split = 0.80 
num_neurons_in_hidden_layers = 128 
num_epochs = 100 
batch_size = 20
# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(rolling_autocorr, 
                                                      num_lags, 
                                                      train_test_split)

将输入数组转换为三维结构,以便在 LSTM 架构中无问题地处理它们:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

设计 LSTM 架构并添加 dropout 层和批归一化。在设置restore_best_weightsTrue的同时,添加早停实现以保持对测试数据的最佳预测参数:

# Create the LSTM model
model = Sequential()
model.add(LSTM(units = num_neurons_in_hidden_layers, input_shape = 
               (num_lags, 1)))
# Adding batch normalization and a dropout of 10%
model.add(BatchNormalization())
model.add(Dropout(0.1)) 
# Adding the output layer
model.add(Dense(units = 1))
# Compile the model
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Early stopping implementation
early_stopping = EarlyStopping(monitor = 'loss', patience = 15, 
 restore_best_weights = True)
# Train the model
model.fit(x_train, y_train, epochs = num_epochs, 
          batch_size = batch_size, callbacks = [early_stopping])

预测并绘制结果:

# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))
# Plotting
plot_train_test_values(300, 50, y_train, y_test, y_predicted)

图 9-16 显示了预测与实际值的对比。由于早停机制的回调,模型在达到 100 个周期之前已停止训练。

图 9-16. 预测相关性

结果如下:

Accuracy Train =  70.37 %
Accuracy Test =  68.12 %
RMSE Train =  0.0658945761
RMSE Test =  0.0585669847
Correlation In-Sample Predicted/Train =  0.945
Correlation Out-of-Sample Predicted/Test =  0.936

需要注意的是,应谨慎使用诸如滚动自相关等指标。它们提供了历史模式的见解,但不能保证未来的表现。此外,滚动自相关作为技术指标的有效性取决于数据的性质和使用的上下文。您可以尝试将 MPF 方法应用于自相关数据。

其他存在的正则化技术包括以下内容:

L1 和 L2 正则化

也称为权重衰减,L1 和 L2 正则化根据权重的大小向损失函数添加惩罚项。L1 正则化将权重的绝对值加到损失中,促进模型的稀疏性。L2 正则化将权重的平方值加到损失中,抑制大的权重值,并倾向于更均匀地分布特征的影响。

DropConnect

这种技术类似于 dropout,但是应用于连接而不是神经元。这种技术在训练期间随机丢弃层之间的连接。

权重约束

限制权重值的大小可以防止模型从噪声中学习复杂模式,并帮助对模型进行正则化。

对抗训练

使用对抗性示例训练模型可以通过使其更抗小扰动来提高其鲁棒性。

使用这些正则化技术并不保证比不使用它们的模型结果更好。然而,深度学习的最佳实践鼓励使用这些技术来避免更严重的问题,如过拟合。

注意

当手动上传包含历史数据的 Excel 文件(例如使用pandas)时,请确保其形状为(n, ),而不是(n, 1)。这样做可以确保在使用data_preprocessing()函数时,四个训练/测试数组将以正确的维度创建。

要将(n, 1)数组转换为(n, ),请使用以下语法:

data = np.reshape(data, (–1))

要将(n, )数组转换为(n, 1),请使用以下语法:

data = np.reshape(data, (–1, 1))

概要

本章介绍了一些可能改进不同机器和深度学习算法的技术。我喜欢将这些技术称为卫星,因为它们围绕着主要组件——神经网络——盘旋。优化和增强对于分析的成功至关重要。例如,某些市场可能会从预测阈值技术和分数微分中受益。试错法是理解数据的关键,当您开始学习第十章并了解强化学习时,您会发现试错不仅仅是人类的任务,它也可以是计算机的任务。