Python数据分析案例70——基于神经网络的时间序列预测(滞后性的效果,预测中存在的问题)

背景

这篇文章可以说是基于 现代的一些神经网络的方法去做时间序列预测的一个介绍科普,也可以说是一个各种模型对比的案例,但也会谈一谈自己做了这么久关于神经网络的时间序列预测的论文,其中一些常见的模式及它们存在的问题以及效果,还有在实际生产中去运用究竟会有什么问题?

时间序列预测的几种模式

首先我们要知道一个滑动窗口的概念,时间序列预测无非就是用之前的数据去预测未来的数据。与传统的机器学习的二维表格不同,时间序列最简单的模式就是单变量单步预测。即我们要预测一个变量,我们就用这个变量前几期的数据去预测未来下一期的数据,例如我们的滑动窗口大小为10,也就是说我们用这个变量 'Xt-10到Xt' 的数据,去预测' Xt+1' 的取值(这里就不写latex公式了反正应该也挺通俗易懂的)。也就是说,在单变量单步预测模型中,你的一个X向量就是 'Xt-10到Xt' 的数据 ,y标量就是 ' Xt+1' 的取值。然后我们可以不停的滑动这个窗口,得到很多组Xy,就可以去训练模型了。(注意这里的X是三维数据,形状为(n,t,p), n为样本量,t是时间步长,也就是滑动窗口大小,p是特征数量,这里单变量就是1,y形状为(n,1)的向量 )

现代神经网络基本都是这样做的,所以这导致了一个问题,我们要预测' Xt+2' 就没有办法了,因为我们不知道' Xt+1' 的真实值。有的人说可以吧 'Xt+1' 的预测值放进去继续预测,确实,传统模型都是这样做的,但是这样势必又会导致另外一个问题,即误差的累计,因为 'Xt+1' 的预测值不是真实值,所以有误差,预测出来的'Xt+2'就是在有误差的数据上预测出来的,那效果肯定会更差,并且时间步长越久,误差越夸张。 根据经验,这种方法预测出来到三步,五步以后,基本都没法看,和真实值差距是天涯海角。(传统的ARIMA都是这样做的,所以基本上很多人用这个模型都会发现他们预测出来的数据就是一条直线..........)

所以这就衍生出了一个问题,那我们要做多步预测该怎么办?这个也是我们要谈到的神经网络的第二种预测模式,即单变量多步预测。既然我们因为不知道'Xt+1'所以没办法预测'Xt+2',那我们干脆直接用 'Xt-10到Xt' 的数据,去预测出来 'Xt+1 到 Xt+5'(假设我们要预测5步),很多计量经济学和传统统计学的同学要炸毛了,因为在传统的统计学模式基本看不到这种y取值是多个的情况,在无数的传统统计学和计量的模型以及一些基础的机器学习模型,无论是分类还是回归问题,我们的y都是一个标量,都是一个具体的取值,一个数字,而不是一个向量。向量也能作为y? 在神经网络里面是可以的,毕竟深度学习连图片,连音频,连文本, 视频都可以作为y,一个向量作为y是很正常的事情,大部分同学做模型的时候连数据的形状都没弄清楚,其实这是一个很重要的认知,你一定要认清楚你的Xy是什么形状,你才能去用对应的模型。

回到我们的单变量多步预测模型上来,我们的X还是之前的X,形状为(n,t,p),但是我们的y却不再是一个向量,而是一个矩阵形状为(n,T) ,T为你要预测的时间的步长,多步神经网络有2种方法去训练,即直接要预测几步, 你的输出层就用几个神经元:

outputs = Dense(5, activation='linear')(pooled_output)  # 输出形状: (n, 5)

pooled_output是上一层经过池化 或者是循环神经网络最后一刻的状态,有可能是经过展开的mlp,反正就是将输入的X从3维变为2维度之后的情况,输入的pooled_output是二维数据,这样输出的就是一个(n,5)的向量,即n个样本,每个样本都预测了5步。这种方法直观,我目前基本上做的多步预测都是用这个方法。

也可以使用另外一种方法训练:使用 TimeDistributed

lstm_output = LSTM(16, return_sequences=True)(inputs)  # 输出形状: (n, 10, 16)
# 使用 TimeDistributed
outputs = TimeDistributed(Dense(1, activation='linear'))(lstm_output)  # 输出形状: (n, 10, 1)

可以看到,这里的滑动窗口的时间步长和预测的步长就得是一样的。输入的lstm_output还是三维进去,三维出来,然后直接直接转为二维y矩阵了。(y就一个特征所以可以直接reshape为(n,10))。所以这种方法不能灵活的控制你的输入滑动窗口的步长跟你要预测的时间步长,必须一样。至于精准度和上面的方法比起来,我也没试过哪一种好。

讲完了上面的单变量的单步和多步预测,下面另外两种模式也更好理解,也无非就是X特征变多了,是多变量的单步跟多变量的多步预测

其实思路和训练代码上没有太多差异,X形状还是三维,形状为(n,t,p), 这里的p就不是1了,是2以上。单步预测的y还是(n,1),多步还是(n,T), 只是在构建我们的训练集和测试集的时候,需要注意一下这个多维度的时候,构建Xy去对数据运用切片索引的问题。

按道理来说,以前基于树模型的二维表格的机器学习都是变量越多越好,但是在如今的这个循环神经网络里面并不是特征p越多越好。时间序列很看你上一时刻的这些变量的情况,有的时间序列特征噪音会特别多,而且会突变(例如之前的可能取值都是1,后面突然一下变成100),会对我们要预测的变量造成严重的干扰,这个可能也得进行一定的选择。

还有一些别的模式,例如用'Xt-10到Xt' 去预测 'Xt+8' 或者是 'Xt+9', 这种美言自称是多步预测的模式,我就不多说了,这本质也是单步预测..............都是一个换汤不换药的概念。(反正在自己的论文里面咋吹都是合理的)

神经网络预测时间序列预测的一些问题

常见的时间序列预测的模式都讲完了,下面再来讲一下。这些模式会有哪些问题?

首先就是很多新手刚开始做出一个模型,预测值和真实值一对比就会有一个:滞后性的困惑

 

所谓滞后性的困惑就是如上图一样,我们的预测值看起来就像真实值往后挪了一个时间单位。可能我这个图有点密集,画的太小看不清楚,但整体而言,滞后性的困惑 就是我们预测出来的'Xt+1'的值好像就是上一时刻Xt的值,看起来我们的预测无非就是把真实值进行了滞后一期罢了。

很多人不懂为什么,我刚开始学的时候也有这个困惑,但是后来做多了也就没管了,反正大家都这样干,我也就这样干吧。但现在我大概明白了里面的这个原理,和大家讲一讲。

首先一些正常的时间序列,例如股票价格,空气质量,人体的血糖浓度......都是具有强烈的自相关的序列数据,这点是不可否认的,即我们的'Xt+1'跟'Xt'肯定是具有高度的相关性系数,并且是线性相关。就例如股价你无论再怎么变,你肯定也是昨天价格的±10%的区间,不可能离昨天的值差距太远太离谱。所以就造成了这种今天的价格跟昨天的价格是强烈的线性正相关的关系。

我们都知道随机过程中经典的醉汉问题,即一个醉汉如果在开始坐标为(0,0)的二维平面上开始进行随机游走,每1秒钟走一步,那么走了十几个小时或者是一天之后,我们在哪里找到这个醉汉的概率最大?

答案是原点,就是(0,0)的位置,因为是随机游走,在随机游走中,醉汉的每一步移动是独立的,且在每个方向上的移动是随机的。假设醉汉在二维平面上每秒钟移动一步,每一步在x轴和y轴上的移动是独立的,且在每个方向上(左、右、上、下)的概率相等。他的动向的分布肯定是符合正态的,符合均值为零的。

所以再回到我们的时间序列预测问题。我们要预测下一时间的Xt+1时间的取值,也就是去找可能概率最大的取值,其可能性在哪里呢?那么就在上 Xt 上,也就是Xt的取值是作为'Xt+1'的概率最大,所以模型们都很聪明——他们基本都学到了概率最大的情况,也就是:直接把昨天的值稍微修改那么一点点,作为今天的预测值就好了,这也是为什么我们用循环神经网络做这种单步预测的时候,发现这种有滞后性的困惑的问题。本质就是时间序列的跟上一时刻的强烈的自相关带来的概率化最优的问题。

当然实际上模型肯定不会直接用上一个的取值完全相同作为下一时间的预测值,因为有些时间序列的动向变化肯定也不会是完全的概率相同的随机过程。他们神经网络模型肯定还是会根据数据的一些模式,例如季节性,波动性,趋势性学到一点点的修改。但是这种修改到底是好还是坏呢?我们肯定是用一些误差指标来进行衡量。然后进行一个对比。

这就引出了第二个问题,即 神经网络预测出来的都是类似滞后一期的数据。那我能不能构建一个基准模型——即MA(1)模型,直接用Xt 的值作为'Xt+1'的预测值,然后在整个样本上计算误差评价指标和神经网络预测出来的预测值进行一个对比,我们来看看效果到底行不行。

为什么叫MA(1)模型,MA大家都知道是移动平均模型,即用前n天的真实值平均一下作为每天的预测值。如果我们极端一点,取n等于1, 那么就是用前一天的平均值作为下一天的预测值,也就是说直接用今天的真实值作为下一天的预测值,我们就可以对比——我们花费了无数时间精力学习到构建出来的循环神经网络模型(整了一堆乱七八糟的卷积门控注意力机制transformer层)和我们最简单的MA(1)模型对比到底,到底能够'强多少'?

这就是今天这个案例的目的。


数据介绍

本次用的数据都是之前的案例常用的一些时间序列,我懒得找新的了,就主要是这5个:

 

每个数据都是两列,一列时间,一列是它的取值,当然它们的时间频率不一样,但是无所谓,神经网络也不管你是日度还是月度还是季度的,还是甚至是秒级的,反正都是一样训练。

本次下面演示的就用石油价格这个序列进行划分训练集和测试集,构建3维的数据张量,进行单变量单步预测。

对比如下的神经网络模型:

["Transformer", "CNN+BiLSTM", "BiGRU-Attention","BiLSTM-Attention", "BiGRU", "BiLSTM", "TCN", "GRU", "CNN", "LSTM","RNN","MLP", ]

在目前大量外行还在用lstm这种模型发论文的时候,我上面就随便这几个模型都可以写一篇普通期刊的论文,再缝合一点模态分解,优化算法或者损失函数都可以发SCI了。并且他们的构建很简单,我全部都统一化和模块化了。

当然,需要本次演示的数据和全部代码文件的同学还是可以参考:神经网络时间序列

代码实现

导入包,我们用keras框架,默认TensorFlow后端。3.0以上可以用pytorch作为后端,API接口类似。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt 
import seaborn as sns
import os
import time
from datetime import datetime
import random as rn
import scipy.special as sc_special
plt.rcParams ['font.sans-serif'] ='SimHei'               #显示中文
plt.rcParams ['axes.unicode_minus']=False               #显示负号from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error,r2_scoreimport tensorflow as tf
import keras
import keras.backend as K
from keras.models import Model, Sequential
from keras.layers import Dense,Input, Dropout, Flatten,MaxPooling1D,Conv1D,SimpleRNN,LSTM,GRU,GlobalMaxPooling1D,Layer
from keras.layers import BatchNormalization,GlobalAveragePooling1D,MultiHeadAttention,AveragePooling1D,Bidirectional,LayerNormalization
from keras.callbacks import EarlyStopping

这个框架的代码做了太多次了,以前的文章也有非常多,所以说我也不过多的写解释了,下面就简单罗列一下这个过程。都是为了我们要展示一下最终的结果对比罢了。

读取数据

data0=pd.read_excel('时间序列测试数据.xlsx',parse_dates=['date'],sheet_name=1).set_index('date').ffill()
data0.head()

sheet_name=1,因为我的石油价格是装在第二个sheet里面,所以我们用等于一,然后把data设置为时间索引。最后的.ffill 是为了防止数据的缺失值,就用前一个值进行填充。

展示数据折线图

data0.plot(figsize=(12,3))

构建训练集和测试集

单变量单步模型,构建X和y的函数:

def build_sequences(text, window_size=24):#text:list of capacityx, y = [],[]for i in range(len(text) - window_size):sequence = text[i:i+window_size]target = text[i+window_size]x.append(sequence)y.append(target)return np.array(x), np.array(y)def get_traintest(data,train_ratio=0.8,window_size=24):train_size=int(len(data0)*train_ratio)train=data[:train_size]test=data[train_size-window_size:]X_train,y_train=build_sequences(train,window_size=window_size)X_test,y_test=build_sequences(test,window_size=window_size)return X_train,y_train,X_test,y_test

划分训练集和测试集,滑动窗口大小为64

train_ratio=0.8     #训练集比例
window_size=64      #滑动窗口大小,即循环神经网络的时间步长
X_train,y_train,X_test,y_test=get_traintest(np.array(data0).reshape(-1,),window_size=window_size,train_ratio=train_ratio)
print(X_train.shape,y_train.shape,X_test.shape,y_test.shape)

可以看到上面数据X还是2维的,下面归一化

#归一化
scaler = MinMaxScaler() 
scaler = scaler.fit(X_train)
X_train=scaler.transform(X_train)
X_test=scaler.transform(X_test)y_train_orage=y_train.copy()
y_scaler = MinMaxScaler() 
y_scaler = y_scaler.fit(y_train.reshape(-1,1))
y_train=y_scaler.transform(y_train.reshape(-1,1))

转为3维

X_train=X_train.reshape(X_train.shape[0],X_train.shape[1],1)
X_test=X_test.reshape(X_test.shape[0],X_test.shape[1],1)
y_test=y_test.reshape(-1,1)   ; test_size=y_test.shape[0]
print(X_train.shape,y_train.shape,X_test.shape,y_test.shape)

画图展示:

plt.figure(figsize=(10,5),dpi=256)
plt.plot(data0.index[:-test_size],data0.iloc[:-test_size],label='Train',color='#FA9905')
plt.plot(data0.index[-test_size:],data0.iloc[-(test_size):],label='Test',color='#FB8498',linestyle='dashed')
plt.legend()
plt.ylabel('Predict Series',fontsize=16)
plt.xlabel('Time',fontsize=16)
plt.show()

定义评价指标

回归问题,总是用这四个指标:mae,rmse,mape,R2

定义随机数种子和计算评价指标的函数,当然我这里就没要R2了,用的mse,是一样的,要用R2就改一下下面的函数就行了。

def set_my_seed():os.environ['PYTHONHASHSEED'] = '0'np.random.seed(1)rn.seed(12345)tf.random.set_seed(123)def evaluation(y_test, y_predict):mae = mean_absolute_error(y_test, y_predict)mse = mean_squared_error(y_test, y_predict)rmse = np.sqrt(mean_squared_error(y_test, y_predict))mape=(abs(y_predict -y_test)/ y_test).mean()#r_2=r2_score(y_test, y_predict)return mse, rmse, mae, mape 

构建ma1模型:

### 基准预测情况
result = pd.DataFrame()
result['t'] = pd.Series(data0.iloc[:,0])
# 生成第1列到第10列,每一列是t+1到t+10滑动窗口的值
for i in range(1, 6):result[f't-{i}'] = result['t'].shift(i)
result=result.dropna()for t in result.columns[1:]:score=list(evaluation(result['t'], result[t]))s=[round(i,3) for i in score]print(f'{t}的预测效果为:RMSE:{s[0]},MAE:{s[1]},MAPE:{s[2]},R2:{s[3]}')

可以看到MA1模型的RMSE:1.791,MAE:1.338,MAPE:0.768,R2:0.013,很低。

构建模型

下面构建我们的神经网络模型,由于要用transformer层,所以我们需要自定义很多东西:

class AttentionLayer(Layer):    #自定义注意力层def __init__(self, **kwargs):super(AttentionLayer, self).__init__(**kwargs)def build(self, input_shape):self.W = self.add_weight(name='attention_weight',shape=(input_shape[-1], input_shape[-1]),initializer='random_normal',trainable=True)self.b = self.add_weight(name='attention_bias',shape=(input_shape[1], input_shape[-1]),initializer='zeros',trainable=True)super(AttentionLayer, self).build(input_shape)def call(self, x):# Applying a simpler attention mechanisme = K.tanh(K.dot(x, self.W) + self.b)a = K.softmax(e, axis=1)output = x * areturn outputdef compute_output_shape(self, input_shape):return input_shape#from __future__ import print_function
from keras import backend as K
from keras.layers import Layer
from tensorflow.keras import layers
from tensorflow import kerasclass PositionEncoding(Layer):def __init__(self, model_dim, **kwargs):self._model_dim = model_dimsuper(PositionEncoding, self).__init__(**kwargs)def call(self, inputs):seq_length = inputs.shape[1]position_encodings = np.zeros((seq_length, self._model_dim))for pos in range(seq_length):for i in range(self._model_dim):position_encodings[pos, i] = pos / np.power(10000, (i-i%2) / self._model_dim)position_encodings[:, 0::2] = np.sin(position_encodings[:, 0::2]) # 2iposition_encodings[:, 1::2] = np.cos(position_encodings[:, 1::2]) # 2i+1position_encodings = K.cast(position_encodings, 'float32')return position_encodingsdef compute_output_shape(self, input_shape):return input_shape
class Add(Layer):def __init__(self, **kwargs):super(Add, self).__init__(**kwargs)def call(self, inputs):input_a, input_b = inputsreturn input_a + input_bdef compute_output_shape(self, input_shape):return input_shape[0]class TransformerEncoder(layers.Layer):def __init__(self, embed_dim, dense_dim, num_heads, **kwargs):super().__init__(**kwargs)self.embed_dim = embed_dimself.dense_dim = dense_dimself.num_heads = num_headsself.attention = layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)self.dense_proj = keras.Sequential([layers.Dense(dense_dim, activation="relu"),layers.Dense(embed_dim),] )self.layernorm_1 = layers.LayerNormalization()self.layernorm_2 = layers.LayerNormalization()def call(self, inputs, mask=None):if mask is not None:mask = mask[:, tf.newaxis, :]attention_output = self.attention(inputs, inputs, attention_mask=mask)proj_input = self.layernorm_1(inputs + attention_output)proj_output = self.dense_proj(proj_input)return self.layernorm_2(proj_input + proj_output)def get_config(self):config = super().get_config()config.update({"embed_dim": self.embed_dim,"num_heads": self.num_heads,"dense_dim": self.dense_dim,})return config

构建模型函数:

def build_model(X_train,mode='LSTM',hidden_dim=[64,32]):set_my_seed()if mode=='MLP':model = Sequential()model.add(Flatten())model.add(Dense(hidden_dim[0],activation='relu',input_shape=(X_train.shape[-2],X_train.shape[-1])))model.add(Dense(hidden_dim[1],activation='relu'))#model.add(Dense(16,activation='relu'))model.add(Dense(1))elif mode=='RNN':model = Sequential()model.add(SimpleRNN(hidden_dim[0],return_sequences=True, input_shape=(X_train.shape[-2],X_train.shape[-1])))model.add(Dropout(0.2))model.add(SimpleRNN(hidden_dim[1])) model.add(Dropout(0.2))model.add(Dense(1))elif mode=='CNN':model = Sequential()model.add(Conv1D(hidden_dim[0],X_train.shape[-2]-2,activation='relu',input_shape=(X_train.shape[-2],X_train.shape[-1])))model.add(GlobalMaxPooling1D())model.add(Dense(hidden_dim[1],activation='relu'))model.add(Dense(1))elif mode=='LSTM':model = Sequential()model.add(LSTM(hidden_dim[0],return_sequences=True, input_shape=(X_train.shape[-2],X_train.shape[-1])))model.add(Dropout(0.2))model.add(LSTM(hidden_dim[1]))model.add(Dropout(0.2))model.add(Dense(1))elif mode=='GRU':model = Sequential()model.add(GRU(hidden_dim[0],return_sequences=True, input_shape=(X_train.shape[-2],X_train.shape[-1])))model.add(Dropout(0.2))model.add(GRU(hidden_dim[1]))model.add(Dropout(0.2))model.add(Dense(1))elif mode=='BiLSTM':model = Sequential()model.add(Bidirectional(LSTM(hidden_dim[0],return_sequences=True, input_shape=(X_train.shape[-2],X_train.shape[-1]))))model.add(Dropout(0.2))model.add(Bidirectional(LSTM(hidden_dim[1])))model.add(Dropout(0.2))model.add(Dense(1))elif mode=='BiGRU':model = Sequential()model.add(Bidirectional(GRU(hidden_dim[0],return_sequences=True, input_shape=(X_train.shape[-2],X_train.shape[-1]))))model.add(Dropout(0.2))model.add(Bidirectional(GRU(hidden_dim[1])))model.add(Dropout(0.2))model.add(Dense(1))elif mode == 'BiGRU-Attention':model = Sequential()model.add(GRU(hidden_dim[0], return_sequences=True, input_shape=(X_train.shape[-2], X_train.shape[-1])))model.add(AttentionLayer())# Adding normalization and dropout for better training stability and performancemodel.add(LayerNormalization())#model.add(Dropout(0.1))model.add(GRU(hidden_dim[1]))model.add(Dense(1))elif mode == 'BiLSTM-Attention':model = Sequential()model.add(Bidirectional(LSTM(hidden_dim[0], return_sequences=True), input_shape=(X_train.shape[-2], X_train.shape[-1])))model.add(AttentionLayer())model.add(LayerNormalization())model.add(Dropout(0.2))model.add(Bidirectional(LSTM(hidden_dim[1])))#model.add(Flatten())model.add(Dense(hidden_dim[1],activation='relu'))model.add(Dense(1))elif mode=='CNN+BiLSTM': model = Sequential()model.add(Conv1D(filters=hidden_dim[0], kernel_size=3, padding="same",activation="relu"))model.add(MaxPooling1D(pool_size=2))model.add(Bidirectional(LSTM(hidden_dim[1])))model.add(Dense(1))elif mode == 'TCN':model = Sequential()for dilation_rate in [1, 2]:model.add(Conv1D(filters=hidden_dim[0], kernel_size=2, dilation_rate=dilation_rate, padding='causal', activation='relu', input_shape=(X_train.shape[-2], X_train.shape[-1])))model.add(Flatten())model.add(Dense(1))elif mode=='Transformer':model = Sequential()inputs = Input(shape=[X_train.shape[-2],X_train.shape[-1]], name="inputs")encodings = PositionEncoding(32)(inputs)encodings = Add()([inputs, encodings])x = TransformerEncoder(32,  hidden_dim[1], 2)(encodings)        #嵌入维度,全连接层神经元数,多头数x = GlobalAveragePooling1D()(x)#x = Dropout(0.2)(x)#x = Dense(32, activation='relu')(x)outputs = Dense(1)(x)model = Model(inputs=[inputs], outputs=outputs)else:raise ValueError("Unsupported mode: " + mode)model.compile(optimizer='Adam', loss='mse' ,metrics=[tf.keras.metrics.RootMeanSquaredError(),"mape","mae"])return model

定义一些画图展示用的函数

def plot_loss(hist,imfname=''):plt.subplots(1,4,figsize=(16,2))for i,key in enumerate(hist.history.keys()):n=int(str('14')+str(i+1))plt.subplot(n)plt.plot(hist.history[key], 'k', label=f'Training {key}')plt.title(f'{imfname} Training {key}')plt.xlabel('Epochs')plt.ylabel(key)plt.legend()plt.tight_layout()plt.show()
def plot_fit(y_test, y_pred):plt.figure(figsize=(4,2))plt.plot(y_test, color="red", label="actual")plt.plot(y_pred, color="blue", label="predict")plt.title(f"拟合值和真实值对比")plt.xlabel("Time")plt.ylabel('power')plt.legend()plt.show()

定义训练函数,初始化两个数据框,用于储存我们的误差评价指标和预测值,我们会在序列函数里面进行模型的训练,预测,误差评价指标的计算,以及储存。

df_eval_all=pd.DataFrame(columns=['MSE','RMSE','MAE','MAPE'])
df_preds_all=pd.DataFrame()
def train_fuc(mode='LSTM',batch_size=32,epochs=50,hidden_dim=[32,16],verbose=0,show_loss=True,show_fit=True):#构建模型s = time.time()set_my_seed()model=build_model(X_train=X_train,mode=mode,hidden_dim=hidden_dim)earlystop = EarlyStopping(monitor='loss', min_delta=0, patience=5)hist=model.fit(X_train, y_train,batch_size=batch_size,epochs=epochs,callbacks=[earlystop],verbose=verbose)if show_loss:plot_loss(hist)#预测y_pred = model.predict(X_test)y_pred = y_scaler.inverse_transform(y_pred)#print(f'真实y的形状:{y_test.shape},预测y的形状:{y_pred.shape}')if show_fit:plot_fit(y_test, y_pred)e=time.time()print(f"运行时间为{round(e-s,3)}")df_preds_all[mode]=y_pred.reshape(-1,)s=list(evaluation(y_test, y_pred))df_eval_all.loc[f'{mode}',:]=ss=[round(i,3) for i in s]print(f'{mode}的预测效果为:MSE:{s[0]},RMSE:{s[1]},MAE:{s[2]},MAPE:{s[3]}')print("=======================================运行结束==========================================")return s[0]

初始化参数

window_size=64
batch_size=32
epochs=50
hidden_dim=[32,16]verbose=0
show_fit=True
show_loss=True
mode='LSTM'  #MLP,GRU

模型训练

构建MLP模型

train_fuc(mode='MLP',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)

上面4个小图分别是训练时候的不同损失的下降情况可以看到基本上40轮以后肯定都收敛了。下面是真实值和预测值对比,然后会打印这些误差指标以及运行时间。

构建RNN模型:

train_fuc(mode='RNN',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)

就不展示那么多了,下面我们把所有的模型都一起训练,然后所有的误差指标都会储存起来,我们后面一起查看就可以了。

train_fuc(mode='CNN',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)
train_fuc(mode='TCN',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)
train_fuc(mode='GRU',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)
train_fuc(mode='LSTM',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)
train_fuc(mode='BiGRU',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)
train_fuc(mode='BiLSTM',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)
train_fuc(mode='CNN+BiLSTM',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)
train_fuc(mode='BiLSTM-Attention',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)
train_fuc(mode='BiGRU-Attention',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)
train_fuc(mode='Transformer',batch_size=batch_size,epochs=epochs,hidden_dim=hidden_dim)

有的同学可能惊讶于我不同模型就修改一个mode参数就行了???其他全自动???

我知道大部分同学写代码都是东拼西凑,而且没有很好的编码风格,也不规范,所以说会花费大量的时间在于人的手工调整以及修改中。优雅的写法当然是只修改一个参数就可以把所有重复的过程都再进行一遍,这就是函数的封装性和简洁性的妙用,重复的代码绝对不会重写,重复的工作绝对不会重做。

因为我们写文章,无非都是训练不同的模型预测结果进行评价对比,这都是重复的工作,只是说模型不一样罢了。所有的过程都是重复性的过程,自然就要用代码去消灭这些重复性的工作,就得把他们封装的特别好,简洁和易用。

查看评价指标

我们直接按照MSE排个序:

df_eval_all.loc['MA1',:]=evaluation(result['t'], result['t-1'])
df_eval_all.sort_values('MSE',ascending=True).style.bar(color='pink')

可以看到MA1模型误差最低,不直观的话,可以进行可视化:

bar_width = 0.4
colors=['c', 'orange','g', 'tomato','b', 'm', 'y', 'lime', 'k','orange','pink','grey','tan']
fig, ax = plt.subplots(2,2,figsize=(8,6),dpi=128)
for i,col in enumerate(df_eval_all.columns):n=int(str('22')+str(i+1))plt.subplot(n)df_col=df_eval_all[col]m =np.arange(len(df_col))plt.bar(x=m,height=df_col.to_numpy(),width=bar_width,color=colors)##plt.xlabel('Methods',fontsize=12)names=df_col.indexplt.xticks(range(len(df_col)),names,fontsize=10)plt.xticks(rotation=90)plt.ylabel(col,fontsize=14)plt.tight_layout()
#plt.savefig('柱状图.jpg',dpi=512)
plt.show()

什么结果不用多说了吧,虽然transformer是所有神经网络里面表现效果最好的模型,mse最小,也符合常理。但是,所有的神经网络都没有MA1模型的误差低。也就是说,我们费尽千辛万苦,各种复杂的结构层算法构建出来的神经网络居然都不如直接用Xt 的值作为'Xt+1'的预测值这种最简单的MA1模型!!!!

是不是颠覆三观了,也就是说,这么多充斥在学术界和研究界的用神经网络去做时间序列预测的模型的水论文研究,基本上都是无用功。(当然,更高级的模型我没试过不知道,不乱说)

但是没什么人来指出这个问题,听说最近国外在顶会上有人针对这种神经网络预测长时间序列提出了一系列的问题,但是他们的聚焦点还是在于这些误差评价指标不适用于时间序列预测中,并没有意识到神经网络用于时间序列目前的这个构建方法是存在问题的。

好在我们不用那么悲观,虽然没意义,但是90%的论文谁不是为了水论文呢,谁又真的拿去实际生产模型中去部署调用呢?更重要的是所谓的专家,学者,老师,导师,审稿人都对这些一窍不通,也没人发现这其中的问题。

当然,我为什么选着这个油价数据,是因为他是最具有代表性的。我测试了在其他数据上的这些模型的对比表现,我发现ma1模型并不总是最好的,但是它总是能够获得一个中等偏上的水平,也就是说他总能够打败60%以上的神经网络模型,如果你的数据有的还挺适合神经网络的,那就还可以用神经网络做一些有价值的工作吧。

但是一般来说,频率很高的这种强自相关的数据,ma1效果都挺好的。

最后再画一个不同模型预测出来的序列结果的对比图,这也是水文章里面常用的:

 


总结

神经网络做时间序列预测主要有,单变量单步预测,单变量多步预测,多变量单步预测,多变量多步预测,

本次演示的是最简单的单变量单步预测,对比了10种神经网络模型。["Transformer", "CNN+BiLSTM", "BiGRU-Attention","BiLSTM-Attention", "BiGRU", "BiLSTM", "TCN", "GRU", "CNN", "LSTM","RNN","MLP", ],然后发现所有的神经网络都没有MA1模型的误差低。也就是说,我们费尽千辛万苦,各种复杂的结构层算法构建出来的神经网络居然都不如直接用Xt 的值作为'Xt+1'的预测值这种最简单的MA1模型。

但是也不用太悲观,在有的数据上表现ma1不一定最好,并且大部分专家,审稿人,导师都不懂,国内也没有人指出这个问题,所以目前水论文还是可以随便放心的用。并且知道这个东西没啥意义就好,要是真的以为自己做了个模型能够产生多少价值跟收益,那可太天真了。


各种模态分解优化算法,损失函数缝合不同的神经网络预测时间序列的模型在往期文章中都有:

Python数据分析案例24——基于深度学习的锂电池寿命预测_锂离子电池寿命预测

Python数据分析案例25——海上风力发电预测(多变量循环神经网络)

Python数据分析案例41——基于CNN-BiLSTM的沪深300收盘价预测

Python数据分析案例42——基于Attention-BiGRU的时间序列数据预测

Python数据分析案例44——基于模态分解和深度学习的电负荷量预测(VMD+BiGRU+注意力)

Python数据分析案例50——基于EEMD-LSTM的石油价格预测

Python数据分析案例52——基于SSA-LSTM的风速预测(麻雀优化)

怎么水论文里面也写的非常清楚。代码都是类似的,框架高度封装,换个数据就能用,不需要怎么修改。

随便组合缝合都能发SCI,毕业真的太容易了有木有。

创作不易,看官觉得写得还不错的话点个关注和赞吧,本人会持续更新python数据分析领域的代码文章~(需要定制类似的代码可私信)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/4452.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

opencv笔记2

图像灰度 彩色图像转化为灰度图像的过程是图像的灰度化处理。彩色图像中的每个像素的颜色由R,G,B三个分量决定,而每个分量中可取值0-255,这样一个像素点可以有256*256*256变化。而灰度图像是R,G,B三个分量…

LeetCode:2266. 统计打字方案数(DP Java)

目录 2266. 统计打字方案数 题目描述: 实现代码与解析: 动态规划 原理思路: 2266. 统计打字方案数 题目描述: Alice 在给 Bob 用手机打字。数字到字母的 对应 如下图所示。 为了 打出 一个字母,Alice 需要 按 对…

http://noi.openjudge.cn/——4.7算法之搜索——【169:The Buses】

题目 169:The Buses 总时间限制: 5000ms 内存限制: 65536kB 描述 A man arrives at a bus stop at 12:00. He remains there during 12:00-12:59. The bus stop is used by a number of bus routes. The man notes the times of arriving buses. The times when buses arrive …

java基础概念59-File

一、路径 二、File类 2-1、常见的构造方法 示例: 【注意】: 一般不自己用分割符把父路径和子路径拼接起来,因为,不用的操作系统,分隔符不同。 2-2、小结 2-3、File中常见的成员方法 示例: 【注意】&#…

PortSwigger靶场练习---第二关-查找和利用未使用的 API 端点

第二关:Finding and exploiting an unused API endpoint 实验:查找和利用未使用的 API 端点 PortSwigger靶场地址: Dashboard | Web Security Academy - PortSwigger 题目: 官方提示: 在 Burp 的浏览器中&#xff0c…

软路由系统iStoreOS 一键安装 docker compose

一键安装命令 大家好!今天我来分享一个快速安装 docker-compose 的方法。以下是我常用的命令,当前版本是 V2.32.4。如果你需要最新版本,可以查看获取docker compose最新版本号 部分,获取最新版本号后替换命令中的版本号即可。 w…

SpringCloud nacos 2.0.0 + seata 2.0.0

NACOS 下载nacos https://github.com/alibaba/nacos/releases/tag/2.2.0 启动nacos startup.cmd -m standalone SEATA 下载seata https://seata.apache.org/release-history/seata-server 新建数据库-seata CREATE TABLE branch_table (branch_id bigint NOT NULL,xid …

springboot音乐播放器系统

Spring Boot音乐播放器系统是一个基于Spring Boot框架开发的音乐播放平台,旨在为用户提供高效、便捷的音乐播放体验。 一、系统背景与意义 随着互联网的飞速发展和人们对音乐娱乐需求的不断增长,音乐播放器已经成为人们日常生活中不可或缺的一部分。传…

奉加微PHY6230兼容性:部分手机不兼容

从事嵌入式单片机的工作算是符合我个人兴趣爱好的,当面对一个新的芯片我即想把芯片尽快搞懂完成项目赚钱,也想着能够把自己遇到的坑和注意事项记录下来,即方便自己后面查阅也可以分享给大家,这是一种冲动,但是这个或许并不是原厂希望的,尽管这样有可能会牺牲一些时间也有哪天原…

Go-知识 版本演进

Go-知识 版本演进 Go release notesr56(2011/03/16)r57(2011/05/03)Gofix 工具语言包工具小修订 r58(2011/06/29)语言包工具小修订 r59(2011/08/01)语言包工具 r60(2011/09/07)语言包工具 [go1 2012-03-28](https://golang.google.cn/doc/devel/release#go1)[go1.1 2013-05-13]…

C#,入门教程(02)—— Visual Studio 2022开发环境搭建图文教程

如果这是您阅读的本专栏的第一篇博文,建议先阅读如何安装Visual Studio 2022。 C#,入门教程(01)—— Visual Studio 2022 免费安装的详细图文与动画教程https://blog.csdn.net/beijinghorn/article/details/123350910 一、简单准备 开始学习、编写程序…

数字艺术类专业人才供需数据获取和分析研究

本文章所用数据集:数据集 本文章所用源代码:源代码和训练好的模型 第1章 绪论 1.1研究背景及意义 随着社会经济的迅速发展和科技的飞速进步,数字艺术类专业正逐渐崛起,并呈现出蓬勃发展的势头。数字艺术作为创作、设计和表现形式的…

imbinarize函数用法详解与示例

一、函数概述 众所周知,im2bw函数可以将灰度图像转换为二值图像。但MATLAB中还有一个imbinarize函数可以将灰度图像转换为二值图像。imbinarize函数是MATLAB图像处理工具箱中用于将灰度图像或体数据二值化的工具。它可以通过全局或自适应阈值方法将灰度图像转换为二…

使用ffmpeg提高mp4压缩比,减小文件体积【windows+ffmpeg+batch脚本】

文章目录 关于前情提要FFmpeg是什么使用脚本运行FFmpeg首先,下载ffmpeg.exe然后在视频相同位置写一个bat脚本运行压缩脚本 关于 个人博客,里面偶尔更新,最近比较忙。发一些总结的帖子和思考。 江湖有缘相见🤝。如果读者想和我交…

Codeforces Round 997 (Div. 2) A~C

今天的封面是水母猫猫和佩佩,原图在这里,记得关注画师夏狩大大 至此,天鹅完成了连续四场比赛在四个不同比赛上四次分的壮举!(ABC388,CodeChef169,牛客月赛109,CF997) 这场…

JavaFx + SpringBoot 快速开始脚手架

JavaFX系列项目模板 JDK8 & JavaFX & SpringBoot 加持SpringBoot,项目示例,Maven打包插件带可执行程序JDK8 & JavaFX 不依赖SpringBoot,项目示例,Maven打包插件带可执行程序JDK11 & JavaFX15 使用 jlink 打包为精…

蓝桥杯3525 公因数匹配 | 枚举+数学

题目传送门 这个题目是一个数学题,由于只需要找到存在大于1的公因数的两数,所以比较方便的做法是统计每一个数的(质)因数。可以通过筛法统计质因数降低复杂度,但是直接枚举因数也可以满足要求。使用字典记录每个因数出…

当PHP遇上区块链:一场奇妙的技术之旅

PHP 与区块链的邂逅 在技术的广袤宇宙中,区块链技术如同一颗耀眼的新星,以其去中心化、不可篡改、透明等特性,掀起了一场席卷全球的变革浪潮。众多开发者怀揣着对新技术的热忱与探索精神,纷纷投身于区块链开发的领域,试…

利用Ai,帮我完善了UsbCamera App的几个界面和设置功能

早些时候,我有开源了一个UsbCamera App的代码,后来因为一些原因,就只针对星球成员和课程视频成员开源了。最近,我对这个App进行了一些内容的补充。 主要是添加了一些设置相关的内容,支持rtmp推流、循环录像、镜像&…

【系统分享01】Python+Vue电影推荐系统

大家好,作为一名老程序员,今天我将带你一起走进电影推荐系统的世界,分享如何利用 Django REST Framework 和 Vue 搭建一套完整的电影推荐系统,结合 协同过滤算法,根据用户评分与影片喜好,精准推送用户可能喜…