灵活的多变量预测神经网络
2026/3/19 21:10:02 网站建设 项目流程

原文:towardsdatascience.com/neural-networks-for-flexible-multivariate-forecasting-82194d6cca0f

预测多个时间序列可以迅速变得复杂;传统方法要么需要每个序列一个单独的模型(即 SARIMA),要么所有序列都相关(即 VARMA)。神经网络提供了一种灵活的方法,无论序列相关性如何,都可以使用单个模型进行多序列预测。

此外,这种方法允许轻松地纳入外生变量,并可以预测未来的多个时间步,从而产生一个强大的通用解决方案,在各种情况下表现良好。

在这篇文章中,我们将展示如何执行数据窗口操作,将我们的数据从时间序列转换为监督学习格式,适用于单变量和多变量时间序列。一旦我们的数据被转换,我们将展示如何训练深度神经网络和 LSTM 进行多变量预测。

检查我们的数据

我们将使用一个数据集,该数据集捕捉了 2013 年至 2016 年间德里印度的每日平均温度和湿度。这些数据可在 Kaggle 上找到,并许可在CC0:公共领域下使用,这使得它非常适合我们的项目。

以表格格式显示数据,我们看到我们有 3 列,其中 1 列包含测量日期时间,每列分别对应‘meantemp’和‘humidity’。平均温度和湿度将形成我们想要预测的两个时间序列。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/fa6bf2daf8adeeb87ff4f6393471fa8d.png

图 1:基础数据集

我们可以绘制这些数据,如图 2 所示,并看到两个数据集的尺度相当一致,并且似乎存在某种反向相关性,即较高的平均温度与较低的湿度相关。

# Create tracesfig=go.Figure()forfactor_levelinids:# Adding plot of original_dffig.add_trace(go.Scatter(x=formatted_df['Date'],y=formatted_df[factor_level],mode='lines',name=factor_level))fig.update_layout(title="Temp and Humidity over Time, Delhi India")

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/eaf68f9fa8434c65f7fbd1dedd3d90f5.png

图 2:随时间变化的温度和湿度

数据窗口

现在是这种方法中最困难的部分;确保使用窗口方法我们的数据处于正确的格式。我们面临的问题是,我们需要将单变量时间序列 Y 转换为两个变量 X1 和 Y1,其中 X1 中的每一行都包含 Y1 中相应行之前的 n 个时间步。在这个例子中,n 是我们的窗口大小,数据转换可以在图 1 中看到。

如果我们的初始 Y 数据集有 10 个点,我们将窗口大小 n 设置为 2,那么我们的输出 X1 和 Y1 的维度将分别为(8 x 2)和(8 x 1)。有了这两个新变量,我们可以拟合一个普通回归模型,将每个 Y1 的值作为前两个值的函数来建模。

例如,在图 3 中,我们可以看到,窗口大小为 2 时,我们的第一个 Y 值将是初始 Y 序列中的第 3 个索引,并且它将依赖于 Y 中的索引 1 和 2 的值。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/530f1fe328c353801596d822b492c3fc.png

图 3:一元窗口转换

如果我们感兴趣的不是单一的时间序列,而是预测多个时间序列,我们可以执行完全相同的过程,并将每个数据集按图 2 所示连接起来。在下面的图中,我们可以看到有两个初始时间序列 Y1 和 Y2,它们的长度相同,我们再次选择窗口大小为 2 来进行转换。一旦每个序列像一元示例中那样被转换,我们只需将它们连接起来,就得到了 X*T 为(t-2 x 2n)和 Y_T 为(t-n x 2)。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/dd41a26c4b278a0bdc271cd1dc5f93a2.png

图 4:多元窗口转换

如果我们只有一个想要预测的时间序列和一个外生序列,我们可以在图 2 中进行相同的转换,但我们的 Y_T 将只有一列而不是两列。例如,如果我们想预测空气质量作为响应变量,并且有一个与响应变量相关的外生变量,如通勤里程,但我们不感兴趣进行预测。

应用数据转换

现在我们将数据窗口化过程应用到我们的数据集上。在这种情况下,我们的基础数据集有 2 列,我们想要预测的标签为‘meantemp’和‘humidity’,以及一个日期列,如图 1 所示。

我们首先将应用 Min-Max 缩放器,以确保每一列都在相同的尺度上,并提高模型性能。与传统的 ARIMA 过程不同,神经网络不需要数据集是平稳的来进行预测,然而标准化和归一化已被证明可以显著提高性能[3]。

# creating scalarscaler=MinMaxScaler(feature_range=(0,1))# normalizing target columnsforcolinids:formatted_df[col]=scaler.fit_transform(formatted_df[[col]])formatted_df.head(10)

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/bab2933472190375f66803358a81d1c6.png

图 5:缩放数据集

接下来,我们将我们的数据集分为测试集和训练集,我们的数据已经按日期升序排序,因此我们可以将数据集的前部分作为我们的训练集,后部分作为测试集。

注意:当处理时间序列数据时,确保数据按时间排序并分割是至关重要的,这样我们就不会有未来数据泄露到过去。

# setting test/train ratiototal_observations=len(formatted_df)train_ratio=0.7# performing time based test/train splittrain_df=formatted_df[:int(total_observations*train_ratio)]test_df=formatted_df[int(total_observations*train_ratio):]print(len(train_df),len(test_df))

最后,我们需要将我们的数据从标准时间序列格式转换为窗口格式,如图 3 和图 4 所示。为此,我们定义了 2 个辅助函数,convert_to_supervised,它将一维时间序列转换为窗口格式,以及 rename_dataframe_supervised,它更新列名为 T-1,T-2,…,T-n。

defconvert_to_supervised(data,window_size=1,forecast_size=1,dropnan=True):'''Converts a 1d time series dataset into a 2d supervised learning format'''df=pd.DataFrame(data)cols=list()# Training sequence: t-window_size, ..., t-1foriinrange(window_size,0,-1):cols.append(df.shift(i))# Forecast sequence: (t, t+1, ... t+forecast_size)foriinrange(0,forecast_size):cols.append(df.shift(-i))# Concatenating columns togetheragg=pd.concat(cols,axis=1)# drop rows with NaN valuesifdropnan:agg.dropna(inplace=True)returnagg.valuesdefrename_dataframe_supervised(data_df,state_name=""):'''Takes a dataframe with column names 1-x, relables them as t through t - x and returns. Used to illustrate how the timeseries to supervised conversion works'''# Renaming columns in dataframemax_col=max(data_df.columns.astype(int))forcolindata_df.columns:ifint(col)==max_col:data_df.rename(columns={col:"t"},inplace=True)else:data_df.rename(columns={col:"{state}t - {val}".format(state=state_name,val=max_col-col)},inplace=True)returndata_df

在定义了辅助函数之后,我们现在可以通过迭代每个序列(在这种情况下是‘meantemp’和‘humidity’)来转换我们的数据,将序列转换为监督格式,重命名它,然后将它连接到一个 numpy 数组。

timesteps=6features=len(ids)count=0# Storing resultstrain_x_all,test_x_all=np.array([]),np.array([])train_y_all,test_y_all=np.array([]),np.array([])# Iterating through each series of interestforvalinids:train_vals,test_vals=train_df[val].values,test_df[val].values# Transforming to time seriestrain_data,test_data=convert_to_supervised(train_vals,window_size=timesteps),convert_to_supervised(test_vals,window_size=timesteps)# Converting to dataframetrain_df_sup,test_df_sup=rename_dataframe_supervised(pd.DataFrame(train_data)),rename_dataframe_supervised(pd.DataFrame(test_data))# Separating x and ytrain_y,train_x=train_df_sup['t'].to_numpy(),train_df_sup.drop(columns=['t']).to_numpy()test_y,test_x=test_df_sup['t'].to_numpy(),test_df_sup.drop(columns=['t']).to_numpy()ifcount==0:train_x_all=train_x train_y_all=train_y test_x_all=test_x test_y_all=test_yelse:train_x_all=np.concatenate((train_x_all,train_x),axis=1)train_y_all=np.stack((train_y_all,train_y),axis=0)test_x_all=np.concatenate((test_x_all,test_x),axis=1)test_y_all=np.stack((test_y_all,test_y),axis=0)count+=1train_y_all=train_y_all.T test_y_all=test_y_all.Tprint('Y-Train Shape: ',train_y_all.shape)print('X-Train Shape: ',train_x_all.shape)

在执行上述转换后,我们想要通过检查 X/Y 训练集来验证我们的数据是否处于正确的格式。我们的原始数据集有 1462 个观测值,使用其中的 70%作为训练集,我们预计会有 1023 个训练观测值,但由于我们的窗口大小,我们会丢失 6 个。因此,我们的新训练集应该有 1017 行,Y 集和 X 集各有 2 和 12 列。这正是我们在图 4 中看到的内容,因此我们可以继续。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e77f40a5037ed241705bff02d0c2ad6f.png

图 6:输出数据维度

在我们继续进行模型训练之前,我们需要进行最后一次数据转换。我们一直在以二维形式重塑我们的数据,然而我们的神经网络期望 X 是一个三维数组,其中第三个维度是预测到未来的时间步数。在这种情况下,我们只对预测一个时间步到未来感兴趣,因此我们可以将我们的数据框重塑如下:(1017 x 12) -> (1017 x 1 x 12)。我们的最终 X 格式将是一个三维 numpy 数组,维度为[行(观测值)x 时间步 x 列(特征)]。

现在复杂的部分已经完成,我们准备进入建模阶段!

# need data formatted as [rows x timesteps into future x columns]train_x_all=np.reshape(train_x_all,(train_x_all.shape[0],1,train_x_all.shape[1]))test_x_all=np.reshape(test_x_all,(test_x_all.shape[0],1,test_x_all.shape[1]))# checking shapeprint('Train X shape:',train_x_all.shape)print('Test X shape:',test_x_all.shape)

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6919d319f36b1028d87926921bb57cf3.png

图 7:最终数据形状

开发我们的第一个模型:深度神经网络

现在我们已经将数据转换成所需的窗口格式,我们准备定义和训练我们的第一个模型。首先,我们将使用一个基本的深度神经网络,具有两个隐藏层。与假设线性关系的传统方法(如 ARIMA/SARIMA)不同,深度神经网络可以模拟非线性时间序列,这可以根据数据集具有显著的优势[1]。

因为我们将比较多种模型类型,所以我们的第一步是定义一个 train_and_predict 函数,如下所示,这个函数接受一个模型,编译并拟合它,然后进行预测并返回结果。

要使用这个模型与深度神经网络,我们定义我们的模型在每个密集隐藏层中使用 16 个神经元,并指定 relu 激活函数。然后,我们将模型传递给我们的训练和预测函数,该函数返回结果。

由于我们的神经网络较小,我们可以在图 8 的插图中展示它。因为我们选择了窗口大小为 6,并且有两个序列,所以我们将有总共 12 个输入,由输入层的 12 个神经元表示,然后我们有我们的 2 个密集隐藏层,最后是我们的两个输出,每个序列一个。

这个网络的一个重要特征是每个输入神经元都与第一隐藏层中的每个神经元相连,这意味着我们的模型实际上正在使用 Series B 的数据来帮助预测 Series A,反之亦然。这是使用统一模型而不是单独模型的一个显著优势,如果两个序列之间存在关系,我们可以捕捉到这一点并利用它来提高我们的整体预测性能。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e5394b69bb7677148598a662e231e362.png

图 8:DNN 示例

定义我们的模型:

# defining DNN modelunits=64model_dnn=Sequential()model_dnn.add(Dense(units=units,activation='relu'))model_dnn.add(Dense(units=units,activation='relu'))model_dnn.add(Dense(features))epochs=100# training model and returning predictionstrainPredict_dnn,testPredict_dnn,trainY,testY=train_and_predict(model_dnn,train_x_all,test_x_all,train_y_all,test_y_all,epochs)

定义我们的训练和预测函数,我们将模型传递给它们:

deftrain_and_predict(model,train_x_all,test_x_all,train_y_all,test_y_all,epochs):# compiling and fitting modelmodel.compile(loss='mean_absolute_error',optimizer='adam')model.fit(train_x_all,train_y_all,epochs=epochs,batch_size=1,verbose=3)# make predictionstrainPredict=model.predict(train_x_all).reshape(train_y_all.shape[0],features)testPredict=model.predict(test_x_all).reshape(test_y_all.shape[0],features)# inverting predictions back to initial scaletrainPredict=scaler.inverse_transform(trainPredict)trainY=scaler.inverse_transform(train_y_all)testPredict=scaler.inverse_transform(testPredict)testY=scaler.inverse_transform(test_y_all)returntrainPredict,testPredict,trainY,testY

现在我们已经成功训练了我们的深度神经网络并使用它进行预测,但我们需要一个方法来评估其性能。为此,我们定义了一个 plot_comparison 函数,它接受我们的预测和真实标签,然后绘制每个预测值与实际序列的对比图,并计算每个序列的均方根误差(MAPE)。

defplot_comparison(train_pred,test_pred,train_y,test_y,dates,model_type):factor_levels=train_pred.shape[1]fig=make_subplots(rows=factor_levels,cols=1,subplot_titles=("Plot 1","Plot 2"))mapes=[]# iterating through factor levelsforlevelinrange(factor_levels):fig.layout.annotations[level].update(text="Updated Plot 1")# defining dates for x axistrain_dates=dates[:train_pred.shape[0]]test_dates=dates[-test_pred.shape[0]:]fig.append_trace(go.Scatter(x=train_dates,y=train_y[:,level],mode='lines',name='Actual-Train'),row=level+1,col=1)# test setfig.append_trace(go.Scatter(x=test_dates,y=test_y[:,level],mode='lines',name='Actual-Test'),row=level+1,col=1)# train predfig.append_trace(go.Scatter(x=train_dates,y=train_pred[:,level],mode='markers',name='Pred-Train'),row=level+1,col=1)# test predfig.append_trace(go.Scatter(x=test_dates,y=test_pred[:,level],mode='markers',name='Pred-Test'),row=level+1,col=1)# calculating mape on test set and updating titlemape=round(mean_absolute_percentage_error(test_y[:,level],test_pred[:,level]),4)fig.layout.annotations[level].update(text="Series {series}, Test MAPE, {mape}".format(mape=mape,series=level+1))mapes.append(mape)fig.update_layout(height=600,width=600,title_text="{model_type}, Forecast By Series, Avg MAPE {mape}".format(model_type=model_type,mape=round(sum(mapes)/len(mapes),4)))fig.show()

现在我们可以调用我们的 plot_comparison 函数来评估深度神经网络的表现。

# graphing datadates=formatted_df['Date'].sort_values(ascending=True).drop_duplicates().to_list()model_type='DNN'plot_comparison(trainPredict_dnn,testPredict_dnn,trainY,testY,dates,model_type)

在图 9 中,我们可以看到这次评估的结果,显示了每个序列(‘meantemp’和‘humidity’)的测试集和训练集的预测值与实际值。我们可以看到,我们模型的预测值是实际值的合理近似,两个序列的整体平均绝对百分比误差(MAPE)为 0.0847。

恭喜,我们现在已经训练并评估了我们的第一个神经网络预测器!

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/961026ceed4a1f847509b58d658ca7b0.png

图 9:DNN 预测性能

RNN 和 LSTM 以及概述

现在我们已经训练并评估了我们的第一个模型,但想看看我们是否可以改进;进入 LSTM。首先是一些快速背景,什么是 LSTM(长短期记忆)以及为什么它可能优于基本的前馈神经网络?

LSTM 是一种递归神经网络(RNN),一种将信息反馈到自身的网络,这使得它具有一种“记忆”形式,这在数据顺序重要的问题中可能很有用;如时间序列预测、自然语言处理、文本翻译等。基本结构如图 10 所示,如果我们展开这个结构,我们得到右侧的结构,其中时间 0 的输入被馈送到时间 1,然后被修改并发送到时间 2,以此类推。更多信息可以在这里找到:www.ibm.com/topics/recurrent-neural-networks

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/dbfb3dbf9a30b0ce623b8584bf5f27fe.png

图 10:递归神经网络(RNN)结构,作者:fdeloche – 自有作品,CC BY-SA 4.0

虽然这听起来很棒,但我们想训练一个 LSTM 而不是 RNN,因为 RNN 可能会因为图 10 中显示的反馈循环而遭受梯度消失/爆炸问题。想象一下,有一些初始输入 X0,这个输入将在每个递归步骤中被某个权重 W 缩放。这将导致时间 t 的输出值等于图 11 中显示的公式。例如,如果我们有 10 个递归步骤,我们的权重是 2,那么第 10 个神经元的输入将是 X0 * 2¹⁰或 1024 * X0,这将压倒任何 Xt 值。或者,如果权重小于 1,梯度可能会消失,因为小于 1 的值的大幂几乎等于 0。

重要的是,梯度消失和梯度爆炸问题可能会导致过去的事件在我们的模型中没有权重,或者超过任何当前信号。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/119032a8b56827e59e567fdecb006955.png

图 11:RNN 梯度函数

LSTM 通过拥有两个记忆路径来设计以克服这个问题,一个用于短期,一个用于长期。这使得 LSTM 比基本的 RNN 更适合时间序列预测,然而不幸的是,这也使得基本的 LSTM 单元比 RNN 的单元复杂得多。

在非常高的层面上,LSTM 单元可以在图 12 中看到,顶部路径输出的 Ct 代表长期记忆。你会注意到长期记忆项有一个乘法元素和一个加法元素来修改值,但是没有权重来避免我们与基本 RNN 看到的梯度消失/爆炸问题。

图 12 底部路径输出的 Ht 代表我们的短期记忆,与我们的长期记忆不同,它确实有可以修改它的权重。然而,因为这个路径仅用于短期,并且不会多次复合,所以我们不应该看到梯度爆炸或消失。

短期和长期记忆之间还有更多组件和交互作用,这些超出了本文的范围。如果感兴趣,我最喜欢的 LSTM 结构解释来自 StatQuest,www.youtube.com/watch?v=YCzL96nL7j0。对我们来说,重要的是要知道,LSTM 被设计来克服 RNN 的不足,并且在许多应用中已被证明优于传统的 ARIMA 方法和深度神经网络 [2]。

<…/Images/6f98687fab04d389a2ab69c2cf81b5ae.png>

图 12:LSTM 单元结构,由 Guillaume Chevalier 绘制,授权于 Creative Commons [4]。

训练 LSTM

现在我们已经简要概述了 LSTM 背后的理论,我们可以继续实施一个 LSTM,幸运的是,由于 tensorflow 的魔力,这要容易得多。

我们下面定义了一个简单的顺序模型,并添加了一个 LSTM 层和一个 Dense 层来提供输出。在这种情况下,我们将我们的时间步长定义为 1,因为我们只对进行 1 个时间步长的预测感兴趣,并将特征定义为 2,因为我们对预测的两个序列感兴趣。最后,我们将我们的模型传递给与 DNN 使用的相同的 train_and_predict 函数。

# defining lstm modelunits=8model_lstm=Sequential()model_lstm.add(LSTM(units,input_shape=(1,int(timesteps*features))))model_lstm.add(Dense(features))epochs=100# training model and returning predictionstrainPredict_lstm,testPredict_lstm,trainY,testY=train_and_predict(model_lstm,train_x_all,test_x_all,train_y_all,test_y_all,epochs)

在做出我们的预测之后,我们想要检查结果并看看我们的 LSTM 相对于 DNN 的表现如何。为此,可以简单地调用之前使用的相同 plot_comparison 方法。这些结果如图 13 所示,与我们的深度神经网络类似,LSTM 预测紧密地反映了我们的实际值,我们可以看到我们的平均 MAPE 甚至更低 0.0812 比 0.0847。

我们的长短期记忆网络(LSTM)优于我们的深度神经网络(DNN)。

# graphing datadates=formatted_df['Date'].sort_values(ascending=True).drop_duplicates().to_list()model_type='LSTM'plot_comparison(trainPredict_lstm,testPredict_lstm,trainY,testY,dates,model_type)

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a3cff4a9deb22ce5a54a7e7dbbf0c683.png

图 13:LSTM 预测性能

总结

多变量时间序列预测可能很容易变成一项复杂的任务,因为不同的时间序列需要不同的模型,并且需要跟踪和维护这些模型。正如本文所示,神经网络可以提供一个简单的多输出解决方案,使得可以同时预测多个序列。这减少了我们需要训练的模型数量,并允许我们的模型在两个序列之间存在相关性时,使用一个序列来帮助预测另一个序列。

最终,没有灵丹妙药,你应该使用最适合你用例的模型,但神经网络可以是非常有用的工具,对于时间序列预测,应该在任何工具箱中。

引用:

  1. Casolaro, A.,Capone, V.,Iannuzzo, G.,& Camastra, F. (2023)。深度学习在时间序列预测中的应用:进展与开放问题。信息14(11),598。doi.org/10.3390/info14110598

  2. S. Siami-Namini,N. Tavakoli 和 A. Siami Namin,“ARIMA 和 LSTM 在时间序列预测中的比较”,2018 年第 17 届 IEEE 国际机器学习与应用会议(ICMLA),佛罗里达州奥兰多,2018 年,第 1394–1401 页,doi:10.1109/ICMLA.2018.00227。关键词:{时间序列分析;预测;预测模型;自回归过程;经济学;深度学习;数据模型;深度学习,长短期记忆(LSTM),自回归积分移动平均(ARIMA),预测,时间序列数据},

  3. Tawakuli, A.,Havers, B.,Gulisano, V.,Kaiser, D.,& Engel, T. (2024)。调查:时间序列数据预处理:调查和实证分析。工程研究杂志doi.org/10.1016/j.jer.2024.02.018

  4. 长短期记忆。 (2024 年 10 月 17 日)。在维基百科en.wikipedia.org/wiki/Long_short-term_memory

  5. 循环神经网络。 (2024, 10 月 24 日)。在维基百科en.wikipedia.org/wiki/Recurrent_neural_network

资源

  1. 数据来源:www.kaggle.com/datasets/sumanthvrao/daily-climate-time-series-data

  2. 本文的代码在 Github 上:github.com/pinstripezebra/forecasting/blob/main/Neural%20Network%20Forecast%20-%20Simple.ipynb

  3. Keras 中的 LSTM 实现:keras.io/api/layers/recurrent_layers/lstm/

  4. LSTM 的简要说明:colah.github.io/posts/2015-08-Understanding-LSTMs/

  5. 涵盖 LSTM 架构的 YouTube 视频:www.youtube.com/watch?v=YCzL96nL7j0

***图表和图形:*除非另有说明,所有图像均由作者提供。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询