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

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

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

import torch
import torch.nn as nn
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

flight_data  = pd.read_csv('flights.csv')
flight_data.shape #(144, 3)

绘制数据看看大致的走向

fig_size = plt.rcParams["figure.figsize"]
fig_size[0] = 15
fig_size[1] = 5
plt.rcParams["figure.figsize"] = fig_size
plt.title('Month vs Passenger')
plt.ylabel('Total Passengers')
plt.xlabel('Months')
plt.grid(True)
plt.autoscale(axis='x',tight=True)
plt.plot(flight_data['passengers'])

# 提取所有乘客量的数据
all_data = flight_data['passengers'].values.astype(float)
print(all_data)

'''输出为
[112. 118. 132. 129. 121. 135. 148. 148. 136. 119. 104. 118. 115. 126.
 141. 135. 125. 149. 170. 170. 158. 133. 114. 140. 145. 150. 178. 163.
 172. 178. 199. 199. 184. 162. 146. 166. 171. 180. 193. 181. 183. 218.
 230. 242. 209. 191. 172. 194. 196. 196. 236. 235. 229. 243. 264. 272.
 237. 211. 180. 201. 204. 188. 235. 227. 234. 264. 302. 293. 259. 229.
 203. 229. 242. 233. 267. 269. 270. 315. 364. 347. 312. 274. 237. 278.
 284. 277. 317. 313. 318. 374. 413. 405. 355. 306. 271. 306. 315. 301.
 356. 348. 355. 422. 465. 467. 404. 347. 305. 336. 340. 318. 362. 348.
 363. 435. 491. 505. 404. 359. 310. 337. 360. 342. 406. 396. 420. 472.
 548. 559. 463. 407. 362. 405. 417. 391. 419. 461. 472. 535. 622. 606.
 508. 461. 390. 432.]
'''

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

#将数据区分为训练数据和测试数据
test_data_size = 12
train_data = all_data[:-test_data_size]
test_data = all_data[-test_data_size:]

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

# 由于训练数据存在相差较大的,因此使用min/max尺度变换对训练数据进行归一化
# 注意只对训练数据进行归一化,为了防止有些信息从训练数据泄露到的测试数据
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler(feature_range=(-1, 1))
train_data_normalized = scaler.fit_transform(train_data .reshape(-1, 1))

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

#现在可以看到所有训练值都在[-1,1]的范围内
print(train_data_normalized[:5])
print(train_data_normalized[-5:])

'''输出
[[-0.96483516]
 [-0.93846154]
 [-0.87692308]
 [-0.89010989]
 [-0.92527473]]
[[1.        ]
 [0.57802198]
 [0.33186813]
 [0.13406593]
 [0.32307692]]
'''

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

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

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

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

 定义sequence length 为12

train_window = 12
train_inout_seq = create_inout_sequences(train_data_normalized, train_window)

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

class LSTM(nn.Module):
    def __init__(self, input_size=1, hidden_size=100, output_size=1):
        super().__init__()
        self.hidden_size = hidden_size
        # 定义lstm 层
        self.lstm = nn.LSTM(input_size, hidden_size)
        # 定义线性层,即在LSTM的的输出后面再加一个线性层
        self.linear = nn.Linear(hidden_size, output_size)
    
    # input_seq参数表示输入sequence
    def forward(self, input_seq):
        
        # lstm默认的输入X是(sequence_legth,bacth_size,input_size)
        lstm_out, self.hidden_cell = self.lstm(input_seq.view(len(input_seq) ,1, -1), self.hidden_cell)
        
        # lstm_out的默认大小是(sequence_legth,bacth_size,hidden_size)
        # 转化之后lstm_out的大小是(sequence_legth, bacth_size*hidden_size)
        predictions = self.linear(lstm_out.view(len(input_seq), -1))
        
        # 由于bacth_size = 1, 可知predictions 的维度为(sequence_legth, output_size)
        # [-1] 表示的是取最后一个时间步长对应的输出
        return predictions[-1]

''' 
# 另一种常见的lstm_out的变换方式是将其转换为(sequence_legth* batch_size, num_hiddens)
即 
   predictions = self.linear(lstm_out.view(-1, lstm_out.shape[-1]))
   return predictions[-1]
但是,当batch_size!= 1 时,这种转换在这里就不合适了,因为这里的predictions[-1] 对应的并不是最后一个步长的输出。这种转换更常用于预测每个步长都有对应输出的情况,如http://tangshusen.me/Dive-into-DL-PyTorch/#/chapter06_RNN/6.4_rnn-scratch 中的 “6.4.7 定义模型训练函数” 对应的就是这种情况。

'''        

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

# 模型实例化并定义损失函数和优化函数
model = LSTM()
loss_function = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
print(model)

'''输出
LSTM(
  (lstm): LSTM(1, 100)
  (linear): Linear(in_features=100, out_features=1, bias=True)
)
'''

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

epochs = 150
for i in range(epochs):
    for seq, labels in train_inout_seq:
        optimizer.zero_grad()
        # 下面这一步是对隐含状态以及细胞转态进行初始化
        # 这一步是必须的,否则会报错 "RuntimeError: Trying to backward through the graph a     
        # second time, but the buffers have already been freed"
        model.hidden_cell = (torch.zeros(1, 1, model.hidden_size),
                           torch.zeros(1, 1, model.hidden_size))
        y_pred = model(seq)
        single_loss = loss_function(y_pred, labels)
        single_loss.backward()
        optimizer.step()

    if i%25 == 1:
        print(f'epoch: {i:3} loss: {single_loss.item():10.8f}')

print(f'epoch: {i:3} loss: {single_loss.item():10.10f}')

'''输出
epoch:   1 loss: 0.00139548
epoch:  26 loss: 0.01831282
epoch:  51 loss: 0.00002390
epoch:  76 loss: 0.00029238
epoch: 101 loss: 0.00020424
epoch: 126 loss: 0.00000069
epoch: 149 loss: 0.0001339361
'''

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

# 以train data的最后12个数据进行预测
fut_pred = 12
test_inputs = train_data_normalized[-train_window:].tolist()
print(test_inputs)

'''输出
[0.12527473270893097, 0.04615384712815285, 0.3274725377559662, 0.2835164964199066, 0.3890109956264496, 0.6175824403762817, 0.9516483545303345, 1.0, 0.5780220031738281, 0.33186814188957214, 0.13406594097614288, 0.32307693362236023]
'''

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

model.eval()
#基于最后12个数据来预测第133个数据,并基于新的预测数据进一步预测
# 134-144 的数据
for i in range(fut_pred):
    seq = torch.FloatTensor(test_inputs[-train_window:])
    # 模型评价时候关闭梯度下降
    with torch.no_grad():
        model.hidden = (torch.zeros(1, 1, model.hidden_size),
                        torch.zeros(1, 1, model.hidden_size))
        test_inputs.append(model(seq).item())

test_inputs[fut_pred:]

'''输出
LSTM(
  (lstm): LSTM(1, 100)
  (linear): Linear(in_features=100, out_features=1, bias=True)
)

[0.5331172943115234,
 0.5856705904006958,
 0.8344773650169373,
 1.0538733005523682,
 1.2378138303756714,
 1.3108998537063599,
 1.1621872186660767,
 1.4777841567993164,
 1.5506138801574707,
 1.7615976333618164,
 1.7539771795272827,
 1.5737073421478271]

'''

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

# 通过反向尺度变换,将预测数据抓换成非归一化的数据
actual_predictions = scaler.inverse_transform(np.array(test_inputs[train_window:] ).reshape(-1, 1))
print(actual_predictions)

'''输出
[[452.78418446]
 [464.74005932]
 [521.34360054]
 [571.25617588]
 [613.10264641]
 [629.72971672]
 [595.89759225]
 [667.69589567]
 [684.26465774]
 [732.26346159]
 [730.52980834]
 [689.51842034]]
'''

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

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

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

flight数据下载连接

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

  • 18
    点赞
  • 128
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
下面是一个利用LSTM模型预测变量时间序列的Python代码示例: ```python import numpy as np import pandas as pd from keras.models import Sequential from keras.layers import Dense from keras.layers import LSTM # 加载数据集 data = pd.read_csv('time_series_data.csv', header=0, parse_dates=[0], index_col=0, squeeze=True) # 将数据集转换为监督学习问题 def create_dataset(dataset, look_back=1): X, Y = [], [] for i in range(len(dataset)-look_back-1): a = dataset[i:(i+look_back), 0] X.append(a) Y.append(dataset[i + look_back, 0]) return np.array(X), np.array(Y) # 数据预处理 dataset = data.values dataset = dataset.astype('float32') # 分割训练集和测试集 train_size = int(len(dataset) * 0.67) test_size = len(dataset) - train_size train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] # 创建监督学习问题 look_back = 3 trainX, trainY = create_dataset(train, look_back) testX, testY = create_dataset(test, look_back) # 将输入重塑为 [样本数, 时间步, 特征数] 的形状 trainX = np.reshape(trainX, (trainX.shape[0], 1, trainX.shape[1])) testX = np.reshape(testX, (testX.shape[0], 1, testX.shape[1])) # 创建并拟合LSTM模型 model = Sequential() model.add(LSTM(4, input_shape=(1, look_back))) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') model.fit(trainX, trainY, epochs=100, batch_size=1, verbose=2) # 用训练好的模型预测测试集 trainPredict = model.predict(trainX) testPredict = model.predict(testX) # 反转缩放数据 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)) ``` 代码中的数据集是一个变量时间序列,其中每一行代表了一个时间点上的观测值。我们首先将数据集转化为监督学习问题,然后进行数据预处理,包括归一化和分割训练集和测试集。接下来,我们将输入重塑为 [样本数, 时间步, 特征数] 的形状,并创建一个简LSTM模型。我们用训练集拟合模型,并用测试集进行预测。最后,我们计算模型的误差,并输出训练集和测试集的RMSE评分。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值