完整架構(gòu)概述在這篇文章中,我將創(chuàng)建一個(gè)預(yù)測股票價(jià)格變動(dòng)的完整過程。我們將使用生成對抗網(wǎng)絡(luò)(GAN)與LSTM(一種循環(huán)神經(jīng)網(wǎng)絡(luò))作為生成器,使用卷積神經(jīng)網(wǎng)絡(luò)CNN作為鑒別器。我們使用LSTM的原因很明顯,我們正在嘗試預(yù)測時(shí)間序列數(shù)據(jù)。為什么我們使用GAN,特別是卷積神經(jīng)網(wǎng)絡(luò)(CNN)作為鑒別器呢?這是一個(gè)很好的問題:稍后會(huì)有特別的部分。 當(dāng)然,我們將詳細(xì)介紹每個(gè)步驟,但最困難的部分是GAN:成功訓(xùn)練GAN的非常棘手的部分是獲得正確的超參數(shù)集。出于這個(gè)原因,我們將使用貝葉斯優(yōu)化(還有高斯過程)和深度強(qiáng)化學(xué)習(xí)(DRL)來決定何時(shí)以及如何改變GAN的超參數(shù)。在創(chuàng)建強(qiáng)化學(xué)習(xí)時(shí),我將使用該領(lǐng)域的最新進(jìn)展,例如Rainbow和PPO。 我們將使用許多不同類型的輸入數(shù)據(jù)。除了股票的歷史交易數(shù)據(jù)和技術(shù)指標(biāo),我們將使用NLP的最新進(jìn)展(使用“BERT,對NLP進(jìn)行遷移學(xué)習(xí))來創(chuàng)建情感分析(作為基本面分析的來源) ),用于提取整體趨勢方向的傅里葉變換,用于識別其他高級特征的棧式自動(dòng)編碼器,用于查找相關(guān)資產(chǎn)的特征投資組合,差分整合移動(dòng)平均自回歸模型(ARIMA))對于股票函數(shù)近似,以便捕獲盡可能多的關(guān)于股票的信息,模式,依賴關(guān)系等。我們都知道,數(shù)據(jù)越多越好。預(yù)測股價(jià)走勢是一項(xiàng)極其復(fù)雜的任務(wù),所以我們對股票(從不同的角度)了解得越多,我們的系統(tǒng)就會(huì)越好。 為了創(chuàng)建所有神經(jīng)網(wǎng)絡(luò),我們將使用MXNet和它的高級API - Gluon,并在多個(gè)GPU上訓(xùn)練它們。 注:盡管我試圖深入探討數(shù)學(xué)和幾乎所有算法和技術(shù)背后的機(jī)制,但本文并沒有明確地解釋機(jī)器/深度學(xué)習(xí)或股票市場是如何運(yùn)作的。其目的是展示我們?nèi)绾问褂貌煌募夹g(shù)和算法來準(zhǔn)確預(yù)測股票價(jià)格的變動(dòng),并給出每一步使用每種技術(shù)的原因和有用性背后的理論基礎(chǔ)。 1.簡介準(zhǔn)確預(yù)測股票市場是一項(xiàng)復(fù)雜的任務(wù),因?yàn)橛袛?shù)百萬種情況會(huì)影響它。因此,我們需要能夠盡可能多地捕獲這些前置條件。我們還需要做出幾個(gè)重要的假設(shè):1)市場不是100%隨機(jī),2)歷史重復(fù),3)市場遵循人們的理性行為,4)市場是“ 完美的 ”。 我們將嘗試預(yù)測高盛(NYSE: GS)的價(jià)格走勢。為此,我們將使用2010年1月1日至2018年12月31日的每日收盤價(jià)(七年數(shù)據(jù)用于訓(xùn)練,兩年數(shù)據(jù)用于驗(yàn)證)。我們將交替使用“高盛”和“GS”這兩個(gè)術(shù)語。 2.數(shù)據(jù)我們需要盡可能多地合并信息(從不同方面和角度描繪股票)。我們將使用每日數(shù)據(jù),1585天來訓(xùn)練各種算法(我們有70%的數(shù)據(jù))并預(yù)測接下來的680天(測試數(shù)據(jù)),然后我們將把預(yù)測結(jié)果與測試數(shù)據(jù)進(jìn)行比較。每種類型的數(shù)據(jù)(我們將其稱為特征)將在后面的章節(jié)中進(jìn)行更詳細(xì)的解釋,但是,作為一個(gè)高層次的概述,我們將使用的特征是:
接下來,有這么多特征,我們需要執(zhí)行幾個(gè)重要步驟:
作為數(shù)據(jù)準(zhǔn)備的最后一步,我們還將使用主成分分析(PCA)創(chuàng)建Eigen投資組合,以減少自動(dòng)編碼器創(chuàng)建的特征的維數(shù)。 from utils import *import timeimport numpy as npfrom mxnet import nd, autograd, gluonfrom mxnet.gluon import nn, rnnimport mxnet as mximport datetimeimport seaborn as snsimport matplotlib.pyplot as plt%matplotlib inlinefrom sklearn.decomposition import PCAimport mathfrom sklearn.preprocessing import MinMaxScalerfrom sklearn.metrics import mean_squared_errorfrom sklearn.preprocessing import StandardScalerimport xgboost as xgbfrom sklearn.metrics import accuracy_scoreimport warningswarnings.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)dataset_ex_df[['Date', 'GS']].head(3) print('There are {} number of days in the dataset.'.format(dataset_ex_df.shape[0])) output >>> There are 2265 number of days in the dataset. 讓我們想象一下過去九年的股票。垂直虛線表示訓(xùn)練和測試數(shù)據(jù)之間的分離。 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、相關(guān)資產(chǎn) 如前所述,我們將使用其他資產(chǎn)作為特征,而不僅僅是GS。 那么,還有哪些資產(chǎn)會(huì)影響高盛的股價(jià)走勢呢?對公司、業(yè)務(wù)線、競爭環(huán)境、依賴關(guān)系、供應(yīng)商和客戶類型等的良好理解對于選擇正確的相關(guān)資產(chǎn)集非常重要:
總的來說,我們在數(shù)據(jù)集中有72個(gè)其他資產(chǎn) - 每個(gè)資產(chǎn)的每日價(jià)格。 2.2、技術(shù)指標(biāo) 我們已經(jīng)討論了什么是技術(shù)指標(biāo)以及為什么使用它們,現(xiàn)在讓我們直接跳到Python代碼。我們將只為GS創(chuàng)建技術(shù)指標(biāo)。 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 datasetdataset_TI_df = get_technical_indicators(dataset_ex_df[['GS']])dataset_TI_df.head() 所以我們有每個(gè)交易日的技術(shù)指標(biāo)(包括MACD、Bollinger bands等)。我們總共有12項(xiàng)技術(shù)指標(biāo)。 讓我們想象一下這些指標(biāo)的最后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、基本面分析 對于基本面分析,我們將對所有關(guān)于GS的每日新聞進(jìn)行情感分析。最后使用sigmoid,結(jié)果將在0和1之間。分?jǐn)?shù)越接近0 - 新聞越負(fù)面(接近1表示正面情感)。對于每一天,我們將創(chuàng)建平均每日得分(作為0到1之間的數(shù)字)并將其添加為特征。 2.3.1、 BERT 為了將新聞分類為正面或負(fù)面(或中性),我們將使用BERT,這是一種預(yù)訓(xùn)練的語言表示。 已經(jīng)在MXNet / Gluon中提供預(yù)訓(xùn)練的BERT模型。我們只需要實(shí)例化它們并添加兩個(gè)(任意數(shù)量)Dense層,softmax - 得分從0到1。 # just import bertimport bert 2.4、傅立葉變換用于趨勢分析 傅立葉變換采用函數(shù)并創(chuàng)建一系列正弦波(具有不同的幅度和幀)。組合時(shí),這些正弦波接近原始函數(shù)。從數(shù)學(xué)上講,變換看起來像這樣: 我們將使用傅里葉變換來提取GS股票的整體和局部趨勢,并對其進(jìn)行降噪。我們來看看它是如何工作的。 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中看到的,我們使用傅里葉變換的成分越多,逼近函數(shù)越接近實(shí)際股票價(jià)格(100個(gè)成分變換幾乎與原始函數(shù)相同 - 紅色和紫色線幾乎重疊)。我們使用傅立葉變換來提取長期和短期趨勢,因此我們將使用具有3,6和9個(gè)成分的變換。您可以推斷出具有3個(gè)成分的轉(zhuǎn)換是長期趨勢。 用于去噪數(shù)據(jù)的另一種技術(shù)是調(diào)用小波。小波和傅里葉變換給出了類似的結(jié)果,因此我們只使用傅里葉變換。 from collections import dequeitems = 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作為一項(xiàng)特征值 ARIMA是一種預(yù)測時(shí)間序列數(shù)據(jù)的技術(shù)。我們將展示如何使用它,雖然ARIMA不能作為我們的最終預(yù)測,但我們將使用它作為一種技術(shù)來稍微降低噪聲,并(可能)提取一些新的模式或特征。 from statsmodels.tsa.arima_model import ARIMAfrom pandas import DataFramefrom pandas import datetimeseries = 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_plotautocorrelation_plot(series)plt.figure(figsize=(10, 7), dpi=80)plt.show() from pandas import read_csvfrom pandas import datetimefrom statsmodels.tsa.arima_model import ARIMAfrom sklearn.metrics import mean_squared_errorX = series.valuessize = int(len(X) * 0.66)train, test = X[0:size], X[size:len(X)]history = [x for x in train]predictions = list()for t in range(len(test)): model = ARIMA(history, order=(5,1,0)) model_fit = model.fit(disp=0) output = model_fit.forecast() yhat = output[0] predictions.append(yhat) obs = test[t] history.append(obs)error = mean_squared_error(test, predictions)print('Test MSE: %.3f' % error) Test MSE: 10.151 # Plot the predicted (from ARIMA) and real pricesplt.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給出了一個(gè)非常接近實(shí)際股價(jià)的結(jié)果。我們將通過ARIMA使用預(yù)測價(jià)格作為LSTM的輸入特征,因?yàn)檎缥覀兦懊嫣岬降?,我們希望盡可能多地捕獲關(guān)于高盛的特征和模式。我們測試MSE(均方誤差)為10.151,這本身并不是一個(gè)壞結(jié)果(考慮到我們有很多測試數(shù)據(jù)),但是我們?nèi)匀恢粚⑵渥鳛長STM中的一個(gè)特征。 2.6、統(tǒng)計(jì)檢查 確保數(shù)據(jù)具有良好的質(zhì)量對于機(jī)器學(xué)習(xí)模型非常重要。為了確保我們的數(shù)據(jù)擬合,我們將執(zhí)行幾個(gè)簡單的檢查,以確保我們實(shí)現(xiàn)和觀察到的結(jié)果是真實(shí)的,而不是因?yàn)榈讓訑?shù)據(jù)分布存在基本錯(cuò)誤而受到損害。 2.6.1、異方差性,多重共線性,序列相關(guān)性
我們不會(huì)在這里進(jìn)入代碼,因?yàn)樗芎唵?,我們的重點(diǎn)更多地放在深度學(xué)習(xí)部分,但數(shù)據(jù)是定性的。 2.7、特征工程 print('Total dataset has {} samples, and {} features.'.format(dataset_total_df.shape[0], \ dataset_total_df.shape[1])) Total dataset has 2265 samples, and 112 features. 因此,在添加了所有類型的數(shù)據(jù)(相關(guān)資產(chǎn)、技術(shù)指標(biāo)、基礎(chǔ)分析、傅立葉和Arima)之后,我們在這2,265天中總共有112個(gè)特征(如前所述,訓(xùn)練數(shù)據(jù)只有1,585天)。 我們還將從自動(dòng)編碼器生成更多特征。 2.7.1、XGBoost的重要性 有這么多的特征,我們必須要考慮它們是否真的代表了走勢。例如,我們在機(jī)器學(xué)習(xí)數(shù)據(jù)集中包含了以美元計(jì)價(jià)的LIBOR利率,因?yàn)槲覀冋J(rèn)為LIBOR的變化可能表明經(jīng)濟(jì)的變化,而經(jīng)濟(jì)的變化又可能表明GS的股票行為的變化。但我們需要測試。測試特征重要性的方法有很多,但是我們將使用XGBoost,因?yàn)樗诜诸惡突貧w問題中都給出了最好的結(jié)果之一。 由于特征數(shù)據(jù)集非常大,出于演示目的,我們將僅使用技術(shù)指標(biāo)。在真實(shí)特征重要性測試期間,所有選定的特征都證明有些重要,因此我們在訓(xùn)練GAN時(shí)不會(huì)排除任何內(nèi)容。 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'])) 讓我們繪制訓(xùn)練和驗(yàn)證誤差的曲線圖,以便觀察訓(xùn)練和檢查過擬合(欠擬合)。 plt.scatter(x=training_rounds,y=eval_result['validation_0']['rmse'],label='Training Error')plt.scatter(x=training_rounds,y=eval_result['validation_1']['rmse'],label='Validation Error')plt.xlabel('Iterations')plt.ylabel('RMSE')plt.title('Training Vs Validation Error')plt.legend()plt.show() 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是其中的重要特征。 我遵循相同的邏輯來對整個(gè)數(shù)據(jù)集執(zhí)行特征重要性 - 與僅少數(shù)幾個(gè)特征相比,訓(xùn)練花費(fèi)的時(shí)間更長,結(jié)果更難以閱讀。 2.8、使用Stacked Autoencoders提取高級特征 在我們進(jìn)入自動(dòng)編碼器之前,我們將探索激活函數(shù)。 2.8.1、激活功函數(shù)- GELU(高斯誤差) 最近提出了GELU - Gaussian Error Linear Unites - link。在論文中,作者展示了使用ReLU作為激活的使用GELU的神經(jīng)網(wǎng)絡(luò)優(yōu)于網(wǎng)絡(luò)的幾個(gè)實(shí)例。gelu也用于BERT,我們用于新聞情緒分析的NLP方法。 我們將使用GELU作為自動(dòng)編碼器。 注:下面的單元格展示了GELU數(shù)學(xué)背后的邏輯。它不是作為激活函數(shù)的實(shí)際實(shí)現(xiàn)。我必須在MXNet中實(shí)現(xiàn)GELU。如果您按照代碼將act_type='relu'更改為act_type='gelu',那么它將不起作用,除非您更改MXNet的實(shí)現(xiàn)。對整個(gè)項(xiàng)目發(fā)出pull請求,以訪問GELU的MXNet實(shí)現(xiàn)。 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) 讓我們來看看GELU、ReLU和LeakyReLU(最后一個(gè)主要用于GAN)。 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() 好的,回到自動(dòng)編碼器,如下所示(圖像只是原理圖,它不代表實(shí)際的層數(shù),units等) 注意:通常,在自動(dòng)編碼器中編碼器的數(shù)量==解碼器的數(shù)量。但是,我們希望提取更高級別的特征(而不是創(chuàng)建相同的輸入),因此我們可以跳過解碼器中的最后一層。我們實(shí)現(xiàn)了這一點(diǎn),在訓(xùn)練期間創(chuàng)建了具有相同層數(shù)的編碼器和解碼器。 batch_size = 64n_batches = VAE_data.shape[0]/batch_sizeVAE_data = VAE_data.valuestrain_iter = mx.io.NDArrayIter(data={'data': VAE_data[:num_training_days,:-1]}, \ label={'label': VAE_data[:num_training_days, -1]}, batch_size = batch_size)test_iter = mx.io.NDArrayIter(data={'data': VAE_data[num_training_days:,:-1]}, \ label={'label': VAE_data[num_training_days:,-1]}, batch_size = batch_size)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 lossn_hidden=400 # neurons in each layern_latent=2 n_layers=3 # num of dense layers in encoder and decoder respectivelyn_output=VAE_data.shape[1]-1 net = VAE(n_hidden=n_hidden, n_latent=n_latent, n_layers=n_layers, n_output=n_output, batch_size=batch_size, act_type='gelu')net.collect_params().initialize(mx.init.Xavier(), ctx=mx.cpu())net.hybridize()trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': .01})print(net) 所以我們在編碼器和解碼器中都有3層(每個(gè)層有400個(gè)神經(jīng)元)。 n_epoch = 150print_period = n_epoch // 10start = time.time()training_loss = []validation_loss = []for epoch in range(n_epoch): epoch_loss = 0 epoch_val_loss = 0 train_iter.reset() test_iter.reset() n_batch_train = 0 for batch in train_iter: n_batch_train +=1 data = batch.data[0].as_in_context(mx.cpu()) with autograd.record(): loss = net(data) loss.backward() trainer.step(data.shape[0]) epoch_loss += nd.mean(loss).asscalar() n_batch_val = 0 for batch in test_iter: n_batch_val +=1 data = batch.data[0].as_in_context(mx.cpu()) loss = net(data) epoch_val_loss += nd.mean(loss).asscalar() epoch_loss /= n_batch_train epoch_val_loss /= n_batch_val training_loss.append(epoch_loss) validation_loss.append(epoch_val_loss) '''if epoch % max(print_period, 1) == 0: print('Epoch {}, Training loss {:.2f}, Validation loss {:.2f}'.\ format(epoch, epoch_loss, epoch_val_loss))'''end = time.time()print('Training completed in {} seconds.'.format(int(end-start))) Training completed in 62 seconds. dataset_total_df['Date'] = dataset_ex_df['Date']vae_added_df = mx.nd.array(dataset_total_df.iloc[:, :-1].values)print('The shape of the newly created (from the autoencoder) features is {}.'.format(vae_added_df.shape)) The shape of the newly created (from the autoencoder) features is (2265, 112). 我們從自動(dòng)編碼器中創(chuàng)建了112個(gè)更多特征。由于我們只想擁有高級特征(整體模式),我們將使用主成分分析(PCA)在新創(chuàng)建的112個(gè)特征上創(chuàng)建特征投資組合。這將減少數(shù)據(jù)的維度(列數(shù))。Eigen組合的描述能力將與原始的112個(gè)特征相同。 注意再一次,這純粹是實(shí)驗(yàn)性的。我并非100%確定描述的邏輯將成立。作為人工智能和深度學(xué)習(xí)的其他一切,這是藝術(shù)和需要實(shí)驗(yàn)。 2.8.2、主成分分析的特征組合 # We want the PCA to create the new components to explain 80% of the variancepca = PCA(n_components=.8)x_pca = StandardScaler().fit_transform(vae_added_df)principalComponents = pca.fit_transform(x_pca)principalComponents.n_components_ 84 所以,為了解釋80%的方差我們需要84個(gè)(112個(gè))特征。這仍然是一個(gè)很大的問題。因此,目前我們不包括自動(dòng)編碼器創(chuàng)建的特征。我將致力于創(chuàng)建autoencoder架構(gòu),在該架構(gòu)中,我們從中間層(而不是最后一層)獲得輸出,并使用30個(gè)神經(jīng)元將其連接到另一個(gè)Dense 層。因此,我們將1)只提取更高級別的特征,2)提供更少的列數(shù)。 3.生成性對抗網(wǎng)絡(luò)(GAN)GAN的體系結(jié)構(gòu) GAN如何運(yùn)作? 如前所述,本文的目的不是詳細(xì)解釋深度學(xué)習(xí)背后的數(shù)學(xué),而是展示其應(yīng)用。當(dāng)然,在我看來,徹底和非常堅(jiān)實(shí)的理解從基礎(chǔ)到最小的細(xì)節(jié),是極其必要的。因此,我們將嘗試平衡并給出一個(gè)關(guān)于GAN如何工作的高級概述,以便讀者充分理解使用GAN預(yù)測股價(jià)走勢背后的原理。 GAN網(wǎng)絡(luò)由兩個(gè)模型組成 - Generator(G)和Discriminator (D)。訓(xùn)練GAN的步驟如下:
當(dāng)組合在一起時(shí),D和G作為一種playing minmax游戲(Generator試圖欺騙Discriminator ,使其增加假例子的概率,即最小化z?pz(z)[log(1-D(G) z)))]。Discriminator 想要通過最大化x~pr(x)[logD(x)]來分離來自發(fā)生器D(G(z))的數(shù)據(jù)。但是,具有分離的損失函數(shù),它是不清楚兩者如何匯合在一起(這就是為什么我們使用普通GAN的一些進(jìn)步,例如Wasserstein GAN)??偟膩碚f,組合損失函數(shù)看起來像: 3.1、為什么GAN用于股市預(yù)測 生成對抗網(wǎng)絡(luò)(GAN)最近主要用于創(chuàng)建逼真的圖像,繪畫和視頻剪輯。在我們的案例中,沒有多少GAN用于預(yù)測時(shí)間序列數(shù)據(jù)。然而,主要想法應(yīng)該是相同的 - 我們希望預(yù)測未來的股票變動(dòng)。在未來,GS股票的模式和行為應(yīng)該或多或少相同(除非它開始以完全不同的方式運(yùn)作,或者經(jīng)濟(jì)急劇變化)。因此,我們希望“生成”未來的數(shù)據(jù),這些數(shù)據(jù)將具有與我們已有的相似(當(dāng)然不完全相同)的分布 - 歷史交易數(shù)據(jù)。所以,從理論上講,它應(yīng)該有效。 在我們的例子中,我們將使用LSTM作為時(shí)間序列生成器,并使用CNN作為Discriminator。 3.2、Metropolis-Hastings GAN和Wasserstein GAN I. Metropolis-Hastings GAN Uber的工程團(tuán)隊(duì)最近對傳統(tǒng)GAN進(jìn)行了改進(jìn),稱為Metropolis-Hastings GAN(MHGAN)。優(yōu)步的方法背后的想法(正如他們所述)與谷歌和加州大學(xué)伯克利分校創(chuàng)建的另一種方法有點(diǎn)類似,稱為DRS?;旧希?dāng)我們訓(xùn)練GAN時(shí),我們使用Discriminator(D)的唯一目的是更好地訓(xùn)練Generator(G)。通常,在訓(xùn)練GAN之后我們不再使用D. 然而,MHGAN和DRS嘗試使用D來選擇由G生成的接近實(shí)際數(shù)據(jù)分布的樣本(MHGAN之間的微小差異是使用馬爾可夫鏈蒙特卡羅(MCMC)進(jìn)行采樣)。 MHGAN采用從G生成的K個(gè)樣本(從獨(dú)立的噪聲輸入到下圖中的G-z0到zK)。然后它依次運(yùn)行K個(gè)輸出(x'0到x'K)并遵循接受規(guī)則(從Discriminator創(chuàng)建)決定是接受當(dāng)前樣本還是保留最后接受的樣本。最后保留的輸出是被認(rèn)為是G的實(shí)際輸出的輸出。 注意:MHGAN最初由優(yōu)步在pytorch中實(shí)現(xiàn)。我只把它轉(zhuǎn)移到MXNet / Gluon。 圖10:MHGAN的視覺表示 II。Wasserstein GAN 訓(xùn)練GAN非常困難。模型可能永遠(yuǎn)不會(huì)收斂,模式崩潰很容易發(fā)生。我們將使用名為Wasserstein GAN - WGAN的GAN修改。 最值得注意的要點(diǎn)是:
3.4、Generator - One layer RNN 3.4.1、LSTM或GRU 如前所述,Generator是LSTM網(wǎng)絡(luò)的一種循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)。RNN用于時(shí)間序列數(shù)據(jù),因?yàn)樗鼈兏櫵邢惹暗臄?shù)據(jù)點(diǎn),并且可以捕獲隨時(shí)間發(fā)展的模式。由于它們的性質(zhì),RNN很多時(shí)候都會(huì)受到梯度消失的影響 - 也就是說,在訓(xùn)練期間權(quán)重變化的變化變得如此之小,以至于它們不會(huì)改變,使得網(wǎng)絡(luò)無法收斂到最小的損失(相反的問題也可以有時(shí)會(huì)觀察到 - 當(dāng)梯度變得太大時(shí)。這稱為梯度爆炸,但解決方法很簡單。兩個(gè)方法解決了這個(gè)問題 - 門控循環(huán)單元(GRU)和長短期記憶(LSTM)。兩者之間最大的區(qū)別是:1)GRU有2個(gè)門(update 和reset),LSTM有4個(gè)(update, input, forget, 和output),2)LSTM保持內(nèi)部存儲(chǔ)器狀態(tài),而GRU沒有, 3)LSTM在輸出門之前應(yīng)用非線性(sigmoid),GRU不應(yīng)用。 在大多數(shù)情況下,LSTM和GRU在準(zhǔn)確性方面給出了類似的結(jié)果,但GRU的計(jì)算密集程度要低得多,因?yàn)镚RU的可訓(xùn)練參數(shù)更少。然而,LSTM使用得更多。 嚴(yán)格地說,LSTM cell (gates)背后的數(shù)學(xué)是: LSTM cell背后的數(shù)學(xué) 其中⊙是一個(gè)逐元素的乘法運(yùn)算符,并且,對于所有x = [x1,x2,...,xk]?∈R^ k,激活函數(shù): 3.4.2LSTM架構(gòu) LSTM架構(gòu)非常簡單 - LSTM一層有112個(gè)輸入單元(因?yàn)槲覀冊跀?shù)據(jù)集中有112個(gè)特征)和500個(gè)隱藏單元,Dense層有1個(gè)輸出 - 每天的價(jià)格。初始化器是Xavier,我們將使用L1損失(這是L1正則化的平均絕對誤差損失)。 注意 - 在Python代碼中,您可以看到我們使用Adam(使用learning rate.01)作為優(yōu)化器。 gan_num_features = dataset_total_df.shape[1]sequence_length = 17class 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個(gè)神經(jīng)元并使用Xavier初始化。為了正則化,我們將使用L1。讓我們來看看LSTMMXNet打印的內(nèi)容。 print(lstm_model) 正如我們所看到的,LSTM的輸入是112個(gè)特征(dataset_total_df.shape[1])然后進(jìn)入LSTM層中的500個(gè)神經(jīng)元,然后轉(zhuǎn)換為單個(gè)輸出 - 股票價(jià)格值。 LSTM背后的邏輯是:我們?nèi)?7天(sequence_length)的數(shù)據(jù)(同樣,這些數(shù)據(jù)是GS股票每天的股價(jià)+當(dāng)天的所有其他特性——相關(guān)資產(chǎn)、情緒等),并嘗試預(yù)測第18天。 3.4.3、學(xué)習(xí)率調(diào)度程序 最重要的超參數(shù)之一是學(xué)習(xí)率。在訓(xùn)練神經(jīng)網(wǎng)絡(luò)時(shí),為幾乎所有優(yōu)化器(例如SGD,Adam或RMSProp)設(shè)置學(xué)習(xí)速率至關(guān)重要,因?yàn)樗饶芸刂剖諗克俣龋帜芸刂凭W(wǎng)絡(luò)的最終性能。最簡單的學(xué)習(xí)率策略之一是在整個(gè)訓(xùn)練過程中具有固定的學(xué)習(xí)率。選擇較小的學(xué)習(xí)速率可以使優(yōu)化器找到好的解決方案,但這是以限制初始收斂速度為代價(jià)的。隨著時(shí)間的推移改變學(xué)習(xí)率可以克服這種權(quán)衡。 讓我們繪制我們將在每個(gè)epoch使用的學(xué)習(xí)率。 class TriangularSchedule(): def __init__(self, min_lr, max_lr, cycle_length, inc_fraction=0.5): self.min_lr = min_lr self.max_lr = max_lr self.cycle_length = cycle_length self.inc_fraction = inc_fraction def __call__(self, iteration): if iteration <= self.cycle_length*self.inc_fraction: unit_cycle = iteration * 1 / (self.cycle_length * self.inc_fraction) elif iteration <= self.cycle_length: unit_cycle = (self.cycle_length - iteration) * 1 / (self.cycle_length * (1 - self.inc_fraction)) else: unit_cycle = 0 adjusted_cycle = (unit_cycle * (self.max_lr - self.min_lr)) + self.min_lr return adjusted_cycleclass CyclicalSchedule(): def __init__(self, schedule_class, cycle_length, cycle_length_decay=1, cycle_magnitude_decay=1, **kwargs): self.schedule_class = schedule_class self.length = cycle_length self.length_decay = cycle_length_decay self.magnitude_decay = cycle_magnitude_decay self.kwargs = kwargs def __call__(self, iteration): cycle_idx = 0 cycle_length = self.length idx = self.length while idx <= iteration: cycle_length = math.ceil(cycle_length * self.length_decay) cycle_idx += 1 idx += cycle_length cycle_offset = iteration - idx + cycle_length schedule = self.schedule_class(cycle_length=cycle_length, **self.kwargs) return schedule(cycle_offset) * self.magnitude_decay**cycle_idxschedule = CyclicalSchedule(TriangularSchedule, min_lr=0.5, max_lr=2, cycle_length=500)iterations=1500plt.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、如何防止過度擬合和偏差 - 方差權(quán)衡 有很多特征和神經(jīng)網(wǎng)絡(luò),我們需要確保我們避免過擬合,并注意總損失。 我們使用幾種技術(shù)來防止過度擬合(不僅在LSTM中,并且在CNN和自動(dòng)編碼器中):
構(gòu)建復(fù)雜神經(jīng)網(wǎng)絡(luò)時(shí)的另一個(gè)重要考慮因素是偏差 - 方差權(quán)衡。基本上,我們在訓(xùn)練網(wǎng)絡(luò)時(shí)得到的誤差是偏差,方差和不可減少誤差的函數(shù) - σ(由噪聲和隨機(jī)性引起的誤差)。權(quán)衡的最簡單公式是:Error=bias^2+variance+σ.
3.5、Discriminator 為什么CNN作為Discriminator?? 我們通常將CNN用于與圖像相關(guān)的工作(分類,上下文提取等)。例如,在狗的圖像中,第一個(gè)卷積層將檢測邊緣,第二個(gè)將開始檢測圓圈,第三個(gè)將檢測到鼻子。在我們的例子中,數(shù)據(jù)點(diǎn)形成小趨勢,小趨勢形成更大,趨勢形成模式。CNN檢測特征的能力可用于提取有關(guān)GS股票價(jià)格變動(dòng)模式的信息。 使用CNN的另一個(gè)原因是CNN在空間數(shù)據(jù)上運(yùn)行良好 - 這意味著彼此更接近的數(shù)據(jù)點(diǎn)彼此之間的相關(guān)性更高,而不是數(shù)據(jù)點(diǎn)。對于時(shí)間序列數(shù)據(jù),這應(yīng)該適用。在我們的例子中,每個(gè)數(shù)據(jù)點(diǎn)(對于每個(gè)特征)是連續(xù)的每一天。很自然地假設(shè)彼此距離越近,彼此之間的相關(guān)性就越大。需要考慮的一件事(雖然沒有涉及這項(xiàng)工作)是季節(jié)性以及它如何改變(如果有的話)CNN的工作。 3.5.1。CNN架構(gòu) 建議的CNN模型的架構(gòu) GAN中CNN的Python代碼是這樣的: num_fc = 512# ... other parts of the GANcnn_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 讓我們打印CNN。 print(cnn_net) 3.6、超參數(shù) 我們將跟蹤和優(yōu)化的超參數(shù)是:
我們將訓(xùn)練超過200個(gè)epochs。 4.超參數(shù)優(yōu)化在200個(gè)epochs的GAN訓(xùn)練之后,它將記錄MAE(這是LSTM中的誤差函數(shù),GG)并將其作為獎(jiǎng)勵(lì)值傳遞給強(qiáng)化學(xué)習(xí),該學(xué)習(xí)決定是否改變繼續(xù)訓(xùn)練的超參數(shù)。 如果RL決定它將更新超參數(shù),它將調(diào)用貝葉斯優(yōu)化(下面討論)庫,它將提供下一個(gè)最佳預(yù)期的超級參數(shù)集 4.1、超參數(shù)優(yōu)化的強(qiáng)化學(xué)習(xí) 為什么我們在超參數(shù)優(yōu)化中使用強(qiáng)化學(xué)習(xí)?股市一直在變化。即使我們設(shè)法訓(xùn)練我們的GAN和LSTM以創(chuàng)建非常準(zhǔn)確的結(jié)果,結(jié)果可能僅在一段時(shí)間內(nèi)有效。意思是,我們需要不斷優(yōu)化整個(gè)過程。為了優(yōu)化流程,我們可以:
4.1.1、強(qiáng)化學(xué)習(xí)理論 在不解釋RL的基礎(chǔ)知識的情況下,我們將詳細(xì)介紹我們在此實(shí)現(xiàn)的具體方法。我們將使用model-free RL算法,原因很明顯我們不了解整個(gè)環(huán)境,因此沒有明確的環(huán)境工作模型 - 如果我們不需要預(yù)測股票價(jià)格變動(dòng) - 它們只會(huì)按照該模型。我們將使用model-free RL的兩個(gè)細(xì)分 - 策略優(yōu)化和Q學(xué)習(xí)。
構(gòu)建RL算法的一個(gè)關(guān)鍵方面是準(zhǔn)確設(shè)置獎(jiǎng)勵(lì)。它必須捕獲環(huán)境的所有方面以及代理與環(huán)境的交互。我們將獎(jiǎng)勵(lì)R定義為:
其中l(wèi)ossG,accuracyG和lossD分別是Generator的損失和準(zhǔn)確性,以及Discriminator的損失。環(huán)境是GAN和LSTM訓(xùn)練的結(jié)果。不同代理可以采取的動(dòng)作是如何更改GAN的D和G網(wǎng)絡(luò)的超參數(shù)。 4.1.1.1、Rainbow Rainbow(link)是一種基于Q學(xué)習(xí)的非策略深度強(qiáng)化學(xué)習(xí)算法,它將七種算法結(jié)合在一起:
4.1.1.2、PPO 近端策略優(yōu)化(PPO)是一種無策略優(yōu)化模型的強(qiáng)化學(xué)習(xí)。實(shí)現(xiàn)其他算法要簡單得多,效果非常好。 我們?yōu)槭裁匆褂肞PO?PPO的一個(gè)優(yōu)點(diǎn)是它直接學(xué)習(xí)策略,而不是間接地通過值(Q學(xué)習(xí)使用Q值來學(xué)習(xí)策略的方式)。它可以在連續(xù)動(dòng)作空間中很好地工作,這在我們的使用案例中是合適的,并且可以(通過平均值和標(biāo)準(zhǔn)偏差)學(xué)習(xí)分布概率(如果將softmax作為輸出添加)。 政策梯度方法的問題在于它們對步長選擇極其敏感 - 如果它很小,則進(jìn)度需要太長時(shí)間(很可能主要是由于需要二階導(dǎo)數(shù)矩陣); 如果它很大,會(huì)有很多噪音會(huì)顯著降低性能。由于政策的變化(以及獎(jiǎng)勵(lì)和觀察變化的分布),輸入數(shù)據(jù)是非平穩(wěn)的。與監(jiān)督學(xué)習(xí)相比,選擇不當(dāng)?shù)牟襟E可能會(huì)更具破壞性,因?yàn)樗鼤?huì)影響下次訪問的整個(gè)分布。PPO可以解決這些問題。更重要的是,與其他一些方法相比,PPO:
注意:出于練習(xí)的目的,我們不會(huì)過多地研究和優(yōu)化RL方法,PPO和其他方法。相反,我們將采用可用的方法,并嘗試適應(yīng)我們的GAN,LSTM和CNN模型的超參數(shù)優(yōu)化過程。我們將重用和自定義的Python代碼由OpenAI創(chuàng)建,可在此處獲得(https://github.com/openai/baselines)。 4.1.2、進(jìn)一步加強(qiáng)學(xué)習(xí)的工作 進(jìn)一步探索強(qiáng)化學(xué)習(xí)的一些想法:
4.2、貝葉斯優(yōu)化 我們將使用貝葉斯優(yōu)化來代替網(wǎng)格搜索,這可能需要花費(fèi)大量時(shí)間來找到超參數(shù)的最佳組合。我們將使用的庫已經(jīng)實(shí)現(xiàn) - 鏈接(https://github.com/fmfn/BayesianOptimization)。 4.2.1、高斯過程 # Initialize the optimizerfrom bayes_opt import BayesianOptimizationfrom bayes_opt import UtilityFunctionutility = UtilityFunction(kind='ucb', kappa=2.5, xi=0.0) 4.2.2、高斯過程結(jié)果 5.結(jié)果最后,當(dāng)在過程的不同階段之后將看不見的(測試)數(shù)據(jù)用作輸入時(shí),我們將比較LSTM的輸出。
from utils import plot_predictionplot_prediction('Predicted and Real price - after first epoch.') 2.繪制50個(gè)epochs后的圖像。 plot_prediction('Predicted and Real price - after first 50 epochs.') plot_prediction('Predicted and Real price - after first 200 epochs.') RL運(yùn)行en episodes(我們將eposide定義為200個(gè)epochs上的一個(gè)完整GAN訓(xùn)練)。 plot_prediction('Final result.') |
|