使用LSTM预测航班人数

郑重声明,文章大部分翻译自:
Time Series Prediction with LSTM Recurrent Neural Networks in Python with Keras

数据: 1949到1960共12年,每年12个月的数据,一共 144 个数据,单位是 1000, 原文数据下载在这里

目标: 预测国际航班未来 1 个月的乘客数

1.导入相应库文件及数据情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#导入相应的库
import numpy
import matplotlib.pyplot as plt
from pandas import read_csv
import math
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.utils import plot_model
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error
from IPython.display import SVG
from keras.utils.vis_utils import model_to_dot

#将数据存储为两个矩阵,一个矩阵的ind位置存储t时刻的值,另一个矩阵存储t+1时刻的值
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 numpy.array(dataX), numpy.array(dataY)

# fix random seed for reproducibility
numpy.random.seed(7)

#读取数据
dataframe = read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3)
dataset = dataframe.values
dataset = dataset.astype('float32')

#查看数据集
print('样本中的前面两个数据: \n',dataset[0:2])
print('整个样本的规模: ',len(dataset))

plt.plot(dataset)
plt.show()

输出:

样本中的前面两个数据:
[[112.] [118.]]
整个样本的规模: 144

数据


2.标准化数据,划分数据

1
2
3
4
5
6
7
8
9
10
11
#LSTM对输入数据的规模很敏感,特别是在使用sigmoid(默认)或tanh激活函数时。
#将数据重新调整到0到1的范围(也称为标准化)可能是一种很好的做法。

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

# 划分训练集与测试集,这里使用67%的原始数据作为训练数据,剩下33%作为测试数据
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('划分数据集后的得到的训练数据和测试数据(训练数据未有标签): ',train.shape,test.shape)

输出:

划分数据集后的得到的训练数据和测试数据(训练数据未有标签): (96, 1) (48, 1)


3.生成样本

1
2
3
4
5
6
7
8
9
10
11
12
# 生成[t,t+look_back]时间间隔和t+look_back时刻的两个矩阵
look_back = 1
trainX, trainY = create_dataset(train, look_back)
testX, testY = create_dataset(test, look_back)

print(trainX[:2], trainY[:2])

# 数据被Reshape成 [samples, time steps, features],这是放入LSTM的shape
trainX = numpy.reshape(trainX, (trainX.shape[0], 1, trainX.shape[1]))
testX = numpy.reshape(testX, (testX.shape[0], 1, testX.shape[1]))

print('构造得到模型的输入数据(训练数据已有标签trainY): ',trainX.shape,testX.shape)

输出:

[[0.01544401] # 第一个月份数据
[0.02702703]] # 第二个月份数据
[0.02702703 0.05405405] # 每个样本在模型上的应该得到的输出

构造得到模型的输入数据(训练数据已有标签trainY): (95, 1, 1) (47, 1, 1)


这里解释下数据为什么这样划分?
前面我们已经说明了,我们是基于历史数据预测下一时刻的数据,但是每次依赖多少历史数据,我们没有说.这个例子的参数look_back=1设置说明历史数据是1,也就是基于前一个月份数据预测下一个月份数据.下面我以第一年的数据说明数据划分情况.

enter description here

当我们基于1个历史数据预测下一个值时,样本划分就像图示的蓝,红框,蓝色框表示输入模型的数据,红色表示希望模型输出的数据(当然只是希望,会有偏差,后面我们用均方根误差来衡量模型真实输出和这个值的差距).蓝,红框在所有的数据上滑动,得到类似上面的数据划分情况.

当然 #007480,你也可以改动这个look_back这个值,基于历史多少数据来预测下一个数据可以自己设定.

注意:本来训练数据和测试数据分别有96,48个,但是经过这样划分后都减少1个,分别为95,47.这是因为最后一个数据没有标签.但是测试数据没有必要这样分,因为他不需要标签,这里分的意思是利用分到的标签用于计算模型在测试数据上的均方根误差. #800014


4.构建LSTM网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#构建LSTM网络
model = Sequential()
model.add(LSTM(4, input_shape=(1, look_back)))
model.add(Dense(1))

#编译训练LSTM网络
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(trainX, trainY, epochs=50, batch_size=1, verbose=1)

#打印模型
model.summary()

#保存模型
SVG(model_to_dot(model,show_shapes=True).create(prog='dot', format='svg'))

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Epoch 1/50
95/95 [==============================] - 2s 18ms/step - loss: 0.0406
Epoch 2/50
95/95 [==============================] - 1s 6ms/step - loss: 0.0199
Epoch 3/50
95/95 [==============================] - 1s 6ms/step - loss: 0.0147
........后面直到50次省略

______________________________________________________________________________________
Layer (type) Output Shape Param #
======================================================================================
lstm_7 (LSTM) (None, 4) 96
______________________________________________________________________________________
dense_7 (Dense) (None, 1) 5
======================================================================================
Total params: 101
Trainable params: 101
Non-trainable params: 0
______________________________________________________________________________________

enter description here


5.查看模型效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 使用已训练的模型进行预测
trainPredict = model.predict(trainX)
testPredict = model.predict(testX)

# 预测的值是[0,1]这样的标准化数据,需要将该值转换回原始值
trainPredict = scaler.inverse_transform(trainPredict)
trainY = scaler.inverse_transform([trainY])
testPredict = scaler.inverse_transform(testPredict)
testY = scaler.inverse_transform([testY])


# 计算预测的均方根误差
trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0]))
print('Train Score: %.2f RMSE' % (trainScore))
testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0]))
print('Test Score: %.2f RMSE' % (testScore))

# 画图:对训练数据的预测
trainPredictPlot = numpy.empty_like(dataset)
trainPredictPlot[:, :] = numpy.nan
trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict


# 画图:对测试数据的预测
testPredictPlot = numpy.empty_like(dataset)
testPredictPlot[:, :] = numpy.nan
#testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict
testPredictPlot[len(trainPredict)+look_back:len(dataset)-1, :] = testPredict

# 显示图片
plt.plot(scaler.inverse_transform(dataset),color='blue',label='Raw data')
plt.plot(trainPredictPlot,color='red',label='Train process')
plt.plot(testPredictPlot,color='green',label='Test process')

#在折线图上显示标签
leg = plt.legend(loc='best', ncol=1, fancybox=True)
leg.get_frame().set_alpha(0.5)

plt.show()

输出:

Train Score: 23.39 RMSE # 训练数据的均方根误差
Test Score: 46.92 RMSE # 测试数据的均方根误差

enter description here

蓝色线是原始数据,红色 是训练数据的预测情况,绿色测试数据的预测情况,红色和绿色线越靠近蓝色线,表示模型对数据拟合能力越好.


6.预测未来的数据

最后一个数据集的下一个月情况没有被预测,现把它拿到后进行预测.

1
2
3
4
5
6
7
8
9
10
11
#测试数据的最后一个数据没有预测,这里补上
finalX = numpy.reshape(test[-1], (1, 1, testX.shape[1]))

#预测得到标准化数据
featruePredict = model.predict(finalX)

#将标准化数据转换为人数
featruePredict = scaler.inverse_transform(featruePredict)

#原始数据是1949-1960年的数据,下一个月是1961年1月份
print('模型预测1961年1月份的国际航班人数是: ',featruePredict)

输出:

模型预测1961年1月份的国际航班人数是: [[430.27188]]


7.扩展

模型有些参数可以自己手动调一下,看看模型在不同参数下的效果(虽然我估计数据量太少,可能调参带来的变化不是很大,但是可以体验调参的过程),下面我就可以调的参数说明:

(1)损失函数现在使用的是mean_squared_error,可以调成别的
(2)优化器是adam,也可以调,甚至对优化器内的参数进行调整(比如学习率)
(3)训练次数是50,可以调低点(因为我看后面模型的损失不下降了)
(4)基于历史多少数据的参数look_back可调,你可以设置为3,5…

全部代码可以在这里找到.