神经网络与数字货币量化交易系列(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%,仍是超出个人预期。不知道是否是哪里搞错了
固然此模型没有什么实盘价值,但简单易懂,仅依此入门,接下来还会有更多的神经网络入门应用在数字货币量化的入门课程。

相关文章
相关标签/搜索