本文在上一篇文章的基础上,继续进行卷积神经网络(CNN)对土壤湿度数据进行回归预测。本文主要从网络构建、模型训练、模型预测、预测可视化的角度对项目继续完善。
1. 项目回顾
本预测项目属于机器学习范畴,根据多特征数据集(包含土壤不同深度的湿度逐年变化、降水天数逐年变化、土壤降雨量逐年变化等因素)搭建合适的神经网络对未来几年10cm土壤湿度进行预测。
本项目使用PaddlePaddle框架搭建卷积神经网络(CNN)对10cm土壤湿度进行回归预测,主要包括数据处理、数据预处理、模型构建、模型训练、模型预测、可视化结果等过程。
2. 数据集介绍
数据集统计了某草原从2012年1月-2022年3月的土壤数据,包括10cm湿度(kg/m2),土壤蒸发量(W/m2),降水天数字段等等。需要对数据集按照时间顺序进行排序处理方可进行时间序列预测。
- 经过初步分析随着时间变化,土壤湿度同样呈现规律性变化
数据集所包含字段以及该字段的数据类型如下所示:
- 筛选合适的字段进行预测即可
| 10cm湿度(kg/m2) | 40cm湿度(kg/m2) | 100cm湿度(kg/m2) | 200cm湿度(kg/m2) | datetime | 土壤蒸发量(W/m2) | 土壤蒸发量(mm) | month | year | 经度(lon) | 纬度(lat) | 植被指数(NDVI) | 降水量(mm) | 最大单日降水量(mm) | 降水天数 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| float64 | float64 | float64 | float64 | object | float64 | float64 | int64 | int64 | float64 | float64 | float64 | float64 | float64 | int64 |
3. 数据预处理
3.1 划分数据集
- 按照0.8:0.2的比例划分训练集与测试集数据
- 并对划分后的数据集进行可视化
all_data = soil_humidity_10cm.values
split_fraction = 0.8
train_split = int(split_fraction * int(soil_humidity_10cm.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[:, 0], label='train data')
plt.plot(np.arange(train_data.shape[0], train_data.shape[0] + test_data.shape[0]), test_data[:, 0], label='test data')
plt.legend()
运行结果如下图所示:
- 下图将数据集划分成两部分(训练集和测试集)
- 分别以两种颜色来呈现
3.2 归一化
使用最大最小值归一化的方式为训练数据与测试数据进行归一化。
- 使用sklearn库中的最大最小归一化函数MinMaxScaler进行归一化
- 当然,也可以考虑使用标准化
- 将需要归一化的数据进行reshape(-1, 1)处理
- 将其变成n行1列的数组
from sklearn.preprocessing import MinMaxScaler
# 归一化处理
scaler = MinMaxScaler(feature_range=(-1, 1))
train_scal = scaler.fit_transform(train_data.reshape(-1, 1))
test_scal = scaler.fit_transform(test_data.reshape(-1, 1))
3.3 划分卷积窗口与标签值
- 构造函数,指定哪些数据用来卷积,哪些数据是这个卷积运算所对用的标签(label),用于后续训练。
- 指定一个序列的12个连续数据为1个卷积窗,进行CNN预测,得出的值需要与该序列的第13个数据进行比较,从而计算损失。
- 经过一下处理就可以将训练数据构造成由窗口序列和标签值组成的数据
# 转换成 tensor
train_scal = train_scal.reshape(-1)
train_scal = paddle.to_tensor(train_scal, dtype='float32')
window_size = 12
#将数据按window_size一组分段,每次输入一段后,会输出一个预测的值y_pred
#y_pred与每段之后的window_size+1个数据作为对比值,用于计算损失函数
#例如前5个数据为(1,2,3,4,5),取前4个进行CNN预测,得出的值与(5)比较计算loss
#这里使用每组13个数据,最后一个数据作评估值,即window_size=12
def input_data(seq,ws):
out = []
L = len(seq)
for i in range(L-ws):
window = seq[i:i+ws]
label = seq[i+ws:i+ws+1]
out.append((window, label))
return out
train_scal_data = input_data(train_scal,window_size)
# 打印一组数据集
train_scal_data[0]
运行结果如下图所示:
- 打印出一组张量,一个张量分别由12和元素组成的窗口和1个元素组成的标签构成
4. 模型组网
- 一维卷积层(convolution1d layer),根据输入、卷积核、步长(stride)、填充(padding)、空洞大小(dilations)一组参数计算输出特征层大小。
- 网络大体构造如下
- 先经过一维卷积层Conv1D。
- 使用ReLU激活函数对其进行激活。
- 然后经过第1层线性层Linear1。
- 再经过第2层线性层Linear2。
class CNNnetwork(paddle.nn.Layer):
def __init__(self):
super().__init__()
self.conv1d = paddle.nn.Conv1D(1,1,kernel_size=2)
self.relu = paddle.nn.ReLU()
self.Linear1= paddle.nn.Linear(11,50)
self.Linear2= paddle.nn.Linear(50,1)
def forward(self,x):
x = self.conv1d(x)
x = self.relu(x)
x = self.Linear1(x)
x = self.relu(x)
x = self.Linear2(x)
return x
5. 模型训练
- 训练轮数指定100轮
- 训练损失使用mse损失函数
- 随机数种子为666
- 优化器使用Adam优化器
- 学习率设置为:learning_rate=0.001
import time
paddle.seed(666)
model = CNNnetwork()
# 设置损失函数,这里使用的是均方误差损失
criterion = nn.MSELoss()
# 设置优化函数和学习率lr
optimizer = paddle.optimizer.Adam(parameters=model.parameters(), learning_rate=0.001)
# 设置训练周期
epochs = 100
model.train()
start_time = time.time()
for epoch in range(epochs):
for seq, y_train in train_scal_data:
# 每次更新参数前都梯度归零和初始化
optimizer.clear_grad()
# 注意这里要对样本进行reshape,
# 转换成conv1d的input size(batch size, channel, series length)
seq = paddle.reshape(seq, [1, 1,-1])
# seq = seq.reshape(1,1,-1)
seq = paddle.to_tensor(seq, dtype='float32')
y_pred = model(seq)
y_train = paddle.to_tensor(y_train, dtype='float32')
loss = criterion(y_pred, y_train)
loss.backward()
optimizer.step()
print(f'Epoch: {epoch+1:2} Loss: {loss.item():10.8f}')
print(f'\nDuration: {time.time() - start_time:.0f} seconds')
6. 模型预测
- 使用训练集的最后12个序列作为预测的输入
- 将其传入训练后的模型进行预测操作
- 先定义一个空列表y_pred1 = [],用于接收预测出的预测值数据
future = 12
# 选取序列最后12个值开始预测
preds = train_scal_data[-window_size:]
y_pred1 = []
# 设置成eval模式
model.eval()
# 循环的每一步表示向时间序列向后滑动一格
for seq, y_train in preds:
# 每次更新参数前都梯度归零和初始化
# 转换成conv1d的input size(batch size, channel, series length)
seq = paddle.reshape(seq, [1, 1,-1])
# seq = seq.reshape(1,1,-1)
seq = paddle.to_tensor(seq, dtype='float32')
result = model(seq)
y_pred1.append(result)
print("当前预测值:", y_pred1)
y_pred1 = np.array(y_pred1)
y_pred1 = y_pred1.reshape(-1,1)
print("完整预测值:", y_pred1)
7. 预测结果反归一化
- 因为我们之前将训练集数据进行了归一化处理
- 在我们使用训练好的模型进行预测时,输出的结果同样为归一化后的结果
- 要想得到原来的值,需要进行反归一化(逆归一化)处理
# 逆归一化还原真实值
true_predictions = scaler.inverse_transform(y_pred1).reshape(-1, 1)
8. 预测结果可视化
- 我们将原始数据与预测结果放在一张图中进行可视化显示
- 从而观察预测后的结果
# 对比真实值和预测值
plt.figure(figsize=(12,4))
plt.grid(True)
# 绘制真实值
plt.plot(train_data[-window_size:])
# 绘制预测值
plt.plot(true_predictions)
plt.show()
运行结果如下图所示:
- 预测出的数据与真实数据比较相近
- 大体趋势也相同,预测效果还是很客观的
9. 总结
- 本项目使用PaddlePaddle框架搭建卷积神经网络模型完成了回归预测(土壤湿度预测)
- 卷积神经网络进行预测的模型参考较少,因此想自行构建CNN模型做一次尝试
- 在今后,可以尝试使用LSTM或者CNN网络构建多变量传入-单变量传出模型对本数据集进行预测
本文正在参加「金石计划 . 瓜分6万现金大奖」