人工智能与量化投资--深度学习前沿技术在股价预测中的应用

在本文中,我将创建一个预测股票价格变动的完整流程。按照我们的思路走下去,我们将获得一些非常好的结果。为此,我们将使用生成对抗网络(GAN)与LSTM(一种递归神经网络)作为生成器,并使用卷积神经网络CNN作为裁决器。我们使用LSTM的原因很明显,我们正在尝试预测时间序列数据。为什么我们使用GAN,特别是CNN作为裁决器?这是一个很好的问题:稍后会有特别的部分来说明这个问题。

当然,我们将详细介绍每个步骤,但最困难的部分是GAN:成功训练GAN的非常困难的部分是获得正确的超参数集。出于这个原因,我们将使用贝叶斯优化(连同高斯过程)和深度强化学习(DRL)来决定何时以及如何改变GAN的超参数。在创建强化学习时,我将使用该领域的最新进展,例如Rainbow和PPO。

我们将使用许多不同类型的输入数据。除了股票的历史交易数据和技术指标,我们将使用NLP的最新进展(BERT迁移学习)来创建情绪分析(作为基本面分析的一部分),用于提取整体趋势方向的傅立叶变换,用于识别其他高级特征的栈式自编码器,用于查找相关资产的特征投资组合,用于股票函数近似的自回归积分移动平均值(ARIMA)等等,以便捕获尽可能多的关于股票的信息,模式,依赖关系等。众所周知,数据越多越好。预测股票价格变动是一项非常复杂的任务,因此我们对股票的了解越多(从不同的角度来看),我们的盈利就越高。

为了创建所有的神经网络,我们将使用MXNet及其高级API - Gluon,并在多个GPU上进行训练。

注意:虽然我试图详细了解了数学和几乎所有算法和技术背后的机制,但本文不会明确地解释机器/深度学习或股票市场如何运作。我们的目的是分享如何使用不同的技术和算法来准确预测股票价格变动,并且给出在每一步使用每种技术的原因和背后的理由。

目录

1、概述

2、数据

2.1、关联资产

2.2、技术指标

2.3、基本面分析

2.3.1、Transformer的双向嵌入表示 - BERT

2.4、用于趋势分析的傅立叶变换

2.5、ARIMA作为特征

2.6、统计学检查

2.6.1、异方差性,多重共线性,序列相关性

2.7、特征工程

2.7.1、特征对于XGBoost的重要性

2.8、使用栈式自编码器提取高级特征

2.8.1、激活函数 - GELU(高斯误差)

3、生成对抗网络(GAN)

3.1、为什么GAN用于股市预测

3.2、Metropolis-Hastings GAN和Wasserstein GAN

3.3、生成器 - 单层RNN

3.3.1、LSTM还是GRU

3.3.2、LSTM的架构

3.3.3、学习率调度程序

3.3.4、如何防止过拟合和偏差 - 方差权衡

3.4、裁决器 - 一维CNN

3.4.1、为什么CNN可以做裁决器?

3.4.2、CNN的架构

3.5、超参数

4、超参数优化

4.1、针对超参数优化的强化学习

4.1.1、强化学习理论

4.1.1.1、Rainbow

4.1.1.2、PPO

4.1.2、进一步深入强化学习

4.2、贝叶斯优化

4.2.1、高斯过程

5、结果

6、接下来的研究内容?

7、免责声明

1、概述

准确预测股票市场是一项复杂的任务,因为特定股票在特定方向上有数百万个事件和前提条件。因此,我们需要能够捕获尽可能多的这些前提条件。我们还需要做出几个重要假设:1)市场不是100%随机,2)历史会重演,3)市场遵循人们的理性行为,4)市场是“完美的”。不过,请阅读底部的免责声明。

我们将尝试预测高盛(纽约证券交易所代码:GS)的价格走势。为此,我们将使用2010年1月1日至2018年12月31日的每日收盘价(训练数据为7年,验证数据为2年)。

2、数据

我们需要了解影响GS的股价上涨或下跌的因素。这是人们所关心的问题。因此,我们需要尽可能多地合并信息(从不同方面和角度描绘股票)。 (我们将使用1585天的日线数据来训练各种算法(我们拥有70%的训练数据)并预测接下来的680天(测试数据)。然后我们将预测结果与测试数据分类进行比较(我们将其称为特征)在后面的章节中我们会详细介绍,我们将使用的特征包括:

关联资产 - 这些是其他资产(任何类型,不一定是股票,如商品,外汇,指数,甚至固定收益证券)。像高盛这样的大公司显然不会“生活”在一个孤立的世界中 - 它依赖于许多外部因素并与之互动,包括竞争对手,客户,全球经济,地缘政治形势,财政和货币政策,融资情况等。详情将在后面列出。

技术指标 - 很多投资者都遵循技术指标。我们将最受欢迎的指标作为独立特征。其中包括 - 7日和21日均线,指数均线,动量,布林通道,MACD。

基本面分析 - 一个非常重要的特征,表明股票是上涨还是下跌。基本面分析有两个方面可以为我们所用:1)使用10-K和10-Q报告分析公司业绩,分析ROE和市盈率等(我们不会使用此报告),2)新闻 - 可能新闻可以指示可能在特定方向上移动股票的即将发生的事件。我们将阅读高盛的所有每日新闻,并提取当天对高盛的总体情绪是正面的,中立的还是负面的(我们的打分会介于0-1之间)。由于许多投资者都会仔细阅读新闻并根据新闻做出投资决策(当然不是所有人),如果今天高盛的消息非常积极,那么股票将在明天激增。这里的重点在于,我们将以这些特征的权重作为我们以后判断股票趋势的依据。稍后会详细介绍。

为了创建准确的情绪预测,我们将使用神经语言处理(NLP)。我们将使用BERT - 谷歌最近宣布的NLP方法用于情感分类股票新闻情绪提取的迁移学习。

傅立叶变换 - 除了每日收盘价,我们还将创建傅里叶变换,以概括多个长期和短期趋势。使用这些变换,我们将消除大量噪声(随机的)并创建真实股票移动的近似值。趋势近似可以帮助LSTM网络更准确地选择其预测趋势。

自回归整合移动平均线(ARIMA) - 这是预测时间序列数据未来值的最流行技术之一(在神经网络流行之前)。我们把它也加上,看看它是否是一个重要的预测特征。

栈式自编码器 - 经过数十年的研究后,人们发现了大多数上述特征(基本面分析,技术分析等)。但也许我们错过了一些东西。也许由于大量的数据点,事件,资产,图表等,人们无法理解隐藏的相关性。使用栈式自编码器(神经网络),我们可以使用计算机的强大功能,可能找到新的影响股票走势的特征。即使我们无法用人类语言理解这些功能,我们也会在GAN中使用它们。

深度无监督学习用于期权定价中的异常检测。我们将再使用一项特征 - 每天我们都会为高盛股票增加90天看涨期权的价格。期权定价本身结合了大量数据。期权合约的价格取决于股票的未来价值(分析师也试图预测价格,以便为看涨期权提供最准确的价格)。使用深度无监督学习(自组织映射),我们将尝试发现每天定价中的异常情况。异常(例如定价的急剧变化)可能有利于LSTM来做股票价格的模式识别。

现在我们有了这么多的特征,接下来我们需要执行几个重要的步骤:

对数据的“质量”进行统计检查。如果我们创建的数据存在缺陷,那么无论我们的算法有多复杂,结果都不会很理想。检查包括确保数据不会受到异方差性,多重共线性或串行相关性的影响。

创建特征权重。如果某个特征(例如另一个股票或技术指标)对我们想要预测的股票没有权重,那么我们就不需要在神经网络的训练中使用它。我们将使用XGBoost(eXtreme Gradient Boosting),一种增强树回归算法来创建权重。

作为我们数据准备的最后一步,我们还将使用主成分分析(PCA)创建Eigen投资组合,以减少自编码器创建的特征的维数。


2.1、关联资产

如前所述,我们将使用其他股票数据作为特征,而不仅仅是GS。

那么其他股票是否会影响GS的股票走势?良好地了解公司,其业务线,竞争格局,依赖关系,供应商和客户类型等对于选择正确的关联资产非常重要:

首先是与GS类似的公司。我们将把JPMorgan Chase和Morgan Stanley等添加到数据集中。

作为一家投资银行,高盛(Goldman Sachs)依赖于全球经济。经济不景气或波动意味着没有并购或首次公开募股,也可能是有限的自营交易收益。这就是为什么我们将包括全球经济指数。此外,我们将包括LIBOR(美元和英镑计价)利率,因为分析师可能会考虑经济的冲击来设定这些利率以及其他FI证券。

每日波动率指数(VIX) - 理由同上。

综合指数 - 如纳斯达克和纽约证券交易所(来自美国),FTSE100(英国),日经225(日本),恒生和BSE Sensex(亚太指数)指数。

货币 - 全球贸易多次反映货币如何变动,因此我们将使用一揽子货币(如美元兑日元,英镑兑美元等)作为特征。

总的来说,我们在数据集中有72个其他资产 - 每个资产的日线数据。

2.2、技术指标

我们已经介绍了什么是技术指标以及我们使用它们的原因,让我们直接来看代码。我们将仅为GS创建技术指标。

""" Function to create the technical indicators """

def get_technical_indicators(dataset):

# Create 7 and 21 days Moving Average

dataset['ma7'] = dataset['price'].rolling(window=7).mean()

dataset['ma21'] = dataset['price'].rolling(window=21).mean()

# Create MACD

dataset['26ema'] = pd.ewma(dataset['price'], span=26)

dataset['12ema'] = pd.ewma(dataset['price'], span=12)

dataset['MACD'] = (dataset['12ema']-dataset['26ema'])

# Create Bollinger Bands

dataset['20sd'] = pd.stats.moments.rolling_std(dataset['price'],20)

dataset['upper_band'] = dataset['ma21'] + (dataset['20sd']*2)

dataset['lower_band'] = dataset['ma21'] - (dataset['20sd']*2)

# Create Exponential moving average

dataset['ema'] = dataset['price'].ewm(com=0.5).mean()

# Create Momentum

dataset['momentum'] = dataset['price']-1

return dataset

所以我们每个交易日都有技术指标(包括MACD,布林带等)。我们共有12项技术指标。

2.3、基本面分析

对于基本面分析,我们将对所有关于GS的每日新闻进行情绪分析。最后使用sigmoid,结果将在0和1之间。分数越接近0 - 新闻越消极(接近1表示积极情绪)。对于每一天,我们将创建平均每日分数(作为0到1之间的数字)并将其添加到特征。

2.3.1、Transformer的双向嵌入表示 - BERT

为了将新闻分类为正面或负面(或中性),我们将使用BERT,一种预训练的NLP模型。

MXNet / Gluon已经提供预先训练的BERT模型。我们只需要实例化它们并添加两个(任意数量)Dense层,并使用softmax。

import bert

BERT和NLP部分的细节不在本文的范围内,如果您有兴趣,可以自行dig一番!

2.4、用于趋势分析的傅立叶变换

傅立叶变换使用函数创建一系列正弦波(具有不同的幅度和帧)。组合时,这些正弦波接近原始函数。

我们将使用傅里叶变换来提取GS的全球和本地趋势,并对其进行一些去噪。那么让我们看看它是如何工作的。

""" Code to create the Fuorier trasfrom """

data_FT = dataset_ex_df[['Date', 'GS']]

close_fft = np.fft.fft(np.asarray(data_FT['GS'].tolist()))

fft_df = pd.DataFrame({'fft':close_fft})

fft_df['absolute'] = fft_df['fft'].apply(lambda x: np.abs(x))

fft_df['angle'] = fft_df['fft'].apply(lambda x: np.angle(x))

plt.figure(figsize=(14, 7), dpi=100)

fft_list = np.asarray(fft_df['fft'].tolist())

for num_ in [3, 6, 9, 100]:

fft_list_m10= np.copy(fft_list); fft_list_m10[num_:-num_]=0

plt.plot(np.fft.ifft(fft_list_m10), label='Fourier transform with {} components'.format(num_))

plt.plot(data_FT['GS'], label='Real')

plt.xlabel('Days')

plt.ylabel('USD')

plt.title('Figure 3: Goldman Sachs (close) stock prices & Fourier transforms')

plt.legend()

plt.show()

我们使用傅里叶变换的组件越多,逼近函数越接近实际股票价格。我们使用傅里叶变换来提取长期和短期趋势,因此我们将使用具有3、6和9个分量的变换。您可以推断出具有3个组件的转换表示长期趋势。

用于去噪数据的另一种技术称为小波变换。小波和傅立叶变换给出了类似的结果,因此我们只使用傅里叶变换。

2.5、ARIMA作为特征

ARIMA是一种预测时间序列数据的技术。我们将展示如何使用它,尽管ARIMA不会作为我们的最终预测,我们将使用它作为一种技术来对股票进行一些去噪并(可能)提取一些新的模式或特征。

error = mean_squared_error(test, predictions)

print('Test MSE: %.3f' % error)

output >>> Test MSE: 10.151

ARIMA给出了实际股票价格的非常好的近似值。我们将通过ARIMA使用预测价格作为LSTM的输入特征,因为正如我们之前提到的,我们希望捕获尽可能多的关于高盛的特征和模式。我们测试了MSE(均方误差),结果不太差(考虑到我们确实有很多测试数据),但是,我们只会将它用作LSTM中的一个特征。

2.6、统计学检查

确保数据质量良好对我们的模型非常重要。为了确保我们的数据合适,我们将执行几个简单的检查,以确保我们实现和观察的结果确实是真实的,而不是由于基础数据分布导致一些逻辑性的错误。

2.6.1、异方差性,多重共线性,序列相关性

当误差项(回归的预测值与实际值之间的差异)取决于数据时,会出现条件异方差性 - 例如,当数据点(沿x轴)增长时,误差项会增大。

多重共线性是指误差项(也称为残差)相互依赖的时间。

串行相关是指一个数据(特征)是另一个特征的条件(或完全依赖)。

我们这里不会给出代码,因为它很简单,我们的重点更多地放在深度学习部分,但数据质量必须有保障。

2.7、特征工程

print('Total dataset has {} samples, and {} features.'.format(dataset_total_df.shape[0], dataset_total_df.shape[1]))

output >>> Total dataset has 2265 samples, and 112 features.

在添加所有类型的数据(相关资产,技术指标,基本面分析,傅里叶和Arima)之后,我们有2265天内的112个特征(训练数据只有1585天) 。

我们还将用自编码器生成更多特征。

2.7.1、特征对于XGBoost的重要性

拥有如此多的功能,我们必须考虑它们是否真正表明了GS股票的方向。例如,我们在数据集中包含美元计价的LIBOR利率,因为我们认为LIBOR的变化可能表明经济的变化,反过来,这可能表明GS股票行为的变化。但我们需要用实践说话。有许多方法可以测试特征重要性,但我们将使用XGBoost,因为它在分类和回归问题中都有相当不错的表现。

由于数据集非常大,为了演示,我们将仅使用技术指标。在实际特征重要性测试中,所有选定的特征都被证明很重要,因此我们在训练GAN时不会去掉任何特征。

regressor = xgb.XGBRegressor(gamma=0.0,n_estimators=150,base_score=0.7,colsample_bytree=1,learning_rate=0.05)

xgbModel = regressor.fit(X_train_FI,y_train_FI, eval_set = [(X_train_FI, y_train_FI), (X_test_FI, y_test_FI)], verbose=False)

fig = plt.figure(figsize=(8,8))

plt.xticks(rotation='vertical')

plt.bar([i for i in range(len(xgbModel.feature_importances_))], xgbModel.feature_importances_.tolist(), tick_label=X_test_FI.columns)

plt.title('Figure 6: Feature importance of the technical indicators.')

plt.show()

结果很明确,MA7,MACD和BB是其中的重要特征。

我遵循相同的逻辑来对整个数据集执行特征重要性测试,训练花费的时间更长,结果更难以归纳。

2.8、使用栈式自编码器提取高级特征

在我们研究自编码器之前,我们先讨论激活函数。

2.8.1、激活函数 - GELU(高斯误差)

最近提出的GELU,全称为Gaussian Error Linear Unites。在论文中,作者展示了使用GELU优于ReLU的几个实例。 gelu也用于BERT,用于新闻情绪分析的NLP方法。

我们将使用GELU作为自编码器。

我在MXNet中实现了GELU。如果您按照代码并将act_type ='relu'更改为act_type ='gelu',则无法使用,除非您更改MXNet的实现。

让我们来看一下GELU,ReLU和LeakyReLU(最后一个主要用于GAN - 我们也使用它)。

def gelu(x):

return 0.5 * x * (1 + math.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * math.pow(x, 3))))

def relu(x):

return max(x, 0)

def lrelu(x):

return max(0.01*x, x)

plt.figure(figsize=(15, 5))

plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=.5, hspace=None)

ranges_ = (-10, 3, .25)

plt.subplot(1, 2, 1)

plt.plot([i for i in np.arange(*ranges_)], [relu(i) for i in np.arange(*ranges_)], label='ReLU', marker='.')

plt.plot([i for i in np.arange(*ranges_)], [gelu(i) for i in np.arange(*ranges_)], label='GELU')

plt.hlines(0, -10, 3, colors='gray', linestyles='--', label='0')

plt.title('Figure 7: GELU as an activation function for autoencoders')

plt.ylabel('f(x) for GELU and ReLU')

plt.xlabel('x')

plt.legend()

plt.subplot(1, 2, 2)

plt.plot([i for i in np.arange(*ranges_)], [lrelu(i) for i in np.arange(*ranges_)], label='Leaky ReLU')

plt.hlines(0, -10, 3, colors='gray', linestyles='--', label='0')

plt.ylabel('f(x) for Leaky ReLU')

plt.xlabel('x')

plt.title('Figure 8: LeakyReLU')

plt.legend()

plt.show()

注意:在后面的文章中,我将尝试使用U-Net,并尝试利用卷积层并提取(并创建)关于股票的模式的更多特征。现在,我们将只使用一个仅由Dense层组成的简单自编码器。

我们回到自编码器的讨论。

注意:我将在以后的文章中探讨的一件事是删除解码器中的最后一层。通常,在自编码器中,编码器的数量==解码器的数量。但是,我们希望提取更高级别的特征(而不是创建相同的输入),因此我们可以跳过解码器中的最后一层。我们实现了这一点,在训练期间创建了具有相同层数的编码器和解码器,但是当我们创建输出时,我们去掉了最后一层,因为这样解码器能包含更高级别的特征。

我们从自编码器中创建了112个特征。由于我们只想拥有高级特征(整体模式),我们将使用主成分分析(PCA)在新创建的112个特征上创建特征投资组合。这将减少数据的维度(列数)。 Eigen组合的描述能力将与原始的112个特征相同。

注意,这纯粹是实验性的。我并非100%确定我描述的逻辑成立。但是人工智能和深度学习不就是一场彻头彻尾的实验吗?

3、生成对抗网络(GAN)

GAN如何运作?

如前所述,本文的目的不是详细解释深度学习背后的数学,而是展示其应用。当然,在我看来,对基础和细节的理解是非常必要的。因此,我们将尝试介绍GAN如何工作,以便读者完全理解使用GAN预测股票价格变动的理由。如果您对GAN有经验,请跳过此部分和下一部分(并跳到第4.2节)。

GAN网络由两个模型组成 - 生成器(G)和裁决器(D)。训练GAN的步骤如下:

使用随机数据(噪声表示为z),生成器试图“生成”与真实数据无法区分或非常接近的数据。其目的是学习真实数据的分布。

随机生成的数据被拟合到裁决器中,裁决器充当分类器并试图了解数据是来自生成器还是真实数据。 D估计输入样本到真实数据集的(分布)概率。

然后,G和D的Loss通过生成器传播回来。因此,生成器的Loss取决于生成器和裁决器。这帮助Generator了解真实数据分布。如果生成器在生成真实数据(具有相同分布)方面做得不好,则裁决器的工作将很容易区分生成与真实数据集。因此,裁决器的Loss将非常小。裁决器Loss越小,生成器Loss则越大。这使得创建裁决器比较困难,因为裁决器太优秀会导致巨大的生成器Loss,使得生成器无法学习。

该过程一直持续到Discriminator无法再区分生成数据与实际数据区。

当组合在一起时,D和G像是在玩minmax游戏(生成器试图欺骗裁决器,即最小化z〜pz(z)[log(1-D(G(G) z)))]。裁决器想要通过最大化x〜pr(x)[logD(x)]来分离来自生成器D(G(z))的数据。但是,由于具有各自的损失函数,它是不清楚两者如何汇合在一起(这就是为什么我们使用普通GAN的一些变种,例如Wasserstein GAN)。

注意:我不会在本文中包含GAN背后的完整代码和强化学习部分。

3.1、为什么GAN用于股市预测

生成对抗网络(GAN)主要用于创建逼真的图像,绘画和视频剪辑。在我们的案例中,没有很多GAN用于预测时间序列数据的应用。然而,主要思想应该是相同的 - 我们希望预测未来的股票变动。在未来,GS股票的模式和行为应该或多或少相同(除非它开始以完全不同的方式运作,或者经济急剧变化)。因此,我们希望“生成”未来的数据,这些数据将具有与我们已有的相似(当然不完全相同) - 历史交易数据。所以,从理论上讲,它应该有效。

在我们的例子中,我们将使用LSTM作为时间序列生成器,并使用CNN作为裁决器。

3.2、Metropolis-Hastings GAN和Wasserstein GAN

注意:接下来的内容我们假定读者有一些GAN的经验。

I、Metropolis-Hastings GAN

Uber的工程团队最近对传统GAN进行了改进,名为Metropolis-Hastings GAN(MHGAN)。优步的方法背后的想法(正如他们所说)与谷歌和加州大学伯克利分校创建的另一种方法有点类似,称为判别式拒绝采样(DRS)。基本上,当我们训练GAN时,我们使用裁决器(D)的唯一目的是更好地训练生成器(G)。通常,在训练GAN之后我们不再使用D.然而,MHGAN和DRS尝试使用D来选择由G生成的接近实际数据分布的样本(MHGAN之间的微小差异是使用马尔可夫链蒙特卡罗(MCMC)进行采样)。

MHGAN采用从G生成的K个样本。然后它依次运行K输出并遵循接受规则(从裁决器创建)决定是接受当前样本还是保留最后接受的样本。最后保留的输出是被认为是G的实际输出的输出。

注意:MHGAN最初由优步在pytorch中实现。我只把它移植到MXNet / Gluon。

II、Wasserstein GAN

训练GAN非常困难。模型可能永远不会收敛,模式崩溃很容易发生。我们将使用名为Wasserstein GAN。

同样,我们不会详细介绍,但最值得注意的点是:

我们知道GAN背后的主要目标是让Generator开始将随机噪声转换为我们想要模仿的某些给定数据。因此,在GAN中,比较两个分布之间的相似性的想法是非常必要的。两个最广泛使用的指标是:

KL散度(Kullback-Leibler) - DKL(p‖q)=∫xp(x)logp(x)q(x)dx。当p(x)等于q(x)时,DKL为零。

JS Divergence(Jensen-Shannon)。 JS散度以0和1为界,与KL散度不同,它是对称的,更平滑的。当损失从KL转为JS分歧时,GAN训练取得了重大成功。

WGAN使用Wasserstein距离,W(pr,pg)=1Ksup‖f‖L≤Kx~pr[f(x)] - x~pg[f(x)](其中sup代表supremum),作为损失函数(也称为地球移动者的距离,因为它通常被解释为将一堆沙子移动到另一堆,两个堆具有不同的概率分布,在转换期间使用最小能量)。与KL和JS差异相比,Wasserstein度量给出了一个平滑的度量(没有突然的跳跃)。这使得它更适合在梯度下降期间创建稳定的学习过程。

此外,与KL和JS相比,Wasserstein距离几乎无处不在。众所周知,在反向传播过程中,我们会区分损失函数以创建渐变,从而更新权重。因此,具有可微分的损失函数是非常重要的。

这是本文最难的部分。混合WGAN和MHGAN花了我三天时间。

3.3、生成器 - 单层RNN

3.3.1、LSTM还是GRU

如前所述,生成器使用LSTM(RNN)。 RNN用于时间序列数据,因为它们跟踪所有先前的数据点,并且可以捕获随时间发展的模式。由于RNN的特点,RNN很多时候都会受到梯度消失的影响 - 也就是说,在训练期间权重变化变得非常之小,以至于网络无法收敛到最小的损失(相反的状况有时也会出现 - 梯度变得太大。这称为梯度爆炸,解决方法非常简单 - 如果梯度开始超过某个常数,截断它。GRU和长短期记忆(LSTM)解决了这两个问题。它们两者之间的最大区别是:1)GRU有2个Gate(更新和复位),LSTM有4个(更新,输入,忘记和输出),2)LSTM维持内存状态,而GRU没有, 3)LSTM在输出Gate之前用非线性(sigmoid),GRU不用。

在大多数情况下,LSTM和GRU在准确性方面给出了类似的结果,但GRU的计算密度要低得多,因为GRU的可训练参数更少。不过LSTM应用更为广泛。

3.3.2、LSTM的架构

我们使用的LSTM架构非常简单 - 一个LSTM层具有112个输入单元(因为我们在数据集中有112个特征)和500个隐藏单元,一个Dense层具有1个输出 - 每天的价格。初始化使用Xavier,我们将使用L1 Loss(这是L1正则化的平均绝对误差损失)。

注意 - 在代码中,您可以看到我们使用Adam(学习率为.01)作为优化器。大家暂时可以不用过多关注 - 有一节特别专门解释我们使用的超参数以及我们如何优化这些超参数。

gan_num_features = dataset_total_df.shape[1]

sequence_length = 17

class RNNModel(gluon.Block):

def __init__(self, num_embed, num_hidden, num_layers, bidirectional=False, sequence_length=sequence_length, **kwargs):

super(RNNModel, self).__init__(**kwargs)

self.num_hidden = num_hidden

with self.name_scope():

self.rnn = rnn.LSTM(num_hidden, num_layers, input_size=num_embed, bidirectional=bidirectional, layout='TNC')

self.decoder = nn.Dense(1, in_units=num_hidden)

def forward(self, inputs, hidden):

output, hidden = self.rnn(inputs, hidden)

decoded = self.decoder(output.reshape((-1,self.num_hidden)))

return decoded, hidden

def begin_state(self, *args, **kwargs):

return self.rnn.begin_state(*args, **kwargs)

lstm_model = RNNModel(num_embed=gan_num_features, num_hidden=500, num_layers=1)

lstm_model.collect_params().initialize(mx.init.Xavier(), ctx=mx.cpu())

trainer = gluon.Trainer(lstm_model.collect_params(), 'adam', {'learning_rate': .01})

loss = gluon.loss.L1Loss()

我们在LSTM层中使用500个神经元并使用Xavier初始化。正则化使用L1。让我们看看LSTM里面有什么。

print(lstm_model)

output >>>

RNNModel(

(rnn): LSTM(112 -> 500, TNC)

(decoder): Dense(500 -> 1, linear)

)

正如我们所看到的,LSTM的输入是112个特征(dataset_total_df.shape [1]),然后是LSTM层中的500个神经元,然后转换为单个输出 - 股票价格值。

LSTM背后的逻辑是:我们需要17天(sequence_length)天数据(数据是每天GS股票的股票价格+当天所有其他特征 - 相关资产,情绪等)并试图预测第18天。然后我们将17天的窗口移动一天,并再次预测第18天。我们在整个数据集上进行迭代(当然是批量)。

在另一篇文章中,我将探讨vanilla LSTM以及如何优化,例如:

使用双向LSTM层 - 理论上,向后(从数据集的末尾到开头)可能以某种方式帮助LSTM找出股票运行的模式。

使用栈式RNN架构 - 多个LSTM层。然而,我们可能最终过度拟合模型,因为我们没有大量数据(我们只有1,585天的数据)。

GRU - 如前所述,GRU的cell要简单得多。

将注意力机制添加到RNN。

--------------------------------------------

3.3.3、学习率调度程序

最重要的超参数之一是学习率。在训练神经网络时,为几乎所有优化器(例如SGD,Adam或RMSProp)设置学习速率至关重要,因为它既控制收敛速度又控制网络的最终性能。最简单的学习率策略之一是在整个培训过程中具有固定的学习率。选择较小的学习速率可以使优化器找到好的解决方案,但这是以限制收敛速度为代价的。随着时间训练的推移改变学习率是比较好的折中方法。

schedule = CyclicalSchedule(TriangularSchedule, min_lr=0.5, max_lr=2, cycle_length=500)

iterations=1500

plt.plot([i+1 for i in range(iterations)],[schedule(i) for i in range(iterations)])

plt.title('Learning rate for each epoch')

plt.xlabel("Epoch")

plt.ylabel("Learning Rate")

plt.show()

3.3.4、如何防止过拟合和偏差 - 方差权衡

特征众多的神经网络尤其需要防止过度拟合。

我们使用几种技术来防止过度拟合:

首先确保数据质量。我们已经进行了统计检查,并确保数据不会受到多重共线性或串行自相关的影响。此外,我们对每个特征进行了权重检查。最后还要注意初始特征选择(例如,选择相关资产,技术指标等),这是通过一些关于股票市场运作方式背后的机制的领域知识来完成的。

正则化(或权重惩罚)。两种最广泛使用的正则化技术是LASSO(L1)和Ridge(L2)。 L1增加了平均绝对误差,L2增加了平均误差。简单的解释差异就是,L1同时进行变量选择和参数收缩,而L2仅进行参数收缩并最终保留模型中的所有系数。在存在相关变量的情况下,L2可能是首选。此外,L2在最小二乘估计具有较高方差的情况下效果最佳。因此,这取决于我们的模型目标。两种类型的正则化的影响是完全不同的。虽然它们都会对大权重进行惩罚,但L1正则化会导致零不可微函数。 L2正则化有利于较小的权重,但L1正则化有利于权重变为零。因此,使用L1正则化,您可以得到一个稀疏模型 - 一个参数较少的模型。在这两种情况下,L1和L2正则化模型的参数“收缩”,但在L1正则化的情况下,收缩直接影响模型的复杂性(参数的数量)。准确地说,L2在最小二乘估计具有较高方差的情况下效果最佳。 L1对异常值更稳健,在数据稀疏时使用,并创建特征权重。我们将使用L1。

Dropout。 Dropout层随机删除隐藏图层中的节点。

Dense-sparse-dense训练。

Early stopping。

构建复杂神经网络时的另一个重要需考虑因素是偏差 - 方差权衡。基本上,训练神经网络时我们得到的误差是偏差,方差和不可约误差的函数 - σ(由噪声和随机性引起的误差)。权衡的最简单公式是:误差=偏差^ 2 +方差+σ。

偏差。偏差表现了模型的泛化能力。高偏差(欠拟合)意味着模型不能很好地处理未知数据。

方差。方差衡量模型对数据集变化的敏感性。高方差表示过度拟合。

3.4、裁决器 - 一维CNN

3.4.1、为什么CNN可以做裁决器?

我们通常将CNN用于与图像相关的工作(分类,上下文提取等)。它们在提取特征方面非常强大。例如,在狗的图像中,第一个卷积层将检测边缘,第二个将开始检测圆,第三个将检测到鼻子。在我们的例子中,数据点形成小趋势,小趋势形成大趋势,大趋势形成模式。 CNN检测特征的能力可用于提取有关GS股票价格变动模式的信息。

使用CNN的另一个原因是CNN在空间数据上运行良好 - 这意味着彼此更接近的数据点彼此之间的相关性更高,而不是数值相关。对于时间序列数据,这也适用。在我们的例子中,每个数据点(对于每个特征)是连续的每一天。很自然地假设彼此距离越近,彼此之间的相关性就越大。需要考虑的一件事(虽然这项工作没有涉及)是周期性以及它如何改变(如果有的话)CNN。

注意:与本文中的许多其他部分一样,使用CNN进行时间序列数据是实验性的。我们将检查结果,而不提供数学或其他研究。结果可能因使用不同的数据,激活函数等而有所不同。

3.4.2、CNN架构

如下是用MXNet打印的CNN。

Sequential(

(0): Conv1D(None -> 32, kernel_size=(5,), stride=(2,))

(1): LeakyReLU(0.01)

(2): Conv1D(None -> 64, kernel_size=(5,), stride=(2,))

(3): LeakyReLU(0.01)

(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=None)

(5): Conv1D(None -> 128, kernel_size=(5,), stride=(2,))

(6): LeakyReLU(0.01)

(7): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=None)

(8): Dense(None -> 220, linear)

(9): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=None)

(10): LeakyReLU(0.01)

(11): Dense(None -> 220, linear)

(12): Activation(relu)

(13): Dense(None -> 1, linear)

)

3.5、超参数

我们将跟踪和优化的超参数是:

batch_size:LSTM和CNN的批大小

cnn_lr:CNN的学习率

stride:CNN中的步长

lrelu_alpha:GAN中LeakyReLU的alpha

batchnorm_momentum:CNN中批量正则化的动量

padding:CNN中的padding

kernel_size:CNN中的内核大小

Dropout:LSTM中的Dropout

filters:初始filter数量

我们将训练200多个epoch。

4、超参数优化

在200个epoch的GAN训练之后,我们记录MAE(这是LSTM中的LOSS函数,GG)并将其作为奖励值传递给强化学习,该学习将决定是否改变继续训练的超参数。如稍后所述,该方法严格用于试验RL。

如果RL决定它将更新超参数,它将调用贝叶斯优化库,该库将提供下一个最佳预期的超级参数集

4.1、针对超参数优化的强化学习

为什么我们在超参数优化中使用强化学习?因为股市一直在变化。即使我们设法训练我们的GAN和LSTM以创建非常准确的结果,结果可能仅在一段时间内有效。意思是,我们需要不断优化整个过程。为了优化流程,我们可以:

添加或删除特征(例如添加可能相关的新股票或货币)

改善我们的深度学习模式。改进模型的最重要方法之一是通过超参数。一旦找到了一组超参数,我们需要决定何时更改它们以及何时使用已知的集合。此外,股票市场代表一个连续的空间,取决于数百万参数。

注意:本文整个强化学习部分的目的面向研究。我们将使用GAN作为环境探索不同的RL方法。在不使用RL的情况下,我们可以通过多种方式在深度学习模型上成功执行超参数优化。

注意:接下来的内容假定您对RL有一些了解 - 尤其是Policy optimization和Q-learning。

4.1.1、强化学习理论

在不解释RL的基础知识的情况下,我们将详细介绍我们实现的具体方法。我们将使用无模型RL算法,原因很明显我们不了解整个环境,因此没有明确的模型来说明环境是如何工作的,我们不需要预测股票价格变动。我们将使用无模型RL的两个流行的实现 - Policy optimization和Q学习。

Q-learning - 在Q-learning中我们学习从一个给定的状态采取行动的值。 Q值是采取行动后的预期回报。我们将使用Rainbow,它是七种Q学习算法的组合。

Policy optimization - 在Policy optimization中,我们学习从给定状态采取的操作。 (如果我们使用像Actor / Critic这样的方法),我们也会学习处于给定状态的值。我们将使用Proximal Policy Optimization。

构建RL算法的一个关键点是准确设置奖励。它必须捕获环境的所有方面以及代理与环境的交互。我们将奖励R定义为:

奖励= 2 * lossG + lossD + accuracyG,

其中lossG,accuracyG和lossD分别是Generator的损失和准确性,以及Discriminator的损失。环境是GAN和LSTM训练的结果。不同代理可以采取的行动是如何更改GAN的D和G网络的超参数。

4.1.1.1、Rainbow

什么是Rainbow?

Rainbow是一种基于Q学习的非策略深度强化学习算法,它将七种算法结合在一起:

DQN。 DQN是Q学习算法的扩展,其使用神经网络来表示Q值。与监督(深度)学习类似,在DQN中,我们训练神经网络并尝试最小化损失函数。我们通过随机抽样过渡(状态,行动,奖励)来训练网络。例如,这些层不仅可以是完全连接的层,也可以是卷积层。

Double Q learning。双QL处理Q学习中的大问题,即高估偏差。

Prioritized replay。在vanilla DQN中,所有转换都存储在重放缓冲区中,并均匀地对此缓冲区进行采样。然而,并非所有过渡在学习阶段都同样有益(这也使得学习效率低,因为需要更多的epoch)。Prioritized replay不是均匀采样,而是使用分布,该分布为先前迭代中具有较高Q损失的样本提供更高的概率。

Dueling networks。它通过使用两个单独的流(即,具有两个不同的微型神经网络)来改变Q learning架构。一个流用于value,一个用于advantage。它们都共享一个卷积编码器。棘手的部分是流的合并 - 它使用了一个特殊的聚合器。

Multi-step learning。差异在于它使用N步返回计算Q值所以更准确。

分布式RL。 Q学习使用平均估计Q值作为目标值。但是,在许多情况下,Q值在不同情况下可能不同。分布式RL可以直接学习(或近似)Q值的分布,而不是对它们求平均值。同样,数学要复杂得多,但对我们而言,好处是Q值的采样更精确。

Noisy Nets。基本DQN实现了一个简单的ε-贪婪机制来进行探索。这种探索方法有时效率低下。 Noisy Nets解决这个问题的方法是添加一个有噪声的线性层。随着时间的推移,网络将学习如何忽略噪声。但是这种学习在空间的不同部分以不同的速度进行。

4.1.1.2、PPO

近端策略优化(PPO)是一种无策略优化模型的强化学习。比实现其他算法要简单得多,效果非常好。

我们为什么要使用PPO? PPO的一个优点是它直接学习策略,而不是间接地通过值(Q学习使用Q值来学习策略的方式)。它可以在连续action空间中很好地工作,这在我们的使用案例中是合适的,并且可以(通过平均值和标准偏差)学习分布概率(如果将softmax作为输出)。

策略梯度方法的问题在于它们对步长选择极其敏感 - 如果它很小,则需要太长时间(二阶导数矩阵);如果它很大,会有很多噪声显著降低性能。由于策略的变化(以及奖励和观察变化的分布),输入数据是非平稳的。与监督学习相比,不当的步骤可能会更具破坏性,因为它会影响下次访问的整个分布。 PPO可以解决这些问题。更重要的是,与其他一些方法相比的优点例如:

与ACER相比,它需要额外的代码来保持非策略相关性,还需要一个replay缓冲区,或者对代理目标函数施加约束的TRPO(旧的和新的之间的KL分歧)要复杂得多)。这种约束用于控制变化过多的策略 - 这本身就会造成不稳定。 PPO通过利用剪切(在[1-ε,1 +ε]之间)代理目标函数和修改目标函数来减少计算(由约束创建),因为具有非常大的更新。

与TRPO相比,它与在值和策略函数或辅助损失之间共享参数的算法兼容(尽管PPO也具有信任区域PO的增益)。

注意:为了我们的训练,我们不会过多地研究和优化RL方法,PPO和其他方法。相反,我们将采用可用的方法,并尝试适应我们的GAN,LSTM和CNN模型的超参数优化过程。

4.1.2、进一步深入强化学习

进一步探索强化学习的一些想法:

接下来我将介绍的第一件事是使用增强随机搜索(链接)作为替代算法。该算法的作者已经设法获得与其他最先进的方法(如PPO)类似的奖励结果,但速度平均快15倍。

选择奖励函数非常重要。可以尝试使用不同的函数作为替代。

使用好奇心作为exploration策略。

创建多agent体系结构。

4.2、贝叶斯优化

我们也可以使用贝叶斯优化来代替网格搜索,我们可以使用现有的库。

4.2.1、高斯过程

5、结果

6、接下来的研究内容?

接下来,我将尝试创建一个RL环境来测试交易算法,以决定何时以及如何交易。 GAN的输出将是环境中的参数之一。

7、免责声明

本文中提供的任何内容均不构成任何特定证券,证券组合,交易或投资策略适合任何特定人士的建议。期货,股票和期权交易涉及巨大的损失风险,并不适合每个投资者。期货,股票和期权的估值可能会波动,因此客户的损失可能超过其原始投资。

使用所有交易策略需要您自担风险。

还有许多细节需要探索 - 选择数据特征,选择算法,调整算法等等。本文花了我2个星期才完成。而其实还有很多未涉及的方法。

人工智能与深度学习做量化请关注:AI量化(https://t.zsxq.com/RvfY37y) 星球限时免费,如需加入,请私信我获得免费邀请码!

零基础学习Python与深度学习应用请关注星球:Python与深度学习 https://t.zsxq.com/bUFayZ3

微信公众号:QTechAI

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容