本期作者:Rafael

本期翻譯:Chloe | 公眾號翻譯部成員

正文

我們建立了一個深度神經網路模型來預測比特幣價格——測試結果出奇地準確!

看看這個結果吧:

看起來相當精確,不是嗎?

在你問我之前我先回答你:是的,上面的回測只用以前的數據去訓練模型(稍後會給出細節)。

所以這是一個可以讓我變富有的印鈔機!

好了,讓我們就此打住吧!不要這樣做。

我再說一遍:不要這樣做!不要用它來交易!

請不要被欺騙了。

上面的結果是極其具有迷惑性的,讓我來解釋一下。

太美好以至於不夠真實

在過去幾周甚至幾個月,我們碰到了好多類似文章,用類似的方法給讀者呈現了關於加密貨幣價格預測的類似上圖的結果。

當你看到這些看起來非常精確的結果時,就應該給自己敲響警鐘。

這些結果明顯太美好而感覺不夠真實。

「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、colah.github.io/posts/2

2、blog.echen.me/2017/05/3

目前我們用Python和Keras來實現LSTM演算法。

1. 獲得數據

首先,我通過API從cryptocompare獲得比特幣的歷史價格數據(對於其他任何加密貨幣或者股票你都可以這麼做)。

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那一列的數據,也就是我想預測的每天的收盤價格。

arget_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而其他值代表著相應於第一個元素的變化率。因此,我在預測價格的變化率,而不是絕對的數值大小。

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。

說明:網路構架和所有因子的選擇都是任意的,我並沒有對他們做優化,因為這不是本文的著重點。

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. 結果

用訓練好的模型去預測剩餘的測試集,我們得到了本文一開始的圖。

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天,然後仔細觀察一下。

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)

看到了嗎?估計你已經準確地猜到了,這個模型的基本錯誤是當做某一天的預測時,基本只用到了前一天的值。

那條紅色的預測曲線,看起來基本只是那條綠色的實際價格曲線的平移而已。

事實上,如果我們將預測曲線調整一下,往前平移一天,那我們所觀察到的現象會更顯而易見。

line_plot(targets[-n:][:-1], preds[-n:].shift(-1))

正如你所看到的,我們幾乎可以觀察到實際數據和預測數據的一個近乎完美的重合。也就是說,我們的模型本質上只學習了前一天的價格。

這樣的結果正是我在許多用LSTM做單點預測的事例中看到的。

為了揭示得更清晰,讓我們來計算預測價格回報的期望,然後跟實際回報的期望做對比。

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)

實際回報和預測回報,右圖中預測回報往前平移了一天

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

不論是原始的形式還是平移一天的形式,如果我們看實際回報和預測的回報,我們可以得到相同的觀察結論。

事實上,如果我們計算實際回報和預測回報之前的相關性,不論是原始的預測還是平移一天的預測,我們都可以得到如下觀察結果:

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來預測未來結果。我已經證明瞭為什麼這樣的模型在實際交易中可能不可靠。

沒錯,深度神經網路可以很有效地學習。但它最終訓練得到的策略竟是預測一個跟前一天數據非常接近的數值,以成功地實現最小化均值絕對誤差。

然而,不管這個預測從均值絕對誤差的角度看有多精確,實際上,正如我們文中例子展現的那樣,僅僅基於歷史數據的單點預測模型的結果依然很難有所作為,尤其在實際交易中。

毋庸置疑,更複雜且實施有效的用於價格預測的LSTM模型可能是存在的。首先可以使用更多的數據,優化網路架構和參數。不過在我看來,引入區別於歷史數據的其他數據和特徵會更有用,畢竟金融股市的久存名言說:

「過去不能代表未來」

文章來源:hackernoon.com/dont-be-

推薦閱讀:

相關文章