案例背景
不知道大家发现了没,现在的神经网络做时间序列的预测都是单步预测,即(需要使用X的t-n期到X的t-1期的数据去预测X的t期的数据),这种预测只能预测一个点,我需要预测X的t+1期的数据就没办法了,有的同学说可以把预测的结果X的t+1拿进来作为新的x去预测。。。我只能说这种情况是有误差的,而且误差会累加,这样效果很差。(看很多ARIMA的预测效果一条直线就知道了)
很多时候需要进行多步预测,即(需要使用X的t-n期到X的t-1期的数据去预测X的t期到t+n期的数据,预测出来的就不止一个点。这种方法,ARIMA这种传统统计学的方法是做不到的了,神经网络可以做到,因为神经网络可以接受一条序列作为y,这样去训练就可以得到多步预测模型了。
本次案例使用某城市的AQI数据,去预测未来一年365天的数据。来看看我怎么完成的。
数据介绍
没啥好介绍的,一般下载城市的数据都是这样的,我们只需要AQI这一列就行。
任务介绍:基于空气质量检测数据,采用人工神经网络对AQI进行回归预测。
- (1)利用Python实现回归预测并得出2024年的预测结果。
- (2)展示随迭代次数增加,不同激活函数下的损失函数的变化情况。
当然,需要本次演示案例的数据和所有代码文件的同学可以参考: AQI预测
代码实现
导入包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei'] #解决中文显示乱码问题
plt.rcParams['axes.unicode_minus']=Falsefrom keras.models import Sequential
from keras.layers import LSTM, Dense,Flatten
from keras.callbacks import EarlyStopping
from sklearn.preprocessing import MinMaxScaler
读取数据,设置日期索引:
data=pd.read_excel('AQI数据.xlsx')#.set_index('日期')
data['日期']=pd.to_datetime(data['日期'])
data=data.set_index('日期')
data
数据跨度从2019-2023年,日度数据。
简单画个图看看
data.aqi.plot(figsize=(10,3))
很符合AQI的摸样,波动很大,参差不齐,还有一定的周期性。
数据准备
时间序列做神经网络预测,一般都需要进行三维化,即把数据变为(n,t,p)的形状,n是样本量,t是时间步长,p是特征数量。一般 的表格数据都是(n,p)的结构,时间序列要多一个时间t的维度。
数据构建X和y之前要归一化,神经网络很需要,不然模型会不收敛。
# 数据预处理
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_aqi = scaler.fit_transform(data['aqi'].values.reshape(-1, 1))# 创建LSTM需要的序列数据
def create_dataset(dataset, start_index, end_index, history_size, target_size):data = [] ; labels = []start_index = start_index + history_sizeif end_index is None:end_index = len(dataset) - target_sizefor i in range(start_index, end_index):indices = range(i-history_size, i)data.append(np.reshape(dataset[indices], (history_size, 1)))labels.append(dataset[i:i+target_size])return np.array(data), np.array(labels)# 用过去的700天数据来预测接下来的365天
past_history = 700
future_target = 365X_train, y_train = create_dataset(scaled_aqi, 0, None, past_history, future_target)
y_train=y_train.reshape(y_train.shape[0],y_train.shape[1])
X_train.shape, y_train.shape
我定义了一个转化时间序列构建X和y的函数,然后采用时间窗口为700,也就是t=700的时间步长,然后去预测未来365天的数据,也就是一年。
为什么是700,,,没有为什么,因为要预测365个点,我需要时间步长大一点,那就大概2倍的数据吧,我就选择了凑个整数700,当然699,701,710,720,730,都是可以的,可以去试试。
是不是时间步长越长越好?不一定,首先看你样本量,我数据只有1500多个点,我选择了700时间步长,其实就损失了700个样本了,可以看到我样本量只有486个,有点少。其次,时间步长过长会造成运行时间过长,你也不想体验等一次运行结果要等上一天的感觉吧。。。
当然大家可以更具自己的需要预测的时间长度,还有样本量来调整自己的时间步长t。
预测2024年数据(默认tanh激活函数)
这里构建的是最简单的神经网络MLP模型,一个小案例,就没使用LSTM,GRU,transform这种序列模型了。大家感兴趣可以自己改一下试试。
# 创建MLP模型
model = Sequential()
model.add(Flatten())
model.add(Dense(512))
model.add(Dense(128))
model.add(Dense(future_target))
model.compile(optimizer='adam', loss='mse')# 训练模型
early_stop = EarlyStopping(monitor='loss', patience=10)
history=model.fit(X_train, y_train, epochs=50, batch_size=32, callbacks=[early_stop], verbose=1)
训练了50轮,loss没怎么变了。
画图看看:
plt.figure(figsize=(7,3))
plt.plot(history.history['loss'], label=f'loss')
plt.legend()
plt.show()
基本收敛了,然后我们预测,预测的数据要逆归一化回来,然后加上预测的日期的索引。
# 进行预测
prediction = model.predict(X_train[-1].reshape(1, past_history, 1))
# 逆缩放预测结果
predicted_aqi = scaler.inverse_transform(prediction).flatten()
predicted_aqi.shape# 创建预测日期的范围
last_date = data.index[-1]
predicted_dates = pd.date_range(start=last_date, periods=future_target+1, closed='right')# 创建包含预测结果的DataFrame
predicted_df = pd.DataFrame({'日期': predicted_dates,'预测aqi': predicted_aqi})
画个图看看:
# 绘制预测和实际的AQI值
plt.figure(figsize=(12, 3),dpi=128)
plt.plot(data.index, data['aqi'], label='Actual AQI')
plt.plot(predicted_dates, predicted_aqi, label='Predicted AQI', linestyle='dashed')
plt.title('AQI Prediction')
plt.xlabel('Date')
plt.ylabel('AQI')
plt.legend()
plt.show()
后面橙色的虚线就是我预测的数据了。看这效果还不错的样子,波动性学到了,季节性也学到了。
由于目前还没有真实的2024年的AQI数据,也不知道效果好不好。。。也不知道别的LSTM之类的模型效果好不好。。所以没法计算误差去评价。
储存预测结果
## 储存
predicted_df.to_excel('AQI预测结果.xlsx')
保存了,可以本地excel查看了。
不同损失函数
下面是一个其他任务的彩蛋吧,看看不同的激活函数对模型的训练过程是否有影响。
有兴趣的同学可以看看。
展示随迭代次数增加,不同激活函数下的损失函数的变化情况。
- (用了五种激活函数)['relu', 'tanh', 'sigmoid','elu','softplus']
定义和训练:
# Function to create and train LSTM model with different activation functions
def train_lstm_model(X_train, y_train, activation='relu', epochs=100, batch_size=32):model = Sequential()model.add(Flatten())model.add(Dense(512))model.add(Dense(128))model.add(Dense(future_target))model.compile(optimizer='adam', loss='mse')# Early stopping to prevent overfittingearly_stop = EarlyStopping(monitor='loss', patience=10, verbose=1)# Train the modelhistory = model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=0, callbacks=[early_stop])return model, history# Activations to try
activations = ['relu', 'tanh', 'sigmoid','elu','softplus']
# Dictionary to store models and histories
models = {}
histories = {}# Training models with different activation functions
for activation in activations:model, history = train_lstm_model(X_train, y_train, activation=activation)models[activation] = modelhistories[activation] = history.history['loss']
画图查看:
## 五种激活函数
plt.figure(figsize=(9, 3),dpi=128)
for activation in activations:plt.plot(histories[activation], label=f'Activation = {activation}')
plt.title('Training Loss with Different Activation Functions')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend()
plt.show()
五种激活函数差不多,区别不大。
创作不易,看官觉得写得还不错的话点个关注和赞吧,本人会持续更新python数据分析领域的代码文章~