预测比特币的价格,LSTM介绍
今天我们将讨论如何通过分析过去6年的定价信息来预测比特币的价格。请注意,我们已经确定,我们的分析将只关注定价信息,而把任何可能影响比特币价格的因素放在一边,比如说新闻,它可以发挥非常重要的规则。出于这个原因,我说这篇文章和相关项目只用于教育目的,不应该用于生产。这是一个过于简单的模型,将帮助我们解释和理解使用Python和递归神经网络(RNN)的时间序列预测,更确切地说,我们将建立一个LSTM(长短期记忆)模型。
介绍性的概念
什么是RNN?
循环神经网络(RNN)是一类人工神经网络,节点之间的连接沿着时间序列形成一个有向图。这使它能够表现出时间上的动态行为,并使它们非常适合用于时间序列分析、语音识别、语法学习、文字合成等。
什么是LSTM?
长短时记忆(LSTM)是一种RNN,它不仅允许我们处理单个数据点(如图像),还允许我们处理整个数据序列(如语音或视频)。它们是时间序列预测的绝佳选择,也是我们今天要使用的架构类型。
LSTM单元
数据探索
在我们做任何事情之前,我们需要收集数据(我已经为你做了),我们需要了解数据。让我们先用Python加载数据集,并先看一下。
data = pd.read_csv("data/bitcoin.csv")
data = data.sort_values('Date')
data.head()
比特币数据,来源:coinbase
head() 函数已经给了我们一些关于数据集的列的有价值的信息,以及这些信息可能是什么样子。对于我们的目的,我们对Close 列感兴趣,它包含了当天结束时比特币的价格,对于那个特定日期。让我们看看我们是否可以用我们的数据集建立一个图表,向我们展示比特币的价格随时间的变化。
price = data[['Close']]
plt.figure(figsize = (15,9))
plt.plot(price)
plt.xticks(range(0, data.shape[0],50), data['Date'].loc[::50],rotation=45)
plt.title("Bitcoin Price",fontsize=18, fontweight='bold')
plt.xlabel('Date',fontsize=18)
plt.ylabel('Close Price (USD)',fontsize=18)
plt.show()
从2014年12月到2020年6月的比特币价格
还记得那些天比特币几乎是20K吗?让我们不要分心,让我们看看我们的Close 信息是否包含任何空值,空值对我们没有用处,如果有的话我们应该把它们弄出来。
price.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 2001 entries, 2000 to 0
Data columns (total 1 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Close 2001 non-null float64
dtypes: float64(1)
memory usage: 31.3 KB
一个简单的.info()在我们的数据框架上给了我们一些有用的信息,比如条目的数量,以及非空条目的数量,在我们的例子中是一样的,所以这里没有其他事情要做。
数据准备
归一化
我们对数据采取的第一个步骤是对其数值进行标准化。归一化的目的是将数据集中的数字列的值改变为一个共同的尺度,而不扭曲值的范围的差异。
为了我们的目的,我们将使用sklearn 库中的MinMaxScaler 。
from sklearn.preprocessing import MinMaxScaler
min_max_scaler = MinMaxScaler()
norm_data = min_max_scaler.fit_transform(price.values)
让我们试着比较一下规范化前后的数值。
Real: [370.], Normalized: [0.01280082]
Real: [426.1], Normalized: [0.01567332]
Real: [8259.99], Normalized: [0.41679416]
数据分割
在这一步中,我们实际上要解决两个问题,首先是我们需要将我们的数据集分成训练数据和测试数据。训练数据我们将用于教导我们的模型,而测试数据我们将用作我们预测的比较基线。这一点非常重要,因为我们要确保我们的预测是有意义的,但我们不能在我们训练网络的同一数据上进行测试,因为我们可能会遇到过拟合的风险。
此外,在这里我们还将为LSTM网络准备我们的数据。这种特殊类型的网络要求我们将时间序列分成几块数据,将我们用于训练的 "历史 "数据和我们的 "目标 "分开,后者告诉我们模型需要学习预测多远的未来。
负责这第二部分的将是我们的univariate_data 函数。
def univariate_data(dataset, start_index, end_index, history_size, target_size):
data = []
labels = []
start_index = start_index + history_size
if end_index is None:
end_index = len(dataset) - target_size
for i in range(start_index, end_index):
indices = range(i-history_size, i)
# Reshape data from (history_size,) to (history_size, 1)
data.append(np.reshape(dataset[indices], (history_size, 1)))
labels.append(dataset[i+target_size])
return np.array(data), np.array(labels)
分割将发生在这里。
past_history = 5
future_target = 0
TRAIN_SPLIT = int(len(norm_data) * 0.8)
x_train, y_train = univariate_data(norm_data,
0,
TRAIN_SPLIT,
past_history,
future_target)
x_test, y_test = univariate_data(norm_data,
TRAIN_SPLIT,
None,
past_history,
future_target)
通过使用past_history ,我们告诉我们的网络,我们需要用5天的数据来学习预测时间序列的下一个点future_target 。
建立模型
下一步是建立我们的模型架构。找到合适的模型是一门艺术,需要多次尝试加上经验,才能找到合适的层和每个层的超参数。
我们不会去讨论每一层的细节,每一层的复杂性都足以写一篇文章。但我要强调的是,我们在这里建立的模型是相当简单的,对这类问题来说是相当标准的,至少在所使用的层的类型上是如此。
from keras.models import Sequential
from keras.optimizers import Adam
from keras.layers import Dense, LSTM, LeakyReLU, Dropout
num_units = 64
learning_rate = 0.0001
activation_function = 'sigmoid'
adam = Adam(lr=learning_rate)
loss_function = 'mse'
batch_size = 5
num_epochs = 50
# Initialize the RNN
model = Sequential()
model.add(LSTM(units = num_units, activation=activation_function, input_shape=(None, 1)))
model.add(LeakyReLU(alpha=0.5))
model.add(Dropout(0.1))
model.add(Dense(units = 1))
# Compiling the RNN
model.compile(optimizer=adam, loss=loss_function)
让我们看看我们的架构是什么样子的。
Model: "sequential_13"
_______________________________________________________________
Layer (type) Output Shape Param #
===============================================================
lstm_6 (LSTM) (None, 64) 16896
_______________________________________________________________
leaky_re_lu_4 (LeakyReLU) (None, 64) 0
_______________________________________________________________
dropout_4 (Dropout) (None, 64) 0
_______________________________________________________________
dense_6 (Dense) (None, 1) 65
===============================================================
Total params: 16,961
Trainable params: 16,961
Non-trainable params: 0
_______________________________________________________________
训练模型
现在我们已经准备好了我们的数据,并且编译了我们的模型,我们可以开始训练,用Keras就像一行代码一样简单。
# Using the training set to train the model
history = model.fit(
x_train,
y_train,
validation_split=0.1,
batch_size=batch_size,
epochs=num_epochs,
shuffle=False
)
在这里找到合适的超参数也是艺术的一部分,然而,非常重要的是,参数shuffle ,设置为False 。我们的分析完全取决于信息的顺序,如果我们改变顺序,我们的结果将毫无意义。
这个模型的训练是你可以做的,即使没有GPU,数据量也很低,而且网络结构非常简单。在更高级的模型上,并且有更细化的信息,这些模型可能需要几个小时或几天的时间来训练。
在你完成训练后,重要的是我们要评估我们的训练结果,我们做得好吗?我们的训练损失和验证损失函数是什么样子的?如果有些地方看起来不对,那么你可能需要回到前面的步骤,玩玩你的超参数,或者甚至可能修改你的模型结构。
这里有一个不错的图表,你可以用来比较这两个函数。
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(loss))
plt.figure()
plt.plot(epochs, loss, 'b', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Validation loss')
plt.title("Training and Validation Loss")
plt.legend()
plt.show()
训练和验证损失
我的结果可能不是很理想,但对于我们的目的来说已经足够了。
预测
现在我们的模型已经训练好了,我们可以开始做一些预测,并将这些预测与我们的测试数据进行评估,看看我们的模型表现如何。
original = pd.DataFrame(min_max_scaler.inverse_transform(y_test))
predictions = pd.DataFrame(min_max_scaler.inverse_transform(model.predict(x_test)))
ax = sns.lineplot(x=original.index, y=original[0], label="Test Data", color='royalblue')
ax = sns.lineplot(x=predictions.index, y=predictions[0], label="Prediction", color='tomato')
ax.set_title('Bitcoin price', size = 14, fontweight='bold')
ax.set_xlabel("Days", size = 14)
ax.set_ylabel("Cost (USD)", size = 14)
ax.set_xticklabels('', size=10)
预测与实际的比特币价格
这张图在我看来非常好,对结果非常满意,你怎么看?
总结
RNNs和LSTM是我们可以用来分析和预测时间序列信息的伟大架构。在这篇文章中,我们更关注故事,而不是实现的技术细节,但如果你对这个话题感兴趣,可以做研究,玩玩它,改变层,超参数,尝试不同的东西,使用不同的列,或归一化方法,阅读更详细的文章,以及关于这个主题的论文。