StockProphet (2.0):如何使用 LSTM 模型预测股市

485 阅读13分钟

简介

投资股市是一件棘手的事情,即使是最有经验的投资者也很难对未来趋势做出准确的预测。在之前一篇题为 "StockProphet:试图预测股市"我们探讨了如何利用决策树来预测股市。然而,在这篇文章中,我们将更进一步,向你介绍一种更先进的机器学习技术--长短时记忆(LSTM)模型。

LSTM模型是一种递归神经网络,在时间序列预测任务中显示出显著的成功,使其成为预测股票市场的热门选择。在这篇文章中,我们将提供一个逐步的指南,说明如何使用LSTM模型来预测股市。

我们将首先引导你完成数据准备和预处理的过程,然后转向模型结构和训练。在此过程中,我们将涵盖一些关键概念,如特征工程、超参数调整和评估指标。

在本文结束时,你将对如何使用LSTM模型对未来股市趋势进行准确预测有一个坚实的了解。无论你是一个希望提高预测能力的经验丰富的投资者,还是一个探索LSTM模型能力的数据科学家,这篇文章对每个人都有意义。

image.png

所以,如果你已经准备好把你的股市预测提高到一个新的水平,让我们开始吧!对于那些错过了我们之前关于股市预测决策树的文章的人,你可以在这里找到它:StockProphet:试图预测股票市场

此外,我们将在Github资源库中提供我们在本文中使用的所有代码,所以你可以很容易地跟随,并在自己身上复制结果。这个资源库包括本文中使用的所有必要文件、数据和代码片段,使其成为对使用LSTM模型进行股市预测感兴趣的人的宝贵资源。

你可以在这里找到本文的Github资源库:Pouyaexe/StockProphet

什么是LSTM?

LSTM(长短时记忆)是一种循环神经网络(RNN),特别适合于处理连续数据。与传统的前馈神经网络不同,传统的前馈神经网络是独立处理每个输入的,而RNN在处理序列中的新输入时可以考虑到之前的输入。LSTM是RNN的一种特殊类型,能够通过有选择地保留或遗忘信息来捕捉时间序列数据中的长期依赖关系。

与决策树相比,LSTM模型在股票市场预测中具有几个优势。决策树的解释很简单,可以处理分类和数字数据。然而,他们可能难以捕捉到长期的依赖关系,并可能过度拟合训练数据。另一方面,LSTM模型可以处理数字和分类数据,并能更有效地捕捉长期依赖关系,使其更适合预测复杂和动态的时间序列数据,如股市。

与决策树相比,LSTM模型的另一个优势是它们能够处理可变长度的序列。在股市中,序列的长度可以根据所选择的时间框架而变化,如每日、每周或每月。LSTM模型可以通过自动调整其记忆单元的大小来处理可变长度的序列,而决策树则需要固定数量的输入特征。

Rian Dolphin有一篇关于LSTM的优秀文章,你可以在这里找到:LSTM网络|详细解释

数据准备

在深入研究如何使用LSTM预测股票价格的技术细节之前,正确准备数据是至关重要的。在本节中,我们将通过必要的步骤,让数据准备好进行建模。

首先,我们需要下载我们感兴趣的股票市场指数的历史数据。在这个例子中,我们将使用罗素1000指数,它是罗素3000指数的一个子集,追踪美国最大的1000家公司的表现。

import yfinance as yfdata = yf.download(tickers = '^RUI', start = '2012-03-11',end = '2022-07-10')

接下来,我们将在数据中添加一些技术指标,这可以帮助LSTM模型捕捉股市中更复杂的模式。在这个例子中,我们将添加四个技术指标:相对强弱指数(RSI),不同长度的指数移动平均线(EMA)。

import pandas_ta as tadata['RSI'] = ta.rsi(data.Close, length=15)data['EMAF'] = ta.ema(data.Close, length=20)data['EMAM'] = ta.ema(data.Close, length=100)data['EMAS'] = ta.ema(data.Close, length=150)

我们还需要创建我们的目标变量,即调整后的收盘价与股市指数的开盘价之间的差额。我们将把这个变量转移一天,使其成为一个前瞻性的预测。

data['Target'] = data['Adj Close'] - data.Opendata['Target'] = data['Target'].shift(-1)

为了训练一个二元分类模型,我们需要将目标变量转换为二元变量,其中1表示股市指数上升,0表示下降或没有变化。

data['TargetClass'] = [1 if data.Target[i] > 0 else 0 for i in range(len(data))]

最后,我们需要删除一些不必要的列,并删除任何包含缺失数据的行。

data['TargetNextClose'] = data['Adj Close'].shift(-1)

经过这些步骤,我们就有了准备好的数据集,其中包括技术指标、目标变量和第二天调整后的收盘价的目标变量。在下一节中,我们将探讨如何使用这个数据集来训练一个预测股市指数的LSTM模型。

缩放数据

缩放数据是一个必要的步骤,以确保输入的数据与输出的数据在一个类似的范围内。这一步对于保证模型能够正确地从数据中学习至关重要。如果我们不对数据进行缩放,模型可能无法正确地学习基本模式。

对于这个项目,我们使用scikit-learn库中的MinMaxScaler来扩展数据。MinMaxScaler将所有的输入特征缩放为0到1之间的数值。这个缩放器对这个项目很有效,因为它保持了数据点之间的相对差异。

from sklearn.preprocessing import MinMaxScalersc = MinMaxScaler(feature_range=(0, 1))data_set_scaled = sc.fit_transform(data_set)print(data_set_scaled)

上面的代码显示了如何使用MinMaxScaler来缩放数据。我们首先从scikit-learn库中导入MinMaxScaler。然后,我们创建一个缩放器对象,并将特征范围设置为0和1之间。最后,我们将缩放器对象拟合到数据集上,并将其转换为缩放版本。我们打印缩放后的数据,看看它看起来如何。

通过缩放数据,我们确保所有的输入特征都在一个相似的范围内,这有助于模型更有效地从数据中学习。

从提供给模型的数据中获得多个特征

在这一节中,我们将准备好我们的数据,将多个特征提供给我们的LSTM模型。我们将对所有特征使用相同的MinMaxScaler对象,然后我们将对数据进行相应的转换。

我们将取每个特征的30个先前值来预测当前值。因此,我们将为每个特征创建一个数组列表,然后沿第三维度堆叠。

X = []backcandles = 30print(data_set_scaled.shape[0])for j in range(8):    X.append([])    for i in range(backcandles, data_set_scaled.shape[0]):        X[j].append(data_set_scaled[i - backcandles:i, j])X = np.moveaxis(X, [0], [2])X, yi = np.array(X), np.array(data_set_scaled[backcandles:, -1])y = np.reshape(yi, (len(yi), 1))

在这里,我们创建了一个列表X ,以存储每个特征的先前值的数组。我们使用了一个循环来迭代所有的特征并创建各自的数组。我们使用了moveaxis() 函数来沿着第三维度堆叠数组。

我们还创建了两个数组,yyiy 包含我们模型的目标值,也就是收盘价。yi 是一个临时数组,存储所有时间步骤的收盘价,除了前30步。

接下来,我们将把我们的数据分成训练集和测试集。我们将使用80%的数据进行训练,剩下的20%用于测试。

splitlimit = int(len(X) * 0.8)X_train, X_test = X[:splitlimit], X[splitlimit:]y_train, y_test = y[:splitlimit], y[splitlimit:]

在这里,我们使用splitlimit 变量来确定我们将把数据分成训练集和测试集的索引。我们使用数组切片将我们的数据分成X_train,X_test,y_train, 和y_test

创建LSTM模型

现在我们将创建一个LSTM模型来预测股票价格。LSTM模型的输入将是过去股票价格和技术指标的序列,而输出将是第二天的股票价格。

LSTM模型的输入维度是8,这与我们使用的技术指标的数量相对应。隐藏维度是150,这意味着LSTM层有150个神经元。层数是1,这意味着我们只有一个LSTM层。输出维度为1,对应于预测的股票价格。

我们将使用平均平方误差(MSE)损失函数来衡量预测的股票价格和实际股票价格之间的差异。我们将使用亚当优化器在训练期间更新LSTM模型的权重。

class LSTM(nn.Module):    def __init__(self, input_dim, hidden_dim, num_layers, output_dim):        super(LSTM, self).__init__()        self.hidden_dim = hidden_dim        self.num_layers = num_layers        self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True)        self.fc1 = nn.Linear(hidden_dim, output_dim)    def forward(self, x):        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_dim).requires_grad_()        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_dim).requires_grad_()        out, (hn, cn) = self.lstm(x, (h0.detach(), c0.detach()))        out = self.fc1(out[:, -1, :])        return outinput_dim = 8hidden_dim = 150num_layers = 1output_dim = 1model = LSTM(input_dim, hidden_dim, num_layers, output_dim)criterion = nn.MSELoss()optimizer = optim.Adam(model.parameters(), lr=0.01)

在LSTM类的__init__ 方法中,我们用hidden_dim 神经元、num_layers 层和batch_first=True 参数来定义LSTM层,以表示输入张量以批量为第一维。我们还定义了一个有一个神经元的全连接层来输出预测的股票价格。在forward 方法中,我们用零初始化隐藏状态和单元状态,然后将输入张量传递给LSTM层。我们取LSTM层的最后一个输出,并将其传递给全连接层以得到预测的股票价格。

然后我们定义平均平方误差(MSE)损失函数和学习率为0.01的亚当优化器。

模型训练

现在,我们已经准备好了数据,并按比例创建了我们的LSTM模型,是时候使用训练数据来训练我们的模型了。

损失函数和优化器

在训练过程中,模型试图优化其参数以最小化损失函数。在这种情况下,我们将使用平均平方误差(MSE)作为我们的损失函数。

我们还将使用亚当优化器来更新我们模型的参数。Adam是一种优化算法,可用于根据训练数据迭代更新网络权重。

纪元

纪元指的是模型在训练数据中迭代的次数。每个历时包括通过模型的一次前向传递和一次后向传递(也称为反向传播)。

选择正确的历时数对于优化模型的性能至关重要。太少的历时可能导致欠拟合,而太多的历时可能导致过度拟合。一个常见的技术是监测验证损失,当它停止减少时,停止训练。

模型训练代码

为了训练我们的LSTM模型,我们将使用以下代码:

input_dim = 8hidden_dim = 150num_layers = 1output_dim = 1model = LSTM(input_dim, hidden_dim, num_layers, output_dim)criterion = nn.MSELoss()optimizer = optim.Adam(model.parameters(), lr=0.01)epochs = 30losses = []for epoch in range(epochs):    optimizer.zero_grad()    outputs = model(torch.Tensor(X_train))    loss = criterion(outputs, torch.Tensor(y_train))    loss.backward()    optimizer.step()    if epoch % 5 == 0:        print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch+1, epochs, loss.item()))

在上面的代码中,我们已经用输入和输出尺寸、隐藏尺寸和层数初始化了我们的模型。我们还定义了我们的损失函数和优化器。

在训练循环中,我们将历时的数量设置为30,并跟踪每个历时的损失。在每个历时中,我们通过模型传递训练数据并计算损失。然后我们使用反向传播来计算梯度,并使用优化器更新参数。我们还每隔5个历时打印损失,以监测训练进度。

运行训练循环后,我们将有一个训练好的模型,可以用来预测股票价格。

模型评估

训练完模型后,是时候评估它在测试集上的表现了。要做到这一点,我们需要将我们的numpy数组转换为PyTorch张量,并在测试集上进行预测。

# Convert numpy arrays to PyTorch tensorsX_test_tensor = torch.Tensor(X_test)y_test_tensor = torch.Tensor(y_test)# Make predictions on the test setwith torch.no_grad():    y_pred_tensor = model(X_test_tensor)    y_pred = y_pred_tensor.cpu().numpy()

我们首先使用torch.Tensor() 方法将X_testy_test 转换为PyTorch张量。然后,我们使用训练好的模型对测试集进行预测。我们将其包裹在torch.no_grad() ,以确保在预测过程中不计算梯度。最后,我们使用cpu().numpy() 方法将预测值转换回一个小数组。

现在我们有了预测值,我们可以使用诸如平均平方误差(MSE)或根平均平方误差(RMSE)等指标来评估我们模型的性能。这些指标量化了预测值和实际值之间的差异。

mse = mean_squared_error(y_test, y_pred)rmse = np.sqrt(mse)print('MSE: {:.4f}, RMSE: {:.4f}'.format(mse, rmse))

在这里,我们使用scikit-learn的mean_squared_error 函数来计算MSE,然后取其平方根来得到RMSE。较低的MSE和RMSE值表明该模型的性能较好。

请注意,评估模型的性能是很重要的,以确保它不会过度拟合或不足拟合数据。对模型的超参数进行微调也很关键,如隐藏维数和层数,以获得最佳性能。

绘制结果

在对模型进行评估之后,是时候将结果可视化了。我们可以使用Matplotlib来绘制测试集上的实际和预测的股票价格

# Plot the resultsplt.figure(figsize=(16,8))plt.plot(y_test, color='black', label='Test')plt.plot(y_pred, color='green', label='Pred')plt.legend()plt.show()

这段代码创建了一个黑色的实际股票价格和绿色的预测股票价格的图。plt.figure(figsize=(16,8)) 设置了绘图的大小,plt.plot(y_test, color='black', label='Test') 绘制了实际的股票价格,plt.plot(y_pred, color='green', label='Pred') 绘制了预测的股票价格。最后,plt.legend() 为该图添加了一个图例。

该图显示,该模型已经学会预测股票价格的趋势。然而,它并不完美,预测价格和实际价格之间存在一些差异。

结论

总之,我们已经看到如何使用LSTM模型来预测股票价格。我们首先准备了数据,然后对其进行缩放,以确保输入数据与输出数据在一个类似的范围内。然后,我们创建了LSTM模型,在训练数据上进行训练,并在测试数据上评估其性能。最后,我们绘制了结果,看到该模型在预测股票价格方面做得很好。

需要注意的是,虽然LSTM模型是预测股票价格的有力工具,但它们并不完美,永远无法完全准确地预测未来。然而,通过使用历史数据并将其纳入预测模型,我们可以做出更明智的投资决策。

我希望你认为这篇文章对你有帮助,并且有参考价值。请随时关注我,了解更多关于机器学习和数据科学的文章。不要忘记点赞和评论,让我知道你的想法。

你可以在GitHub仓库中找到这个项目的完整代码(LSTM.py):Pouyaexe/StockProphet: