Python数据分析案例70——基于神经网络的时间序列预测(滞后性的效果,预测中存在的问题)
背景
这篇文章可以说是基于 现代的一些神经网络的方法去做时间序列预测的一个介绍科普,也可以说是一个各种模型对比的案例,但也会谈一谈自己做了这么久关于神经网络的时间序列预测的论文,其中一些常见的模式及它们存在的问题以及效果,还有在实际生产中去运用究竟会有什么问题?
时间序列预测的几种模式
首先我们要知道一个滑动窗口的概念,时间序列预测无非就是用之前的数据去预测未来的数据。与传统的机器学习的二维表格不同,时间序列最简单的模式就是单变量单步预测。即我们要预测一个变量,我们就用这个变量前几期的数据去预测未来下一期的数据,例如我们的滑动窗口大小为10,也就是说我们用这个变量 'Xt-10到Xt' 的数据,去预测' Xt+1' 的取值(这里就不写latex公式了反正应该也挺通俗易懂的)。也就是说,在单变量单步预测模型中,你的一个X向量就是 'Xt-10到Xt' 的数据 ,y标量就是 ' Xt+1' 的取值。然后我们可以不停的滑动这个窗口,得到很多组X跟y,就可以去训练模型了。(注意这里的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是很正常的事情,大部分同学做模型的时候连数据的形状都没弄清楚,其实这是一个很重要的认知,你一定要认清楚你的X跟y是什么形状,你才能去用对应的模型。
回到我们的单变量多步预测模型上来,我们的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), 只是在构建我们的训练集和测试集的时候,需要注意一下这个多维度的时候,构建X和y去对数据运用切片索引的问题。
按道理来说,以前基于树模型的二维表格的机器学习都是变量越多越好,但是在如今的这个循环神经网络里面并不是特征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数据分析领域的代码文章~(需要定制类似的代码可私信)
相关文章:

Python数据分析案例70——基于神经网络的时间序列预测(滞后性的效果,预测中存在的问题)
背景 这篇文章可以说是基于 现代的一些神经网络的方法去做时间序列预测的一个介绍科普,也可以说是一个各种模型对比的案例,但也会谈一谈自己做了这么久关于神经网络的时间序列预测的论文,其中一些常见的模式及它们存在的问题以及效果&#x…...

vue+高德API搭建前端Echarts图表页面
利用vue搭建Echarts图表页面,在搭建Echarts图表中,如果搭建地理地形图需要准备一些额外的文件,地理json文件和js文件,js文件目前在网上只能找省一级的,json文件有对应的省市县,js文件和json文件对应的也是不…...

提示词工程:解锁AI潜能的关键技术
什么是提示词工程? 提示词工程(Prompt Engineering)是一门新兴的技术领域,专注于如何设计和优化与生成式人工智能的交互提示,以获得最佳的输出结果。它是连接人类意图和AI能力的桥梁,通过精心设计的文本输入来引导AI模型产生准确、相关且有价值的输出。 核心概念 提示(…...

Python制作简易PDF查看工具PDFViewerV1.0
PDFViewer PDF浏览工具,Python自制PDF查看工具,可实现基本翻页浏览功能,其它功能在进一步开发完善当中,如果有想一起开发的朋友,可以留言。本软件完全免费,自由使用。 软件界面简洁,有菜单栏、…...

嵌入式硬件篇---基本组合逻辑电路
文章目录 前言基本逻辑门电路1.与门(AND Gate)2.或门(OR Gate)3.非门(NOT Gate)4.与非门(NAND Gate)5.或非门(NOR Gate)6.异或门(XOR Gate&#x…...

CSRF攻击XSS攻击
概述 在 HTML 中,<a>, <form>, <img>, <script>, <iframe>, <link> 等标签以及 Ajax 都可以指向一个资源地址,而所谓的跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不一样。这里的域指…...

ARM学习(42)CortexM3/M4 MPU配置
笔者之前学习过CortexR5的MPU配置,现在学习一下CortexM3/M4 MPU配置 1、背景介绍 笔者在工作中遇到NXP MPU在访问异常地址时,就会出现总线挂死,所以需要MPU抓住异常,就需要配置MPU。具体背景情况可以参考ARM学习(41)NXP MCU总线挂死,CPU could not be halted以及无法连…...
opencv3.4 ffmpeg3.4 arm-linux 交叉编译
一些依赖安装: sudo apt-get install pkg-config libgtk2.0-dev libavcodec-dev libavformat-dev libswscale-dev 交叉编译工具链准备:gcc-linaro-6.3.1 1、下载 https://github.com/FFmpeg/FFmpeg 解压后新建目录:Fmpeg-n3.4.13/ffmpeg…...

spring的事物管理的认知
事物 它是一个原子操作要么全部不执行,要么全部执行成功,如果有一个失败也会撤销,它保证用户每一次的操作都是可靠的,即使时出现了错误也不至于破坏数据的完整性 它包含了四种特性: 原子性:保证事物要么…...

麒麟LINUX V10SP3 2401安装ORACLE 12.2.1 runInstaller直接报UNZIP格式不对
好久没有安装ORACLE了,一般都是RHEL上安装得比较多,这不,现在大家都是选择国产操作系统来安装数据库了,以前在龙蜥,欧拉,麒麟上也安装过,都没有问题,想来在麒麟LINUX v10sp3 2401上面…...

华为HuaweiCloudStack(一)介绍与架构
本文简单介绍了华为HCS私有云解决方案,并从下至上介绍HCS的整体架构,部署架构、部署方式等内容。 目录 HCS简介 HCS架构 纵向结构 ?管理平台类型 HCS节点类型 FusionSphere OpenStack CPS ServiceOM SC 运营面 OC 运维面 HCS部署架构 regi…...

微服务学习:基础理论
一、微服务和应用现代化 1、时代的浪潮,企业的机遇和挑战 在互联网化数字化智能化全球化的当今社会,IT行业也面临新的挑战: 【快】业务需求如“滔滔江水连绵不绝”,企业需要更快的交付【变】林子大了,百色用户&…...
C++实现设计模式---迭代器模式 (Iterator)
迭代器模式 (Iterator) 迭代器模式 是一种行为型设计模式,它提供了一种方法,顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。 意图 提供一种方法,可以顺序访问一个容器对象中的元素,而无需暴露其…...

海康工业相机的应用部署不是简简单单!?
作者:SkyXZ CSDN:SkyXZ~-CSDN博客 博客园:SkyXZ - 博客园 笔者使用的设备及环境:WSL2-Ubuntu22.04MV-CS016-10UC 不会吧?不会吧?不会还有人拿到海康工业相机还是一脸懵叭?不会还有人…...

Windows电脑安装File Browser与cpolar轻松搭建本地云盘
文章目录 前言1.下载安装File Browser2.启动访问File Browser3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 无论是个人用户还是企业团队,都希望能够有一个高效、安全的解决方案来…...

mac配置 iTerm2 使用lrzsz与服务器传输文件
mac配置 1. 安装支持rz和sz命令的lrzsz brew install lrzsz2. 下载iterm2-send-zmodem.sh和iterm2-recv-zmodem.sh两个脚本 # 克隆仓库 git clone https://github.com/aikuyun/iterm2-zmodem ~/iterm2-zmodem# 进入到仓库目录 cd ~/iterm2-zmodem# 设置脚本文件可执行权限 c…...

【HBuilderX 中 Git 的使用】
目录: 一:安装必要的版本控制工具二:把Github上的项目克隆到本地三:将本地的项目上传到Github上 一:安装必要的版本控制工具 1️⃣ 安装 TortoiseGit 工具,下载地址:https://tortoisegit.org/do…...

Golang结合MySQL和DuckDB提高查询性能
要在Golang中组合MySQL和DuckDB以提高查询性能,请考虑使用混合查询执行方法。这种方法利用了MySQL强大的事务管理和DuckDB闪电般的分析处理能力。本文介绍如何充分利用两者的方法。 各取所长 用MySQL处理事务,用DuckDB处理分析 MySQL应该处理常规的INS…...

学技术学英语:TCP的三次握手和四次挥手
单词 汉语意思 音标 acknowledge 承认,确认 /əkˈnɒl.ɪdʒ/ acknowledgment 确认,承认 /əkˈnɒl.ɪdʒ.mənt/ duplex 双向的 /ˈdjuː.pleks/ establish 建立 /ɪˈstb.lɪʃ/ handshake 握手,握手协议 /ˈhnd.ʃeɪk…...

xiao esp32 S3播放SD卡wav音频
本文旨在使用xiao esp32 S3 播放SD卡上的音频文件 1 硬件准备 SD卡 2 代码实现 2.1 依赖库 ESP32-audioI2S-master 2.2 代码 #include "Arduino.h" #include "Audio.h" #include "SD.h"// Digital I/O used #define I2S_DOUT 6 #defi…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...