数据集
数据集来自 flights.csv,从1949年1月到1960年12月,美国航空每月的乘客数量,在我上传的资源里边可以获取,改天给弄到个人的Github里去。
数据集如下:
https://download.csdn.net/download/rongsenmeng2835/12621779
目标
创建RNN模型,对乘客数量进行预测。比如70%的训练集,对未来的30%月份进行预测。
使用RNN模型进行训练
包导入、定义超参、数据加载
先将数据读取进来:
import numpy as np
import pandas as pd
import torch
from torch import nn
import matplotlib.pyplot as plt
#定义超参数
EPOCH = 1000
LR = 0.01
#数据加载
data = pd.read_csv('flight/flights.csv')
dataset_ori = data['passengers'].values.astype('float32')
#print(data)
#print(dataset_ori)
数据预处理
先将数据进行尺度变换:
max_value = np.max(dataset_ori)
min_value = np.min(dataset_ori)
#尺子
scalar = max_value-min_value
#缩放
dataset = list(map(lambda x:x/scalar,dataset_ori))
print(dataset)
再将数据转化为可被训练的数据集格式,我们认为每个月乘客数量在时间序列上存在先后关系,以前后两个月的乘客数量作为某一时刻的输入和输出,组合成我们要训练的数据集,此处转化后数据还是numpy.ndarray的类型:
#数据转换
#利用前两个月的数据预测后一个月的数据
'''
look_back: 过去多少个月的乘客数
dataX: 生成的数据集X
dataY: 下一个月的乘客数
'''
def create_dataset(dataset,look_back=2):
dataX,dataY =[],[]
for i in range(len(dataset)-look_back):
a = dataset[i:(i+look_back)]
dataX.append(a)
dataY.append(dataset[i+look_back])
return np.array(dataX),np.array(dataY)
#创建输入输出
data_X,data_Y = create_dataset(dataset)
print(data_X,data_Y)
#data_X_shape:(142, 2),data_Y_shape:(142,)
接下来我们开始划分数据集,并将数据集转换为tensor的格式,以变pytorch训练:
#划分数据集
train_size = int(len(data_X) * 0.7)
train_X = data_X[:train_size]
train_Y = data_Y[:train_size]
'''
改变数据的维度
RNN读入的数据维度是 (batch, seq, feature)
只有一个序列,所以 seq 是 1
feature 代表依据的几个月份,这里定的是两个月份,所以 feature 就是 2
'''
train_X = train_X.reshape(-1,1,2)#batch,seq,feature,-1代表指定后面两个后数据自动切割分配成多少个batch
train_Y = train_Y.reshape(-1,1,1)
#print(train_X)
#print(train_X.shape)=(99, 1, 2)
#print(train_Y)
#print(train_Y.shape)=(99, 1, 1)
#转化为tensor
train_x = torch.from_numpy(train_X)
#type(train_x)=torch.Tensor
train_y = torch.from_numpy(train_Y)
定义网络模型
定义训练要使用的网络模型,使用torch.nn中自带的RNN模型,需要注意各层传入数据的尺寸与维度变换:
class rnn_model(nn.Module):
def __init__(self):
#super() 函数是用于调用父类(超类)的一个方法。rnn_model的父类是nn.Module
super(rnn_model,self).__init__()
#输入是两个月的数据input_size=2,输出是下一个月的数据output_size=1,中间隐藏层的层数任意 指定,指定为4
input_size,hidden_size,output_size =2,4,1
num_layers = 1
#此处模型可以从RNN、LSTM、GRU中任选
self.rnn = nn.RNN(input_size,hidden_size,num_layers)
#self.rnn = nn.LSTM(input_size,hidden_size,num_layers)
#self.rnn = nn.GRU(input_size,hidden_size,num_layers)
self.out = nn.Linear(hidden_size,output_size)
def forward(self,x):
x,_ = self.rnn(x)
#batch,seq,hidden
b,s,h = x.shape
#转化为线性层的输入
x = x.view(b*s,h)
x = self.out(x)
x = x.view(b,s,-1)#(99, 1, 1)
return x
设置使用GPU/CPU
设置使用GPU,并将模型导入:
#设置使用GPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
#使用定义好的RNN
model = rnn_model()
#注意模型和输入数据都需要to device,训练数据后面传入
mode = model.to(device)
定义损失函数及优化器
回归问题损失函数使用MSE,优化器我一般会选用Adam:
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(),lr = LR)
模型训练
模型训练一般包含几个步骤:
- 取出数据及标签并均送入GPU或CPU
- 前向传播
- 计算损失函数
- 清空上一轮梯度
- 反向传播
- 参数更新
- 周期打印loss值或tensorboard数据保存绘图
- 保存模型参数(非必要)
#开始训练
for epoch in range(EPOCH):
#数据送入GPU
var_x = train_x.to(device)
var_y = train_y.to(device)
#前向传播
out = model(var_x)
#计算损失函数,传入的是预测值和真实值
loss = criterion(out,var_y)
#反向传播之前先清空上一轮的梯度,在pytorch里边需要注意这个
optimizer.zero_grad()
#反向传播
loss.backward()
#参数更新
optimizer.step()
#loss值打印
if(epoch+1)%100 == 0:
print('Epoch:{},loss:{:.5f}'.format(epoch+1,loss.item()))
结果如下:
Epoch:100,loss:0.00788
Epoch:200,loss:0.00367
Epoch:300,loss:0.00251
Epoch:400,loss:0.00235
Epoch:500,loss:0.00225
Epoch:600,loss:0.00216
Epoch:700,loss:0.00209
Epoch:800,loss:0.00202
Epoch:900,loss:0.00197
Epoch:1000,loss:0.00192
模型测试与验证
这一块也需要对测试数据进行对应的数据变换,过程一般与模型训练的前半部分类似,由于我们之前对数据进行了尺度变换,测试完成之后为了后面的可视化,我们需要将数据还原回来:
#训练完成后进行测试
# eval()会自动把BN和DropOut固定住,不会取平均,而是用训练好的值
model = model.eval()
#使用全量数据
data_X = data_X.reshape(-1,1,2)
data_X = torch.from_numpy(data_X)
# 测试集的预测结果
pred_test = model(data_X.cuda())
# 改变输出的格式
pred_test = pred_test.view(-1).data.cpu().numpy()
#数据反变换
pred_test = list(map(lambda x: x*scalar,pred_test))
结果可视化
# 画出实际结果和预测的结果
plt.plot(pred_test, 'r', label='prediction')
plt.plot(dataset_ori, 'b', label='real')
#添加图例,可以指定图例名,没有参数时默认上面的标签就是图例名
plt.legend(['Prediction','Real'])
我们得到的结果:
可以看到原始RNN的效果还是很不错的,我们还可以换LSTM和RGU看看效果:
LSTM训练结果:
再看看GRU的:
貌似在我这组超参数(EPOCH=1000,LR=0.01)的情况下,原始RNN训练出来的效果还要更好一点,这里只做简单展示,不作细调。
使用传统线性回归模型
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
#% matplotlib inline
# 加载数据
data = pd.read_csv('flights.csv')
#print(data.head())
dataset_ori = data['passengers'].values.astype('float32')
# 线性回归
from sklearn import linear_model
model = linear_model.LinearRegression()
y = data['passengers']
x = [[x] for x in range(1, len(y)+1)]
#print(x)
#print(y)
train_size = int(len(x) * 0.7)
train_x = x[:train_size]
train_y = y[:train_size]
model.fit(train_x, train_y)
y = model.predict(x)
#print(y)
plt.plot(x, y)
plt.plot(dataset_ori)
plt.show()
结果如下:
或者我们将模型换成多项式的非线性回归模型:
# 非线性回归
from sklearn.preprocessing import PolynomialFeatures
# 0-3次方
poly_reg = PolynomialFeatures(degree=3)
x_poly = poly_reg.fit_transform(x)
print(x_poly)
train_x_poly = x_poly[:train_size]
model = linear_model.LinearRegression()
model.fit(train_x_poly, train_y)
y = model.predict(x_poly)
#print(len(x))
#print(len(y))
plt.plot(x, y)
plt.plot(dataset_ori)
plt.show()
结果如下
小小总结
通过上面的尝试我们不难发现,在处理序列信息时(如此处的时间先后关系),RNN模型相较于传统线性回归和多项式的非线性回归,其优势都十分明显,多一种招数来适应特征各异的数据,这也许就是我们学习RNN的意义所在吧。
加油!跑起来,就有了风!