神經網絡與數字貨幣量化交易系列(1)——LSTM預測比特幣價格

首發地址:https://www.fmz.com/digest-topic/4035

1.簡單介紹

深度神經網絡這些年愈來愈熱門,在不少領域解決了過去沒法解決的難題,體現了強大的能力。在時間序列的預測上,經常使用的神經網絡價格是RNN,由於RNN不只有當前數據輸入,還有歷史數據的輸入,固然,當咱們談論RNN預測價格時,每每談論的是RNN的一種:LSTM。本文就將以pytorch爲基礎,構建預測比特幣價格的模型。網上相關的資料雖然多,但仍是不夠透徹,使用pytorch的也相對較少,仍是有必要寫一篇文章, 最終結果是利用比特幣行情的開盤價、收盤價、最高價、最低價、交易量來預測下一個收盤價。我我的神經網絡知識通常,歡迎各位大佬批評指正。
本教程由FMZ發明者數字貨幣量化交易平臺出品(www.fmz.com),歡迎入QQ羣:863946592 交流。html

 

2.數據和參考

比特幣價格數據來源自FMZ發明者量化交易平臺:https://www.quantinfo.com/Tools/View/4.html
一個相關的價格預測例子:https://yq.aliyun.com/articles/538484
關於RNN模型的詳細介紹:https://zhuanlan.zhihu.com/p/27485750
理解RNN的輸入輸出:https://www.zhihu.com/question/41949741/answer/318771336
關於pytorch:官方文檔 https://pytorch.org/docs 其它資料自行搜索吧。
另外讀懂本文還須要一些前置知識,如pandas/爬蟲/數據處理等,但不會也不要緊。node

 

3.pytorch LSTM模型的參數

LSTM的參數:python

第一次看到文檔上這些密密麻麻的參數,個人反應是:

隨着慢慢閱讀,總算大概明白了json

input_size: 輸入向量x的特徵大小,若是以收盤價預測收盤價,那麼input_size=1;若是以高開低收預測收盤價,那麼input_size=4
hidden_size: 隱含層大小
num_layers: RNN的層數
batch_first: 若是爲True則輸入維度的第一個爲batch_size,這個參數也很讓人困惑,下面將詳細介紹。網絡

輸入數據參數:app

input: 具體輸入的數據,是一個三維的tensor, 具體的形狀爲:(seq_len, batch, input_size)。其中,seq_len指序列的長度,即LSTM須要考慮多長時間的歷史數據,注意這個指只是數據的格式,不是LSTM內部的結構,同一個LSTM模型能夠輸入不一樣seq_len的數據,都能給出預測的結果;batch指batch的大小,表明有多少組不一樣的數據;input_size就是前面的input_size。
h_0: 初始的hidden狀態, 形狀爲(num_layers * num_directions, batch, hidden_size),若是時雙向網絡num_directions=2
c_0: 初始的cell狀態, 形狀同上, 能夠不指定。函數

輸出參數:優化

output: 輸出的形狀 (seq_len, batch, num_directions * hidden_size),注意和模型參數batch_first有關
h_n: t = seq_len時刻的h狀態,形狀同h_0
c_n: t = seq_len時刻的c狀態,形狀同c_0spa

 

4.LSTM輸入輸出的簡單例子

首先導入所須要的包調試

import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import matplotlib.pyplot as plt

  

定義LSTM模型

LSTM = nn.LSTM(input_size=5, hidden_size=10, num_layers=2, batch_first=True)

 

準備輸入的數據

x = torch.randn(3,4,5)
# x的值爲:
tensor([[[ 0.4657,  1.4398, -0.3479,  0.2685,  1.6903],
         [ 1.0738,  0.6283, -1.3682, -0.1002, -1.7200],
         [ 0.2836,  0.3013, -0.3373, -0.3271,  0.0375],
         [-0.8852,  1.8098, -1.7099, -0.5992, -0.1143]],

        [[ 0.6970,  0.6124, -0.1679,  0.8537, -0.1116],
         [ 0.1997, -0.1041, -0.4871,  0.8724,  1.2750],
         [ 1.9647, -0.3489,  0.7340,  1.3713,  0.3762],
         [ 0.4603, -1.6203, -0.6294, -0.1459, -0.0317]],

        [[-0.5309,  0.1540, -0.4613, -0.6425, -0.1957],
         [-1.9796, -0.1186, -0.2930, -0.2619, -0.4039],
         [-0.4453,  0.1987, -1.0775,  1.3212,  1.3577],
         [-0.5488,  0.6669, -0.2151,  0.9337, -1.1805]]])

 

x的形狀爲(3,4,5),因爲咱們以前定義了batch_first=True, 此時的batch_size的大小爲3, sqe_len爲4, input_size爲5。 x[0]表明了第一個batch。

若是沒定義batch_first,默認爲False,則此時數據的表明的徹底不一樣,batch大小爲4, sqe_len爲3, input_size爲5。 此時x[0]表明了全部batch在t=0時數據,依次類推。 我的感受這種設定不符合直覺, 因此添加了參數batch_first=True.

二者之間數據的轉換也很方便: x.permute(1,0,2)

輸入和輸出

LSTM的輸入輸出的形狀很容易讓人迷惑,藉助下圖能夠輔助理解:

來源:https://www.zhihu.com/question/41949741/answer/318771336

x = torch.randn(3,4,5)
h0 = torch.randn(2, 3, 10)
c0 = torch.randn(2, 3, 10)
output, (hn, cn) = LSTM(x, (h0, c0))
print(output.size()) #在這裏思考一下,若是batch_first=False輸出的大小會是多少?
print(hn.size())
print(cn.size())
#結果
torch.Size([3, 4, 10])
torch.Size([2, 3, 10])
torch.Size([2, 3, 10])

 

觀察輸出的結果,和前面的參數的解釋一致.注意到hn.size()的第二個值爲3,和batch_size的大小保持一致,說明hn中並無保存中間狀態,只保存了最後一步。
因爲咱們的LSTM網絡有兩層,其實hn最後一層的輸出就是output的值,output的形狀爲[3, 4, 10],保存了t=0,1,2,3全部時刻的結果,因此:

hn[-1][0] == output[0][-1] #第一個batch在hn最後一層的輸出等於第一個batch在t=3時output的結果 hn[-1][1] == output[1][-1] hn[-1][2] == output[2][-1] 
 

5.準備比特幣行情數據

前面講了這麼多內容,只是鋪墊,理解LSTM的輸入輸出非常重要,不然隨意從網上摘抄一些代碼很容易出錯,因爲LSTM在時間序列上的強大能力,即便模型是錯誤的,最後也能得出的不錯結果。

數據的獲取

數據使用的是Bitfinex交易所BTC_USD交易對的行情數據。

import requests
import json

resp = requests.get('https://www.quantinfo.com/API/m/chart/history?symbol=BTC_USD_BITFINEX&resolution=60&from=1525622626&to=1562658565')
data = resp.json()
df = pd.DataFrame(data,columns = ['t','o','h','l','c','v'])
print(df.head(5))

 

數據格式以下:

數據的預處理

df.index = df['t'] # index設爲時間戳
df = (df-df.mean())/df.std() # 數據的標準化,不然模型的Loss會很是大,不利於收斂
df['n'] = df['c'].shift(-1) # n爲下一個週期的收盤價,是咱們預測的目標
df = df.dropna()
df = df.astype(np.float32) # 改變下數據格式適應pytorch

 

數據的標準化的方法很是粗糙,會有一些問題,僅僅是演示,可使用收益率之類的數據標準化。

準備訓練數據

seq_len = 10 # 輸入10個週期的數據
train_size = 800 # 訓練集batch_size
def create_dataset(data, seq_len):
    dataX, dataY=[], []
    for i in range(0,len(data)-seq_len, seq_len):
        dataX.append(data[['o','h','l','c','v']][i:i+seq_len].values)
        dataY.append(data['n'][i:i+seq_len].values)
    return np.array(dataX), np.array(dataY)
data_X, data_Y = create_dataset(df, seq_len)
train_x = torch.from_numpy(data_X[:train_size].reshape(-1,seq_len,5)) #變化形狀,-1表明的值會自動計算
train_y = torch.from_numpy(data_Y[:train_size].reshape(-1,seq_len,1))

 

最終train_x和train_y的形狀分別爲:torch.Size([800, 10, 5]), torch.Size([800, 10, 1])。 因爲咱們的模型是根據10個週期的數據預測下個週期的收盤價,理論上800個batch,只要有800個預測收盤價就好了。但train_y在每一個batch中有10個數據,實際上每一個batch預測的中間結果是保留的,並不僅有最後一個。在計算最後的Loss時,能夠把全部的10個預測結果都考慮進去和train_y中實際值進行比較。 理論上也能夠只計算最後一個預測結果的Loss。畫了一個粗糙的圖說明這個問題。因爲LSTM的模型實際不包含seq_len參數,因此模型可適用不一樣的長度,中間的預測結果也是有意義的,所以我傾向於合併計算Loss。

注意在準備訓練數據時,窗口的移動是跳躍的,已經使用的數據再也不使用,固然窗口也能夠逐個移動,這樣獲得的訓練集會大不少。但感受這樣相鄰的batch數據過重複了,因而採用了當前方法。

 

6.構造LSTM模型

最終構建的模型以下, 包含一個兩層的LSTM, 一個Linear層。

class LSTM(nn.Module):
    def __init__(self, input_size, hidden_size, output_size=1, num_layers=2):
        super(LSTM, self).__init__()
        self.rnn = nn.LSTM(input_size,hidden_size,num_layers,batch_first=True)
        self.reg = nn.Linear(hidden_size,output_size) # 線性層,把LSTM的結果輸出成一個值

    def forward(self, x):
        x, _ = self.rnn(x) # 若是不理解前向傳播中數據維度的變化,可單獨調試
        x = self.reg(x)
        return x
        
net = LSTM(5, 10) # input_size爲5,表明了高開低收和交易量. 隱含層爲10.

 

 

7.開始訓練模型

終於開始訓練了,代碼以下:

criterion = nn.MSELoss() # 使用了簡單的均方差損失函數
optimizer = torch.optim.Adam(net.parameters(),lr=0.01) # 優化函數,lr可調
for epoch in range(600): # 因爲速度很快,這裏的epoch多一些
    out = net(train_x) # 因爲數據量很小, 直接拿全量數據計算
    loss = criterion(out, train_y)
    optimizer.zero_grad()
    loss.backward() # 反向傳播損失
    optimizer.step() # 更新參數
    print('Epoch: {:<3}, Loss:{:.6f}'.format(epoch+1, loss.item()))

 

訓練結果以下:

 

8.模型評價

模型的預測值:

p = net(torch.from_numpy(data_X))[:,-1,0] # 這裏只取最後一個預測值做爲比較
plt.figure(figsize=(12,8))
plt.plot(p.data.numpy(), label= 'predict')
plt.plot(data_Y[:,-1], label = 'real')
plt.legend()
plt.show()

 


根據圖上能夠看出,訓練數據(800以前)的吻合度很是高,但後期比特幣價格上漲新高,模型未見過這些數據,預測就力不從心了。這也說明了前面數據標準化時有問題。
雖然預測的價格不必定準確,那麼預測漲跌的準確率如何呢?截取一段預測數據看一下:

r = data_Y[:,-1][800:1000]
y = p.data.numpy()[800:1000]
r_change = np.array([1 if i > 0 else 0 for i in r[1:200] - r[:199]])
y_change = np.array([1 if i > 0 else 0 for i in y[1:200] - y[:199]])
print((r_change == y_change).sum()/float(len(r_change)))

 

結果預測漲跌的準確率達到了77.4%,仍是超出個人預期。不知道是否是哪裏搞錯了
固然此模型沒有什麼實盤價值,但簡單易懂,僅依此入門,接下來還會有更多的神經網絡入門應用在數字貨幣量化的入門課程。

相關文章
相關標籤/搜索