【2萬字乾貨】利用深度學習最新前沿預測股價走勢

本期作者:Boris B

本期翻譯:1+1=6 | 公眾號翻譯部成員

↓↓年度巨獻↓↓

【重磅發布】2018中國量化投資年度盤點?

mp.weixin.qq.com圖標

完整代碼文末獲取

正文

在本篇文章中,我們將創建一個完整的程序來預測股票價格的變動。為此,我們將使用生成對抗性網路(GAN),其中LSTM是一種遞歸神經網路,它是生成器,而卷積神經網路CNN是鑒別器。我們使用LSTM的原因很明顯,我們試圖預測時間序列數據。為什麼我們使用GAN,特別是CNN作為鑒別器?這是一個好問題,後面會有專門的部分介紹。

當然,我們將對每個步驟會進行詳細的介紹,但最難的部分是GAN:成功訓練GAN非常棘手的部分是獲得正確的超參數集。因此,我們將使用貝葉斯優化(以及高斯過程)和深度強化學習(DRL)來決定何時以及如何改變GAN的超參數(探索與開發的兩難境地)。在創建強化學習時,我們將使用該領域的最新進展,如RainbowPPO

我們將使用許多不同類型的輸入數據。隨著股票的歷史交易數據和技術指標,我們將使用NLP最新的進展(使用Bidirectional Embedding Representations from Transformers,BERT,一種傳輸學習NLP)創建情緒分析(作為基本分析的來源),傅里葉變換提取總體趨勢方向,stacked autoencoders識別其他高級特徵,尋找相關資產的特徵組合ARIMA用於股票函數的近似度等等,以便儘可能多地獲取關於股票的信息、模式、相關性等。我們都知道,數據越多越好。預測股價走勢是一項極其複雜的任務,所以我們對股票(從不同的角度)了解得越多,我們的變化就越大。

為了創建所有的神經網路,我們將使用MXNet及其高級API - Gluon,並在多個GPU上對它們進行訓練。

註:儘管我們試圖深入探討數學和幾乎所有演算法和技術背後的機制,但本文並沒有明確地解釋機器/深度學習或股票市場是如何運作的。其目的是展示我們如何使用不同的技術和演算法來準確預測股票價格的變動,並給出每一步使用每種技術的原因和有用性背後的理論基礎。

簡介

準確預測股票市場是一項複雜的任務,因為有數百萬的事件和特定股票在特定方向上移動的前提條件。 因此,我們需要能夠儘可能多地捕獲這些前置條件。 我們還需要做出幾個重要的假設:

1、市場不是100%隨機,

2、歷史可以重演

3、市場遵循人們的理性行為

4、市場是「完美的」

我們將嘗試預測高盛的價格走勢。 為此,我們將使用2010年1月1日至2018年12月31日的每日收盤價(訓練集7年,測試集2年)。 我們將互換使用「Goldman Sachs」和「GS」這兩個術語。後面可以換成A股的某個股票,大家可以嘗試。

數據

我們需要了解是什麼影響了GS的股價是上漲還是下跌。這是人們的整體想法。因此,我們需要整合儘可能多的信息(從不同的方面和角度來描述股票)。我們將使用每天的數據—1585天來訓練各種演算法(70%的數據),並預測接下來的680天(測試數據)。然後我們將把預測結果與測試數據進行比較。每種類型的數據(我們將其稱為特徵)將在後面的章節中進行更詳細的解釋,但是,作為一個高層次的概述,我們將使用的特性是:

1、相關資產

這些是其他資產(任何類型,不一定是股票,如大宗商品、外匯、指數,甚至是固定收益證券)。高盛這樣的大公司顯然不是「生活」在一個孤立的世界裡——它依賴並與許多外部因素相互作用,包括競爭對手、客戶、全球經濟、地緣政治形勢、財政和貨幣政策、資本獲取渠道等。

2、技術指標

很多投資者遵循技術指標。我們將包括最受歡迎的指標作為獨立的功能。其中7和21天移動平均線,指數移動平均線,動量,波林格帶,MACD。

3、基本面分析

一個非常重要的特徵,表明股票是否可能上漲或下跌。基本面分析有兩個特點:

a. 使用10-K和10-Q報告分析公司業績,分析ROE和P/E,等等。

b. 我們將為高盛和閱讀每日新聞提取總情緒是否對高盛在那一天是正的,中性的,還是消極的(如得分從0到1)。像許多投資者密切閱讀新聞和做出投資決策基於新聞,如果機會高盛今天是非常積極的消息明天股票飆升。關鍵的一點是,我們將在以後對每個特性執行特性重要性(這意味著它對GS的移動有多麼具有指示性),並決定是否使用它。稍後將對此進行詳細介紹。

為了建立準確的情緒預測,我們將使用NLP。我們將使用BERT -谷歌最近宣布的NLP方法來轉移學習情緒分類股票新聞情緒提取。

4、傅立葉變換

隨著每日收盤價,我們將創建傅立葉變換,以概括幾個長期和短期趨勢。使用這些變換,我們將消除許多噪音(隨機漫步),並創建真實股票運動的近似。採用趨勢逼近可以幫助LSTM網路更準確地選取預測趨勢。

5、ARIMA

這是(在前神經網路時代)預測時間序列數據未來值最流行的技術之一。讓我們看看它是否是一個重要的預測方法。

6、棧式自動編碼器(Stacked autoencoders)

上面提到的大部分特性(基礎分析、技術分析等)都是人們經過幾十年的研究發現的。但也許我們錯過了什麼。也許有一些隱藏的相關性,人們無法理解,因為有大量的數據點、事件、資產、圖表等。通過棧式自動編碼器,我們可以利用計算機的力量,可能會發現影響股票走勢的新類型的特徵。即使我們無法理解人類語言中的這些特性,我們也將在GAN中使用它們。

7、深度無監督學習

期權定價中異常檢測的深度無監督學習。我們還將使用一個新功能——每增加一個高盛股票90天看漲期權的價格。期權定價本身結合了大量數據。期權合約的價格取決於股票的未來價值(分析師也試圖預測價格,以便得出最準確的看漲期權價格)。利用深度無監督學習,我們將試圖發現每天定價中的異常。異常(例如價格的劇烈變化)可能表明一個事件可能對LSTM了解整個股票模式有用。

有這麼多特性,接下來,我們需要執行幾個重要步驟:

1、對數據的「質量」進行統計檢查。如果我們創建的數據有缺陷,那麼無論我們的演算法多麼複雜,結果都不會是正面的。檢查包括確保數據不受異方差、多重共線性或序列相關性的影響。

2、創建特徵的重要性。如果一個特徵(如另一隻股票或一個技術指標)對我們想預測的股票沒有解釋力,那麼我們就沒有必要在神經網路的訓練中使用它。我們將使用XGBoost(eXtreme Gradient boost),一種增強樹回歸演算法。

作為數據準備的最後一步,我們還將使用主成分分析(PCA)創建特徵投資組合,以減少自編碼器創建的特徵的維數。

from utils import *

import time
import numpy as np

from mxnet import nd, autograd, gluon
from mxnet.gluon import nn, rnn
import mxnet as mx
import datetime
import seaborn as sns

import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.decomposition import PCA

import math

from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import StandardScaler

import xgboost as xgb
from sklearn.metrics import accuracy_score
import warnings
warnings.filterwarnings("ignore")
context = mx.cpu(); model_ctx=mx.cpu()
mx.random.seed(1719)

def parser(x):
return datetime.datetime.strptime(x,%Y-%m-%d)
dataset_ex_df = pd.read_csv(data/panel_data_close.csv, header=0, parse_dates=[0], date_parser=parser)

plt.figure(figsize=(14, 5), dpi=100)
plt.plot(dataset_ex_df[Date], dataset_ex_df[GS], label=Goldman Sachs stock)
plt.vlines(datetime.date(2016,4, 20), 0, 270, linestyles=--, colors=gray, label=Train/Test data cut-off)
plt.xlabel(Date)
plt.ylabel(USD)
plt.title(Figure 2: Goldman Sachs stock price)
plt.legend()
plt.show()

虛線表示訓練和測試數據之間的分離:

num_training_days = int(dataset_ex_df.shape[0]*.7)
print(Number of training days: {}. Number of test days: {}..format(num_training_days,
dataset_ex_df.shape[0]-num_training_days))
Number of training days: 1585. Number of test days: 680.

2.1 相關資產

如前所述,我們將使用其他資產作為特性,而不僅僅是GS。

那麼,還有哪些資產會影響高盛的股價走勢呢?對公司、業務線、競爭環境、依賴關係、供應商和客戶類型等的良好理解對於選擇正確的相關資產集非常重要:

1、首先是類似於GS的公司。我們將把摩根大通和摩根士丹利等公司加入數據集。

2、作為一家投資銀行,高盛依賴於全球經濟。糟糕或不穩定的經濟意味著沒有併購或IPO,可能限制自營交易收入。這就是我們將納入全球經濟指數的原因。此外,我們還將包括LIBOR(以美元和英鎊計價)利率,因為分析師設定這些利率可能會考慮到經濟中的衝擊。

3、每日波動率指數(VIX)。

4、綜合指數。例如納斯達克和紐約證券交易所、FTSE100指數、Nikkei225指數、恒生指數和BSE Sensex指數。

5、貨幣。全球貿易經常反映在貨幣的走勢上,因此我們將使用一籃子貨幣(如美元兌日元、英鎊兌美元等)作為特徵。

2.2 技術指標

我們已經討論了什麼是技術指標以及為什麼使用它們,現在讓我們直接跳到代碼。我們將只為GS創建技術指標。

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個技術指標。

部分指標

讓我們來看看這些指標的最後400天的走勢:

def plot_technical_indicators(dataset, last_days):
plt.figure(figsize=(16, 10), dpi=100)
shape_0 = dataset.shape[0]
xmacd_ = shape_0-last_days

dataset = dataset.iloc[-last_days:, :]
x_ = range(3, dataset.shape[0])
x_ =list(dataset.index)

# Plot first subplot
plt.subplot(2, 1, 1)
plt.plot(dataset[ma7],label=MA 7, color=g,linestyle=--)
plt.plot(dataset[price],label=Closing Price, color=b)
plt.plot(dataset[ma21],label=MA 21, color=r,linestyle=--)
plt.plot(dataset[upper_band],label=Upper Band, color=c)
plt.plot(dataset[lower_band],label=Lower Band, color=c)
plt.fill_between(x_, dataset[lower_band], dataset[upper_band], alpha=0.35)
plt.title(Technical indicators for Goldman Sachs - last {} days..format(last_days))
plt.ylabel(USD)
plt.legend()

# Plot second subplot
plt.subplot(2, 1, 2)
plt.title(MACD)
plt.plot(dataset[MACD],label=MACD, linestyle=-.)
plt.hlines(15, xmacd_, shape_0, colors=g, linestyles=--)
plt.hlines(-15, xmacd_, shape_0, colors=g, linestyles=--)
plt.plot(dataset[log_momentum],label=Momentum, color=b,linestyle=-)

plt.legend()
plt.show()

plot_technical_indicators(dataset_TI_df, 400)

2.3 基本面分析

對於基本面分析,我們將對所有關於GS的每日新聞進行情緒分析。最後使用sigmoid,結果將在0到1之間。得分越接近0,負面消息就越多(接近1表示正面情緒)。對於每一天,我們將創建平均每日分數(作為0到1之間的數字),並將其添加為一個特徵。

2.3.1 BERT

為了將新聞分類為積極的或消極的(或中性的),我們將使用BERT,這是一種預先訓練的語言表示。

import bert

已經在MXNet/Gluon中提供了訓練有素的BERT模型。我們只需要實例化它們並添加兩個(任意數字)Denselayers,到softmax—分數從0到1。

2.4 用於趨勢分析的傅里葉變換

傅里葉變換取一個函數並創建一系列正弦波(具有不同的振幅和幀)。當這些正弦波合在一起時,就近似於原始函數。從數學上講,變換是這樣的:

我們將使用傅里葉變換來提取GS股票的整體和局部趨勢,並對其進行降噪。我們來看看它是如何工作的。

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所示,我們使用的傅里葉變換的分量越多,逼近函數就越接近真實股價(100個分量的變換幾乎與原始函數相同——紅色和紫色的線幾乎重合)。我們使用傅里葉變換的目的是提取長期和短期的趨勢,所以我們將使用含有3、6和9個分量的變換。可以推斷,包含3個組件的轉換是長期趨勢。from collections import deque

items = deque(np.asarray(fft_df[absolute].tolist()))

items.rotate(int(np.floor(len(fft_df)/2)))

plt.figure(figsize=(10, 7), dpi=80)

plt.stem(items)

plt.title(Figure 4: Components of Fourier transforms)

plt.show()

用於降噪數據的另一種技術是調用小波。小波和傅里葉變換給出了相似的結果所以我們只使用傅里葉變換。

2.5 ARIMA作為一個特徵

ARIMA是一種預測時間序列數據的方法。我們將展示如何使用它,雖然ARIMA不能作為我們的最終預測,但我們將使用它作為一種技術來稍微降低庫存的雜訊,並(可能)提取一些新的模式或特性。

from statsmodels.tsa.arima_model import ARIMA
from pandas import DataFrame
from pandas import datetime

series = data_FT[GS]
model = ARIMA(series, order=(5, 1, 0))
model_fit = model.fit(disp=0)
print(model_fit.summary())

from pandas.tools.plotting import autocorrelation_plot
autocorrelation_plot(series)
plt.figure(figsize=(10, 7), dpi=80)
plt.show()

plt.figure(figsize=(12, 6), dpi=100)
plt.plot(test, label=Real)
plt.plot(predictions, color=red, label=Predicted)
plt.xlabel(Days)
plt.ylabel(USD)
plt.title(Figure 5: ARIMA model on GS stock)
plt.legend()
plt.show()

從圖5中可以看出,ARIMA給出了一個非常接近實際股價的結果。我們將通過ARIMA使用預測價格作為LSTM的輸入特徵,因為正如我們前面提到的,我們希望儘可能多地捕獲關於高盛的特性和模式。我們測試MSE(均方誤差)為10.151,這本身並不是一個壞結果(考慮到我們有很多測試數據),但是我們仍然只將其作為LSTM中的一個特徵。

2.6 統計檢查

對於我們的模型來說,確保數據具有良好的質量是非常重要的。為了確保我們的數據是合適的,我們將執行幾個簡單的檢查,以確保我們實現和觀察到的結果是真實的,而不是因為底層數據分布存在基本錯誤而受到損害。

2.6.1 異方差,多重共線性,序列相關

1、條件異方差發生在誤差項(通過回歸得到的預測值與實際值之間的差)依賴於數據時例如,誤差項隨著數據點(沿x軸)的增長而增長。

2、多重共線性是指錯誤項(也稱為殘差)相互依賴。

3、序列相關性是指一個數據(特徵)是另一個特徵的公式(或完全不相關)。

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)之後,我們在這2,265天中總共有112個特徵(如前所述,訓練數據只有1,585天)。

我們還將從自動編碼器中生成更多的特徵

2.7.1 XGBoost的特性重要性

有這麼多的特點,我們必須考慮是否所有這些都是真正的指示方向,GS股票將採取。例如,我們在數據集中包含了以美元計價的LIBOR利率,因為我們認為LIBOR的變化可能表明經濟的變化,而經濟的變化又可能表明GS的股票行為的變化。但我們需要測試。測試特性重要性的方法有很多,但是我們將使用XGBoost,因為它在分類和回歸問題中都給出了最好的結果之一。

由於特性數據集非常大,因此在這裡我們僅使用技術指標進行演示。在實際的特徵重要性測試中,所有選擇的特徵都被證明是比較重要的,所以我們在訓練GAN時不會排除任何東西。

def get_feature_importance_data(data_income):
data = data_income.copy()
y = data[price]
X = data.iloc[:, 1:]

train_samples = int(X.shape[0] * 0.65)

X_train = X.iloc[:train_samples]
X_test = X.iloc[train_samples:]

y_train = y.iloc[:train_samples]
y_test = y.iloc[train_samples:]

return (X_train, y_train), (X_test, y_test)
# Get training and test data
(X_train_FI, y_train_FI), (X_test_FI, y_test_FI) = get_feature_importance_data(dataset_TI_df)
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)
eval_result = regressor.evals_result()
training_rounds = range(len(eval_result[validation_0][rmse]))

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 -高斯誤差線性單元是近年來提出的一種新的激活函數。在這篇論文中,作者展示了幾個使用GELU的神經網路優於使用ReLU作為激活的神經網路的實例。GELU也被用於BERT,我們用於新聞情緒分析的NLP方法。

論文地址:arxiv.org/pdf/1606.0841

我們將使用GELU作為自動編碼器。

註:下面的單元格展示了GELU數學背後的邏輯。它不是作為激活函數的實際實現。我必須在MXNet中實現GELU。如果您按照代碼將act_type=relu更改為act_type=gelu,那麼它將不起作用,除非您更改MXNet的實現。對整個項目發出pull請求,以訪問GELU的MXNet實現。

讓我們來看看GELU、ReLU和LeakyReLU(最後一個主要用於GANs,我們也使用它)。

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 ,並嘗試利用卷積層,提取並創建更多關於股票基本移動模式的特徵。現在,我們將只使用一個簡單的自動編碼器只由密集的層。

好了,回到自動編碼器,如下圖所示(圖像只是示意圖,它不代表真實的層數、單元數等)。

由於代碼超長,我們只給出一段代碼:

model_ctx = mx.cpu()
class VAE(gluon.HybridBlock):
def __init__(self, n_hidden=400, n_latent=2, n_layers=1, n_output=784,
batch_size=100, act_type=relu, **kwargs):
self.soft_zero = 1e-10
self.n_latent = n_latent
self.batch_size = batch_size
self.output = None
self.mu = None
super(VAE, self).__init__(**kwargs)

with self.name_scope():
self.encoder = nn.HybridSequential(prefix=encoder)

for i in range(n_layers):
self.encoder.add(nn.Dense(n_hidden, activation=act_type))
self.encoder.add(nn.Dense(n_latent*2, activation=None))

self.decoder = nn.HybridSequential(prefix=decoder)
for i in range(n_layers):
self.decoder.add(nn.Dense(n_hidden, activation=act_type))
self.decoder.add(nn.Dense(n_output, activation=sigmoid))

def hybrid_forward(self, F, x):
h = self.encoder(x)
#print(h)
mu_lv = F.split(h, axis=1, num_outputs=2)
mu = mu_lv[0]
lv = mu_lv[1]
self.mu = mu

eps = F.random_normal(loc=0, scale=1, shape=(self.batch_size, self.n_latent), ctx=model_ctx)
z = mu + F.exp(0.5*lv)*eps
y = self.decoder(z)
self.output = y

KL = 0.5*F.sum(1+lv-mu*mu-F.exp(lv),axis=1)
logloss = F.sum(x*F.log(y+self.soft_zero)+ (1-x)*F.log(1-y+self.soft_zero), axis=1)
loss = -logloss-KL

return loss

注意:在以後的版本中探討的一件事是刪除解碼器中的最後一層。通常,在自動編碼器中,編碼器的數量==解碼器的數量。但是,我們希望提取更高級別的特徵(而不是創建相同的輸入),這樣我們就可以跳過解碼器中的最後一層。在訓練過程中,我們使用相同數量的層來創建編碼器和解碼器,但是當我們創建輸出時,我們使用唯一層旁邊的層,因為它將包含更高級別的特徵。

生成對抗網路(GAN)

GAN的概念大家可以百度一下,這裡不再論述。

3.1 為什麼使用GAN預測股市

生成對抗網路(GAN)最近主要用於創建現實圖像、繪畫和視頻剪輯。在我們的例子中,並沒有很多GANs用於預測時間序列數據。然而,主要思想應該是一樣的——我們希望預測未來的股市走勢。在未來,GS股票的模式和行為應該或多或少是相同的(除非它開始以一種完全不同的方式運作,或者經濟發生劇烈變化)。因此,我們希望為未來「生成」與我們已有的歷史交易數據分布類似(當然不是完全相同)的數據。因此,理論上,這應該行得通。

在我們的例子中,我們將使用LSTM作為時間序列生成器,CNN作為鑒別器。

3.2 Metropolis-Hastings GAN 和 Wasserstein GAN

注意:接下來的幾個部分假設您有一些使用GANs的經驗。

I. Metropolis-Hastings GAN

最近,Uber的工程團隊對傳統的甘系統進行了改進,並將其命名為Metropolis-Hastings GAN (MHGAN)。Uber的方法背後的想有點類似於谷歌和加州大學伯克利分校創建的另一種方法,稱為甄別器拒絕抽樣(Discriminator Rejection Sampling, DRS)。基本上,當我們訓練GAN時,我們使用鑒別器(D)的唯一目的是更好地訓練生成器(G)。通常,在訓練完GAN後,我們不再使用D。然而,MHGAN和DRS試圖使用D來選擇G生成的接近真實數據分布的樣本(稍微不同的是,MHGAN使用Markov Chain Monte Carlo (MCMC)進行抽樣)。

MHGAN從G中提取K個樣本(從圖中G - z0到zK的獨立雜訊輸入創建)。然後按順序貫穿K輸出(x′0 x′K)和後一個驗收規則決定是否接受當前樣本或保持最後接受。最後保留的輸出被認為是G的實際輸出。

註:MHGAN最初由Uber在Pytorch中實現。我們只是把它轉到了MXNet/Gluon上。

II. Wasserstein GAN

訓練GANs是相當困難的。模型可能永遠不會收斂,很容易崩潰。我們將使用Wasserstein GAN - WGAN進行。

正如我們所知,GANs背後的主要目標是讓生成器開始將隨機雜訊轉換成我們想要模擬的某些給定數據。因此,在GAN中,比較兩個分布之間的相似性的是非常必要的。最廣泛使用的兩種指標是:

  • KL divergence (Kullback–Leibler)?—?DKL(p‖q)=∫xp(x)logp(x)q(x)dx. DKL is zero when p(x) is equal to q(x)。
  • JS Divergence (Jensen–Shannon)。JS Divergence以0和1為界,與KL divergence不同,它是對稱的,更平滑。當損失從KL轉移到JS散度時,GAN訓練取得了顯著的成功。

WGAN使用Wasserstein distance,W(pr,pg)=1Ksup‖f‖L≤K??x~pr[f(x)]???x~pg[f(x)] (supsup代表上確界),作為損失函數。與KL和JS的差異相比,Wasserstein給出了一個平滑的度量。這使得它更適合在梯度下降過程中創建一個穩定的學習過程。

而且,與KL和JS相比,Wasserstein距離幾乎在任何地方都是可微的。正如我們所知,在反向傳播期間,我們對損失函數進行微分,以創建梯度,從而更新權重。因此,有一個可微損失函數是非常重要的。

毫無疑問,這是本文本中最難的部分。

3.4 一層RNN

3.4.1 LSTM或GRU

如前所述,生成器是LSTM網路,是一種遞歸神經網路(RNN)。RNN用於時間序列數據,因為它們跟蹤所有以前的數據點,並可以捕獲隨時間發展的模式。因倉儲物的性質、RNNs很多時間受到消失的梯度,也就是權重變化期間接受的培訓變得如此之小,他們不改變,使網路無法收斂到最小損失(有時也可以觀察到相反的問題——當梯度太大了。這叫做梯度爆炸,但是解決這個問題的方法很簡單——如果梯度開始超過某個常數,即梯度漸變)。有兩種方法可以解決這個問題——門控循環單元(GRU)和長短期記憶(LSTM)。兩者最大的區別是:

1、GRU有2個門(update and reset),LSTM有4個門(update, input, forget, and output)。

2、LSTM保持內部內存狀態,而GRU沒有。

3、LSTM在輸出門之前應用非線性(sigmoid), GRU沒有。

在大多數情況下,LSTM和GRU在準確率方面給出了類似的結果,但GRU的計算量要小得多,因為GRU的可訓練參數要少得多。然而,LSTMs使用得更多。

嚴格地說,LSTM單元背後的數學是:

⊙是乘法運算元,對所有x =(x1,x2,…,xk)?∈R ^ k兩個激活函數

3.4.2 LSTM

LSTM非常簡單——一個LSTM層有112個輸入單元(正如我們在數據集中有112個特徵一樣)和500個隱藏單元,一個密集層有1個輸出——每天的價格。初始化器是Xavier,我們將使用L1損耗(L1正則化的平均絕對誤差損耗——見3.4.5)。有關正則化的更多信息)。

注意:在代碼中,你可以看到我們使用Adam(學習率為0.01)作為優化器。有一節專門解釋我們使用什麼超參數(學習速率被排除在外,因為我們有學習速率調度器見第3.4.3)以及我們如何優化這些超參數見第3.6。

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。讓我們看看MXNet列印的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天的數據(同樣,這些數據是GS股票每天的股價+當天的所有其他特性——相關資產、情緒等),並嘗試預測第18天。然後我們用一天移動17天窗口,再次預測第18天。我們這樣迭代整個數據集。

3.4.3 Learning rate scheduler

最重要的超參數之一是學習速率。在訓練神經網路時,為幾乎每個優化器(如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.4.4 如何防止過擬合和偏方差權衡

有很多特徵和神經網路,我們需要確保我們避免過擬合,並注意總損失。

我們使用幾種技術來防止過擬合(不僅在LSTM中,而且在CNN和自動編碼器中):

1、確保數據質量。我們已經進行了統計檢查,確保數據不受多重共線性或序列自相關的影響。進一步,我們對每個特性執行了特性重要性檢查。最後,利用一些有關股票市場運作機制的領域知識進行了初始特徵選擇(例如,選擇相關資產、技術指標等)。

2、正則化。最常用的兩種正則化技術是LASSO (L1)和Ridge (L2)。L1加上平均絕對誤差L2加上平均平方誤差。沒有太多的數學細節,基本的區別是:lasso回歸(L1)既做變數選擇又做參數收縮,而Ridge回歸只做參數收縮,並最終包含模型中的所有係數。在相關變數存在的情況下,嶺回歸可能是首選。此外,嶺回歸在最小二乘估計方差較大的情況下效果最好。因此,這取決於我們的模型目標。這兩種規範化的影響是完全不同的。雖然它們都對較大的權值不利,但L1正則化會導致在零點處出現不可微函數。L2正則化傾向於權值較小,但L1正則化傾向於權值趨於0。L1正則化可以得到一個稀疏模型,一個參數更少的模型。在這兩種情況下,L1和L2正則化模型的參數都「收縮」,但是在L1正則化的情況下,收縮直接影響模型的複雜性(參數的數量)。準確地說,嶺回歸在最小二乘估計方差較大的情況下最有效。L1對異常值更加健壯,在數據稀疏的情況下使用L1,並且具有重要的特性。我們用L1。

3、Dropout. Dropout layers randomly remove nodes in the hidden layers

4、Dense-sparse-dense training

5、Early stoppin

在構建複雜神經網路時,另一個重要的考慮因素是偏方差權衡。基本上,我們得到的錯誤當訓練網是一個函數的偏差,方差和不可約錯誤-σ(由於雜訊和隨機誤差)。最簡單的權衡是:

Error=bias^2+variance+σ

Bias。偏差衡量的是一個訓練過的(在訓練數據集上)演算法在不可見數據上的泛化程度。高偏差(欠擬合)意味著模型不能很好地處理不可見數據。

Variance。方差度量模型對數據集中的更改的敏感性。高方差是過擬合。

3.5 鑒別器-一維CNN

3.5.1 為什麼CNN是鑒別器?

我們通常使用CNNs進行與圖像相關的工作(分類、上下文提取等)。它們在從特徵中提取特徵方面非常強大。例如,在狗的圖像中,第一個卷積層將檢測邊緣,第二個將開始檢測圓圈,第三個將檢測鼻子。在我們的例子中,數據點形成小趨勢,小趨勢形成大趨勢,趨勢反過來形成模式。CNNs檢測特徵的能力可用於提取有關GS股價走勢模式的信息

使用CNN的另一個原因是,CNNs可以很好地處理空間數據——這意味著彼此距離較近的數據點之間的關係比分布在各處的數據點之間的關係更密切。這對於時間序列數據應該是正確的。在我們的例子中,每個數據點(對於每個特徵)都是連續的。人們很自然地認為,兩天的時間越近,它們之間的關係就越密切。有一件事需要考慮,那就是季節性以及它可能如何改變CNN的工作。

註:在本文其他部分,使用CNN作為時間序列數據是實驗性的。我們將檢查結果,不提供數學或其他證明。使用不同的數據、激活函數等可能會導致不同的結果。

3.5.2 CNN

num_fc = 512

# ... other parts of the GAN

cnn_net = gluon.nn.Sequential()
with net.name_scope():

# Add the 1D Convolutional layers
cnn_net.add(gluon.nn.Conv1D(32, kernel_size=5, strides=2))
cnn_net.add(nn.LeakyReLU(0.01))
cnn_net.add(gluon.nn.Conv1D(64, kernel_size=5, strides=2))
cnn_net.add(nn.LeakyReLU(0.01))
cnn_net.add(nn.BatchNorm())
cnn_net.add(gluon.nn.Conv1D(128, kernel_size=5, strides=2))
cnn_net.add(nn.LeakyReLU(0.01))
cnn_net.add(nn.BatchNorm())

# Add the two Fully Connected layers
cnn_net.add(nn.Dense(220, use_bias=False), nn.BatchNorm(), nn.LeakyReLU(0.01))
cnn_net.add(nn.Dense(220, use_bias=False), nn.Activation(activation=relu))
cnn_net.add(nn.Dense(1))

# ... other parts of the GAN

展示由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.6 超參數

我們將跟蹤和優化的超參數是:

  • batch_size : batch size of the LSTM and CNN
  • cnn_lr: the learningrate of the CNN
  • strides: the number of strides in the CNN
  • lrelu_alpha: the alpha for the LeakyReLU in the GAN
  • batchnorm_momentum: momentum for the batch normalisation in the CNN
  • padding: the padding in the CNN
  • kernel_size:1: kernel size in the CNN
  • dropout: dropout in the LSTM
  • filters: the initial number of filters

超參數優化

GAN訓練200期後,它將記錄(LSTM的誤差函數,GG)並將其作為獎勵價值傳遞給強化學習,將決定是否改變hyperparameters與同一組hyperparameters保持訓練。正如後面所描述的,這種方法嚴格用於RL的試驗。

如果RL決定更新超參數,它將調用貝葉斯優化庫(下文將討論),該庫將給出下一個最佳預期超參數集。

4.1 超參數優化的強化學習

為什麼我們在超參數優化中使用強化學習?股票市場一直在變化。即使我們成功地訓練了GAN和LSTM來創建非常準確的結果,結果也可能只在一定時期內有效。也就是說,我們需要不斷優化整個過程。為了優化流程,我們可以:

1、添加或刪除功能(例如添加可能相關的新股票或貨幣)

2、完善我們的深度學習模式。改進模型最重要的方法之一是使用hyper參數(在第5節中列出),一旦找到了一組特定的超參數,我們需要決定何時更改它們以及何時使用已知的集(探索vs.利用)。此外,股票市場代表了一個依賴於數百萬參數的連續空間。

註:本文整體強化學習部分的目的更偏向於研究。我們將探索使用GAN作為環境的不同RL方法。在不使用RL的情況下,有許多方法可以成功地對深度學習模型執行超參數優化。

註:接下來的幾個部分假設您對RL有一定的了解,特別是策略方法和Q-learning。

4.1.1 強化學習理論

在不解釋RL的基礎知識的情況下,我們將跳到這裡實現具體方法的細節。我們將使用無模型的RL演算法,原因很明顯,我們不了解整個環境,因此沒有定義了環境如何工作的模型——如果有的話,我們不需要預測股價走勢——他們只會遵循模型。我們將使用無模型RL-策略優化和Q-learning這兩個部分

Q-learning

在Q-learning中,我們從給定的狀態學習採取行動的價值。q值是採取行動後的預期收益。我們將使用Rainbow,它是7個Q-learning演算法的組合。

策略優化

在策略優化中,我們學習從給定狀態採取的操作。

構建RL演算法的一個關鍵方面是準確設置獎勵。它必須捕獲環境的所有方面以及代理與環境的交互。我們將獎勵R定義為:

Reward=2?lossG+lossD+accuracyG

4.2 貝葉斯優化

我們將使用貝葉斯優化,而不是網格搜索,這將花費大量的時間來尋找超參數的最佳組合。我們將要使用的庫已經實現了。

4.2.1 準備高斯過程

# Initialize the optimizer
from bayes_opt import BayesianOptimization
from bayes_opt import UtilityFunction

utility = UtilityFunction(kind="ucb", kappa=2.5, xi=0.0)

結果

最後,我們將比較在流程的不同階段之後將不可見的(測試)數據用作輸入時LSTM的輸出。

在第一階段之後繪圖:

from utils import plot_prediction
plot_prediction(Predicted and Real price - after first epoch.)

2. after 50 epo

plot_prediction(Predicted and Real price - after first 200 epochs.)

RL運行10episodes(我們將eposide定義為200個epochs上的一個完整GAN訓練)。

下一步,我將嘗試將所有內容分開,並提供一些關於什麼是有效的以及為什麼有效的分析。為什麼我們會得到這些結果呢?

接下來,我將嘗試創建一個RL環境來測試決定何時以及如何進行交易的交易演算法。GAN的輸出將是環境中的一個參數。

還有很多很多的細節需要探索,選擇數據特徵,選擇演算法,調優演算法等等。

文章來源:medium.com/@borisborev/

代碼獲取

【2萬字乾貨】利用深度學習最新前沿預測股價走勢?

mp.weixin.qq.com圖標
推薦閱讀:

TAG:深度學習(DeepLearning) | 量化交易 | 機器學習 |