从零开始实现,LSTM模型进行单变量时间序列预测

        上一篇博客讲了基于LSTM不同类型的时间预测,这篇文档使用pytorch 动手实现如何基于LSTM模型单变量时间预测。同样使用sns flight(数据网盘下载链接见文末) 作为数据源,这里将数据下载下来存放在本机中。

首先读取存储在本机中的flights.csv数据:


   
   
  1. import torch
  2. import torch.nn as nn
  3. import numpy as np
  4. import pandas as pd
  5. import matplotlib.pyplot as plt
  6. %matplotlib inline
  7. flight_data = pd.read_csv('flights.csv')
  8. flight_data.shape #( 144, 3)

绘制数据看看大致的走向


   
   
  1. fig_ size = plt.rcParams[ "figure.figsize"]
  2. fig_ size[ 0] = 15
  3. fig_ size[ 1] = 5
  4. plt.rcParams[ "figure.figsize"] = fig_ size
  5. plt.title( 'Month vs Passenger')
  6. plt.ylabel( 'Total Passengers')
  7. plt.xlabel( 'Months')
  8. plt.grid( True)
  9. plt.autoscale(axis = 'x',tight = True)
  10. plt.plot(flight_ data[ 'passengers'])


   
   
  1. # 提取所有乘客量的数据
  2. all_data = flight_data[ 'passengers'].values.astype( float)
  3. print(all_data)
  4. '''输出为
  5. [112. 118. 132. 129. 121. 135. 148. 148. 136. 119. 104. 118. 115. 126.
  6. 141. 135. 125. 149. 170. 170. 158. 133. 114. 140. 145. 150. 178. 163.
  7. 172. 178. 199. 199. 184. 162. 146. 166. 171. 180. 193. 181. 183. 218.
  8. 230. 242. 209. 191. 172. 194. 196. 196. 236. 235. 229. 243. 264. 272.
  9. 237. 211. 180. 201. 204. 188. 235. 227. 234. 264. 302. 293. 259. 229.
  10. 203. 229. 242. 233. 267. 269. 270. 315. 364. 347. 312. 274. 237. 278.
  11. 284. 277. 317. 313. 318. 374. 413. 405. 355. 306. 271. 306. 315. 301.
  12. 356. 348. 355. 422. 465. 467. 404. 347. 305. 336. 340. 318. 362. 348.
  13. 363. 435. 491. 505. 404. 359. 310. 337. 360. 342. 406. 396. 420. 472.
  14. 548. 559. 463. 407. 362. 405. 417. 391. 419. 461. 472. 535. 622. 606.
  15. 508. 461. 390. 432.]
  16. '''

可知,共有144个数据,我们将前132个数据作为训练集,后12个数据作为测试集


   
   
  1. #将数据区分为训练数据和测试数据
  2. test_ data_ size = 12
  3. train_ data = all_ data[:- test_ data_ size]
  4. test_ data = all_ data[- test_ data_ size:]

        根据数据绘制结果,可以发现数据呈现较快的增长趋势,范围跨度也比较大,为便于训练,我们将训练数据进行归一化处理,归一化的方法是min/max方法,该方法归一化后的数据值域为[-1, 1]。这里注意,我们仅仅对训练数据进行归一化而未考虑测试数据,这样做的目的为了防止信息从训练数据泄露到的测试数据。


   
   
  1. # 由于训练数据存在相差较大的,因此使用min /max尺度变换对训练数据进行归一化
  2. # 注意只对训练数据进行归一化,为了防止有些信息从训练数据泄露到的测试数据
  3. from sklearn.preprocessing import MinMaxScaler
  4. scaler = MinMaxScaler(feature_range =(- 1, 1))
  5. train_ data_normalized = scaler.fit_transform(train_ data .reshape(- 1, 1))

现在我们看看归一化以后的数据,全部位于[-1,1]区间内: 


   
   
  1. #现在可以看到所有训练值都在[-1,1]的范围内
  2. print(train_data_normalized[: 5])
  3. print(train_data_normalized[- 5:])
  4. '''输出
  5. [[-0.96483516]
  6. [-0.93846154]
  7. [-0.87692308]
  8. [-0.89010989]
  9. [-0.92527473]]
  10. [[1. ]
  11. [0.57802198]
  12. [0.33186813]
  13. [0.13406593]
  14. [0.32307692]]
  15. '''

 然后将数据转化为tensor 以便于训练


   
   
  1. # 将数据转换为torch tensor
  2. train_ data_normalized = torch.FloatTensor(train_ data_normalized).view(- 1)

        这里我们的目的是使用LSTM进行时间序列预测,那么我们首先需要明确输入LSTM的时间序列的步长,由于我们输出的是多个年份的数据、且数据以月为单位,所以可以很自然想到使用12作为我们输入时间序列长度。通过当前12个数据预测第13个数据的值。以sequence length 为参数,我们构建一个函数来提取时间序列的输入输出:


   
   
  1. # 由于我们使用的是以月为单位的乘客数据,所以很自然想到的是
  2. # 使用 12 作为一个时间序列,我们使用 12个月的数据来预测第 13个月的乘客量
  3. # 定义如下函数来构建输入输出
  4. # @tw 为 sequence length
  5. def create_inout_sequences( input_ data, tw):
  6. inout_seq = []
  7. L = len( input_ data)
  8. for i in range(L-tw):
  9. train_seq = input_ data[i:i +tw]
  10. train_label = input_ data[i +tw:i +tw + 1]
  11. inout_seq.append((train_seq ,train_label))
  12. return inout_seq

 定义sequence length 为12


   
   
  1. train_window = 12
  2. train_inout_seq = create_inout_sequences(train_ data_normalized, train_window)

        接下来我们定义LSTM模型,注意LSTM模型的输出大小为(sequence_legth,bacth_size,hidden_size),其中输出指的是隐藏层在各个时间步上计算并输出的隐藏状态,它们通常作为后续输出层的输入。需要强调的是,该“输出”本身并不涉及输出层计算,形状为(时间步数, 批量大小, 隐藏单元个数)。所以这里在输出后面再加一个线性层来获得最终输出。


   
   
  1. class LSTM(nn.Module):
  2. def __init__( self, input_ size = 1, hidden_ size = 100, output_ size = 1):
  3. super().__init__()
  4. self.hidden_ size = hidden_ size
  5. # 定义lstm 层
  6. self.lstm = nn.LSTM( input_ size, hidden_ size)
  7. # 定义线性层,即在LSTM的的输出后面再加一个线性层
  8. self.linear = nn.Linear(hidden_ size, output_ size)
  9. # input_seq参数表示输入 sequence
  10. def forward( self, input_seq):
  11. # lstm默认的输入X是( sequence_legth,bacth_ size, input_ size
  12. lstm_out, self.hidden_cell = self.lstm( input_seq.view(len( input_seq) , 1, - 1), self.hidden_cell)
  13. # lstm_out的默认大小是( sequence_legth,bacth_ size,hidden_ size
  14. # 转化之后lstm_out的大小是( sequence_legth, bacth_ size *hidden_ size)
  15. predictions = self.linear(lstm_out.view(len( input_seq), - 1))
  16. # 由于bacth_ size = 1, 可知predictions 的维度为( sequence_legth, output_ size)
  17. # [- 1] 表示的是取最后一个时间步长对应的输出
  18. return predictions[- 1]
  19. '' '
  20. # 另一种常见的lstm_out的变换方式是将其转换为(sequence_legth* batch_size, num_hiddens)
  21. predictions = self.linear(lstm_out.view(-1, lstm_out.shape[-1]))
  22. return predictions[-1]
  23. 但是,当batch_size!= 1 时,这种转换在这里就不合适了,因为这里的predictions[-1] 对应的并不是最后一个步长的输出。这种转换更常用于预测每个步长都有对应输出的情况,如http://tangshusen.me/Dive-into-DL-PyTorch/#/chapter06_RNN/6.4_rnn-scratch 中的 “6.4.7 定义模型训练函数” 对应的就是这种情况。
  24. ' ''

接下来,定义模型实例、损失函数、优化函数


   
   
  1. # 模型实例化并定义损失函数和优化函数
  2. model = LSTM()
  3. loss_function = nn.MSELoss()
  4. optimizer = torch.optim.Adam(model.parameters(), lr= 0.001)
  5. print(model)
  6. '''输出
  7. LSTM(
  8. (lstm): LSTM(1, 100)
  9. (linear): Linear(in_features=100, out_features=1, bias=True)
  10. )
  11. '''

训练模型,设置迭代的次数为150, 这里的batch为1,所以我们取train_inout_seq的每一行数据即为一个batch


   
   
  1. epochs = 150
  2. for i in range(epochs):
  3. for seq, labels in train_inout_seq:
  4. optimizer.zero_grad()
  5. # 下面这一步是对隐含状态以及细胞转态进行初始化
  6. # 这一步是必须的,否则会报错 "RuntimeError: Trying to backward through the graph a
  7. # second time, but the buffers have already been freed"
  8. model.hidden_cell = (torch.zeros( 1, 1, model.hidden_size),
  9. torch.zeros( 1, 1, model.hidden_size))
  10. y_pred = model(seq)
  11. single_loss = loss_function(y_pred, labels)
  12. single_loss.backward()
  13. optimizer.step()
  14. if i% 25 == 1:
  15. print( f'epoch: {i:3} loss: {single_loss.item():10.8f}')
  16. print( f'epoch: {i:3} loss: {single_loss.item():10.10f}')
  17. '''输出
  18. epoch: 1 loss: 0.00139548
  19. epoch: 26 loss: 0.01831282
  20. epoch: 51 loss: 0.00002390
  21. epoch: 76 loss: 0.00029238
  22. epoch: 101 loss: 0.00020424
  23. epoch: 126 loss: 0.00000069
  24. epoch: 149 loss: 0.0001339361
  25. '''

提取train data 中最后12 个数据,作为预测的输入


   
   
  1. # 以train data的最后12个数据进行预测
  2. fut_pred = 12
  3. test_inputs = train_data_normalized[-train_window:].tolist()
  4. print(test_inputs)
  5. '''输出
  6. [0.12527473270893097, 0.04615384712815285, 0.3274725377559662, 0.2835164964199066, 0.3890109956264496, 0.6175824403762817, 0.9516483545303345, 1.0, 0.5780220031738281, 0.33186814188957214, 0.13406594097614288, 0.32307693362236023]
  7. '''

        进行模型的预测,首先我们将模型设置为模型评价模式防止出现模型预测不一致的情况(比如说采用了dropout 层)。可以看到,下面预测基本思路是将新的预测值加入到数据集当中用于进一步的预测,我们最开始使用是第121-132个值去预测第133值,然后我们在用第122-133去预测第134个值,依次类推直到第144个值被预测。


   
   
  1. model.eval()
  2. #基于最后 12个数据来预测第 133个数据,并基于新的预测数据进一步预测
  3. # 134- 144 的数据
  4. for i in range(fut_pred):
  5. seq = torch.FloatTensor( test_inputs[-train_window:])
  6. # 模型评价时候关闭梯度下降
  7. with torch. no_grad():
  8. model.hidden = (torch. zeros( 1, 1, model.hidden_ size),
  9. torch. zeros( 1, 1, model.hidden_ size))
  10. test_inputs.append(model(seq).item())
  11. test_inputs[fut_pred:]
  12. '' '输出
  13. LSTM(
  14. (lstm): LSTM(1, 100)
  15. (linear): Linear(in_features=100, out_features=1, bias=True)
  16. )
  17. [0.5331172943115234,
  18. 0.5856705904006958,
  19. 0.8344773650169373,
  20. 1.0538733005523682,
  21. 1.2378138303756714,
  22. 1.3108998537063599,
  23. 1.1621872186660767,
  24. 1.4777841567993164,
  25. 1.5506138801574707,
  26. 1.7615976333618164,
  27. 1.7539771795272827,
  28. 1.5737073421478271]
  29. ' ''

        由于输出输入时,我们采用了归一化的操作,所以这里通过反向变换,将预测之后的数据进行转换。


   
   
  1. # 通过反向尺度变换,将预测数据抓换成非归一化的数据
  2. actual_predictions = scaler.inverse_transform(np.array(test_inputs[train_window:] ).reshape(- 1, 1))
  3. print(actual_predictions)
  4. '''输出
  5. [[452.78418446]
  6. [464.74005932]
  7. [521.34360054]
  8. [571.25617588]
  9. [613.10264641]
  10. [629.72971672]
  11. [595.89759225]
  12. [667.69589567]
  13. [684.26465774]
  14. [732.26346159]
  15. [730.52980834]
  16. [689.51842034]]
  17. '''

 接下来,我们通过可视化看看预测的值和实际的值之间的关系


   
   
  1. # 绘制图像查看预测的[ 133- 144]的数据和实际的 133- 144 之间的数据差别
  2. x = np.arange( 132, 144, 1)
  3. print(x)
  4. plt.title( 'Month vs Passenger')
  5. plt.ylabel( 'Total Passengers')
  6. plt.grid( True)
  7. plt.autoscale(axis = 'x', tight = True)
  8. plt.plot(flight_ data[ 'passengers'])
  9. plt.plot(x,actual_predictions)
  10. plt.show()

        可以发现预测的值在总体的趋势上与实际类似,但是在预测数值还存在一定的差距,这里只是为了说明如何使用LSTM进行单变量进行预测,所以不过分追求精度。实际应用中,需要通过调参不断的优化模型来提高预测的精度。

flight数据下载连接

链接:https://pan.baidu.com/s/1p7INoocovFMkUWnv7MGy_g 
提取码:dlcw      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值