深度学习实战案例:基于LSTM的国际航空公司乘客预测(附完整代码)

时间序列预测问题是一类相对困难的预测建模问题。与回归预测建模不同,时间序列还增加了输入变量之间序列依赖性的复杂性。长短期记忆网络或 LSTM 网络是一种用于深度学习的递归神经网络,比较擅长解决此类问题。

在本文中,我将分享如何使用 Keras 深度学习库在 Python 中开发 LSTM 网络来解决演示时间序列预测问题。详情如下:

  • 关于国际航空公司乘客时间序列预测问题
  • 如何为时间序列预测问题的回归、窗口和基于时间步长的框架开发 LSTM 网络
  • 如何使用在很长的序列中保持状态(记忆)的 LSTM 网络开发和进行预测

这些示例将准确展示你如何针对时间序列预测建模问题开发自己的不同结构的 LSTM 网络。

技术提升

技术要学会分享、交流,不建议闭门造车。一个人走的很快、一堆人可以走的更远。

完整代码、数据、技术交流提升, 均可加入知识星球交流群获取,群友已超过2000人,添加时切记的备注方式为:来源+兴趣方向,方便找到志同道合的朋友。

方式①、添加微信号:pythoner666,备注:来自 CSDN + lstm乘客预测
方式②、微信搜索公众号:Python学习与数据挖掘,后台回复:资料

问题描述

你将在这篇文章中看到的问题是国际航空公司乘客预测问题。

下面是文件前几行的示例。

"Month","Passengers"
"1949-01",112
"1949-02",118
"1949-03",132
"1949-04",129
"1949-05",121

你可以使用 Pandas 库轻松加载此数据集。你对日期不感兴趣,因为每个观察值都以相同的一个月间隔分隔。因此,当你加载数据集时,你可以排除第一列。
加载后,你可以轻松绘制整个数据集。下面列出了加载和绘制数据集的代码。

扫描二维码关注公众号,回复: 15056434 查看本文章
import pandas
import matplotlib.pyplot as plt
dataset = pandas.read_csv('airline-passengers.csv', usecols=[1], engine='python')
plt.plot(dataset)
plt.show()

随着时间的推移,你可以看到数据集中呈上升趋势。

长短期记忆网络

Long Short-Term Memory 网络或 LSTM 网络是使用反向传播训练的循环神经网络,它克服了梯度消失问题。因此,它可用于创建大型循环网络,进而可用于解决机器学习中的困难序列问题并获得最先进的结果。

LSTM 网络没有神经元,而是具有通过层连接的记忆块。一个块具有使其比经典神经元更聪明的组件和对最近序列的记忆。块包含管理块状态和输出的门。块对输入序列进行操作,块内的每个门都使用 sigmoid 激活单元来控制它是否被触发,从而使状态的变化和流经块的信息的添加成为条件。

一个单元内有三种类型的门:

  • 忘记门:有条件地决定从块中丢弃哪些信息
  • 输入门:有条件地决定输入的哪些值更新内存状态
  • 输出门:根据输入和块的内存有条件地决定输出什么

每个单元就像一个微型状态机,其中单元的门具有在训练过程中学习的权重。
你可以看到如何通过 LSTM 层实现复杂的学习和记忆,并且不难想象高阶抽象如何与多个这样的层分层。

用于回归的 LSTM 网络

你可以将该问题表述为回归问题。即,给定本月的旅客人数(以千为单位),下个月的旅客人数是多少?

你可以编写一个简单的函数,将单列数据转换为双列数据集:第一列包含本月 (t) 的乘客人数,第二列包含待预测的下个月 (t+1) 的乘客人数。

在开始之前,让我们首先导入你将使用的所有函数和类。这假定安装了 Keras 深度学习库的工作 SciPy 环境。

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import LSTM
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error

你还可以使用上一节中的代码将数据集加载为 Pandas 数据框。然后,你可以从数据帧中提取 NumPy 数组并将整数值转换为浮点值,这更适合使用神经网络建模。

# load the dataset
dataframe = pd.read_csv('airline-passengers.csv', usecols=[1], engine='python')
dataset = dataframe.values
dataset = dataset.astype('float32')

LSTM 对输入数据的规模很敏感,特别是在使用 sigmoid(默认)或 tanh 激活函数时。将数据重新缩放到 0 到 1 的范围可能是一个很好的做法,也称为归一化。你可以使用scikit-learn 库中的MinMaxScaler预处理类轻松规范化数据集。

# normalize the dataset
scaler = MinMaxScaler(feature_range=(0, 1))
dataset = scaler.fit_transform(dataset)

在对数据建模并评估模型在训练数据集上的技能后,你需要了解模型在新的未见数据上的技能。对于正常的分类或回归问题,你可以使用交叉验证来执行此操作。

对于时间序列数据,值的顺序很重要。你可以使用的一种简单方法是将有序数据集拆分为训练数据集和测试数据集。下面的代码计算分割点的索引并将数据分离到训练数据集中,其中 67% 的观察值用于训练模型,剩下的 33% 用于测试模型。

# split into train and test sets
train_size = int(len(dataset) * 0.67)
test_size = len(dataset) - train_size
train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:]
print(len(train), len(test))

现在,你可以定义一个函数来创建新的数据集,如上所述。

该函数有两个参数:dataset和look_back,前一个时间步数用作输入变量以预测下一个时间段——在本例中, look_back 是要转换为数据集的 NumPy 数组。默认为 1。

此默认值将创建一个数据集,其中 X 是给定时间 (t) 的乘客数量,Y 是下一个时间 (t + 1) 的乘客数量。

它可以通过在下一节中构建不同形状的数据集来配置。

# convert an array of values into a dataset matrix
def create_dataset(dataset, look_back=1):
	dataX, dataY = [], []
	for i in range(len(dataset)-look_back-1):
		a = dataset[i:(i+look_back), 0]
		dataX.append(a)
		dataY.append(dataset[i + look_back, 0])
	return np.array(dataX), np.array(dataY)

让我们看一下此函数对数据集第一行的影响(为清楚起见,以非标准化形式显示)。

X		Y
112		118
118		132
132		129
129		121
121		135

如果将这前五行与上一节中列出的原始数据集样本进行比较,你可以在数字中看到 X=t 和 Y=t+1 模式。
让我们使用这个函数来准备用于建模的训练和测试数据集。

# reshape into X=t and Y=t+1
look_back = 1
trainX, trainY = create_dataset(train, look_back)
testX, testY = create_dataset(test, look_back)

LSTM 网络期望输入数据 (X) 以[samples, time steps, features]_的形式提供特定的数组结构。

目前,数据的形式是 [ samples, features ],你将问题定义为每个样本的一个时间步长。**你可以使用numpy.reshape()**将准备好的训练和测试输入数据转换为预期的结构,如下所示:

# reshape input to be [samples, time steps, features]
trainX = np.reshape(trainX, (trainX.shape[0], 1, trainX.shape[1]))
testX = np.reshape(testX, (testX.shape[0], 1, testX.shape[1]))

你现在已准备好针对此问题设计和拟合你的 LSTM 网络。

该网络有一个带 1 个输入的可见层、一个带 4 个 LSTM 块或神经元的隐藏层,以及一个进行单值预测的输出层。默认的 sigmoid 激活函数用于 LSTM 块。该网络训练了 100 个时期,使用的批量大小为 1。

模型拟合后,你可以估计模型在训练和测试数据集上的性能。这将为你提供新模型的比较点。

请注意,你将在计算错误分数之前反转预测,以确保以与原始数据相同的单位(每月千名乘客)报告绩效。

# make predictions
trainPredict = model.predict(trainX)
testPredict = model.predict(testX)
# invert predictions
trainPredict = scaler.inverse_transform(trainPredict)
trainY = scaler.inverse_transform([trainY])
testPredict = scaler.inverse_transform(testPredict)
testY = scaler.inverse_transform([testY])
# calculate root mean squared error
trainScore = np.sqrt(mean_squared_error(trainY[0], trainPredict[:,0]))
print('Train Score: %.2f RMSE' % (trainScore))
testScore = np.sqrt(mean_squared_error(testY[0], testPredict[:,0]))
print('Test Score: %.2f RMSE' % (testScore))

最后,你可以使用模型为训练数据集和测试数据集生成预测,以直观地了解模型的技能。

由于数据集的准备方式,你必须移动预测,以便它们在 x 轴上与原始数据集对齐。准备好数据后,绘制数据,以蓝色显示原始数据集,以绿色显示训练数据集的预测,以红色显示对未见过的测试数据集的预测。

# shift train predictions for plotting
trainPredictPlot = np.empty_like(dataset)
trainPredictPlot[:, :] = np.nan
trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict
# shift test predictions for plotting
testPredictPlot = np.empty_like(dataset)
testPredictPlot[:, :] = np.nan
testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict
# plot baseline and predictions
plt.plot(scaler.inverse_transform(dataset))
plt.plot(trainPredictPlot)
plt.plot(testPredictPlot)
plt.show()

你可以看到该模型在拟合训练数据集和测试数据集方面表现出色。

使用窗口方法进行回归的 LSTM

你还可以对问题进行表述,以便可以使用多个最近的时间步长来预测下一个时间步长。这称为窗口,窗口的大小是可以针对每个问题进行调整的参数。

例如,给定当前时间 (t) 来预测序列 (t+1) 中下一个时间的值,你可以使用当前时间 (t),以及两个先前时间 (t-1 和 t -2) 作为输入变量。当表述为回归问题时,输入变量为 t-2、t-1 和 t,输出变量为 t+1。

上一节中创建的create_dataset()函数允许你通过将look_back参数从 1 增加到 3来创建时间序列问题的这种表述。
使用此公式的数据集示例如下:

X1	X2	X3	Y
112	118	132	129
118	132	129	121
132	129	121	135
129	121	135	148
121	135	148	148

你可以使用更大的窗口大小重新运行上一节中的示例。

运行示例提供以下输出:

Epoch 95/100
92/92 - 0s - loss: 0.0023 - 35ms/epoch - 384us/step
Epoch 96/100
92/92 - 0s - loss: 0.0023 - 36ms/epoch - 389us/step
Epoch 97/100
92/92 - 0s - loss: 0.0024 - 37ms/epoch - 404us/step
Epoch 98/100
92/92 - 0s - loss: 0.0023 - 36ms/epoch - 392us/step
Epoch 99/100
92/92 - 0s - loss: 0.0022 - 36ms/epoch - 389us/step
Epoch 100/100
92/92 - 0s - loss: 0.0022 - 35ms/epoch - 384us/step
3/3 [==============================] - 0s 514us/step
2/2 [==============================] - 0s 533us/step
Train Score: 24.86 RMSE
Test Score: 70.48 RMSE

你可以看到与上一节相比,错误略有增加。窗口大小和网络架构没有调整:这只是一个如何构建预测问题的演示。

用于时间步长回归的 LSTM

你可能已经注意到,LSTM 网络的数据准备包括时间步长。某些序列问题可能每个样本具有不同数量的时间步长。例如,你可能对导致故障点或浪涌点的物理机器进行了测量。每个事件都是导致事件发生的观察样本,这将是时间步长,观察到的变量将是特征。

时间步长提供了另一种表达时间序列问题的方法。与上面的窗口示例一样,你可以将时间序列中的先前时间步长作为输入来预测下一个时间步长的输出。

你可以将它们用作单个输入特征的时间步长,而不是将过去的观察结果表述为单独的输入特征,这确实是对问题更准确的框架。

你可以使用与前面基于窗口的示例相同的数据表示来执行此操作,除非你重塑数据,将列设置为时间步长维度并将特征维度更改回 1。例如:

# reshape input to be [samples, time steps, features]
trainX = np.reshape(trainX, (trainX.shape[0], trainX.shape[1], 1))
testX = np.reshape(testX, (testX.shape[0], testX.shape[1], 1))

具有批次间记忆的 LSTM

LSTM 网络具有能够记住长序列的记忆。通常,在拟合模型时以及每次调用**model.predict()model.evaluate()**时,网络内的状态会在每个训练批次后重置。

通过使 LSTM 层“有状态”,你可以更好地控制何时在 Keras 中清除 LSTM 网络的内部状态。这意味着它可以在整个训练序列上建立一个状态,甚至可以在需要进行预测时保持该状态。

它要求训练数据在拟合网络时不被打乱。它还需要通过调用**model.reset_states()**在每次暴露于训练数据(纪元)后显式重置网络状态。

这意味着你必须创建自己的纪元外循环,并在每个纪元内调用model.fit()model.reset_states()。例如:

for i in range(100):
	model.fit(trainX, trainY, epochs=1, batch_size=batch_size, verbose=2, shuffle=False)
	model.reset_states()

最后,在构建 LSTM 层时,stateful参数必须设置为True。你必须通过设置batch_input_shape参数对批次中的样本数、样本中的时间步数以及时间步中的特征数进行硬编码,而不是指定输入维度。例如:

model.add(LSTM(4, batch_input_shape=(batch_size, time_steps, features), stateful=True))

稍后在评估模型和进行预测时必须使用相同的批量大小。例如:

model.predict(trainX, batch_size=batch_size)

你可以调整前面的时间步示例以使用有状态 LSTM。

运行示例提供以下输出:

...
92/92 - 0s - loss: 0.0024 - 46ms/epoch - 502us/step
92/92 - 0s - loss: 0.0023 - 49ms/epoch - 538us/step
92/92 - 0s - loss: 0.0023 - 47ms/epoch - 514us/step
92/92 - 0s - loss: 0.0023 - 48ms/epoch - 526us/step
92/92 - 0s - loss: 0.0022 - 48ms/epoch - 517us/step
92/92 - 0s - loss: 0.0022 - 48ms/epoch - 521us/step
92/92 - 0s - loss: 0.0022 - 47ms/epoch - 512us/step
92/92 - 0s - loss: 0.0021 - 50ms/epoch - 540us/step
92/92 - 0s - loss: 0.0021 - 47ms/epoch - 512us/step
92/92 - 0s - loss: 0.0021 - 52ms/epoch - 565us/step
92/92 [==============================] - 0s 448us/step
44/44 [==============================] - 0s 383us/step
Train Score: 24.48 RMSE
Test Score: 49.55 RMSE

你确实看到结果比一些好,比其他的差。该模型可能需要更多的模块,并且可能需要训练更多的 epoch 来内化问题的结构。

批次间具有记忆的堆叠式 LSTM

最后,让我们看一下 LSTM 的一大优势:当堆叠到深度网络架构中时,它们可以成功训练。

LSTM 网络可以像堆叠其他层类型一样在 Keras 中堆叠。所需配置的一项补充是,每个后续 LSTM 层之前的 LSTM 层必须返回序列。这可以通过将层上的return_sequences参数设置为True来完成。

你可以将上一节中的有状态 LSTM 扩展为两层,如下所示:

model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True, return_sequences=True))
model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True))

运行该示例会产生以下输出。

...
92/92 - 0s - loss: 0.0016 - 78ms/epoch - 849us/step
92/92 - 0s - loss: 0.0015 - 80ms/epoch - 874us/step
92/92 - 0s - loss: 0.0015 - 78ms/epoch - 843us/step
92/92 - 0s - loss: 0.0015 - 78ms/epoch - 845us/step
92/92 - 0s - loss: 0.0015 - 79ms/epoch - 859us/step
92/92 - 0s - loss: 0.0015 - 78ms/epoch - 848us/step
92/92 - 0s - loss: 0.0015 - 78ms/epoch - 844us/step
92/92 - 0s - loss: 0.0015 - 78ms/epoch - 852us/step
92/92 [==============================] - 0s 563us/step
44/44 [==============================] - 0s 453us/step
Train Score: 20.58 RMSE
Test Score: 55.99 RMSE

这是表明需要额外训练时期的更多证据。

概括

在本文中,你了解了如何使用 Keras 深度学习网络在 Python 中开发用于时间序列预测的 LSTM 循环神经网络。

具体来说,你了解到:

  • 关于国际航线旅客时间序列预测问题
  • 如何为时间序列问题的回归和窗口公式创建 LSTM
  • 如何使用时间序列问题的时间步公式创建 LSTM
  • 如何创建带状态的 LSTM 和带状态的堆叠 LSTM 以学习长序列

猜你喜欢

转载自blog.csdn.net/weixin_38037405/article/details/130461973