乐趣区

关于深度学习:使用-LSTM-进行多变量时间序列预测

在本文中咱们将应用深度学习办法 (LSTM) 执行多元工夫序列预测。

咱们先来理解两个主题——

  • 什么是工夫序列剖析?
  • 什么是 LSTM?

工夫序列剖析:工夫序列示意基于工夫程序的一系列数据。它能够是秒、分钟、小时、天、周、月、年。将来的数据将取决于它以前的值。

在事实世界的案例中,咱们次要有两种类型的工夫序列剖析——

  • 单变量工夫序列
  • 多元工夫序列

对于单变量工夫序列数据,咱们将应用单列进行预测。

正如咱们所见,只有一列,因而行将到来的将来值将仅取决于它之前的值。

然而在多元工夫序列数据的状况下,将有不同类型的特征值并且指标数据将依赖于这些特色。

正如在图片中看到的,在多元变量中将有多个列来对目标值进行预测。(上图中“count”为目标值)

在下面的数据中,count 不仅取决于它以前的值,还取决于其余特色。因而,要预测行将到来的 count 值,咱们必须思考包含指标列在内的所有列来对目标值进行预测。

在执行多元工夫序列剖析时必须记住一件事,咱们须要应用多个特色预测以后的指标,让咱们通过一个例子来了解 –

在训练时,如果咱们应用 5 列 [feature1, feature2, feature3, feature4, target] 来训练模型,咱们须要为行将到来的预测日提供 4 列 [feature1, feature2, feature3, feature4]。

LSTM

本文中不打算具体探讨 LSTM。所以只提供一些简略的形容,如果你对 LSTM 没有太多的理解,能够参考咱们以前公布的文章。

LSTM 基本上是一个循环神经网络,可能解决长期依赖关系。

假如你在看一部电影。所以当电影中产生任何状况时,你都曾经晓得之前产生了什么,并且能够了解因为过来产生的事件所以才会有新的状况产生。RNN 也是以同样的形式工作,它们记住过来的信息并应用它来解决以后的输出。RNN 的问题是,因为突变隐没,它们不能记住长期依赖关系。因而为了防止长期依赖问题设计了 lstm。

当初咱们探讨了工夫序列预测和 LSTM 实践局部。让咱们开始编码。

让咱们首先导入进行预测所需的库

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM
from tensorflow.keras.layers import Dense, Dropout
from sklearn.preprocessing import MinMaxScaler
from keras.wrappers.scikit_learn import KerasRegressor
from sklearn.model_selection import GridSearchCV

加载数据,并查看输入 -

df=pd.read_csv("train.csv",parse_dates=["Date"],index_col=[0])
df.head()

df.tail()

当初让咱们花点工夫看看数据:csv 文件中蕴含了谷歌从 2001-01-25 到 2021-09-29 的股票数据,数据是依照天数频率的。

[如果您违心,您能够将频率转换为“B”[工作日]或“D”,因为咱们不会应用日期,我只是放弃它的现状。]

这里咱们试图预测“Open”列的将来值,因而“Open”是这里的指标列

让咱们看一下数据的形态

df.shape
(5203,5)

当初让咱们进行训练测试拆分。这里咱们不能打乱数据,因为在工夫序列中必须是程序的。

test_split=round(len(df)*0.20)
df_for_training=df[:-1041]
df_for_testing=df[-1041:]
print(df_for_training.shape)
print(df_for_testing.shape)

(4162, 5)
(1041, 5)

能够留神到数据范畴十分大,并且它们没有在雷同的范畴内缩放,因而为了防止预测谬误,让咱们先应用 MinMaxScaler 缩放数据。(也能够应用 StandardScaler)

scaler = MinMaxScaler(feature_range=(0,1))
df_for_training_scaled = scaler.fit_transform(df_for_training)
df_for_testing_scaled=scaler.transform(df_for_testing)
df_for_training_scaled

将数据拆分为 X 和 Y,这是最重要的局部,正确浏览每一个步骤。

def createXY(dataset,n_past):
    dataX = []
    dataY = []
    for i in range(n_past, len(dataset)):
            dataX.append(dataset[i - n_past:i, 0:dataset.shape[1]])
            dataY.append(dataset[i,0])
    return np.array(dataX),np.array(dataY)

trainX,trainY=createXY(df_for_training_scaled,30)
testX,testY=createXY(df_for_testing_scaled,30)

让咱们看看下面的代码中做了什么:

N_past 是咱们在预测下一个目标值时将在过来查看的步骤数。

这里应用 30,意味着将应用过来的 30 个值 (包含指标列在内的所有个性) 来预测第 31 个目标值。

因而,在 trainX 中咱们会有所有的特征值,而在 trainY 中咱们只有目标值。

让咱们合成 for 循环的每一部分

对于训练,dataset = df_for_training_scaled, n_past=30

当 i = 30:

data_X.addend (df_for_training_scaled[i – n_past:i, 0:df_for_training.shape[1]])

从 n_past 开始的范畴是 30,所以第一次数据范畴将是 -[30 – 30,30,0:5] 相当于 [0:30,0:5]

因而在 dataX 列表中,df_for_training_scaled[0:30,0:5]数组将第一次呈现。

当初, dataY.append(df_for_training_scaled[i,0])

i = 30,所以它将只取第 30 行开始的 open(因为在预测中,咱们只须要 open 列,所以列范畴仅为 0,示意 open 列)。

第一次在 dataY 列表中存储 df_for_training_scaled[30,0]值。

所以蕴含 5 列的前 30 行存储在 dataX 中,只有 open 列的第 31 行存储在 dataY 中。而后咱们将 dataX 和 dataY 列表转换为数组,它们以数组格局在 LSTM 中进行训练。

咱们来看看形态。

print("trainX Shape--",trainX.shape)
print("trainY Shape--",trainY.shape)

(4132, 30, 5)
(4132,)

print("testX Shape--",testX.shape)
print("testY Shape--",testY.shape)

(1011, 30, 5)
(1011,)

4132 是 trainX 中可用的数组总数,每个数组共有 30 行和 5 列,在每个数组的 trainY 中,咱们都有下一个目标值来训练模型。

让咱们看一下蕴含来自 trainX 的 (30,5) 数据的数组之一 和 trainX 数组的 trainY 值

print("trainX[0]-- \n",trainX[0])
print("trainY[0]--",trainY[0])

如果查看 trainX[1] 值,会发现到它与 trainX[0] 中的数据雷同(第一列除外),因为咱们将看到前 30 个来预测第 31 列,在第一次预测之后它会主动挪动 到第 2 列并取下一个 30 值来预测下一个目标值。

让咱们用一种简略的格局来解释这所有——

trainX — — →trainY

[0 : 30,0:5] → [30,0]

[1:31, 0:5] → [31,0]

[2:32,0:5] →[32,0]

像这样,每个数据都将保留在 trainX 和 trainY 中

当初让咱们训练模型,我应用 girdsearchCV 进行一些超参数调整以找到根底模型。

def build_model(optimizer):
    grid_model = Sequential()
    grid_model.add(LSTM(50,return_sequences=True,input_shape=(30,5)))
    grid_model.add(LSTM(50))
    grid_model.add(Dropout(0.2))
    grid_model.add(Dense(1))

grid_model.compile(loss = 'mse',optimizer = optimizer)
    return grid_modelgrid_model = KerasRegressor(build_fn=build_model,verbose=1,validation_data=(testX,testY))

parameters = {'batch_size' : [16,20],
              'epochs' : [8,10],
              'optimizer' : ['adam','Adadelta'] }

grid_search  = GridSearchCV(estimator = grid_model,
                            param_grid = parameters,
                            cv = 2)

如果你想为你的模型做更多的超参数调整,也能够增加更多的层。然而如果数据集十分大倡议减少 LSTM 模型中的期间和单位。

在第一个 LSTM 层中看到输出形态为 (30,5)。它来自 trainX 形态。(trainX.shape[1],trainX.shape[2]) → (30,5)

当初让咱们将模型拟合到 trainX 和 trainY 数据中。

grid_search = grid_search.fit(trainX,trainY)

因为进行了超参数搜寻,所以这将须要一些工夫来运行。

你能够看到损失会像这样缩小——

当初让咱们查看模型的最佳参数。

grid_search.best_params_

{‘batch_size’: 20,‘epochs’: 10,‘optimizer’:‘adam’}

将最佳模型保留在 my_model 变量中。

my_model=grid_search.best_estimator_.model

当初能够用测试数据集测试模型。

prediction=my_model.predict(testX)
print("prediction\n", prediction)
print("\nPrediction Shape-",prediction.shape)

testY 和 prediction 的长度是一样的。当初能够将 testY 与预测进行比拟。

然而咱们一开始就对数据进行了缩放,所以首先咱们必须做一些逆缩放过程。

scaler.inverse_transform(prediction)

报错了,这是因为在缩放数据时,咱们每行有 5 列,当初咱们只有 1 列是指标列。

所以咱们必须扭转形态来应用 inverse_transform

prediction_copies_array = np.repeat(prediction,5, axis=-1)

5 列值是类似的,它只是将单个预测列复制了 4 次。所以当初咱们有 5 列雷同的值。

prediction_copies_array.shape
(1011,5)

这样就能够应用 inverse_transform 函数。

pred=scaler.inverse_transform(np.reshape(prediction_copies_array,(len(prediction),5)))[:,0]

然而逆变换后的第一列是咱们须要的,所以咱们在最初应用了 → [:,0]。

当初将这个 pred 值与 testY 进行比拟,然而 testY 也是按比例缩放的,也须要应用与上述雷同的代码进行逆变换。

original_copies_array = np.repeat(testY,5, axis=-1)
original=scaler.inverse_transform(np.reshape(original_copies_array,(len(testY),5)))[:,0]

当初让咱们看一下预测值和原始值 →

print("Pred Values--" ,pred)
print("\nOriginal Values--" ,original)

最初绘制一个图来比照咱们的 pred 和原始数据。

plt.plot(original, color = 'red', label = 'Real Stock Price')
plt.plot(pred, color = 'blue', label = 'Predicted Stock Price')
plt.title('Stock Price Prediction')
plt.xlabel('Time')
plt.ylabel('Google Stock Price')
plt.legend()
plt.show()

看样子还不错,到目前为止,咱们训练了模型并用测试值查看了该模型。当初让咱们预测一些将来值。

从主 df 数据集中获取咱们在开始时加载的最初 30 个值[为什么是 30?因为这是咱们想要的过来值的数量,来预测第 31 个值]

df_30_days_past=df.iloc[-30:,:]
df_30_days_past.tail()

能够看到有包含指标列(“Open”)在内的所有列。当初让咱们预测将来的 30 个值。

在多元工夫序列预测中,须要通过应用不同的特色来预测单列,所以在进行预测时咱们须要应用特征值(指标列除外)来进行行将到来的预测。

这里咱们须要“High”、“Low”、“Close”、“Adj Close”列的行将到来的 30 个值来对“Open”列进行预测。

df_30_days_future=pd.read_csv("test.csv",parse_dates=["Date"],index_col=[0])
df_30_days_future

剔除“Open”列后,应用模型进行预测之前还须要做以下的操作:

缩放数据,因为删除了‘Open’列,在缩放它之前,增加一个所有值都为“0”的 Open 列。

缩放后,将将来数据中的“Open”列值替换为“nan”

当初附加 30 天旧值和 30 天新值(其中最初 30 个“关上”值是 nan)

df_30_days_future["Open"]=0
df_30_days_future=df_30_days_future[["Open","High","Low","Close","Adj Close"]]
old_scaled_array=scaler.transform(df_30_days_past)
new_scaled_array=scaler.transform(df_30_days_future)
new_scaled_df=pd.DataFrame(new_scaled_array)
new_scaled_df.iloc[:,0]=np.nan
full_df=pd.concat([pd.DataFrame(old_scaled_array),new_scaled_df]).reset_index().drop(["index"],axis=1)

full_df 形态是 (60,5),最初第一列有 30 个 nan 值。

要进行预测必须再次应用 for 循环,咱们在拆分 trainX 和 trainY 中的数据时所做的。然而这次咱们只有 X,没有 Y 值

full_df_scaled_array=full_df.values
all_data=[]
time_step=30
for i in range(time_step,len(full_df_scaled_array)):
    data_x=[]
    data_x.append(full_df_scaled_array[i-time_step :i , 0:full_df_scaled_array.shape[1]])
    data_x=np.array(data_x)
    prediction=my_model.predict(data_x)
    all_data.append(prediction)
    full_df.iloc[i,0]=prediction

对于第一个预测,有之前的 30 个值,当 for 循环第一次运行时它会查看前 30 个值并预测第 31 个“Open”数据。

当第二个 for 循环将尝试运行时,它将跳过第一行并尝试获取下 30 个值 [1:31]。这里会报错谬误因为 Open 列最初一行是“nan”,所以须要每次都用预测替换“nan”。

最初还须要对预测进行逆变换→

new_array=np.array(all_data)
new_array=new_array.reshape(-1,1)
prediction_copies_array = np.repeat(new_array,5, axis=-1)
y_pred_future_30_days = scaler.inverse_transform(np.reshape(prediction_copies_array,(len(new_array),5)))[:,0]
print(y_pred_future_30_days)

这样一个残缺的流程就曾经跑通了。

如果你想看残缺的代码,能够在这里查看:

https://www.overfit.cn/post/1…

作者:Sksujanislam

退出移动版