本文主要内容是在上一篇文章的基础上,继续完成LSTM神经网络实现中国人口预测项目,上一节介绍了项目的数据处理部分,本节将主要对数据预处理,模型搭建,模型训练,模型预测部分进行开发与演示。
1. 项目回顾
本项目使用PaddlePaddle框架进行机器学习实战,根据指定数据集(中国人口数据集等)使用Paddle框架搭建LSTM神经网络,包括数据预处理、模型构建、模型训练、模型预测、预测结果可视化等。
- 我们将根据中国人口数据集中的多个特征(features),例如:出生人口(万)、中国人均GPA(美元计)、中国性别比例(按照女生=100)、自然增长率(%)等8个特征字段,预测中国未来总人口(万人)这1个标签字段。属于多输入,单输出LSTM神经网路预测范畴。
- LSTM算法是一种重要的目前使用最多的时间序列算法,是一种特殊的RNN(Recurrent Neural Network,循环神经网络),能够学习长期的依赖关系。
2. 数据预处理
2.1 构造窗口
- 因为LSTM网络是由一个个窗口进行滑动去预测的,因此,需要定义窗口函数
- 对于LSTM网络,此数据集中,“总人口(万人)”为目标值
- 在每个窗口后的“总人口(万人)”数据为此窗口的目标值
- 目标值为表中的第3列数据
data[ , 2]
# 窗口划分
def split_windows(data, size):
X = []
Y = []
# X作为数据,Y作为标签
# 滑动窗口,步长为1,构造窗口化数据,每一个窗口的数据标签是窗口末端的“总人口(万人)”
for i in range(len(data) - size):
X.append(data[i:i+size, :])
Y.append(data[i+size, 2])
return np.array(X), np.array(Y)
2.2 划分数据集
- 在训练之前我们还需要划分训练集与测试集样本
- 划分训练集与测试集样本
- 切分比例为: 0.6 :0.4
- 对划分后的数据集进行可视化
all_data = population.values
split_fraction = 0.6
train_split = int(split_fraction * int(population.shape[0])) # 计算切分的训练集大小
train_data = all_data[:train_split, :]
test_data = all_data[train_split:, :]
plt.figure(figsize=(10, 5))
# 数据可视化
plt.plot(np.arange(train_data.shape[0]), train_data[:, 2], label='train data')
plt.plot(np.arange(train_data.shape[0], train_data.shape[0] + test_data.shape[0]), test_data[:, 2], label='test data')
plt.legend()
运行结果如下图所示:
- 数据集共有50条样本
- 经过划分,有30条训练数据,20条测试数据
2.3 归一化并划分窗口
- 使用sklearn库中的最大最小归一化对训练数据与测试数据进行归一化
- 使用之前定义的窗口划分函数对训练集和测试集的特征与目标值进行划分
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaled_train_data = scaler.fit_transform(train_data)
# 使用训练集的最值对测试集归一化,保证训练集和测试集的分布一致性
scaled_test_data = scaler.transform(test_data)
# 训练集测试集划分
window_size = 5
train_X, train_Y = split_windows(scaled_train_data, size=window_size)
test_X, test_Y = split_windows(scaled_test_data, size=window_size)
print('train shape', train_X.shape, train_Y.shape)
print('test shape', test_X.shape, test_Y.shape)
运行结果如下图所示:
3. 模型搭建
3.1 搭建LSTM模型
下面将进行LSTM神经网络模型进行搭建。网络构造大体如下:
- Reshape层:用于将输入数据转换为指定的输入形式。
- 2D Conv层:进行卷积操作,滤波器个数为64,padding设置为same用于获取相同大小的feature map,激活函数为relu。
- Maxpooling层:进行下采样,然后接一个Dropout用于防止过拟合。
- LSTM层:定义了一层LSTM层
- Linear:最后设置一层全连接层进行输出下一时刻的预测值
- 窗口大小:window_size = 5
- 特征数:fea_num = 10
- 批处理大小:batch_size = 5
# 输入的指标维度
window_size = 5
fea_num = 10
batch_size = 5
class CNN_LSTM(paddle.nn.Layer):
def __init__(self, window_size, fea_num):
super().__init__()
self.window_size = window_size
self.fea_num = fea_num
self.conv1 = paddle.nn.Conv2D(in_channels=1, out_channels=64, stride=1, kernel_size=3, padding='same')
self.relu1 = paddle.nn.ReLU()
self.pool = paddle.nn.MaxPool2D(kernel_size=2, stride=1, padding='same')
self.dropout = paddle.nn.Dropout2D(0.3)
self.lstm1 = paddle.nn.LSTM(input_size=64*fea_num, hidden_size=128, num_layers=1, time_major=False)
# self.lstm2 = paddle.nn.LSTM(input_size=128, hidden_size=64, num_layers=1, time_major=False)
self.fc = paddle.nn.Linear(in_features=128, out_features=64)
self.relu2 = paddle.nn.ReLU()
self.head = paddle.nn.Linear(in_features=64, out_features=1)
def forward(self, x):
x = x.reshape([x.shape[0], 1, self.window_size, self.fea_num])
x = self.conv1(x)
x = self.relu1(x)
x = self.pool(x)
x = self.dropout(x)
x = x.reshape([x.shape[0], self.window_size, -1])
x, (h, c) = self.lstm1(x)
# x, (h,c) = self.lstm2(x)
x = x[:,-1,:] # 最后一个LSTM只要窗口中最后一个特征的输出
x = self.fc(x)
x = self.relu2(x)
x = self.head(x)
return x
3.2 查看搭建的网络参数
在搭建完神经网络以后,我们可以使用一下代码对网络结构进行查看:
# 打印网络结构
model = CNN_LSTM(window_size, fea_num)
paddle.summary(model, (5, 5, 10)) # batchsize样本数,窗口大小,特征数量
运行结果如下图所示:
3.3 定义超参数与优化器
下面将定义训练轮数,batchsize,学习率等超参数,然后定义损失函数以及优化器。
# 定义超参数
base_lr = 0.005
BATCH_SIZE = 5
EPOCH = 50
lr_schedual = paddle.optimizer.lr.CosineAnnealingDecay(learning_rate=base_lr, T_max=EPOCH, verbose=True)
loss_fn = nn.MSELoss()
metric = paddle.metric.Accuracy()
opt = paddle.optimizer.Adam(parameters=model.parameters(), learning_rate=lr_schedual, beta1=0.9, beta2=0.999)
def process(data, bs):
l = len(data)
tmp = []
for i in range(0, l, bs):
if i + bs > l:
tmp.append(data[i:].tolist())
else:
tmp.append(data[i:i+bs].tolist())
tmp = np.array(tmp)
return tmp
# 处理训练数据
train_X = process(train_X, 5)
train_Y = process(train_Y, 5)
print(train_X.shape, train_Y.shape)
4. 模型训练
-
在训练过程中打印训练轮数、训练损失
-
保存模型参数到路径
work/cnn_lstm_ep50_lr0.005
-
训练超参数已在前面指定:
- base_lr = 0.005
- BATCH_SIZE = 5
- EPOCH = 50
# 模型训练
for epoch in range(EPOCH):
model.train()
loss_train = 0
for batch_id, data in enumerate(train_X):
label = train_Y[batch_id]
data = paddle.to_tensor(data, dtype='float32')
label = paddle.to_tensor(label, dtype='float32')
label = label.reshape([label.shape[0],1])
y = model(data)
loss = loss_fn(y, label)
opt.clear_grad()
loss.backward()
opt.step()
loss_train += loss.item()
print("[TRAIN]epoch: {}, loss: {:.4f}".format(epoch+1, loss_train))
lr_schedual.step()
# 保存模型参数
paddle.save(model.state_dict(), 'work/cnn_lstm_ep50_lr0.005.params')
paddle.save(lr_schedual.state_dict(), 'work/cnn_lstm_ep50_lr0.005.pdopts')
部分运行结果如下图所示:
- 经过上述开启训练代码,模型就可以训练起来了。
5. 模型预测
经过模型训练,就可以开始模型预测步骤了。
- 使用训练好的模型对测试集数据进行预测
- 因为之前测试集数据经过了归一化,因此我们还需要对预测结果以及测试集真实数据进行反归一化
- 可视化展示测试集的预测标签数据与真实标签数据
# 加载模型
model = CNN_LSTM(window_size, fea_num)
model_dict = paddle.load('work/cnn_lstm_ep150_lr0.005.params')
model.load_dict(model_dict)
test_X = paddle.to_tensor(test_X, dtype='float32')
prediction = model(test_X)
prediction = prediction.cpu().numpy()
prediction = prediction.reshape(prediction.shape[0], )
# 反归一化
scaled_prediction = prediction * (scaler.data_max_[2] - scaler.data_min_[2]) + scaler.data_min_[2]
scaled_true = test_Y * (scaler.data_max_[2] - scaler.data_min_[2]) + scaler.data_min_[2]
# 画图
plt.plot(range(len(scaled_true)), scaled_true, label='true')
plt.plot(range(len(scaled_prediction)), scaled_prediction, label='prediction', marker='*')
plt.legend()
运行结果如下图所示:
- 经过预测,发现模型学到了数据的大体变化趋势
- 因为本数据集样本量太小,模型训练后的效果有限,还需要进一步优化
6. 总结
本文完成了LSTM人口预测项目的数据预处理、模型搭建、模型训练与模型预测阶段。若想进一步提升模型准确率,在今后可以对模型进一步改进,大体方向如下:
- 由于本数据集样本量有限,今后可以选择在样本更充分的数据集进行实验。
- 另外,在今后可以考虑改善网络结构与参数来进一步优化模型。
- 使用不同的归一化手段(标准化)
本文正在参加「金石计划 . 瓜分6万现金大奖」