矩池雲 | 利用LSTM框架實時預測比特幣價格

image.png

溫馨提示:本案例只作為學習研究用途,不構成投資建議。

比特幣的價格數據是基於時間序列的,因此比特幣的價格預測大多採用 LSTM 模型來實現。

長期短期記憶(LSTM)是一種特別適用於時間序列數據(或具有時間 / 空間 / 結構順序的數據,例如電影、句子等)的深度學習模型,是預測加密貨幣的價格走向的理想模型。

本文主要寫了通過 LSTM 進行數據擬合,從而預測比特幣的未來價格。

import 需要使用的庫

import pandas as pd
import numpy as np

from sklearn.preprocessing import MinMaxScaler, LabelEncoder
from keras.models import Sequential
from keras.layers import LSTM, Dense, Dropout

from matplotlib import pyplot as plt
%matplotlib inline

數據分析

數據載入

讀取 BTC 的日交易數據

data = pd.read_csv(filepath_or_buffer="btc_data_day")

查看數據可得,現在的數據一共有 1380 條,數據由 Date、Open、High、Low、Close、Volume(BTC)、Volume(Currency)、Weighted Price 這幾列組成。其中除去 Date 列以外,其餘的數據列都是 float64 數據類型。

data.info()

查看下前 10 行的數據

data.head(10)

image.png

數據可視化

使用 matplotlib 將 Weighted Price 繪製出來,看下數據的分布跟走勢。在圖中我們發現了有一段數據 0 的部分,我們需要確認下數據是否有異常。

plt.plot(data['Weighted Price'], label='Price')
plt.ylabel('Price')
plt.legend()
plt.show()

image.png

異常數據處理

先查看下數據是否含有 nan 的數據,可以看到我們的數據中沒有 nan 的數據

data.isnull().sum()

Date                 0
Open                 0
High                 0
Low                  0
Close                0
Volume (BTC)         0
Volume (Currency)    0
Weighted Price       0
dtype: int64

再查看下 0 數據,可以看到我們的數據中含有 0 值,我們需要對 0 值做下處理

(data == 0).astype(int).any()

Date                 False
Open                  True
High                  True
Low                   True
Close                 True
Volume (BTC)          True
Volume (Currency)     True
Weighted Price        True
dtype: bool

data['Weighted Price'].replace(0, np.nan, inplace=True)
data['Weighted Price'].fillna(method='ffill', inplace=True)
data['Open'].replace(0, np.nan, inplace=True)
data['Open'].fillna(method='ffill', inplace=True)
data['High'].replace(0, np.nan, inplace=True)
data['High'].fillna(method='ffill', inplace=True)
data['Low'].replace(0, np.nan, inplace=True)
data['Low'].fillna(method='ffill', inplace=True)
data['Close'].replace(0, np.nan, inplace=True)
data['Close'].fillna(method='ffill', inplace=True)
data['Volume (BTC)'].replace(0, np.nan, inplace=True)
data['Volume (BTC)'].fillna(method='ffill', inplace=True)
data['Volume (Currency)'].replace(0, np.nan, inplace=True)
data['Volume (Currency)'].fillna(method='ffill', inplace=True)

(data == 0).astype(int).any()

Date                 False
Open                 False
High                 False
Low                  False
Close                False
Volume (BTC)         False
Volume (Currency)    False
Weighted Price       False
dtype: bool

再看下數據的分布跟走勢,這個時候曲線已經非常的連續

plt.plot(data['Weighted Price'], label='Price')
plt.ylabel('Price')
plt.legend()
plt.show()

image.png

訓練數據集和測試數據集劃分

將數據歸一化到 0-1

data_set = data.drop('Date', axis=1).values
data_set = data_set.astype('float32')
mms = MinMaxScaler(feature_range=(0, 1))
data_set = mms.fit_transform(data_set)

以 2:8 劃分測試數據集跟訓練數據集

ratio = 0.8
train_size = int(len(data_set) * ratio)
test_size = len(data_set) - train_size
train, test = data_set[0:train_size,:], data_set[train_size:len(data_set),:]

創建訓練數據集跟測試數據集,以 1 天作為窗口期來創建我們的訓練數據集跟測試數據集。

def create_dataset(data):
    window = 1
    label_index = 6
    x, y = [], []
    for i in range(len(data) - window):
        x.append(data[i:(i + window), :])
        y.append(data[i + window, label_index])
    return np.array(x), np.array(y)

train_x, train_y = create_dataset(train)
test_x, test_y = create_dataset(test)

定義模型並訓練

這次我們使用一個簡單的模型,這個模型結構如下 1. LSTM2. Dense。

這裡需要對 LSTM 的 inputh shape 做下說明, Input Shape 的輸入維度為(batch_size, time steps, features)。其中,time steps 值的是數據輸入的時候的時間窗口間隔,這裡我們使用 1 天作為時間窗口,並且我們的數據都是日數據,因此這裡我們的 time steps 為 1。

長短期記憶(Long short-term memory, LSTM)是一種特殊的 RNN,主要是為了解決長序列訓練過程中的梯度消失和梯度爆炸問題,這裡先簡單介紹下 LSTM。

image.png

從 LSTM 的網路結構示意圖中,可以看到 LSTM 其實是一個小型的模型,他包含了 3 個 sigmoid 激活函數,2 個 tanh 激活函數,3 個乘法,1 個加法。

細胞狀態

細胞狀態是 LSTM 的核心,他是上圖中最上面的那根黑線, 在這根黑線下面是一些門,我們在後面介紹。細胞狀態會根據每個門的結果,來得到更新。下面我們介紹下這些門,你就會理解細胞狀態的流程。

LSTM 網路能通過一種被稱為門的結構對細胞狀態進行刪除或者添加資訊。門能夠有選擇性的決定讓哪些資訊通過。門的結構是一個 sigmoid 層和一個點乘操作的組合。因為 sigmoid 層的輸出是 0-1 的值,0 表示都不能通過,1 表示都能通過。一個 LSTM 裡面包含三個門來控制細胞狀態。下面我們來一一介紹下這些門。

遺忘門

LSTM 的第一步就是決定細胞狀態需要丟棄哪些資訊。這部分操作是通過一個稱為忘記門的 sigmoid 單元來處理的。我們來看下動畫示意圖,

我們可以看到,遺忘門通過查看h_{l-1}x_{t}資訊來輸出一個 0-1 之間的向量,該向量裡面的 0-1 值表示細胞狀態C_{t-1}中的哪些資訊保留或丟棄多少。0 表示不保留,1 表示都保留。

數學表達式: f_{t}=\sigma\left(W_{f} \cdot\left[h_{t-1}, x_{t}\right]+b_{f}\right)

輸入門

下一步是決定給細胞狀態添加哪些新的資訊,這個步驟是通過輸入門開完成的。我們先來看下動畫示意圖,

我們看到了h_{l-1}x_{t}的資訊又被放入了一個遺忘門(sigmoid)跟輸入門(tanh)中。因為遺忘門的輸出結果是 0-1 的值,因此,如果遺忘門輸出的是 0 的話,輸入門後的結果C_{i}將不會被添加到當前的細胞狀態中,如果是 1,會全部的被添加到細胞狀態中,因此這裡的遺忘門的作用是將輸入門的結果選擇性的添加到細胞狀態中。

數學公式為: C_{t}=f_{t} * C_{t-1}+i_{t} * \\tilde{C}_{t}

輸出門

更新完細胞狀態後需要根據h_{l-1}x_{t}輸入的和來判斷輸出細胞的哪些狀態特徵,這裡需要將輸入經過一個稱為輸出門的 sigmoid 層得到判斷條件,然後將細胞狀態經過 tanh 層得到一個-1~1 之間值的向量,該向量與輸出門得到的判斷條件相乘就得到了最終該 RNN 單元的輸出, 動畫示意圖如下

def create_model():
    model = Sequential()
    model.add(LSTM(50, input_shape=(train_x.shape[1], train_x.shape[2])))
    model.add(Dense(1))
    model.compile(loss='mae', optimizer='adam')
    model.summary()
    return model

model = create_model()

image.png

history = model.fit(train_x, train_y, epochs=80, batch_size=64, validation_data=(test_x, test_y), verbose=1, shuffle=False)

plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='test')
plt.legend()
plt.show()

image.png

train_x, train_y = create_dataset(train)
test_x, test_y = create_dataset(test)

預測

predict = model.predict(test_x)
plt.plot(predict, label='predict')
plt.plot(test_y, label='ground true')
plt.legend()
plt.show()

image.png

當前利用機器學習預測比特幣長期價格走勢還是非常困難的,本文只能作為學習案例使用。該案例之後會上線與矩池雲的 Demo 鏡像之中,感興趣的用戶可以直接體驗。