【干货】RNN-LSTM的Keras实现:以预测比特币和以太坊价格为例(附代码))

【导读】本文是Siavash Fahimi撰写的一篇很棒的技术博文,主要讲解了用Keras实现RNN-LSTM,并用来预测比特币和以太坊的价格。在过去的一年,互联网行业最火的名词除了AI以外,就当属区块链了,虽然本文不涉及区块链的技术讲解,但是由于是对比特币价格进行预测,所以在此提及。言归正传,本文首先介绍RNNLSTM的原理,这是两种应用广泛的时序模型,相信很多读者也都有所了解。本文的重点在于通过一个完整的实例来帮助读者理解RNN-LSTM以及Keras的用法, 并附完整实现代码,相信能给您带来新的感悟。

 How to predict Bitcoin and Ethereum price with RNN-LSTM in Keras

如何在Keras用RNN-LSTM预测Bitcoin和Ethereum的价格


2017年对于AI和Cryptocurrency而言是伟大的一年。在人工智能行业已经有许多研究和突破,而且人工智能是当今最流行的技术之一,未来还会更加流行。对于cryptocurrencies,我个人在2017年没有看到它成为主流。 它是一个巨大的牛市,在Bitcoin(比特币)、Ethereum(以太坊)、Litecoin、Ripple等cryptocurrencies的投资上会有一些疯狂的回报。


我已经在2017年初开始深入了解机器学习技术的细节,并且与许多其他ML专家和爱好者一样,将这些技术应用到cryptocurrencies(加密数字货币)市场,这会是非常诱人的。有趣的部分是ML和Deep Learning模型可以多种方式用于股票市场或我们的案例密码市场。


我发现建立单点预测模型可以成为深入探索时间序列深度学习(如价格数据)的绝佳起点。 当然,它并不会在这里结束,因为总会有改进的空间并且可以增加更多的输入数据。 我最喜欢的是使用Deep Reinforcement Learning作为自动交易代理。 这也是目前我正在研究的内容,然而学习使用LSTM网络并建立一个良好的预测模型将是第一步。

 

先决条件和开发环境




假设你已经拥有Python的一些编程技能,并具备机器学习的基本知识,尤其是深度学习。 如果没有,请查看这篇文章快速浏览。(链接)

https://medium.freecodecamp.org/want-to-know-how-deep-learning-works-heres-a-quick-guide-for-everyone-1aedeca88076

 

我选择的开发环境是谷歌的Colab。 我选择Colab,因为环境设置的简单性以及免费GPU的使用,这使得训练时间变得非常重要。 以下是如何在Google云端硬盘中设置和使用colab的教程。 你可以在GitHub上找到我的完整Colab Notebook。(链接)

https://github.com/SiaFahim/lstm-crypto-predictor/blob/master/lstm_crypto_price_prediction.ipynb

 

如果您希望设置AWS环境,我也在前面写了一篇关于如何在GPU上使用Docker设置AWS实例的教程。 链接在这里。(链接)

https://towardsdatascience.com/how-to-set-up-deep-learning-machine-on-aws-gpu-instance-3bb18b0a2579

 

我将使用带有TensorFlow后端的Keras库来构建模型并训练历史数据。

 

什么是递归神经网络?




为了解释递归神经网络,首先让我们回到带有一个隐藏层的简单感知器网络。这种网络可以把简单的分类问题做好。通过添加更多的隐藏层,网络将能够从我们输入的数据中推断出更复杂的模式,并提高预测的准确性。 然而,这些类型的网络适用于独立于历史的任务,对于这些历史任务,时间顺序是无关紧要的。例如图像分类,其中训练集中的先前样本不影响下一个样本。 换句话说,感知器对过去没有记忆。对于卷积神经网络来说也一样,卷积神经网络是为图像识别设计的感知器的更复杂的体系结构。

具有一个隐藏层和两个输出的简单感知器神经网络


RNNs是神经网络的一种类型,它通过循环地将当前时刻的数据和上一时刻的隐藏状态同时输入来解决感知器的过去记忆问题。


让我来解释这一点,在每一次新样本进入时,网络都会忘记上一步中的样本,解决时间序列问题的一种方法是将前一个输入样本与当前样本进行馈送,这样我们的网络就可以知道以前发生了什么,但是,这样我们就无法捕捉在上一步以前的时间序列的完整历史记录。 一个更好的方法是从前一个输入样本中获得隐藏层(隐藏层的权重矩阵),并将其与当前输入样本一起输入到我们的网络中。


我将隐藏层的权重矩阵看作网络的心理状态,如果我们以这种方式来看,隐藏层已经以所有神经元的权重分布的形式捕获过去时间信息,这更能丰富的代表过去的网络。 下面这张来自colah博客的图片很好的向我们展示了RNN的原理。

 当Xt到达时,来自Xt-1的隐藏状态将与Xt串联,并作为网络在时间t的输入。 这个过程将对时间序列中的每个样本重复一次。

 

我尽量表达得简单一点。如果您想更深入地了解RNNs,这里有很多资源。下面是关于RNN的好资源:


Introduction to RNNs

Recurrent Neural Networks for Beginners

The Unreasonable Effectiveness of Recurrent Neural Networks


其链接分别是:

http://www.wildml.com/2015/09/recurrent-neural-networks-tutorial-part-1-introduction-to-rnns/

https://medium.com/@camrongodbout/recurrent-neural-networks-for-beginners-7aca4e933b82

http://karpathy.github.io/2015/05/21/rnn-effectiveness/

 

什么是长短期记忆(LSTM)




在告诉你什么是LSTM之前,让我们先看一下关于RNN的最大问题。到目前为止,在我们通过反向传播训练之前,一切看起来都很好。当训练样本的梯度通过网络向后传播时,它变得越来越弱,当它到达代表我们时间序列中较旧数据点的那些神经元时,它无法正确调整它们。 这个问题被称为梯度消失。 LSTM单元是一种RNN,它存储关于过去的重要信息并忘记非重要的部分。 这样,当渐变向后传播时,它不会被不必要的信息所消耗。


当你阅读一本书时,经常在阅读一章之后自己回顾一下,虽然你可以记住前一章的内容,但是你可能无法记住所有关于它的重要观点。 解决这个问题的一个方法是,我们强调并记录那些重要的点,忽略对该主题不重要的解释。Christopher Olah的《Understanding LSTM Networks 》是深入了解LSTM的重要资源。

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

 

开始编码




首先,我们导入我们项目所需的库。

import gc
import datetime
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

import keras
from keras.models import Sequential
from keras.layers import Activation, Dense
from keras.layers import LSTM
from keras.layers import Dropout

 

历史数据




我使用了www.coinmarketcap.com的历史数据,您也可以用其它数据,但我觉得这数据很适合这篇文章。我们将获得Bitcoin的每日价格数据。但是,在colab笔记本中,您也会看到 Ethereum的代码。我以某种方式编写代码以便可以重复应用于其他加密货币。


现在让我们编写一个获取市场数据的函数。

def get_market_data(market, tag=True):
"""
market: the full name of the cryptocurrency as spelled on coinmarketcap.com.
eg.: 'bitcoin'
tag: eg.: 'btc', if provided it will add a tag to the name of every column.
 returns: panda DataFrame
This function will use the coinmarketcap.com url for provided coin/token
page.
Reads the OHLCV and Market Cap. Converts the date format to be readable.
Makes sure that the data is consistant by converting non_numeric values to
a number very close to 0.
And finally tags each columns if provided.
 """
 market_data = pd.read_html("https://coinmarketcap.com/currencies/" +
market + "/historical-data/?start=20130428&end="+time.strftime("%Y%m%d"),
flavor='html5lib')[0]
market_data = market_data.assign(Date=pd.to_datetime(market_data['Date']))
market_data['Volume'] = (pd.to_numeric(market_data['Volume'],
errors='coerce').fillna(0))
if tag:
market_data.columns = [market_data.columns[0]] + [tag + '_' + i for i in
market_data.columns[1:]]
return market_data

 

现在让我们获取Bitcoin 的数据并将其加载到变量'''btc_data'''并显示我们数据的第一行。

btc_data = get_market_data("bitcoin", tag='BTC')
btc_data.head()

BTC的市场数据

 

让我们来看看Bitcoin的‘Close’价格以及随着时间的推移日常交易量

show_plot(btc_data, tag='BTC')


数据准备




构建任何深度学习模型,其中很大一部分是准备我们的数据,用于神经网络的训练或预测。 此步骤称为预处理,根据我们正在使用的数据类型,可能包含多个步骤。 在我们的案例中,我们将做下面的工作,并作为我们预处理的一部分:

数据清理,填补缺失的数据点

合并多个数据通道。Bitcoin 和Ethereum在一个数据框中。

计算价格波动并将其添加为新列

删除不必要的列

按照日期升序对我们的数据进行排序

拆分数据用于训练和测试

创建输入样本并在0和1之间进行归一化

创建训练和测试集的目标输出并将其归一化到0-1之间

将我们的数据转换为numpy数组以供我们的模型使用

 

数据清理部分已经在我们加载数据的第一个函数中完成了。 您可以在下面找到必要的函数来完成上述任务:


def merge_data(a, b, from_date=merge_date):
"""
 a: first DataFrame
 b: second DataFrame
 from_date: includes the data from the provided date and drops the any data
 before that date.
 returns merged data as Pandas DataFrame
 """
 merged_data = pd.merge(a, b, on=['Date'])
merged_data = merged_data[merged_data['Date'] >= from_date]
return merged_data


def add_volatility(data, coins=['BTC', 'ETH']):
"""
 data: input data, pandas DataFrame
 coins: default is for 'btc and 'eth'. It could be changed as needed
 This function calculates the volatility and close_off_high of each given
 coin in 24 hours,
 and adds the result as new columns to the DataFrame.
 Return: DataFrame with added columns
 """
 for coin in coins:
# calculate the daily change
   kwargs = {coin + '_change': lambda x: (x[coin + '_Close'] - x[coin +
'_Open']) / x[coin + '_Open'], coin + '_close_off_high': lambda x:
2*(x[coin + '_High'] - x[coin + '_Close']) / (x[coin + '_High'] -
x[coin + '_Low']) - 1, coin + '_volatility': lambda x: (x[coin +
'_High'] - x[coin + '_Low']) / (x[coin + '_Open'])}
data = data.assign(**kwargs)
return data


def create_model_data(data):
"""
 data: pandas DataFrame
This function drops unnecessary columns and reverses the order of DataFrame
based on decending dates.
 Return: pandas DataFrame
 """
 #data = data[['Date']+[coin+metric for coin in ['btc_', 'eth_'] for metric
in ['Close','Volume','close_off_high','volatility']]]
 data = data[['Date']+[coin+metric for coin in ['BTC_', 'ETH_'] for metric
in ['Close','Volume']]]
data = data.sort_values(by='Date')
return data


def split_data(data, training_size=0.8):
"""
 data: Pandas Dataframe
 training_size: proportion of the data to be used for training
 This function splits the data into training_set and test_set based on the
 given training_size
 Return: train_set and test_set as pandas DataFrame
 """
 return data[:int(training_size*len(data))],
data[int(training_size*len(data)):]


def create_inputs(data, coins=['BTC', 'ETH'], window_len=window_len):
"""
 data: pandas DataFrame, this could be either training_set or test_set
 coins: coin datas which will be used as the input. Default is 'btc', 'eth'
 window_len: is an intiger to be used as the look back window for creating
 a single input sample.
 This function will create input array X from the given dataset and will
 normalize 'Close' and 'Volume' between 0 and 1
 Return: X, the input for our model as a python list which later needs to
 be converted to numpy array.
 """
 norm_cols = [coin + metric for coin in coins for metric in ['_Close',
'_Volume']]
inputs = []
for i in range(len(data) - window_len):
temp_set = data[i:(i + window_len)].copy()
inputs.append(temp_set)
for col in norm_cols:
inputs[i].loc[:, col] = inputs[i].loc[:, col] / inputs[i].loc[:, col].
iloc[0] - 1  
 return inputs


def create_outputs(data, coin, window_len=window_len):
"""
 data: pandas DataFrame, this could be either training_set or test_set
 coin: the target coin in which we need to create the output labels for
 window_len: is an intiger to be used as the look back window for creating
 a single input sample.
 This function will create the labels array for our training and validation
 and normalize it between 0 and 1
 Return: Normalized numpy array for 'Close' prices of the given coin
 """
 return (data[coin + '_Close'][window_len:].values / data[coin + '_Close']
[:-window_len].values) - 1


def to_array(data):
"""
 data: DataFrame
 This function will convert list of inputs to a numpy array
 Return: numpy array
 """
 x = [np.array(data[i]) for i in range (len(data))]
return np.array(x)


下面是绘图和创建日期标签的代码:

def show_plot(data, tag):
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios':
[3, 1]})
ax1.set_ylabel('Closing Price ($)', fontsize=12)
ax2.set_ylabel('Volume ($ bn)', fontsize=12)
ax2.set_yticks([int('%d000000000' % i) for i in range(10)])
ax2.set_yticklabels(range(10))
ax1.set_xticks([datetime.date(i, j, 1) for i in range(2013, 2019) for j
in [1, 7]])
ax1.set_xticklabels('')
ax2.set_xticks([datetime.date(i, j, 1) for i in range(2013, 2019) for j
in [1, 7]])
ax2.set_xticklabels([datetime.date(i, j, 1).strftime('%b %Y') for i in
range(2013, 2019) for j in [1, 7]])
ax1.plot(data['Date'].astype(datetime.datetime), data[tag + '_Open'])
ax2.bar(data['Date'].astype(datetime.datetime).values, data[tag +
'_Volume'].values)
fig.tight_layout()
plt.show()


def date_labels():
last_date = market_data.iloc[0, 0]
date_list = [last_date - datetime.timedelta(days=x) for x in
range(len(X_test))]
return [date.strftime('%m/%d/%Y') for date in date_list][::-1]


def plot_results(history, model, Y_target, coin):
plt.figure(figsize=(25, 20))
plt.subplot(311)
plt.plot(history.epoch, history.history['loss'], )
plt.plot(history.epoch, history.history['val_loss'])
plt.xlabel('Number of Epochs')
plt.ylabel('Loss')
plt.title(coin + ' Model Loss')
plt.legend(['Training', 'Test'])

plt.subplot(312)
plt.plot(Y_target)
plt.plot(model.predict(X_train))
plt.xlabel('Dates')
plt.ylabel('Price')
plt.title(coin + ' Single Point Price Prediction on Training Set')
plt.legend(['Actual', 'Predicted'])

ax1 = plt.subplot(313)
plt.plot(test_set[coin + '_Close'][window_len:].values.tolist())
plt.plot(((np.transpose(model.predict(X_test)) + 1) * test_set[coin +
'_Close'].values[:-window_len])[0])
plt.xlabel('Dates')
plt.ylabel('Price')
plt.title(coin + ' Single Point Price Prediction on Test Set')
plt.legend(['Actual', 'Predicted'])

date_list = date_labels()
ax1.set_xticks([x for x in range(len(date_list))])
for label in ax1.set_xticklabels([date for date in date_list],
rotation='vertical')[::2]:
label.set_visible(False)

plt.show()

在这里我们将调用上面的函数来创建我们模型的最终数据集。

train_set = train_set.drop('Date', 1)
test_set = test_set.drop('Date', 1)
X_train = create_inputs(train_set)
Y_train_btc = create_outputs(train_set, coin='BTC')
X_test = create_inputs(test_set)
Y_test_btc = create_outputs(test_set, coin='BTC')
Y_train_eth = create_outputs(train_set, coin='ETH')
Y_test_eth = create_outputs(test_set, coin='ETH')
X_train, X_test = to_array(X_train), to_array(X_test)

现在我们来构建我们的LSTM-RNN模型。 在这个模型中,我使用了3层LSTM,每层512个神经元,然后在每个LSTM层之后有个0.25概率的Dropout层,以防止过度拟合(over-fitting),并且每隔一个Dense层产生我们的输出。


def build_model(inputs, output_size, neurons, activ_func=activation_function
, dropout=dropout, loss=loss, optimizer=optimizer):
"""
 inputs: input data as numpy array
 output_size: number of predictions per input sample
 neurons: number of neurons/ units in the LSTM layer
 active_func: Activation function to be used in LSTM layers and Dense layer
 dropout: dropout ration, default is 0.25
 loss: loss function for calculating the gradient
 optimizer: type of optimizer to backpropagate the gradient
 This function will build 3 layered RNN model with LSTM cells with dripouts
 after each LSTM layer
 and finally a dense layer to produce the output using keras' sequential
 model.
 Return: Keras sequential model and model summary
 """
 model = Sequential()
model.add(LSTM(neurons, return_sequences=True,
input_shape=(inputs.shape[1], inputs.shape[2]), activation=activ_func))
model.add(Dropout(dropout))
model.add(LSTM(neurons, return_sequences=True, activation=activ_func))
model.add(Dropout(dropout))
model.add(LSTM(neurons, activation=activ_func))
model.add(Dropout(dropout))
model.add(Dense(units=output_size))
model.add(Activation(activ_func))
model.compile(loss=loss, optimizer=optimizer, metrics=['mae'])
model.summary()
return model

我使用了'tanh'作为我的激活函数,MSE作为我的损失,'adam'作为我的优化器。 我建议对每部分进行不同的选择,看看它们如何影响模型的性能。

这是我们的模型总结:

我已经在代码开始时声明了超参数,以便对于不同的变体从某个地方更容易地做出更改。 这里是我的超参数:

neurons = 512                 
activation_function = 'tanh'  
loss = 'mse'                  
optimizer="adam"              
dropout = 0.25                
batch_size = 12              
epochs = 53                  
window_len = 7              
training_size = 0.8
merge_date = '2016-01-01'

现在是对我们收集的数据进行模型训练的时候了

# clean up the memory
gc.collect()
# random seed for reproducibility
np.random.seed(202)
# initialise model architecture
btc_model = build_model(X_train, output_size=1, neurons=neurons)
# train model on data
btc_history = btc_model.fit(X_train, Y_train_btc, epochs=epochs,
batch_size=batch_size, verbose=1, validation_data=(X_test, Y_test_btc),
shuffle=False)

上述代码可能需要一段时间才能完成,这取决于您的计算能力,完成后,您的训练模型也完成了:)

我们来看看BTC和ETH的结果.

参考文献

https://medium.com/@siavash_37715/how-to-predict-bitcoin-and-ethereum-price-with-rnn-lstm-in-keras-a6d8ee8a5109

ttps://dashee87.github.io/deep%20learning/python/predicting-cryptocurrency-prices-with-deep-learning/

代码连接:

https://github.com/SiaFahim/lstm-crypto-predictor/blob/master/lstm_crypto_price_prediction.ipynb

h

-END-

专 · 知

人工智能领域主题知识资料查看获取【专知荟萃】人工智能领域26个主题知识资料全集(入门/进阶/论文/综述/视频/专家等)

请PC登录www.zhuanzhi.ai或者点击阅读原文,注册登录专知,获取更多AI知识资料

请扫一扫如下二维码关注我们的公众号,获取人工智能的专业知识!

请加专知小助手微信(Rancho_Fang),加入专知主题人工智能群交流!

点击“阅读原文”,使用专知

展开全文
Top
微信扫码咨询专知VIP会员