前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【干货】教你如何鉴别那些用深度学习预测股价的花哨模型?

【干货】教你如何鉴别那些用深度学习预测股价的花哨模型?

作者头像
量化投资与机器学习微信公众号
发布2019-02-26 15:58:44
7680
发布2019-02-26 15:58:44
举报

正文

我们建立了一个深度神经网络模型来预测比特币价格——测试结果出奇地准确!

看看这个结果吧:

看起来相当精确,不是吗?

在你问我之前我先回答你:是的,上面的回测只用以前的数据去训练模型(稍后会给出细节)。

所以这是一个可以让我变富有的印钞机!

好了,让我们就此打住吧!不要这样做。

我再说一遍:不要这样做!不要用它来交易!

请不要被欺骗了。

上面的结果是极其具有迷惑性的,让我来解释一下。

太美好以至于不够真实

在过去几周甚至几个月,我们碰到了好多类似文章,用类似的方法给读者呈现了关于加密货币价格预测的类似上图的结果。

当你看到这些看起来非常精确的结果时,就应该给自己敲响警钟。

这些结果明显太美好而感觉不够真实。

“When something looks too good to be true, it usually is.” — Emmy Rossum

接下来,我们来一步步证明为什么是这样的。

请不要理解错了——我的意图并不是低估那些文章的价值。他们很好,应当获得掌声。事实上,从技术层面讲许多这样的方法都是非常精确的。

本文的目的在于解释为什么那些模型在实际应用中靠不住,为什么他们的预测结果不一定适合于实际交易。

那为什么是这样的呢?让我们一起看个究竟。

用LSTMs来预测比特币价格

为了更好地解释,我先给你一个实例。该实例通过建立多维Long Short Term Memory (LSTM) 神经网络模型来预测比特币价格,并产生了如上图你所看到的一样精确的预测结果。

LSTMs 是一类特殊的Recurrent Neural Networks (RNN))模型,特别适合时间序列问题。因此,LSTM在预测加密货币或者股票价格的模型中非常流行。

关于深度介绍LSTMs的文章,推荐这两篇:

1、http://colah.github.io/posts/2015-08-Understanding-LSTMs/

2、http://blog.echen.me/2017/05/30/exploring-lstms/

目前我们用Python和Keras来实现LSTM算法。

1. 获得数据

首先,我通过API从cryptocompare获得比特币的历史价格数据(对于其他任何加密货币或者股票你都可以这么做)。

代码语言:javascript
复制
import json
import requests

from keras.models import Sequential
from keras.layers import Activation, Dense, Dropout, LSTM
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn.metrics import mean_absolute_error

sns.set_palette('Set2')
%matplotlib inline

endpoint = 'https://min-api.cryptocompare.com/data/histoday'
res = requests.get(endpoint + '?fsym=BTC&tsym=USD&limit=2000')
hist = pd.DataFrame(json.loads(res.content)['Data'])
hist = hist.set_index('time')
hist.index = pd.to_datetime(hist.index, unit='s')

hist.head()

我们获得了从2012年10月10日到2018年04月04日共约2000天的BTC每日数据。

2. 训练集和测试集分组

然后,我将所有数据分成训练集和测试集两部分。用最近10%的数据作为测试集,也就是说将数据从2017年09月14日那天分开。在那天之前的所有数据作为训练集,那天以及那天之后的所有数据用于测试这个模型。接下来画出了我们的DataFrame数据结构中close那一列的数据,也就是我想预测的每天的收盘价格。

代码语言:javascript
复制
target_col = 'close'

def train_test_split(df, test_size=0.1):
   split_row = len(df) - int(test_size * len(df))
   train_data = df.iloc[:split_row]
   test_data = df.iloc[split_row:]
   return train_data, test_data

def line_plot(line1, line2, label1=None, label2=None, title='', lw=2):
   fig, ax = plt.subplots(1, figsize=(16, 9))
   ax.plot(line1, label=label1, linewidth=lw)
   ax.plot(line2, label=label2, linewidth=lw)
   ax.set_ylabel('price [USD]', fontsize=14)
   ax.set_title(title, fontsize=18)
   ax.legend(loc='best', fontsize=18);

line_plot(train[target_col], test[target_col], 'training', 'test', title='BTC')

比特币历史数据的训练集和测试集分离

3. 建立模型

为了训练LSTM模型,将所有训练数据连续划分成互不相交的窗口,每个窗口有7天(这个数字可以任意选,我只是简单地选了7天)。在每个窗口内,我将数据正规化成零基准,也就是说,每个窗口第一个元素是0而其他值代表着相应于第一个元素的变化率。因此,我在预测价格的变化率,而不是绝对的数值大小。

代码语言:javascript
复制
def normalise_zero_base(df):
   """ Normalise dataframe column-wise to reflect changes with respect to first entry. """
   return df / df.iloc[0] - 1

def normalise_min_max(df):
   """ Normalise dataframe column-wise min/max. """
   return (df - df.min()) / (data.max() - df.min())

def extract_window_data(df, window_len=10, zero_base=True):
   """ Convert dataframe to overlapping sequences/windows of len `window_data`.
   
       :param window_len: Size of window
       :param zero_base: If True, the data in each window is normalised to reflect changes
           with respect to the first entry in the window (which is then always 0)
   """
   window_data = []
   for idx in range(len(df) - window_len):
       tmp = df[idx: (idx + window_len)].copy()
       if zero_base:
           tmp = normalise_zero_base(tmp)
       window_data.append(tmp.values)
   return np.array(window_data)

def prepare_data(df, target_col, window_len=10, zero_base=True, test_size=0.2):
   """ Prepare data for LSTM. """
   # train test split
   train_data, test_data = train_test_split(df, test_size=test_size)
   
   # extract window data
   X_train = extract_window_data(train_data, window_len, zero_base)
   X_test = extract_window_data(test_data, window_len, zero_base)
   
   # extract targets
   y_train = train_data[target_col][window_len:].values
   y_test = test_data[target_col][window_len:].values
   if zero_base:
       y_train = y_train / train_data[target_col][:-window_len].values - 1
       y_test = y_test / test_data[target_col][:-window_len].values - 1

   return train_data, test_data, X_train, X_test, y_train, y_test

只用一个简单的神经网络模型。这个神经网络带一个LSTM层(包含20个神经元和一个失效因子dropout factor 0.25)和一个包含单个线性激发函数的稠密层。另外,我用均值绝对误差(Mean Absolute Error)作为损失函数和Adam优化器。

我训练这个模型50次(epochs),每次批量大小(batch size)为4。

说明:网络构架和所有因子的选择都是任意的,我并没有对他们做优化,因为这不是本文的着重点。

代码语言:javascript
复制
def build_lstm_model(input_data, output_size, neurons=20, activ_func='linear',
                     dropout=0.25, loss='mae', optimizer='adam'):
    model = Sequential()

    model.add(LSTM(neurons, input_shape=(input_data.shape[1], input_data.shape[2])))
    model.add(Dropout(dropout))
    model.add(Dense(units=output_size))
    model.add(Activation(activ_func))

    model.compile(loss=loss, optimizer=optimizer)
    return model

np.random.seed(42)

# data params
window_len = 7
test_size = 0.1
zero_base = True

# model params
lstm_neurons = 20
epochs = 50
batch_size = 4
loss = 'mae'
dropout = 0.25
optimizer = 'adam'

train, test, X_train, X_test, y_train, y_test = prepare_data(
    hist, target_col, window_len=window_len, zero_base=zero_base, test_size=test_size)

model = build_lstm_model(
    X_train, output_size=1, neurons=lstm_neurons, dropout=dropout, loss=loss,
    optimizer=optimizer)
history = model.fit(
    X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=1, shuffle=True)
Epoch 1/50

4. 结果

用训练好的模型去预测剩余的测试集,我们得到了本文一开始的图。

代码语言:javascript
复制
targets = test[target_col][window_len:]
preds = model.predict(X_test).squeeze()

mean_absolute_error(preds, y_test)
0.044705889968596577

preds = test[target_col].values[:-window_len] * (preds + 1)
preds = pd.Series(index=targets.index, data=preds)

line_plot(targets, preds, 'actual', 'prediction', lw=3)

那这个结果到底是哪里出问题了呢?

为什么我们不能将这个模型用于实际交易?

我们将这个图放大到最近的30天,然后仔细观察一下。

代码语言:javascript
复制
targets = test[target_col][window:]
preds = model.predict(X_test).squeeze()
# convert change predictions back to actual price
preds = test.close.values[:-window] * (preds + 1)
preds = pd.Series(index=targets.index, data=preds)
n = 30
line_plot(targets[-n:], preds[-n:], 'actual', 'prediction')

看到了吗?估计你已经准确地猜到了,这个模型的基本错误是当做某一天的预测时,基本只用到了前一天的值。

那条红色的预测曲线,看起来基本只是那条绿色的实际价格曲线的平移而已。

事实上,如果我们将预测曲线调整一下,往前平移一天,那我们所观察到的现象会更显而易见。

代码语言:javascript
复制
line_plot(targets[-n:][:-1], preds[-n:].shift(-1))

正如你所看到的,我们几乎可以观察到实际数据和预测数据的一个近乎完美的重合。也就是说,我们的模型本质上只学习了前一天的价格。

这样的结果正是我在许多用LSTM做单点预测的事例中看到的。

为了揭示得更清晰,让我们来计算预测价格回报的期望,然后跟实际回报的期望做对比。

代码语言:javascript
复制
actual_returns = targets.pct_change()[1:]
predicted_returns = preds.pct_change()[1:]

def dual_line_plot(line1, line2, line3, line4, label1=None, label2=None, title='', lw=2):
    import matplotlib.dates as mdates
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(21, 9))
    ax1.plot(line1, label=label1, linewidth=lw)
    ax1.plot(line2, label=label2, linewidth=lw)
    ax2.plot(line3, label=label1, linewidth=lw)
    ax2.plot(line4, label=label2, linewidth=lw)
    ax2.set_xticks(ax1.get_xticks())
    ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
    ax1.set_ylabel('daily returns', fontsize=14)
    ax2.legend(loc='best', fontsize=18);

dual_line_plot(actual_returns[-n_points:],
          predicted_returns[-n_points:],
          actual_returns[-n_points:][:-1],
          predicted_returns[-n_points:].shift(-1),
          'actual returns', 'predicted returns', lw=3)

实际回报和预测回报,右图中预测回报往前平移了一天

代码语言:javascript
复制
line_plot(actual_returns[-n_points:][:-1], predicted_returns[-n_points:].shift(-1),
           'actual returns', 'predicted returns', lw=3)

不论是原始的形式还是平移一天的形式,如果我们看实际回报和预测的回报,我们可以得到相同的观察结论。

事实上,如果我们计算实际回报和预测回报之前的相关性,不论是原始的预测还是平移一天的预测,我们都可以得到如下观察结果:

代码语言:javascript
复制
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 9))

# actual correlation
corr = np.corrcoef(actual_returns, predicted_returns)[0][1]
ax1.scatter(actual_returns, predicted_returns, color='k', marker='o', alpha=0.5, s=100)
ax1.set_title('r = {:.2f}'.format(corr), fontsize=18)

# shifted correlation
shifted_actual = actual_returns[:-1]
shifted_predicted = predicted_returns.shift(-1).dropna()
corr = np.corrcoef(shifted_actual, shifted_predicted)[0][1]
ax2.scatter(shifted_actual, shifted_predicted, color='k', marker='o', alpha=0.5, s=100)
ax2.set_title('r = {:.2f}'.format(corr), fontsize=18);

从上图我们可以看到,比特币价格的实际回报和原始预测回报之间没有相关性,而和平移一天后的预测回报之间有非常高的相关性。

总结

本文目的在于总结在过去几个月遇到的用深度神经网络来预测加密货币或者股票价格的例子。这些例子与本文用了完全类似的方法:都是用历史数据通过LSTM来预测未来结果。我已经证明了为什么这样的模型在实际交易中可能不可靠。

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

本文分享自 量化投资与机器学习 微信公众号,前往查看

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

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

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